From 33c731ef76967f4bd3f6ba8b287ecc0c52412f33 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 23 Jun 2017 15:35:00 +0200 Subject: [PATCH 0001/1249] Initial Colorbleed config for Avalon --- .gitignore | 99 +++ .travis.yml | 27 + colorbleed.bat | 4 + colorbleed/__init__.py | 17 + colorbleed/action.py | 151 +++++ colorbleed/api.py | 41 ++ colorbleed/maya/__init__.py | 37 + colorbleed/maya/commands.py | 637 ++++++++++++++++++ colorbleed/maya/interactive.py | 288 ++++++++ colorbleed/maya/lib.py | 222 ++++++ colorbleed/maya/menu.py | 72 ++ colorbleed/plugin.py | 34 + .../maya/create/colorbleed_animation.py | 39 ++ .../plugins/maya/create/colorbleed_camera.py | 12 + .../plugins/maya/create/colorbleed_groom.py | 9 + .../maya/create/colorbleed_instancer.py | 9 + .../plugins/maya/create/colorbleed_layout.py | 20 + .../plugins/maya/create/colorbleed_look.py | 9 + .../maya/create/colorbleed_mayaascii.py | 9 + .../plugins/maya/create/colorbleed_model.py | 9 + .../maya/create/colorbleed_pointcache.py | 9 + .../plugins/maya/create/colorbleed_rig.py | 17 + .../plugins/maya/create/colorbleed_yetifur.py | 18 + .../plugins/maya/load/load_animation.py | 150 +++++ .../plugins/maya/load/load_historylook.py | 23 + colorbleed/plugins/maya/load/load_look.py | 50 ++ colorbleed/plugins/maya/load/load_model.py | 31 + colorbleed/plugins/maya/load/load_rig.py | 57 ++ .../publish/__collect_instance_per_item.py | 177 +++++ .../maya/publish/__collect_yeti_caches.py | 156 +++++ .../plugins/maya/publish/__extract_layout.py | 81 +++ .../maya/publish/__validate_layout_nodes.py | 91 +++ .../publish/__validate_related_node_ids.py | 83 +++ .../publish/__validate_unique_ids_in_item.py | 167 +++++ colorbleed/plugins/maya/publish/_debug.py | 19 + .../maya/publish/_integrate_cb_asset.py | 85 +++ .../plugins/maya/publish/_integrate_files.py | 47 ++ .../maya/publish/_validate_look_node_ids.py | 41 ++ .../maya/publish/_validate_rig_node_ids.py | 62 ++ .../_validate_rig_pointcache_node_ids.py | 85 +++ ...idate_rig_pointcache_shape_render_stats.py | 86 +++ .../maya/publish/_validate_unique_node_ids.py | 63 ++ .../maya/publish/_validate_units_angular.py | 20 + .../maya/publish/_validate_units_fps.py | 28 + .../maya/publish/_validate_units_linear.py | 20 + .../maya/publish/collect_alembic_attrs.py | 25 + .../maya/publish/collect_current_file.py | 18 + .../plugins/maya/publish/collect_history.py | 40 ++ .../maya/publish/collect_instancer_caches.py | 54 ++ .../maya/publish/collect_instancer_history.py | 63 ++ .../plugins/maya/publish/collect_instances.py | 117 ++++ .../plugins/maya/publish/collect_look.py | 221 ++++++ .../maya/publish/collect_look_textures.py | 135 ++++ .../maya/publish/collect_maya_units.py | 30 + .../plugins/maya/publish/collect_metadata.py | 39 ++ .../plugins/maya/publish/collect_model.py | 21 + .../maya/publish/collect_particles_history.py | 47 ++ .../plugins/maya/publish/extract_alembic.py | 235 +++++++ .../maya/publish/extract_camera_baked.py | 140 ++++ .../maya/publish/extract_camera_raw.py | 46 ++ .../plugins/maya/publish/extract_instancer.py | 144 ++++ .../maya/publish/extract_layout_mayaAscii.py | 38 ++ .../plugins/maya/publish/extract_look.py | 79 +++ .../maya/publish/extract_maya_ascii.py | 35 + .../maya/publish/extract_maya_ascii_raw.py | 49 ++ .../plugins/maya/publish/extract_metadata.py | 21 + .../plugins/maya/publish/extract_model.py | 79 +++ .../plugins/maya/publish/extract_particles.py | 43 ++ .../maya/publish/extract_yeti_nodes.py | 62 ++ .../publish/validate_camera_attributes.py | 67 ++ .../maya/publish/validate_camera_contents.py | 64 ++ .../maya/publish/validate_frame_range.py | 50 ++ .../publish/validate_gpuCache_not_empty.py | 91 +++ .../publish/validate_instance_has_members.py | 27 + .../maya/publish/validate_instance_subset.py | 49 ++ .../publish/validate_instancer_content.py | 74 ++ .../validate_instancer_frame_ranges.py | 151 +++++ .../maya/publish/validate_joints_hidden.py | 97 +++ .../maya/publish/validate_latest_versions.py | 108 +++ .../maya/publish/validate_layout_content.py | 87 +++ .../maya/publish/validate_layout_node_ids.py | 45 ++ .../validate_layout_parent_no_transforms.py | 73 ++ .../publish/validate_layout_shape_node_ids.py | 150 +++++ .../validate_layout_unique_node_ids.py | 65 ++ .../maya/publish/validate_look_contents.py | 27 + ...lidate_look_default_shaders_connections.py | 62 ++ .../publish/validate_look_deformed_shapes.py | 129 ++++ .../validate_look_ignore_color_space.py | 58 ++ .../publish/validate_look_members_node_ids.py | 55 ++ .../publish/validate_look_members_unique.py | 69 ++ .../validate_look_no_default_shaders.py | 85 +++ .../publish/validate_look_node_unique_ids.py | 48 ++ .../maya/publish/validate_maya_units.py | 39 ++ .../maya/publish/validate_mesh_has_uv.py | 96 +++ .../publish/validate_mesh_lamina_faces.py | 34 + .../validate_mesh_no_negative_scale.py | 49 ++ .../publish/validate_mesh_non_manifold.py | 41 ++ .../publish/validate_mesh_non_zero_edge.py | 58 ++ .../publish/validate_mesh_normals_unlocked.py | 53 ++ .../publish/validate_mesh_single_uv_set.py | 123 ++++ .../validate_mesh_vertices_have_edges.py | 89 +++ .../maya/publish/validate_model_content.py | 144 ++++ .../maya/publish/validate_namespace_empty.py | 41 ++ .../maya/publish/validate_no_animation.py | 42 ++ .../publish/validate_no_default_camera.py | 31 + .../maya/publish/validate_no_namespace.py | 54 ++ .../publish/validate_no_null_transforms.py | 78 +++ .../maya/publish/validate_no_unknown_nodes.py | 34 + .../maya/publish/validate_no_vraymesh.py | 24 + .../maya/publish/validate_node_no_ghosting.py | 49 ++ .../maya/publish/validate_nodes_visible.py | 43 ++ .../validate_references_no_failed_edits.py | 114 ++++ .../maya/publish/validate_resources.py | 31 + .../maya/publish/validate_rig_contents.py | 63 ++ ...alidate_rig_pointcache_related_node_ids.py | 108 +++ .../publish/validate_scene_set_workspace.py | 47 ++ .../publish/validate_shape_default_names.py | 88 +++ .../publish/validate_shape_render_stats.py | 65 ++ .../maya/publish/validate_single_assembly.py | 29 + .../maya/publish/validate_step_size.py | 42 ++ .../maya/publish/validate_transfers.py | 49 ++ .../validate_transform_naming_suffix.py | 83 +++ .../maya/publish/validate_transform_zero.py | 64 ++ .../publish/validate_yeti_cache_frames.py | 90 +++ .../validate_yeti_cache_non_publish.py | 43 ++ .../validate_yeti_cache_unique_filenames.py | 81 +++ colorbleed/plugins/publish/collect_comment.py | 11 + colorbleed/plugins/publish/collect_time.py | 12 + colorbleed/plugins/publish/integrate_asset.py | 273 ++++++++ .../plugins/publish/validate_file_saved.py | 15 + 130 files changed, 9353 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 colorbleed.bat create mode 100644 colorbleed/__init__.py create mode 100644 colorbleed/action.py create mode 100644 colorbleed/api.py create mode 100644 colorbleed/maya/__init__.py create mode 100644 colorbleed/maya/commands.py create mode 100644 colorbleed/maya/interactive.py create mode 100644 colorbleed/maya/lib.py create mode 100644 colorbleed/maya/menu.py create mode 100644 colorbleed/plugin.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_animation.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_camera.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_groom.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_instancer.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_layout.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_look.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_mayaascii.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_model.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_pointcache.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_rig.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_yetifur.py create mode 100644 colorbleed/plugins/maya/load/load_animation.py create mode 100644 colorbleed/plugins/maya/load/load_historylook.py create mode 100644 colorbleed/plugins/maya/load/load_look.py create mode 100644 colorbleed/plugins/maya/load/load_model.py create mode 100644 colorbleed/plugins/maya/load/load_rig.py create mode 100644 colorbleed/plugins/maya/publish/__collect_instance_per_item.py create mode 100644 colorbleed/plugins/maya/publish/__collect_yeti_caches.py create mode 100644 colorbleed/plugins/maya/publish/__extract_layout.py create mode 100644 colorbleed/plugins/maya/publish/__validate_layout_nodes.py create mode 100644 colorbleed/plugins/maya/publish/__validate_related_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py create mode 100644 colorbleed/plugins/maya/publish/_debug.py create mode 100644 colorbleed/plugins/maya/publish/_integrate_cb_asset.py create mode 100644 colorbleed/plugins/maya/publish/_integrate_files.py create mode 100644 colorbleed/plugins/maya/publish/_validate_look_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/_validate_rig_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py create mode 100644 colorbleed/plugins/maya/publish/_validate_unique_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/_validate_units_angular.py create mode 100644 colorbleed/plugins/maya/publish/_validate_units_fps.py create mode 100644 colorbleed/plugins/maya/publish/_validate_units_linear.py create mode 100644 colorbleed/plugins/maya/publish/collect_alembic_attrs.py create mode 100644 colorbleed/plugins/maya/publish/collect_current_file.py create mode 100644 colorbleed/plugins/maya/publish/collect_history.py create mode 100644 colorbleed/plugins/maya/publish/collect_instancer_caches.py create mode 100644 colorbleed/plugins/maya/publish/collect_instancer_history.py create mode 100644 colorbleed/plugins/maya/publish/collect_instances.py create mode 100644 colorbleed/plugins/maya/publish/collect_look.py create mode 100644 colorbleed/plugins/maya/publish/collect_look_textures.py create mode 100644 colorbleed/plugins/maya/publish/collect_maya_units.py create mode 100644 colorbleed/plugins/maya/publish/collect_metadata.py create mode 100644 colorbleed/plugins/maya/publish/collect_model.py create mode 100644 colorbleed/plugins/maya/publish/collect_particles_history.py create mode 100644 colorbleed/plugins/maya/publish/extract_alembic.py create mode 100644 colorbleed/plugins/maya/publish/extract_camera_baked.py create mode 100644 colorbleed/plugins/maya/publish/extract_camera_raw.py create mode 100644 colorbleed/plugins/maya/publish/extract_instancer.py create mode 100644 colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py create mode 100644 colorbleed/plugins/maya/publish/extract_look.py create mode 100644 colorbleed/plugins/maya/publish/extract_maya_ascii.py create mode 100644 colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py create mode 100644 colorbleed/plugins/maya/publish/extract_metadata.py create mode 100644 colorbleed/plugins/maya/publish/extract_model.py create mode 100644 colorbleed/plugins/maya/publish/extract_particles.py create mode 100644 colorbleed/plugins/maya/publish/extract_yeti_nodes.py create mode 100644 colorbleed/plugins/maya/publish/validate_camera_attributes.py create mode 100644 colorbleed/plugins/maya/publish/validate_camera_contents.py create mode 100644 colorbleed/plugins/maya/publish/validate_frame_range.py create mode 100644 colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py create mode 100644 colorbleed/plugins/maya/publish/validate_instance_has_members.py create mode 100644 colorbleed/plugins/maya/publish/validate_instance_subset.py create mode 100644 colorbleed/plugins/maya/publish/validate_instancer_content.py create mode 100644 colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py create mode 100644 colorbleed/plugins/maya/publish/validate_joints_hidden.py create mode 100644 colorbleed/plugins/maya/publish/validate_latest_versions.py create mode 100644 colorbleed/plugins/maya/publish/validate_layout_content.py create mode 100644 colorbleed/plugins/maya/publish/validate_layout_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py create mode 100644 colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_contents.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_members_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_members_unique.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py create mode 100644 colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_maya_units.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_has_uv.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py create mode 100644 colorbleed/plugins/maya/publish/validate_model_content.py create mode 100644 colorbleed/plugins/maya/publish/validate_namespace_empty.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_animation.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_default_camera.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_namespace.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_null_transforms.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py create mode 100644 colorbleed/plugins/maya/publish/validate_no_vraymesh.py create mode 100644 colorbleed/plugins/maya/publish/validate_node_no_ghosting.py create mode 100644 colorbleed/plugins/maya/publish/validate_nodes_visible.py create mode 100644 colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py create mode 100644 colorbleed/plugins/maya/publish/validate_resources.py create mode 100644 colorbleed/plugins/maya/publish/validate_rig_contents.py create mode 100644 colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_scene_set_workspace.py create mode 100644 colorbleed/plugins/maya/publish/validate_shape_default_names.py create mode 100644 colorbleed/plugins/maya/publish/validate_shape_render_stats.py create mode 100644 colorbleed/plugins/maya/publish/validate_single_assembly.py create mode 100644 colorbleed/plugins/maya/publish/validate_step_size.py create mode 100644 colorbleed/plugins/maya/publish/validate_transfers.py create mode 100644 colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py create mode 100644 colorbleed/plugins/maya/publish/validate_transform_zero.py create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py create mode 100644 colorbleed/plugins/publish/collect_comment.py create mode 100644 colorbleed/plugins/publish/collect_time.py create mode 100644 colorbleed/plugins/publish/integrate_asset.py create mode 100644 colorbleed/plugins/publish/validate_file_saved.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..c3acdec771 --- /dev/null +++ b/.gitignore @@ -0,0 +1,99 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# Pycharm IDE settings +.idea diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..ee263058c2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: python + +sudo: required +dist: trusty + +python: + - 2.7 + +services: + - docker + +install: + - git clone https://github.com/getavalon/core.git + - docker run --name mindbender-mongo -d mongo + - docker build -t pyblish/mindbender -f Dockerfile-maya2016 . + +script: + - > + PYTHONPATH=$(pwd)/core + docker run + --rm + -v $(pwd):/workspace + --link mindbender-mongo:mongo + -e COVERALLS_REPO_TOKEN + -e TRAVIS_JOB_ID + -e MINDBENDER_MONGO=mongodb://mongo:27017 + pyblish/mindbender diff --git a/colorbleed.bat b/colorbleed.bat new file mode 100644 index 0000000000..3bf3119e6a --- /dev/null +++ b/colorbleed.bat @@ -0,0 +1,4 @@ +:: Set paths to ensure plugins have access to the inhouse tools + +set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cb; +set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cbra; diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py new file mode 100644 index 0000000000..a6d0890f25 --- /dev/null +++ b/colorbleed/__init__.py @@ -0,0 +1,17 @@ +import os +from pyblish import api as pyblish + +PACKAGE_DIR = os.path.dirname(__file__) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") + + +def install(): + publish_path = os.path.join(PLUGINS_DIR, "publish") + + print("Registering global plug-ins..") + pyblish.register_plugin_path(publish_path) + + +def uninstall(): + pyblish.deregister_plugin_path(PUBLISH_PATH) \ No newline at end of file diff --git a/colorbleed/action.py b/colorbleed/action.py new file mode 100644 index 0000000000..aad054044d --- /dev/null +++ b/colorbleed/action.py @@ -0,0 +1,151 @@ +# absolute_import is needed to counter the `module has no cmds error` in Maya +from __future__ import absolute_import + +import pyblish.api + +from maya import cmds + + +def get_errored_instances_from_context(context): + + instances = list() + for result in context.data["results"]: + if result["instance"] is None: + # When instance is None we are on the "context" result + continue + + if result["error"]: + instances.append(result["instance"]) + + return instances + + +class RepairAction(pyblish.api.Action): + """Repairs the action + + To retrieve the invalid nodes this assumes a static `repair(instance)` + method is available on the plugin. + + """ + label = "Repair" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + + if not hasattr(plugin, "repair"): + raise RuntimeError("Plug-in does not have repair method.") + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + for instance in instances: + plugin.repair(instance) + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid nodes in Maya when plug-in failed. + + To retrieve the invalid nodes this assumes a static `get_invalid()` + method is available on the plugin. + + """ + label = "Select invalid" + on = "failed" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes..") + invalid = list() + for instance in instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning("Plug-in returned to be invalid, " + "but has no selectable nodes.") + + # Ensure unique (process each node only once) + invalid = list(set(invalid)) + + if invalid: + self.log.info("Selecting invalid nodes: %s" % ", ".join(invalid)) + cmds.select(invalid, replace=True, noExpand=True) + else: + self.log.info("No invalid nodes found.") + cmds.select(deselect=True) + + +class GenerateUUIDsOnInvalidAction(pyblish.api.Action): + """Generate UUIDs on the invalid nodes in the instance. + + Invalid nodes are those returned by the plugin's `get_invalid` method. + As such it is the plug-in's responsibility to ensure the nodes that + receive new UUIDs are actually invalid. + + Requires: + - currentFile on context + + """ + + label = "Regenerate UUIDs" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + import cbra.lib + import cbra.utils.maya.node_uuid as id_utils + + self.log.info("Finding bad nodes..") + + # Get the errored instances + errored_instances = [] + for result in context.data["results"]: + if result["error"] is not None and result["instance"] is not None: + if result["error"]: + instance = result["instance"] + errored_instances.append(instance) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the nodes from the all instances that ran through this plug-in + invalid = [] + for instance in instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + invalid.extend(invalid_nodes) + + if not invalid: + self.log.info("No invalid nodes found.") + return + + # Ensure unique (process each node only once) + invalid = list(set(invalid)) + + # Parse context from current file + self.log.info("Parsing current context..") + try: + current_file = context.data['currentFile'] + context = cbra.lib.parse_context(current_file) + except RuntimeError, e: + self.log.error("Can't generate UUIDs because scene isn't " + "in new-style pipeline: ".format(current_file)) + raise e + + # Generate and add the ids to the nodes + ids = id_utils.generate_ids(context, invalid) + id_utils.add_ids(ids) + self.log.info("Generated ids on nodes: {0}".format(invalid)) diff --git a/colorbleed/api.py b/colorbleed/api.py new file mode 100644 index 0000000000..21ac9ba409 --- /dev/null +++ b/colorbleed/api.py @@ -0,0 +1,41 @@ +from collections import OrderedDict + +from .plugin import ( + + Extractor, + + ValidatePipelineOrder, + ValidateContentsOrder, + ValidateSceneOrder, + ValidateMeshOrder +) + +# temporary fix, might +from .action import ( + SelectInvalidAction, + GenerateUUIDsOnInvalidAction, + RepairAction +) + + +def merge(*args): + """Helper to merge OrderedDict instances""" + data = OrderedDict() + for arg in args: + for key, value in arg.items(): + data.pop(key, None) + data[key] = value + return data + + +all = [ + "Extractor", + "ValidatePipelineOrder", + "ValidateContentsOrder", + "ValidateSceneOrder", + "ValidateMeshOrder", + + "SelectInvalidAction", + "GenerateUUIDsOnInvalidAction", + "RepairAction" +] diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py new file mode 100644 index 0000000000..7e1da1a190 --- /dev/null +++ b/colorbleed/maya/__init__.py @@ -0,0 +1,37 @@ +import os +import site + +from avalon import api as avalon +from pyblish import api as pyblish + +from . import menu + +PARENT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.path.dirname(PARENT_DIR) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") + +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "maya", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") + + +def install(): + + # add local pipeline library to the paths + site.addsitedir(r"P:\pipeline\dev\git\cb") + site.addsitedir(r"C:\Users\User\Documents\development\cbra") + site.addsitedir(r"C:\Users\User\Documents\development\pyblish-cb") + + pyblish.register_plugin_path(PUBLISH_PATH) + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + + menu.install() + + +def uninstall(): + pyblish.deregister_plugin_path(PUBLISH_PATH) + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + + menu.uninstall() diff --git a/colorbleed/maya/commands.py b/colorbleed/maya/commands.py new file mode 100644 index 0000000000..b31e4abe64 --- /dev/null +++ b/colorbleed/maya/commands.py @@ -0,0 +1,637 @@ +"""Used for scripting + +These are used in other scripts and mostly require explicit input, +such as which specific nodes they apply to. + +For interactive use, see :mod:`interactive.py` + +""" + +import sys + +from maya import cmds + +from . import lib + +if sys.version_info[0] == 3: + basestring = str + +# Flags +LocalSpace = 1 << 0 +WorldSpace = 1 << 1 + + +def auto_connect2(src, dst): + """Connect to `dst` based on what `dst` is and `src` has available + + TODO: Offer optionbox of choices when multiple inputs are possible. + For example, connecting a mesh to a wrap node could either + go to driverMesh, or baseMesh. + + """ + + to_from = { + "mesh": ( + ["mesh", (".outMesh", ".inMesh")], + ), + "nurbsSurface": ( + ["nurbsSurface", (".local", ".create")], + ), + "nurbsCurve": ( + ["nurbsCurve", (".local", ".create")], + ), + "decomposeMatrix": ( + ["transform", (".worldMatrix", ".inputMatrix")], + ), + "transform": ( + [ + "transform", ( + (".translate", ".rotate", ".scale"), + (".translate", ".rotate", ".scale")) + ], + [ + "decomposeMatrix", ( + (".outTranslate", ".outRotate", ".outScale"), + (".translate", ".rotate", ".scale")) + ], + ), + "objectSet": ( + ["dagNode", (".message", ".dagSetMembers")], + ["entity", (".message", ".dnSetMembers")], + ), + } + + support = next( + (to_from[to] for to in to_from + if to in cmds.nodeType(dst, inherited=True)), None + ) + + if not support: + # Guess, based on available inputs, + # what is the closest match + print("Guessing..") + pass + + assert support, "No supported outputs for '%s'" % (cmds.nodeType(src)) + + out_, in_ = next( + (typ for typ in support + if typ[0] in cmds.nodeType(src, inherited=True)), (None, None) + ) + + assert in_ and out_, "No matching attributes found for %s" % src + + if not isinstance(in_, tuple): + in_ = (in_,) + + if not isinstance(out_, tuple): + out_ = (out_,) + + assert len(in_) == len(out_) + + map(lambda io: cmds.connectAttr(src + io[0], + dst + io[1], + force=True), zip(out_, in_)) + + +def auto_connect(src, dst): + """Connect `src` to `dst` via the most likely input and output + + Usage: + >>> # Create cube and transfer mesh into new shape + >>> shape = cmds.createNode("mesh", name="newShape") + >>> transform, generator = cmds.polyCube(name="original") + >>> auto_connect(generator, shape) + >>> cmds.delete(transform) + + """ + + out_ = { + "mesh": ".outMesh", + "nurbsSurface": ".local", + "nurbsCurve": ".local", + "decomposeMatrix": (".outTranslate", + ".outRotate", + ".outScale"), + "transform": (".translate", + ".rotate", + ".scale", + ".visibility") + } + + in_ = { + "mesh": ".inMesh", + "nurbsSurface": ".create", + "nurbsCurve": ".create", + "decomposeMatrix": "inputMatrix", + "transform": (".translate", + ".rotate", + ".scale", + ".visibility"), + "objectSet": ["dnSetMembers", "dgSetMembers"] + } + + try: + in_ = in_[cmds.nodeType(dst)] + except KeyError: + in_ = next((attr for attr in (".input", + ".inputGeometry") + if cmds.objExists(dst + attr)), None) + + try: + out_ = out_[cmds.nodeType(src)] + except KeyError: + out_ = next((attr for attr in (".output", + ".outputGeometry") + if cmds.objExists(src + attr)), None) + + assert in_ and out_, "No matching attributes found for %s" % src + + if not isinstance(in_, tuple): + in_ = (in_,) + + if not isinstance(out_, tuple): + out_ = (out_,) + + assert len(in_) == len(out_) + + map(lambda io: cmds.connectAttr(src + io[0], + dst + io[1], + force=True), zip(out_, in_)) + + +@lib.maintained_selection +def match_transform(src, dst): + """Transform `src` to `dst`, taking worldspace into account + + Arguments: + src (str): Absolute path to source transform + dst (str): Absolute path to destination transform + + """ + + try: + parent = cmds.listRelatives(src, parent=True)[0] + except Exception: + parent = None + + node_decompose = cmds.createNode("decomposeMatrix") + node_multmatrix = cmds.createNode("multMatrix") + + connections = { + dst + ".worldMatrix": node_multmatrix + ".matrixIn[0]", + node_multmatrix + ".matrixSum": node_decompose + ".inputMatrix", + node_decompose + ".outputTranslate": src + ".translate", + node_decompose + ".outputRotate": src + ".rotate", + node_decompose + ".outputScale": src + ".scale", + } + + if parent: + connections.update({ + parent + ".worldInverseMatrix": node_multmatrix + ".matrixIn[1]" + }) + + for s, d in connections.iteritems(): + cmds.connectAttr(s, d, force=True) + + cmds.refresh() + + cmds.delete([node_decompose, node_multmatrix]) + + +def connect_shapes(src, dst): + """Connect geometry of `src` to source geometry of dst + + Arguments: + src (str): Name of source shape + dst (list): Names of destination nodes + + """ + + out_attr = None + + if cmds.nodeType(src) == "mesh": + out_attr = ".outMesh" + + elif cmds.nodeType(src) in ("nurbsSurface", "nurbsCurve"): + out_attr = ".local" + + else: + for wildcard in (".output",): + if cmds.objExists(src + wildcard): + out_attr = wildcard + break + + if not out_attr: + return cmds.warning("Could not detect output of %s" % src) + + for target in dst: + in_attr = None + + if cmds.nodeType(target) == "mesh": + in_attr = ".inMesh" + + elif cmds.nodeType(target) in ("nurbsSurface", "nurbsCurve"): + in_attr = ".create" + + else: + # Support unspecific nodes with common input attributes + for support, wildcard in (("mesh", ".inputPolymesh"), + ("mesh", ".inputMesh"), + ("mesh", ".inputGeometry")): + if cmds.objExists(target + wildcard): + if not cmds.nodeType(src) == support: + cmds.warning("Could not connect: %s -> %s" % (src, + target)) + break + + in_attr = wildcard + break + + if not in_attr: + cmds.warning("Could not detect input of %s" % target) + continue + + try: + cmds.connectAttr(src + out_attr, + target + in_attr, + force=True) + except Exception as e: + cmds.warning("Could not connect: %s%s -> %s%s (%s)" % ( + src, out_attr, + target, in_attr, e) + ) + + +def connect_transform(driver, driven, source=WorldSpace, compensate=False): + """Connect translation, rotation and scale via decomposeMatrix + + Arguments: + driver (str): Absolute path to driver + driven (str): Absolute path to driven + source (str, optional): Either WorldSpace or LocalSpace, + default WorldSpace + compensate (bool, optional): Whether or not to take into account + the current transform, default False. + + Returns: + output (list): Newly created nodes + + """ + + outputattr = ".matrix" if source == LocalSpace else ".worldMatrix[0]" + + assert cmds.objExists(driver), "%s not found" % driver + assert cmds.objExists(driven), "%s not found" % driven + + decompose = driver + "_decompose" + output = [decompose] + + if not cmds.objExists(decompose): + decompose = cmds.createNode("decomposeMatrix", name=decompose) + + if compensate: + + multMatrix = cmds.createNode( + "multMatrix", name=driver + "_multMatrix") + + # Compensate for drivens parentMatrix. + cmds.connectAttr(driver + outputattr, + multMatrix + ".matrixIn[0]") + cmds.connectAttr(driven + ".parentInverseMatrix", + multMatrix + ".matrixIn[1]") + cmds.connectAttr(multMatrix + ".matrixSum", + decompose + ".inputMatrix") + + output.append(multMatrix) + else: + cmds.connectAttr(driver + outputattr, + decompose + ".inputMatrix") + + # Drive driven with compensated driver. + cmds.connectAttr(decompose + ".outputTranslate", driven + ".t") + cmds.connectAttr(decompose + ".outputRotate", driven + ".r") + cmds.connectAttr(decompose + ".outputScale", driven + ".s") + + return output + + +def clone(shape, worldspace=False): + """Clone `shape` + + Arguments: + shape (str): Absolute path to shape + worldspace (bool, optional): Whether or not to consider worldspace + + Returns: + node (str): Newly created clone + + """ + + type = cmds.nodeType(shape) + assert type in ("mesh", "nurbsSurface", "nurbsCurve"), ( + "clone() works on polygonal and nurbs surfaces") + + src, dst = { + "mesh": (".outMesh", ".inMesh"), + "nurbsSurface": (".local", ".create"), + "nurbsCurve": (".local", ".create"), + }[type] + + nodetype = cmds.nodeType(shape) + + name = lib.unique(name=shape.rsplit("|")[-1]) + clone = cmds.createNode(nodetype, name=name) + + cmds.connectAttr(shape + src, clone + dst, force=True) + + if worldspace: + transform = cmds.createNode("transformGeometry", + name=name + "_transformGeometry") + + cmds.connectAttr(shape + src, + transform + ".inputGeometry", force=True) + cmds.connectAttr(shape + ".worldMatrix[0]", + transform + ".transform", force=True) + cmds.connectAttr(transform + ".outputGeometry", + clone + dst, force=True) + + # Assign default shader + cmds.sets(clone, addElement="initialShadingGroup") + + return clone + + +def combine(nodes): + """Produce a new mesh with the contents of `nodes` + + Arguments: + nodes (list): Path to shapes + + """ + + unite = cmds.createNode("polyUnite", n=nodes[0] + "_polyUnite") + + count = 0 + for node in nodes: + # Are we dealing with transforms, or shapes directly? + shapes = cmds.listRelatives(node, shapes=True) or [node] + + for shape in shapes: + try: + cmds.connectAttr(shape + ".outMesh", + unite + ".inputPoly[%s]" % count, force=True) + cmds.connectAttr(shape + ".worldMatrix", + unite + ".inputMat[%s]" % count, force=True) + count += 1 + + except Exception: + cmds.warning("'%s' is not a polygonal mesh" % shape) + + if count: + output = cmds.createNode("mesh", n=nodes[0] + "_combinedShape") + cmds.connectAttr(unite + ".output", output + ".inMesh", force=True) + return output + + else: + cmds.delete(unite) + return None + + +def transfer_outgoing_connections(src, dst): + """Connect outgoing connections from `src` to `dst` + + Connections that cannot be made are ignored. + + Arguments: + src (str): Absolute path to source node + dst (str): Absolute path to destination node + + """ + + for destination in cmds.listConnections(src, + source=False, + plugs=True) or []: + for source in cmds.listConnections(destination, + destination=False, + plugs=True) or []: + try: + cmds.connectAttr(source.replace(src, dst), + destination, force=True) + except RuntimeError: + continue + + +def parent_group(source, transferTransform=True): + """Create and transfer transforms to parent group""" + assert cmds.objExists(source), "%s does not exist" % source + assert cmds.nodeType(source) == "transform", ( + "%s must be transform" % source) + + parent = cmds.listRelatives(source, parent=True) + + if transferTransform: + group = cmds.createNode("transform", n="%s_parent" % source) + match_transform(group, source) + + try: + cmds.parent(source, group) + except Exception: + cmds.warning("Failed to parent child under new parent") + cmds.delete(group) + + if parent: + cmds.parent(group, parent[0]) + + else: + cmds.select(source) + group = cmds.group(n="%s_parent" % source) + + return group + + +def _output_node(source, type, suffix): + newname = lib.unique(name=source.rsplit("_", 1)[0] + suffix) + + node = cmds.createNode(type) + node = [cmds.listRelatives(node, parent=True) or node][0] + node = cmds.rename(node, newname) + + try: + cmds.parent(node, source) + match_transform(node, source) + + except Exception: + cmds.warning("Could not create %s" % node) + cmds.delete(node) + + return node + + +def output_locator(source, suffix="_LOC"): + """Create child locator + + Arguments: + source (str): Parent node + suffix (str): Suffix of output + + """ + + return _output_node(source, "locator", suffix) + + +def output_joint(source, suffix="_JNT"): + """Create child joint + + Arguments: + source (str): Parent node + suffix (str): Suffix of output + + """ + + return _output_node(source, "joint", suffix) + + +def follicle(shape, u=0, v=0, name=""): + """Attach follicle to "shape" at specified "u" and "v" values""" + + type = cmds.nodeType(shape) + assert type in ("mesh", "nurbsSurface"), ( + "follicle() works on polygonal meshes and nurbs") + + src, dst = { + "mesh": (".outMesh", ".inputMesh"), + "nurbsSurface": (".local", ".inputSurface") + }[type] + + follicle = cmds.createNode("follicle", name=name + "Shape") + transform = cmds.listRelatives(follicle, parent=True)[0] + + cmds.setAttr(follicle + ".parameterU", u) + cmds.setAttr(follicle + ".parameterV", v) + + cmds.connectAttr(follicle + ".outTranslate", transform + ".translate") + cmds.connectAttr(follicle + ".outRotate", transform + ".rotate") + cmds.connectAttr(shape + ".worldMatrix[0]", follicle + ".inputWorldMatrix") + cmds.connectAttr(shape + src, follicle + dst, force=True) + + return transform + + +def connect_matching_attributes(source, target): + """Connect matching attributes from source to target + + Arguments: + source (str): Absolute path to node from which to connect + target (str): Target node + + Example: + >>> # Select two matching nodes + >>> source = cmds.createNode("transform", name="source") + >>> target = cmds.createNode("transform", name="target") + >>> cmds.select([source, target], replace=True) + >>> source, target = cmds.ls(selection=True) + >>> connect_matching_attributes(source, target) + + """ + + dsts = cmds.listAttr(target, keyable=True) + for src in cmds.listAttr(source, keyable=True): + if src not in dsts: + continue + + try: + src = "." + src + cmds.connectAttr(source + src, + target + src, + force=True) + except RuntimeError as e: + cmds.warning("Could not connect %s: %s" % (src, e)) + + +def create_ncloth(input_mesh): + """Replace Create nCloth menu item + + This performs the identical option of nCloth -> Create nCloth + with the following changes. + + 1. Input mesh not made intermediate + 2. Current mesh and shape named "currentMesh" + + Arguments: + input_mesh (str): Path to shape + + """ + + assert cmds.nodeType(input_mesh) == "mesh", ( + "%s was not of type mesh" % input_mesh) + + nucleus = cmds.createNode("nucleus", name="nucleus1") + ncloth = cmds.createNode("nCloth", name="nClothShape1") + current_mesh = cmds.createNode("mesh", name="currentMesh") + + cmds.connectAttr(input_mesh + ".worldMesh[0]", ncloth + ".inputMesh") + cmds.connectAttr(ncloth + ".outputMesh", current_mesh + ".inMesh") + cmds.connectAttr("time1.outTime", nucleus + ".currentTime") + cmds.connectAttr("time1.outTime", ncloth + ".currentTime") + cmds.connectAttr(ncloth + ".currentState", nucleus + ".inputActive[0]") + cmds.connectAttr(ncloth + ".startState", nucleus + ".inputActiveStart[0]") + cmds.connectAttr(nucleus + ".outputObjects[0]", ncloth + ".nextState") + cmds.connectAttr(nucleus + ".startFrame", ncloth + ".startFrame") + + # Assign default shader + cmds.sets(current_mesh, addElement="initialShadingGroup") + + return current_mesh + + +def enhanced_parent(child, parent): + if "shape" in cmds.nodeType(child, inherited=True): + cmds.parent(relative=True, shape=True) + else: + cmds.parent(child, parent) + + +def auto_connect_assets(src, dst): + """Attempt to automatically two assets + + Arguments: + src (str): Name of source reference node + dst (str): Name of destination reference node + + Raises: + StopIteration on missing in_SET + + """ + + in_set = None + + for node in cmds.referenceQuery(dst, nodes=True): + if node.endswith("in_SET"): + in_set = node + break + + for input_transform in cmds.sets(in_set, query=True): + mbid = cmds.getAttr(input_transform + ".mbID") + input_shape = cmds.listRelatives(input_transform, shapes=True)[0] + + for output_transform in lib.lsattr("mbID", value=mbid): + + ref = cmds.referenceQuery(output_transform, referenceNode=True) + if ref != src: + continue + + print("Connecting %s -> %s" % (output_transform, input_transform)) + output_shape = cmds.listRelatives(output_transform, shapes=True)[0] + + try: + auto_connect(output_transform, input_transform) + except RuntimeError: + # Already connected + pass + + try: + auto_connect(output_shape, input_shape) + except RuntimeError: + # Already connected + pass diff --git a/colorbleed/maya/interactive.py b/colorbleed/maya/interactive.py new file mode 100644 index 0000000000..740870e0d2 --- /dev/null +++ b/colorbleed/maya/interactive.py @@ -0,0 +1,288 @@ +"""Interactive functionality + +These depend on user selection in Maya, and may be used as-is. They +implement the functionality in :mod:`commands.py`. + +Each of these functions take `*args` as argument, because when used +in a Maya menu an additional argument is passed with metadata about +what state the button was pressed in. None of this data is used here. + +""" + +from maya import cmds, mel +from . import commands, lib + + +def connect_shapes(*args): + """Connect the first selection to the last selection(s)""" + selection = cmds.ls(selection=True) + + src = selection.pop(0) + commands.connect_shapes(src, dst=selection) + + +def combine(*args): + """Combine currently selected meshes + + This differs from the default Maya combine in that it + retains the original mesh and produces a new mesh with the result. + + """ + + commands.combine(cmds.ls(sl=1)) + + +def read_selected_channels(*args): + """Return a list of selected channels in the Channel Box""" + channelbox = mel.eval("global string $gChannelBoxName; " + "$temp=$gChannelBoxName;") + return cmds.channelBox(channelbox, + query=True, + selectedMainAttributes=True) or [] + + +def set_defaults(*args): + """Set currently selected values from channel box to their default value + + If no channel is selected, default all keyable attributes. + + """ + + for node in cmds.ls(selection=True): + selected_channels = read_selected_channels() + for channel in (selected_channels or + cmds.listAttr(node, keyable=True)): + try: + default = cmds.attributeQuery(channel, + node=node, + listDefault=True)[0] + except Exception: + continue + + else: + cmds.setAttr(node + "." + channel, default) + + +def transfer_outgoing_connections(*args): + """Connect outgoing connections from first to second selected node""" + + try: + src, dst = cmds.ls(selection=True) + except ValueError: + return cmds.warning("Select source and destination nodes") + + commands.transfer_outgoing_connections(src, dst) + + +def clone_special(*args): + """Clone in localspace, and preserve user-defined attributes""" + + for transform in cmds.ls(selection=True, long=True): + if cmds.nodeType(transform) != "transform": + cmds.warning("Skipping '%s', not a `transform`" % transform) + continue + + shape = _find_shape(transform) + type = cmds.nodeType(shape) + + if type not in ("mesh", "nurbsSurface", "nurbsCurve"): + cmds.warning("Skipping '{transform}': cannot clone nodes " + "of type '{type}'".format(**locals())) + continue + + cloned = commands.clone(shape, worldspace=False) + new_transform = cmds.listRelatives(cloned, + parent=True, + fullPath=True)[0] + + new_transform = cmds.rename(new_transform, + new_transform.rsplit(":", 1)[-1]) + + for attr in cmds.listAttr(transform, + userDefined=True) or list(): + try: + cmds.addAttr(new_transform, longName=attr, dataType="string") + except Exception: + continue + + value = cmds.getAttr(transform + "." + attr) + cmds.setAttr(new_transform + "." + attr, value, type="string") + + # Connect visibility + cmds.connectAttr(transform + ".visibility", + new_transform + ".visibility") + + +def clone_worldspace(*args): + return _clone(worldspace=True) + + +def clone_localspace(*args): + return _clone(worldspace=False) + + +def _clone(worldspace=False): + """Clone selected objects in viewport + + Arguments: + worldspace (bool): Whether or not to append a transformGeometry to + resulting clone. + + """ + + clones = list() + + for node in cmds.ls(selection=True, long=True): + shape = _find_shape(node) + type = cmds.nodeType(shape) + + if type not in ("mesh", "nurbsSurface", "nurbsCurve"): + cmds.warning("Skipping '{node}': cannot clone nodes " + "of type '{type}'".format(**locals())) + continue + + cloned = commands.clone(shape, worldspace=worldspace) + clones.append(cloned) + + if not clones: + return + + # Select newly created transform nodes in the viewport + transforms = list() + + for clone in clones: + transform = cmds.listRelatives(clone, parent=True, fullPath=True)[0] + transforms.append(transform) + + cmds.select(transforms, replace=True) + + +def _find_shape(element): + """Return shape of given 'element' + + Supports components, meshes, and surfaces + + Arguments: + element (str): Path to component, mesh or surface + + Returns: + str of path if found, None otherwise + + """ + + # Get either shape or transform, based on element-type + node = cmds.ls(element, objectsOnly=True, long=True)[0] + + if cmds.nodeType(node) == "transform": + try: + return cmds.listRelatives(node, shapes=True, fullPath=True)[0] + except IndexError: + return cmds.warning("Could not find shape in %s" % element) + else: + return node + + +def connect_matching_attributes_from_selection(*args): + try: + source, target = cmds.ls(sl=True) + except ValueError: + raise ValueError("Select (1) source and (2) target nodes only.") + + return commands.connect_matching_attributes(source, target) + + +def auto_connect(*args): + """Connect `src` to `dst` via the most likely input and output""" + try: + commands.auto_connect(*cmds.ls(selection=True)) + except TypeError: + cmds.warning("Select only source and destination nodes.") + + +def create_ncloth(): + selection = cmds.ls(selection=True)[0] + + input_mesh = cmds.listRelatives(selection, shapes=True)[0] + current_mesh = commands.create_ncloth(input_mesh) + + # Optionally append suffix + comp = selection.rsplit("_", 1) + suffix = ("_" + comp[-1]) if len(comp) > 1 else "" + + cmds.rename(current_mesh, "currentMesh%sShape" % suffix) + + # Mimic default nCloth command + cmds.hide(selection) + + +def follicle(*args): + supported = ["mesh", "nurbsSurface"] + selection = cmds.ls(sl=1) + + new_follicles = [] + for sel in selection: + uv = lib.uv_from_element(sel) + + geometry_shape = lib.shape_from_element(sel) + geometry_transform = cmds.listRelatives(geometry_shape, parent=True)[0] + + # Figure out output connection + inputs = [".inputMesh", ".inputSurface"] + outputs = [".outMesh", ".local"] + + failed = False + type = cmds.nodeType(geometry_shape) + if type not in supported: + failed = True + shapes = cmds.listRelatives(geometry_shape, shapes=True) + + if shapes: + geometry_shape = shapes[0] + type = cmds.nodeType(geometry_shape) + if type in supported: + failed = False + + if failed: + cmds.error("Skipping '%s': Type not accepted" % type) + return + + input = inputs[supported.index(type)] + output = outputs[supported.index(type)] + + # Make follicle + follicle = cmds.createNode("follicle", + name=geometry_transform + "_follicleShape1") + follicle_transform = cmds.listRelatives(follicle, parent=True)[0] + follicle_transform = cmds.rename(follicle_transform, + geometry_transform + "_follicle1") + + # Set U and V value + cmds.setAttr(follicle + ".parameterU", uv[0]) + cmds.setAttr(follicle + ".parameterV", uv[1]) + + # Make the connections + cmds.connectAttr(follicle + ".outTranslate", + follicle_transform + ".translate") + cmds.connectAttr(follicle + ".outRotate", + follicle_transform + ".rotate") + cmds.connectAttr(geometry_shape + output, + follicle + input) + + # Select last + new_follicles.append(follicle_transform) + + # Select newly created follicles + if new_follicles: + cmds.select(new_follicles, r=1) + + return new_follicles + + +def auto_connect_assets(*args): + references = cmds.ls(selection=True, type="reference") + + if not len(references) == 2: + raise RuntimeError("Select source and destination " + "reference nodes, in that order.") + + return commands.auto_connect_assets(*references) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py new file mode 100644 index 0000000000..9a594e4904 --- /dev/null +++ b/colorbleed/maya/lib.py @@ -0,0 +1,222 @@ +"""Standalone helper functions""" + +import re +import contextlib +from maya import cmds + + +def maintained_selection(arg=None): + if arg is not None: + return _maintained_selection_context() + else: + return _maintained_selection_decorator(arg) + + +def _maintained_selection_decorator(func): + """Function decorator to maintain the selection once called + + Example: + >>> @_maintained_selection + ... def my_function(): + ... # Modify selection + ... cmds.select(clear=True) + ... + >>> # Selection restored + + """ + + def wrapper(*args, **kwargs): + previous_selection = cmds.ls(selection=True) + try: + return func(*args, **kwargs) + finally: + if previous_selection: + cmds.select(previous_selection, + replace=True, + noExpand=True) + else: + cmds.select(deselect=True, + noExpand=True) + + return wrapper + + +@contextlib.contextmanager +def _maintained_selection_context(): + """Maintain selection during context + + Example: + >>> scene = cmds.file(new=True, force=True) + >>> node = cmds.createNode("transform", name="Test") + >>> cmds.select("persp") + >>> with maintained_selection(): + ... cmds.select("Test", replace=True) + >>> "Test" in cmds.ls(selection=True) + False + + """ + + previous_selection = cmds.ls(selection=True) + try: + yield + finally: + if previous_selection: + cmds.select(previous_selection, + replace=True, + noExpand=True) + else: + cmds.select(deselect=True, + noExpand=True) + + +def unique(name): + assert isinstance(name, basestring), "`name` must be string" + + while cmds.objExists(name): + matches = re.findall(r"\d+$", name) + + if matches: + match = matches[-1] + name = name.rstrip(match) + number = int(match) + 1 + else: + number = 1 + + name = name + str(number) + + return name + + +def uv_from_element(element): + """Return the UV coordinate of given 'element' + + Supports components, meshes, nurbs. + + """ + + supported = ["mesh", "nurbsSurface"] + + uv = [0.5, 0.5] + + if "." not in element: + type = cmds.nodeType(element) + if type == "transform": + geometry_shape = cmds.listRelatives(element, shapes=True) + + if len(geometry_shape) >= 1: + geometry_shape = geometry_shape[0] + else: + return + + elif type in supported: + geometry_shape = element + + else: + cmds.error("Could not do what you wanted..") + return + else: + # If it is indeed a component - get the current Mesh + try: + parent = element.split(".", 1)[0] + + # Maya is funny in that when the transform of the shape + # of the component elemen has children, the name returned + # by that elementection is the shape. Otherwise, it is + # the transform. So lets see what type we're dealing with here. + if cmds.nodeType(parent) in supported: + geometry_shape = parent + else: + geometry_shape = cmds.listRelatives(parent, shapes=1)[0] + + if not geometry_shape: + cmds.error("Skipping %s: Could not find shape." % element) + return + + if len(cmds.ls(geometry_shape)) > 1: + cmds.warning("Multiple shapes with identical " + "names found. This might not work") + + except TypeError as e: + cmds.warning("Skipping %s: Didn't find a shape " + "for component elementection. %s" % (element, e)) + return + + try: + type = cmds.nodeType(geometry_shape) + + if type == "nurbsSurface": + # If a surfacePoint is elementected on a nurbs surface + root, u, v = element.rsplit("[", 2) + uv = [float(u[:-1]), float(v[:-1])] + + if type == "mesh": + # ----------- + # Average the U and V values + # =========== + uvs = cmds.polyListComponentConversion(element, toUV=1) + if not uvs: + cmds.warning("Couldn't derive any UV's from " + "component, reverting to default U and V") + raise TypeError + + # Flatten list of Uv's as sometimes it returns + # neighbors like this [2:3] instead of [2], [3] + flattened = [] + + for uv in uvs: + flattened.extend(cmds.ls(uv, flatten=True)) + + uvs = flattened + + sumU = 0 + sumV = 0 + for uv in uvs: + try: + u, v = cmds.polyEditUV(uv, query=True) + except Exception: + cmds.warning("Couldn't find any UV coordinated, " + "reverting to default U and V") + raise TypeError + + sumU += u + sumV += v + + averagedU = sumU / len(uvs) + averagedV = sumV / len(uvs) + + uv = [averagedU, averagedV] + except TypeError: + pass + + return uv + + +def shape_from_element(element): + """Return shape of given 'element' + + Supports components, meshes, and surfaces + + """ + + try: + # Get either shape or transform, based on element-type + node = cmds.ls(element, objectsOnly=True)[0] + except Exception: + cmds.warning("Could not find node in %s" % element) + return None + + if cmds.nodeType(node) == 'transform': + try: + return cmds.listRelatives(node, shapes=True)[0] + except Exception: + cmds.warning("Could not find shape in %s" % element) + return None + + else: + return node + + +def add_attributes(node, data): + + for key, value in data.items(): + cmds.addAttr() \ No newline at end of file diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py new file mode 100644 index 0000000000..07a9ba81ce --- /dev/null +++ b/colorbleed/maya/menu.py @@ -0,0 +1,72 @@ +import sys +from maya import cmds +from avalon.vendor.Qt import QtWidgets, QtCore + +self = sys.modules[__name__] +self._menu = "colorbleed" +self._parent = { + widget.objectName(): widget + for widget in QtWidgets.QApplication.topLevelWidgets() +}.get("MayaWindow") + + +def install(): + from . import interactive + + uninstall() + + def deferred(): + cmds.menu(self._menu, + label="Colorbleed", + tearOff=True, + parent="MayaWindow") + + # Modeling sub-menu + cmds.menuItem("Modeling", + label="Modeling", + tearOff=True, + subMenu=True, + parent=self._menu) + + cmds.menuItem("Combine", command=interactive.combine) + + # Rigging sub-menu + cmds.menuItem("Rigging", + label="Rigging", + tearOff=True, + subMenu=True, + parent=self._menu) + + cmds.menuItem("Auto Connect", command=interactive.auto_connect) + cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) + cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) + cmds.menuItem("Clone (Special)", command=interactive.clone_special) + cmds.menuItem("Create Follicle", command=interactive.follicle) + + # Animation sub-menu + cmds.menuItem("Animation", + label="Animation", + tearOff=True, + subMenu=True, + parent=self._menu) + + cmds.menuItem("Set Defaults", command=interactive.set_defaults) + + cmds.setParent("..", menu=True) + + cmds.menuItem(divider=True) + + cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) + + # Allow time for uninstallation to finish. + QtCore.QTimer.singleShot(100, deferred) + + +def uninstall(): + app = QtWidgets.QApplication.instance() + widgets = dict((w.objectName(), w) for w in app.allWidgets()) + menu = widgets.get(self._menu) + + if menu: + menu.deleteLater() + del(menu) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py new file mode 100644 index 0000000000..5adf7dbe24 --- /dev/null +++ b/colorbleed/plugin.py @@ -0,0 +1,34 @@ +import tempfile +import pyblish.api + +ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 +ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 +ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 +ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 + + +class Extractor(pyblish.api.InstancePlugin): + """Extractor base class. + + The extractor base class implements a "staging_dir" function used to + generate a temporary directory for an instance to extract to. + + This temporary directory is generated through `tempfile.mkdtemp()` + + """ + + order = pyblish.api.ExtractorOrder + + def staging_dir(self, instance): + """Provide a temporary directory in which to store extracted files + + Upon calling this method the staging directory is stored inside + the instance.data['stagingDir'] + """ + staging_dir = instance.data.get('stagingDir', None) + + if not staging_dir: + staging_dir = tempfile.mkdtemp() + instance.data['stagingDir'] = staging_dir + + return staging_dir diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py new file mode 100644 index 0000000000..c2585dd5e5 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -0,0 +1,39 @@ +from collections import OrderedDict + +import avalon.maya +from maya import cmds + + +class CreateAnimation(avalon.maya.Creator): + """THe animated objects in the scene""" + + name = "animationDefault" + label = "Animation" + family = "colorbleed.animation" + + def __init__(self, *args, **kwargs): + super(CreateAnimation, self).__init__(*args, **kwargs) + + # get scene values as defaults + start = cmds.playbackOptions(query=True, animationStartTime=True) + end = cmds.playbackOptions(query=True, animationEndTime=True) + + # build attributes + attributes = OrderedDict() + attributes["startFrame"] = start + attributes["endFrame"] = end + attributes["handles"] = 1 + attributes["step"] = 1.0 + + # Write vertex colors with the geometry. + attributes["writeColorSets"] = False + + # Include only renderable visible shapes. + # Skips locators and empty transforms + attributes["renderableOnly"] = False + + # Include only nodes that are visible at least once during the + # frame range. + attributes["visibleOnly"] = False + + self.data = attributes diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py new file mode 100644 index 0000000000..52f037278c --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -0,0 +1,12 @@ +import avalon.maya + + +class CreateCamera(avalon.maya.Creator): + """Single baked camera extraction""" + + name = "cameraDefault" + label = "Camera" + family = "colorbleed.camera" + + # def process(self): + # pass \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_groom.py b/colorbleed/plugins/maya/create/colorbleed_groom.py new file mode 100644 index 0000000000..6f8d0f72f7 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_groom.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateGroom(avalon.maya.Creator): + """Hair / fur definition for an asset""" + + name = "groomDefault" + label = "Groom" + family = "colorbleed.groom" diff --git a/colorbleed/plugins/maya/create/colorbleed_instancer.py b/colorbleed/plugins/maya/create/colorbleed_instancer.py new file mode 100644 index 0000000000..4721253752 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_instancer.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateInstance(avalon.maya.Creator): + """Maya instancer using cached particles""" + + name = "instanceDefault" + label = "Instance" + family = "colorbleed.instance" diff --git a/colorbleed/plugins/maya/create/colorbleed_layout.py b/colorbleed/plugins/maya/create/colorbleed_layout.py new file mode 100644 index 0000000000..15fa50c97c --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_layout.py @@ -0,0 +1,20 @@ +import avalon.maya + + +class CreateLayout(avalon.maya.Creator): + """The layout of a episode / sequence / shot """ + + name = "layoutDefault" + label = "Layout" + family = "colorbleed.layout" + + def __init__(self, *args, **kwargs): + super(CreateLayout, self).__init__(*args, **kwargs) + from maya import cmds + + self.data.update({ + "startFrame": lambda: cmds.playbackOptions( + query=True, animationStartTime=True), + "endFrame": lambda: cmds.playbackOptions( + query=True, animationEndTime=True), + }) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py new file mode 100644 index 0000000000..1ddc1afead --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateLook(avalon.maya.Creator): + """Polygonal geometry for animation""" + + name = "lookDefault" + label = "Look Dev" + family = "colorbleed.look" \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py new file mode 100644 index 0000000000..2c19e13711 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateMayaAscii(avalon.maya.Creator): + """Raw Maya Ascii file of the item(s)""" + + name = "mayaAscii" + label = "Maya Ascii" + family = "colorbleed.mayaAscii" diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py new file mode 100644 index 0000000000..364d00dc8d --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateModel(avalon.maya.Creator): + """Polygonal geometry for animation""" + + name = "modelDefault" + label = "Model" + family = "colorbleed.model" diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py new file mode 100644 index 0000000000..d8a837c4e1 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreatePointCache(avalon.maya.Creator): + """Alembic extract""" + + name = "pointcache" + label = "Point Cache" + family = "colorbleed.pointcache" \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py new file mode 100644 index 0000000000..4af7eaae3e --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -0,0 +1,17 @@ +import avalon.maya +from maya import cmds + + +class CreateRig(avalon.maya.Creator): + """Skeleton and controls for manipulation of the geometry""" + + name = "rigDefault" + label = "Rig" + family = "colorbleed.rig" + + def process(self): + instance = super(CreateRig, self).process() + + controls = cmds.sets(name="controls_SET", empty=True) + pointcache = cmds.sets(name="pointcache_SET", empty=True) + cmds.sets([controls, pointcache], forceElement=instance) diff --git a/colorbleed/plugins/maya/create/colorbleed_yetifur.py b/colorbleed/plugins/maya/create/colorbleed_yetifur.py new file mode 100644 index 0000000000..b8e523a95c --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_yetifur.py @@ -0,0 +1,18 @@ +import avalon.maya +import colorbleed.api as api + + +class CreateYetiFur(avalon.maya.Creator): + """Cached yeti fur extraction""" + + name = "yetiFur" + label = "Yeti Fur" + family = "colorbleed.yetifur" + + def process(self): + + time_with_handles = api.OrderedDict(startFrame=True, + endFrame=True, + handles=True) + + api.merge() diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py new file mode 100644 index 0000000000..cbb1e6bff5 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -0,0 +1,150 @@ +import pprint +from avalon import api + + +class AbcLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", "colorbleed.camera"] + representations = ["abc"] + + def process(self, name, namespace, context): + from maya import cmds + + cmds.loadPlugin("AbcImport.mll", quiet=True) + # Prevent identical alembic nodes from being shared + # Create unique namespace for the cameras + + nodes = cmds.file(self.fname, + namespace=namespace, + sharedReferenceFile=False, + groupReference=True, + groupName="{}:{}".format(namespace, name), + reference=True, + returnNewNodes=True) + + self[:] = nodes + + +class CurvesLoader(api.Loader): + """Specific loader of Curves for the avalon.animation family""" + + families = ["colorbleed.animation"] + representations = ["curves"] + + def process(self, name, namespace, context): + from maya import cmds + from avalon import maya + + cmds.loadPlugin("atomImportExport.mll", quiet=True) + + rig = context["representation"]["dependencies"][0] + container = maya.load(rig, + name=name, + namespace=namespace, + # Skip creation of Animation instance + post_process=False) + + try: + control_set = next( + node for node in cmds.sets(container, query=True) + if node.endswith("controls_SET") + ) + except StopIteration: + raise TypeError("%s is missing controls_SET") + + cmds.select(control_set) + options = ";".join([ + "", + "", + "targetTime=3", + "option=insert", + "match=hierarchy", + "selected=selectedOnly", + "search=", + "replace=", + "prefix=", + "suffix=", + "mapFile=", + ]) + + with maya.maintained_selection(): + cmds.select( + control_set, + replace=True, + + # Support controllers being embedded in + # additional selection sets. + noExpand=False + ) + + nodes = cmds.file( + self.fname, + i=True, + type="atomImport", + renameAll=True, + namespace=namespace, + options=options, + returnNewNodes=True, + ) + + self[:] = nodes + cmds.sets(container, query=True) + [container] + + def post_process(self, name, namespace, context): + import os + from maya import cmds + from avalon import maya, io + + # Task-dependent post-process + if os.getenv("AVALON_TASK") != "animate": + return self.log.info( + "No animation instance created due to task != animate" + ) + + # Find associated rig to these curves + try: + dependency = context["representation"]["dependencies"][0] + except (KeyError, IndexError): + return self.log.warning("No dependencies found for %s" % name) + + dependency = io.find_one({"_id": io.ObjectId(dependency)}) + _, _, dependency, _ = io.parenthood(dependency) + + # TODO(marcus): We are hardcoding the name "out_SET" here. + # Better register this keyword, so that it can be used + # elsewhere, such as in the Integrator plug-in, + # without duplication. + output = next((node for node in self + if node.endswith("out_SET")), None) + controls = next((node for node in self + if node.endswith("controls_SET")), None) + + assert output, "No out_SET in rig, this is a bug." + assert controls, "No controls_SET in rig, this is a bug." + + with maya.maintained_selection(): + cmds.select([output, controls], noExpand=True) + + dependencies = [context["representation"]["_id"]] + name = "anim{}_".format(dependency["name"].title()) + + # TODO(marcus): Hardcoding the family here, better separate this. + family = [f for f in self.families if f.endswith("animation")] + assert len(family) == 1, ("None or multiple animation " + "families found") + family = family[0] + maya.create( + name=maya.unique_name(name, suffix="_SET"), + family=family, + options={"useSelection": True}, + data={"dependencies": " ".join(str(d) for d in dependencies)}) + + +class HistoryLoader(api.Loader): + """Specific loader of Curves for the avalon.animation family""" + + families = ["colorbleed.animation"] + representations = ["history"] + + def process(self, name, namespace, context): + raise NotImplementedError("Can't load history yet.") diff --git a/colorbleed/plugins/maya/load/load_historylook.py b/colorbleed/plugins/maya/load/load_historylook.py new file mode 100644 index 0000000000..e4c49685cb --- /dev/null +++ b/colorbleed/plugins/maya/load/load_historylook.py @@ -0,0 +1,23 @@ +from maya import cmds +from avalon import api + + +class HistoryLookLoader(api.Loader): + """Specific loader for lookdev""" + + families = ["colorbleed.historyLookdev"] + representations = ["ma"] + + def process(self, name, namespace, context): + from avalon import maya + with maya.maintained_selection(): + nodes = cmds.file( + self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=namespace + ":" + name + ) + + self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py new file mode 100644 index 0000000000..eae0a5b5a2 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_look.py @@ -0,0 +1,50 @@ +import os +import json + +from maya import cmds +from avalon import api + + +class LookLoader(api.Loader): + """Specific loader for lookdev""" + + families = ["colorbleed.lookdev"] + representations = ["ma"] + + def process(self, name, namespace, context): + from avalon import maya + try: + existing_reference = cmds.file(self.fname, + query=True, + referenceNode=True) + except RuntimeError as e: + if e.message.rstrip() != "Cannot find the scene file.": + raise + + self.log.info("Loading lookdev for the first time..") + with maya.maintained_selection(): + nodes = cmds.file( + self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True + ) + else: + self.log.info("Reusing existing lookdev..") + nodes = cmds.referenceQuery(existing_reference, nodes=True) + namespace = nodes[0].split(":", 1)[0] + + # Assign shaders + self.fname = self.fname.rsplit(".", 1)[0] + ".json" + + if not os.path.isfile(self.fname): + self.log.warning("Look development asset " + "has no relationship data.") + return nodes + + with open(self.fname) as f: + relationships = json.load(f) + + maya.apply_shaders(relationships, namespace) + + self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py new file mode 100644 index 0000000000..65dd3e2fd0 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_model.py @@ -0,0 +1,31 @@ +from maya import cmds +from avalon import api + + +class ModelLoader(api.Loader): + """Load models + + Stores the imported asset in a container named after the asset. + + """ + + families = ["colorbleed.model"] + representations = ["ma"] + + def process(self, name, namespace, context): + from avalon import maya + with maya.maintained_selection(): + nodes = cmds.file( + self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=namespace + ":" + name + ) + + # Assign default shader to meshes + meshes = cmds.ls(nodes, type="mesh") + cmds.sets(meshes, forceElement="initialShadingGroup") + + self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py new file mode 100644 index 0000000000..1ccf5184d0 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -0,0 +1,57 @@ +from maya import cmds +from avalon import api + + +class RigLoader(api.Loader): + """Specific loader for rigs + + This automatically creates an instance for animators upon load. + + """ + + families = ["colorbleed.rig"] + representations = ["ma"] + + def process(self, name, namespace, context): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName=namespace + ":" + name) + + # Store for post-process + self[:] = nodes + + def post_process(self, name, namespace, context): + from avalon import maya + + # TODO(marcus): We are hardcoding the name "out_SET" here. + # Better register this keyword, so that it can be used + # elsewhere, such as in the Integrator plug-in, + # without duplication. + output = next( + (node for node in self + if node.endswith("out_SET")), None) + controls = next( + (node for node in self + if node.endswith("controls_SET")), None) + + assert output, "No out_SET in rig, this is a bug." + assert controls, "No controls_SET in rig, this is a bug." + + with maya.maintained_selection(): + cmds.select([output, controls], noExpand=True) + + dependencies = [context["representation"]["_id"]] + asset = context["asset"]["name"] + "_" + + # TODO(marcus): Hardcoding the family here, better separate this. + maya.create( + name=maya.unique_name(asset, suffix="_SET"), + asset=context["asset"]["name"], + family="avalon.animation", + options={"useSelection": True}, + data={ + "dependencies": " ".join(str(d) for d in dependencies) + }) diff --git a/colorbleed/plugins/maya/publish/__collect_instance_per_item.py b/colorbleed/plugins/maya/publish/__collect_instance_per_item.py new file mode 100644 index 0000000000..7dd70d444a --- /dev/null +++ b/colorbleed/plugins/maya/publish/__collect_instance_per_item.py @@ -0,0 +1,177 @@ +from collections import defaultdict + +from maya import cmds + +import cbra.utils.maya.node_uuid as node_uuid +import cbra.lib + +import pyblish.api + + +class CollectInstancePerItem(pyblish.api.ContextPlugin): + """Collect instances from the Maya scene and breaks them down per item id + + An instance is identified by having an _INST suffix + and a .family user-defined attribute. + + All other user-defined attributes of the object set + is accessible within each instance's data. + + This collector breaks the instances down to each Item member it contains, + by using the IDs on the nodes in the instance it will split up the instance + into separate instances for each unique "item" id it finds. + + Note: + - Only breaks down based on children members and ignores parent members. + - Discards members without IDs. + + """ + + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["maya"] + label = "Instance per Item" + + _include_families = ["colorbleed.look"] + + def process(self, context): + + invalid = list() + + for objset in cmds.ls("*_SET", + objectsOnly=True, + type='objectSet', + long=True, + recursive=True): # Include namespace + + try: + family = cmds.getAttr("{}.family".format(objset)) + except ValueError: + self.log.error("Found: %s found, but no family." % objset) + continue + + if family not in self._include_families: + continue + + # ignore referenced sets + if cmds.referenceQuery(objset, isNodeReferenced=True): + continue + + instances = self.build_instances(context, objset) + if not instances: + + # Log special error messages when objectSet is completely + # empty (has no members) to clarify to artists the root of + # their problem. + if not cmds.sets(objset, query=True): + self.log.error("Instance objectSet has no members: " + "{}".format(objset)) + + self.log.error("No instances retrieved from objectSet: " + "{}".format(objset)) + invalid.append(objset) + + if invalid: + raise RuntimeError("Invalid instances: {}".format(invalid)) + + # Sort context based on family + context[:] = sorted( + context, key=lambda instance: instance.data("family")) + + def build_instances(self, context, objset): + """Build the instances for a single instance objectSet + + Returns: + list: The constructed instances from the objectSet. + + """ + + self.log.info("Collecting: %s" % objset) + + short_name = objset.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + + # Default data + default_data = {"name": short_name[:-5], + "subset": "default"} + + # Get user data from user defined attributes + user_data = dict() + for attr in cmds.listAttr(objset, userDefined=True): + try: + value = cmds.getAttr("{}.{}".format(objset, attr)) + user_data[attr] = value + except RuntimeError: + continue + + # Maintain nested object sets + members = cmds.sets(objset, query=True) + members = cmds.ls(members, long=True) + + children = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + + # Exclude intermediate objects + children = cmds.ls(children, noIntermediate=True, long=True) + + nodes = members + children + nodes = list(set(nodes)) + + # Group nodes using ids to an Item + nodes_id = node_uuid.build_cache(nodes, include_without_ids=True) + + # Log warning for nodes without ids + if None in nodes_id: + self.log.warning("Skipping nodes without ids: " + "{}".format(nodes_id[None])) + + # ignore nodes without ids + context.data["instancePerItemNodesWithoutId"] = nodes_id.pop(None, + None) + + item_groups = defaultdict(list) + + for id, nodes in nodes_id.iteritems(): + item_id = id.rsplit(":", 1)[0] + item_groups[item_id].extend(nodes) + + instances = list() + for item_id, item_nodes in item_groups.iteritems(): + + ctx = node_uuid.parse_id("{}:fake_node_uuid".format(item_id)) + + # Use itemPath to parse full blown context using official lib + ctx = cbra.lib.parse_context(ctx['itemPath']) + + item = ctx.get('item', None) + if item is None: + self.log.info("Unparsed item id: {}".format(item_id)) + self.log.error("Item can't be parsed and seems to be " + "non-existent. Was an asset renamed? Or your" + "project set incorrectly?") + raise RuntimeError("Item not parsed. See log for description.") + + instance = context.create_instance(objset) + + # Set the related members + instance[:] = item_nodes + instance.data['setMembers'] = item_nodes + + # Set defaults and user data + instance.data.update(default_data.copy()) + instance.data.update(user_data.copy()) + + # Override the label to be clear + name = instance.data['name'] + instance.data['label'] = "{0} ({1})".format(name, item) + + # Store that the instance is collected per item + instance.data['_instancePerItem'] = True + instance.data['_itemContext'] = ctx + + assert "family" in instance.data, "No family data in instance" + assert "name" in instance.data, ("No objectSet name data " + "in instance") + + instances.append(instance) + + return instances diff --git a/colorbleed/plugins/maya/publish/__collect_yeti_caches.py b/colorbleed/plugins/maya/publish/__collect_yeti_caches.py new file mode 100644 index 0000000000..a236836cdf --- /dev/null +++ b/colorbleed/plugins/maya/publish/__collect_yeti_caches.py @@ -0,0 +1,156 @@ +import os +import re +import pyseq +import glob + +import pyblish.api + +from maya import cmds + + +class SeletYetiCachesAction(pyblish.api.Action): + """Select the nodes related to the collected file textures""" + + label = "Select yeti nodes" + on = "succeeded" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + + self.log.info("Finding textures..") + + # Get the errored instances + instances = [] + for result in context.data["results"]: + instance = result["instance"] + if instance is None: + continue + + instances.append(instance) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(instances, plugin) + + # Get the texture nodes from the instances + nodes = [] + for instance in instances: + texture_nodes = instance.data['yetiCaches'].keys() + nodes.extend(texture_nodes) + + # Ensure unique + nodes = list(set(nodes)) + + if nodes: + self.log.info("Selecting nodes: %s" % ", ".join(nodes)) + cmds.select(nodes, r=True, noExpand=True) + else: + self.log.info("No nodes found.") + cmds.select(deselect=True) + + +def get_sequence(filename, pattern="%04d"): + """Get pyseq sequence from filename + + Supports negative frame ranges like (-001, 0000, 0001 and -0001, 0000, 0001). + + Arguments: + filename (str): The full path to filename containing the given pattern. + pattern (str): The pattern to swap with the variable frame number. + + Returns: + pyseq.Sequence: file sequence. + + """ + + glob_pattern = filename.replace(pattern, "*") + + escaped = re.escape(filename) + re_pattern = escaped.replace(pattern, "-?[0-9]+") + + files = glob.glob(glob_pattern) + files = [str(f) for f in files if re.match(re_pattern, f)] + + return pyseq.get_sequences(files) + + +class CollectYetiCaches(pyblish.api.InstancePlugin): + """Collect used yeti caches. + + Collects the file sequences from pgYetiMaya.cacheFileName + + """ + + order = pyblish.api.CollectorOrder + 0.495 + label = 'Yeti Caches' + families = ["colorbleed.groom"] + actions = [SeletYetiCachesAction] + + TYPES = {"pgYetiMaya": "cacheFileName"} + + def process(self, instance): + + # Get textures from sets + members = instance.data("setMembers") + members = cmds.ls(members, dag=True, shapes=True, type="pgYetiMaya", + noIntermediate=True, long=True) + if not members: + raise RuntimeError("Instance appears to be empty (no members)") + + # Collect only those cache frames that are required + # If handles are required it is assumed to already be included + # in the start frame and end frames. + # (e.g. using frame handle collector) + start_frame = instance.data("startFrame") + end_frame = instance.data("endFrame") + required = set(range(int(start_frame), int(end_frame) + 1)) + + history = cmds.listHistory(members) or [] + + resources = instance.data.get("resources", []) + yeti_caches = dict() + + for node_type, attr in self.TYPES.iteritems(): + for node in cmds.ls(history, type=node_type, long=True): + + attribute = "{0}.{1}".format(node, attr) + + # Source + source = cmds.getAttr(attribute) + if not source: + self.log.error("Node does not have a file set: " + "{0}".format(node)) + + # Collect the source as expanded path because that's also + # how the attribute must be 'set' for yeti nodes. + source = os.path.realpath(cmds.workspace(expandName=source)) + + # Collect the frames we need from the sequence + sequences = get_sequence(source) + files = list() + for sequence in sequences: + for index, frame in enumerate(sequence.frames()): + if frame not in required: + continue + + item = sequence[index] + files.append(item.path) + + # Define the resource + resource = {"tags": ["maya", "yeti", "attribute"], + "node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files, # required for resources + "subfolder": "caches" # optional for resources + } + + resources.append(resource) + + # For validations + yeti_caches[node] = {"attribute": attribute, + "source": source, + "sequences": sequences} + + # Store data on instance + instance.data['yetiCaches'] = yeti_caches + instance.data['resources'] = resources diff --git a/colorbleed/plugins/maya/publish/__extract_layout.py b/colorbleed/plugins/maya/publish/__extract_layout.py new file mode 100644 index 0000000000..a589dc05ae --- /dev/null +++ b/colorbleed/plugins/maya/publish/__extract_layout.py @@ -0,0 +1,81 @@ +import json + +from maya import cmds + +import pyblish_maya +import colorbleed.api + +import cb.utils.maya.context as context +import cbra.utils.maya.layout as layout + + +def get_upstream_hierarchy_fast(nodes): + """Passed in nodes must be long names!""" + + matched = set() + parents = [] + + for node in nodes: + hierarchy = node.split("|") + num = len(hierarchy) + for x in range(1, num-1): + parent = "|".join(hierarchy[:num-x]) + if parent in parents: + break + else: + parents.append(parent) + matched.add(parent) + + return parents + + +class ExtractLayout(colorbleed.api.Extractor): + """Extract Layout as both gpuCache and Alembic""" + + label = "Layout (gpuCache & alembic)" + hosts = ["maya"] + families = ["colorbleed.layout"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + + start = instance.data.get("startFrame", 1) + end = instance.data.get("endFrame", 1) + step = instance.data.get("step", 1.0) + placeholder = instance.data.get("placeholder", False) + write_color_sets = instance.data.get("writeColorSets", False) + renderable_only = instance.data.get("renderableOnly", False) + visible_only = instance.data.get("visibleOnly", False) + + layers = instance.data.get("animLayersActive", None) + if layers: + layers = json.loads(layers) + self.log.info("Publishing with animLayers active: " + "{0}".format(layers)) + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + + # Get children hierarchy + nodes = instance.data['setMembers'] + cmds.select(nodes, r=True, hierarchy=True) + hierarchy = cmds.ls(selection=True, long=True) + + with context.evaluation("off"): + with context.no_refresh(): + with context.active_anim_layers(layers): + layout.extract_layout(hierarchy, + dir_path, + start=start, + end=end, + step=step, + placeholder=placeholder, + write_color_sets=write_color_sets, + renderable_only=renderable_only, + visible_only=visible_only) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, dir_path)) diff --git a/colorbleed/plugins/maya/publish/__validate_layout_nodes.py b/colorbleed/plugins/maya/publish/__validate_layout_nodes.py new file mode 100644 index 0000000000..fda17e4433 --- /dev/null +++ b/colorbleed/plugins/maya/publish/__validate_layout_nodes.py @@ -0,0 +1,91 @@ +import os + +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + +import cbra.lib +from cb.utils.python.decorators import memorize + + +def isclose(a, b, rel_tol=1e-9, abs_tol=0.0): + return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + + +@memorize +def is_published_path(path): + """Return whether path is from a published file""" + + # Quick check (optimization) without going through the folder + # structure + if cbra.lib.DIR_PUBLISH.lower() not in path.lower(): + return False + + try: + context = cbra.lib.parse_context(path) + except RuntimeError: + context = dict() + + return all([context.get("family", None), + context.get("subset", None), + context.get("version", None)]) + + +class ValidateLayoutNodes(pyblish.api.InstancePlugin): + """Validates that layout nodes behave to certain rules + + Gpu caches in a layout may not have sub-frame offsets, like offsets with a + value after the decimal point. (e.g. 1.45) + + Gpu caches loaded in a layout MUST come from a published source that has + family and version. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = 'Layout Nodes' + families = ['colorbleed.layout'] + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + caches = cmds.ls(instance, type="gpuCache", long=True) + + # Validate sub-frame offsets + invalid_offsets = list() + for cache in caches: + + offset = cmds.getAttr("{}.animOffset".format(cache)) + if not isclose(offset, round(offset)): + cls.log.warning("Invalid sub-frame offset on: %s" % cache) + invalid_offsets.append(cache) + + # Validate gpuCache paths are from published files + invalid_paths = list() + for cache in caches: + path = cmds.getAttr("{}.cacheFileName".format(cache)) + path = os.path.normpath(path) + if not is_published_path(path): + cls.log.warning("GpuCache path not from published file: " + "{0} -> {1}".format(cache, path)) + invalid_paths.append(cache) + + invalid = invalid_offsets + invalid_paths + + return invalid + + def process(self, instance): + + # Clear cache only once per publish. So we store a value on + # the context on the first instance so we clear only once. + name = self.__class__.__name__ + key = "_plugin_{0}_processed".format(name) + if not instance.context.data.get(key, False): + is_published_path.cache.clear() + instance.context.data[key] = True + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_related_node_ids.py b/colorbleed/plugins/maya/publish/__validate_related_node_ids.py new file mode 100644 index 0000000000..a79138168d --- /dev/null +++ b/colorbleed/plugins/maya/publish/__validate_related_node_ids.py @@ -0,0 +1,83 @@ +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils +import cbra.lib + + +class ValidateRelatedNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have related colorbleed ids. + + An ID is 'related' if its built in the current Item. + + Note that this doesn't ensure it's from the current Task. An ID created + from `lookdev` has the same relation to the Item as one coming from others, + like `rigging` or `modeling`. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.model'] + hosts = ['maya'] + label = 'Related Id Attributes' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @classmethod + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" + + context = instance.context + current_file = context.data.get('currentFile', None) + if not current_file: + raise RuntimeError("No current file information: " + "{0}".format(current_file)) + + try: + context = cbra.lib.parse_context(current_file) + except RuntimeError, e: + cls.log.error("Can't generate UUIDs because scene isn't " + "in new-style pipeline: ".format(current_file)) + raise e + + def to_item(id): + """Split the item id part from a node id""" + return id.rsplit(":", 1)[0] + + # Generate a fake id in the current context to retrieve the item + # id prefix that should match with ids on the nodes + fake_node = "__node__" + ids = id_utils.generate_ids(context, [fake_node]) + id = ids[fake_node] + item_prefix = to_item(id) + + # Take only the ids with more than one member + invalid = list() + invalid_items = set() + for member in instance: + member_id = id_utils.get_id(member) + + # skip nodes without ids + if not member_id: + continue + + if not member_id.startswith(item_prefix): + invalid.append(member) + invalid_items.add(to_item(member_id)) + + # Log invalid item ids + if invalid_items: + for item_id in sorted(invalid_items): + cls.log.warning("Found invalid item id: {0}".format(item_id)) + + return invalid + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Nodes found with non-related " + "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py b/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py new file mode 100644 index 0000000000..55d3a14377 --- /dev/null +++ b/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py @@ -0,0 +1,167 @@ +import os +from collections import defaultdict + +import pyblish.api +import colorbleed.api + +import cbra.lib +from cbra.utils.maya.abc import get_alembic_ids +from cbra.utils.maya.node_uuid import get_id + + +def get_subset_path(context): + return os.path.join(context['itemPath'], + cbra.lib.DIR_PUBLISH, + context['family'], + context['subset']) + + +class ValidateUniqueIdsInItem(pyblish.api.InstancePlugin): + """Checks whether IDs are unique across other subsets + + This ensures a model to be published can't have ids + which are already present in another subset. For example + the "default" model can't have ids present in the "high" + subset. + + Note: + This will also invalidate the instance if it contains + nodes that are present in another instance in the scene. + So ensure the instance you're publishing actually has + the correct set members. + + """ + + order = colorbleed.api.ValidateMeshOrder + families = ['colorbleed.model'] + hosts = ['maya'] + label = 'Unique Ids in Item' + actions = [colorbleed.api.SelectInvalidAction] + optional = True + + @classmethod + def iter_invalid(cls, instance): + + verbose = instance.data.get("verbose", False) + + def _get_instance_ids(instance): + """Collect ids in an instance""" + nodes_per_id = defaultdict(list) + for node in instance: + node_id = get_id(node) + if node_id: + nodes_per_id[node_id].append(node) + return nodes_per_id + + nodes_per_id = _get_instance_ids(instance) + if not nodes_per_id: + return + + ids_lookup = set(nodes_per_id.keys()) + + instance_context = instance.data["instanceContext"] + instance_subset = instance.data['subset'] + + assert instance_context, "Instance must have 'instanceContext' data" + assert instance_subset, "Instance must have 'subset' data" + + subsets_checked = set() + subsets_checked.add(instance_subset) # we can skip this subset + + # Compare with all other *currently publishing instances* + # of family 'model' for this item + for other_instance in instance.context: + if other_instance is instance: + continue + + if other_instance.data['subset'] == instance_subset: + cls.log.error("Another instance has the same subset? " + "This should never happen.") + + if other_instance.data['family'] != "model": + continue + + if other_instance.data['instanceContext']['item'] != \ + instance_context['item']: + cls.log.error("Also publishing model for other item? " + "This should never happen.") + continue + other_ids = _get_instance_ids(other_instance).keys() + + # Perform comparison + intersection = ids_lookup.intersection(other_ids) + if intersection: + for node_id in intersection: + nodes = nodes_per_id[node_id] + for node in nodes: + yield node + + # Those that are invalid don't need to be checked again + ids_lookup.difference_update(other_ids) + + if not ids_lookup: + # Once we have no ids to check for anymore we can already + # return + return + + subsets_checked.add(other_instance.data['subset']) + + # Compare with all previously *published instances* + # of family 'model' for this item + ctx = instance_context.copy() + ctx['family'] = "model" + + published_subsets = cbra.lib.list_subsets(ctx) + published_subsets = set(x for x in published_subsets if + x != instance_subset) + + for published_subset in published_subsets: + ctx['subset'] = published_subset + ctx['subsetPath'] = get_subset_path(ctx) + + versions = cbra.lib.list_versions(ctx) + version = cbra.lib.find_highest_version(versions) + if not version: + cls.log.debug("No published version for " + "'model': {0}".format(published_subset)) + continue + + ctx['currentVersion'] = version + publish_abc = cbra.lib.get_filepath(ctx) + ".abc" + + if not os.path.exists(publish_abc): + cls.log.error("Published file to compare with does not exist: " + "{0}".format(publish_abc)) + continue + + if verbose: + cls.log.debug("Comparing with: {0}".format(publish_abc)) + + abc_ids = set(get_alembic_ids(publish_abc).values()) + + # Perform comparison + intersection = ids_lookup.intersection(abc_ids) + if intersection: + for node_id in intersection: + nodes = nodes_per_id[node_id] + for node in nodes: + yield node + + # Those that are invalid don't need to be checked again + ids_lookup.difference_update(abc_ids) + + if not ids_lookup: + # Once we have no ids to check for anymore we can already + # return + return + + return + + @classmethod + def get_invalid(cls, instance): + return list(cls.iter_invalid(instance)) + + def process(self, instance): + """Process all meshes""" + if any(self.iter_invalid(instance)): + raise RuntimeError("Invalid nodes found in {0}".format(instance)) diff --git a/colorbleed/plugins/maya/publish/_debug.py b/colorbleed/plugins/maya/publish/_debug.py new file mode 100644 index 0000000000..7919b1fda1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_debug.py @@ -0,0 +1,19 @@ +import pyblish.api + + +class DebugPlugin(pyblish.api.InstancePlugin): + + label = "Debug" + order = pyblish.api.IntegratorOrder - 0.4 + + def process(self, instance): + + import pprint + + self.log("\n\n----------------------") + self.log("Instance") + pprint.pprint(instance) + + self.log("\n\n----------------------") + self.log("Instance.data") + pprint.pprint(instance.data) diff --git a/colorbleed/plugins/maya/publish/_integrate_cb_asset.py b/colorbleed/plugins/maya/publish/_integrate_cb_asset.py new file mode 100644 index 0000000000..61535e58db --- /dev/null +++ b/colorbleed/plugins/maya/publish/_integrate_cb_asset.py @@ -0,0 +1,85 @@ +import os +import shutil + +import pyblish_cb.lib +import colorbleed.api + + +class IntegrateColorbleedAssets(colorbleed.api.Integrator): + """Name and position instances on disk for instances. + + The files are transferred from the `extractDir` to the + computed `integrationDir` and are renamed as: + - "{item}_{family}_{subsetName}_{version}.{ext}" + + Assumptions: + - Each extracted instance is 1 file (no directories) + + """ + + label = "Asset" + families = ["colorbleed.model", "colorbleed.rig", "colorbleed.pointcache", + "colorbleed.proxy", "colorbleed.layout", "colorbleed.look", + "colorbleed.vrmeshReplace", "colorbleed.review", + "colorbleed.instancer", "colorbleed.camera", + "colorbleed.mayaAscii", + "colorbleed.furYeti"] + + def process(self, instance): + super(IntegrateColorbleedAssets, self).process(instance) + + self.log.info("Integrating {0}..".format(instance)) + + integration = pyblish_cb.lib.compute_integration(instance) + + # Store reference for upcoming plug-ins + instance.data["integrationDir"] = integration['path'] + instance.data["integrationVersion"] = integration['versionNum'] + + path = integration['path'] + data = integration.copy() + + try: + if not os.path.exists(path): + os.makedirs(path) + + self.log.info("Moving files to %s" % path) + + tmp = instance.data["extractDir"] + for src in (os.path.join(tmp, f) for f in os.listdir(tmp)): + + self.log.debug("Integrating %s" % src) + + # Source must be a file + if not os.path.isfile(src): + self.log.error("Source is not a file: {0}".format(src)) + continue + + # TODO(marcus): Consider files without extension + data["ext"] = src.split(".", 1)[-1] + dst = os.path.join(path, "{item}_" + "{family}_" + "{subsetName}_" + "{version}.{ext}".format( + **data)) + + # Copy + self.log.info("\"%s\" -> \"%s\"" % (src, dst)) + shutil.copyfile(src, dst) + + self.log.debug("Tagged %s with .Version" % path) + + try: + subset_path = os.path.dirname(path) + cquery.tag(subset_path, ".Subset") + self.log.debug("Tagged %s with .Subset" % subset_path) + except cquery.TagExists: + pass + + except OSError as e: + # If, for whatever reason, this instance did not get written. + instance.data.pop("integrationDir") + raise e + + except Exception as e: + raise Exception("An unknown error occured: %s" % e) diff --git a/colorbleed/plugins/maya/publish/_integrate_files.py b/colorbleed/plugins/maya/publish/_integrate_files.py new file mode 100644 index 0000000000..65105797cc --- /dev/null +++ b/colorbleed/plugins/maya/publish/_integrate_files.py @@ -0,0 +1,47 @@ +import os +import shutil +import pyblish.api + +import colorbleed.api + + +class IntegrateFiles(colorbleed.api.Integrator): + """Integrate Files + + Copies the transfer queue to the destinations. + + """ + + order = pyblish.api.IntegratorOrder + 0.1 + label = "Transfer Files" + + def process(self, instance): + """Copy textures from srcPath to destPath + + The files should be stored in the "integrateFiles" data on the instance. Each item in the + list should be a dictionary with 'srcPath' and 'destPath' key values. + + - srcPath: Source path (must be absolute!) + - destPath: Destination path (can be relative) + + """ + super(IntegrateFiles, self).process(instance) + + # Get unique texture transfers + # (since different nodes might load same texture) + transfers = instance.data.get("transfers", []) + + for src, dest in transfers: + + self.log.info("Copying: {0} -> {1}".format(src, dest)) + + # Source is destination + if os.path.normpath(dest) == os.path.normpath(src): + self.log.info("Skip copy of resource file: {0}".format(src)) + continue + + # Ensure folder exists + folder = os.path.dirname(dest) + if not os.path.exists(folder): + os.makedirs(folder) + shutil.copyfile(src, dest) diff --git a/colorbleed/plugins/maya/publish/_validate_look_node_ids.py b/colorbleed/plugins/maya/publish/_validate_look_node_ids.py new file mode 100644 index 0000000000..fcb91701e7 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_look_node_ids.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api + + +class ValidateLookNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have colorbleed id attributes + + All look sets should have id attributes. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Id Attributes' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid(instance): + import maya.cmds as cmds + + nodes = instance.data["lookSets"] + + # Ensure all nodes have a cbId + invalid = list() + for node in nodes: + uuid = cmds.attributeQuery("mbId", node=node, exists=True) + if not uuid: + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Nodes found without " + "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py b/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py new file mode 100644 index 0000000000..19142caac9 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py @@ -0,0 +1,62 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRigNodeIds(pyblish.api.InstancePlugin): + """Validate nodes in instance have colorbleed id attributes + + To fix this use the action to select the invalid nodes. Identify these + are nodes created locally to the rig; if they are not they should've gotten + their ID elsewhere! This is important, because then you should NOT fix it + in your scene but earlier in the pipeline. If these invalid nodes are local + to your rig then you should generate ids for them. + + For Dummies: + For the pipeline it's important in further stages to identify exactly + "what nodes is what node". Basically it saying: Hey! It's me! To + accompany that each node stores an ID, like its own passport. This + validator will tell you if there are nodes that have no such + passport (ID). + + Warning: + This does NOT validate the IDs are unique in the instance. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.rig', + 'colorbleed.rigcontrols', + "colorbleed.rigpointcache"] + hosts = ['maya'] + label = 'Rig Id Attributes' + actions = [colorbleed.api.SelectInvalidAction] + + # includes: yeti grooms and v-ray fur, etc. + TYPES = ("transform", "mesh", "nurbsCurve", "geometryShape") + + @staticmethod + def get_invalid(instance): + + # filter to nodes of specific types + dag = cmds.ls(instance, noIntermediate=True, + long=True, type=ValidateRigNodeIds.TYPES) + + # Ensure all nodes have a cbId + invalid = list() + for node in dag: + # todo: refactor `mbId` when attribute is updated + uuid = cmds.attributeQuery("mbId", node=node, exists=True) + if not uuid: + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes found without " + "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py new file mode 100644 index 0000000000..afb2fd5bb2 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py @@ -0,0 +1,85 @@ +from collections import defaultdict + +import pyblish.api +import colorbleed.api + + +class ValidateRigPointcacheNodeIds(pyblish.api.InstancePlugin): + """Validate rig pointcache_SET nodes have ids + + The nodes in a rig's pointcache_SET must all have node IDs + that are all unique. + + Geometry in a rig should be using published model's geometry. + As such when this validation doesn't pass it means you're using + local newly created nodes that are not coming from a published + model file. Ensure you update the ids from the model. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.rig', "colorbleed.rigpointcache"] + hosts = ['maya'] + label = 'Rig Pointcache Node Ids' + actions = [colorbleed.api.SelectInvalidAction] + + ignore_types = ("constraint",) + + @classmethod + def get_invalid(cls, instance): + from maya import cmds + + # Get pointcache_SET + sets = cmds.ls(instance, type='objectSet') + pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + + nodes = list() + for s in pointcache_sets: + members = cmds.sets(s, query=True) + members = cmds.ls(members, long=True) # ensure long names + descendents = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + descendents = cmds.ls(descendents, noIntermediate=True, long=True) + hierarchy = members + descendents + nodes.extend(hierarchy) + + # ignore certain node types (e.g. constraints) + ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) + if ignore: + ignore = set(ignore) + nodes = [node for node in nodes if node not in ignore] + + # Missing ids + missing = list() + ids = defaultdict(list) + for node in nodes: + has_id = cmds.attributeQuery("mbId", node=node, exists=True) + if not has_id: + missing.append(node) + continue + + uuid = cmds.getAttr("{}.mbId".format(node)) + ids[uuid].append(node) + + non_uniques = list() + for uuid, nodes in ids.iteritems(): + if len(nodes) > 1: + non_uniques.extend(nodes) + + if missing: + cls.log.warning("Missing node ids: {0}".format(missing)) + + if non_uniques: + cls.log.warning("Non unique node ids: {0}".format(non_uniques)) + + invalid = missing + non_uniques + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Missing or non-unique node IDs: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py new file mode 100644 index 0000000000..7663103bbe --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py @@ -0,0 +1,86 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRigPointcacheShapeRenderStats(pyblish.api.Validator): + """Ensure all render stats are set to the default values.""" + + order = colorbleed.api.ValidateMeshOrder + families = ['colorbleed.model'] + hosts = ['maya'] + category = 'model' + optional = False + version = (0, 1, 0) + label = 'Rig Pointcache Shape Default Render Stats' + actions = [colorbleed.api.SelectInvalidAction] + + defaults = {'castsShadows': 1, + 'receiveShadows': 1, + 'motionBlur': 1, + 'primaryVisibility': 1, + 'smoothShading': 1, + 'visibleInReflections': 1, + 'visibleInRefractions': 1, + 'doubleSided': 1, + 'opposite': 0} + + ignore_types = ("constraint",) + + @classmethod + def get_pointcache_nodes(cls, instance): + + # Get pointcache_SET + sets = cmds.ls(instance, type='objectSet') + pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + + nodes = list() + for s in pointcache_sets: + members = cmds.sets(s, q=1) + members = cmds.ls(members, long=True) # ensure long names + descendents = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + descendents = cmds.ls(descendents, + noIntermediate=True, + long=True) + hierarchy = members + descendents + nodes.extend(hierarchy) + + # ignore certain node types (e.g. constraints) + ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) + if ignore: + ignore = set(ignore) + nodes = [node for node in nodes if node not in ignore] + + return nodes + + @classmethod + def get_invalid(cls, instance): + # It seems the "surfaceShape" and those derived from it have + # `renderStat` attributes. + + nodes = cls.get_pointcache_nodes(instance) + + shapes = cmds.ls(nodes, long=True, type='surfaceShape') + invalid = [] + for shape in shapes: + for attr, requiredValue in \ + ValidateRigPointcacheShapeRenderStats.defaults.iteritems(): + + if cmds.attributeQuery(attr, node=shape, exists=True): + value = cmds.getAttr('{node}.{attr}'.format(node=shape, + attr=attr)) + if value != requiredValue: + invalid.append(shape) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Shapes with non-standard renderStats " + "found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py new file mode 100644 index 0000000000..d7453a3a4c --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py @@ -0,0 +1,63 @@ +import pyblish.api +import colorbleed.api + + +class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have colorbleed id attributes""" + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.model'] + hosts = ['maya'] + label = 'Unique Id Attributes' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid_dict(instance): + """Return a dictionary mapping of id key to list of member nodes""" + + import maya.cmds as cmds + + uuid_attr = "mbId" + + # Collect each id with their members + from collections import defaultdict + ids = defaultdict(list) + for member in instance: + has_attr = cmds.attributeQuery(uuid_attr, node=member, exists=True) + if not has_attr: + continue + mbid = cmds.getAttr("{}.{}".format(member, uuid_attr)) + ids[mbid].append(member) + + # Skip those without IDs (if everything should have an ID that should + # be another validation) + ids.pop(None, None) + + # Take only the ids with more than one member + invalid = dict((id, members) for id, members in ids.iteritems() if + len(members) > 1) + return invalid + + @classmethod + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" + + invalid_dict = cls.get_invalid_dict(instance) + + # Take only the ids with more than one member + invalid = list() + for members in invalid_dict.itervalues(): + invalid.extend(members) + + return invalid + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid_dict(instance) + if invalid: + raise RuntimeError("Nodes found with non-unique " + "asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/_validate_units_angular.py b/colorbleed/plugins/maya/publish/_validate_units_angular.py new file mode 100644 index 0000000000..6ac560ee60 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_units_angular.py @@ -0,0 +1,20 @@ +import pyblish.api +import colorbleed.api + + +class ValidateUnitsAngular(pyblish.api.ContextPlugin): + """Scene angular units must be in degrees""" + + order = colorbleed.api.ValidateSceneOrder + label = "Units (angular)" + families = ["colorbleed.rig", + "colorbleed.model", + "colorbleed.pointcache", + "colorbleed.curves"] + + def process(self, context): + units = context.data('angularUnits') + + self.log.info('Units (angular): {0}'.format(units)) + assert units and units == 'deg', ( + "Scene angular units must be degrees") diff --git a/colorbleed/plugins/maya/publish/_validate_units_fps.py b/colorbleed/plugins/maya/publish/_validate_units_fps.py new file mode 100644 index 0000000000..b28c86804e --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_units_fps.py @@ -0,0 +1,28 @@ +import pyblish.api +import colorbleed.api + + +class ValidateUnitsFps(pyblish.api.ContextPlugin): + """Validate the scene linear, angular and time units.""" + + order = colorbleed.api.ValidateSceneOrder + label = "Units (fps)" + families = ["colorbleed.rig", + "colorbleed.pointcache", + "colorbleed.curves"] + actions = [colorbleed.api.RepairAction] + optional = True + + def process(self, context): + + fps = context.data['fps'] + + self.log.info('Units (time): {0} FPS'.format(fps)) + assert fps and fps == 25.0, "Scene must be 25 FPS" + + @classmethod + def repair(cls): + """Fix the current FPS setting of the scene, set to PAL(25.0 fps) + """ + import maya.cmds as cmds + cmds.currentUnit(time="pal") diff --git a/colorbleed/plugins/maya/publish/_validate_units_linear.py b/colorbleed/plugins/maya/publish/_validate_units_linear.py new file mode 100644 index 0000000000..9230ae7626 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_units_linear.py @@ -0,0 +1,20 @@ +import pyblish.api +import colorbleed.api + + +class ValidateUnitsLinear(pyblish.api.ContextPlugin): + """Scene must be in linear units""" + + order = colorbleed.api.ValidateSceneOrder + label = "Units (linear)" + families = ["colorbleed.rig", + "colorbleed.model", + "colorbleed.pointcache", + "colorbleed.curves"] + + def process(self, context): + units = context.data('linearUnits') + + self.log.info('Units (linear): {0}'.format(units)) + assert units and units == 'cm', ("Scene linear units must " + "be centimeters") diff --git a/colorbleed/plugins/maya/publish/collect_alembic_attrs.py b/colorbleed/plugins/maya/publish/collect_alembic_attrs.py new file mode 100644 index 0000000000..5d1cf95744 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_alembic_attrs.py @@ -0,0 +1,25 @@ +import pyblish.api + + +class CollectAlembicCBAttrs(pyblish.api.InstancePlugin): + """Collects settings for the Alembic extractor""" + + order = pyblish.api.CollectorOrder + 0.499 + families = ['colorbleed.model', 'colorbleed.pointcache'] + label = "Alembic Colorbleed Attrs" + + def process(self, instance): + + attrPrefix = instance.data.get("attrPrefix", []) + attrPrefix.append("cb") + instance.data['attrPrefix'] = attrPrefix + + # Ensure visibility keys are written + instance.data['writeVisibility'] = True + + # Write creases + instance.data['writeCreases'] = True + + # Ensure UVs are written + instance.data['uvWrite'] = True + diff --git a/colorbleed/plugins/maya/publish/collect_current_file.py b/colorbleed/plugins/maya/publish/collect_current_file.py new file mode 100644 index 0000000000..305116fb2d --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_current_file.py @@ -0,0 +1,18 @@ +import os + +from maya import cmds + +import pyblish.api + + +class CollectMayaCurrentFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Maya Current File" + hosts = ['maya'] + + def process(self, context): + """Inject the current working file""" + current_file = cmds.file(query=True, sceneName=True) + context.data['currentFile'] = os.path.normpath(current_file) diff --git a/colorbleed/plugins/maya/publish/collect_history.py b/colorbleed/plugins/maya/publish/collect_history.py new file mode 100644 index 0000000000..840e916133 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_history.py @@ -0,0 +1,40 @@ +from maya import cmds + +import pyblish.api + + +class CollectMayaHistory(pyblish.api.InstancePlugin): + """Collect history for instances from the Maya scene + + Note: + This removes render layers collected in the history + + This is separate from Collect Instances so we can target it towards only + specific family types. + + """ + + order = pyblish.api.CollectorOrder + 0.1 + hosts = ["maya"] + label = "Maya History" + families = ["colorbleed.rig"] + verbose = False + + def process(self, instance): + + # Collect the history with long names + history = cmds.listHistory(instance, leaf=False) or [] + history = cmds.ls(history, long=True) + + # Remove invalid node types (like renderlayers) + invalid = cmds.ls(history, type="renderLayer", long=True) + if invalid: + invalid = set(invalid) # optimize lookup + history = [x for x in history if x not in invalid] + + # Combine members with history + members = instance[:] + history + members = list(set(members)) # ensure unique + + # Update the instance + instance[:] = members diff --git a/colorbleed/plugins/maya/publish/collect_instancer_caches.py b/colorbleed/plugins/maya/publish/collect_instancer_caches.py new file mode 100644 index 0000000000..71237551a6 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_instancer_caches.py @@ -0,0 +1,54 @@ +import pyblish.api +import maya.cmds as cmds + +class CollectInstancerCaches(pyblish.api.InstancePlugin): + """For an Instancer collect the history. + + This would collect its particles with nucleus and cacheFile + + """ + + order = pyblish.api.CollectorOrder + 0.495 + families = ['colorbleed.instancer'] + label = "Instancer Cache Files" + + def process(self, instance): + + members = instance.data.get("exactExportMembers", None) + assert members, "Instancer must have members" + + resources = instance.data.get("resources", []) + caches = cmds.ls(members, type="cacheFile") + + errors = False + for cache in caches: + + self.log.debug("Collecting cache files for: {0}".format(cache)) + + files = cmds.cacheFile(cache, query=True, fileName=True) + + # Ensure there are any files and the cacheFile is linked + # correctly. + if not files: + errors = True + self.log.error("Cache has no files: {0}".format(cache)) + continue + + source = files[0] # The first file is the .xml file + + # TODO: Filter the files to only contain the required frame range. + + resource = {"tags": ["maya", "node", "cacheFile"], + "node": cache, + "source": source, + "files": files, + "subfolder": "caches"} + + resources.append(resource) + + # Store on the instance + instance.data['resources'] = resources + + if errors: + raise RuntimeError("Errors during collecting caches. " + "Are the caches linked correctly?") diff --git a/colorbleed/plugins/maya/publish/collect_instancer_history.py b/colorbleed/plugins/maya/publish/collect_instancer_history.py new file mode 100644 index 0000000000..4d2d57bd03 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_instancer_history.py @@ -0,0 +1,63 @@ +import pyblish.api +import maya.cmds as cmds + + +class CollectInstancerHistory(pyblish.api.InstancePlugin): + """For an Instancer collect the history. + + This would collect its particles with nucleus and cacheFile + + """ + + order = pyblish.api.CollectorOrder + 0.49 + families = ['colorbleed.instancer'] + label = "Instancer History" + + def process(self, instance): + + members = instance.data["setMembers"] + + # Include history of the instancer + instancers = cmds.ls(members, type="instancer") + if not instancers: + self.log.info("No instancers found") + return + + export = instancers[:] + + # Get the required inputs of the particles from history + history = cmds.listHistory(instancers) or [] + particles = cmds.ls(history, type="nParticle") + export.extend(particles) + if particles: + self.log.info("Particles: {0}".format(particles)) + + particles_history = cmds.listHistory(particles) or [] + self.log.debug("Particle history: {0}".format(particles_history)) + + nucleus = cmds.ls(particles_history, long=True, type="nucleus") + self.log.info("Collected nucleus: {0}".format(nucleus)) + export.extend(nucleus) + + caches = cmds.ls(particles_history, long=True, type="cacheFile") + self.log.info("Collected caches: {0}".format(caches)) + export.extend(caches) + + # Collect input shapes for the instancer + for instancer in cmds.ls(instancers, exactType="instancer", long=True): + attr = "{}.inputHierarchy".format(instancer) + inputs = cmds.listConnections(attr, source=True, + destination=False) or [] + export.extend(inputs) + + # Add it to the instance + data = instance[:] + data.extend(export) + # Ensure unique objects only + data = list(set(data)) + self.log.info("Setting members to {0}".format(data)) + instance[:] = data + + # Store the recommended export selection so the export can do it + # accordingly + instance.data["exactExportMembers"] = export diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py new file mode 100644 index 0000000000..498b4fb30c --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -0,0 +1,117 @@ +from maya import cmds + +import pyblish.api + + +class CollectMindbenderInstances(pyblish.api.ContextPlugin): + """Gather instances by objectSet and pre-defined attribute + + This collector takes into account assets that are associated with + an objectSet and marked with a unique identifier; + + Identifier: + id (str): "pyblish.avalon.instance" + + Supported Families: + avalon.model: Geometric representation of artwork + avalon.rig: An articulated model for animators. + A rig may contain a series of sets in which to identify + its contents. + + - cache_SEL: Should contain cachable polygonal meshes + - controls_SEL: Should contain animatable controllers for animators + - resources_SEL: Should contain nodes that reference external files + + Limitations: + - Only Maya is supported + - One (1) rig per scene file + - Unmanaged history, it is up to the TD to ensure + history is up to par. + avalon.animation: Pointcache of `avalon.rig` + + Limitations: + - Does not take into account nodes connected to those + within an objectSet. Extractors are assumed to export + with history preserved, but this limits what they will + be able to achieve and the amount of data available + to validators. + + """ + + label = "Collect Mindbender Instances" + order = pyblish.api.CollectorOrder + hosts = ["maya"] + + def process(self, context): + objectset = cmds.ls("*.id", long=True, type="objectSet", + recursive=True, objectsOnly=True) + for objset in objectset: + + members = cmds.sets(objset, query=True) + if members is None: + self.log.info("Skipped following Set: \"%s\" " % objset) + continue + + if not cmds.attributeQuery("id", node=objset, exists=True): + continue + + id_attr = "{}.id".format(objset) + if cmds.getAttr(id_attr) != "pyblish.avalon.instance": + continue + + # The developer is responsible for specifying + # the family of each instance. + has_family = cmds.attributeQuery("family", node=objset, + exists=True) + assert has_family, "\"%s\" was missing a family" % objset + + data = dict() + + # Apply each user defined attribute as data + for attr in cmds.listAttr(objset, userDefined=True) or list(): + try: + value = cmds.getAttr("{}.{}".format(objset, attr)) + except Exception: + # Some attributes cannot be read directly, + # such as mesh and color attributes. These + # are considered non-essential to this + # particular publishing pipeline. + value = None + + data[attr] = value + + # Collect members + members = cmds.ls(members, long=True) or [] + children = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) + parents = self.get_all_parents(members) + members_hierarchy = list(set(members + children + parents)) + + # Create the instance + name = cmds.ls(objset, long=False)[0] # use short name + instance = context.create_instance(data.get("name", name)) + instance[:] = members_hierarchy + instance.data["setMembers"] = members + instance.data.update(data) + + # Produce diagnostic message for any graphical + # user interface interested in visualising it. + self.log.info("Found: \"%s\" " % instance.data["name"]) + + def get_all_parents(self, nodes): + """Get all parents by using string operations (optimization) + + Args: + nodes (list): the nodes which are found in the objectSet + + Returns: + list + """ + parents = [] + for node in nodes: + splitted = node.split("|") + items = ["|".join(splitted[0:i]) for i in range(2, len(splitted))] + parents.extend(items) + + return list(set(parents)) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py new file mode 100644 index 0000000000..cd4593ffcd --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -0,0 +1,221 @@ +from maya import cmds + +from cb.utils.maya import context +import cbra.utils.maya.node_uuid as id_utils +import pyblish.api + + +SHAPE_ATTRS = ["castsShadows", + "receiveShadows", + "motionBlur", + "primaryVisibility", + "smoothShading", + "visibleInReflections", + "visibleInRefractions", + "doubleSided", + "opposite"] +SHAPE_ATTRS = set(SHAPE_ATTRS) + + +def get_look_attrs(node): + """Returns attributes of a node that are important for the look. + + These are the "changed" attributes (those that have edits applied + in the current scene). + + Returns: + list: Attribute names to extract + + """ + + result = cmds.listAttr(node, userDefined=True, + changedSinceFileOpen=True) or [] + + # For shapes allow render stat changes + if cmds.objectType(node, isAType="shape"): + attrs = cmds.listAttr(node, changedSinceFileOpen=True) or [] + valid = [attr for attr in attrs if attr in SHAPE_ATTRS] + result.extend(valid) + + if "cbId" in result: + result.remove("cbId") + + return result + + +class CollectLook(pyblish.api.InstancePlugin): + """Collect look data for instance. + + For the shapes/transforms of the referenced object to collect look for + retrieve the user-defined attributes (like V-ray attributes) and their + values as they were created in the current scene. + + For the members of the instance collect the sets (shadingEngines and + other sets, e.g. VRayDisplacement) they are in along with the exact + membership relations. + + Collects: + lookAttribtutes (list): Nodes in instance with their altered attributes + lookSetRelations (list): Sets and their memberships + lookSets (list): List of set names included in the look + + """ + + order = pyblish.api.CollectorOrder + 0.4 + families = ["colorbleed.look"] + label = "Collect Look" + hosts = ["maya"] + + # Ignore specifically named sets (check with endswith) + IGNORE = ["pointcache_SET", "controls_SET", "_INST"] + + def collect(self, instance): + + # Whether to log information verbosely + verbose = instance.data.get("verbose", False) + + self.log.info("Looking for look associations " + "for {0}..".format(instance.data['label'])) + + # Get view sets (so we can ignore those sets later) + model_panels = cmds.getPanel(type="modelPanel") + view_sets = set() + + for panel in model_panels: + view_set = cmds.modelEditor(panel, query=True, viewObjects=True) + if view_set: + view_sets.add(view_set) + + # Discover related object sets + self.log.info("Gathering sets..") + sets = dict() + for node in instance: + + node_sets = cmds.listSets(object=node, extendToShape=False) or [] + if verbose: + self.log.info("Found raw sets " + "{0} for {1}".format(node_sets, node)) + + if not node_sets: + continue + + # Exclude deformer sets + deformer_sets = cmds.listSets(object=node, + extendToShape=False, + type=2) or [] + deformer_sets = set(deformer_sets) # optimize lookup + node_sets = [s for s in node_sets if s not in deformer_sets] + + if verbose: + self.log.debug("After filtering deformer sets " + "{0}".format(node_sets)) + + # Ignore specifically named sets + node_sets = [s for s in node_sets if + not any(s.endswith(x) for x in self.IGNORE)] + + if verbose: + self.log.debug("After filtering ignored sets " + "{0}".format(node_sets)) + + # Ignore viewport filter view sets (from isolate select and + # viewports) + node_sets = [s for s in node_sets if s not in view_sets] + + if verbose: + self.log.debug("After filtering view sets {0}".format(node_sets)) + + self.log.info("Found sets {0} for {1}".format(node_sets, node)) + + for objset in node_sets: + if objset not in sets: + sets[objset] = {"name": objset, + "uuid": id_utils.get_id(objset), + "members": list()} + + # Lookup with absolute names (from root namespace) + instance_lookup = set([str(x) for x in cmds.ls(instance, + long=True, + absoluteName=True)]) + + self.log.info("Gathering set relations..") + for objset in sets: + self.log.debug("From %s.." % objset) + content = cmds.sets(objset, query=True) + for member in cmds.ls(content, long=True, absoluteName=True): + + node, components = (member.rsplit(".", 1) + [None])[:2] + + # Only include valid members of the instance + if node not in instance_lookup: + if verbose: + self.log.info("Skipping member %s" % member) + continue + + if member in [m["name"] for m in sets[objset]["members"]]: + continue + + if verbose: + self.log.debug("Such as %s.." % member) + + member_data = {"name": node, "uuid": id_utils.get_id(node)} + + # Include components information when components are assigned + if components: + member_data["components"] = components + + sets[objset]["members"].append(member_data) + + # Remove sets that didn't have any members assigned in the end + for objset, data in sets.items(): + if not data['members']: + self.log.debug("Removing redundant set " + "information: {0}".format(objset)) + sets.pop(objset) + + # Member attributes (shapes + transforms) + + self.log.info("Gathering attribute changes to instance members..") + attrs = [] + for node in instance: + + # Collect changes to "custom" attributes + node_attrs = get_look_attrs(node) + + # Only include if there are any properties we care about + if not node_attrs: + continue + + attributes = {} + for attr in node_attrs: + attribute = "{}.{}".format(node, attr) + attributes[attr] = cmds.getAttr(attribute) + + # attributes = dict((attr, pm.getAttr("{}.{}".format(node, attr)) + # for attr in node_attrs)) + data = {"name": node, + "uuid": id_utils.get_id(node), + "attributes": attributes} + + attrs.append(data) + + # Store data on the instance + instance.data["lookAttributes"] = attrs + instance.data["lookSetRelations"] = sets.values() + instance.data["lookSets"] = cmds.ls(sets.keys(), + absoluteName=True, + long=True) + + # Log a warning when no relevant sets were retrieved for the look. + if not instance.data['lookSets']: + self.log.warning("No sets found for the nodes in the instance: {0}".format(instance[:])) + + self.log.info("Collected look for %s" % instance) + + def process(self, instance): + """Collect the Look in the instance with the correct layer settings""" + + layer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(layer): + self.log.info("Checking out layer: {0}".format(layer)) + self.collect(instance) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py new file mode 100644 index 0000000000..772deaeba1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -0,0 +1,135 @@ +from maya import cmds + +import pyblish.api + +import cb.utils.maya.shaders as shader + +TAGS = ["maya", "attribute", "look"] +TAGS_LOOKUP = set(TAGS) + + +class SelectTextureNodesAction(pyblish.api.Action): + """Select the nodes related to the collected file textures""" + + label = "Select texture nodes" + on = "succeeded" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + + self.log.info("Finding textures..") + + # Get the errored instances + instances = [] + for result in context.data["results"]: + instance = result["instance"] + if instance is None: + continue + + instances.append(instance) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(instances, plugin) + + def is_texture_resource(resource): + """Return whether the resource is a texture""" + + tags = resource.get("tags", []) + if not TAGS_LOOKUP.issubset(tags): + return False + + if resource.get("subfolder", None) != "textures": + return False + + if "node" not in resource: + return False + + return True + + # Get the texture nodes from the instances + nodes = [] + for instance in instances: + for resource in instance.data.get("resources", []): + if is_texture_resource(resource): + node = resource['node'] + nodes.append(node) + + # Ensure unique + nodes = list(set(nodes)) + + if nodes: + self.log.info("Selecting texture nodes: %s" % ", ".join(nodes)) + cmds.select(nodes, r=True, noExpand=True) + else: + self.log.info("No texture nodes found.") + cmds.select(deselect=True) + + +class CollectLookTextures(pyblish.api.InstancePlugin): + """Collect look textures + + Includes the link from source to destination. + + """ + + order = pyblish.api.CollectorOrder + 0.498 + label = 'Textures' + families = ["colorbleed.look"] + actions = [SelectTextureNodesAction] + + def process(self, instance): + + verbose = instance.data.get("verbose", False) + + # Get textures from sets + sets = instance.data["lookSets"] + if not sets: + raise RuntimeError("No look sets found for the nodes in the instance. {0}".format(sets)) + + # Get the file nodes + history = cmds.listHistory(sets) or [] + files = cmds.ls(history, type="file") + files = list(set(files)) + + resources = instance.data.get("resources", []) + for node in files: + + attribute = "%s.fileTextureName" % node + source = cmds.getAttr(attribute) + + # Get the computed file path (e.g. the one with the pattern + # in it) So we can reassign it this computed file path whenever + # we need to. + computed_attribute = "%s.computedFileTextureNamePattern" % node + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + if verbose: + self.log.debug("File node computed pattern differs from " + "original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) + + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = computed_source.replace("\\", "/") + + files = shader.get_file_node_files(node) + if not files: + self.log.error("File node does not have a texture set: " + "{0}".format(node)) + + # Define the resource + resource = {"tags": TAGS[:], + "node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files, # required for resources + "subfolder": "textures" # optional for resources + } + + resources.append(resource) + + # Store resources + instance.data['resources'] = resources diff --git a/colorbleed/plugins/maya/publish/collect_maya_units.py b/colorbleed/plugins/maya/publish/collect_maya_units.py new file mode 100644 index 0000000000..2421641d26 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_maya_units.py @@ -0,0 +1,30 @@ +import maya.cmds as cmds +import maya.mel as mel + +import pyblish.api + + +class CollectMayaUnits(pyblish.api.ContextPlugin): + """Collect Maya's scene units.""" + + label = "Maya Units" + order = pyblish.api.CollectorOrder + hosts = ["maya"] + + def process(self, context): + + # Get the current linear units + units = cmds.currentUnit(query=True, linear=True) + + # Get the current angular units ('deg' or 'rad') + units_angle = cmds.currentUnit(query=True, angle=True) + + # Get the current time units + # Using the mel command is simpler than using + # `cmds.currentUnit(q=1, time=1)`. Otherwise we + # have to parse the returned string value to FPS + fps = mel.eval('currentTimeUnitToFPS()') + + context.data['linearUnits'] = units + context.data['angularUnits'] = units_angle + context.data['fps'] = fps diff --git a/colorbleed/plugins/maya/publish/collect_metadata.py b/colorbleed/plugins/maya/publish/collect_metadata.py new file mode 100644 index 0000000000..b5924c25de --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_metadata.py @@ -0,0 +1,39 @@ +import pyblish.api +import copy + + +class CollectMetadata(pyblish.api.ContextPlugin): + """Transfer context metadata to the instance. + + This applies a copy of the `context.data['metadata']` to the + `instance.data['metadata']` for the following metadata: + + Provides: + { + "topic": "topic", + "author": "user", + "date": "date", + "filename": "currentFile" + } + + + """ + order = pyblish.api.CollectorOrder + 0.2 + label = "Metadata" + + mapping = {"topic": "topic", + "author": "user", + "date": "date", + "filename": "currentFile"} + + def process(self, context): + + metadata = {} + for key, source in self.mapping.iteritems(): + if source in context.data: + metadata[key] = context.data.get(source) + + for instance in context: + instance.data["metadata"] = copy.deepcopy(metadata) + + self.log.info("Collected {0}".format(metadata)) diff --git a/colorbleed/plugins/maya/publish/collect_model.py b/colorbleed/plugins/maya/publish/collect_model.py new file mode 100644 index 0000000000..b1294c7824 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_model.py @@ -0,0 +1,21 @@ +from maya import cmds + +import pyblish.api + + +class CollectModelData(pyblish.api.InstancePlugin): + """Collect model data + + Ensures always only a single frame is extracted (current frame). + + """ + + order = pyblish.api.CollectorOrder + 0.499 + label = 'Model Data' + families = ["colorbleed.model"] + + def process(self, instance): + # Extract only current frame (override) + frame = cmds.currentTime(query=True) + instance.data['startFrame'] = frame + instance.data['endFrame'] = frame diff --git a/colorbleed/plugins/maya/publish/collect_particles_history.py b/colorbleed/plugins/maya/publish/collect_particles_history.py new file mode 100644 index 0000000000..b562737f8f --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_particles_history.py @@ -0,0 +1,47 @@ +import maya.cmds as cmds + +import pyblish.api + + +class CollectParticlesHistory(pyblish.api.InstancePlugin): + """For a Particle system collect the history. + + This would collect its nucleus and cache files. + + """ + + order = pyblish.api.CollectorOrder + 0.499 + families = ['colorbleed.particles'] + label = "Particles History" + + def process(self, instance): + + # Include history of the instancer + particles = cmds.ls(instance, dag=True, shapes=True, + leaf=True, long=True) + particles = cmds.ls(particles, type="nParticle", long=True) + if not particles: + self.log.info("No particles found") + return + + export = particles + + # Get the required inputs of the particles from its history + particles_history = cmds.listHistory(particles) or [] + if particles_history: + nucleus = cmds.ls(particles_history, type="nucleus") + export.extend(nucleus) + caches = cmds.ls(particles_history, type="cacheFile") + export.extend(caches) + + # Add it to the instance + data = instance[:] + data.extend(export) + # Ensure unique objects only + data = list(set(data)) + self.log.info("Setting members to {0}".format(data)) + instance[:] = data + + # Store the recommended export selection so the export can do it + # accordingly + instance.data["exactExportMembers"] = export diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py new file mode 100644 index 0000000000..55b8fa2619 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -0,0 +1,235 @@ +import os +import json +import contextlib + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +@contextlib.contextmanager +def suspension(): + try: + cmds.refresh(suspend=True) + yield + finally: + cmds.refresh(suspend=False) + + +class ExtractAlembic(colorbleed.api.Extractor): + """Extract Alembic Cache + + This extracts an Alembic cache using the `-selection` flag to minimize + the extracted content to solely what was Collected into the instance. + + Arguments: + + startFrame (float): Start frame of output. Ignored if `frameRange` + provided. + + endFrame (float): End frame of output. Ignored if `frameRange` + provided. + + frameRange (str): Frame range in the format of "startFrame endFrame". + Overrides `startFrame` and `endFrame` arguments. + + dataFormat (str): The data format to use for the cache, + defaults to "ogawa" + + verbose (bool): When on, outputs frame number information to the + Script Editor or output window during extraction. + + noNormals (bool): When on, normal data from the original polygon + objects is not included in the exported Alembic cache file. + + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, + such as hidden objects, are not included in the Alembic file. + Defaults to False. + + stripNamespaces (bool): When on, any namespaces associated with the + exported objects are removed from the Alembic file. For example, an + object with the namespace taco:foo:bar appears as bar in the + Alembic file. + + uvWrite (bool): When on, UV data from polygon meshes and subdivision + objects are written to the Alembic file. Only the current UV map is + included. + + worldSpace (bool): When on, the top node in the node hierarchy is + stored as world space. By default, these nodes are stored as local + space. Defaults to False. + + eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with + an Euler filter. Euler filtering helps resolve irregularities in + rotations especially if X, Y, and Z rotations exceed 360 degrees. + Defaults to True. + """ + + label = "Alembic" + families = ["colorbleed.model", + "colorbleed.pointcache", + "colorbleed.animation", + "colorbleed.proxy"] + optional = True + + @property + def options(self): + """Overridable options for Alembic export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + return {"startFrame": float, + "endFrame": float, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "eulerFilter": bool, + "frameRelativeSample": float, + "noNormals": bool, + "renderableOnly": bool, + "step": float, + "stripNamespaces": bool, + "uvWrite": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeVisibility": bool, + "writeColorSets": bool, + "writeFaceSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "dataFormat": str, + "root": (list, tuple), + "attr": (list, tuple), + "attrPrefix": (list, tuple), + "userAttr": (list, tuple), + "melPerFrameCallback": str, + "melPostJobCallback": str, + "pythonPerFrameCallback": str, + "pythonPostJobCallback": str, + "selection": bool} + + @property + def default_options(self): + """Supply default options to extraction. + + This may be overridden by a subclass to provide + alternative defaults. + + """ + + start_frame = cmds.playbackOptions(query=True, animationStartTime=True) + end_frame = cmds.playbackOptions(query=True, animationEndTime=True) + + return {"startFrame": start_frame, + "endFrame": end_frame, + "selection": True, + "uvWrite": True, + "eulerFilter": True, + "dataFormat": "ogawa" # ogawa, hdf5 + } + + def process(self, instance): + # Ensure alembic exporter is loaded + cmds.loadPlugin('AbcExport', quiet=True) + + parent_dir = self.staging_dir(instance) + filename = "%s.abc" % instance.name + path = os.path.join(parent_dir, filename) + + # Alembic Exporter requires forward slashes + path = path.replace('\\', '/') + + options = self.default_options + options["userAttr"] = ("uuid",) + options = self.parse_overrides(instance, options) + + job_str = self.parse_options(options) + job_str += ' -file "%s"' % path + + self.log.info('Extracting alembic to: "%s"' % path) + + verbose = instance.data('verbose', False) + if verbose: + self.log.debug('Alembic job string: "%s"'% job_str) + + if not os.path.exists(parent_dir): + os.makedirs(parent_dir) + + with suspension(): + with pyblish_maya.maintained_selection(): + self.log.debug( + "Preparing %s for export using the following options: %s\n" + "and the following string: %s" + % (list(instance), + json.dumps(options, indent=4), + job_str)) + cmds.select(instance.data("setMembers"), hierarchy=True) + cmds.AbcExport(j=job_str, verbose=verbose) + + def parse_overrides(self, instance, options): + """Inspect data of instance to determine overridden options + + An instance may supply any of the overridable options + as data, the option is then added to the extraction. + + """ + + for key in instance.data(): + if key not in self.options: + continue + + # Ensure the data is of correct type + value = instance.data(key) + if not isinstance(value, self.options[key]): + self.log.warning( + "Overridden attribute {key} was of " + "the wrong type: {invalid_type} " + "- should have been {valid_type}".format( + key=key, + invalid_type=type(value).__name__, + valid_type=self.options[key].__name__)) + continue + + options[key] = value + + return options + + @classmethod + def parse_options(cls, options): + """Convert key-word arguments to job arguments string + + Args: + options (dict): the options for the command + """ + + # Convert `startFrame` and `endFrame` arguments + if 'startFrame' in options or 'endFrame' in options: + start_frame = options.pop('startFrame', None) + end_frame = options.pop('endFrame', None) + + if 'frameRange' in options: + cls.log.debug("The `startFrame` and/or `endFrame` arguments " + "are overridden by the provided `frameRange`.") + elif start_frame is None or end_frame is None: + cls.log.warning("The `startFrame` and `endFrame` arguments " + "must be supplied together.") + else: + options['frameRange'] = "%s %s" % (start_frame, end_frame) + + job_args = list() + for key, value in options.items(): + if isinstance(value, (list, tuple)): + for entry in value: + job_args.append("-%s %s" % (key, entry)) + elif isinstance(value, bool): + job_args.append("%s" % key) + else: + job_args.append("-%s %s" % (key, value)) + + job_str = " ".join(job_args) + + return job_str diff --git a/colorbleed/plugins/maya/publish/extract_camera_baked.py b/colorbleed/plugins/maya/publish/extract_camera_baked.py new file mode 100644 index 0000000000..89db766631 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_baked.py @@ -0,0 +1,140 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + +import cb.utils.maya.context as context +from cb.utils.maya.animation import bakeToWorldSpace + + +def massage_ma_file(path): + """Clean up .ma file for backwards compatibility. + + Massage the .ma of baked camera to stay + backwards compatible with older versions + of Fusion (6.4) + + """ + # Get open file's lines + f = open(path, "r+") + lines = f.readlines() + f.seek(0) # reset to start of file + + # Rewrite the file + for line in lines: + # Skip all 'rename -uid' lines + stripped = line.strip() + if stripped.startswith("rename -uid "): + continue + + f.write(line) + + f.truncate() # remove remainder + f.close() + + +class ExtractCameraBaked(colorbleed.api.Extractor): + """Extract as Maya Ascii and Alembic a baked camera. + + The cameras gets baked to world space and then extracted. + + The extracted Maya ascii file gets "massaged" removing the uuid values + so they are valid for older versions of Fusion (e.g. 6.4) + + """ + + label = "Camera Baked (Maya Ascii + Alembic)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + nodetype = 'camera' + + # Define extract output file path + dir_path = self.staging_dir(instance) + alembic_as_baked = instance.data("cameraBakedAlembic", True) + + # get cameras + members = instance.data['setMembers'] + cameras = cmds.ls(members, leaf=True, shapes=True, + dag=True, type=nodetype) + + # Bake the cameras + transforms = cmds.listRelatives(cameras, parent=True, + fullPath=True) or [] + + framerange = [instance.data.get("startFrame", 1), + instance.data.get("endFrame", 1)] + + self.log.info("Performing camera bakes for: {0}".format(transforms)) + with context.evaluation("off"): + with context.no_refresh(): + baked = bakeToWorldSpace(transforms, frameRange=framerange) + + # Extract using the shape so it includes that and its hierarchy + # above. Otherwise Alembic takes only the transform + baked_shapes = cmds.ls(baked, type=nodetype, dag=True, + shapes=True, long=True) + + # Perform maya ascii extraction + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(baked_shapes, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + massage_ma_file(path) + + # Perform alembic extraction + filename = "{0}.abc".format(instance.name) + path = os.path.join(dir_path, filename) + + if alembic_as_baked: + abc_shapes = baked_shapes + else: + # get cameras in the instance + members = instance.data['setMembers'] + abc_shapes = cmds.ls(members, leaf=True, shapes=True, dag=True, + long=True, type=nodetype) + + # Whenever the camera was baked and Maya's scene time warp was enabled + # then we want to disable it whenever we publish the baked camera + # otherwise we'll get double the scene time warping. But whenever + # we *do not* publish a baked camera we want to keep it enabled. This + # way what the artist has in the scene visually represents the output. + with context.timewarp(state=not alembic_as_baked): + with pyblish_maya.maintained_selection(): + cmds.select(abc_shapes, replace=True, noExpand=True) + + # Enforce forward slashes for AbcExport because we're + # embedding it into a job string + path = path.replace("\\", "/") + + job_str = ' -selection -dataFormat "ogawa" ' + job_str += ' -attrPrefix cb' + job_str += ' -frameRange {0} {1} '.format(framerange[0], + framerange[1]) + job_str += ' -file "{0}"'.format(path) + + with context.evaluation("off"): + with context.no_refresh(): + cmds.AbcExport(j=job_str, verbose=False) + + # Delete the baked camera (using transform to leave no trace) + cmds.delete(baked) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_raw.py b/colorbleed/plugins/maya/publish/extract_camera_raw.py new file mode 100644 index 0000000000..43c74a8471 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_raw.py @@ -0,0 +1,46 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +class ExtractCameraRaw(colorbleed.api.Extractor): + """Extract as Maya Ascii + + Includes constraints and channels + + """ + + label = "Camera Raw (Maya Ascii)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.raw.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # get cameras + cameras = cmds.ls(instance.data['setMembers'], leaf=True, + shapes=True, dag=True, type='camera') + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(cameras, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=True, + shader=False, + expressions=False) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_instancer.py b/colorbleed/plugins/maya/publish/extract_instancer.py new file mode 100644 index 0000000000..3d76cff89c --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_instancer.py @@ -0,0 +1,144 @@ +import os +import contextlib + +import maya.cmds as cmds + +import pyblish_maya +import colorbleed.api + +import cb.utils.maya.context as context + + +def _set_cache_file_path(node, path): + """Forces a cacheFile.cachePath attribute to be set to path. + + When the given path does not exist Maya will raise an error + when using `maya.cmds.setAttr` to set the "cachePath" attribute. + + Arguments: + node (str): Name of cacheFile node. + path (str): Path value to set. + + """ + + path = str(path) + + # Temporary unique attribute name + attr = "__tmp_path" + while cmds.attributeQuery(attr, node=node, exists=True): + attr += "_" + + # Create the temporary attribute, set its value and connect + # it to the `.cachePath` attribute to force the value to be + # set and applied without errors. + cmds.addAttr(node, longName=attr, dataType="string") + plug = "{0}.{1}".format(node, attr) + try: + cmds.setAttr(plug, path, type="string") + cmds.connectAttr(plug, + "{0}.cachePath".format(node), + force=True) + finally: + # Ensure the temporary attribute is deleted + cmds.deleteAttr(plug) + + +@contextlib.contextmanager +def cache_file_paths(mapping): + """Set the cacheFile paths during context. + + This is a workaround context manager that allows + to set the .cachePath attribute to a folder that + doesn't actually exist since using regular + `maya.cmds.setAttr` results in an error. + + Arguments: + mapping (dict): node -> path mapping + + """ + + # Store the original values + original = dict() + for node in mapping: + original[node] = cmds.getAttr("{}.cachePath".format(node)) + + try: + for node, path in mapping.items(): + _set_cache_file_path(node, path) + yield + finally: + for node, path in original.items(): + _set_cache_file_path(node, path) + + +def is_cache_resource(resource): + """Return whether resource is a cacheFile resource""" + start_tags = ["maya", "node", "cacheFile"] + required = set(start_tags) + tags = resource.get("tags", []) + return required.issubset(tags) + + +class ExtractInstancerMayaAscii(colorbleed.api.Extractor): + """Extract as Maya Ascii""" + + label = "Instancer (Maya Ascii)" + hosts = ["maya"] + families = ["colorbleed.instancer"] + + # TODO: Find other solution than expanding vars to fix lack of support of cacheFile + + def process(self, instance): + + export = instance.data("exactExportMembers") + + # Set up cacheFile path remapping. + resources = instance.data.get("resources", []) + attr_remap = dict() + cache_remap = dict() + for resource in resources: + if not is_cache_resource(resource): + continue + + node = resource['node'] + destination = resource['destination'] + + folder = os.path.dirname(destination) + fname = os.path.basename(destination) + if fname.endswith(".xml"): + fname = fname[:-4] + + # Ensure the folder path ends with a slash + if not folder.endswith("\\") and not folder.endswith("/"): + folder += "/" + + # Set path and name + attr_remap["{0}.cacheName".format(node)] = os.path.expandvars(fname) + cache_remap[node] = os.path.expandvars(folder) + + self.log.info("Mapping {0} to {1}".format(node, destination)) + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + with cache_file_paths(cache_remap): + with context.attribute_values(attr_remap): + cmds.select(export, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py new file mode 100644 index 0000000000..17a9f894c9 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py @@ -0,0 +1,38 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +class ExtractLayoutMayaAscii(colorbleed.api.Extractor): + """Extract as Maya Ascii""" + + label = "Layout (Maya ASCII)" + hosts = ["maya"] + families = ["colorbleed.layout"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(instance, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=True, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py new file mode 100644 index 0000000000..a0b9b3daf0 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -0,0 +1,79 @@ +import os +import json + +from maya import cmds + +import pyblish_maya +import colorbleed.api + +import cb.utils.maya.context as context + + +class ExtractLook(colorbleed.api.Extractor): + """Extract Look (Maya Ascii + JSON) + + Only extracts the sets (shadingEngines and alike) alongside a .json file + that stores it relationships for the sets and "attribute" data for the + instance members. + + """ + + label = "Look (Maya ASCII + JSON)" + hosts = ["maya"] + families = ["colorbleed.look"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + maya_fname = "{0}.ma".format(instance.name) + json_fname = "{0}.json".format(instance.name) + + maya_path = os.path.join(dir_path, maya_fname) + json_path = os.path.join(dir_path, json_fname) + + self.log.info("Performing extraction..") + + # Remove all members of the sets so they are not included in the + # exported file by accident + self.log.info("Extract sets (Maya ASCII)..") + sets = instance.data["lookSets"] + + # Define the texture file node remapping + resource_remap = dict() + required = ["maya", "attribute", "look"] # required tags to be a look resource + resources = instance.data.get("resources", []) + for resource in resources: + resource_tags = resource.get("tags", []) + if all(tag in resource_tags for tag in required): + node = resource['node'] + destination = resource['destination'] + resource_remap["{}.fileTextureName".format(node)] = destination + + # Extract in corect render layer + layer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(layer): + # TODO: Ensure membership edits don't become renderlayer overrides + with context.empty_sets(sets): + with context.attribute_values(resource_remap): + with pyblish_maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) + + # Write the JSON data + self.log.info("Extract json..") + data = {"attributes": instance.data["lookAttributes"], + "sets": instance.data["lookSetRelations"]} + with open(json_path, "w") as f: + json.dump(data, f) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, + maya_path)) diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii.py b/colorbleed/plugins/maya/publish/extract_maya_ascii.py new file mode 100644 index 0000000000..7b5bc09383 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii.py @@ -0,0 +1,35 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +class ExtractMayaAscii(colorbleed.api.Extractor): + """Extract as Maya Ascii""" + + label = "Maya ASCII" + hosts = ["maya"] + families = ["colorbleed.rig"] + optional = True + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(instance, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=True) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py new file mode 100644 index 0000000000..7f52388581 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py @@ -0,0 +1,49 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +class ExtractMayaAsciiRaw(colorbleed.api.Extractor): + """Extract as Maya Ascii (raw) + + This will preserve all references, construction history, etc. + + """ + + label = "Maya ASCII (Raw)" + hosts = ["maya"] + families = ["colorbleed.mayaAscii"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Whether to include all nodes in the instance (including those from + # history) or only use the exact set members + members_only = instance.data.get("exactSetMembersOnly", False) + if members_only: + members = instance.data.get("setMembers", list()) + if not members: + raise RuntimeError("Can't export 'exact set members only' " + "when set is empty.") + else: + members = instance[:] + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(members, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=True, + constructionHistory=True) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_metadata.py b/colorbleed/plugins/maya/publish/extract_metadata.py new file mode 100644 index 0000000000..6c5ff8cf61 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_metadata.py @@ -0,0 +1,21 @@ +import os +import json +import colorbleed.api + + +class ExtractMetadata(colorbleed.api.Extractor): + """Extract origin metadata from scene""" + + label = "Metadata" + + def process(self, instance): + + temp_dir = self.staging_dir(instance) + temp_file = os.path.join(temp_dir, "metadata.meta") + + metadata = instance.data("metadata") + self.log.info("Extracting %s" % metadata) + with open(temp_file, "w") as f: + json.dump(metadata, f, indent=2, sort_keys=True) + + self.log.info("Written to %s" % temp_file) diff --git a/colorbleed/plugins/maya/publish/extract_model.py b/colorbleed/plugins/maya/publish/extract_model.py new file mode 100644 index 0000000000..19b55e03a1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_model.py @@ -0,0 +1,79 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + +from cb.utils.maya import context + + +class ExtractModel(colorbleed.api.Extractor): + """Extract as Model (Maya Ascii) + + Only extracts contents based on the original "setMembers" data to ensure + publishing the least amount of required shapes. From that it only takes + the shapes that are not intermediateObjects + + During export it sets a temporary context to perform a clean extraction. + The context ensures: + - Smooth preview is turned off for the geometry + - Default shader is assigned (no materials are exported) + - Remove display layers + + """ + + label = "Model (Maya ASCII)" + hosts = ["maya"] + families = ["colorbleed.model"] + optional = True + + def process(self, instance): + + # Define extract output file path + stagingdir = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(stagingdir, filename) + + # Perform extraction + self.log.info("Performing extraction..") + + # Get only the shape contents we need in such a way that we avoid + # taking along intermediateObjects + members = instance.data("setMembers") + members = cmds.ls(members, + dag=True, + shapes=True, + type=("mesh", "nurbsCurve"), + noIntermediate=True, + long=True) + + with context.no_display_layers(instance): + with context.displaySmoothness(members, + divisionsU=0, + divisionsV=0, + pointsWire=4, + pointsShaded=1, + polygonObject=1): + with context.shader(members, + shadingEngine="initialShadingGroup"): + with pyblish_maya.maintained_selection(): + cmds.select(members, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=False, + constraints=False, + expressions=False, + constructionHistory=False) + + # Store reference for integration + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_particles.py b/colorbleed/plugins/maya/publish/extract_particles.py new file mode 100644 index 0000000000..d47e2c9a9b --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_particles.py @@ -0,0 +1,43 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + + +class ExtractParticlesMayaAscii(colorbleed.api.Extractor): + """Extract as Maya Ascii""" + + label = "Particles (Maya Ascii)" + hosts = ["maya"] + families = ["colorbleed.particles"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + export = instance.data("exactExportMembers") + + # TODO: Transfer cache files and relink temporarily on the particles + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + cmds.select(export, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py new file mode 100644 index 0000000000..d380f0b86b --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py @@ -0,0 +1,62 @@ +import os + +from maya import cmds + +import pyblish_maya +import colorbleed.api + +import cb.utils.maya.context as context + + +class ExtractFurYeti(colorbleed.api.Extractor): + """Extract as Yeti nodes""" + + label = "Yeti Nodes" + hosts = ["maya"] + families = ["colorbleed.groom"] + + def process(self, instance): + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing extraction..") + + # Get only the shape contents we need in such a way that we avoid + # taking along intermediateObjects + members = instance.data("setMembers") + members = cmds.ls(members, + dag=True, + shapes=True, + type="pgYetiMaya", + noIntermediate=True, + long=True) + + # Remap cache files names and ensure fileMode is set to load from cache + resource_remap = dict() + required = ["maya", "yeti", "attribute"] # required tags to be a yeti resource + resources = instance.data.get("resources", []) + for resource in resources: + resource_tags = resource.get("tags", []) + if all(tag in resource_tags for tag in required): + attribute = resource['attribute'] + destination = resource['destination'] + resource_remap[attribute] = destination + + # Perform extraction + with pyblish_maya.maintained_selection(): + with context.attribute_values(resource_remap): + cmds.select(members, r=True, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + shader=False) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/validate_camera_attributes.py b/colorbleed/plugins/maya/publish/validate_camera_attributes.py new file mode 100644 index 0000000000..46b9de7ecd --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_camera_attributes.py @@ -0,0 +1,67 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateCameraAttributes(pyblish.api.InstancePlugin): + """Validates Camera has no invalid attribute keys or values. + + The Alembic file format does not a specifc subset of attributes as such + we validate that no values are set there as the output will not match the + current scene. For example the preScale, film offsets and film roll. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.camera'] + hosts = ['maya'] + label = 'Camera Attributes' + actions = [colorbleed.api.SelectInvalidAction] + + DEFAULTS = [ + ("filmFitOffset", 0.0), + ("horizontalFilmOffset", 0.0), + ("verticalFilmOffset", 0.0), + ("preScale", 1.0), + ("filmTranslateH", 0.0), + ("filmTranslateV", 0.0), + ("filmRollValue", 0.0) + ] + + @classmethod + def get_invalid(cls, instance): + + # get cameras + members = instance.data['setMembers'] + shapes = cmds.ls(members, dag=True, shapes=True, long=True) + cameras = cmds.ls(shapes, type='camera', long=True) + + invalid = set() + for cam in cameras: + + for attr, default_value in cls.DEFAULTS: + plug = "{}.{}".format(cam, attr) + value = cmds.getAttr(plug) + + # Check if is default value + if value != default_value: + cls.log.warning("Invalid attribute value: {0} " + "(should be: {1}))".format(plug, + default_value)) + invalid.add(cam) + + if cmds.listConnections(plug, source=True, destination=False): + # TODO: Validate correctly whether value always correct + cls.log.warning("%s has incoming connections, validation " + "is unpredictable." % plug) + + return list(invalid) + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Invalid camera attributes: %s" % invalid) diff --git a/colorbleed/plugins/maya/publish/validate_camera_contents.py b/colorbleed/plugins/maya/publish/validate_camera_contents.py new file mode 100644 index 0000000000..162cc89786 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_camera_contents.py @@ -0,0 +1,64 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateCameraContents(pyblish.api.InstancePlugin): + """Validates Camera instance contents. + + A Camera instance may only hold a SINGLE camera's transform, nothing else. + + It may hold a "locator" as shape, but different shapes are in the up- + and down + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.camera'] + hosts = ['maya'] + label = 'Camera Contents' + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + # get cameras + members = instance.data['setMembers'] + shapes = cmds.ls(members, dag=True, shapes=True, long=True) + + # single camera + invalid = [] + cameras = cmds.ls(shapes, type='camera', long=True) + if len(cameras) != 1: + cls.log.warning("Camera instance must have a single camera. " + "Found {0}: {1}".format(len(cameras), cameras)) + invalid.extend(cameras) + + # We need to check this edge case because returning an extended + # list when there are no actual cameras results in + # still an empty 'invalid' list + if len(cameras) < 1: + raise RuntimeError("No cameras in instance.") + + # non-camera shapes + valid_shapes = cmds.ls(shapes, type=('camera', 'locator'), long=True) + shapes = set(shapes) - set(valid_shapes) + if shapes: + shapes = list(shapes) + cls.log.warning("Camera instance should only contain camera " + "shapes. Found: {0}".format(shapes)) + invalid.extend(shapes) + + invalid = list(set(invalid)) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid camera contents: " + "{0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py new file mode 100644 index 0000000000..82b2002677 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -0,0 +1,50 @@ +import pyblish.api +import colorbleed.api + + +class ValidateFrameRange(pyblish.api.InstancePlugin): + """Valides the frame ranges. + + Checks the `startFrame`, `endFrame` and `handles` data. + This does NOT ensure there's actual data present. + + This validates: + - `startFrame` is lower than or equal to the `endFrame`. + - must have both the `startFrame` and `endFrame` data. + - The `handles` value is not lower than zero. + + """ + + order = colorbleed.api.ValidateContentsOrder + + label = "Frame Range" + + def process(self, instance): + + start = instance.data.get("startFrame", None) + end = instance.data.get("endFrame", None) + handles = instance.data.get("handles", None) + + # Check if any of the values are present. If not we'll assume the + # current instance does not require any time values. + if all(value is None for value in [start, end, handles]): + self.log.debug("No time values for this instance. " + "(Missing `startFrame`, `endFrame` or `handles`)") + return + + # If only one of the two raise an error, it will require both. + has_start = int(start is not None) + has_end = int(end is not None) + if has_start + has_end == 1: + raise RuntimeError("Only a start frame or an end frame is provided" + " instead of both.") + + if has_start and has_end: + self.log.info("Comparing start (%s) and end (%s)" % (start, end)) + if start > end: + raise RuntimeError("The start frame is a higher value " + "than the end frame: {0}>{1}".format(start, end)) + + if handles is not None: + if handles < 0.0: + raise RuntimeError("Handles are set to a negative value") diff --git a/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py b/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py new file mode 100644 index 0000000000..7160ceaeec --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py @@ -0,0 +1,91 @@ +import re +from collections import defaultdict + +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + + +def get_gpu_cache_subnodes(cache): + """Return the amount of subnodes in the cache + + This uses `maya.cmds.gpuCache(showStats=True)` and parses + the resulting stats for the amount of internal sub nodes. + + Args: + cache (str): gpuCache node name. + + Returns: + int: Amount of subnodes in loaded gpuCache + + Raises: + TypeError: when `cache` is not a gpuCache object type. + RuntimeError: when `maya.cmds.gpuCache(showStats=True)` + does not return stats from which we can parse the + amount of subnodes. + + """ + + # Ensure gpuCache + if not cmds.objectType(cache, isType="gpuCache"): + raise TypeError("Node is not a gpuCache: {0}".format(cache)) + + stats = cmds.gpuCache(cache, query=True, showStats=True) + for line in stats.splitlines(): + match = re.search('nb of internal sub nodes: ([0-9]+)$', line) + if match: + return int(match.group(1)) + + raise RuntimeError("Couldn't parse amount of subnodes " + "in cache stats: {0}".format(cache)) + + +def get_empty_gpu_caches(caches): + empty = list() + + # Group caches per path (optimization) so + # we check each file only once + caches_per_path = defaultdict(list) + for cache in caches: + path = cmds.getAttr(cache + ".cacheFileName") + caches_per_path[path].append(cache) + + # We consider the cache empty if its stats + # result in 0 subnodes + for path, path_caches in caches_per_path.items(): + + cache = path_caches[0] + num = get_gpu_cache_subnodes(cache) + if num == 0: + empty.extend(path_caches) + + return empty + + +class ValidateGPUCacheNotEmpty(pyblish.api.InstancePlugin): + """Validates that gpuCaches have at least one visible shape in them. + + This is tested using the `maya.cmds.gpuCache(cache, showStats=True)` + command. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = 'GpuCache has subnodes' + families = ['colorbleed.layout'] + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + caches = cmds.ls(instance, type="gpuCache", long=True) + invalid = get_empty_gpu_caches(caches) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_instance_has_members.py b/colorbleed/plugins/maya/publish/validate_instance_has_members.py new file mode 100644 index 0000000000..b5787172c0 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_instance_has_members.py @@ -0,0 +1,27 @@ +import pyblish.api +import colorbleed.api + + +class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): + """Validates instance objectSet has *any* members.""" + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + label = 'Instance has members' + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + invalid = list() + if not instance.data["setMembers"]: + objectset_name = instance.data['subset'] + invalid.append(objectset_name) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Empty instances found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_instance_subset.py b/colorbleed/plugins/maya/publish/validate_instance_subset.py new file mode 100644 index 0000000000..42498941a3 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_instance_subset.py @@ -0,0 +1,49 @@ +import pyblish.api +import colorbleed.api +import string + +# Allow only characters, numbers and underscore +allowed = set(string.ascii_lowercase + + string.ascii_uppercase + + string.digits + + '_') + + +def validate_name(subset): + return all(x in allowed for x in subset) + + +class ValidateSubsetName(pyblish.api.InstancePlugin): + """Adheres to the content of 'model' family + + - Must have one top group named: geo_GRP + - Must only contain: transforms, meshes and groups + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ["*"] + label = "Subset Name" + + def process(self, instance): + + subset = instance.data.get("subset", None) + + # Ensure subset data + if subset is None: + raise RuntimeError("Instance is missing subset " + "name: {0}".format(subset)) + + if not isinstance(subset, basestring): + raise TypeError("Instance subset name must be string, " + "got: {0} ({1})".format(subset, type(subset))) + + # Ensure is not empty subset + if not subset: + raise ValueError("Instance subset name is " + "empty: {0}".format(subset)) + + # Validate subset characters + if not validate_name(subset): + raise ValueError("Instance subset name contains invalid " + "characters: {0}".format(subset)) diff --git a/colorbleed/plugins/maya/publish/validate_instancer_content.py b/colorbleed/plugins/maya/publish/validate_instancer_content.py new file mode 100644 index 0000000000..68c6fbc819 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_instancer_content.py @@ -0,0 +1,74 @@ +import pyblish.api +import maya.cmds as cmds +import cb.utils.maya.dag as dag + + +class ValidateInstancerContent(pyblish.api.InstancePlugin): + """Validates that all meshes in the instance have object IDs. + + This skips a check on intermediate objects because we consider them + not important. + """ + order = pyblish.api.ValidatorOrder + label = 'Instancer Content' + families = ['colorbleed.instancer'] + + def process(self, instance): + + invalid = False + members = instance.data['setMembers'] + export_members = instance.data['exactExportMembers'] + + self.log.info("Contents {0}".format(members)) + + if not len(members) == len(cmds.ls(members, type="instancer")): + self.log.error("Instancer can only contain instancers") + invalid = True + + # TODO: Implement better check for particles are cached + if not cmds.ls(export_members, type="nucleus"): + self.log.error("Instancer must have a connected nucleus") + invalid = True + + if not cmds.ls(export_members, type="cacheFile"): + self.log.error("Instancer must be cached") + invalid = True + + # Ensure all instanced geometry is hidden + shapes = cmds.ls(export_members, + dag=True, shapes=True, + noIntermediate=True) + meshes = cmds.ls(shapes, type="mesh") + + def invalidate(node): + """Whether mesh is in a valid state + + Arguments: + node (str): The node to check + + Returns: + bool: Whether it is in a valid state. + + """ + return dag.is_visible(node, + displayLayer=False, + intermediateObject=False) + + visible = [node for node in meshes if invalidate(node)] + if visible: + self.log.error("Instancer input geometry must be hidden " + "the scene. Invalid: {0}".format(visible)) + invalid = True + + # Ensure all in one group + parents = cmds.listRelatives(members, + allParents=True, + fullPath=True) or [] + roots = list(set(cmds.ls(parents, assemblies=True, long=True))) + if len(roots) > 1: + self.log.error("Instancer should all be contained in a single " + "group. Current roots: {0}".format(roots)) + invalid = True + + if invalid: + raise RuntimeError("Instancer Content is invalid. See log.") diff --git a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py new file mode 100644 index 0000000000..dd2bae089f --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py @@ -0,0 +1,151 @@ +import pyblish.api + +VERBOSE = False + + +def is_cache_resource(resource): + """Return whether resource is a cacheFile resource""" + required = set(["maya", "node", "cacheFile"]) + tags = resource.get("tags", []) + return required.issubset(tags) + + +class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): + """Validates all instancer particle systems are cached correctly. + + This means they should have the files/frames as required by the start-end + frame (including handles). + + This also checks the files exist and checks the "ticks" (substeps) files. + + """ + order = pyblish.api.ValidatorOrder + label = 'Instancer Cache Frame Ranges' + families = ['colorbleed.instancer'] + + @classmethod + def get_invalid(cls, instance): + + import os + import pyseq + + start_frame = instance.data.get("startFrame", 0) + end_frame = instance.data.get("endFrame", 0) + required = range(int(start_frame), int(end_frame) + 1) + + invalid = list() + resources = instance.data.get("resources", []) + + for resource in resources: + if not is_cache_resource(resource): + continue + + node = resource['node'] + all_files = resource['files'][:] + + all_lookup = set(all_files) + + # The first file is usually the .xml description file. + xml = all_files.pop(0) + assert xml.endswith(".xml") + + if VERBOSE: + cls.log.info("Checking: {0}".format(all_files)) + + # Ensure all files exist (including ticks) + # The remainder file paths should be the .mcx or .mcc files + for f in all_files: + assert os.path.exists(f) + assert f.endswith(".mcx") or f.endswith(".mcc") + + # Maya particle caches support substeps by saving out additional files + # that end with a Tick60.mcx, Tick120.mcx, etc. suffix. To avoid `pyseq` + # getting confused we filter those out and then for each file (except + # the last frame) check that at least all ticks exist. + tick_files = set() + ticks = set() + for path in all_files: + import re + match = re.match(".+Tick([0-9]+).mcx$", os.path.basename(path)) + + if match: + tick_files.add(path) + num = match.group(1) + ticks.add(int(num)) + + files = [f for f in all_files if f not in tick_files] if tick_files else all_files + sequences = pyseq.get_sequences(files) + + if len(sequences) != 1: + invalid.append(node) + cls.log.warning("More than one sequence found? " + "{0} {1}".format(node, files)) + cls.log.warning("Found caches: {0}".format(sequences)) + continue + + sequence = sequences[0] + cls.log.debug("Found sequence: {0}".format(sequence)) + + start = sequence.start() + end = sequence.end() + + if start > start_frame or end < end_frame: + invalid.append(node) + cls.log.warning("Sequence does not have enough " + "frames: {0}-{1} (requires: {2}-{3})" + "".format(start, end, + start_frame, + end_frame)) + continue + + # Ensure all frames are present + missing = set(sequence.missing()) + if missing: + required_missing = [x for x in required if x in missing] + if required_missing: + invalid.append(node) + cls.log.warning("Sequence is missing required frames: " + "{0}".format(required_missing)) + continue + + # Ensure all tick files (substep) exist for the files in the folder + # for the frames required by the time range. + if ticks: + ticks = list(sorted(ticks)) + cls.log.info("Found ticks: {0} (substeps: {1})".format(ticks, len(ticks))) + + # Check all frames except the last since we don't + # require subframes after our time range. + tick_check_frames = set(required[:-1]) + + # Check all frames + for item in sequence: + frame = item.frame + if not frame: + invalid.append(node) + cls.log.error("Path is not a frame in sequence: {0}".format(item)) + continue + + # Not required for our time range + if frame not in tick_check_frames: + continue + + path = item.path + for num in ticks: + base, ext = os.path.splitext(path) + tick_file = base + "Tick{0}".format(num) + ext + if tick_file not in all_lookup: + invalid.append(node) + cls.log.warning("Tick file found that is not " + "in cache query filenames: {0}".format(tick_file)) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + self.log.error("Invalid nodes: {0}".format(invalid)) + raise RuntimeError("Invalid particle caches in instance. " + "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/colorbleed/plugins/maya/publish/validate_joints_hidden.py new file mode 100644 index 0000000000..77bfe9f5de --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_joints_hidden.py @@ -0,0 +1,97 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def is_visible(node, + displayLayer=True, + intermediateObject=True, + parentHidden=True, + visibility=True): + """Is `node` visible? + + Returns whether a node is hidden by one of the following methods: + - The node exists (always checked) + - The node must be a dagNode (always checked) + - The node's visibility is off. + - The node is set as intermediate Object. + - The node is in a disabled displayLayer. + - Whether any of its parent nodes is hidden. + + Roughly based on: http://ewertb.soundlinker.com/mel/mel.098.php + + Returns: + bool: Whether the node is visible in the scene + + """ + + # Only existing objects can be visible + if not cmds.objExists(node): + return False + + # Only dagNodes can be visible + if not cmds.objectType(node, isAType='dagNode'): + return False + + if visibility: + if not cmds.getAttr('{0}.visibility'.format(node)): + return False + + if intermediateObject and cmds.objectType(node, isAType='shape'): + if cmds.getAttr('{0}.intermediateObject'.format(node)): + return False + + if displayLayer: + # Display layers set overrideEnabled and overrideVisibility on members + if cmds.attributeQuery('overrideEnabled', node=node, exists=True): + override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node)) + override_visibility = cmds.getAttr('{}.overrideVisibility'.format(node)) + if override_enabled and override_visibility: + return False + + if parentHidden: + parents = cmds.listRelatives(node, parent=True, fullPath=True) + if parents: + parent = parents[0] + if not is_visible(parent, + displayLayer=displayLayer, + intermediateObject=False, + parentHidden=parentHidden, + visibility=visibility): + return False + + return True + + +class ValidateJointsHidden(pyblish.api.InstancePlugin): + """Validate all joints are hidden visually. + + This includes being hidden: + - visibility off, + - in a display layer that has visibility off, + - having hidden parents or + - being an intermediate object. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.rig'] + category = 'rig' + version = (0, 1, 0) + label = "Joints Hidden" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + joints = cmds.ls(instance, type='joint', long=True) + return [j for j in joints if is_visible(j, displayLayer=True)] + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Visible joints found: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_latest_versions.py b/colorbleed/plugins/maya/publish/validate_latest_versions.py new file mode 100644 index 0000000000..0b7d2dd826 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_latest_versions.py @@ -0,0 +1,108 @@ +import os + +from maya import cmds + +import pyblish.api +import colorbleed.api + +import cbra.lib +from cb.utils.python.decorators import memorize + + +def is_latest_version(path): + """Return whether path is the latest version. + + Args: + path (str): Full path to published file. + + Returns: + bool: Whether the path belongs to the latest version. + + """ + + ctx = cbra.lib.parse_context(path) + versions = cbra.lib.list_versions(ctx) + highest = cbra.lib.find_highest_version(versions) + + if ctx.get('version', None) != highest: + return False + else: + return True + + +@memorize +def is_latest_version_cached(path): + """Memorized cached wrapper to `is_latest_version`""" + return is_latest_version(path) + + +class ValidateLatestVersions(pyblish.api.InstancePlugin): + """Validates content included is using latest published versions. + + If published contents are out of date they can be easily updated to the + latest version using the scripts > pyblish > utilities > update_xxx for + the corresponding node type. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.layout'] + label = "Latest Versions" + actions = [colorbleed.api.SelectInvalidAction] + optional = True + + # (node_type, attribute) that are non-referenced to check paths for + LOCAL_CHECKS = { + "gpuCache": "cacheFileName", + "VRayMesh": "fileName2" + } + + @classmethod + def get_invalid(cls, instance): + + all_nodes = instance[:] + invalid = list() + + # check non-referenced nodes + for node_type, attr in cls.LOCAL_CHECKS.iteritems(): + + nodes = cmds.ls(all_nodes, type=node_type, long=True) + referenced = cmds.ls(nodes, referencedNodes=True, long=True) + non_referenced = [n for n in nodes if n not in referenced] + + for node in non_referenced: + + path = cmds.getAttr("{0}.{1}".format(node, attr)) + path = os.path.normpath(path) + if not is_latest_version_cached(path): + invalid.append(node) + + # reference nodes related to this isntance + referenced = cmds.ls(all_nodes, long=True, referencedNodes=True) + referenced_nodes = set(cmds.referenceQuery(reference, referenceNode=True) + for reference in referenced) + + for reference in referenced_nodes: + path = cmds.referenceQuery(reference, + filename=True, + withoutCopyNumber=True) + path = os.path.normpath(path) + if not is_latest_version_cached(path): + invalid.append(reference) + + return invalid + + def process(self, instance): + + # Clear cache only once per publish. So we store a value on + # the context on the first instance so we clear only once. + name = self.__class__.__name__ + key = "_plugin_{0}_processed".format(name) + if not instance.context.data.get(key, False): + is_latest_version_cached.cache.clear() + instance.context.data[key] = True + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Used Items are not updated to latest versions:" + "{0}".format(invalid)) \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/validate_layout_content.py b/colorbleed/plugins/maya/publish/validate_layout_content.py new file mode 100644 index 0000000000..dd4f1e8c3d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_layout_content.py @@ -0,0 +1,87 @@ +import pyblish.api +import maya.cmds as cmds +import colorbleed.api +import pyblish_maya + +import cb.utils.maya.dag as dag + + +class ValidateLayoutContent(pyblish.api.InstancePlugin): + """Validates that layout contains at least a gpuCache or mesh shape node + + Also validates that (at the current frame that this is tested at) at least + a single shape is visible. + + Without any shape nodes the layout would simply cache 'nothing' visually + and would seem redundant. + + Note: Theoretically this validation does disable the possibility to just + cache some "transforms" to be used elsewhere. As such currently the + 'layout' family is only intended to be used for visual shapes. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = 'Layout Content' + families = ['colorbleed.layout'] + + def process(self, instance): + + placeholder = instance.data.get("placeholder", False) + + # Ensure any meshes or gpuCaches in instance + if not cmds.ls(instance, type=("mesh", "gpuCache", "nurbsCurve"), long=True): + raise RuntimeError("Layout has no mesh, gpuCache or nurbsCurve children: " + "{0}".format(instance)) + + # Ensure at least any extract nodes readily available after filtering + with pyblish_maya.maintained_selection(): + + import cbra.utils.maya.layout as layout + + nodes = instance.data['setMembers'] + cmds.select(nodes, r=1, hierarchy=True) + hierarchy = cmds.ls(sl=True, long=True) + extract_nodes = layout.filter_nodes(hierarchy) + + if not extract_nodes: + self.log.info("Set members: {0}".format(nodes)) + self.log.info("Hierarchy: {0}".format(hierarchy)) + raise RuntimeError("No nodes to extract after " + "filtering: {0}".format(extract_nodes)) + + # If no meshes in layout the gpuCache command will crash as such + # we consider this invalid, unless "placeholder" is set to True + meshes = cmds.ls(cmds.ls(extract_nodes, + dag=True, + leaf=True, + shapes=True, + noIntermediate=True, + long=True), + type=("mesh", "gpuCache"), + long=True) + if not meshes and not placeholder: + raise RuntimeError("No meshes in layout. " + "Set placeholder to True on instance to allow " + "extraction without meshes") + + # Ensure at least one MESH shape is visible + extract_shapes = cmds.ls(extract_nodes, + shapes=True, + long=True) + + if not placeholder: + # We validate that at least one shape is visible to avoid erroneous + # extractions of invisible-only content. + for shape in extract_shapes: + if dag.is_visible(shape, + displayLayer=False, + intermediateObject=True, + visibility=True, + parentHidden=True): + break + else: + raise RuntimeError("No extract shape is visible. " + "Layout requires at least one " + "shape to be visible.") + diff --git a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py new file mode 100644 index 0000000000..d56620271b --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py @@ -0,0 +1,45 @@ +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateLayoutNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have colorbleed id attributes + + All non-referenced transform nodes in the hierarchy should have unique IDs + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.layout'] + hosts = ['maya'] + label = 'Layout Transform Ids' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid(instance): + + from maya import cmds + + nodes = cmds.ls(instance, type='transform', long=True) + referenced = cmds.ls(nodes, referencedNodes=True, long=True) + non_referenced = set(nodes) - set(referenced) + + invalid = [] + for node in non_referenced: + if not id_utils.get_id(node): + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Transforms (non-referenced) found in layout " + "without asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py b/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py new file mode 100644 index 0000000000..79a1ec4198 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py @@ -0,0 +1,73 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + +from cb.utils.maya.core import getHighestInHierarchy, iterParents + +_IDENTITY = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + +_ATTRS = ['tx', 'ty', 'tz', + 'rx', 'ry', 'rz', + 'sx', 'sy', 'sz', + 'shearXY', 'shearXZ', 'shearYZ'] + + +def is_identity(node, tolerance=1e-30): + + mat = cmds.xform(node, query=True, matrix=True, objectSpace=True) + if not all(abs(x-y) < tolerance for x, y in zip(_IDENTITY, mat)): + return False + return True + + +def is_animated(node): + return any(cmds.listConnections("{}.{}".format(node, attr), source=True, + destination=False) for attr in _ATTRS) + + +class ValidateLayoutParentNoTransforms(pyblish.api.InstancePlugin): + """Validate layout parents have no transformations. + + The parent nodes above the extracted layout contents MUST have zero + transformation (no offsets in translate, rotate, scale) for this pass + validly. + + This is required to ensure no offsets are lacking from extracted caches. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.layout'] + hosts = ['maya'] + label = 'Layout No Parent Transforms' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + # Get highest in hierarchy + nodes = instance.data["setMembers"] + highest = getHighestInHierarchy(nodes) + + invalid = [] + + for node in highest: + for parent in iterParents(node): + if not is_identity(parent) or is_animated(parent): + invalid.append(parent) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Transforms (non-referenced) found in layout " + "without asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py new file mode 100644 index 0000000000..a36be266c6 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py @@ -0,0 +1,150 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +def get_id_from_history(node): + """Return the ID from the first node in the history of the same type + + If the node itself has an ID that will be returned. If no ID found None is + returned. + + Returns: + str: The id on first node in history + + """ + + nodeType = cmds.nodeType(node) + history = cmds.listHistory(node, leaf=False) or [] + similar = cmds.ls(history, exactType=nodeType, long=True) + + for node in similar: + id = id_utils.get_id(node) + if id: + return id + + +class CopyUUIDsFromHistoryAction(pyblish.api.Action): + """Copy UUIDs from the history of a node. + + This allows a deformed Shape to take its UUID from the original shape. + + """ + + label = "Copy UUIDs from History" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + + self.log.info("Finding bad nodes..") + + # Get the errored instances + errored_instances = [] + for result in context.data["results"]: + if result["error"] is not None and result["instance"] is not None: + if result["error"]: + instance = result["instance"] + errored_instances.append(instance) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the nodes from the all instances that ran through this plug-in + invalid = [] + for instance in instances: + invalid_nodes = plugin.get_invalid(instance) + invalid.extend(invalid_nodes) + + # Ensure unique + invalid = list(set(invalid)) + + if not invalid: + self.log.info("No invalid nodes found.") + return + + # Generate a mapping of UUIDs using history + mapping = dict() + for shape in invalid: + id = get_id_from_history(shape) + if not id: + self.log.info("No ID found in history of: {0}".format(shape)) + continue + mapping[shape] = id + + # Add the ids to the nodes + id_utils.add_ids(mapping) + self.log.info("Generated ids on nodes: {0}".format(mapping.values())) + + +class ValidateLayoutShapeNodeIds(pyblish.api.InstancePlugin): + """Validate shapes nodes have colorbleed id attributes + + All non-referenced transforms in the hierarchy should have unique IDs. + This does not check for unique shape ids to allow a same non-referenced + shape in the output (e.g. when multiple of the same characters are in + the scene with a deformer on it). + + How? + + This usually happens when a node was created locally and did not come + from a correctly published asset. + + In the case you're entirely sure you still want to publish the shapes + you can forcefully generate ids for them. USE WITH CARE! Select the + nodes (shapes!) and run: + > scripts > pyblish > utilities > regenerate_uuids + + Why? + + The pipeline needs the ids to be able to identify "what" an object is. + When it knows that it's able to correctly assign its shaders or do all + kinds of other magic with it! + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.layout'] + hosts = ['maya'] + label = 'Layout Shape Ids' + actions = [colorbleed.api.SelectInvalidAction, + CopyUUIDsFromHistoryAction] + + @staticmethod + def get_invalid(instance): + + nodes = cmds.ls(instance, shapes=True, long=True) + referenced = cmds.ls(nodes, referencedNodes=True, long=True) + non_referenced = set(nodes) - set(referenced) + + # Ignore specific node types + # `deformFunc` = deformer shapes + IGNORED = ("gpuCache", + "constraint", + "lattice", + "baseLattice", + "geometryFilter", + "deformFunc", + "locator") + + ignored_nodes = cmds.ls(list(non_referenced), type=IGNORED, long=True) + if ignored_nodes: + non_referenced -= set(ignored_nodes) + + invalid = [] + for node in non_referenced: + if not id_utils.get_id(node): + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Shapes (non-referenced) found in layout " + "without asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py new file mode 100644 index 0000000000..3ea61aff27 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py @@ -0,0 +1,65 @@ +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateLayoutUniqueNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have unique colorbleed id attributes""" + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.layout'] + hosts = ['maya'] + label = 'Layout Transform Unique Ids' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid_dict(instance): + """Return a dictionary mapping of id key to list of member nodes""" + from maya import cmds + + nodes = cmds.ls(instance, type='transform', long=True) + referenced = cmds.ls(nodes, referencedNodes=True, long=True) + non_referenced = set(nodes) - set(referenced) + members = non_referenced + + # Collect each id with their members + from collections import defaultdict + ids = defaultdict(list) + for member in members: + id = id_utils.get_id(member) + ids[id].append(member) + + # Skip those without IDs (if everything should have an ID that should + # be another validation) + ids.pop(None, None) + + # Take only the ids with more than one member + invalid = dict((id, members) for id, members in ids.iteritems() if + len(members) > 1) + return invalid + + @classmethod + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" + + invalid_dict = cls.get_invalid_dict(instance) + + # Take only the ids with more than one member + invalid = list() + for members in invalid_dict.itervalues(): + invalid.extend(members) + + return invalid + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid_dict(instance) + + if invalid: + raise RuntimeError("Transforms found with non-unique " + "asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py new file mode 100644 index 0000000000..6c91f83206 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -0,0 +1,27 @@ +import pyblish.api +import colorbleed.api + + +class ValidateLookContents(pyblish.api.InstancePlugin): + """Validate look instance contents + + This is invalid when the collection was unable to collect the required + data for a look to be published correctly. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Contents' + + def process(self, instance): + """Process all the nodes in the instance""" + + if not instance[:]: + raise RuntimeError("Instance is empty") + + # Required look data + assert "lookSets" in instance.data + assert "lookSetRelations" in instance.data + assert "lookAttributes" in instance.data diff --git a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py new file mode 100644 index 0000000000..30ad75c482 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py @@ -0,0 +1,62 @@ +import pyblish.api +import colorbleed.api + + +class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): + """Validate default shaders in the scene have their default connections. + + For example the lambert1 could potentially be disconnected from the + initialShadingGroup. As such it's not lambert1 that will be identified + as the default shader which can have unpredictable results. + + To fix the default connections need to be made again. See the logs for + more details on which connections are missing. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Default Shader Connections' + + # The default connections to check + DEFAULTS = [ + ("initialShadingGroup.surfaceShader", "lambert1"), + ("initialParticleSE.surfaceShader", "lambert1"), + ("initialParticleSE.volumeShader", "particleCloud1") + ] + + def process(self, instance): + + # Ensure check is run only once. We don't use ContextPlugin because + # of a bug where the ContextPlugin will always be visible. Even when + # the family is not present in an instance. + key = "__validate_look_default_shaders_connections_checked" + context = instance.context + is_run = context.data.get(key, + False) + if is_run: + return + else: + context.data[key] = True + + # Process as usual + from maya import cmds + + invalid = list() + for plug, input_node in self.DEFAULTS: + + inputs = cmds.listConnections(plug, + source=True, + destination=False) or None + + if not inputs or inputs[0] != input_node: + self.log.error("{0} is not connected to {1}. " + "This can result in unexpected behavior. " + "Please reconnect to continue.".format( + plug, + input_node)) + invalid.append(plug) + + if invalid: + raise RuntimeError("Invalid connections.") diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py new file mode 100644 index 0000000000..ed8f50fd15 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -0,0 +1,129 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +from colorbleed.api import get_errored_instances_from_context + +from cbra.utils.maya.node_uuid import get_id, add_ids + + +def get_deformed_history_id_mapping(shapes): + """Return the id from history for nodes that are "Deformed". + + When shapes are referenced and get deformed by a deformer + the shape is duplicated *without its attributes* as such + the new shape misses object ids. This method will try to + trace back in the history to find the first shape with + ids to identify the possible best match. + + Args: + shapes (list): The shapes that are deformed. + + Returns: + dict: Mapping of deformed shape to history shape. + + """ + + shapes = cmds.ls(shapes, shapes=True, long=True) + + # Possibly deformed shapes + deformed_shapes = [x for x in shapes if "Deformed" in x.rsplit("|", 1)[-1]] + + # The deformed shape should not be referenced + is_referenced = lambda n: cmds.referenceQuery(n, isNodeReferenced=True) + deformed_shapes = [x for x in deformed_shapes if not is_referenced(x)] + + # Shapes without id + deformed_shapes_without_id = [x for x in deformed_shapes if not get_id(x)] + + mapping = {} + for shape in deformed_shapes_without_id: + + node_type = cmds.objectType(shape) + history = cmds.listHistory(shape)[1:] # history, skipping itself + history_shapes = cmds.ls(history, exactType=node_type, long=True) + if not history_shapes: + continue + + for history_shape in history_shapes: + id = get_id(history_shape) + if not id: + continue + + mapping[shape] = history_shape + break + + return mapping + + +class CopyUUIDsFromHistory(pyblish.api.Action): + """Repairs the action + + To retrieve the invalid nodes this assumes a static `repair(instance)` + method is available on the plugin. + + """ + label = "Copy UUIDs from History" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + ids_map = dict() + for instance in instances: + invalid = plugin.get_invalid(instance) + mapping = get_deformed_history_id_mapping(invalid) + + for destination, source in mapping.items(): + ids_map[destination] = get_id(source) + + if not ids_map: + return + add_ids(ids_map) + + +class ValidateLookDeformedShapes(pyblish.api.InstancePlugin): + """Validate look textures are set to ignore color space when set to RAW + + Whenever the format is NOT set to sRGB for a file texture it must have + its ignore color space file rules checkbox enabled to avoid unwanted + reverting to sRGB settings upon file relinking. + + To fix this use the select invalid action to find the invalid file nodes + and then check the "Ignore Color Space File Rules" checkbox under the + Color Space settings. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look deformed shapes' + actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory] + + @classmethod + def get_invalid(cls, instance): + + context = instance.context + nodes = context.data.get("instancePerItemNodesWithoutId", None) + if not nodes: + return list() + + mapping = get_deformed_history_id_mapping(nodes) + return mapping.keys() + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Shapes found that are considered 'Deformed'" + "without object ids: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py new file mode 100644 index 0000000000..58f0bb2f23 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py @@ -0,0 +1,58 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateLookIgnoreColorSpace(pyblish.api.InstancePlugin): + """Validate look textures are set to ignore color space when set to RAW + + Whenever the format is NOT set to sRGB for a file texture it must have + its ignore color space file rules checkbox enabled to avoid unwanted + reverting to sRGB settings upon file relinking. + + To fix this use the select invalid action to find the invalid file nodes + and then check the "Ignore Color Space File Rules" checkbox under the + Color Space settings. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look RAW Ignore color space' + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + # Get texture nodes from the collected resources + required = ["maya", "look", "attribute"] + nodes = list() + for resource in instance.data.get("resources", []): + if all(tag in resource.get("tags", []) for tag in required): + node = resource['node'] + nodes.append(node) + + nodes = list(sorted(set(nodes))) + cls.log.info("Checking nodes: {0}".format(nodes)) + + # Validate + invalid = [] + for node in nodes: + color_space = cmds.getAttr(node + ".colorSpace") + ignore_rules = cmds.getAttr(node + ".ignoreColorSpaceFileRules") + if color_space != "sRGB" and not ignore_rules: + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Non-sRGB file textures nodes with ignore " + "color space file rules disabled: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py new file mode 100644 index 0000000000..4c07dffd37 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -0,0 +1,55 @@ +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): + """Validate look members have colorbleed id attributes + + Looks up the contents of the look to see if all its members have + colorbleed id attributes so they can be connected correctly. + + When invalid it's very likely related to the model not having the id + attributes that it should have. These should have been generated in the + work files for the model/rig/fur or alike. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Members Id Attributes' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + # Get all members from the sets + members = [] + relations = instance.data["lookSetRelations"] + for sg in relations: + sg_members = sg['members'] + sg_members = [member['name'] for member in sg_members] + members.extend(sg_members) + + # Get all sets + + members = list(set(members)) + + # Ensure all nodes have a cbId + invalid = list() + for node in members: + if not id_utils.has_id(node): + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Members found without " + "asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py new file mode 100644 index 0000000000..f5973e62dc --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -0,0 +1,69 @@ +from collections import defaultdict + +from maya import cmds + +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateLookMembersUnique(pyblish.api.InstancePlugin): + """Validate members of look are unique. + + This ensures the same id is not present as more than one node in the look. + + That means there's only ever one of a specific node inside the look to be + published. For example if you'd have a loaded 3x the same tree and by + accident you're trying to publish them all together in a single look that + would be invalid, because they are the same tree it should be included + inside the look instance only once. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Members Unique' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + # Get all members from the sets + members = [] + relations = instance.data["lookSetRelations"] + for sg in relations: + sg_members = sg['members'] + sg_members = [member['name'] for member in sg_members] + members.extend(sg_members) + + # Ensure we don't have components but the objects + members = cmds.ls(members, objectsOnly=True, long=True) + members = list(set(members)) + + # Group members per id + id_nodes = defaultdict(set) + for node in members: + id = id_utils.get_id(node) + if not id: + continue + + id_nodes[id].add(node) + + invalid = list() + for nodes in id_nodes.itervalues(): + if len(nodes) > 1: + invalid.extend(nodes) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Members found without " + "asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py new file mode 100644 index 0000000000..901a6b9fed --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -0,0 +1,85 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): + """Validate look contains no default shaders. + + This checks whether the look has any members of: + - lambert1 + - initialShadingGroup + - initialParticleSE + - particleCloud1 + + If any of those is present it will raise an error. A look is not allowed + to have any of the "default" shaders present in a scene as they can + introduce problems when referenced (overriding local scene shaders). + + To fix this no shape nodes in the look must have any of default shaders + applied. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look No Default Shaders' + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid_sets(cls, instance): + + disallowed = ["lambert1", + "initialShadingGroup", + "initialParticleSE", + "particleCloud1"] + disallowed = set(disallowed) + + # Check among the sets + sets = instance.data['lookSets'] + lookup = set(sets) + intersect = lookup.intersection(disallowed) + if intersect: + cls.log.error("Default shaders found in the " + "look: {0}".format(list(intersect))) + return list(intersect) + + # Check among history/inputs of the sets + history = cmds.listHistory(sets) or [] + lookup = set(history) + + intersect = lookup.intersection(disallowed) + if intersect: + cls.log.error("Default shaders found in the history of the " + "look: {0}".format(list(intersect))) + return list(intersect) + + return list() + + @classmethod + def get_invalid(cls, instance): + + shaders = cls.get_invalid_sets(instance) + nodes = instance[:] + + # Get members of the shaders + all = set() + for shader in shaders: + members = cmds.sets(shader, q=True) or [] + members = cmds.ls(members, long=True) + all.update(members) + + # Get the instance nodes among the shader members + invalid = all.intersection(nodes) + invalid = list(invalid) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance""" + + sets = self.get_invalid_sets(instance) + if sets: + raise RuntimeError("Invalid shaders found: {0}".format(sets)) diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py new file mode 100644 index 0000000000..e4778e914e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -0,0 +1,48 @@ +from collections import defaultdict +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): + """Validate look sets have unique colorbleed id attributes + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Id Unique Attributes' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid(instance): + + nodes = instance.data["lookSets"] + + # Ensure all nodes have a cbId + id_sets = defaultdict(list) + invalid = list() + for node in nodes: + id = id_utils.get_id(node) + if not id: + continue + + id_sets[id].append(node) + + for id, nodes in id_sets.iteritems(): + if len(nodes) > 1: + invalid.extend(nodes) + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Nodes found without " + "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py new file mode 100644 index 0000000000..538dec949c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -0,0 +1,39 @@ +import pyblish.api +import colorbleed.api + + +class ValidateMayaUnits(pyblish.api.ContextPlugin): + """Check if the Maya units are set correct""" + + order = colorbleed.api.ValidateSceneOrder + label = "Maya Units" + families = ["colorbleed.rig", + "colorbleed.model", + "colorbleed.pointcache", + "colorbleed.curves"] + actions = [colorbleed.api.RepairAction] + + def process(self, context): + + linearunits = context.data('linearUnits') + angularunits = context.data('angularUnits') + fps = context.data['fps'] + + self.log.info('Units (linear): {0}'.format(linearunits)) + self.log.info('Units (angular): {0}'.format(angularunits)) + self.log.info('Units (time): {0} FPS'.format(fps)) + + # check if units are correct + assert linearunits and linearunits == 'cm', ("Scene linear units must " + "be centimeters") + + assert angularunits and angularunits == 'deg', ("Scene angular units " + "must be degrees") + + assert fps and fps == 25.0, "Scene must be 25 FP" + + @classmethod + def repair(cls): + """Fix the current FPS setting of the scene, set to PAL(25.0 fps) + """ + raise NotImplementedError() diff --git a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py new file mode 100644 index 0000000000..f3e760e27d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py @@ -0,0 +1,96 @@ +import re + +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def len_flattened(components): + """Return the length of the list as if it was flattened. + + Maya will return consecutive components as a single entry + when requesting with `maya.cmds.ls` without the `flatten` + flag. Though enabling `flatten` on a large list (e.g. millions) + will result in a slow result. This command will return the amount + of entries in a non-flattened list by parsing the result with + regex. + + Args: + components (list): The non-flattened components. + + Returns: + int: The amount of entries. + + """ + assert isinstance(components, (list, tuple)) + n = 0 + for c in components: + match = re.search("\[([0-9]+):([0-9]+)\]", c) + if match: + start, end = match.groups() + n += int(end) - int(start) + 1 + else: + n += 1 + return n + + +class ValidateMeshHasUVs(pyblish.api.InstancePlugin): + """Validate the current mesh has UVs. + + It validates whether the current UV set has non-zero UVs and + at least more than the vertex count. It's not really bulletproof, + but a simple quick validation to check if there are likely + UVs for every face. + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'geometry' + label = 'Mesh Has UVs' + actions = [colorbleed.api.SelectInvalidAction] + optional = True + + @classmethod + def get_invalid(cls, instance): + invalid = [] + + for node in cmds.ls(instance, type='mesh'): + uv = cmds.polyEvaluate(node, uv=True) + + if uv == 0: + invalid.append(node) + continue + + vertex = cmds.polyEvaluate(node, vertex=True) + if uv < vertex: + + # Workaround: + # Maya can have instanced UVs in a single mesh, for example + # imported from an Alembic. With instanced UVs the UV count from + # `maya.cmds.polyEvaluate(uv=True)` will only result in the unique + # UV count instead of for all vertices. + # + # Note: Maya can save instanced UVs to `mayaAscii` but cannot + # load this as instanced. So saving, opening and saving + # again will lose this information. + map_attr = "{}.map[*]".format(node) + uv_to_vertex = cmds.polyListComponentConversion(map_attr, + toVertex=True) + uv_vertex_count = len_flattened(uv_to_vertex) + if uv_vertex_count < vertex: + invalid.append(node) + else: + cls.log.warning("Node has instanced UV points: " + "{0}".format(node)) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Meshes found in instance without " + "valid UVs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py new file mode 100644 index 0000000000..8dbb3f3594 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py @@ -0,0 +1,34 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): + """Validate meshes don't have lamina faces. + + Lamina faces share all of their edges. + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'geometry' + version = (0, 1, 0) + label = 'Mesh Lamina Faces' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + meshes = cmds.ls(instance, type='mesh', long=True) + return [mesh for mesh in meshes if cmds.polyInfo(mesh, laminaFaces=True)] + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Meshes found with lamina faces: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py new file mode 100644 index 0000000000..9c032d730a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py @@ -0,0 +1,49 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshNoNegativeScale(pyblish.api.Validator): + """Ensure that meshes don't have a negative scale. + + Using negatively scaled proxies in a VRayMesh results in inverted + normals. As such we want to avoid this. + + We also avoid this on the rig or model because these are often the + previous steps for those that are cached to proxies so we can catch this + issue early. + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.proxy', 'colorbleed.rig', 'colorbleed.model'] + label = 'Mesh No Negative Scale' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + meshes = cmds.ls(instance, + type='mesh', + long=True, + noIntermediate=True) + + invalid = [] + for mesh in meshes: + transform = cmds.listRelatives(mesh, parent=True, fullPath=True)[0] + scale = cmds.getAttr("{0}.scale".format(transform))[0] + + if any(x < 0 for x in scale): + invalid.append(mesh) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Meshes found with negative " + "scale: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py b/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py new file mode 100644 index 0000000000..c1185cf587 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py @@ -0,0 +1,41 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshNonManifold(pyblish.api.Validator): + """Ensure that meshes don't have non-manifold edges or vertices + + To debug the problem on the meshes you can use Maya's modeling + tool: "Mesh > Cleanup..." + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + label = 'Mesh Non-Manifold Vertices/Edges' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + meshes = cmds.ls(instance, type='mesh', long=True) + + invalid = [] + for mesh in meshes: + if (cmds.polyInfo(mesh, nonManifoldVertices=True) or + cmds.polyInfo(mesh, nonManifoldEdges=True)): + invalid.append(mesh) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Meshes found with non-manifold " + "edges/vertices: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py new file mode 100644 index 0000000000..1cbac35197 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py @@ -0,0 +1,58 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + +from pyblish_cb.tmp_utils import polyConstraint + + +class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): + """Validate meshes don't have edges with a zero length. + + Based on Maya's polyCleanup 'Edges with zero length'. + + Note: + This can be slow for high-res meshes. + + """ + + order = colorbleed.api.ValidateMeshOrder + families = ['colorbleed.model'] + hosts = ['maya'] + category = 'geometry' + version = (0, 1, 0) + label = 'Mesh Edge Length Non Zero' + actions = [colorbleed.api.SelectInvalidAction] + + __tolerance = 1e-5 + + @classmethod + def get_invalid(cls, instance): + """Return the invalid edges. + + Also see: http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup + + """ + + meshes = cmds.ls(instance, type='mesh', long=True) + if not meshes: + return list() + + # Get all edges + edges = ['{0}.e[*]'.format(node) for node in meshes] + + # Filter by constraint on edge length + invalid = polyConstraint(edges, + t=0x8000, # type=edge + length=1, + lengthbound=(0, cls.__tolerance)) + + return invalid + + def process(self, instance): + """Process all meshes""" + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Meshes found with zero " + "edge length: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py b/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py new file mode 100644 index 0000000000..68049fd60a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py @@ -0,0 +1,53 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshNormalsUnlocked(pyblish.api.Validator): + """Validate all meshes in the instance have unlocked normals + + These can be unlocked manually through: + Modeling > Mesh Display > Unlock Normals + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'geometry' + version = (0, 1, 0) + label = 'Mesh Normals Unlocked' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + optional = True + + @staticmethod + def has_locked_normals(mesh): + """Return whether a mesh node has locked normals""" + return any(cmds.polyNormalPerVertex("{}.vtxFace[*][*]".format(mesh), + query=True, + freezeNormal=True)) + + @classmethod + def get_invalid(cls, instance): + """Return the meshes with locked normals in instance""" + + meshes = cmds.ls(instance, type='mesh', long=True) + return [mesh for mesh in meshes if cls.has_locked_normals(mesh)] + + def process(self, instance): + """Raise invalid when any of the meshes have locked normals""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Meshes found with " + "locked normals: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + """Unlocks all normals on the meshes in this instance.""" + invalid = cls.get_invalid(instance) + for mesh in invalid: + cmds.polyNormalPerVertex(mesh, unFreezeNormal=True) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py new file mode 100644 index 0000000000..3d02f54251 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -0,0 +1,123 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): + """Ensure no multiple UV sets exist for each polygon mesh""" + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model', 'colorbleed.pointcache'] + category = 'uv' + optional = True + version = (0, 1, 0) + label = "Mesh Single UV Set" + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + @staticmethod + def get_invalid(instance): + + meshes = cmds.ls(instance, type='mesh', long=True) + + invalid = [] + for mesh in meshes: + uvSets = cmds.polyUVSet(mesh, + query=True, + allUVSets=True) or [] + + # ensure unique (sometimes maya will list 'map1' twice) + uvSets = set(uvSets) + + if len(uvSets) != 1: + invalid.append(mesh) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Nodes found with multiple " + "UV sets: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + for mesh in cls.get_invalid(instance): + cls._repair_mesh(mesh) + + @classmethod + def _repair_mesh(cls, mesh): + """Process a single mesh, deleting other UV sets than the active one. + + Keep only current UV set and ensure it's the default 'map1' + + """ + from maya import cmds + + uvSets = cmds.polyUVSet(mesh, + query=True, + allUVSets=True) + current = cmds.polyUVSet(mesh, + query=True, + currentUVSet=True)[0] + + # Copy over to map1 + if current != 'map1': + cmds.polyUVSet(mesh, + uvSet=current, + newUVSet='map1', + copy=True) + cmds.polyUVSet(mesh, + currentUVSet=True, + uvSet='map1') + current = 'map1' + + # Delete all non-current UV sets + deleteUVSets = [uvSet for uvSet in uvSets if uvSet != current] + uvSet = None + + # Maya Bug (tested in 2015/2016): + # In some cases the API's MFnMesh will report less UV sets + # than maya.cmds.polyUVSet. + # This seems to happen when the deletion of UV sets has not + # triggered a cleanup of the UVSet array + # attribute on the mesh node. It will still have extra + # entries in the attribute, though it will not + # show up in API or UI. Nevertheless it does show up in + # maya.cmds.polyUVSet. + # To ensure we clean up the array we'll force delete the + # extra remaining 'indices' that we don't want. + + # TODO: Implement a better fix + # The best way to fix would be to get the UVSet + # indices from api with MFnMesh (to ensure we keep + # correct ones) and then only force delete the other + # entries in the array attribute on the node. + # But for now we're deleting all entries except first + # one. Note that the first entry could never + # be removed (the default 'map1' always exists and is + # supposed to be undeletable.) + try: + for uvSet in deleteUVSets: + cmds.polyUVSet(mesh, delete=True, uvSet=uvSet) + except RuntimeError, e: + cls.log.warning('uvSet: {0} - ' + 'Error: {1}'.format(uvSet, e)) + + indices = cmds.getAttr('{0}.uvSet'.format(mesh), + multiIndices=True) + if not indices: + cls.log.warning("No uv set found indices for: {0}".format(mesh)) + return + + # Delete from end to avoid shifting indices + # and remove the indices in the attribute + indices = reversed(indices[1:]) + for i in indices: + attr = '{0}.uvSet[{1}]'.format(mesh, i) + cmds.removeMultiInstance(attr, b=True) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py new file mode 100644 index 0000000000..3db14f226a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py @@ -0,0 +1,89 @@ +import re + +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def len_flattened(components): + """Return the length of the list as if it was flattened. + + Maya will return consecutive components as a single entry + when requesting with `maya.cmds.ls` without the `flatten` + flag. Though enabling `flatten` on a large list (e.g. millions) + will result in a slow result. This command will return the amount + of entries in a non-flattened list by parsing the result with + regex. + + Args: + components (list): The non-flattened components. + + Returns: + int: The amount of entries. + + """ + assert isinstance(components, (list, tuple)) + n = 0 + + pattern = re.compile(r"\[(\d+):(\d+)\]") + for c in components: + match = pattern.search(c) + if match: + start, end = match.groups() + n += int(end) - int(start) + 1 + else: + n += 1 + return n + + +class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): + """Validate meshes have only vertices that are connected by to edges. + + Maya can have invalid geometry with vertices that have no edges or + faces connected to them. + + In Maya 2016 EXT 2 and later there's a command to fix this: + `maya.cmds.polyClean(mesh, cleanVertices=True) + + In older versions of Maya it works to select the invalid vertices + and merge the components. + + To find these invalid vertices select all vertices of the mesh + that are visible in the viewport (drag to select), afterwards + invert your selection (Ctrl + Shift + I). The remaining selection + contains the invalid vertices. + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'geometry' + label = 'Mesh Vertices Have Edges' + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + invalid = [] + + meshes = cmds.ls(instance, type="mesh", long=True) + for mesh in meshes: + num_vertices = cmds.polyEvaluate(mesh, vertex=True) + + # Vertices from all edges + edges = "%s.e[*]" % mesh + vertices = cmds.polyListComponentConversion(edges, toVertex=True) + num_vertices_from_edges = len_flattened(vertices) + + if num_vertices != num_vertices_from_edges: + invalid.append(mesh) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Meshes found in instance with vertices that " + "have no edges: %s" % invalid) diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py new file mode 100644 index 0000000000..51b0647441 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -0,0 +1,144 @@ +import pprint + +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def is_visible(node, + displayLayer=True, + intermediateObject=True, + parentHidden=True, + visibility=True): + """Is `node` visible? + + Returns whether a node is hidden by one of the following methods: + - The node exists (always checked) + - The node must be a dagNode (always checked) + - The node's visibility is off. + - The node is set as intermediate Object. + - The node is in a disabled displayLayer. + - Whether any of its parent nodes is hidden. + + Roughly based on: http://ewertb.soundlinker.com/mel/mel.098.php + + Returns: + bool: Whether the node is visible in the scene + + """ + + # Only existing objects can be visible + if not cmds.objExists(node): + return False + + # Only dagNodes can be visible + if not cmds.objectType(node, isAType='dagNode'): + return False + + if visibility: + if not cmds.getAttr('{0}.visibility'.format(node)): + return False + + if intermediateObject and cmds.objectType(node, isAType='shape'): + if cmds.getAttr('{0}.intermediateObject'.format(node)): + return False + + if displayLayer: + # Display layers set overrideEnabled and overrideVisibility on members + if cmds.attributeQuery('overrideEnabled', node=node, exists=True): + if cmds.getAttr('{0}.overrideEnabled'.format(node)) and \ + cmds.getAttr('{0}.overrideVisibility'.format(node)): + return False + + if parentHidden: + parents = cmds.listRelatives(node, parent=True, fullPath=True) + if parents: + parent = parents[0] + if not is_visible(parent, + displayLayer=displayLayer, + intermediateObject=False, + parentHidden=parentHidden, + visibility=visibility): + return False + + return True + + +class ValidateModelContent(pyblish.api.InstancePlugin): + """Adheres to the content of 'model' family + + - Must have one top group. + - Must only contain: transforms, meshes and groups + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + families = ["colorbleed.model"] + label = "Model Content" + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + pprint.pprint(instance.data) + + content_instance = instance.data.get("setMembers", None) + if not content_instance: + cls.log.error("Instance has no nodes!") + return True + + # Ensure only valid node types + allowed = ('mesh', 'transform', 'nurbsCurve') + nodes = cmds.ls(content_instance, long=True) + valid = cmds.ls(content_instance, long=True, type=allowed) + invalid = set(nodes) - set(valid) + + if invalid: + cls.log.error("These nodes are not allowed: %s" % invalid) + return list(invalid) + + # Top group + assemblies = cmds.ls(content_instance, assemblies=True, long=True) + if len(assemblies) != 1: + cls.log.error("Must have exactly one top group") + if len(assemblies) == 0: + cls.log.warning("No top group found. " + "(Are there objects in the instance?)") + return assemblies or True + + if not valid: + cls.log.error("No valid nodes in the instance") + return True + + def _is_visible(node): + """Return whether node is visible""" + return is_visible(node, + displayLayer=False, + intermediateObject=True, + parentHidden=True, + visibility=True) + + # The roots must be visible (the assemblies) + for assembly in assemblies: + if not _is_visible(assembly): + cls.log.error("Invisible assembly (root node) is not " + "allowed: {0}".format(assembly)) + invalid.add(assembly) + + # Ensure at least one shape is visible + shapes = cmds.ls(valid, long=True, shapes=True) + if not any(_is_visible(shape) for shape in shapes): + cls.log.error("No visible shapes in the model instance") + invalid.update(shapes) + + return list(invalid) + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Model content is invalid. See log.") + diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py new file mode 100644 index 0000000000..a44214a657 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_namespace_empty.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api +from maya import cmds + + +class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): + """Validate there are no empty namespaces in the scene. + + This is a scene wide validation that filters out "UI" and "shared" + namespaces that exist by default in Maya and are mostly hidden. + + """ + + order = colorbleed.api.ValidateSceneOrder + hosts = ["maya"] + families = ["colorbleed.model"] + category = "scene" + version = (0, 1, 0) + label = "No Empty Namespaces" + + def process(self, context): + """Process the Context""" + all_namespaces = cmds.namespaceInfo(":", + listOnlyNamespaces=True, + recurse=True) + non_internal_namespaces = [ns for ns in all_namespaces + if ns not in ["UI", "shared"]] + + invalid = [] + # TODO: Check whether currently a namespace with + # another namespace in it (both empty) is + # considered empty + for namespace in non_internal_namespaces: + namespace_content = cmds.namespaceInfo(namespace, + listNamespace=True, + recurse=True) + if not namespace_content: + invalid.append(namespace) + + assert not invalid, ( + "Empty namespaces found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_no_animation.py b/colorbleed/plugins/maya/publish/validate_no_animation.py new file mode 100644 index 0000000000..6c57183d31 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_animation.py @@ -0,0 +1,42 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateNoAnimation(pyblish.api.Validator): + """Ensure no keyframes on nodes in the Instance. + + Even though a Model would extract without animCurves correctly this avoids + getting different output from a model when extracted from a different + frame than the first frame. (Might be overly restrictive though) + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "No Animation" + hosts = ["maya"] + families = ["colorbleed.model"] + optional = True + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + nodes = instance[:] + + if not nodes: + return [] + + curves = cmds.keyframe(nodes, query=True, name=True) + if curves: + return list(set(cmds.listConnections(curves))) + + return [] + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Keyframes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_no_default_camera.py b/colorbleed/plugins/maya/publish/validate_no_default_camera.py new file mode 100644 index 0000000000..cce4433836 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_default_camera.py @@ -0,0 +1,31 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): + """Ensure no default (startup) cameras are in the instance. + + This might be unnecessary. In the past there were some issues with + referencing/importing files that contained the start up cameras overriding + settings when being loaded and sometimes being skipped. + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.animation'] + version = (0, 1, 0) + label = "No Default Cameras" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + cameras = cmds.ls(instance, type='camera', long=True) + return [cam for cam in cameras if + cmds.camera(cam, query=True, startupCamera=True)] + + def process(self, instance): + """Process all the cameras in the instance""" + invalid = self.get_invalid(instance) + assert not invalid, "Default cameras found: {0}".format(invalid) diff --git a/colorbleed/plugins/maya/publish/validate_no_namespace.py b/colorbleed/plugins/maya/publish/validate_no_namespace.py new file mode 100644 index 0000000000..0f0bbad1c1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_namespace.py @@ -0,0 +1,54 @@ +import pymel.core as pm +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + + +def get_namespace(node_name): + # ensure only node's name (not parent path) + node_name = node_name.rsplit("|")[-1] + # ensure only namespace + return node_name.rpartition(":")[0] + + +class ValidateNoNamespace(pyblish.api.InstancePlugin): + """Ensure the nodes don't have a namespace""" + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'cleanup' + version = (0, 1, 0) + label = 'No Namespaces' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + @staticmethod + def get_invalid(instance): + nodes = cmds.ls(instance, long=True) + return [node for node in nodes if get_namespace(node)] + + def process(self, instance): + """Process all the nodes in the instance""" + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Namespaces found: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + """Remove all namespaces from the nodes in the instance""" + + invalid = cls.get_invalid(instance) + + # Get nodes with pymel since we'll be renaming them + # Since we don't want to keep checking the hierarchy + # or full paths + nodes = pm.ls(invalid) + + for node in nodes: + namespace = node.namespace() + if namespace: + name = node.nodeName() + node.rename(name[len(namespace):]) diff --git a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py new file mode 100644 index 0000000000..077844df14 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py @@ -0,0 +1,78 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + + +def has_shape_children(node): + # Check if any descendants + allDescendents = cmds.listRelatives(node, + allDescendents=True, + fullPath=True) + if not allDescendents: + return False + + # Check if there are any shapes at all + shapes = cmds.ls(allDescendents, shapes=True) + if not shapes: + return False + + # Check if all descendent shapes are intermediateObjects; + # if so we consider this node a null node and return False. + if all(cmds.getAttr('{0}.intermediateObject'.format(x)) for x in shapes): + return False + + return True + + +class ValidateNoNullTransforms(pyblish.api.InstancePlugin): + """Ensure no null transforms are in the scene. + + Warning: + Transforms with only intermediate shapes are also considered null + transforms. These transform nodes could potentially be used in your + construction history, so take care when automatically fixing this or + when deleting the empty transforms manually. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'cleanup' + version = (0, 1, 0) + label = 'No Empty/Null Transforms' + actions = [colorbleed.api.RepairAction, colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + """Return invalid transforms in instance""" + + transforms = cmds.ls(instance, type='transform', long=True) + + invalid = [] + for transform in transforms: + if not has_shape_children(transform): + invalid.append(transform) + + return invalid + + def process(self, instance): + """Process all the transform nodes in the instance """ + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Empty transforms found: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + """Delete all null transforms. + + Note: If the node is used elsewhere (eg. connection to attributes or + in history) deletion might mess up things. + + """ + invalid = cls.get_invalid(instance) + + if invalid: + cmds.delete(invalid) diff --git a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py new file mode 100644 index 0000000000..6e4c0028db --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py @@ -0,0 +1,34 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): + """Checks to see if there are any unknown nodes in the instance. + + This often happens if nodes from plug-ins are used but are not available + on this machine. + + Note: Some studios use unknown nodes to store data on (as attributes) + because it's a lightweight node. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model', 'colorbleed.layout', 'colorbleed.rig'] + optional = True + label = "Unknown Nodes" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + return cmds.ls(instance, type='unknown') + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Unknown nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_no_vraymesh.py b/colorbleed/plugins/maya/publish/validate_no_vraymesh.py new file mode 100644 index 0000000000..7aad11e14c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_no_vraymesh.py @@ -0,0 +1,24 @@ +import pyblish.api +from maya import cmds + + +class ValidateNoVRayMesh(pyblish.api.InstancePlugin): + """Validate there are no VRayMesh objects in the instance""" + + order = pyblish.api.ValidatorOrder + label = 'No V-Ray Proxies (VRayMesh)' + families = ["colorbleed.pointcache"] + + def process(self, instance): + + shapes = cmds.ls(instance, + shapes=True, + type="mesh") + + inputs = cmds.listConnections(shapes, + destination=False, + source=True) or [] + vray_meshes = cmds.ls(inputs, type='VRayMesh') + if vray_meshes: + raise RuntimeError("Meshes that are VRayMeshes shouldn't " + "be pointcached: {0}".format(vray_meshes)) diff --git a/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py b/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py new file mode 100644 index 0000000000..ca5c4a1edc --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py @@ -0,0 +1,49 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): + """Ensure nodes do not have ghosting enabled. + + If one would publish towards a non-Maya format it's likely that stats + like ghosting won't be exported, eg. exporting to Alembic. + + Instead of creating many micro-managing checks (like this one) to ensure + attributes have not been changed from their default it could be more + efficient to export to a format that will never hold such data anyway. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model', 'colorbleed.rig'] + label = "No Ghosting" + actions = [colorbleed.api.SelectInvalidAction] + + _attributes = {'ghosting': 0} + + @classmethod + def get_invalid(cls, instance): + + # Transforms and shapes seem to have ghosting + nodes = cmds.ls(instance, long=True, type=['transform', 'shape']) + invalid = [] + for node in nodes: + for attr, required_value in cls._attributes.iteritems(): + if cmds.attributeQuery(attr, node=node, exists=True): + + value = cmds.getAttr('{0}.{1}'.format(node, attr)) + if value != required_value: + invalid.append(node) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Nodes with ghosting enabled found: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_nodes_visible.py b/colorbleed/plugins/maya/publish/validate_nodes_visible.py new file mode 100644 index 0000000000..fc55fadbc2 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_nodes_visible.py @@ -0,0 +1,43 @@ +import pyblish.api +import colorbleed.api + +from maya import cmds +import cb.utils.maya.dag as dag + + +class ValidateNodesVisible(pyblish.api.InstancePlugin): + """Validate all shape nodes are currently visible. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.furYeti'] + hosts = ['maya'] + label = "Nodes Visible" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + members = instance.data["setMembers"] + members = cmds.ls(members, + dag=True, + shapes=True, + long=True, + noIntermediate=True) + + invalid = [] + for node in members: + if not dag.is_visible(node, displayLayer=False): + invalid.append(node) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Instance contains invisible shapes: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py b/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py new file mode 100644 index 0000000000..18a09ff58f --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py @@ -0,0 +1,114 @@ +import pyblish.api +import colorbleed.api +from maya import cmds + + + +class RepairFailedEditsAction(pyblish.api.Action): + label = "Remove failed edits" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + from maya import cmds + self.log.info("Finding bad nodes..") + + # Get the errored instances + errored_instances = [] + for result in context.data["results"]: + if result["error"] is not None and result["instance"] is not None: + if result["error"]: + instance = result["instance"] + errored_instances.append(instance) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the nodes from the all instances that ran through this plug-in + invalid = [] + for instance in instances: + invalid_nodes = plugin.get_invalid(instance) + invalid.extend(invalid_nodes) + + if not invalid: + self.log.info("No invalid nodes found.") + return + + for ref in invalid: + self.log.info("Remove failed edits for: {0}".format(ref)) + cmds.referenceEdit(ref, + removeEdits=True, + failedEdits=True, + successfulEdits=False) + self.log.info("Removed failed edits") + + +class ValidateReferencesNoFailedEdits(pyblish.api.InstancePlugin): + """Validate that all referenced nodes' reference nodes don't have failed + reference edits. + + Failed reference edits can happen if you apply a change to a referenced + object in the scene and then change the source of the reference + (referenced file) to remove the object. The reference edit can't be + applied to the node because it is missing, hence a "failed edit". This + could unnecessarily bloat file sizes and degrade load/save speed. + + To investigate reference edits you can "List edits" on a reference + and look for those edits that appear as failed. Usually failed edits + are near the bottom of the list. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.layout'] + category = 'layout' + optional = True + version = (0, 1, 0) + label = 'References Failed Edits' + actions = [colorbleed.api.SelectInvalidAction, + RepairFailedEditsAction] + + @staticmethod + def get_invalid(instance): + """Return invalid reference nodes in the instance + + Terminology: + reference node: The node that is the actual reference containing + the nodes (type: reference) + referenced nodes: The nodes contained within the reference + (type: any type of nodes) + + """ + referenced_nodes = cmds.ls(instance, referencedNodes=True, long=True) + if not referenced_nodes: + return list() + + # Get reference nodes from referenced nodes + # (note that reference_nodes != referenced_nodes) + reference_nodes = set() + for node in referenced_nodes: + reference_node = cmds.referenceQuery(node, referenceNode=True) + if reference_node: + reference_nodes.add(reference_node) + + # Check for failed edits on each reference node. + invalid = [] + for reference_node in reference_nodes: + failed_edits = cmds.referenceQuery(reference_node, + editNodes=True, + failedEdits=True, + successfulEdits=False) + if failed_edits: + invalid.append(reference_node) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Reference nodes found with failed " + "reference edits: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_resources.py b/colorbleed/plugins/maya/publish/validate_resources.py new file mode 100644 index 0000000000..57a9239905 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_resources.py @@ -0,0 +1,31 @@ +import pyblish.api +import colorbleed.api + +import os + + +class ValidateResources(pyblish.api.InstancePlugin): + """Validates mapped resources. + + These are external files to the current application, for example + these could be textures, image planes, cache files or other linked + media. + + This validates: + - The resources are existing files. + - The resources have correctly collected the data. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Resources" + + def process(self, instance): + + for resource in instance.data.get('resources', []): + + # Required data + assert "source" in resource + assert "destination" in resource + assert "files" in resource + assert all(os.path.exists(f) for f in resource['files']) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py new file mode 100644 index 0000000000..fb7ae0751a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -0,0 +1,63 @@ +import pyblish.api +import colorbleed.api + + +class ValidateRigContents(pyblish.api.InstancePlugin): + """Ensure rig contains pipeline-critical content + + Every rig must contain at least two object sets: + "controls_SET" - Set of all animatable controls + "pointcache_SET" - Set of all cachable meshes + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Rig Contents" + hosts = ["maya"] + families = ["colorbleed.rig", "colorbleed.rigcontrols", + "colorbleed.rigpointcache"] + + def process(self, instance): + + from maya import cmds + + objsets = ("controls_SET", "pointcache_SET") + + missing = list() + for objset in objsets: + if objset not in instance: + missing.append(objset) + + assert not missing, ("%s is missing %s" + % (instance, missing)) + + # Ensure there are at least some transforms or dag nodes + # in the rig instance + set_members = instance.data['setMembers'] + if not cmds.ls(set_members, type="dagNode", long=True): + raise RuntimeError("No dag nodes in the pointcache instance. " + "(Empty instance?)") + + self.log.info("Evaluating contents of object sets..") + not_meshes = list() + members = cmds.sets("pointcache_SET", query=True) or [] + shapes = cmds.listRelatives(members, + allDescendents=True, + shapes=True, + fullPath=True) or [] + for shape in shapes: + if cmds.nodeType(shape) != "mesh": + not_meshes.append(shape) + + not_transforms = list() + for node in cmds.sets("controls_SET", query=True) or []: + if cmds.nodeType(node) != "transform": + not_meshes.append(node) + + assert not_transforms == [], ( + "Only transforms can be part of the controls_SET: %s" + % not_transforms) + + assert not_meshes == [], ( + "Only meshes can be part of the pointcache_SET: %s" + % not_meshes) diff --git a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py new file mode 100644 index 0000000000..5a7ba1fd01 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py @@ -0,0 +1,108 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + +import cbra.utils.maya.node_uuid as id_utils + + +class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): + """Validate rig pointcache_SET nodes have related ids to current context + + An ID is 'related' if its built in the current Item. + + Note that this doesn't ensure it's from the current Task. An ID created + from `lookdev` has the same relation to the Item as one coming from others, + like `rigging` or `modeling`. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.rigpointcache', 'colorbleed.pointcache'] + hosts = ['maya'] + label = 'Rig Pointcache Related Node Ids' + actions = [colorbleed.api.SelectInvalidAction] + optional = True + + ignore_types = ("constraint",) + + @classmethod + def get_pointcache_nodes(cls, instance): + + # Get pointcache_SET + sets = cmds.ls(instance, type='objectSet') + pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + + nodes = list() + for s in pointcache_sets: + members = cmds.sets(s, query=True) + members = cmds.ls(members, long=True) # ensure long names + descendents = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + descendents = cmds.ls(descendents, + noIntermediate=True, + long=True) + hierarchy = members + descendents + nodes.extend(hierarchy) + + # ignore certain node types (e.g. constraints) + ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) + if ignore: + ignore = set(ignore) + nodes = [node for node in nodes if node not in ignore] + + return nodes + + @classmethod + def get_invalid(cls, instance): + import cbra.lib + + # Get a full context from the instance context + context = instance.data['instanceContext'] + item_path = context['itemPath'] + context = cbra.lib.parse_context(item_path) + nodes = cls.get_pointcache_nodes(instance) + + def to_item(id): + """Split the item id part from a node id""" + return id.rsplit(":", 1)[0] + + # Generate a fake id in the current context to retrieve the item + # id prefix that should match with ids on the nodes + fake_node = "__node__" + ids = id_utils.generate_ids(context, [fake_node]) + id = ids[fake_node] + item_prefix = to_item(id) + + # Parse the invalid + invalid = list() + invalid_items = set() + for member in nodes: + member_id = id_utils.get_id(member) + + # skip nodes without ids + if not member_id: + continue + + if not member_id.startswith(item_prefix): + invalid.append(member) + invalid_items.add(to_item(member_id)) + + # Log invalid item ids + if invalid_items: + for item_id in sorted(invalid_items): + cls.log.warning("Found invalid item id: {0}".format(item_id)) + + return invalid + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Nodes found with non-related " + "asset IDs: {0}".format(invalid)) + diff --git a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py new file mode 100644 index 0000000000..9ca28564f9 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py @@ -0,0 +1,47 @@ +import os + +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + + +def is_subdir(path, root_dir): + """ Returns whether path is a subdirectory (or file) within root_dir """ + path = os.path.realpath(path) + root_dir = os.path.realpath(root_dir) + + # If not on same drive + if os.path.splitdrive(path)[0] != os.path.splitdrive(root_dir)[0]: + return False + + # Get 'relative path' (can contain ../ which means going up) + relative = os.path.relpath(path, root_dir) + + # Check if the path starts by going up, if so it's not a subdirectory. :) + if relative.startswith(os.pardir) or relative == os.curdir: + return False + else: + return True + + +class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): + """Validate the scene is inside the currently set Maya workspace""" + + order = colorbleed.api.ValidatePipelineOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'scene' + version = (0, 1, 0) + label = 'Maya Workspace Set' + + def process(self, context): + + scene_name = cmds.file(query=True, sceneName=True) + if not scene_name: + raise RuntimeError("Scene hasn't been saved. Workspace can't be validated.") + + root_dir = cmds.workspace(query=True, rootDirectory=True) + + if not is_subdir(scene_name, root_dir): + raise RuntimeError("Maya workspace is not set correctly.") diff --git a/colorbleed/plugins/maya/publish/validate_shape_default_names.py b/colorbleed/plugins/maya/publish/validate_shape_default_names.py new file mode 100644 index 0000000000..75fd8f3f1e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_shape_default_names.py @@ -0,0 +1,88 @@ +import re + +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def short_name(node): + return node.rsplit("|", 1)[-1].rsplit(":", 1)[-1] + + +class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): + """Validates that Shape names are using Maya's default format. + + When you create a new polygon cube Maya will name the transform + and shape respectively: + - ['pCube1', 'pCubeShape1'] + If you rename it to `bar1` it will become: + - ['bar1', 'barShape1'] + Then if you rename it to `bar` it will become: + - ['bar', 'barShape'] + Rename it again to `bar1` it will differ as opposed to before: + - ['bar1', 'bar1Shape'] + Note that bar1Shape != barShape1 + Thus the suffix number can be either in front of Shape or behind it. + Then it becomes harder to define where what number should be when a + node contains multiple shapes, for example with many controls in + rigs existing of multiple curves. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'cleanup' + optional = True + version = (0, 1, 0) + label = "Shape Default Naming" + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + @staticmethod + def _define_default_name(shape): + parent = cmds.listRelatives(shape, parent=True, fullPath=True)[0] + transform = short_name(parent) + return '{0}Shape'.format(transform) + + @staticmethod + def _is_valid(shape): + """ Return whether the shape's name is similar to Maya's default. """ + transform = cmds.listRelatives(shape, parent=True, fullPath=True)[0] + + transform_name = short_name(transform) + shape_name = short_name(shape) + + # A Shape's name can be either {transform}{numSuffix} + # Shape or {transform}Shape{numSuffix} + # Upon renaming nodes in Maya that is + # the pattern Maya will act towards. + transform_no_num = transform_name.rstrip("0123456789") + pattern = '^{transform}[0-9]*Shape[0-9]*$'.format( + transform=transform_no_num) + + if re.match(pattern, shape_name): + return True + else: + return False + + @classmethod + def get_invalid(cls, instance): + shapes = cmds.ls(instance, shapes=True, long=True) + return [shape for shape in shapes if not cls._is_valid(shape)] + + def process(self, instance): + """Process all the shape nodes in the instance""" + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Incorrectly named shapes " + "found: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + """Process all the shape nodes in the instance""" + for shape in cls.get_invalid(instance): + correct_shape_name = cls._define_default_name(shape) + cmds.rename(shape, correct_shape_name) diff --git a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py new file mode 100644 index 0000000000..36f9e2caa4 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py @@ -0,0 +1,65 @@ +import pyblish.api +import colorbleed.api + +from maya import cmds + + +class ValidateShapeRenderStats(pyblish.api.Validator): + """Ensure all render stats are set to the default values.""" + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'model' + optional = False + version = (0, 1, 0) + label = 'Shape Default Render Stats' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + defaults = {'castsShadows': 1, + 'receiveShadows': 1, + 'motionBlur': 1, + 'primaryVisibility': 1, + 'smoothShading': 1, + 'visibleInReflections': 1, + 'visibleInRefractions': 1, + 'doubleSided': 1, + 'opposite': 0} + + @staticmethod + def get_invalid(instance): + # It seems the "surfaceShape" and those derived from it have + # `renderStat` attributes. + shapes = cmds.ls(instance, long=True, type='surfaceShape') + invalid = [] + for shape in shapes: + for attr, requiredValue in \ + ValidateShapeRenderStats.defaults.iteritems(): + + if cmds.attributeQuery(attr, node=shape, exists=True): + value = cmds.getAttr('{}.{}'.format(shape, attr)) + if value != requiredValue: + invalid.append(shape) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Shapes with non-standard renderStats " + "found: {0}".format(invalid)) + + @staticmethod + def repair(instance): + shape_render_defaults = ValidateShapeRenderStats.defaults + for shape in ValidateShapeRenderStats.get_invalid(instance): + for attr, default_value in shape_render_defaults.iteritems(): + + if cmds.attributeQuery(attr, node=shape, exists=True): + plug = '{0}.{1}'.format(shape, attr) + value = cmds.getAttr(plug) + if value != default_value: + cmds.setAttr(plug, default_value) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py new file mode 100644 index 0000000000..566a332106 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -0,0 +1,29 @@ +import pyblish.api +import colorbleed.api + + +class ValidateSingleAssembly(pyblish.api.InstancePlugin): + """Ensure all nodes are in a single assembly + + Published assets must be contained within a single transform + at the root of your outliner. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.rig', 'colorbleed.layout', 'avalon.animation'] + label = 'Single Assembly' + + def process(self, instance): + from maya import cmds + + assemblies = cmds.ls(instance, assemblies=True) + + # ensure unique (somehow `maya.cmds.ls` doesn't manage that) + assemblies = set(assemblies) + + assert len(assemblies) > 0, ( + "One assembly required for: %s (currently empty?)" % instance) + assert len(assemblies) < 2, ( + 'Multiple assemblies found: %s' % assemblies) diff --git a/colorbleed/plugins/maya/publish/validate_step_size.py b/colorbleed/plugins/maya/publish/validate_step_size.py new file mode 100644 index 0000000000..097bb1399d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_step_size.py @@ -0,0 +1,42 @@ +import pyblish.api +import colorbleed.api + + +class ValidateStepSize(pyblish.api.InstancePlugin): + """Validates the step size for the instance is in a valid range. + + For example the `step` size should never be lower or equal to zero. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = 'Step size' + families = ['colorbleed.layout'] + actions = [colorbleed.api.SelectInvalidAction] + + MIN = 0.01 + MAX = 1.0 + + @classmethod + def get_invalid(cls, instance): + + objset = instance.data['setMembers'] + + invalid = set() + step = instance.data.get("step", 1.0) + if step < cls.MIN or step > cls.MAX: + cls.log.warning("Step size is outside of valid range: {0} " + "(valid: {1} to {2})".format(step, + cls.MIN, + cls.MAX)) + invalid.add(objset) + + invalid = list(invalid) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid instances found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/colorbleed/plugins/maya/publish/validate_transfers.py new file mode 100644 index 0000000000..6d98e8d97d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_transfers.py @@ -0,0 +1,49 @@ +import pyblish.api +import colorbleed.api + +from collections import defaultdict + + +class ValidateTransfers(pyblish.api.InstancePlugin): + """Validates mapped resources. + + This validates: + - The resources all transfer to a unique destination. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Transfers" + + def process(self, instance): + + transfers = instance.data.get("transfers", []) + if not transfers: + return + + verbose = instance.data.get('verbose', False) + + # Collect all destination with its sources + collected = defaultdict(set) + for source, destination in transfers: + collected[destination.lower()].add(source.lower()) + + invalid = False + invalid_destinations = list() + for destination, sources in collected.items(): + if len(sources) > 1: + if verbose: + self.log.error("Non-unique file transfer for resources: {0} " + "(sources: {1})".format(destination, sources)) + invalid = True + invalid_destinations.append(destination) + + if invalid: + + if not verbose: + # If not verbose then still log the resource destination as + # opposed to every individual file transfer + self.log.error("Non-unique file transfers to destinations: " + "%s" % invalid_destinations) + + raise RuntimeError("Invalid transfers in queue.") diff --git a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py new file mode 100644 index 0000000000..2ec2cfd9da --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py @@ -0,0 +1,83 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP"], + 'nurbsCurve': ["_CRV"], + 'nurbsSurface': ["_NRB"], + None: ['_GRP']} + +ALLOW_IF_NOT_IN_SUFFIX_TABLE = True + + +class ValidateTransformNamingSuffix(pyblish.api.Validator): + """Validates transform suffix based on the type of its children shapes. + + Suffices must be: + - mesh: _GEO or _GES or _GEP + - nurbsCurve: _CRV + - nurbsSurface: _NRB + - null/group: _GRP + + .. warning:: + This grabs the first child shape as a reference and doesn't use the + others in the check. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.model'] + category = 'cleanup' + optional = True + version = (0, 1, 0) + label = 'Suffix Naming Conventions' + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def is_valid_name(node_name, shape_type): + """Return whether node's name is correct. + + The correctness for a transform's suffix is dependent on what + `shape_type` it holds. E.g. a transform with a mesh might need and + `_GEO` suffix. + + When `shape_type` is None the transform doesn't have any direct + children shapes. + + """ + if shape_type not in SUFFIX_NAMING_TABLE: + return ALLOW_IF_NOT_IN_SUFFIX_TABLE + else: + suffices = SUFFIX_NAMING_TABLE[shape_type] + for suffix in suffices: + if node_name.endswith(suffix): + return True + return False + + @classmethod + def get_invalid(cls, instance): + transforms = cmds.ls(instance, type='transform', long=True) + + invalid = [] + for transform in transforms: + shapes = cmds.listRelatives(transform, + shapes=True, + fullPath=True, + noIntermediate=True) + + shape_type = cmds.nodeType(shapes[0]) if shapes else None + if not cls.is_valid_name(transform, shape_type): + invalid.append(transform) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Incorrectly named geometry " + "transforms: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_transform_zero.py b/colorbleed/plugins/maya/publish/validate_transform_zero.py new file mode 100644 index 0000000000..fa87539d96 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_transform_zero.py @@ -0,0 +1,64 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateTransformZero(pyblish.api.Validator): + """Transforms can't have any values + + To solve this issue, try freezing the transforms. So long + as the transforms, rotation and scale values are zero, + you're all good. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + families = ["colorbleed.model"] + category = "geometry" + version = (0, 1, 0) + label = "Transform Zero (Freeze)" + actions = [colorbleed.api.SelectInvalidAction] + + _identity = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + _tolerance = 1e-30 + + @classmethod + def get_invalid(cls, instance): + """Returns the invalid transforms in the instance. + + This is the same as checking: + - translate == [0, 0, 0] and rotate == [0, 0, 0] and + scale == [1, 1, 1] and shear == [0, 0, 0] + + .. note:: + This will also catch camera transforms if those + are in the instances. + + Returns: + list: Transforms that are not identity matrix + + """ + + transforms = cmds.ls(instance, type="transform") + + invalid = [] + for transform in transforms: + mat = cmds.xform(transform, q=1, matrix=True, objectSpace=True) + if not all(abs(x-y) < cls._tolerance + for x, y in zip(cls._identity, mat)): + invalid.append(transform) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance "objectSet""" + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Nodes found with transform " + "values: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py new file mode 100644 index 0000000000..6b03deb8dc --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py @@ -0,0 +1,90 @@ +import os + +import pyblish.api +import colorbleed.api + + +class ValidateYetiCacheFrames(pyblish.api.InstancePlugin): + """Validates Yeti nodes have existing cache frames""" + + order = colorbleed.api.ValidateContentsOrder + label = 'Yeti Cache Frames' + families = ['colorbleed.furYeti'] + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + # Check if all frames cache exists for given node. + start_frame = instance.data.get("startFrame") + end_frame = instance.data.get("endFrame") + required = range(int(start_frame), int(end_frame) + 1) + + yeti_caches = instance.data.get('yetiCaches', {}) + invalid = [] + + for node, data in yeti_caches.items(): + cls.log.info("Validating node: {0}".format(node)) + + source = data.get("source", None) + sequences = data.get("sequences", []) + + if not source: + invalid.append(node) + cls.log.warning("Node has no cache file name set: " + "{0}".format(node)) + continue + + folder = os.path.dirname(source) + if not folder or not os.path.exists(folder): + invalid.append(node) + cls.log.warning("Cache folder does not exist: " + "{0} {1}".format(node, folder)) + continue + + if not sequences: + invalid.append(node) + cls.log.warning("Sequence does not exist: " + "{0} {1}".format(node, source)) + continue + + if len(sequences) != 1: + invalid.append(node) + cls.log.warning("More than one sequence found? " + "{0} {1}".format(node, source)) + cls.log.warning("Found caches: {0}".format(sequences)) + continue + + sequence = sequences[0] + + start = sequence.start() + end = sequence.end() + if start > start_frame or end < end_frame: + invalid.append(node) + cls.log.warning("Sequence does not have enough " + "frames: {0}-{1} (requires: {2}-{3})" + "".format(start, end, + start_frame, + end_frame)) + continue + + # Ensure all frames are present + missing = set(sequence.missing()) + required_missing = [x for x in required if x in missing] + if required_missing: + + invalid.append(node) + cls.log.warning("Sequence is missing required frames: " + "{0}".format(required_missing)) + continue + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + self.log.error("Invalid nodes: {0}".format(invalid)) + raise RuntimeError("Invalid yeti nodes in instance. " + "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py new file mode 100644 index 0000000000..e37f3ecfb7 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py @@ -0,0 +1,43 @@ +import pyblish.api +import colorbleed.api + + +class ValidateYetiCacheNonPublish(pyblish.api.InstancePlugin): + """Validates Yeti caches are not published FROM published caches""" + + order = colorbleed.api.ValidateContentsOrder + label = 'Yeti Cache Non Publish' + families = ['colorbleed.furYeti'] + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + import cbra.lib + + invalid = list() + for node, data in instance.data['yetiCaches'].items(): + + source = data['source'] + + # Published folder has at least "publish" in its path + if "publish" not in source.lower(): + continue + + try: + context = cbra.lib.parse_context(source) + except RuntimeError: + continue + + if "family" in context or "subset" in context: + invalid.append(node) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + self.log.error("Invalid nodes: {0}".format(invalid)) + raise RuntimeError("Invalid yeti nodes in instance. " + "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py new file mode 100644 index 0000000000..3fd45e7558 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py @@ -0,0 +1,81 @@ +import os +import re +from collections import defaultdict + +import pyblish.api +import colorbleed.api + +import maya.cmds as cmds + + +class ValidateYetiCacheUniqueFilenames(pyblish.api.InstancePlugin): + """Validates Yeti nodes in instance have unique filename patterns. + + This is to ensure Yeti caches in a single instance don't overwrite each + other's files when published to a single flat folder structure. + + For example: + cache1: path/to/arm.%04d.fur + cache2: other/path/to/arm.%04d.fur + + Both these caches point to unique files, though they have the same filename + pattern. When copied to a single folder they would overwrite each other, + and as such are considered invalid. To fix this rename the caches filenames + to be unique, like `left_arm.%04d.fur` and `right_arm.%04d.fur`. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = 'Yeti Cache Unique Filenames' + families = ['colorbleed.furYeti'] + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + + members = instance.data["setMembers"] + shapes = cmds.ls(members, dag=True, leaf=True, shapes=True, long=True) + yeti_nodes = cmds.ls(shapes, type="pgYetiMaya", long=True) + + if not yeti_nodes: + raise RuntimeError("No pgYetiMaya nodes in instance.") + + def _to_pattern(path): + """Path to pattern that pyseq.get_sequences can use""" + return re.sub(r"([0-9]+|%[0-9]+d)(.fur)$", r"[0-9]*\2", path) + + invalid = list() + + # Collect cache patterns + cache_patterns = defaultdict(list) + for node in yeti_nodes: + + path = cmds.getAttr(node + ".cacheFileName") + if not path: + invalid.append(node) + cls.log.warning("Node has no cache file name set: " + "{0}".format(node)) + continue + + filename = os.path.basename(path) + pattern = _to_pattern(filename) + + cache_patterns[pattern].append(node) + + # Identify non-unique cache patterns + for pattern, nodes in cache_patterns.iteritems(): + if len(nodes) > 1: + cls.log.warning("Nodes have same filename pattern ({0}): " + "{1}".format(pattern, nodes)) + invalid.extend(nodes) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + self.log.error("Invalid nodes: {0}".format(invalid)) + raise RuntimeError("Invalid yeti nodes in instance. " + "See logs for details.") diff --git a/colorbleed/plugins/publish/collect_comment.py b/colorbleed/plugins/publish/collect_comment.py new file mode 100644 index 0000000000..e3da4486c4 --- /dev/null +++ b/colorbleed/plugins/publish/collect_comment.py @@ -0,0 +1,11 @@ +import pyblish.api + + +class CollectMindbenderComment(pyblish.api.ContextPlugin): + """This plug-ins displays the comment dialog box per default""" + + label = "Collect Mindbender Time" + order = pyblish.api.CollectorOrder + + def process(self, context): + context.data["comment"] = "" diff --git a/colorbleed/plugins/publish/collect_time.py b/colorbleed/plugins/publish/collect_time.py new file mode 100644 index 0000000000..9536c610d6 --- /dev/null +++ b/colorbleed/plugins/publish/collect_time.py @@ -0,0 +1,12 @@ +import pyblish.api + + +class CollectMindbenderTime(pyblish.api.ContextPlugin): + """Store global time at the time of publish""" + + label = "Collect Mindbender Time" + order = pyblish.api.CollectorOrder + + def process(self, context): + from avalon import api + context.data["time"] = api.time() diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py new file mode 100644 index 0000000000..457f0cfa67 --- /dev/null +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -0,0 +1,273 @@ +import os +import errno +import shutil +from pprint import pformat + +import pyblish.api +from avalon import api, io + + +class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): + """Write to files and metadata + + This plug-in exposes your data to others by encapsulating it + into a new version. + + Schema: + Data is written in the following format. + ____________________ + | | + | version | + | ________________ | + | | | | + | | representation | | + | |________________| | + | | | | + | | ... | | + | |________________| | + |____________________| + + """ + + label = "Integrate Asset" + order = pyblish.api.IntegratorOrder + families = [ + "colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.historyLookdev", + "colorbleed.group" + ] + + def process(self, instance): + # Required environment variables + PROJECT = os.environ["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] + SILO = os.environ["AVALON_SILO"] + LOCATION = os.getenv("AVALON_LOCATION") + + # todo(marcus): avoid hardcoding labels in the integrator + representation_labels = {".ma": "Maya Ascii", + ".source": "Original source file", + ".abc": "Alembic"} + + context = instance.context + # Atomicity + # + # Guarantee atomic publishes - each asset contains + # an identical set of members. + # __ + # / o + # / \ + # | o | + # \ / + # o __/ + # + assert all(result["success"] for result in context.data["results"]), ( + "Atomicity not held, aborting.") + + # Assemble + # + # | + # v + # ---> <---- + # ^ + # | + # + stagingdir = instance.data.get("stagingDir") + assert stagingdir, ("Incomplete instance \"%s\": " + "Missing reference to staging area." % instance) + + self.log.debug("Establishing staging directory @ %s" % stagingdir) + + project = io.find_one({"type": "project"}) + asset = io.find_one({"name": ASSET}) + + assert all([project, asset]), ("Could not find current project or " + "asset '%s'" % ASSET) + + subset = self.get_subset(asset, instance) + + # get next version + latest_version = io.find_one({"type": "version", + "parent": subset["_id"]}, + {"name": True}, + sort=[("name", -1)]) + + next_version = 1 + if latest_version is not None: + next_version += latest_version["name"] + + self.log.debug("Next version: %i" % next_version) + + version_data = self.create_version_data(context, instance) + version = self.create_version(subset=subset, + version_number=next_version, + locations=[LOCATION], + data=version_data) + + self.log.debug("Creating version: %s" % pformat(version)) + version_id = io.insert_one(version).inserted_id + + # Write to disk + # _ + # | | + # _| |_ + # ____\ / + # |\ \ / \ + # \ \ v \ + # \ \________. + # \|________| + # + template_data = { + "root": api.registered_root(), + "project": PROJECT, + "silo": SILO, + "asset": ASSET, + "subset": subset["name"], + "version": version["name"], + } + + template_publish = project["config"]["template"]["publish"] + + for fname in os.listdir(stagingdir): + name, ext = os.path.splitext(fname) + template_data["representation"] = ext[1:] + + src = os.path.join(stagingdir, fname) + dst = template_publish.format(**template_data) + + # Backwards compatibility + if fname == ".metadata.json": + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, ".metadata.json") + + self.log.info("Copying %s -> %s" % (src, dst)) + + # copy source to destination (library) + self.copy_file(src, dst) + + representation = { + "schema": "avalon-core:representation-2.0", + "type": "representation", + "parent": version_id, + "name": ext[1:], + "data": {"label": representation_labels.get(ext)}, + "dependencies": instance.data.get("dependencies", "").split(), + + # Imprint shortcut to context + # for performance reasons. + "context": { + "project": PROJECT, + "asset": ASSET, + "silo": SILO, + "subset": subset["name"], + "version": version["name"], + "representation": ext[1:] + } + } + + io.insert_one(representation) + + self.log.info("Successfully integrated \"%s\" to \"%s\"" % ( + instance, dst)) + + def get_subset(self, asset, instance): + + subset = io.find_one({"type": "subset", + "parent": asset["_id"], + "name": instance.data["subset"]}) + + if subset is None: + subset_name = instance.data["subset"] + self.log.info("Subset '%s' not found, creating.." % subset_name) + + _id = io.insert_one({ + "schema": "avalon-core:subset-2.0", + "type": "subset", + "name": subset_name, + "data": {}, + "parent": asset["_id"] + }).inserted_id + + subset = io.find_one({"_id": _id}) + + return subset + + def create_representation(self): + pass + + def create_version(self, subset, version_number, locations, data=None): + """ Copy given source to destination + + Arguments: + subset (dict): the registered subset of the asset + version_number (int): the version number + locations (list): the currently registered locations + """ + # Imprint currently registered location + version_locations = [location for location in locations if + location is not None] + + return {"schema": "avalon-core:version-2.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "locations": version_locations, + "data": data} + + def create_version_data(self, context, instance): + """ + Create the data collection for th version + Args: + context (object): the current context + instance(object): the current instance being published + + Returns: + dict: the required information with instance.data as key + """ + + families = [] + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + + families += current_families + if instance_family is not None: + families.append(instance_family) + + # create relative source path for DB + relative_path = os.path.relpath(context.data["currentFile"], + api.registered_root()) + source = os.path.join("{root}", relative_path).replace("\\", "/") + + version_data = {"families": families, + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment")} + + return dict(instance.data, **version_data) + + def copy_file(self, src, dst): + """ Copy given source to destination + + Arguments: + src (str): the source file which needs to be copied + dst (str): the destination of the sourc file + Returns: + None + """ + + dirname = os.path.dirname(dst) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + raise + + shutil.copy(src, dst) \ No newline at end of file diff --git a/colorbleed/plugins/publish/validate_file_saved.py b/colorbleed/plugins/publish/validate_file_saved.py new file mode 100644 index 0000000000..33d8ce0457 --- /dev/null +++ b/colorbleed/plugins/publish/validate_file_saved.py @@ -0,0 +1,15 @@ +import pyblish.api + + +class ValidateCurrentSaveFile(pyblish.api.ContextPlugin): + """File must be saved before publishing""" + + label = "Validate File Saved" + order = pyblish.api.ValidatorOrder - 0.1 + hosts = ["maya", "houdini"] + + def process(self, context): + + current_file = context.data["currentFile"] + if not current_file: + raise RuntimeError("File not saved") From 58d7d17a59b1bdf490d9e82f28d053625ae8f2a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 15:59:53 +0200 Subject: [PATCH 0002/1249] Add README.md --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..8fd9ab077d --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +The [Colorbleed](http://www.colorbleed.nl/) animation studio *config* for [Avalon](https://getavalon.github.io/) + +
+ +_This configuration is used for animation in film and advertising._ + +### Code convention + +Below are some of the standard practices applied to this repositories. + +- **Etiquette: PEP8** + - All code is written in PEP8. It is recommended you use a linter as you work, flake8 and pylinter are both good options. +- **Etiquette: Napoleon docstrings** + - Any docstrings are made in Google Napoleon format. See [Napoleon](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) for details. +- **Etiquette: Semantic Versioning** + - This project follows [semantic versioning](http://semver.org). +- **Etiquette: Underscore means private** + - Anything prefixed with an underscore means that it is internal to wherever it is used. For example, a variable name is only ever used in the parent function or class. A module is not for use by the end-user. In contrast, anything without an underscore is public, but not necessarily part of the API. Members of the API resides in `api.py`. +- **API: Idempotence** + - A public function must be able to be called twice and produce the exact same result. This means no changing of state without restoring previous state when finishing. For example, if a function requires changing the current selection in Autodesk Maya, it must restore the previous selection prior to completing. \ No newline at end of file From 0387bc85aa675b324becd62d8c0e00fa39c9b032 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 16:23:46 +0200 Subject: [PATCH 0003/1249] Improve docstring --- .../plugins/maya/publish/validate_instance_subset.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_instance_subset.py b/colorbleed/plugins/maya/publish/validate_instance_subset.py index 42498941a3..6a26d09594 100644 --- a/colorbleed/plugins/maya/publish/validate_instance_subset.py +++ b/colorbleed/plugins/maya/publish/validate_instance_subset.py @@ -14,12 +14,7 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): - """Adheres to the content of 'model' family - - - Must have one top group named: geo_GRP - - Must only contain: transforms, meshes and groups - - """ + """Validates subset name has only valid characters""" order = colorbleed.api.ValidateContentsOrder families = ["*"] From e39e1afd8def69b1c7b0b019dbb9b06ef58c9f3e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 16:28:18 +0200 Subject: [PATCH 0004/1249] Remove todo and extend the docstring to include the information --- .../plugins/maya/publish/validate_namespace_empty.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py index a44214a657..46591ed125 100644 --- a/colorbleed/plugins/maya/publish/validate_namespace_empty.py +++ b/colorbleed/plugins/maya/publish/validate_namespace_empty.py @@ -8,6 +8,9 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): This is a scene wide validation that filters out "UI" and "shared" namespaces that exist by default in Maya and are mostly hidden. + + A namespace that has other namespaces in it is not considered empty. + Only those that have no children namespaces or nodes is considered emtpy. """ @@ -19,7 +22,7 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): label = "No Empty Namespaces" def process(self, context): - """Process the Context""" + all_namespaces = cmds.namespaceInfo(":", listOnlyNamespaces=True, recurse=True) @@ -27,9 +30,6 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): if ns not in ["UI", "shared"]] invalid = [] - # TODO: Check whether currently a namespace with - # another namespace in it (both empty) is - # considered empty for namespace in non_internal_namespaces: namespace_content = cmds.namespaceInfo(namespace, listNamespace=True, From 158c59752a4d8b620157f58c1664eb157067dc5a Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 23 Jun 2017 17:09:16 +0200 Subject: [PATCH 0005/1249] Refactored families to match previous pipeline families --- colorbleed/maya/lib.py | 28 ++++++++++++++-- .../maya/create/colorbleed_animation.py | 26 ++++++--------- .../plugins/maya/create/colorbleed_camera.py | 13 ++++++-- .../plugins/maya/create/colorbleed_layout.py | 30 +++++++++++++---- .../plugins/maya/create/colorbleed_look.py | 11 ++++++- .../maya/create/colorbleed_pointcache.py | 33 ++++++++++++++++++- .../plugins/maya/create/colorbleed_yetifur.py | 15 +++++---- .../maya/publish/validate_instance_subset.py | 7 +++- .../maya/publish/validate_namespace_empty.py | 8 ++--- 9 files changed, 130 insertions(+), 41 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 9a594e4904..aa5345166c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2,6 +2,8 @@ import re import contextlib +from collections import OrderedDict + from maya import cmds @@ -216,7 +218,27 @@ def shape_from_element(element): return node -def add_attributes(node, data): +def collect_animation_data(): + """Get the basic animation data - for key, value in data.items(): - cmds.addAttr() \ No newline at end of file + Returns: + OrderedDict + + """ + + # get scene values as defaults + start = cmds.playbackOptions(query=True, animationStartTime=True) + end = cmds.playbackOptions(query=True, animationEndTime=True) + + # build attributes + data = OrderedDict() + data["startFrame"] = start + data["endFrame"] = end + data["handles"] = 1 + data["step"] = 1.0 + + return data + + +def get_current_renderlayer(): + return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index c2585dd5e5..d7a008ec10 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,7 +1,5 @@ -from collections import OrderedDict - import avalon.maya -from maya import cmds +from colorbleed.maya import lib class CreateAnimation(avalon.maya.Creator): @@ -14,26 +12,22 @@ class CreateAnimation(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) - # get scene values as defaults - start = cmds.playbackOptions(query=True, animationStartTime=True) - end = cmds.playbackOptions(query=True, animationEndTime=True) + # create an ordered dict with the existing data first + data = lib.OrderedDict(**self.data) - # build attributes - attributes = OrderedDict() - attributes["startFrame"] = start - attributes["endFrame"] = end - attributes["handles"] = 1 - attributes["step"] = 1.0 + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + data[key] = value # Write vertex colors with the geometry. - attributes["writeColorSets"] = False + data["writeColorSets"] = False # Include only renderable visible shapes. # Skips locators and empty transforms - attributes["renderableOnly"] = False + data["renderableOnly"] = False # Include only nodes that are visible at least once during the # frame range. - attributes["visibleOnly"] = False + data["visibleOnly"] = False - self.data = attributes + self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 52f037278c..41864503d8 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,4 +1,5 @@ import avalon.maya +from colorbleed.maya import lib class CreateCamera(avalon.maya.Creator): @@ -8,5 +9,13 @@ class CreateCamera(avalon.maya.Creator): label = "Camera" family = "colorbleed.camera" - # def process(self): - # pass \ No newline at end of file + def __init__(self, *args, **kwargs): + super(CreateCamera, self).__init__(*args, **kwargs) + + # get basic animation data : start / end / handles / steps + data = lib.OrderedDict(**self.data) + animation_data = lib.collect_animation_data() + for key, value in animation_data.items(): + data[key] = value + + self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_layout.py b/colorbleed/plugins/maya/create/colorbleed_layout.py index 15fa50c97c..24855bc178 100644 --- a/colorbleed/plugins/maya/create/colorbleed_layout.py +++ b/colorbleed/plugins/maya/create/colorbleed_layout.py @@ -1,4 +1,5 @@ import avalon.maya +from colorbleed.maya import lib class CreateLayout(avalon.maya.Creator): @@ -10,11 +11,26 @@ class CreateLayout(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateLayout, self).__init__(*args, **kwargs) - from maya import cmds - self.data.update({ - "startFrame": lambda: cmds.playbackOptions( - query=True, animationStartTime=True), - "endFrame": lambda: cmds.playbackOptions( - query=True, animationEndTime=True), - }) + # create an ordered dict with the existing data first + data = lib.OrderedDict(**self.data) + + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + data[key] = value + + # Write vertex colors with the geometry. + data["writeColorSets"] = False + + # Write GPU cache as placeholder cube in stead of full data + data["placeholder"] = False + + # Include only renderable visible shapes. + # Skips locators and empty transforms + data["renderableOnly"] = False + + # Include only nodes that are visible at least once during the + # frame range. + data["visibleOnly"] = False + + self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index 1ddc1afead..b93673b523 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -1,4 +1,5 @@ import avalon.maya +from colorbleed.maya import lib class CreateLook(avalon.maya.Creator): @@ -6,4 +7,12 @@ class CreateLook(avalon.maya.Creator): name = "lookDefault" label = "Look Dev" - family = "colorbleed.look" \ No newline at end of file + family = "colorbleed.look" + + def __init__(self, *args, **kwargs): + super(CreateLook, self).__init__(*args, **kwargs) + + data = lib.OrderedDict(**self.data) + data["renderlayer"] = lib.get_current_renderlayer() + + self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index d8a837c4e1..fb9af66b1c 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -1,3 +1,7 @@ +from collections import OrderedDict + +from maya import cmds + import avalon.maya @@ -6,4 +10,31 @@ class CreatePointCache(avalon.maya.Creator): name = "pointcache" label = "Point Cache" - family = "colorbleed.pointcache" \ No newline at end of file + family = "colorbleed.pointcache" + + def __init__(self, *args, **kwargs): + super(CreatePointCache, self).__init__(*args, **kwargs) + + # get scene values as defaults + start = cmds.playbackOptions(query=True, animationStartTime=True) + end = cmds.playbackOptions(query=True, animationEndTime=True) + + # build attributes + attributes = OrderedDict() + attributes["startFrame"] = start + attributes["endFrame"] = end + attributes["handles"] = 1 + attributes["step"] = 1.0 + + # Write vertex colors with the geometry. + attributes["writeColorSets"] = False + + # Include only renderable visible shapes. + # Skips locators and empty transforms + attributes["renderableOnly"] = False + + # Include only nodes that are visible at least once during the + # frame range. + attributes["visibleOnly"] = False + + self.data = attributes \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_yetifur.py b/colorbleed/plugins/maya/create/colorbleed_yetifur.py index b8e523a95c..5a4b483a30 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yetifur.py +++ b/colorbleed/plugins/maya/create/colorbleed_yetifur.py @@ -1,5 +1,5 @@ import avalon.maya -import colorbleed.api as api +from colorbleed.maya import lib class CreateYetiFur(avalon.maya.Creator): @@ -9,10 +9,13 @@ class CreateYetiFur(avalon.maya.Creator): label = "Yeti Fur" family = "colorbleed.yetifur" - def process(self): + def __init__(self, *args, **kwargs): + super(CreateYetiFur, self).__init__(*args, **kwargs) - time_with_handles = api.OrderedDict(startFrame=True, - endFrame=True, - handles=True) + # get scene values as defaults + data = lib.OrderedDict(**self.data) + animation_data = lib.collect_animation_data() + for key, value in animation_data.items(): + data[key] = value - api.merge() + self.data = data diff --git a/colorbleed/plugins/maya/publish/validate_instance_subset.py b/colorbleed/plugins/maya/publish/validate_instance_subset.py index 6a26d09594..42498941a3 100644 --- a/colorbleed/plugins/maya/publish/validate_instance_subset.py +++ b/colorbleed/plugins/maya/publish/validate_instance_subset.py @@ -14,7 +14,12 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): - """Validates subset name has only valid characters""" + """Adheres to the content of 'model' family + + - Must have one top group named: geo_GRP + - Must only contain: transforms, meshes and groups + + """ order = colorbleed.api.ValidateContentsOrder families = ["*"] diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py index 46591ed125..a44214a657 100644 --- a/colorbleed/plugins/maya/publish/validate_namespace_empty.py +++ b/colorbleed/plugins/maya/publish/validate_namespace_empty.py @@ -8,9 +8,6 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): This is a scene wide validation that filters out "UI" and "shared" namespaces that exist by default in Maya and are mostly hidden. - - A namespace that has other namespaces in it is not considered empty. - Only those that have no children namespaces or nodes is considered emtpy. """ @@ -22,7 +19,7 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): label = "No Empty Namespaces" def process(self, context): - + """Process the Context""" all_namespaces = cmds.namespaceInfo(":", listOnlyNamespaces=True, recurse=True) @@ -30,6 +27,9 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): if ns not in ["UI", "shared"]] invalid = [] + # TODO: Check whether currently a namespace with + # another namespace in it (both empty) is + # considered empty for namespace in non_internal_namespaces: namespace_content = cmds.namespaceInfo(namespace, listNamespace=True, From 47c60ed1d32565c4c581bb799d4c0a2501b2c778 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 17:14:12 +0200 Subject: [PATCH 0006/1249] Reapply previous commits --- .../plugins/maya/publish/validate_instance_subset.py | 7 +------ .../plugins/maya/publish/validate_namespace_empty.py | 8 +++----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_instance_subset.py b/colorbleed/plugins/maya/publish/validate_instance_subset.py index 42498941a3..6a26d09594 100644 --- a/colorbleed/plugins/maya/publish/validate_instance_subset.py +++ b/colorbleed/plugins/maya/publish/validate_instance_subset.py @@ -14,12 +14,7 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): - """Adheres to the content of 'model' family - - - Must have one top group named: geo_GRP - - Must only contain: transforms, meshes and groups - - """ + """Validates subset name has only valid characters""" order = colorbleed.api.ValidateContentsOrder families = ["*"] diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py index a44214a657..5cf19e410f 100644 --- a/colorbleed/plugins/maya/publish/validate_namespace_empty.py +++ b/colorbleed/plugins/maya/publish/validate_namespace_empty.py @@ -8,14 +8,15 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): This is a scene wide validation that filters out "UI" and "shared" namespaces that exist by default in Maya and are mostly hidden. + + A namespace that has other namespaces in it is *not* considered empty. + Only those that have no children namespaces or nodes is considered empty. """ order = colorbleed.api.ValidateSceneOrder hosts = ["maya"] families = ["colorbleed.model"] - category = "scene" - version = (0, 1, 0) label = "No Empty Namespaces" def process(self, context): @@ -27,9 +28,6 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): if ns not in ["UI", "shared"]] invalid = [] - # TODO: Check whether currently a namespace with - # another namespace in it (both empty) is - # considered empty for namespace in non_internal_namespaces: namespace_content = cmds.namespaceInfo(namespace, listNamespace=True, From 19e8cc31c5218f1f5bd888ccba730b7ad2f7389a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 17:50:06 +0200 Subject: [PATCH 0007/1249] Refactor rig's "pointcache_SET" to "out_SET" --- colorbleed/plugins/maya/create/colorbleed_rig.py | 2 +- .../maya/publish/_validate_rig_pointcache_node_ids.py | 8 ++++---- .../_validate_rig_pointcache_shape_render_stats.py | 4 ++-- colorbleed/plugins/maya/publish/collect_look.py | 2 +- colorbleed/plugins/maya/publish/validate_rig_contents.py | 8 ++++---- .../publish/validate_rig_pointcache_related_node_ids.py | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index 4af7eaae3e..b29d5ae7e5 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -13,5 +13,5 @@ class CreateRig(avalon.maya.Creator): instance = super(CreateRig, self).process() controls = cmds.sets(name="controls_SET", empty=True) - pointcache = cmds.sets(name="pointcache_SET", empty=True) + pointcache = cmds.sets(name="out_SET", empty=True) cmds.sets([controls, pointcache], forceElement=instance) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py index afb2fd5bb2..8dcee25aeb 100644 --- a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py +++ b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py @@ -5,9 +5,9 @@ import colorbleed.api class ValidateRigPointcacheNodeIds(pyblish.api.InstancePlugin): - """Validate rig pointcache_SET nodes have ids + """Validate rig out_SET nodes have ids - The nodes in a rig's pointcache_SET must all have node IDs + The nodes in a rig's out_SET must all have node IDs that are all unique. Geometry in a rig should be using published model's geometry. @@ -29,9 +29,9 @@ class ValidateRigPointcacheNodeIds(pyblish.api.InstancePlugin): def get_invalid(cls, instance): from maya import cmds - # Get pointcache_SET + # Get out_SET sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + pointcache_sets = [x for x in sets if x == 'out_SET'] nodes = list() for s in pointcache_sets: diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py index 7663103bbe..0f38f79744 100644 --- a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py +++ b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py @@ -31,9 +31,9 @@ class ValidateRigPointcacheShapeRenderStats(pyblish.api.Validator): @classmethod def get_pointcache_nodes(cls, instance): - # Get pointcache_SET + # Get out_SET sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + pointcache_sets = [x for x in sets if x == 'out_SET'] nodes = list() for s in pointcache_sets: diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index cd4593ffcd..526eb0a2a8 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -67,7 +67,7 @@ class CollectLook(pyblish.api.InstancePlugin): hosts = ["maya"] # Ignore specifically named sets (check with endswith) - IGNORE = ["pointcache_SET", "controls_SET", "_INST"] + IGNORE = ["out_SET", "controls_SET", "_INST"] def collect(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index fb7ae0751a..a48d441797 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -7,7 +7,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Every rig must contain at least two object sets: "controls_SET" - Set of all animatable controls - "pointcache_SET" - Set of all cachable meshes + "out_SET" - Set of all cachable meshes """ @@ -21,7 +21,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): from maya import cmds - objsets = ("controls_SET", "pointcache_SET") + objsets = ("controls_SET", "out_SET") missing = list() for objset in objsets: @@ -40,7 +40,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): self.log.info("Evaluating contents of object sets..") not_meshes = list() - members = cmds.sets("pointcache_SET", query=True) or [] + members = cmds.sets("out_SET", query=True) or [] shapes = cmds.listRelatives(members, allDescendents=True, shapes=True, @@ -59,5 +59,5 @@ class ValidateRigContents(pyblish.api.InstancePlugin): % not_transforms) assert not_meshes == [], ( - "Only meshes can be part of the pointcache_SET: %s" + "Only meshes can be part of the out_SET: %s" % not_meshes) diff --git a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py index 5a7ba1fd01..0f0bcac829 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py @@ -7,7 +7,7 @@ import cbra.utils.maya.node_uuid as id_utils class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate rig pointcache_SET nodes have related ids to current context + """Validate rig out_SET nodes have related ids to current context An ID is 'related' if its built in the current Item. @@ -29,9 +29,9 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): @classmethod def get_pointcache_nodes(cls, instance): - # Get pointcache_SET + # Get out_SET sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'pointcache_SET'] + pointcache_sets = [x for x in sets if x == 'out_SET'] nodes = list() for s in pointcache_sets: From 2907a477669a93d3df88dbb491586089356cec03 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 17:56:10 +0200 Subject: [PATCH 0008/1249] Ensure rig controls_SET and out_SET have at least any members --- .../maya/publish/validate_rig_contents.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index fb7ae0751a..1b6beb8ad7 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -7,7 +7,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): Every rig must contain at least two object sets: "controls_SET" - Set of all animatable controls - "pointcache_SET" - Set of all cachable meshes + "out_SET" - Set of all cachable meshes """ @@ -21,7 +21,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): from maya import cmds - objsets = ("controls_SET", "pointcache_SET") + objsets = ("controls_SET", "out_SET") missing = list() for objset in objsets: @@ -40,7 +40,15 @@ class ValidateRigContents(pyblish.api.InstancePlugin): self.log.info("Evaluating contents of object sets..") not_meshes = list() - members = cmds.sets("pointcache_SET", query=True) or [] + + # Ensure contents in sets + members = cmds.sets("out_SET", query=True) or [] + assert members, "Must have members in rig out_SET" + + controls = cmds.sets("controls_SET", query=True) or [] + assert controls, "Must have controls in rig control_SET" + + # Validate the contents further shapes = cmds.listRelatives(members, allDescendents=True, shapes=True, @@ -59,5 +67,5 @@ class ValidateRigContents(pyblish.api.InstancePlugin): % not_transforms) assert not_meshes == [], ( - "Only meshes can be part of the pointcache_SET: %s" + "Only meshes can be part of the out_SET: %s" % not_meshes) From 41142453306ff1ab80602eed2076bd5a9763239b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Jun 2017 18:02:16 +0200 Subject: [PATCH 0009/1249] Fix creation of family after loading a rig --- colorbleed/plugins/maya/load/load_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 1ccf5184d0..2198c4dbd5 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -50,7 +50,7 @@ class RigLoader(api.Loader): maya.create( name=maya.unique_name(asset, suffix="_SET"), asset=context["asset"]["name"], - family="avalon.animation", + family="colorbleed.animation", options={"useSelection": True}, data={ "dependencies": " ".join(str(d) for d in dependencies) From f2a39d5c12d9c2719b85356f8c4d3990d17e3781 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 23 Jun 2017 18:08:24 +0200 Subject: [PATCH 0010/1249] fixed bug in collect_current_file --- colorbleed/plugins/maya/publish/collect_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_current_file.py b/colorbleed/plugins/maya/publish/collect_current_file.py index 305116fb2d..537f7afa0f 100644 --- a/colorbleed/plugins/maya/publish/collect_current_file.py +++ b/colorbleed/plugins/maya/publish/collect_current_file.py @@ -15,4 +15,4 @@ class CollectMayaCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" current_file = cmds.file(query=True, sceneName=True) - context.data['currentFile'] = os.path.normpath(current_file) + context.data['currentFile'] = current_file From e75c64114670f734cb049979d1b7cea041d0c746 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 26 Jun 2017 11:15:07 +0200 Subject: [PATCH 0011/1249] Integrated context repair class --- colorbleed/action.py | 46 +++++++++++++++++++ colorbleed/api.py | 14 +----- colorbleed/maya/lib.py | 2 +- colorbleed/plugins/maya/load/load_model.py | 1 + .../maya/publish/_validate_units_angular.py | 20 -------- .../maya/publish/_validate_units_fps.py | 28 ----------- .../maya/publish/_validate_units_linear.py | 20 -------- .../maya/publish/collect_current_file.py | 4 +- .../maya/publish/validate_maya_units.py | 26 ++++++++--- .../maya/publish/validate_rig_contents.py | 15 +++--- 10 files changed, 78 insertions(+), 98 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_angular.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_fps.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_units_linear.py diff --git a/colorbleed/action.py b/colorbleed/action.py index aad054044d..69562b1ef0 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -20,6 +20,27 @@ def get_errored_instances_from_context(context): return instances +def get_errored_plugins_from_data(context): + """Get all failed validation plugins + + Args: + context (object): + + Returns: + list of plugins which failed during validation + + """ + + plugins = list() + results = context.data.get("results", []) + for result in results: + if result["success"] == True: + continue + plugins.append(result["plugin"]) + + return plugins + + class RepairAction(pyblish.api.Action): """Repairs the action @@ -47,6 +68,31 @@ class RepairAction(pyblish.api.Action): plugin.repair(instance) +class RepairContextAction(pyblish.api.Action): + """Repairs the action + + To retrieve the invalid nodes this assumes a static `repair(instance)` + method is available on the plugin. + + """ + label = "Repair Context" + on = "failed" # This action is only available on a failed plug-in + + def process(self, context, plugin): + + if not hasattr(plugin, "repair"): + raise RuntimeError("Plug-in does not have repair method.") + + # Get the errored instances + self.log.info("Finding failed instances..") + errored_plugins = get_errored_plugins_from_data(context) + + # Apply pyblish.logic to get the instances for the plug-in + if plugin in errored_plugins: + self.log.info("Attempting fix ...") + plugin.repair() + + class SelectInvalidAction(pyblish.api.Action): """Select invalid nodes in Maya when plug-in failed. diff --git a/colorbleed/api.py b/colorbleed/api.py index 21ac9ba409..9de699fa62 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -14,20 +14,10 @@ from .plugin import ( from .action import ( SelectInvalidAction, GenerateUUIDsOnInvalidAction, - RepairAction + RepairAction, + RepairContextAction ) - -def merge(*args): - """Helper to merge OrderedDict instances""" - data = OrderedDict() - for arg in args: - for key, value in arg.items(): - data.pop(key, None) - data[key] = value - return data - - all = [ "Extractor", "ValidatePipelineOrder", diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index aa5345166c..6d4c9a272c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -241,4 +241,4 @@ def collect_animation_data(): def get_current_renderlayer(): - return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) \ No newline at end of file + return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 65dd3e2fd0..bfb0decaca 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,4 +1,5 @@ from maya import cmds + from avalon import api diff --git a/colorbleed/plugins/maya/publish/_validate_units_angular.py b/colorbleed/plugins/maya/publish/_validate_units_angular.py deleted file mode 100644 index 6ac560ee60..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_angular.py +++ /dev/null @@ -1,20 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsAngular(pyblish.api.ContextPlugin): - """Scene angular units must be in degrees""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (angular)" - families = ["colorbleed.rig", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.curves"] - - def process(self, context): - units = context.data('angularUnits') - - self.log.info('Units (angular): {0}'.format(units)) - assert units and units == 'deg', ( - "Scene angular units must be degrees") diff --git a/colorbleed/plugins/maya/publish/_validate_units_fps.py b/colorbleed/plugins/maya/publish/_validate_units_fps.py deleted file mode 100644 index b28c86804e..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_fps.py +++ /dev/null @@ -1,28 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsFps(pyblish.api.ContextPlugin): - """Validate the scene linear, angular and time units.""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (fps)" - families = ["colorbleed.rig", - "colorbleed.pointcache", - "colorbleed.curves"] - actions = [colorbleed.api.RepairAction] - optional = True - - def process(self, context): - - fps = context.data['fps'] - - self.log.info('Units (time): {0} FPS'.format(fps)) - assert fps and fps == 25.0, "Scene must be 25 FPS" - - @classmethod - def repair(cls): - """Fix the current FPS setting of the scene, set to PAL(25.0 fps) - """ - import maya.cmds as cmds - cmds.currentUnit(time="pal") diff --git a/colorbleed/plugins/maya/publish/_validate_units_linear.py b/colorbleed/plugins/maya/publish/_validate_units_linear.py deleted file mode 100644 index 9230ae7626..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_units_linear.py +++ /dev/null @@ -1,20 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateUnitsLinear(pyblish.api.ContextPlugin): - """Scene must be in linear units""" - - order = colorbleed.api.ValidateSceneOrder - label = "Units (linear)" - families = ["colorbleed.rig", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.curves"] - - def process(self, context): - units = context.data('linearUnits') - - self.log.info('Units (linear): {0}'.format(units)) - assert units and units == 'cm', ("Scene linear units must " - "be centimeters") diff --git a/colorbleed/plugins/maya/publish/collect_current_file.py b/colorbleed/plugins/maya/publish/collect_current_file.py index 305116fb2d..0b38ebcf3d 100644 --- a/colorbleed/plugins/maya/publish/collect_current_file.py +++ b/colorbleed/plugins/maya/publish/collect_current_file.py @@ -1,5 +1,3 @@ -import os - from maya import cmds import pyblish.api @@ -15,4 +13,4 @@ class CollectMayaCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" current_file = cmds.file(query=True, sceneName=True) - context.data['currentFile'] = os.path.normpath(current_file) + context.data['currentFile'] = current_file diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 538dec949c..383562cef2 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -1,3 +1,5 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api @@ -11,7 +13,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): "colorbleed.model", "colorbleed.pointcache", "colorbleed.curves"] - actions = [colorbleed.api.RepairAction] + actions = [colorbleed.api.RepairContextAction] def process(self, context): @@ -29,11 +31,23 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): assert angularunits and angularunits == 'deg', ("Scene angular units " "must be degrees") - - assert fps and fps == 25.0, "Scene must be 25 FP" + assert fps and fps == 25.0, "Scene must be 25 FPS" @classmethod def repair(cls): - """Fix the current FPS setting of the scene, set to PAL(25.0 fps) - """ - raise NotImplementedError() + """Fix the current FPS setting of the scene, set to PAL(25.0 fps)""" + + cls.log.info("Setting angular unit to 'degrees'") + cmds.currentUnit(angle="degree") + current_angle = cmds.currentUnit(query=True, angle=True) + cls.log.debug(current_angle) + + cls.log.info("Setting linear unit to 'centimeter'") + cmds.currentUnit(linear="centimeter") + current_linear = cmds.currentUnit(query=True, linear=True) + cls.log.debug(current_linear) + + cls.log.info("Setting time unit to 'PAL'") + cmds.currentUnit(time="pal") + current_time = cmds.currentUnit(query=True, time=True) + cls.log.debug(current_time) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 1b6beb8ad7..6ebf807762 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -1,3 +1,5 @@ +from maya import cmds + import pyblish.api import colorbleed.api @@ -19,17 +21,14 @@ class ValidateRigContents(pyblish.api.InstancePlugin): def process(self, instance): - from maya import cmds - - objsets = ("controls_SET", "out_SET") + objectsets = ("controls_SET", "out_SET") missing = list() - for objset in objsets: - if objset not in instance: - missing.append(objset) + for objectset in objectsets: + if objectset not in instance: + missing.append(objectset) - assert not missing, ("%s is missing %s" - % (instance, missing)) + assert not missing, ("%s is missing %s" % (instance, missing)) # Ensure there are at least some transforms or dag nodes # in the rig instance From e0b1bdb78099447246f22862f2c27b2234359111 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 26 Jun 2017 12:25:37 +0200 Subject: [PATCH 0012/1249] Added repair function for joints hidden --- colorbleed/action.py | 1 - .../plugins/maya/publish/validate_joints_hidden.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 69562b1ef0..fef737195d 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -63,7 +63,6 @@ class RepairAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - for instance in instances: plugin.repair(instance) diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/colorbleed/plugins/maya/publish/validate_joints_hidden.py index 77bfe9f5de..8378dbf8bc 100644 --- a/colorbleed/plugins/maya/publish/validate_joints_hidden.py +++ b/colorbleed/plugins/maya/publish/validate_joints_hidden.py @@ -81,7 +81,8 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): category = 'rig' version = (0, 1, 0) label = "Joints Hidden" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] @staticmethod def get_invalid(instance): @@ -93,5 +94,9 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Visible joints found: " - "{0}".format(invalid)) + raise ValueError("Visible joints found: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + import maya.mel as mel + mel.eval("HideJoints") From 4ae6eb7429f7f2880edc7aae73c1c1a6b7f10188 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 26 Jun 2017 17:43:38 +0200 Subject: [PATCH 0013/1249] Remove HistoryLoader since it's not implemented anyway --- colorbleed/plugins/maya/load/load_animation.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index cbb1e6bff5..6660468484 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -1,4 +1,3 @@ -import pprint from avalon import api @@ -138,13 +137,3 @@ class CurvesLoader(api.Loader): family=family, options={"useSelection": True}, data={"dependencies": " ".join(str(d) for d in dependencies)}) - - -class HistoryLoader(api.Loader): - """Specific loader of Curves for the avalon.animation family""" - - families = ["colorbleed.animation"] - representations = ["history"] - - def process(self, name, namespace, context): - raise NotImplementedError("Can't load history yet.") From 8c924ffcb1aeb4366a678fec5baa8be6967f2cea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Jun 2017 11:52:46 +0200 Subject: [PATCH 0014/1249] Add label, order, icon and color to Loaders. Also implement draft "gpuCache" loader for "colorbleed.model" --- .../plugins/maya/load/load_animation.py | 9 ++++ .../plugins/maya/load/load_historylook.py | 5 ++ colorbleed/plugins/maya/load/load_look.py | 5 ++ colorbleed/plugins/maya/load/load_model.py | 54 +++++++++++++++++-- colorbleed/plugins/maya/load/load_rig.py | 5 ++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index 6660468484..cbbbc1ff4b 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -7,6 +7,11 @@ class AbcLoader(api.Loader): families = ["colorbleed.animation", "colorbleed.camera"] representations = ["abc"] + label = "Reference animation" + order = -10 + icon = "code-fork" + color = "orange" + def process(self, name, namespace, context): from maya import cmds @@ -31,6 +36,10 @@ class CurvesLoader(api.Loader): families = ["colorbleed.animation"] representations = ["curves"] + label = "Import curves" + order = -1 + icon = "question" + def process(self, name, namespace, context): from maya import cmds from avalon import maya diff --git a/colorbleed/plugins/maya/load/load_historylook.py b/colorbleed/plugins/maya/load/load_historylook.py index e4c49685cb..7ff0557de6 100644 --- a/colorbleed/plugins/maya/load/load_historylook.py +++ b/colorbleed/plugins/maya/load/load_historylook.py @@ -8,6 +8,11 @@ class HistoryLookLoader(api.Loader): families = ["colorbleed.historyLookdev"] representations = ["ma"] + label = "Reference look history" + order = -10 + icon = "code-fork" + color = "orange" + def process(self, name, namespace, context): from avalon import maya with maya.maintained_selection(): diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index eae0a5b5a2..442b780978 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -11,6 +11,11 @@ class LookLoader(api.Loader): families = ["colorbleed.lookdev"] representations = ["ma"] + label = "Reference look" + order = -10 + icon = "code-fork" + color = "orange" + def process(self, name, namespace, context): from avalon import maya try: diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 65dd3e2fd0..b608bc2a80 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -3,15 +3,16 @@ from avalon import api class ModelLoader(api.Loader): - """Load models - - Stores the imported asset in a container named after the asset. - - """ + """Load the model""" families = ["colorbleed.model"] representations = ["ma"] + label = "Reference model" + order = -10 + icon = "code-fork" + color = "orange" + def process(self, name, namespace, context): from avalon import maya with maya.maintained_selection(): @@ -29,3 +30,46 @@ class ModelLoader(api.Loader): cmds.sets(meshes, forceElement="initialShadingGroup") self[:] = nodes + + +class ModelGPUCacheLoader(api.Loader): + """Import a GPU Cache""" + + families = ["colorbleed.model"] + representations = ["abc"] + + label = "Import GPU Cache" + order = -1 + icon = "download" + + def process(self, name, namespace, context): + + from maya import cmds + + # todo: This will likely not be entirely safe with "containerize" + # also this cannot work in the manager because it only works + # on references at the moment! + # especially in cases of duplicating the gpu cache node this will + # mess up the "containered" workflow in the avalon core for maya + print("WARNING: Importing gpuCaches isn't fully tested yet") + + path = self.fname + + cmds.loadPlugin("gpuCache", quiet=True) + + # Create transform with shape + transform = cmds.createNode("transform", + name=name) + cache = cmds.createNode("gpuCache", + parent=transform, + name="{0}Shape".format(name)) + + # Set the cache filepath + cmds.setAttr(cache + '.cacheFileName', path, type="string") + cmds.setAttr(cache + '.cacheGeomPath', "|", type="string") # root + + # Select the transform + cmds.select(transform, r=1) + + # Store the created nodes + self[:] = [transform, cache] diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 2198c4dbd5..ab9863ee28 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -12,6 +12,11 @@ class RigLoader(api.Loader): families = ["colorbleed.rig"] representations = ["ma"] + label = "Reference rig" + order = -10 + icon = "code-fork" + color = "orange" + def process(self, name, namespace, context): nodes = cmds.file(self.fname, namespace=namespace, From deb237113876cae51879e8f15222049eabcf06e7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:46:55 +0200 Subject: [PATCH 0015/1249] changed error catching, results in list of all errors in log --- .../maya/publish/validate_rig_contents.py | 94 ++++++++++++++----- .../maya/publish/validate_rig_controllers.py | 80 ++++++++++++++++ 2 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_rig_controllers.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 6ebf807762..4445a48fca 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -16,17 +16,16 @@ class ValidateRigContents(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder label = "Rig Contents" hosts = ["maya"] - families = ["colorbleed.rig", "colorbleed.rigcontrols", - "colorbleed.rigpointcache"] + families = ["colorbleed.rig"] + + accepted_output = ["mesh", "transform"] + accepted_controllers = ["transform"] + ignore_nodes = [] def process(self, instance): objectsets = ("controls_SET", "out_SET") - - missing = list() - for objectset in objectsets: - if objectset not in instance: - missing.append(objectset) + missing = [obj for obj in objectsets if obj not in instance] assert not missing, ("%s is missing %s" % (instance, missing)) @@ -38,33 +37,78 @@ class ValidateRigContents(pyblish.api.InstancePlugin): "(Empty instance?)") self.log.info("Evaluating contents of object sets..") + not_meshes = list() + not_transforms = list() + invalid_hierachy = list() - # Ensure contents in sets - members = cmds.sets("out_SET", query=True) or [] - assert members, "Must have members in rig out_SET" + error = False - controls = cmds.sets("controls_SET", query=True) or [] - assert controls, "Must have controls in rig control_SET" + # Ensure contents in sets and retrieve long path for all objects + out_members = cmds.sets("out_SET", query=True) or [] + assert out_members, "Must have members in rig out_SET" + out_members = cmds.ls(out_members, long=True) + + controls_members = cmds.sets("controls_SET", query=True) or [] + controls_members = cmds.ls(controls_members, long=True) + assert controls_members, "Must have controls in rig control_SET" + + root_node = cmds.ls(set_members, assemblies=True) + root_content = cmds.listRelatives(root_node, + allDescendents=True, + fullPath=True) # Validate the contents further - shapes = cmds.listRelatives(members, + shapes = cmds.listRelatives(out_members, allDescendents=True, shapes=True, fullPath=True) or [] - for shape in shapes: - if cmds.nodeType(shape) != "mesh": + + # The user can add the shape node to the out_set, this will result + # in none when querying allDescendents + out_shapes = out_members + shapes + + # geometry + for shape in out_shapes: + nodetype = cmds.nodeType(shape) + if nodetype in self.ignore_nodes: + continue + + if nodetype not in self.accepted_output: not_meshes.append(shape) - not_transforms = list() - for node in cmds.sets("controls_SET", query=True) or []: - if cmds.nodeType(node) != "transform": - not_meshes.append(node) + # check if controllers are in the root group + if shape not in root_content: + invalid_hierachy.append(shape) - assert not_transforms == [], ( - "Only transforms can be part of the controls_SET: %s" - % not_transforms) + # curves + for node in controls_members: + nodetype = cmds.nodeType(node) + if nodetype in self.ignore_nodes: + continue - assert not_meshes == [], ( - "Only meshes can be part of the out_SET: %s" - % not_meshes) + if nodetype not in self.accepted_controllers: + not_transforms.append(node) + + # check if controllers are in the root group + if node not in root_content: + invalid_hierachy.append(node) + + if invalid_hierachy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierachy) + error = True + + if not_transforms: + self.log.error("Only transforms can be part of the controls_SET." + "\n%s" % not_transforms) + error = True + + if not_meshes: + self.log.error("Only meshes can be part of the out_SET\n%s" + % not_meshes) + error = True + + if error: + raise RuntimeError("Invalid rig content. See log for details.") diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py new file mode 100644 index 0000000000..dd9f77006c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -0,0 +1,80 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRigControllers(pyblish.api.InstancePlugin): + """Check if the controllers have the transformation attributes set to + default values, locked vibisility attributes and are not keyed + """ + order = colorbleed.api.ValidateContentsOrder + 0.05 + label = "Rig Controllers" + hosts = ["maya"] + families = ["colorbleed.rig"] + + def process(self, instance): + + error = False + is_keyed = list() + not_locked = list() + is_offset = list() + + controls = cmds.sets("controls_SET", query=True) + assert controls, "Must have controls in rig control_SET" + + for control in controls: + valid_keyed = self.validate_keyed_state(control) + if not valid_keyed: + is_keyed.append(control) + + # check if visibility is locked + attribute = "{}.visibility".format(control) + locked = cmds.getAttr(attribute, lock=True) + if not locked: + not_locked.append(control) + + valid_transforms = self.validate_transforms(control) + if not valid_transforms: + is_offset.append(control) + + if is_keyed: + self.log.error("No controls can be keyes. Failed :\n" + "%s" % is_keyed) + + if is_offset: + self.log.error("All controls default transformation values. " + "Failed :\n%s" % is_offset) + + if not_locked: + self.log.error("All controls must have visibility " + "attribute locked. Failed :\n" + "%s" % not_locked) + + if error: + raise RuntimeError("Invalid rig controllers. See log for details.") + + def validate_transforms(self, control): + tolerance = 1e-30 + identity = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + + matrix = cmds.xform(control, query=True, matrix=True, objectSpace=True) + if not all(abs(x - y) < tolerance for x, y in zip(identity, matrix)): + return False + return True + + def validate_keyed_state(self, control): + """Check if the control has an animation curve attached + Args: + control: + + Returns: + + """ + animation_curves = cmds.keyframe(control, query=True, name=True) + if animation_curves: + return False + return True From c64040413db7bf18714a7dfd2a022e09a046cf81 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:49:01 +0200 Subject: [PATCH 0016/1249] removed redundant modules --- .../plugins/maya/publish/collect_metadata.py | 39 ------- .../maya/publish/validate_latest_versions.py | 108 ------------------ 2 files changed, 147 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/collect_metadata.py delete mode 100644 colorbleed/plugins/maya/publish/validate_latest_versions.py diff --git a/colorbleed/plugins/maya/publish/collect_metadata.py b/colorbleed/plugins/maya/publish/collect_metadata.py deleted file mode 100644 index b5924c25de..0000000000 --- a/colorbleed/plugins/maya/publish/collect_metadata.py +++ /dev/null @@ -1,39 +0,0 @@ -import pyblish.api -import copy - - -class CollectMetadata(pyblish.api.ContextPlugin): - """Transfer context metadata to the instance. - - This applies a copy of the `context.data['metadata']` to the - `instance.data['metadata']` for the following metadata: - - Provides: - { - "topic": "topic", - "author": "user", - "date": "date", - "filename": "currentFile" - } - - - """ - order = pyblish.api.CollectorOrder + 0.2 - label = "Metadata" - - mapping = {"topic": "topic", - "author": "user", - "date": "date", - "filename": "currentFile"} - - def process(self, context): - - metadata = {} - for key, source in self.mapping.iteritems(): - if source in context.data: - metadata[key] = context.data.get(source) - - for instance in context: - instance.data["metadata"] = copy.deepcopy(metadata) - - self.log.info("Collected {0}".format(metadata)) diff --git a/colorbleed/plugins/maya/publish/validate_latest_versions.py b/colorbleed/plugins/maya/publish/validate_latest_versions.py deleted file mode 100644 index 0b7d2dd826..0000000000 --- a/colorbleed/plugins/maya/publish/validate_latest_versions.py +++ /dev/null @@ -1,108 +0,0 @@ -import os - -from maya import cmds - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cb.utils.python.decorators import memorize - - -def is_latest_version(path): - """Return whether path is the latest version. - - Args: - path (str): Full path to published file. - - Returns: - bool: Whether the path belongs to the latest version. - - """ - - ctx = cbra.lib.parse_context(path) - versions = cbra.lib.list_versions(ctx) - highest = cbra.lib.find_highest_version(versions) - - if ctx.get('version', None) != highest: - return False - else: - return True - - -@memorize -def is_latest_version_cached(path): - """Memorized cached wrapper to `is_latest_version`""" - return is_latest_version(path) - - -class ValidateLatestVersions(pyblish.api.InstancePlugin): - """Validates content included is using latest published versions. - - If published contents are out of date they can be easily updated to the - latest version using the scripts > pyblish > utilities > update_xxx for - the corresponding node type. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.layout'] - label = "Latest Versions" - actions = [colorbleed.api.SelectInvalidAction] - optional = True - - # (node_type, attribute) that are non-referenced to check paths for - LOCAL_CHECKS = { - "gpuCache": "cacheFileName", - "VRayMesh": "fileName2" - } - - @classmethod - def get_invalid(cls, instance): - - all_nodes = instance[:] - invalid = list() - - # check non-referenced nodes - for node_type, attr in cls.LOCAL_CHECKS.iteritems(): - - nodes = cmds.ls(all_nodes, type=node_type, long=True) - referenced = cmds.ls(nodes, referencedNodes=True, long=True) - non_referenced = [n for n in nodes if n not in referenced] - - for node in non_referenced: - - path = cmds.getAttr("{0}.{1}".format(node, attr)) - path = os.path.normpath(path) - if not is_latest_version_cached(path): - invalid.append(node) - - # reference nodes related to this isntance - referenced = cmds.ls(all_nodes, long=True, referencedNodes=True) - referenced_nodes = set(cmds.referenceQuery(reference, referenceNode=True) - for reference in referenced) - - for reference in referenced_nodes: - path = cmds.referenceQuery(reference, - filename=True, - withoutCopyNumber=True) - path = os.path.normpath(path) - if not is_latest_version_cached(path): - invalid.append(reference) - - return invalid - - def process(self, instance): - - # Clear cache only once per publish. So we store a value on - # the context on the first instance so we clear only once. - name = self.__class__.__name__ - key = "_plugin_{0}_processed".format(name) - if not instance.context.data.get(key, False): - is_latest_version_cached.cache.clear() - instance.context.data[key] = True - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Used Items are not updated to latest versions:" - "{0}".format(invalid)) \ No newline at end of file From 98dc353dcb96d600949b0f0a98af92c7b83b9207 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:53:28 +0200 Subject: [PATCH 0017/1249] refactoring complexity --- .../plugins/maya/publish/collect_look.py | 79 +++++++++++++------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 526eb0a2a8..81caa78c3a 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -69,6 +69,14 @@ class CollectLook(pyblish.api.InstancePlugin): # Ignore specifically named sets (check with endswith) IGNORE = ["out_SET", "controls_SET", "_INST"] + def process(self, instance): + """Collect the Look in the instance with the correct layer settings""" + + layer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(layer): + self.log.info("Checking out layer: {0}".format(layer)) + self.collect(instance) + def collect(self, instance): # Whether to log information verbosely @@ -123,7 +131,7 @@ class CollectLook(pyblish.api.InstancePlugin): node_sets = [s for s in node_sets if s not in view_sets] if verbose: - self.log.debug("After filtering view sets {0}".format(node_sets)) + self.log.debug("After filtering view sets %s" % node_sets) self.log.info("Found sets {0} for {1}".format(node_sets, node)) @@ -140,30 +148,18 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Gathering set relations..") for objset in sets: + self.log.debug("From %s.." % objset) content = cmds.sets(objset, query=True) + objset_members = sets[objset]["members"] for member in cmds.ls(content, long=True, absoluteName=True): - - node, components = (member.rsplit(".", 1) + [None])[:2] - - # Only include valid members of the instance - if node not in instance_lookup: - if verbose: - self.log.info("Skipping member %s" % member) + member_data = self.collect_member_data(member, + objset_members, + instance_lookup, + verbose) + if not member_data: continue - if member in [m["name"] for m in sets[objset]["members"]]: - continue - - if verbose: - self.log.debug("Such as %s.." % member) - - member_data = {"name": node, "uuid": id_utils.get_id(node)} - - # Include components information when components are assigned - if components: - member_data["components"] = components - sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end @@ -212,10 +208,41 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Collected look for %s" % instance) - def process(self, instance): - """Collect the Look in the instance with the correct layer settings""" + def collect_member_data(self, member, objset_members, instance_members, + verbose=False): + """Get all information of the node + Args: + member (str): the name of the node to check + objset_members (list): the objectSet members + instance_members (set): the collected instance members + verbose (bool): get debug information + + Returns: + dict + + """ + + node, components = (member.rsplit(".", 1) + [None])[:2] + + # Only include valid members of the instance + if node not in instance_members: + if verbose: + self.log.info("Skipping member %s" % member) + return + + if member in [m["name"] for m in objset_members]: + return + + if verbose: + self.log.debug("Such as %s.." % member) + + member_data = {"name": node, "uuid": id_utils.get_id(node)} + + # Include components information when components are assigned + if components: + member_data["components"] = components + + return member_data + + - layer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(layer): - self.log.info("Checking out layer: {0}".format(layer)) - self.collect(instance) From 70bab49ce3b7e965a206624b4477d57bcdb4f609 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:54:35 +0200 Subject: [PATCH 0018/1249] refactored error collecting and complexity --- .../maya/publish/collect_look_textures.py | 122 ++++++++++-------- .../maya/publish/validate_look_contents.py | 16 ++- 2 files changed, 83 insertions(+), 55 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index 772deaeba1..586e264307 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -31,26 +31,11 @@ class SelectTextureNodesAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(instances, plugin) - def is_texture_resource(resource): - """Return whether the resource is a texture""" - - tags = resource.get("tags", []) - if not TAGS_LOOKUP.issubset(tags): - return False - - if resource.get("subfolder", None) != "textures": - return False - - if "node" not in resource: - return False - - return True - # Get the texture nodes from the instances nodes = [] for instance in instances: for resource in instance.data.get("resources", []): - if is_texture_resource(resource): + if self.is_texture_resource(resource): node = resource['node'] nodes.append(node) @@ -64,6 +49,21 @@ class SelectTextureNodesAction(pyblish.api.Action): self.log.info("No texture nodes found.") cmds.select(deselect=True) + def is_texture_resource(self, resource): + """Return whether the resource is a texture""" + + tags = resource.get("tags", []) + if not TAGS_LOOKUP.issubset(tags): + return False + + if resource.get("subfolder", None) != "textures": + return False + + if "node" not in resource: + return False + + return True + class CollectLookTextures(pyblish.api.InstancePlugin): """Collect look textures @@ -93,43 +93,61 @@ class CollectLookTextures(pyblish.api.InstancePlugin): resources = instance.data.get("resources", []) for node in files: - - attribute = "%s.fileTextureName" % node - source = cmds.getAttr(attribute) - - # Get the computed file path (e.g. the one with the pattern - # in it) So we can reassign it this computed file path whenever - # we need to. - computed_attribute = "%s.computedFileTextureNamePattern" % node - computed_source = cmds.getAttr(computed_attribute) - if source != computed_source: - if verbose: - self.log.debug("File node computed pattern differs from " - "original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) - - # We replace backslashes with forward slashes because V-Ray - # can't handle the UDIM files with the backslashes in the - # paths as the computed patterns - source = computed_source.replace("\\", "/") - - files = shader.get_file_node_files(node) - if not files: - self.log.error("File node does not have a texture set: " - "{0}".format(node)) - - # Define the resource - resource = {"tags": TAGS[:], - "node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files, # required for resources - "subfolder": "textures" # optional for resources - } - + resource = self.collect_resources(node, verbose) + if not resource: + continue resources.append(resource) # Store resources instance.data['resources'] = resources + + def collect_resources(self, node, verbose=False): + """Collect the link to the file(s) used (resource) + Args: + node (str): name of the node + verbose (bool): enable debug information + + Returns: + dict + """ + + attribute = "{}.fileTextureName".format(node) + source = cmds.getAttr(attribute) + + # Get the computed file path (e.g. the one with the pattern + # in it) So we can reassign it this computed file path whenever + # we need to. + + computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + if verbose: + self.log.debug("File node computed pattern differs from " + "original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) + + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = computed_source.replace("\\", "/") + + files = shader.get_file_node_files(node) + if not files: + self.log.error("File node does not have a texture set: " + "{0}".format(node)) + return + + # Define the resource + resource = {"tags": TAGS[:], + "node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files, # required for resources + "subfolder": "textures" # optional for resources + } + + return resource + + diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 6c91f83206..f117dad794 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -18,10 +18,20 @@ class ValidateLookContents(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" + error = False + + attributes = ["lookSets", + "lookSetRelations", + "lookAttributes"] + if not instance[:]: raise RuntimeError("Instance is empty") # Required look data - assert "lookSets" in instance.data - assert "lookSetRelations" in instance.data - assert "lookAttributes" in instance.data + for attr in attributes: + if attr not in instance.data: + self.log.error("No %s found in data" % attr) + error = True + + if error: + raise RuntimeError("Invalid look content. See log for details.") From 21aeb2edd04d72d470a7fcf7aac532f7c1d3ecdc Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:55:10 +0200 Subject: [PATCH 0019/1249] cosmetics --- colorbleed/plugins/maya/create/colorbleed_rig.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index b29d5ae7e5..3fa718d756 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -1,6 +1,7 @@ -import avalon.maya from maya import cmds +import avalon.maya + class CreateRig(avalon.maya.Creator): """Skeleton and controls for manipulation of the geometry""" @@ -12,6 +13,8 @@ class CreateRig(avalon.maya.Creator): def process(self): instance = super(CreateRig, self).process() + self.log.info("Creating Rig instance set up ...") + controls = cmds.sets(name="controls_SET", empty=True) pointcache = cmds.sets(name="out_SET", empty=True) cmds.sets([controls, pointcache], forceElement=instance) From ef1836c18ba8f7b7fd22d33fab7dc6c8288dbd5d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:57:21 +0200 Subject: [PATCH 0020/1249] cosmetics --- colorbleed/plugins/maya/publish/collect_look.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 81caa78c3a..e72df4bd24 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -243,6 +243,3 @@ class CollectLook(pyblish.api.InstancePlugin): member_data["components"] = components return member_data - - - From 7c3fb0d0c5277df87be876de98a85353db2602e7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 13:57:50 +0200 Subject: [PATCH 0021/1249] cosmetics --- colorbleed/plugins/maya/load/load_model.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index bfb0decaca..bdcdd56ca2 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,6 +1,7 @@ from maya import cmds from avalon import api +from avalon import maya class ModelLoader(api.Loader): @@ -14,7 +15,7 @@ class ModelLoader(api.Loader): representations = ["ma"] def process(self, name, namespace, context): - from avalon import maya + with maya.maintained_selection(): nodes = cmds.file( self.fname, @@ -22,7 +23,7 @@ class ModelLoader(api.Loader): reference=True, returnNewNodes=True, groupReference=True, - groupName=namespace + ":" + name + groupName="{}:{}".format(namespace, name) ) # Assign default shader to meshes From 13e56238bc33e9457542c15535800bea37ba7165 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 14:31:15 +0200 Subject: [PATCH 0022/1249] refactored complexitt --- .../plugins/maya/publish/collect_look.py | 174 +++++++++--------- 1 file changed, 91 insertions(+), 83 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index e72df4bd24..a51eac58a7 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -83,63 +83,11 @@ class CollectLook(pyblish.api.InstancePlugin): verbose = instance.data.get("verbose", False) self.log.info("Looking for look associations " - "for {0}..".format(instance.data['label'])) - - # Get view sets (so we can ignore those sets later) - model_panels = cmds.getPanel(type="modelPanel") - view_sets = set() - - for panel in model_panels: - view_set = cmds.modelEditor(panel, query=True, viewObjects=True) - if view_set: - view_sets.add(view_set) + "for %s" % instance.data['label']) # Discover related object sets self.log.info("Gathering sets..") - sets = dict() - for node in instance: - - node_sets = cmds.listSets(object=node, extendToShape=False) or [] - if verbose: - self.log.info("Found raw sets " - "{0} for {1}".format(node_sets, node)) - - if not node_sets: - continue - - # Exclude deformer sets - deformer_sets = cmds.listSets(object=node, - extendToShape=False, - type=2) or [] - deformer_sets = set(deformer_sets) # optimize lookup - node_sets = [s for s in node_sets if s not in deformer_sets] - - if verbose: - self.log.debug("After filtering deformer sets " - "{0}".format(node_sets)) - - # Ignore specifically named sets - node_sets = [s for s in node_sets if - not any(s.endswith(x) for x in self.IGNORE)] - - if verbose: - self.log.debug("After filtering ignored sets " - "{0}".format(node_sets)) - - # Ignore viewport filter view sets (from isolate select and - # viewports) - node_sets = [s for s in node_sets if s not in view_sets] - - if verbose: - self.log.debug("After filtering view sets %s" % node_sets) - - self.log.info("Found sets {0} for {1}".format(node_sets, node)) - - for objset in node_sets: - if objset not in sets: - sets[objset] = {"name": objset, - "uuid": id_utils.get_id(objset), - "members": list()} + self.gather_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, @@ -147,6 +95,7 @@ class CollectLook(pyblish.api.InstancePlugin): absoluteName=True)]) self.log.info("Gathering set relations..") + sets = self.gather_sets(instance) for objset in sets: self.log.debug("From %s.." % objset) @@ -163,40 +112,15 @@ class CollectLook(pyblish.api.InstancePlugin): sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end - for objset, data in sets.items(): - if not data['members']: - self.log.debug("Removing redundant set " - "information: {0}".format(objset)) - sets.pop(objset) - + sets = self.clean_sets(sets) # Member attributes (shapes + transforms) self.log.info("Gathering attribute changes to instance members..") - attrs = [] - for node in instance: - # Collect changes to "custom" attributes - node_attrs = get_look_attrs(node) - - # Only include if there are any properties we care about - if not node_attrs: - continue - - attributes = {} - for attr in node_attrs: - attribute = "{}.{}".format(node, attr) - attributes[attr] = cmds.getAttr(attribute) - - # attributes = dict((attr, pm.getAttr("{}.{}".format(node, attr)) - # for attr in node_attrs)) - data = {"name": node, - "uuid": id_utils.get_id(node), - "attributes": attributes} - - attrs.append(data) + attributes = self.collect_attributes_changes(instance) # Store data on the instance - instance.data["lookAttributes"] = attrs + instance.data["lookAttributes"] = attributes instance.data["lookSetRelations"] = sets.values() instance.data["lookSets"] = cmds.ls(sets.keys(), absoluteName=True, @@ -204,10 +128,69 @@ class CollectLook(pyblish.api.InstancePlugin): # Log a warning when no relevant sets were retrieved for the look. if not instance.data['lookSets']: - self.log.warning("No sets found for the nodes in the instance: {0}".format(instance[:])) + self.log.warning("No sets found for the nodes in the instance: " + "%s" % instance[:]) self.log.info("Collected look for %s" % instance) + def gather_sets(self, instance): + + # Get view sets (so we can ignore those sets later) + sets = dict() + view_sets = set() + model_panels = cmds.getPanel(type="modelPanel") + for panel in model_panels: + view_set = cmds.modelEditor(panel, query=True, viewObjects=True) + if view_set: + view_sets.add(view_set) + + for node in instance: + node_sets = self.filter_sets(node, view_sets) + if not node_sets: + continue + + for objset in node_sets: + if objset in sets: + continue + sets[objset] = {"name": objset, + "uuid": id_utils.get_id(objset), + "members": list()} + return sets + + def filter_sets(self, node, view_sets): + + node_sets = cmds.listSets(object=node, extendToShape=False) or [] + if not node_sets: + return + + # Exclude deformer sets + deformer_sets = cmds.listSets(object=node, + extendToShape=False, + type=2) or [] + deformer_sets = set(deformer_sets) # optimize lookup + sets = [s for s in node_sets if s not in deformer_sets] + + # Ignore specifically named sets + sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] + + # Ignore viewport filter view sets (from isolate select and + # viewports) + sets = [s for s in sets if s not in view_sets] + + self.log.info("Found sets {0} for {1}".format(node_sets, node)) + + return sets + + def clean_sets(self, sets): + + for objset, data in sets.items(): + if not data['members']: + self.log.debug("Removing redundant set " + "information: %s" % objset) + sets.pop(objset) + + return sets + def collect_member_data(self, member, objset_members, instance_members, verbose=False): """Get all information of the node @@ -243,3 +226,28 @@ class CollectLook(pyblish.api.InstancePlugin): member_data["components"] = components return member_data + + def collect_attributes_changes(self, instance): + + attributes = [] + for node in instance: + + # Collect changes to "custom" attributes + node_attrs = get_look_attrs(node) + + # Only include if there are any properties we care about + if not node_attrs: + continue + + node_attributes = {} + for attr in node_attrs: + attribute = "{}.{}".format(node, attr) + node_attributes[attr] = cmds.getAttr(attribute) + + data = {"name": node, + "uuid": id_utils.get_id(node), + "attributes": node_attributes} + + attributes.append(data) + + return attributes From 5e6b40c1708f2acfb2bf1678950d1450b8184776 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 14:31:41 +0200 Subject: [PATCH 0023/1249] cosmetics --- colorbleed/plugins/maya/publish/collect_look_textures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index 586e264307..5cac73cb20 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -84,7 +84,8 @@ class CollectLookTextures(pyblish.api.InstancePlugin): # Get textures from sets sets = instance.data["lookSets"] if not sets: - raise RuntimeError("No look sets found for the nodes in the instance. {0}".format(sets)) + raise RuntimeError("No look sets found for the nodes in the " + "instance. %s" % sets) # Get the file nodes history = cmds.listHistory(sets) or [] From 923d15097fad291d94635a36dff0b582fc7f87ec Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 15:53:23 +0200 Subject: [PATCH 0024/1249] refactored complexity and cosmetics --- .../maya/publish/validate_rig_contents.py | 154 ++++++++++++------ 1 file changed, 105 insertions(+), 49 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 4445a48fca..5744806005 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -22,93 +22,149 @@ class ValidateRigContents(pyblish.api.InstancePlugin): accepted_controllers = ["transform"] ignore_nodes = [] + invalid_hierarchy = [] + invalid_controls = [] + invalid_geometry = [] + def process(self, instance): + error = False + objectsets = ("controls_SET", "out_SET") missing = [obj for obj in objectsets if obj not in instance] - assert not missing, ("%s is missing %s" % (instance, missing)) # Ensure there are at least some transforms or dag nodes # in the rig instance + set_members = self.check_set_members(instance) + + self.log.info("Evaluating contents of object sets..") + + # Ensure contents in sets and retrieve long path for all objects + output_content = cmds.sets("out_SET", query=True) or [] + assert output_content, "Must have members in rig out_SET" + + controls_content = cmds.set("controls_SET", query=True) or [] + assert controls_content, "Must have members in rig controls_SET" + + root_node = cmds.ls(set_members, assemblies=True) + hierarchy = cmds.listRelatives(root_node, allDescendents=True, + fullPath=True) + + self.invalid_geometry = self.validate_geometry(output_content, + hierarchy) + self.invalid_controls = self.validate_controls(controls_content, + hierarchy) + + if self.invalid_hierachy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % self.invalid_hierachy) + error = True + + if self.not_transforms: + self.log.error("Only transforms can be part of the controls_SET." + "\n%s" % self.not_transforms) + error = True + + if self.invalid_geometry: + self.log.error("Only meshes can be part of the out_SET\n%s" + % self.invalid_geometry) + error = True + + if error: + raise RuntimeError("Invalid rig content. See log for details.") + + def check_set_members(self, instance): + """Check if the instance has any dagNodes + Args: + instance: the instance which needs to be published + Returns: + set_members (list): all dagNodes from instance + """ + set_members = instance.data['setMembers'] if not cmds.ls(set_members, type="dagNode", long=True): raise RuntimeError("No dag nodes in the pointcache instance. " "(Empty instance?)") + return set_members - self.log.info("Evaluating contents of object sets..") + def validate_hierarchy(self, hierarchy, nodes): + """Collect all nodes which are NOT within the hierarchy + Args: + hierarchy (list): nodes within the root node + nodes (list): nodes to check - not_meshes = list() - not_transforms = list() - invalid_hierachy = list() + Returns: + errors (list): list of nodes + """ + errors = [] + for node in nodes: + if node not in hierarchy: + errors.append(node) + return errors - error = False + def validate_geometry(self, set_members, hierarchy): + """Check if the out set passes the validations - # Ensure contents in sets and retrieve long path for all objects - out_members = cmds.sets("out_SET", query=True) or [] - assert out_members, "Must have members in rig out_SET" - out_members = cmds.ls(out_members, long=True) + Checks if all its set members are within the hierarchy of the root + Checks if the node types of the set members valid - controls_members = cmds.sets("controls_SET", query=True) or [] - controls_members = cmds.ls(controls_members, long=True) - assert controls_members, "Must have controls in rig control_SET" + Args: + set_members: list of nodes of the controls_set + hierarchy: list of nodes which reside under the root node - root_node = cmds.ls(set_members, assemblies=True) - root_content = cmds.listRelatives(root_node, - allDescendents=True, - fullPath=True) + Returns: + errors (list) + """ + errors = [] # Validate the contents further - shapes = cmds.listRelatives(out_members, + shapes = cmds.listRelatives(set_members, allDescendents=True, shapes=True, fullPath=True) or [] # The user can add the shape node to the out_set, this will result # in none when querying allDescendents - out_shapes = out_members + shapes + all_shapes = set_members + shapes # geometry - for shape in out_shapes: + invalid_shapes = self.validate_hierarchy(hierarchy, all_shapes) + self.invalid_hierachy.extend(invalid_shapes) + for shape in all_shapes: nodetype = cmds.nodeType(shape) if nodetype in self.ignore_nodes: continue if nodetype not in self.accepted_output: - not_meshes.append(shape) + errors.append(shape) - # check if controllers are in the root group - if shape not in root_content: - invalid_hierachy.append(shape) + return errors - # curves - for node in controls_members: + def validate_controls(self, set_members, hierarchy): + """Check if the controller set passes the validations + + Checks if all its set members are within the hierarchy of the root + Checks if the node types of the set members valid + + Args: + set_members: list of nodes of the controls_set + hierarchy: list of nodes which reside under the root node + + Returns: + errors (list) + """ + + errors = [] + invalid_controllers = self.validate_hierarchy(hierarchy, set_members) + self.invalid_hierachy.extend(invalid_controllers) + for node in set_members: nodetype = cmds.nodeType(node) if nodetype in self.ignore_nodes: continue if nodetype not in self.accepted_controllers: - not_transforms.append(node) + errors.append(node) - # check if controllers are in the root group - if node not in root_content: - invalid_hierachy.append(node) - - if invalid_hierachy: - self.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % invalid_hierachy) - error = True - - if not_transforms: - self.log.error("Only transforms can be part of the controls_SET." - "\n%s" % not_transforms) - error = True - - if not_meshes: - self.log.error("Only meshes can be part of the out_SET\n%s" - % not_meshes) - error = True - - if error: - raise RuntimeError("Invalid rig content. See log for details.") + return errors From 048be38b6dada5782b039d140808be2551e59b6f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 15:56:05 +0200 Subject: [PATCH 0025/1249] moves get_visibile function to lib --- colorbleed/maya/lib.py | 60 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 6d4c9a272c..2c71c05d32 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -242,3 +242,63 @@ def collect_animation_data(): def get_current_renderlayer(): return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + + +def is_visible(node, + displayLayer=True, + intermediateObject=True, + parentHidden=True, + visibility=True): + """Is `node` visible? + + Returns whether a node is hidden by one of the following methods: + - The node exists (always checked) + - The node must be a dagNode (always checked) + - The node's visibility is off. + - The node is set as intermediate Object. + - The node is in a disabled displayLayer. + - Whether any of its parent nodes is hidden. + + Roughly based on: http://ewertb.soundlinker.com/mel/mel.098.php + + Returns: + bool: Whether the node is visible in the scene + + """ + + # Only existing objects can be visible + if not cmds.objExists(node): + return False + + # Only dagNodes can be visible + if not cmds.objectType(node, isAType='dagNode'): + return False + + if visibility: + if not cmds.getAttr('{0}.visibility'.format(node)): + return False + + if intermediateObject and cmds.objectType(node, isAType='shape'): + if cmds.getAttr('{0}.intermediateObject'.format(node)): + return False + + if displayLayer: + # Display layers set overrideEnabled and overrideVisibility on members + if cmds.attributeQuery('overrideEnabled', node=node, exists=True): + override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node)) + override_visibility = cmds.getAttr('{}.overrideVisibility'.format(node)) + if override_enabled and override_visibility: + return False + + if parentHidden: + parents = cmds.listRelatives(node, parent=True, fullPath=True) + if parents: + parent = parents[0] + if not is_visible(parent, + displayLayer=displayLayer, + intermediateObject=False, + parentHidden=parentHidden, + visibility=visibility): + return False + + return True \ No newline at end of file From b14399b2563a57dddec35a42fc13a323fbd771bc Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 15:59:13 +0200 Subject: [PATCH 0026/1249] changes is_visible to lib.is_visible --- .../publish/validate_instancer_content.py | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_instancer_content.py b/colorbleed/plugins/maya/publish/validate_instancer_content.py index 68c6fbc819..bc7d3941cb 100644 --- a/colorbleed/plugins/maya/publish/validate_instancer_content.py +++ b/colorbleed/plugins/maya/publish/validate_instancer_content.py @@ -1,6 +1,7 @@ -import pyblish.api import maya.cmds as cmds -import cb.utils.maya.dag as dag + +import pyblish.api +import colorbleed.maya.lib as lib class ValidateInstancerContent(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): def process(self, instance): - invalid = False + error = False members = instance.data['setMembers'] export_members = instance.data['exactExportMembers'] @@ -23,42 +24,22 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): if not len(members) == len(cmds.ls(members, type="instancer")): self.log.error("Instancer can only contain instancers") - invalid = True + error = True # TODO: Implement better check for particles are cached if not cmds.ls(export_members, type="nucleus"): self.log.error("Instancer must have a connected nucleus") - invalid = True + error = True if not cmds.ls(export_members, type="cacheFile"): self.log.error("Instancer must be cached") - invalid = True + error = True - # Ensure all instanced geometry is hidden - shapes = cmds.ls(export_members, - dag=True, shapes=True, - noIntermediate=True) - meshes = cmds.ls(shapes, type="mesh") - - def invalidate(node): - """Whether mesh is in a valid state - - Arguments: - node (str): The node to check - - Returns: - bool: Whether it is in a valid state. - - """ - return dag.is_visible(node, - displayLayer=False, - intermediateObject=False) - - visible = [node for node in meshes if invalidate(node)] - if visible: + hidden = self.check_geometry_hidden(export_members) + if not hidden: + error = True self.log.error("Instancer input geometry must be hidden " - "the scene. Invalid: {0}".format(visible)) - invalid = True + "the scene. Invalid: {0}".format(hidden)) # Ensure all in one group parents = cmds.listRelatives(members, @@ -68,7 +49,26 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): if len(roots) > 1: self.log.error("Instancer should all be contained in a single " "group. Current roots: {0}".format(roots)) - invalid = True + error = True - if invalid: + if error: raise RuntimeError("Instancer Content is invalid. See log.") + + def check_geometry_hidden(self, export_members): + + # Ensure all instanced geometry is hidden + shapes = cmds.ls(export_members, + dag=True, + shapes=True, + noIntermediate=True) + meshes = cmds.ls(shapes, type="mesh") + + visible = [node for node in meshes + if lib.is_visible(node, + displayLayer=False, + intermediateObject=False)] + if visible: + return False + + return True + From fc7006911c202de5c1e23aa9fa6a02a57930082f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 16:00:00 +0200 Subject: [PATCH 0027/1249] added get_errored_instances_from_context to imports --- colorbleed/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/api.py b/colorbleed/api.py index 9de699fa62..33924cb02b 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -12,6 +12,8 @@ from .plugin import ( # temporary fix, might from .action import ( + + get_errored_instances_from_context, SelectInvalidAction, GenerateUUIDsOnInvalidAction, RepairAction, @@ -24,7 +26,8 @@ all = [ "ValidateContentsOrder", "ValidateSceneOrder", "ValidateMeshOrder", - + # action + "get_errored_instances_from_context", "SelectInvalidAction", "GenerateUUIDsOnInvalidAction", "RepairAction" From 2182f3f238c37c1e11c3121acd8193f1fa7bdf3c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 16:01:15 +0200 Subject: [PATCH 0028/1249] changed is_visible to lib.is_visible --- .../validate_instancer_frame_ranges.py | 65 ++++++++++++------- .../maya/publish/validate_joints_hidden.py | 63 +----------------- .../publish/validate_look_deformed_shapes.py | 5 +- 3 files changed, 45 insertions(+), 88 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py index dd2bae089f..30f8347f35 100644 --- a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py +++ b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py @@ -1,3 +1,5 @@ +import os +import re import pyblish.api VERBOSE = False @@ -10,6 +12,27 @@ def is_cache_resource(resource): return required.issubset(tags) +def valdidate_files(files): + for f in files: + assert os.path.exists(f) + assert f.endswith(".mcx") or f.endswith(".mcc") + + return True + + +def filter_ticks(files): + tick_files = set() + ticks = set() + for path in files: + match = re.match(".+Tick([0-9]+).mcx$", os.path.basename(path)) + if match: + tick_files.add(path) + num = match.group(1) + ticks.add(int(num)) + + return tick_files, ticks + + class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): """Validates all instancer particle systems are cached correctly. @@ -26,7 +49,6 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - import os import pyseq start_frame = instance.data.get("startFrame", 0) @@ -42,7 +64,6 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): node = resource['node'] all_files = resource['files'][:] - all_lookup = set(all_files) # The first file is usually the .xml description file. @@ -54,28 +75,21 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): # Ensure all files exist (including ticks) # The remainder file paths should be the .mcx or .mcc files - for f in all_files: - assert os.path.exists(f) - assert f.endswith(".mcx") or f.endswith(".mcc") + valdidate_files(all_files) - # Maya particle caches support substeps by saving out additional files - # that end with a Tick60.mcx, Tick120.mcx, etc. suffix. To avoid `pyseq` - # getting confused we filter those out and then for each file (except - # the last frame) check that at least all ticks exist. - tick_files = set() - ticks = set() - for path in all_files: - import re - match = re.match(".+Tick([0-9]+).mcx$", os.path.basename(path)) + # Maya particle caches support substeps by saving out additional + # files that end with a Tick60.mcx, Tick120.mcx, etc. suffix. + # To avoid `pyseq` getting confused we filter those out and then + # for each file (except the last frame) check that at least all + # ticks exist. - if match: - tick_files.add(path) - num = match.group(1) - ticks.add(int(num)) + tick_files, ticks = filter_ticks(all_files) + if tick_files: + files = [f for f in all_files if f not in tick_files] + else: + files = all_files - files = [f for f in all_files if f not in tick_files] if tick_files else all_files sequences = pyseq.get_sequences(files) - if len(sequences) != 1: invalid.append(node) cls.log.warning("More than one sequence found? " @@ -112,7 +126,8 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): # for the frames required by the time range. if ticks: ticks = list(sorted(ticks)) - cls.log.info("Found ticks: {0} (substeps: {1})".format(ticks, len(ticks))) + cls.log.info("Found ticks: {0} " + "(substeps: {1})".format(ticks, len(ticks))) # Check all frames except the last since we don't # require subframes after our time range. @@ -123,7 +138,8 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): frame = item.frame if not frame: invalid.append(node) - cls.log.error("Path is not a frame in sequence: {0}".format(item)) + cls.log.error("Path is not a frame in sequence: " + "{0}".format(item)) continue # Not required for our time range @@ -137,7 +153,8 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): if tick_file not in all_lookup: invalid.append(node) cls.log.warning("Tick file found that is not " - "in cache query filenames: {0}".format(tick_file)) + "in cache query filenames: " + "{0}".format(tick_file)) return invalid @@ -148,4 +165,4 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): if invalid: self.log.error("Invalid nodes: {0}".format(invalid)) raise RuntimeError("Invalid particle caches in instance. " - "See logs for details.") + "See logs for details.") \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/colorbleed/plugins/maya/publish/validate_joints_hidden.py index 8378dbf8bc..7772372ad4 100644 --- a/colorbleed/plugins/maya/publish/validate_joints_hidden.py +++ b/colorbleed/plugins/maya/publish/validate_joints_hidden.py @@ -2,66 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api - - -def is_visible(node, - displayLayer=True, - intermediateObject=True, - parentHidden=True, - visibility=True): - """Is `node` visible? - - Returns whether a node is hidden by one of the following methods: - - The node exists (always checked) - - The node must be a dagNode (always checked) - - The node's visibility is off. - - The node is set as intermediate Object. - - The node is in a disabled displayLayer. - - Whether any of its parent nodes is hidden. - - Roughly based on: http://ewertb.soundlinker.com/mel/mel.098.php - - Returns: - bool: Whether the node is visible in the scene - - """ - - # Only existing objects can be visible - if not cmds.objExists(node): - return False - - # Only dagNodes can be visible - if not cmds.objectType(node, isAType='dagNode'): - return False - - if visibility: - if not cmds.getAttr('{0}.visibility'.format(node)): - return False - - if intermediateObject and cmds.objectType(node, isAType='shape'): - if cmds.getAttr('{0}.intermediateObject'.format(node)): - return False - - if displayLayer: - # Display layers set overrideEnabled and overrideVisibility on members - if cmds.attributeQuery('overrideEnabled', node=node, exists=True): - override_enabled = cmds.getAttr('{}.overrideEnabled'.format(node)) - override_visibility = cmds.getAttr('{}.overrideVisibility'.format(node)) - if override_enabled and override_visibility: - return False - - if parentHidden: - parents = cmds.listRelatives(node, parent=True, fullPath=True) - if parents: - parent = parents[0] - if not is_visible(parent, - displayLayer=displayLayer, - intermediateObject=False, - parentHidden=parentHidden, - visibility=visibility): - return False - - return True +import colorbleed.maya.lib as lib class ValidateJointsHidden(pyblish.api.InstancePlugin): @@ -87,7 +28,7 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): joints = cmds.ls(instance, type='joint', long=True) - return [j for j in joints if is_visible(j, displayLayer=True)] + return [j for j in joints if lib.is_visible(j, displayLayer=True)] def process(self, instance): """Process all the nodes in the instance 'objectSet'""" diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py index ed8f50fd15..f688072972 100644 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -2,7 +2,6 @@ from maya import cmds import pyblish.api import colorbleed.api -from colorbleed.api import get_errored_instances_from_context from cbra.utils.maya.node_uuid import get_id, add_ids @@ -71,10 +70,10 @@ class CopyUUIDsFromHistory(pyblish.api.Action): # Get the errored instances self.log.info("Finding failed instances..") - errored_instances = get_errored_instances_from_context(context) + errored = colorbleed.api.get_errored_instances_from_context(context) # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + instances = pyblish.api.instances_by_plugin(errored, plugin) ids_map = dict() for instance in instances: From 100f145151759d618fcbb635ef06b8888a1b648b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Jun 2017 16:13:56 +0200 Subject: [PATCH 0029/1249] Fix CurvesLoader so it passes the RigLoader to `maya.load` so it works with the new load api --- colorbleed/plugins/maya/load/load_animation.py | 9 ++++++--- colorbleed/plugins/maya/load/load_historylook.py | 2 +- colorbleed/plugins/maya/load/load_look.py | 2 +- colorbleed/plugins/maya/load/load_model.py | 4 ++-- colorbleed/plugins/maya/load/load_rig.py | 7 +++++-- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index cbbbc1ff4b..ed65b2f042 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -12,7 +12,7 @@ class AbcLoader(api.Loader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): from maya import cmds cmds.loadPlugin("AbcImport.mll", quiet=True) @@ -40,7 +40,7 @@ class CurvesLoader(api.Loader): order = -1 icon = "question" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): from maya import cmds from avalon import maya @@ -98,7 +98,10 @@ class CurvesLoader(api.Loader): self[:] = nodes + cmds.sets(container, query=True) + [container] - def post_process(self, name, namespace, context): + if data.get("post_process", True): + self._post_process(name, namespace, context, data) + + def _post_process(self, name, namespace, context, data): import os from maya import cmds from avalon import maya, io diff --git a/colorbleed/plugins/maya/load/load_historylook.py b/colorbleed/plugins/maya/load/load_historylook.py index 7ff0557de6..fb18c561c0 100644 --- a/colorbleed/plugins/maya/load/load_historylook.py +++ b/colorbleed/plugins/maya/load/load_historylook.py @@ -13,7 +13,7 @@ class HistoryLookLoader(api.Loader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): from avalon import maya with maya.maintained_selection(): nodes = cmds.file( diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 442b780978..411f2fbed5 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -16,7 +16,7 @@ class LookLoader(api.Loader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): from avalon import maya try: existing_reference = cmds.file(self.fname, diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index fde2f4f72e..313048b1da 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -15,7 +15,7 @@ class ModelLoader(api.Loader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): with maya.maintained_selection(): nodes = cmds.file( @@ -44,7 +44,7 @@ class ModelGPUCacheLoader(api.Loader): order = -1 icon = "download" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): from maya import cmds diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index ab9863ee28..1332e63f4f 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -17,7 +17,7 @@ class RigLoader(api.Loader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context): + def process(self, name, namespace, context, data): nodes = cmds.file(self.fname, namespace=namespace, reference=True, @@ -28,7 +28,10 @@ class RigLoader(api.Loader): # Store for post-process self[:] = nodes - def post_process(self, name, namespace, context): + if data.get("post_process", True): + self._post_process(name, namespace, context, data) + + def _post_process(self, name, namespace, context, data): from avalon import maya # TODO(marcus): We are hardcoding the name "out_SET" here. From 21fa71f11216fca284c3b641c1ea0d503dbc5315 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 17:10:44 +0200 Subject: [PATCH 0030/1249] cosmetics --- .../maya/publish/collect_alembic_attrs.py | 1 - .../maya/publish/collect_instancer_caches.py | 8 ++- .../maya/publish/collect_instancer_history.py | 6 +- .../maya/publish/collect_look_textures.py | 2 - .../plugins/maya/publish/extract_alembic.py | 5 +- .../plugins/maya/publish/extract_instancer.py | 60 +++++++++------- .../plugins/maya/publish/extract_look.py | 5 +- .../maya/publish/extract_yeti_nodes.py | 5 +- .../maya/publish/validate_camera_contents.py | 1 - .../maya/publish/validate_frame_range.py | 3 +- .../publish/validate_gpuCache_not_empty.py | 14 ++-- .../publish/validate_instancer_content.py | 1 - .../validate_instancer_frame_ranges.py | 2 +- .../maya/publish/validate_layout_content.py | 12 ++-- .../maya/publish/validate_layout_node_ids.py | 1 - .../validate_layout_parent_no_transforms.py | 8 +-- .../validate_layout_unique_node_ids.py | 1 - .../publish/validate_look_members_node_ids.py | 1 - .../publish/validate_look_members_unique.py | 1 - .../maya/publish/validate_mesh_has_uv.py | 8 +-- .../publish/validate_mesh_lamina_faces.py | 7 +- .../publish/validate_mesh_non_zero_edge.py | 5 +- .../publish/validate_mesh_single_uv_set.py | 9 +-- .../validate_mesh_vertices_have_edges.py | 10 +-- .../maya/publish/validate_model_content.py | 71 ++----------------- .../maya/publish/validate_namespace_empty.py | 8 +-- .../publish/validate_no_null_transforms.py | 2 - .../maya/publish/validate_no_vraymesh.py | 8 +-- .../validate_references_no_failed_edits.py | 6 +- ...alidate_rig_pointcache_related_node_ids.py | 5 +- .../publish/validate_scene_set_workspace.py | 3 +- .../maya/publish/validate_transfers.py | 6 +- 32 files changed, 110 insertions(+), 175 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_alembic_attrs.py b/colorbleed/plugins/maya/publish/collect_alembic_attrs.py index 5d1cf95744..c16c75a5bb 100644 --- a/colorbleed/plugins/maya/publish/collect_alembic_attrs.py +++ b/colorbleed/plugins/maya/publish/collect_alembic_attrs.py @@ -22,4 +22,3 @@ class CollectAlembicCBAttrs(pyblish.api.InstancePlugin): # Ensure UVs are written instance.data['uvWrite'] = True - diff --git a/colorbleed/plugins/maya/publish/collect_instancer_caches.py b/colorbleed/plugins/maya/publish/collect_instancer_caches.py index 71237551a6..cb59893fdf 100644 --- a/colorbleed/plugins/maya/publish/collect_instancer_caches.py +++ b/colorbleed/plugins/maya/publish/collect_instancer_caches.py @@ -1,6 +1,8 @@ -import pyblish.api import maya.cmds as cmds +import pyblish.api + + class CollectInstancerCaches(pyblish.api.InstancePlugin): """For an Instancer collect the history. @@ -31,10 +33,10 @@ class CollectInstancerCaches(pyblish.api.InstancePlugin): # correctly. if not files: errors = True - self.log.error("Cache has no files: {0}".format(cache)) + self.log.error("Cache has no files: %s" % cache) continue - source = files[0] # The first file is the .xml file + source = files[0] # The first file is the .xml file # TODO: Filter the files to only contain the required frame range. diff --git a/colorbleed/plugins/maya/publish/collect_instancer_history.py b/colorbleed/plugins/maya/publish/collect_instancer_history.py index 4d2d57bd03..dc9cc841ba 100644 --- a/colorbleed/plugins/maya/publish/collect_instancer_history.py +++ b/colorbleed/plugins/maya/publish/collect_instancer_history.py @@ -31,14 +31,14 @@ class CollectInstancerHistory(pyblish.api.InstancePlugin): export.extend(particles) if particles: self.log.info("Particles: {0}".format(particles)) - + particles_history = cmds.listHistory(particles) or [] self.log.debug("Particle history: {0}".format(particles_history)) - + nucleus = cmds.ls(particles_history, long=True, type="nucleus") self.log.info("Collected nucleus: {0}".format(nucleus)) export.extend(nucleus) - + caches = cmds.ls(particles_history, long=True, type="cacheFile") self.log.info("Collected caches: {0}".format(caches)) export.extend(caches) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index 5cac73cb20..e75419868a 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -150,5 +150,3 @@ class CollectLookTextures(pyblish.api.InstancePlugin): } return resource - - diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 55b8fa2619..1efd7396f0 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -87,7 +87,8 @@ class ExtractAlembic(colorbleed.api.Extractor): return {"startFrame": float, "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame + "frameRange": str, + # "start end"; overrides startFrame & endFrame "eulerFilter": bool, "frameRelativeSample": float, "noNormals": bool, @@ -154,7 +155,7 @@ class ExtractAlembic(colorbleed.api.Extractor): verbose = instance.data('verbose', False) if verbose: - self.log.debug('Alembic job string: "%s"'% job_str) + self.log.debug('Alembic job string: "%s"' % job_str) if not os.path.exists(parent_dir): os.makedirs(parent_dir) diff --git a/colorbleed/plugins/maya/publish/extract_instancer.py b/colorbleed/plugins/maya/publish/extract_instancer.py index 3d76cff89c..77d72b1ef6 100644 --- a/colorbleed/plugins/maya/publish/extract_instancer.py +++ b/colorbleed/plugins/maya/publish/extract_instancer.py @@ -86,7 +86,8 @@ class ExtractInstancerMayaAscii(colorbleed.api.Extractor): hosts = ["maya"] families = ["colorbleed.instancer"] - # TODO: Find other solution than expanding vars to fix lack of support of cacheFile + # TODO: Find other solution than expanding vars to fix lack of support + # TODO: of cacheFile def process(self, instance): @@ -94,6 +95,35 @@ class ExtractInstancerMayaAscii(colorbleed.api.Extractor): # Set up cacheFile path remapping. resources = instance.data.get("resources", []) + attr_remap, cache_remap = self.process_resources(resources) + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing extraction..") + with pyblish_maya.maintained_selection(): + with cache_file_paths(cache_remap): + with context.attribute_values(attr_remap): + cmds.select(export, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) + + def process_resources(self, resources): + attr_remap = dict() cache_remap = dict() for resource in resources: @@ -113,32 +143,10 @@ class ExtractInstancerMayaAscii(colorbleed.api.Extractor): folder += "/" # Set path and name - attr_remap["{0}.cacheName".format(node)] = os.path.expandvars(fname) + attr_remap["{0}.cacheName".format(node)] = os.path.expandvars( + fname) cache_remap[node] = os.path.expandvars(folder) self.log.info("Mapping {0} to {1}".format(node, destination)) - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - # Perform extraction - self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): - with cache_file_paths(cache_remap): - with context.attribute_values(attr_remap): - cmds.select(export, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=False, - shader=False, - expressions=False) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) + return attr_remap, cache_remap diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index a0b9b3daf0..75a49112a5 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -41,11 +41,12 @@ class ExtractLook(colorbleed.api.Extractor): # Define the texture file node remapping resource_remap = dict() - required = ["maya", "attribute", "look"] # required tags to be a look resource + # required tags to be a look resource + required_tags = ["maya", "attribute", "look"] resources = instance.data.get("resources", []) for resource in resources: resource_tags = resource.get("tags", []) - if all(tag in resource_tags for tag in required): + if all(tag in resource_tags for tag in required_tags): node = resource['node'] destination = resource['destination'] resource_remap["{}.fileTextureName".format(node)] = destination diff --git a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py index d380f0b86b..45ceddf9d7 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py @@ -37,11 +37,12 @@ class ExtractFurYeti(colorbleed.api.Extractor): # Remap cache files names and ensure fileMode is set to load from cache resource_remap = dict() - required = ["maya", "yeti", "attribute"] # required tags to be a yeti resource + # required tags to be a yeti resource + required_tags = ["maya", "yeti", "attribute"] resources = instance.data.get("resources", []) for resource in resources: resource_tags = resource.get("tags", []) - if all(tag in resource_tags for tag in required): + if all(tag in resource_tags for tag in required_tags): attribute = resource['attribute'] destination = resource['destination'] resource_remap[attribute] = destination diff --git a/colorbleed/plugins/maya/publish/validate_camera_contents.py b/colorbleed/plugins/maya/publish/validate_camera_contents.py index 162cc89786..47ed978296 100644 --- a/colorbleed/plugins/maya/publish/validate_camera_contents.py +++ b/colorbleed/plugins/maya/publish/validate_camera_contents.py @@ -61,4 +61,3 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Invalid camera contents: " "{0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index 82b2002677..7072bebdf4 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -43,7 +43,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): self.log.info("Comparing start (%s) and end (%s)" % (start, end)) if start > end: raise RuntimeError("The start frame is a higher value " - "than the end frame: {0}>{1}".format(start, end)) + "than the end frame: " + "{0}>{1}".format(start, end)) if handles is not None: if handles < 0.0: diff --git a/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py b/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py index 7160ceaeec..61d67f967c 100644 --- a/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py +++ b/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py @@ -9,22 +9,21 @@ import colorbleed.api def get_gpu_cache_subnodes(cache): """Return the amount of subnodes in the cache - + This uses `maya.cmds.gpuCache(showStats=True)` and parses the resulting stats for the amount of internal sub nodes. - + Args: cache (str): gpuCache node name. - + Returns: int: Amount of subnodes in loaded gpuCache - + Raises: TypeError: when `cache` is not a gpuCache object type. RuntimeError: when `maya.cmds.gpuCache(showStats=True)` does not return stats from which we can parse the amount of subnodes. - """ # Ensure gpuCache @@ -65,10 +64,9 @@ def get_empty_gpu_caches(caches): class ValidateGPUCacheNotEmpty(pyblish.api.InstancePlugin): """Validates that gpuCaches have at least one visible shape in them. - + This is tested using the `maya.cmds.gpuCache(cache, showStats=True)` command. - """ order = colorbleed.api.ValidateContentsOrder @@ -78,14 +76,12 @@ class ValidateGPUCacheNotEmpty(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - caches = cmds.ls(instance, type="gpuCache", long=True) invalid = get_empty_gpu_caches(caches) return invalid def process(self, instance): - invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_instancer_content.py b/colorbleed/plugins/maya/publish/validate_instancer_content.py index bc7d3941cb..91b7b15044 100644 --- a/colorbleed/plugins/maya/publish/validate_instancer_content.py +++ b/colorbleed/plugins/maya/publish/validate_instancer_content.py @@ -71,4 +71,3 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): return False return True - diff --git a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py index 30f8347f35..c7307c5a2a 100644 --- a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py +++ b/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py @@ -165,4 +165,4 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): if invalid: self.log.error("Invalid nodes: {0}".format(invalid)) raise RuntimeError("Invalid particle caches in instance. " - "See logs for details.") \ No newline at end of file + "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_layout_content.py b/colorbleed/plugins/maya/publish/validate_layout_content.py index dd4f1e8c3d..6504e96094 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_content.py +++ b/colorbleed/plugins/maya/publish/validate_layout_content.py @@ -4,6 +4,7 @@ import colorbleed.api import pyblish_maya import cb.utils.maya.dag as dag +import cbra.utils.maya.layout as layout class ValidateLayoutContent(pyblish.api.InstancePlugin): @@ -30,15 +31,15 @@ class ValidateLayoutContent(pyblish.api.InstancePlugin): placeholder = instance.data.get("placeholder", False) # Ensure any meshes or gpuCaches in instance - if not cmds.ls(instance, type=("mesh", "gpuCache", "nurbsCurve"), long=True): - raise RuntimeError("Layout has no mesh, gpuCache or nurbsCurve children: " - "{0}".format(instance)) + if not cmds.ls(instance, type=("mesh", "gpuCache", "nurbsCurve"), + long=True): + raise RuntimeError( + "Layout has no mesh, gpuCache or nurbsCurve children: " + "{0}".format(instance)) # Ensure at least any extract nodes readily available after filtering with pyblish_maya.maintained_selection(): - import cbra.utils.maya.layout as layout - nodes = instance.data['setMembers'] cmds.select(nodes, r=1, hierarchy=True) hierarchy = cmds.ls(sl=True, long=True) @@ -84,4 +85,3 @@ class ValidateLayoutContent(pyblish.api.InstancePlugin): raise RuntimeError("No extract shape is visible. " "Layout requires at least one " "shape to be visible.") - diff --git a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py index d56620271b..6ea48efa41 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py @@ -42,4 +42,3 @@ class ValidateLayoutNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Transforms (non-referenced) found in layout " "without asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py b/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py index 79a1ec4198..b0d3c00097 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py @@ -17,9 +17,8 @@ _ATTRS = ['tx', 'ty', 'tz', def is_identity(node, tolerance=1e-30): - mat = cmds.xform(node, query=True, matrix=True, objectSpace=True) - if not all(abs(x-y) < tolerance for x, y in zip(_IDENTITY, mat)): + if not all(abs(x - y) < tolerance for x, y in zip(_IDENTITY, mat)): return False return True @@ -49,12 +48,12 @@ class ValidateLayoutParentNoTransforms(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): + invalid = [] + # Get highest in hierarchy nodes = instance.data["setMembers"] highest = getHighestInHierarchy(nodes) - invalid = [] - for node in highest: for parent in iterParents(node): if not is_identity(parent) or is_animated(parent): @@ -70,4 +69,3 @@ class ValidateLayoutParentNoTransforms(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Transforms (non-referenced) found in layout " "without asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py index 3ea61aff27..6a4e389c98 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py @@ -62,4 +62,3 @@ class ValidateLayoutUniqueNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Transforms found with non-unique " "asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 4c07dffd37..e61c357de3 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -52,4 +52,3 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Members found without " "asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index f5973e62dc..e199138080 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -66,4 +66,3 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Members found without " "asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py index f3e760e27d..bc7c19e5ad 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py @@ -65,12 +65,11 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): vertex = cmds.polyEvaluate(node, vertex=True) if uv < vertex: - # Workaround: # Maya can have instanced UVs in a single mesh, for example - # imported from an Alembic. With instanced UVs the UV count from - # `maya.cmds.polyEvaluate(uv=True)` will only result in the unique - # UV count instead of for all vertices. + # imported from an Alembic. With instanced UVs the UV count + # from `maya.cmds.polyEvaluate(uv=True)` will only result in + # the unique UV count instead of for all vertices. # # Note: Maya can save instanced UVs to `mayaAscii` but cannot # load this as instanced. So saving, opening and saving @@ -90,7 +89,6 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Meshes found in instance without " "valid UVs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py index 8dbb3f3594..80a6968e55 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py @@ -6,7 +6,7 @@ import colorbleed.api class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): """Validate meshes don't have lamina faces. - + Lamina faces share all of their edges. """ @@ -22,7 +22,10 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): meshes = cmds.ls(instance, type='mesh', long=True) - return [mesh for mesh in meshes if cmds.polyInfo(mesh, laminaFaces=True)] + invalid = [mesh for mesh in meshes if + cmds.polyInfo(mesh, laminaFaces=True)] + + return invalid def process(self, instance): """Process all the nodes in the instance 'objectSet'""" diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py index 1cbac35197..b706913592 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py @@ -29,9 +29,8 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): """Return the invalid edges. - Also see: http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup - + """ meshes = cmds.ls(instance, type='mesh', long=True) @@ -43,7 +42,7 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): # Filter by constraint on edge length invalid = polyConstraint(edges, - t=0x8000, # type=edge + t=0x8000, # type=edge length=1, lengthbound=(0, cls.__tolerance)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py index 3d02f54251..85567631d5 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -24,12 +24,12 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): invalid = [] for mesh in meshes: - uvSets = cmds.polyUVSet(mesh, - query=True, + uvSets = cmds.polyUVSet(mesh, + query=True, allUVSets=True) or [] # ensure unique (sometimes maya will list 'map1' twice) - uvSets = set(uvSets) + uvSets = set(uvSets) if len(uvSets) != 1: invalid.append(mesh) @@ -112,7 +112,8 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): indices = cmds.getAttr('{0}.uvSet'.format(mesh), multiIndices=True) if not indices: - cls.log.warning("No uv set found indices for: {0}".format(mesh)) + cls.log.warning( + "No uv set found indices for: {0}".format(mesh)) return # Delete from end to avoid shifting indices diff --git a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py index 3db14f226a..a948e2aedf 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py @@ -39,21 +39,21 @@ def len_flattened(components): class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): """Validate meshes have only vertices that are connected by to edges. - + Maya can have invalid geometry with vertices that have no edges or faces connected to them. - + In Maya 2016 EXT 2 and later there's a command to fix this: `maya.cmds.polyClean(mesh, cleanVertices=True) - + In older versions of Maya it works to select the invalid vertices and merge the components. - + To find these invalid vertices select all vertices of the mesh that are visible in the viewport (drag to select), afterwards invert your selection (Ctrl + Shift + I). The remaining selection contains the invalid vertices. - + """ order = colorbleed.api.ValidateMeshOrder diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 51b0647441..7797054c27 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -4,65 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api - - -def is_visible(node, - displayLayer=True, - intermediateObject=True, - parentHidden=True, - visibility=True): - """Is `node` visible? - - Returns whether a node is hidden by one of the following methods: - - The node exists (always checked) - - The node must be a dagNode (always checked) - - The node's visibility is off. - - The node is set as intermediate Object. - - The node is in a disabled displayLayer. - - Whether any of its parent nodes is hidden. - - Roughly based on: http://ewertb.soundlinker.com/mel/mel.098.php - - Returns: - bool: Whether the node is visible in the scene - - """ - - # Only existing objects can be visible - if not cmds.objExists(node): - return False - - # Only dagNodes can be visible - if not cmds.objectType(node, isAType='dagNode'): - return False - - if visibility: - if not cmds.getAttr('{0}.visibility'.format(node)): - return False - - if intermediateObject and cmds.objectType(node, isAType='shape'): - if cmds.getAttr('{0}.intermediateObject'.format(node)): - return False - - if displayLayer: - # Display layers set overrideEnabled and overrideVisibility on members - if cmds.attributeQuery('overrideEnabled', node=node, exists=True): - if cmds.getAttr('{0}.overrideEnabled'.format(node)) and \ - cmds.getAttr('{0}.overrideVisibility'.format(node)): - return False - - if parentHidden: - parents = cmds.listRelatives(node, parent=True, fullPath=True) - if parents: - parent = parents[0] - if not is_visible(parent, - displayLayer=displayLayer, - intermediateObject=False, - parentHidden=parentHidden, - visibility=visibility): - return False - - return True +import colorbleed.maya.lib as lib class ValidateModelContent(pyblish.api.InstancePlugin): @@ -114,11 +56,11 @@ class ValidateModelContent(pyblish.api.InstancePlugin): def _is_visible(node): """Return whether node is visible""" - return is_visible(node, - displayLayer=False, - intermediateObject=True, - parentHidden=True, - visibility=True) + return lib.is_visible(node, + displayLayer=False, + intermediateObject=True, + parentHidden=True, + visibility=True) # The roots must be visible (the assemblies) for assembly in assemblies: @@ -141,4 +83,3 @@ class ValidateModelContent(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Model content is invalid. See log.") - diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py index 5cf19e410f..7f413c2fd7 100644 --- a/colorbleed/plugins/maya/publish/validate_namespace_empty.py +++ b/colorbleed/plugins/maya/publish/validate_namespace_empty.py @@ -1,6 +1,7 @@ +from maya import cmds + import pyblish.api import colorbleed.api -from maya import cmds class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): @@ -8,7 +9,7 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): This is a scene wide validation that filters out "UI" and "shared" namespaces that exist by default in Maya and are mostly hidden. - + A namespace that has other namespaces in it is *not* considered empty. Only those that have no children namespaces or nodes is considered empty. @@ -35,5 +36,4 @@ class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): if not namespace_content: invalid.append(namespace) - assert not invalid, ( - "Empty namespaces found: {0}".format(invalid)) + assert not invalid, ("Empty namespaces found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py index 077844df14..d94938493b 100644 --- a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py @@ -60,7 +60,6 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): def process(self, instance): """Process all the transform nodes in the instance """ invalid = self.get_invalid(instance) - if invalid: raise ValueError("Empty transforms found: {0}".format(invalid)) @@ -73,6 +72,5 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): """ invalid = cls.get_invalid(instance) - if invalid: cmds.delete(invalid) diff --git a/colorbleed/plugins/maya/publish/validate_no_vraymesh.py b/colorbleed/plugins/maya/publish/validate_no_vraymesh.py index 7aad11e14c..575ad7e549 100644 --- a/colorbleed/plugins/maya/publish/validate_no_vraymesh.py +++ b/colorbleed/plugins/maya/publish/validate_no_vraymesh.py @@ -12,12 +12,12 @@ class ValidateNoVRayMesh(pyblish.api.InstancePlugin): def process(self, instance): shapes = cmds.ls(instance, - shapes=True, - type="mesh") + shapes=True, + type="mesh") inputs = cmds.listConnections(shapes, - destination=False, - source=True) or [] + destination=False, + source=True) or [] vray_meshes = cmds.ls(inputs, type='VRayMesh') if vray_meshes: raise RuntimeError("Meshes that are VRayMeshes shouldn't " diff --git a/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py b/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py index 18a09ff58f..397bf24a6e 100644 --- a/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py +++ b/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py @@ -1,7 +1,7 @@ -import pyblish.api -import colorbleed.api from maya import cmds +import pyblish.api +import colorbleed.api class RepairFailedEditsAction(pyblish.api.Action): @@ -10,7 +10,6 @@ class RepairFailedEditsAction(pyblish.api.Action): icon = "wrench" # Icon from Awesome Icon def process(self, context, plugin): - from maya import cmds self.log.info("Finding bad nodes..") # Get the errored instances @@ -108,7 +107,6 @@ class ValidateReferencesNoFailedEdits(pyblish.api.InstancePlugin): """Process all the nodes in the instance""" invalid = self.get_invalid(instance) - if invalid: raise ValueError("Reference nodes found with failed " "reference edits: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py index 0f0bcac829..8564752cf7 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py @@ -3,6 +3,7 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import cbra.lib import cbra.utils.maya.node_uuid as id_utils @@ -56,8 +57,6 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - import cbra.lib - # Get a full context from the instance context context = instance.data['instanceContext'] item_path = context['itemPath'] @@ -101,8 +100,6 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Nodes found with non-related " "asset IDs: {0}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py index 9ca28564f9..3f4f631897 100644 --- a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py +++ b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py @@ -39,7 +39,8 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): scene_name = cmds.file(query=True, sceneName=True) if not scene_name: - raise RuntimeError("Scene hasn't been saved. Workspace can't be validated.") + raise RuntimeError("Scene hasn't been saved. Workspace can't be " + "validated.") root_dir = cmds.workspace(query=True, rootDirectory=True) diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/colorbleed/plugins/maya/publish/validate_transfers.py index 6d98e8d97d..7fb50567c1 100644 --- a/colorbleed/plugins/maya/publish/validate_transfers.py +++ b/colorbleed/plugins/maya/publish/validate_transfers.py @@ -33,13 +33,13 @@ class ValidateTransfers(pyblish.api.InstancePlugin): for destination, sources in collected.items(): if len(sources) > 1: if verbose: - self.log.error("Non-unique file transfer for resources: {0} " - "(sources: {1})".format(destination, sources)) + self.log.error("Non-unique file transfer for resources: " + "{0} (sources: {1})".format(destination, + sources)) invalid = True invalid_destinations.append(destination) if invalid: - if not verbose: # If not verbose then still log the resource destination as # opposed to every individual file transfer From 27353d4b099a66e8ff699e33d51383c0d7bd45db Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Jun 2017 17:10:53 +0200 Subject: [PATCH 0031/1249] Swap pyblish_maya dependency to avalon.maya --- colorbleed/plugins/maya/publish/__extract_layout.py | 4 ++-- colorbleed/plugins/maya/publish/extract_alembic.py | 4 ++-- colorbleed/plugins/maya/publish/extract_camera_baked.py | 6 +++--- colorbleed/plugins/maya/publish/extract_camera_raw.py | 4 ++-- colorbleed/plugins/maya/publish/extract_instancer.py | 4 ++-- colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py | 4 ++-- colorbleed/plugins/maya/publish/extract_look.py | 4 ++-- colorbleed/plugins/maya/publish/extract_maya_ascii.py | 4 ++-- colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py | 4 ++-- colorbleed/plugins/maya/publish/extract_model.py | 4 ++-- colorbleed/plugins/maya/publish/extract_particles.py | 4 ++-- colorbleed/plugins/maya/publish/extract_yeti_nodes.py | 4 ++-- colorbleed/plugins/maya/publish/validate_layout_content.py | 4 ++-- 13 files changed, 27 insertions(+), 27 deletions(-) diff --git a/colorbleed/plugins/maya/publish/__extract_layout.py b/colorbleed/plugins/maya/publish/__extract_layout.py index a589dc05ae..823337b2e1 100644 --- a/colorbleed/plugins/maya/publish/__extract_layout.py +++ b/colorbleed/plugins/maya/publish/__extract_layout.py @@ -2,7 +2,7 @@ import json from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api import cb.utils.maya.context as context @@ -57,7 +57,7 @@ class ExtractLayout(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): # Get children hierarchy nodes = instance.data['setMembers'] diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 55b8fa2619..9236082268 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -4,7 +4,7 @@ import contextlib from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -160,7 +160,7 @@ class ExtractAlembic(colorbleed.api.Extractor): os.makedirs(parent_dir) with suspension(): - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): self.log.debug( "Preparing %s for export using the following options: %s\n" "and the following string: %s" diff --git a/colorbleed/plugins/maya/publish/extract_camera_baked.py b/colorbleed/plugins/maya/publish/extract_camera_baked.py index 89db766631..6e93beb082 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_baked.py +++ b/colorbleed/plugins/maya/publish/extract_camera_baked.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api import cb.utils.maya.context as context @@ -83,7 +83,7 @@ class ExtractCameraBaked(colorbleed.api.Extractor): path = os.path.join(dir_path, filename) self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(baked_shapes, noExpand=True) cmds.file(path, force=True, @@ -116,7 +116,7 @@ class ExtractCameraBaked(colorbleed.api.Extractor): # we *do not* publish a baked camera we want to keep it enabled. This # way what the artist has in the scene visually represents the output. with context.timewarp(state=not alembic_as_baked): - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(abc_shapes, replace=True, noExpand=True) # Enforce forward slashes for AbcExport because we're diff --git a/colorbleed/plugins/maya/publish/extract_camera_raw.py b/colorbleed/plugins/maya/publish/extract_camera_raw.py index 43c74a8471..18636ae39c 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_raw.py +++ b/colorbleed/plugins/maya/publish/extract_camera_raw.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -30,7 +30,7 @@ class ExtractCameraRaw(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(cameras, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_instancer.py b/colorbleed/plugins/maya/publish/extract_instancer.py index 3d76cff89c..98523e0555 100644 --- a/colorbleed/plugins/maya/publish/extract_instancer.py +++ b/colorbleed/plugins/maya/publish/extract_instancer.py @@ -3,7 +3,7 @@ import contextlib import maya.cmds as cmds -import pyblish_maya +import avalon.maya import colorbleed.api import cb.utils.maya.context as context @@ -125,7 +125,7 @@ class ExtractInstancerMayaAscii(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): with cache_file_paths(cache_remap): with context.attribute_values(attr_remap): cmds.select(export, noExpand=True) diff --git a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py index 17a9f894c9..1128780186 100644 --- a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -22,7 +22,7 @@ class ExtractLayoutMayaAscii(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(instance, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index a0b9b3daf0..95eaf3c2b2 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -3,7 +3,7 @@ import json from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api import cb.utils.maya.context as context @@ -56,7 +56,7 @@ class ExtractLook(colorbleed.api.Extractor): # TODO: Ensure membership edits don't become renderlayer overrides with context.empty_sets(sets): with context.attribute_values(resource_remap): - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(sets, noExpand=True) cmds.file(maya_path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii.py b/colorbleed/plugins/maya/publish/extract_maya_ascii.py index 7b5bc09383..e0129a3614 100644 --- a/colorbleed/plugins/maya/publish/extract_maya_ascii.py +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -23,7 +23,7 @@ class ExtractMayaAscii(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(instance, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py index 7f52388581..528520a0f9 100644 --- a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -37,7 +37,7 @@ class ExtractMayaAsciiRaw(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_model.py b/colorbleed/plugins/maya/publish/extract_model.py index 19b55e03a1..de9dcfa6f1 100644 --- a/colorbleed/plugins/maya/publish/extract_model.py +++ b/colorbleed/plugins/maya/publish/extract_model.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api from cb.utils.maya import context @@ -57,7 +57,7 @@ class ExtractModel(colorbleed.api.Extractor): polygonObject=1): with context.shader(members, shadingEngine="initialShadingGroup"): - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_particles.py b/colorbleed/plugins/maya/publish/extract_particles.py index d47e2c9a9b..96cb71a2de 100644 --- a/colorbleed/plugins/maya/publish/extract_particles.py +++ b/colorbleed/plugins/maya/publish/extract_particles.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api @@ -26,7 +26,7 @@ class ExtractParticlesMayaAscii(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing extraction..") - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): cmds.select(export, noExpand=True) cmds.file(path, force=True, diff --git a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py index d380f0b86b..90281e4636 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py @@ -2,7 +2,7 @@ import os from maya import cmds -import pyblish_maya +import avalon.maya import colorbleed.api import cb.utils.maya.context as context @@ -47,7 +47,7 @@ class ExtractFurYeti(colorbleed.api.Extractor): resource_remap[attribute] = destination # Perform extraction - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): with context.attribute_values(resource_remap): cmds.select(members, r=True, noExpand=True) cmds.file(path, diff --git a/colorbleed/plugins/maya/publish/validate_layout_content.py b/colorbleed/plugins/maya/publish/validate_layout_content.py index dd4f1e8c3d..1c21331d37 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_content.py +++ b/colorbleed/plugins/maya/publish/validate_layout_content.py @@ -1,7 +1,7 @@ import pyblish.api import maya.cmds as cmds import colorbleed.api -import pyblish_maya +import avalon.maya import cb.utils.maya.dag as dag @@ -35,7 +35,7 @@ class ValidateLayoutContent(pyblish.api.InstancePlugin): "{0}".format(instance)) # Ensure at least any extract nodes readily available after filtering - with pyblish_maya.maintained_selection(): + with avalon.maya.maintained_selection(): import cbra.utils.maya.layout as layout From 9cce262694227a82db4bf427318ea3711fee2df3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 27 Jun 2017 18:00:47 +0200 Subject: [PATCH 0032/1249] fixed error in constructing alembic arguments --- colorbleed/plugins/maya/publish/extract_alembic.py | 7 ++++++- .../plugins/maya/publish/validate_naming_convention.py | 0 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/publish/validate_naming_convention.py diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 1efd7396f0..4337fd5a41 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -159,6 +159,11 @@ class ExtractAlembic(colorbleed.api.Extractor): if not os.path.exists(parent_dir): os.makedirs(parent_dir) + # import pprint + # print("START DEBUG") + # print(">>> SET MEMBERS") + # pprint.pprint(instance.data["setMembers"]) + # print("END DEBUG") with suspension(): with pyblish_maya.maintained_selection(): @@ -227,7 +232,7 @@ class ExtractAlembic(colorbleed.api.Extractor): for entry in value: job_args.append("-%s %s" % (key, entry)) elif isinstance(value, bool): - job_args.append("%s" % key) + job_args.append("-%s" % key) else: job_args.append("-%s %s" % (key, value)) diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py new file mode 100644 index 0000000000..e69de29bb2 From 6190980df662a35edf308f458f7292775f99cff2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 12:47:50 +0200 Subject: [PATCH 0033/1249] Disable extract metadata --- .../maya/publish/{extract_metadata.py => _extract_metadata.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/publish/{extract_metadata.py => _extract_metadata.py} (100%) diff --git a/colorbleed/plugins/maya/publish/extract_metadata.py b/colorbleed/plugins/maya/publish/_extract_metadata.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_metadata.py rename to colorbleed/plugins/maya/publish/_extract_metadata.py From 7bc42e9fd9d424fd4770491d17a40be96fdd8b7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 12:49:15 +0200 Subject: [PATCH 0034/1249] Implement extract animation --- colorbleed/maya/lib.py | 182 ++++++++++++++- .../plugins/maya/publish/extract_alembic.py | 213 +----------------- .../plugins/maya/publish/extract_animation.py | 64 ++++++ 3 files changed, 251 insertions(+), 208 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/extract_animation.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2c71c05d32..c6314049ae 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -3,6 +3,11 @@ import re import contextlib from collections import OrderedDict +import logging +import os +import json + +log = logging.getLogger(__name__) from maya import cmds @@ -301,4 +306,179 @@ def is_visible(node, visibility=visibility): return False - return True \ No newline at end of file + return True + + +# The maya alembic export types +_alembic_options = { + "startFrame": float, + "endFrame": float, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "eulerFilter": bool, + "frameRelativeSample": float, + "noNormals": bool, + "renderableOnly": bool, + "step": float, + "stripNamespaces": bool, + "uvWrite": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeVisibility": bool, + "writeColorSets": bool, + "writeFaceSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "dataFormat": str, + "root": (list, tuple), + "attr": (list, tuple), + "attrPrefix": (list, tuple), + "userAttr": (list, tuple), + "melPerFrameCallback": str, + "melPostJobCallback": str, + "pythonPerFrameCallback": str, + "pythonPostJobCallback": str, + "selection": bool +} + + +def extract_alembic(file, + startFrame=None, + endFrame=None, + selection= True, + uvWrite= True, + eulerFilter= True, + dataFormat="ogawa", + verbose=False, + **kwargs): + """Extract a single Alembic Cache. + + This extracts an Alembic cache using the `-selection` flag to minimize + the extracted content to solely what was Collected into the instance. + + Arguments: + + startFrame (float): Start frame of output. Ignored if `frameRange` + provided. + + endFrame (float): End frame of output. Ignored if `frameRange` + provided. + + frameRange (tuple or str): Two-tuple with start and end frame or a + string formatted as: "startFrame endFrame". This argument + overrides `startFrame` and `endFrame` arguments. + + dataFormat (str): The data format to use for the cache, + defaults to "ogawa" + + verbose (bool): When on, outputs frame number information to the + Script Editor or output window during extraction. + + noNormals (bool): When on, normal data from the original polygon + objects is not included in the exported Alembic cache file. + + renderableOnly (bool): When on, any non-renderable nodes or hierarchy, + such as hidden objects, are not included in the Alembic file. + Defaults to False. + + stripNamespaces (bool): When on, any namespaces associated with the + exported objects are removed from the Alembic file. For example, an + object with the namespace taco:foo:bar appears as bar in the + Alembic file. + + uvWrite (bool): When on, UV data from polygon meshes and subdivision + objects are written to the Alembic file. Only the current UV map is + included. + + worldSpace (bool): When on, the top node in the node hierarchy is + stored as world space. By default, these nodes are stored as local + space. Defaults to False. + + eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with + an Euler filter. Euler filtering helps resolve irregularities in + rotations especially if X, Y, and Z rotations exceed 360 degrees. + Defaults to True. + + """ + + # Ensure alembic exporter is loaded + cmds.loadPlugin('AbcExport', quiet=True) + + # Alembic Exporter requires forward slashes + file = file.replace('\\', '/') + + # Pass the start and end frame on as `frameRange` so that it + # never conflicts with that argument + if "frameRange" not in kwargs: + # Fallback to maya timeline if no start or end frame provided. + if startFrame is None: + startFrame = cmds.playbackOptions(query=True, + animationStartTime=True) + if endFrame is None: + endFrame = cmds.playbackOptions(query=True, + animationEndTime=True) + + # Ensure valid types are converted to frame range + assert isinstance(startFrame, _alembic_options["startFrame"]) + assert isinstance(endFrame, _alembic_options["endFrame"]) + kwargs["frameRange"] = "{0} {1}".format(startFrame, endFrame) + else: + # Allow conversion from tuple for `frameRange` + frame_range = kwargs["frameRange"] + if isinstance(frame_range, (list, tuple)): + assert len(frame_range) == 2 + kwargs["frameRange"] = "{0} {1}".format(frame_range[0], + frame_range[1]) + + # Assemble options + options = { + "selection": selection, + "uvWrite": uvWrite, + "eulerFilter": eulerFilter, + "dataFormat": dataFormat + } + options.update(kwargs) + + # Validate options + for key, value in options.copy().items(): + + # Discard unknown options + if key not in _alembic_options: + options.pop(key) + continue + + # Validate value type + valid_types = _alembic_options[key] + if not isinstance(value, valid_types): + raise TypeError("Alembic option unsupported type: " + "{0} (expected {1}}".format(value, valid_types)) + + # Format the job string from options + job_args = list() + for key, value in options.items(): + if isinstance(value, (list, tuple)): + for entry in value: + job_args.append("-{0} {1}".format(key=key, value=entry)) + elif isinstance(value, bool): + job_args.append("-{0}".format(key)) + else: + job_args.append("-{0} {1}".format(key, value)) + + job_str = " ".join(job_args) + job_str += ' -file "%s"' % file + + # Ensure output directory exists + parent_dir = os.path.dirname(file) + if not os.path.exists(parent_dir): + os.makedirs(parent_dir) + + if verbose: + log.debug("Preparing Alembic export with options: %s", + json.dumps(options, indent=4)) + log.debug("Extracting Alembic with job arguments: %s", job_str) + + # Perform extraction + cmds.AbcExport(j=job_str, verbose=verbose) + + if verbose: + log.debug("Extracted Alembic to: %s", file) + + return file diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 9236082268..2bde0bfe9a 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -1,20 +1,9 @@ import os -import json -import contextlib - -from maya import cmds +import copy import avalon.maya import colorbleed.api - - -@contextlib.contextmanager -def suspension(): - try: - cmds.refresh(suspend=True) - yield - finally: - cmds.refresh(suspend=False) +from colorbleed.maya.lib import extract_alembic class ExtractAlembic(colorbleed.api.Extractor): @@ -23,213 +12,23 @@ class ExtractAlembic(colorbleed.api.Extractor): This extracts an Alembic cache using the `-selection` flag to minimize the extracted content to solely what was Collected into the instance. - Arguments: - - startFrame (float): Start frame of output. Ignored if `frameRange` - provided. - - endFrame (float): End frame of output. Ignored if `frameRange` - provided. - - frameRange (str): Frame range in the format of "startFrame endFrame". - Overrides `startFrame` and `endFrame` arguments. - - dataFormat (str): The data format to use for the cache, - defaults to "ogawa" - - verbose (bool): When on, outputs frame number information to the - Script Editor or output window during extraction. - - noNormals (bool): When on, normal data from the original polygon - objects is not included in the exported Alembic cache file. - - renderableOnly (bool): When on, any non-renderable nodes or hierarchy, - such as hidden objects, are not included in the Alembic file. - Defaults to False. - - stripNamespaces (bool): When on, any namespaces associated with the - exported objects are removed from the Alembic file. For example, an - object with the namespace taco:foo:bar appears as bar in the - Alembic file. - - uvWrite (bool): When on, UV data from polygon meshes and subdivision - objects are written to the Alembic file. Only the current UV map is - included. - - worldSpace (bool): When on, the top node in the node hierarchy is - stored as world space. By default, these nodes are stored as local - space. Defaults to False. - - eulerFilter (bool): When on, X, Y, and Z rotation data is filtered with - an Euler filter. Euler filtering helps resolve irregularities in - rotations especially if X, Y, and Z rotations exceed 360 degrees. - Defaults to True. """ - label = "Alembic" families = ["colorbleed.model", "colorbleed.pointcache", - "colorbleed.animation", "colorbleed.proxy"] optional = True - @property - def options(self): - """Overridable options for Alembic export - - Given in the following format - - {NAME: EXPECTED TYPE} - - If the overridden option's type does not match, - the option is not included and a warning is logged. - - """ - - return {"startFrame": float, - "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame - "eulerFilter": bool, - "frameRelativeSample": float, - "noNormals": bool, - "renderableOnly": bool, - "step": float, - "stripNamespaces": bool, - "uvWrite": bool, - "wholeFrameGeo": bool, - "worldSpace": bool, - "writeVisibility": bool, - "writeColorSets": bool, - "writeFaceSets": bool, - "writeCreases": bool, # Maya 2015 Ext1+ - "dataFormat": str, - "root": (list, tuple), - "attr": (list, tuple), - "attrPrefix": (list, tuple), - "userAttr": (list, tuple), - "melPerFrameCallback": str, - "melPostJobCallback": str, - "pythonPerFrameCallback": str, - "pythonPostJobCallback": str, - "selection": bool} - - @property - def default_options(self): - """Supply default options to extraction. - - This may be overridden by a subclass to provide - alternative defaults. - - """ - - start_frame = cmds.playbackOptions(query=True, animationStartTime=True) - end_frame = cmds.playbackOptions(query=True, animationEndTime=True) - - return {"startFrame": start_frame, - "endFrame": end_frame, - "selection": True, - "uvWrite": True, - "eulerFilter": True, - "dataFormat": "ogawa" # ogawa, hdf5 - } - def process(self, instance): - # Ensure alembic exporter is loaded - cmds.loadPlugin('AbcExport', quiet=True) parent_dir = self.staging_dir(instance) filename = "%s.abc" % instance.name path = os.path.join(parent_dir, filename) - # Alembic Exporter requires forward slashes - path = path.replace('\\', '/') + options = copy.deepcopy(instance.data) - options = self.default_options - options["userAttr"] = ("uuid",) - options = self.parse_overrides(instance, options) + options['selection'] = True - job_str = self.parse_options(options) - job_str += ' -file "%s"' % path - - self.log.info('Extracting alembic to: "%s"' % path) - - verbose = instance.data('verbose', False) - if verbose: - self.log.debug('Alembic job string: "%s"'% job_str) - - if not os.path.exists(parent_dir): - os.makedirs(parent_dir) - - with suspension(): + with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): - self.log.debug( - "Preparing %s for export using the following options: %s\n" - "and the following string: %s" - % (list(instance), - json.dumps(options, indent=4), - job_str)) - cmds.select(instance.data("setMembers"), hierarchy=True) - cmds.AbcExport(j=job_str, verbose=verbose) - - def parse_overrides(self, instance, options): - """Inspect data of instance to determine overridden options - - An instance may supply any of the overridable options - as data, the option is then added to the extraction. - - """ - - for key in instance.data(): - if key not in self.options: - continue - - # Ensure the data is of correct type - value = instance.data(key) - if not isinstance(value, self.options[key]): - self.log.warning( - "Overridden attribute {key} was of " - "the wrong type: {invalid_type} " - "- should have been {valid_type}".format( - key=key, - invalid_type=type(value).__name__, - valid_type=self.options[key].__name__)) - continue - - options[key] = value - - return options - - @classmethod - def parse_options(cls, options): - """Convert key-word arguments to job arguments string - - Args: - options (dict): the options for the command - """ - - # Convert `startFrame` and `endFrame` arguments - if 'startFrame' in options or 'endFrame' in options: - start_frame = options.pop('startFrame', None) - end_frame = options.pop('endFrame', None) - - if 'frameRange' in options: - cls.log.debug("The `startFrame` and/or `endFrame` arguments " - "are overridden by the provided `frameRange`.") - elif start_frame is None or end_frame is None: - cls.log.warning("The `startFrame` and `endFrame` arguments " - "must be supplied together.") - else: - options['frameRange'] = "%s %s" % (start_frame, end_frame) - - job_args = list() - for key, value in options.items(): - if isinstance(value, (list, tuple)): - for entry in value: - job_args.append("-%s %s" % (key, entry)) - elif isinstance(value, bool): - job_args.append("%s" % key) - else: - job_args.append("-%s %s" % (key, value)) - - job_str = " ".join(job_args) - - return job_str + extract_alembic(file=path, **options) diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py new file mode 100644 index 0000000000..48cb4711dc --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -0,0 +1,64 @@ +import colorbleed.api + + +class ExtractColorbleedAnimation(colorbleed.api.Extractor): + """Produce an alembic of just point positions and normals. + + Positions and normals are preserved, but nothing more, + for plain and predictable point caches. + + """ + + label = "Extract Animation" + hosts = ["maya"] + families = ["colorbleed.animation"] + + def process(self, instance): + import os + from maya import cmds + import avalon.maya + from colorbleed.maya.lib import extract_alembic + + # Collect the out set nodes + out_sets = [node for node in instance if node.endswith("out_SET")] + if len(out_sets) != 1: + raise RuntimeError("Couldn't find exactly one out_SET: " + "{0}".format(out_sets)) + out_set = out_sets[0] + nodes = cmds.sets(out_set, query=True) + + # Include all descendents + nodes += cmds.listRelatives(nodes, + allDescendents=True, + fullPath=True) or [] + + # Collect the start and end including handles + start = instance.data["startFrame"] + end = instance.data["endFrame"] + handles = instance.data.get("handles", 0) + if handles: + start -= handles + end += handles + + self.log.info("Extracting animation..") + dirname = self.staging_dir(instance) + + self.log.info("nodes: %s" % str(nodes)) + + parent_dir = self.staging_dir(instance) + filename = "{name}.abc".format(**instance.data) + path = os.path.join(parent_dir, filename) + + with avalon.maya.suspended_refresh(): + with avalon.maya.maintained_selection(): + cmds.select(nodes, noExpand=True) + extract_alembic(file=path, **{ + "selection": True, + "frameRange": (start, end), + "writeVisibility": True, + "writeUV": True, + "step": instance.data.get("step", 1.0), + "attributePrefix": ("mb",) + }) + + self.log.info("Extracted {} to {}".format(instance, dirname)) From f9c55ee5ea36bae5e63dd3c2cd2fa7f1c487b4eb Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 28 Jun 2017 14:00:39 +0200 Subject: [PATCH 0035/1249] Changed data structure for lookdev --- colorbleed/plugins/maya/publish/collect_look.py | 13 ++++++------- .../plugins/maya/publish/collect_look_textures.py | 2 +- .../plugins/maya/publish/validate_look_contents.py | 9 +++++---- .../maya/publish/validate_look_members_node_ids.py | 2 +- .../maya/publish/validate_look_members_unique.py | 8 ++++---- .../publish/validate_look_no_default_shaders.py | 5 +++-- .../maya/publish/validate_look_node_unique_ids.py | 2 +- 7 files changed, 21 insertions(+), 20 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index a51eac58a7..7ee62af5fe 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -83,7 +83,7 @@ class CollectLook(pyblish.api.InstancePlugin): verbose = instance.data.get("verbose", False) self.log.info("Looking for look associations " - "for %s" % instance.data['label']) + "for %s" % instance.data['name']) # Discover related object sets self.log.info("Gathering sets..") @@ -118,16 +118,15 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Gathering attribute changes to instance members..") attributes = self.collect_attributes_changes(instance) + looksets = cmds.ls(sets.keys(), absoluteName=True, long=True) # Store data on the instance - instance.data["lookAttributes"] = attributes - instance.data["lookSetRelations"] = sets.values() - instance.data["lookSets"] = cmds.ls(sets.keys(), - absoluteName=True, - long=True) + instance.data["lookData"] = {"attributes": attributes, + "relationships": sets.values(), + "sets": looksets} # Log a warning when no relevant sets were retrieved for the look. - if not instance.data['lookSets']: + if not instance.data["lookData"]["sets"]: self.log.warning("No sets found for the nodes in the instance: " "%s" % instance[:]) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_look_textures.py index e75419868a..a6636b24a2 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_look_textures.py @@ -82,7 +82,7 @@ class CollectLookTextures(pyblish.api.InstancePlugin): verbose = instance.data.get("verbose", False) # Get textures from sets - sets = instance.data["lookSets"] + sets = instance.data["lookData"]["sets"] if not sets: raise RuntimeError("No look sets found for the nodes in the " "instance. %s" % sets) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index f117dad794..c32dfc130f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -20,16 +20,17 @@ class ValidateLookContents(pyblish.api.InstancePlugin): error = False - attributes = ["lookSets", - "lookSetRelations", - "lookAttributes"] + attributes = ["sets", + "relationships", + "attributes"] if not instance[:]: raise RuntimeError("Instance is empty") # Required look data + lookdata = instance.data["lookData"] for attr in attributes: - if attr not in instance.data: + if attr not in lookdata: self.log.error("No %s found in data" % attr) error = True diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index e61c357de3..d601b22601 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -27,7 +27,7 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): # Get all members from the sets members = [] - relations = instance.data["lookSetRelations"] + relations = instance.data["lookData"]["relationships"] for sg in relations: sg_members = sg['members'] sg_members = [member['name'] for member in sg_members] diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index e199138080..18de8e73c4 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -32,7 +32,7 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): # Get all members from the sets members = [] - relations = instance.data["lookSetRelations"] + relations = instance.data["lookData"]["sets"] for sg in relations: sg_members = sg['members'] sg_members = [member['name'] for member in sg_members] @@ -45,11 +45,11 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): # Group members per id id_nodes = defaultdict(set) for node in members: - id = id_utils.get_id(node) - if not id: + node_id = id_utils.get_id(node) + if not node_id: continue - id_nodes[id].add(node) + id_nodes[node_id].add(node) invalid = list() for nodes in id_nodes.itervalues(): diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 901a6b9fed..44b5a3331b 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -38,7 +38,8 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): disallowed = set(disallowed) # Check among the sets - sets = instance.data['lookSets'] + lookdata = instance.data["lookData"] + sets = lookdata['sets'] lookup = set(sets) intersect = lookup.intersection(disallowed) if intersect: @@ -67,7 +68,7 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): # Get members of the shaders all = set() for shader in shaders: - members = cmds.sets(shader, q=True) or [] + members = cmds.sets(shader, query=True) or [] members = cmds.ls(members, long=True) all.update(members) diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index e4778e914e..738075fec5 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -20,7 +20,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): - nodes = instance.data["lookSets"] + nodes = instance.data["lookData"]["sets"] # Ensure all nodes have a cbId id_sets = defaultdict(list) From ef03d9af477105c5fa6dd59e36a288512d372611 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 18:05:19 +0200 Subject: [PATCH 0036/1249] Fix typo and cosmetics --- colorbleed/maya/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c6314049ae..56a54e7413 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -343,9 +343,9 @@ _alembic_options = { def extract_alembic(file, startFrame=None, endFrame=None, - selection= True, - uvWrite= True, - eulerFilter= True, + selection=True, + uvWrite=True, + eulerFilter=True, dataFormat="ogawa", verbose=False, **kwargs): @@ -456,7 +456,7 @@ def extract_alembic(file, for key, value in options.items(): if isinstance(value, (list, tuple)): for entry in value: - job_args.append("-{0} {1}".format(key=key, value=entry)) + job_args.append("-{0} {1}".format(key, entry)) elif isinstance(value, bool): job_args.append("-{0}".format(key)) else: From 4ce33fda6e1ff5fae4e328a4ae751cdd5e459d75 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 18:15:21 +0200 Subject: [PATCH 0037/1249] Fix pointcache creator and refactor lib.OrderedDict import --- colorbleed/plugins/maya/create/colorbleed_animation.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_camera.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_layout.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_look.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_pointcache.py | 4 +++- colorbleed/plugins/maya/create/colorbleed_yetifur.py | 3 ++- 6 files changed, 13 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index d7a008ec10..2030042a01 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -13,7 +14,7 @@ class CreateAnimation(avalon.maya.Creator): super(CreateAnimation, self).__init__(*args, **kwargs) # create an ordered dict with the existing data first - data = lib.OrderedDict(**self.data) + data = OrderedDict(**self.data) # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 41864503d8..749945caf3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -13,7 +14,7 @@ class CreateCamera(avalon.maya.Creator): super(CreateCamera, self).__init__(*args, **kwargs) # get basic animation data : start / end / handles / steps - data = lib.OrderedDict(**self.data) + data = OrderedDict(**self.data) animation_data = lib.collect_animation_data() for key, value in animation_data.items(): data[key] = value diff --git a/colorbleed/plugins/maya/create/colorbleed_layout.py b/colorbleed/plugins/maya/create/colorbleed_layout.py index 24855bc178..2731ee8542 100644 --- a/colorbleed/plugins/maya/create/colorbleed_layout.py +++ b/colorbleed/plugins/maya/create/colorbleed_layout.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -13,7 +14,7 @@ class CreateLayout(avalon.maya.Creator): super(CreateLayout, self).__init__(*args, **kwargs) # create an ordered dict with the existing data first - data = lib.OrderedDict(**self.data) + data = OrderedDict(**self.data) # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index b93673b523..557647edc0 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -12,7 +13,7 @@ class CreateLook(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) - data = lib.OrderedDict(**self.data) + data = OrderedDict(**self.data) data["renderlayer"] = lib.get_current_renderlayer() self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index fb9af66b1c..4b59d0cc59 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -15,12 +15,14 @@ class CreatePointCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) + # create an ordered dict with the existing data first + data = OrderedDict(**self.data) + # get scene values as defaults start = cmds.playbackOptions(query=True, animationStartTime=True) end = cmds.playbackOptions(query=True, animationEndTime=True) # build attributes - attributes = OrderedDict() attributes["startFrame"] = start attributes["endFrame"] = end attributes["handles"] = 1 diff --git a/colorbleed/plugins/maya/create/colorbleed_yetifur.py b/colorbleed/plugins/maya/create/colorbleed_yetifur.py index 5a4b483a30..ffd0d2c31d 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yetifur.py +++ b/colorbleed/plugins/maya/create/colorbleed_yetifur.py @@ -1,3 +1,4 @@ +from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -13,7 +14,7 @@ class CreateYetiFur(avalon.maya.Creator): super(CreateYetiFur, self).__init__(*args, **kwargs) # get scene values as defaults - data = lib.OrderedDict(**self.data) + data = OrderedDict(**self.data) animation_data = lib.collect_animation_data() for key, value in animation_data.items(): data[key] = value From 39662cd1be4c20763387a174f5ce285c7245fbe0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 18:16:31 +0200 Subject: [PATCH 0038/1249] Fix typo --- .../plugins/maya/create/colorbleed_pointcache.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 4b59d0cc59..c24dac0b22 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -23,20 +23,20 @@ class CreatePointCache(avalon.maya.Creator): end = cmds.playbackOptions(query=True, animationEndTime=True) # build attributes - attributes["startFrame"] = start - attributes["endFrame"] = end - attributes["handles"] = 1 - attributes["step"] = 1.0 + data["startFrame"] = start + data["endFrame"] = end + data["handles"] = 1 + data["step"] = 1.0 # Write vertex colors with the geometry. - attributes["writeColorSets"] = False + data["writeColorSets"] = False # Include only renderable visible shapes. # Skips locators and empty transforms - attributes["renderableOnly"] = False + data["renderableOnly"] = False # Include only nodes that are visible at least once during the # frame range. - attributes["visibleOnly"] = False + data["visibleOnly"] = False - self.data = attributes \ No newline at end of file + self.data = data From 80719c6488657742945e618e5f7aa9988bb40930 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 18:18:56 +0200 Subject: [PATCH 0039/1249] Add colorbleed.pointcache family to integrator --- colorbleed/plugins/publish/integrate_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py index 457f0cfa67..531b550749 100644 --- a/colorbleed/plugins/publish/integrate_asset.py +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -38,7 +38,8 @@ class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): "colorbleed.camera", "colorbleed.lookdev", "colorbleed.historyLookdev", - "colorbleed.group" + "colorbleed.group", + "colorbleed.pointcache" ] def process(self, instance): From 08e5ca43dbf010342d122127bb11c90bd1a1a8d3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 28 Jun 2017 18:24:18 +0200 Subject: [PATCH 0040/1249] Implement ExtractColorbleedAlembic --- .../plugins/maya/publish/extract_alembic.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 2bde0bfe9a..7db7f2ea18 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -1,12 +1,12 @@ import os -import copy +from maya import cmds import avalon.maya import colorbleed.api from colorbleed.maya.lib import extract_alembic -class ExtractAlembic(colorbleed.api.Extractor): +class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Extract Alembic Cache This extracts an Alembic cache using the `-selection` flag to minimize @@ -17,18 +17,35 @@ class ExtractAlembic(colorbleed.api.Extractor): families = ["colorbleed.model", "colorbleed.pointcache", "colorbleed.proxy"] - optional = True def process(self, instance): parent_dir = self.staging_dir(instance) filename = "%s.abc" % instance.name path = os.path.join(parent_dir, filename) + options = dict() - options = copy.deepcopy(instance.data) + # Collect the start and end including handles if any provided + # otherwise assume frame 1 as startFrame and the same as endFrame + start = instance.data.get("startFrame", 1) + end = instance.data.get("endFrame", start) + handles = instance.data.get("handles", 0) + if handles: + start -= handles + end += handles + options['frameRange'] = (start, end) - options['selection'] = True + # Default verbosity to False + options['verbose'] = instance.data.get("verbose", False) + + # Collect instance options if found in `instance.data` + # for specific settings (for user customization) + for key in ["renderableOnly", "writeColorSets"]: + if key in instance.data: + options[key] = instance.data[key] with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): + nodes = instance[:] + cmds.select(nodes, replace=True, noExpand=True) extract_alembic(file=path, **options) From 09e5f99723445a71729dd2935e8bed5f66c48b85 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 29 Jun 2017 09:38:25 +0200 Subject: [PATCH 0041/1249] Remove redundant (double) selection --- colorbleed/plugins/maya/load/load_animation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index ed65b2f042..68127020cd 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -61,7 +61,6 @@ class CurvesLoader(api.Loader): except StopIteration: raise TypeError("%s is missing controls_SET") - cmds.select(control_set) options = ";".join([ "", "", From ce77f611dc1344876bd2881ed957078d825478d8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 29 Jun 2017 12:57:33 +0200 Subject: [PATCH 0042/1249] Remove "lambert1" assignment, because our models are published with lambert1 applied there's no need to force assign it. This also fixes the "green" shader issue that appears sometimes. --- colorbleed/plugins/maya/load/load_model.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 313048b1da..faeb78599f 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -27,10 +27,6 @@ class ModelLoader(api.Loader): groupName="{}:{}".format(namespace, name) ) - # Assign default shader to meshes - meshes = cmds.ls(nodes, type="mesh") - cmds.sets(meshes, forceElement="initialShadingGroup") - self[:] = nodes From 719de50d866c9b43230602ee95ed18b26d4c1de4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 30 Jun 2017 15:29:03 +0200 Subject: [PATCH 0043/1249] Refactor maya ascii extractor to rig extractor so it's labelled more clearly, also improve behavior --- .../publish/{extract_maya_ascii.py => extract_rig.py} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename colorbleed/plugins/maya/publish/{extract_maya_ascii.py => extract_rig.py} (77%) diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii.py b/colorbleed/plugins/maya/publish/extract_rig.py similarity index 77% rename from colorbleed/plugins/maya/publish/extract_maya_ascii.py rename to colorbleed/plugins/maya/publish/extract_rig.py index e0129a3614..7b6db28838 100644 --- a/colorbleed/plugins/maya/publish/extract_maya_ascii.py +++ b/colorbleed/plugins/maya/publish/extract_rig.py @@ -6,13 +6,12 @@ import avalon.maya import colorbleed.api -class ExtractMayaAscii(colorbleed.api.Extractor): - """Extract as Maya Ascii""" +class ExtractColorbleedRig(colorbleed.api.Extractor): + """Extract rig as Maya Ascii""" - label = "Maya ASCII" + label = "Extract Rig (Maya ASCII)" hosts = ["maya"] families = ["colorbleed.rig"] - optional = True def process(self, instance): @@ -30,6 +29,9 @@ class ExtractMayaAscii(colorbleed.api.Extractor): typ="mayaAscii", exportSelected=True, preserveReferences=False, + channels=True, + constraints=True, + expressions=True, constructionHistory=True) self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) From ed730c02e64a7e5189b09598b89dec545e245270 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 4 Jul 2017 15:20:06 +0200 Subject: [PATCH 0044/1249] implemented scripts menu to launcher --- colorbleed/__init__.py | 2 +- colorbleed/maya/menu.json | 61 ++++++++++++ colorbleed/maya/menu.py | 98 +++++++++++-------- ...ode_ids.py => validate_unique_node_ids.py} | 2 +- 4 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 colorbleed/maya/menu.json rename colorbleed/plugins/maya/publish/{_validate_unique_node_ids.py => validate_unique_node_ids.py} (98%) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index a6d0890f25..a158fcbee9 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -14,4 +14,4 @@ def install(): def uninstall(): - pyblish.deregister_plugin_path(PUBLISH_PATH) \ No newline at end of file + pyblish.deregister_plugin_path(PUBLISH_PATH) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json new file mode 100644 index 0000000000..1918163f97 --- /dev/null +++ b/colorbleed/maya/menu.json @@ -0,0 +1,61 @@ +{"Animation": [{"title" : "Script A", + "tooltip": "Script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "script", "cluster"], + "icon": "$SCRIPTMENU/resources/script_a.png", + "label": "SCR A"}, + {"title" : "Script B", + "tooltip": "Run script B", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "curves"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["test", "script", "joints"], + "icon": ""} + ], + +"Modeling": [{"title" : "Script A", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "script", "model", "blendshapes"]}, + {"title" : "Script B", + "tooltip": "Run script B", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "normals", "model"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["math", "power", "sum"]} + ], + +"Rigging": [{"title" : "Script A", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_a.py", + "sourcetype": "file", + "tags": ["test", "tool", "rig", "skeleton"]}, + {"title" : "Script B", + "tooltip": "Run script A", + "command": "$SCRIPTMENU/script_b.py", + "sourcetype": "file", + "tags": ["test", "script", "cloth", "rig", "setup"]}, + {"title" : "Script C", + "tooltip": "Run script C", + "command": "$SCRIPTMENU/script_c.py", + "sourcetype": "file", + "tags": ["test", "script", "approval"]} + ], + +"MEL": [{"title" : "Create cube", + "tooltip": "Launch character rigging tool", + "command": "polyCube -w 1 -h 1 -d 1;", + "sourcetype": "mel", + "tags": ["test", "script", "mel"]} + ] +} \ No newline at end of file diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 07a9ba81ce..f72dc07ed9 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -1,62 +1,80 @@ import sys -from maya import cmds + from avalon.vendor.Qt import QtWidgets, QtCore self = sys.modules[__name__] self._menu = "colorbleed" -self._parent = { - widget.objectName(): widget - for widget in QtWidgets.QApplication.topLevelWidgets() -}.get("MayaWindow") +self._parent = {widget.objectName(): widget for widget in + QtWidgets.QApplication.topLevelWidgets()}.get("MayaWindow") def install(): - from . import interactive + # from . import interactive uninstall() def deferred(): - cmds.menu(self._menu, - label="Colorbleed", - tearOff=True, - parent="MayaWindow") - # Modeling sub-menu - cmds.menuItem("Modeling", - label="Modeling", - tearOff=True, - subMenu=True, - parent=self._menu) + import site + import os - cmds.menuItem("Combine", command=interactive.combine) + # todo: replace path with server / library path + site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") - # Rigging sub-menu - cmds.menuItem("Rigging", - label="Rigging", - tearOff=True, - subMenu=True, - parent=self._menu) + from scriptsmenu import launchformaya + import scriptsmenu.scriptsmenu as menu - cmds.menuItem("Auto Connect", command=interactive.auto_connect) - cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) - cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) - cmds.menuItem("Clone (Special)", command=interactive.clone_special) - cmds.menuItem("Create Follicle", command=interactive.follicle) + # load configuration of custon menu + config_path = os.path.join(os.path.dirname(__file__), "menu.json") + config = menu.load_configuration(config_path) - # Animation sub-menu - cmds.menuItem("Animation", - label="Animation", - tearOff=True, - subMenu=True, - parent=self._menu) + # create menu in appliction + cb_menu = launchformaya.main(title=self._menu, parent=self._parent) - cmds.menuItem("Set Defaults", command=interactive.set_defaults) + # apply configuration + menu.load_from_configuration(cb_menu, config) - cmds.setParent("..", menu=True) - - cmds.menuItem(divider=True) - - cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) + # cmds.menu(self._menu, + # label=self._menu.capitalize(), + # tearOff=True, + # parent="MayaWindow") + # + # # Modeling sub-menu + # cmds.menuItem("Modeling", + # label="Modeling", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Combine", command=interactive.combine) + # + # # Rigging sub-menu + # cmds.menuItem("Rigging", + # label="Rigging", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Auto Connect", command=interactive.auto_connect) + # cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) + # cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) + # cmds.menuItem("Clone (Special)", command=interactive.clone_special) + # cmds.menuItem("Create Follicle", command=interactive.follicle) + # + # # Animation sub-menu + # cmds.menuItem("Animation", + # label="Animation", + # tearOff=True, + # subMenu=True, + # parent=self._menu) + # + # cmds.menuItem("Set Defaults", command=interactive.set_defaults) + # + # cmds.setParent("..", menu=True) + # + # cmds.menuItem(divider=True) + # + # cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) # Allow time for uninstallation to finish. QtCore.QTimer.singleShot(100, deferred) diff --git a/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py similarity index 98% rename from colorbleed/plugins/maya/publish/_validate_unique_node_ids.py rename to colorbleed/plugins/maya/publish/validate_unique_node_ids.py index d7453a3a4c..616cabfdc4 100644 --- a/colorbleed/plugins/maya/publish/_validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -18,7 +18,7 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): import maya.cmds as cmds - uuid_attr = "mbId" + uuid_attr = "cbId" # Collect each id with their members from collections import defaultdict From e0ecc72e49c0f3ed8860b05f30eeb3055de4c6ad Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 4 Jul 2017 15:22:56 +0200 Subject: [PATCH 0045/1249] refactored publish plugins --- colorbleed/action.py | 87 +++++++++++--- colorbleed/maya/lib.py | 2 +- .../plugins/maya/publish/collect_look.py | 106 +++++++++++++----- .../plugins/maya/publish/extract_look.py | 7 +- .../publish/validate_look_members_node_ids.py | 9 +- .../publish/validate_look_members_unique.py | 22 ++-- .../publish/validate_look_node_unique_ids.py | 18 +-- .../publish/validate_naming_convention.py | 33 ++++++ .../maya/publish/validate_unique_node_ids.py | 8 +- 9 files changed, 225 insertions(+), 67 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index fef737195d..382699035e 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,10 +1,13 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import -import pyblish.api +import os +import uuid from maya import cmds +import pyblish.api + def get_errored_instances_from_context(context): @@ -34,7 +37,7 @@ def get_errored_plugins_from_data(context): plugins = list() results = context.data.get("results", []) for result in results: - if result["success"] == True: + if result["success"] is True: continue plugins.append(result["plugin"]) @@ -150,8 +153,6 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): icon = "wrench" # Icon from Awesome Icon def process(self, context, plugin): - import cbra.lib - import cbra.utils.maya.node_uuid as id_utils self.log.info("Finding bad nodes..") @@ -182,15 +183,73 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): # Parse context from current file self.log.info("Parsing current context..") - try: - current_file = context.data['currentFile'] - context = cbra.lib.parse_context(current_file) - except RuntimeError, e: - self.log.error("Can't generate UUIDs because scene isn't " - "in new-style pipeline: ".format(current_file)) - raise e + print(">>> DEBUG CONTEXT :", context) + print(">>> DEBUG CONTEXT DATA:", context.data) - # Generate and add the ids to the nodes - ids = id_utils.generate_ids(context, invalid) - id_utils.add_ids(ids) + # # Generate and add the ids to the nodes + node_ids = self.generate_ids(context, invalid) + self.apply_ids(node_ids) self.log.info("Generated ids on nodes: {0}".format(invalid)) + + def get_context(self, instance=None): + + PROJECT = os.environ["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] + SILO = os.environ["AVALON_SILO"] + LOCATION = os.getenv("AVALON_LOCATION") + + return {"project": PROJECT, + "asset": ASSET, + "silo": SILO, + "location": LOCATION} + + def generate_ids(self, context, nodes): + """Generate cb UUIDs for nodes. + + The identifiers are formatted like: + assets:character/test:bluey:46D221D9-4150-8E49-6B17-43B04BFC26B6 + + This is a concatenation of: + - entity (shots or assets) + - folders (parent hierarchy) + - asset (the name of the asset) + - uuid (unique id for node in the scene) + + Raises: + RuntimeError: When context can't be parsed of the current asset + + Returns: + dict: node, uuid dictionary + + """ + + # Make a copy of the context + data = context.copy() + + # Define folders + + node_ids = dict() + for node in nodes: + # Generate a unique ID per node + data['uuid'] = uuid.uuid4() + unique_id = "{asset}:{item}:{uuid}".format(**data) + node_ids[node] = unique_id + + return node_ids + + def apply_ids(self, node_ids): + """Apply the created unique IDs to the node + Args: + node_ids (dict): each node with a unique id + + Returns: + None + """ + + attribute = "mbId" + for node, id in node_ids.items(): + # check if node has attribute + if not cmds.attributeQuery(attribute, node=node, exists=True): + cmds.addAttr(node, longName=attribute, dataType="string") + + cmds.setAttr("{}.{}".format(node, attribute), id) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c6314049ae..175d664f7c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -456,7 +456,7 @@ def extract_alembic(file, for key, value in options.items(): if isinstance(value, (list, tuple)): for entry in value: - job_args.append("-{0} {1}".format(key=key, value=entry)) + job_args.append("-{} {}".format(key, entry)) elif isinstance(value, bool): job_args.append("-{0}".format(key)) else: diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 7ee62af5fe..72946d8588 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -37,8 +37,8 @@ def get_look_attrs(node): valid = [attr for attr in attrs if attr in SHAPE_ATTRS] result.extend(valid) - if "cbId" in result: - result.remove("cbId") + if "mbID" in result: + result.remove("mbID") return result @@ -87,7 +87,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Discover related object sets self.log.info("Gathering sets..") - self.gather_sets(instance) + sets = self.gather_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, @@ -95,9 +95,7 @@ class CollectLook(pyblish.api.InstancePlugin): absoluteName=True)]) self.log.info("Gathering set relations..") - sets = self.gather_sets(instance) for objset in sets: - self.log.debug("From %s.." % objset) content = cmds.sets(objset, query=True) objset_members = sets[objset]["members"] @@ -108,16 +106,14 @@ class CollectLook(pyblish.api.InstancePlugin): verbose) if not member_data: continue - sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end - sets = self.clean_sets(sets) - # Member attributes (shapes + transforms) + sets = self.remove_sets_without_members(sets) self.log.info("Gathering attribute changes to instance members..") - attributes = self.collect_attributes_changes(instance) + attributes = self.collect_attributes_changed(instance) looksets = cmds.ls(sets.keys(), absoluteName=True, long=True) # Store data on the instance @@ -133,41 +129,75 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Collected look for %s" % instance) def gather_sets(self, instance): + """Gather all objectSets which are of importance for publishing + + It checks if all nodes in the instance are related to any objectSet + which need to be + + Args: + instance (list): all nodes to be published + + Returns: + dict + """ # Get view sets (so we can ignore those sets later) sets = dict() view_sets = set() - model_panels = cmds.getPanel(type="modelPanel") - for panel in model_panels: + for panel in cmds.getPanel(type="modelPanel"): view_set = cmds.modelEditor(panel, query=True, viewObjects=True) if view_set: view_sets.add(view_set) for node in instance: - node_sets = self.filter_sets(node, view_sets) - if not node_sets: + related_sets = self.get_related_sets(node, view_sets) + if not related_sets: continue - for objset in node_sets: + for objset in related_sets: if objset in sets: continue + unique_id = cmds.getAttr("%s.mbID" % objset) sets[objset] = {"name": objset, - "uuid": id_utils.get_id(objset), + "uuid": unique_id, "members": list()} return sets - def filter_sets(self, node, view_sets): + def get_related_sets(self, node, view_sets): + """Get the sets which do not belong to any specific group - node_sets = cmds.listSets(object=node, extendToShape=False) or [] - if not node_sets: - return + Filters out based on: + - id attribute is NOT `pyblish.avalon.container` + - shapes and deformer shapes (alembic creates meshShapeDeformed) + - set name ends with any from a predefined list + - set in not in viewport set (isolate selected for example) + + Args: + node (str): name of the current not to check + """ + + ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] + + related_sets = cmds.listSets(object=node, extendToShape=False) + if not related_sets: + return [] + + # Ignore `avalon.container` + sets = [s for s in related_sets if + not cmds.attributeQuery("id", node=s, exists=True) or + not cmds.getAttr("%s.id" % s) in ignored] # Exclude deformer sets + # Autodesk documentation on listSets command: + # type(uint) : Returns all sets in the scene of the given + # >>> type: + # >>> 1 - all rendering sets + # >>> 2 - all deformer sets deformer_sets = cmds.listSets(object=node, extendToShape=False, type=2) or [] deformer_sets = set(deformer_sets) # optimize lookup - sets = [s for s in node_sets if s not in deformer_sets] + sets = [s for s in sets if s not in deformer_sets] # Ignore specifically named sets sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] @@ -176,16 +206,24 @@ class CollectLook(pyblish.api.InstancePlugin): # viewports) sets = [s for s in sets if s not in view_sets] - self.log.info("Found sets {0} for {1}".format(node_sets, node)) + self.log.info("Found sets %s for %s" % (related_sets, node)) return sets - def clean_sets(self, sets): + def remove_sets_without_members(self, sets): + """Remove any set which does not have any members + + Args: + sets (dict): collection if sets with data as value + + Returns: + dict + """ for objset, data in sets.items(): if not data['members']: - self.log.debug("Removing redundant set " - "information: %s" % objset) + self.log.debug("Removing redundant set information: " + "%s" % objset) sets.pop(objset) return sets @@ -218,7 +256,8 @@ class CollectLook(pyblish.api.InstancePlugin): if verbose: self.log.debug("Such as %s.." % member) - member_data = {"name": node, "uuid": id_utils.get_id(node)} + member_data = {"name": node, + "uuid": cmds.getAttr("{}.mbID".format(node, ))} # Include components information when components are assigned if components: @@ -226,7 +265,22 @@ class CollectLook(pyblish.api.InstancePlugin): return member_data - def collect_attributes_changes(self, instance): + def collect_attributes_changed(self, instance): + """Collect all userDefined attributes which have changed + + Each node gets checked for user defined attributes which have been + altered during development. Each changes gets logged in a dictionary + + [{name: node, + uuid: uuid, + attributes: {attribute: value}}] + + Args: + instance (list): all nodes which will be published + + Returns: + list + """ attributes = [] for node in instance: diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index faf1e85876..97c572565a 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -37,7 +37,8 @@ class ExtractLook(colorbleed.api.Extractor): # Remove all members of the sets so they are not included in the # exported file by accident self.log.info("Extract sets (Maya ASCII)..") - sets = instance.data["lookSets"] + lookdata = instance.data["lookData"] + sets = lookdata["sets"] # Define the texture file node remapping resource_remap = dict() @@ -71,8 +72,8 @@ class ExtractLook(colorbleed.api.Extractor): # Write the JSON data self.log.info("Extract json..") - data = {"attributes": instance.data["lookAttributes"], - "sets": instance.data["lookSetRelations"]} + data = {"attributes": lookdata["attributes"], + "sets": lookdata["relationships"]} with open(json_path, "w") as f: json.dump(data, f) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index d601b22601..819f287bf6 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,8 +1,8 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils - class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): """Validate look members have colorbleed id attributes @@ -20,7 +20,8 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Members Id Attributes' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] @staticmethod def get_invalid(instance): @@ -40,7 +41,7 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId invalid = list() for node in members: - if not id_utils.has_id(node): + if not cmds.getAttr("{}.mbID".format(node)): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 18de8e73c4..48dce8b8d7 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -5,7 +5,14 @@ from maya import cmds import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils + +def get_unique_id(node): + attr = 'cbId' + unique_id = None + has_attribute = cmds.attributeQuery(attr, node=node, exists=True) + if has_attribute: + unique_id = cmds.getAttr("{}.{}".format(node, attr)) + return unique_id class ValidateLookMembersUnique(pyblish.api.InstancePlugin): @@ -25,15 +32,16 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Members Unique' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] @staticmethod def get_invalid(instance): # Get all members from the sets members = [] - relations = instance.data["lookData"]["sets"] - for sg in relations: + relationships = instance.data["lookData"]["relationships"] + for sg in relationships: sg_members = sg['members'] sg_members = [member['name'] for member in sg_members] members.extend(sg_members) @@ -45,10 +53,9 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): # Group members per id id_nodes = defaultdict(set) for node in members: - node_id = id_utils.get_id(node) + node_id = get_unique_id(node) if not node_id: continue - id_nodes[node_id].add(node) invalid = list() @@ -61,8 +68,9 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - invalid = self.get_invalid(instance) + print self.actions + invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Members found without " "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index 738075fec5..653e8a347c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -1,9 +1,10 @@ from collections import defaultdict + +import maya.cmds as cmds + import pyblish.api import colorbleed.api -import cbra.utils.maya.node_uuid as id_utils - class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): """Validate look sets have unique colorbleed id attributes @@ -15,7 +16,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Look Id Unique Attributes' actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + colorbleed.api.RepairAction] @staticmethod def get_invalid(instance): @@ -26,13 +27,15 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): id_sets = defaultdict(list) invalid = list() for node in nodes: - id = id_utils.get_id(node) - if not id: + unique_id = None + if cmds.attributeQuery("mbId", node=node, exists=True): + unique_id = cmds.getAttr("{}.mbId".format(node)) + if not unique_id: continue - id_sets[id].append(node) + id_sets[unique_id].append(node) - for id, nodes in id_sets.iteritems(): + for unique_id, nodes in id_sets.iteritems(): if len(nodes) > 1: invalid.extend(nodes) @@ -42,7 +45,6 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): """Process all meshes""" invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Nodes found without " "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py index e69de29bb2..db4846b2bb 100644 --- a/colorbleed/plugins/maya/publish/validate_naming_convention.py +++ b/colorbleed/plugins/maya/publish/validate_naming_convention.py @@ -0,0 +1,33 @@ +import re + +import pyblish.api +import colorbleed.api + + +class ValidateNamingConvention(pyblish.api.InstancePlugin): + + label = "" + host = ["maya"] + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + invalid = [] + # todo: change pattern to company standard + pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") + + nodes = list(instance) + for node in nodes: + match = pattern.match(node) + if not match: + invalid.append(node) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + self.log.error("Found invalid naming convention. Failed noted :\n" + "%s" % invalid) diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index 616cabfdc4..8e3ccb8a43 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -1,3 +1,7 @@ +from collections import defaultdict + +import maya.cmds as cmds + import pyblish.api import colorbleed.api @@ -16,12 +20,9 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): def get_invalid_dict(instance): """Return a dictionary mapping of id key to list of member nodes""" - import maya.cmds as cmds - uuid_attr = "cbId" # Collect each id with their members - from collections import defaultdict ids = defaultdict(list) for member in instance: has_attr = cmds.attributeQuery(uuid_attr, node=member, exists=True) @@ -60,4 +61,3 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Nodes found with non-unique " "asset IDs: {0}".format(invalid)) - From 92be4387aeb5c5429f5dc78fcfc0602c6dd6d0cb Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 10 Jul 2017 11:56:10 +0200 Subject: [PATCH 0046/1249] incorpirated the dynamic menu, wip --- colorbleed/maya/menu.json | 1751 ++++++++++++++++- colorbleed/maya/menu.py | 108 +- .../publish/__collect_instance_per_item.py | 177 -- .../maya/publish/__collect_yeti_caches.py | 156 -- .../plugins/maya/publish/__extract_layout.py | 81 - .../maya/publish/__validate_layout_nodes.py | 91 - .../publish/__validate_related_node_ids.py | 83 - .../publish/__validate_unique_ids_in_item.py | 167 -- .../maya/publish/_integrate_cb_asset.py | 85 - 9 files changed, 1734 insertions(+), 965 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/__collect_instance_per_item.py delete mode 100644 colorbleed/plugins/maya/publish/__collect_yeti_caches.py delete mode 100644 colorbleed/plugins/maya/publish/__extract_layout.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_layout_nodes.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_related_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py delete mode 100644 colorbleed/plugins/maya/publish/_integrate_cb_asset.py diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 1918163f97..9c3cc40bba 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,61 +1,1696 @@ -{"Animation": [{"title" : "Script A", - "tooltip": "Script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "script", "cluster"], - "icon": "$SCRIPTMENU/resources/script_a.png", - "label": "SCR A"}, - {"title" : "Script B", - "tooltip": "Run script B", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "curves"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["test", "script", "joints"], - "icon": ""} - ], +{ + "order": [ + "Modeling", + "Rigging", + "Shading", + "Animation", + "Layout", + "Particles", + "Cleanup", + "Projects", + "Pyblish", + "Others" + ], + "Modeling": [ + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", + "sourcetype": "file", + "tags": ["modeling", "duplicate", "normalized"], + "title": "Duplicate Normalized", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", + "sourcetype": "file", + "tags": ["modeling", "transfer", "uv"], + "title": "Transfer UVs", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", + "sourcetype": "file", + "tags": ["modeling", "mirror", "symmetry"], + "title": "Mirror Symmetry", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI", + "sourcetype": "file", + "tags": ["modeling", "select", "outline", "ui"], + "title": "Select Outline UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", + "sourcetype": "file", + "tags": ["modeling", "polygon", "uvset", "delete"], + "title": "polyDeleteOtherUVSets", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", + "sourcetype": "file", + "tags": ["modeling", "combine", "polygon", "quick"], + "title": "Polygon Combine Quick", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "modeling", + "separateMeshPerShader" + ], + "title": "Separate Mesh Per Shader", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", + "sourcetype": "file", + "tags": [ + "modeling", + "polyDetachSeparate" + ], + "title": "Polygon Detach and Separate", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", + "sourcetype": "file", + "tags": ["modeling", "relax", "verts"], + "title": "Polygon Relax Vertices", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "sourcetype": "file", + "tags": [ + "modeling", + "polySelectEveryNthEdgeUI" + ], + "title": "Select Every Nth Edge" + }, + { + "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", + "sourcetype": "file", + "tags": [ + "modeling", + "djPFXUVs" + ], + "title": "dj PFX UVs", + "tooltip": "" + } + ], -"Modeling": [{"title" : "Script A", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "script", "model", "blendshapes"]}, - {"title" : "Script B", - "tooltip": "Run script B", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "normals", "model"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["math", "power", "sum"]} - ], + "Animation": [ + { + "command": "", + "sourcetype": "", + "tags": ["animation", "attributes"], + "title": "Attributes", + "tooltip": "", + "items": [ + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes"], + "title": "Copy Values", + "tooltip": "Copy attribute values" + },{ + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "connections", "incoming"], + "title": "Copy In Connections", + "tooltip": "Copy incoming connections" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "connections", "out"], + "title": "Copy Out Connections", + "tooltip": "Copy outcoming connections" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "local"], + "title": "Copy Local Transfroms", + "tooltip": "Copy local transfroms" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "matrix"], + "title": "Copy Matrix Transfroms", + "tooltip": "Copy Matrix transfroms" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "UI"], + "title": "Copy Transforms UI", + "tooltip": "Open the Copy Transforms UI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "sourcetype": "file", + "tags": ["animation", "copy", "attributes", "transforms", "UI", "simple"], + "title": "Simple Copy UI", + "tooltip": "Open the simple Copy Transforms UI" + } + ] + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "animation", + "optimize" + ], + "title": "Optimize", + "tooltip": "Optimization scripts", + "items":[ + { + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "sourcetype": "file", + "tags": ["animation", "hierarchy", "toggle", "freeze"], + "title": "Toggle Freeze Hierarchy", + "tooltip": "Freeze and unfreeze hierarchy" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "sourcetype": "file", + "tags": ["animation", "nucleus", "toggle", "parallel"], + "title": "Toggle Parallel Nucleus", + "tooltip": "Toggle parallel nucleus" + } + ] + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", + "sourcetype": "file", + "tags": [ + "animation", + "zvParentMaster" + ], + "title": "ZV Parent Master", + "tooltip": "Open ZV Parent Master UI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", + "sourcetype": "file", + "tags": ["animation", "path"], + "title": "Path Animation", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", + "sourcetype": "file", + "tags": [ + "animation", + "timeStepper" + ], + "title": "TimeStepper", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "sourcetype": "file", + "tags": ["animation", "world space", "bake"], + "title": "Bake To World Space", + "tooltip": "Select the item which need to be baked to World Space" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", + "sourcetype": "file", + "tags": ["animation", "gui", "simple", "capture"], + "title": "Capture GUI - simple", + "tooltip": "Simplified version of the Capture GUI" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", + "sourcetype": "file", + "tags": ["animation", "optimizer", "scene"], + "title": "Animation Scene Optimizer", + "tooltip": "Optimize animation in a scene" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", + "sourcetype": "file", + "tags": [ + "animation", + "poseLibrary" + ], + "title": "Pose Library", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "sourcetype": "file", + "tags": ["animation", "capture", "screenshot", "movie"], + "title": "Capture GUI", + "tooltip": "Render current camera to an image or movie" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", + "sourcetype": "file", + "tags": [ + "animation", + "key_amplifier_ui" + ], + "title": "Key Amplifier UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "sourcetype": "file", + "tags": ["animation", "curves", "scene"], + "title": "Select All Animation Curves", + "tooltip": "Select all animation curves in the scene" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "offsetSelectedObjectsUI" + ], + "title": "Offset Selected Object UI", + "tooltip": "Offset selected objects" + }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "tweenMachineUI" + ], + "title": "Tween Machine UI", + "tooltip": "" + } + ], -"Rigging": [{"title" : "Script A", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_a.py", - "sourcetype": "file", - "tags": ["test", "tool", "rig", "skeleton"]}, - {"title" : "Script B", - "tooltip": "Run script A", - "command": "$SCRIPTMENU/script_b.py", - "sourcetype": "file", - "tags": ["test", "script", "cloth", "rig", "setup"]}, - {"title" : "Script C", - "tooltip": "Run script C", - "command": "$SCRIPTMENU/script_c.py", - "sourcetype": "file", - "tags": ["test", "script", "approval"]} - ], + "Rigging": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "removeRotationAxis" + ], + "title": "removeRotationAxis", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "mirrorCurveShape" + ], + "title": "mirrorCurveShape", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedMesh" + ], + "title": "selectSkinclusterJointsFromSelectedMesh", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "channelBoxManagerUI" + ], + "title": "channelBoxManagerUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointOrientationFromCurrentRotation" + ], + "title": "setJointOrientationFromCurrentRotation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "regenerate_blendshape_targets" + ], + "title": "regenerate_blendshape_targets", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "freezeTransformToGroup" + ], + "title": "freezeTransformToGroup", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedComponents" + ], + "title": "selectSkinclusterJointsFromSelectedComponents", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "superRelativeCluster" + ], + "title": "superRelativeCluster", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "special" + ], + "title": "special", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "averageSkinWeights" + ], + "title": "averageSkinWeights", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "connectUI" + ], + "title": "connectUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "ikHandlePoleVectorLocator" + ], + "title": "ikHandlePoleVectorLocator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSegmentScaleCompensate" + ], + "title": "toggleSegmentScaleCompensate", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "addCurveBetween" + ], + "title": "addCurveBetween", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedMeshes" + ], + "title": "resetBindSelectedMeshes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "rapidRig" + ], + "title": "rapidRig", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "paintItNowUI" + ], + "title": "paintItNowUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSkinclusterDeformNormals" + ], + "title": "toggleSkinclusterDeformNormals", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setSelectedJointsOrientationZero" + ], + "title": "setSelectedJointsOrientationZero", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "groupSelected" + ], + "title": "groupSelected", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelection" + ], + "title": "simpleControllerOnSelection", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedSkinJoints" + ], + "title": "resetBindSelectedSkinJoints", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "quickSetWeightsUI" + ], + "title": "quickSetWeightsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelectionHierarchy" + ], + "title": "simpleControllerOnSelectionHierarchy", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setRotationOrderUI" + ], + "title": "setRotationOrderUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "jointOrientUI" + ], + "title": "jointOrientUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "copySkinWeightsLocal" + ], + "title": "copySkinWeightsLocal", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "tfSmoothSkinWeight" + ], + "title": "tfSmoothSkinWeight", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "characterAutorigger" + ], + "title": "characterAutorigger", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "jointsOnCurve" + ], + "title": "jointsOnCurve", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "parentScaleConstraint" + ], + "title": "parentScaleConstraint", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "rigRoss" + ], + "title": "rigRoss", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "createCenterLocator" + ], + "title": "createCenterLocator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointLabels" + ], + "title": "setJointLabels", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "cbSmoothSkinWeightUI" + ], + "title": "cbSmoothSkinWeightUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleIntermediates" + ], + "title": "toggleIntermediates", + "tooltip": "" + } + ], -"MEL": [{"title" : "Create cube", - "tooltip": "Launch character rigging tool", - "command": "polyCube -w 1 -h 1 -d 1;", - "sourcetype": "mel", - "tags": ["test", "script", "mel"]} - ] -} \ No newline at end of file + "Shading": [ + { + "command": "", + "sourcetype": "file", + "tags": ["shading", "vray"], + "title": "VRay", + "tooltip": "", + "items": [ + { + "title": "Import Proxies", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "sourcetype": "file", + "tags": ["shading", "vray", "import","proxies"], + "tooltip": "" + }, + {"title": "separator"}, + { + "title": "Select All GES", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selectAllGES.py"] + }, + { + "title": "Select All GES Under Selection", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selection","all", "GES"] + }, + {"title": "separator"}, + { + "title": "Selection To VRay Mesh", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selection","vraymesh"] + }, + { + "title": "Add VRay Round Edges Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "round edges", "attribute"] + }, + { + "title": "Add Gamma", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add gamma"] + }, + {"title": "separator"}, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "sourcetype": "file", + "title": "Select Unconnected Shader Materials", + "tags": ["shading", "vray", "select", "vraymesh", "materials", "unconnected shader slots"], + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "sourcetype": "file", + "title": "Merge Similar VRay Mesh Materials", + "tags": ["shading", "vray", "Merge","VRayMesh", "Materials"], + "tooltip": "" + }, + { + "title": "Create Two Sided Material", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "sourcetype": "file", + "tooltip": "Creates two sided material for selected material and renames it", + "tags": ["shading", "vray", "two sided", "material"] + }, + { + "title": "Create Two Sided Material For Selected", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "sourcetype": "file", + "tooltip": "Select material to create a two sided version from it", + "tags": ["shading", "vray", "Create2SidedMtlForSelectedMtl.py"] + }, + {"title": "separator"}, + { + "title": "Add OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add", "open subdiv", "attribute"] + }, + { + "title": "Remove OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "remove","opensubdiv","attributee"] + }, + {"title": "separator"}, + { + "title": "Add Subdivision Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "addVraySubdivisionAttribute"] + }, + { + "title": "Remove Subdivision Attribute.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "remove","subdivision", "attribute"] + }, + {"title": "separator"}, + { + "title": "Add Vray Object Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add", "object id"] + }, + { + "title": "Add Vray Material Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "addVrayMaterialIds.py" + ] + }, + {"title": "separator"}, + { + "title": "Set Physical DOF Depth", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "physical","DOF ","Depth"] + }, + { + "title": "Magic Vray Proxy UI", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "magicVrayProxyUI"] + } + ] + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUI.py", + "sourcetype": "file", + "tags": [ + "shading", + "LightLinkUI" + ], + "title": "Light Link UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "sourcetype": "file", + "tags": ["shading", "CLRImage", "textures", "preview"], + "title": "Set Texture Preview To CLRImage", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior", + "sourcetype": "file", + "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], + "title": "fixDefaultShaderSetBehavior", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "sourcetype": "file", + "tags": [ + "shading", + "fixSelectedShapesReferenceAssignments" + ], + "title": "Fix Shapes Reference Assignments", + "tooltip": "Select shapes to fix the reference assignments" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectLambert1Members" + ], + "title": "Select Lambert1 Members", + "tooltip": "Selects all objects which have the Lambert1 shader assigned" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectShapesWithoutShader" + ], + "title": "Select Shapes Without Shader", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "sourcetype": "file", + "tags": [ + "shading", + "fixRenderLayerOutAdjustmentErrors" + ], + "title": "Fix RenderLayerOut Adjustment Errors", + "tooltip": "" + } + ], + + "Layout": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "alignDistributeUI" + ], + "title": "alignDistributeUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "alignSimpleUI" + ], + "title": "alignSimpleUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "center_locator" + ], + "title": "center_locator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "average_locator" + ], + "title": "average_locator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "selectWithinProximityUI" + ], + "title": "selectWithinProximityUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "dupCurveUI" + ], + "title": "dupCurveUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "randomDeselectUI" + ], + "title": "randomDeselectUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "multiReferencerUI" + ], + "title": "multiReferencerUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "duplicateOffsetUI" + ], + "title": "duplicateOffsetUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "spPaint3d" + ], + "title": "spPaint3d", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "randomizeUI" + ], + "title": "randomizeUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "layout", + "distributeWithinObjectUI" + ], + "title": "distributeWithinObjectUI", + "tooltip": "" + } + ], + + "Particles": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjects" + ], + "title": "instancerToObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstances" + ], + "title": "instancerToObjectsInstances", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticlesAndInstancerCleanSource" + ], + "title": "objectsToParticlesAndInstancerCleanSource", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "particleComponentsToLocators" + ], + "title": "particleComponentsToLocators", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticlesAndInstancer" + ], + "title": "objectsToParticlesAndInstancer", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "spawnParticlesOnMesh" + ], + "title": "spawnParticlesOnMesh", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstancesWithAnimation" + ], + "title": "instancerToObjectsInstancesWithAnimation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticles" + ], + "title": "objectsToParticles", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "add_particle_cacheFile_attrs" + ], + "title": "add_particle_cacheFile_attrs", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "mergeParticleSystems" + ], + "title": "mergeParticleSystems", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "particlesToLocators" + ], + "title": "particlesToLocators", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsWithAnimation" + ], + "title": "instancerToObjectsWithAnimation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "killSelectedParticles" + ], + "title": "killSelectedParticles", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "particles", + "clearInitialState" + ], + "title": "clearInitialState", + "tooltip": "" + } + ], + + "Cleanup": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectIntermediateObjects" + ], + "title": "selectIntermediateObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "resetViewportCache" + ], + "title": "resetViewportCache", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectNonUniqueNames" + ], + "title": "selectNonUniqueNames", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "uniqifyNodeNames" + ], + "title": "uniqifyNodeNames", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectByType" + ], + "title": "selectByType", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeNamespaces" + ], + "title": "removeNamespaces", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "autoRenameFileNodes" + ], + "title": "autoRenameFileNodes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "remove_user_defined_attributes" + ], + "title": "remove_user_defined_attributes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnknownNodes" + ], + "title": "removeUnknownNodes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "colorbleedRename" + ], + "title": "colorbleedRename", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnloadedReferences" + ], + "title": "removeUnloadedReferences", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "referenceEditsUI" + ], + "title": "referenceEditsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "renameShapesToTransform" + ], + "title": "renameShapesToTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeReferencesFailedEdits" + ], + "title": "removeReferencesFailedEdits", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "reorderUI" + ], + "title": "reorderUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "pastedCleaner" + ], + "title": "pastedCleaner", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "cleanup", + "deleteGhostIntermediateObjects" + ], + "title": "deleteGhostIntermediateObjects", + "tooltip": "" + } + ], + + "Projects": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "fifa" + ], + "title": "fifa", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "beakbuds" + ], + "title": "beakbuds", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "redeemer" + ], + "title": "redeemer", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bjorn" + ], + "title": "bjorn", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "aldi" + ], + "title": "aldi", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "eneco" + ], + "title": "eneco", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "duurzame_verpakking" + ], + "title": "duurzame_verpakking", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bunch" + ], + "title": "bunch", + "tooltip": "" + } + ], + + "Pyblish": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "layout" + ], + "title": "layout", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "look" + ], + "title": "look", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "submit_to_deadline" + ], + "title": "submit_to_deadline", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "instance_creator" + ], + "title": "instance_creator", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "utilities" + ], + "title": "utilities", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "loader" + ], + "title": "loader", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "animation" + ], + "title": "animation", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "lighting" + ], + "title": "lighting", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "inventory_ui" + ], + "title": "inventory_ui", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "pyblish", + "explorer" + ], + "title": "explorer", + "tooltip": "" + } + ], + + "Others": [ + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "instanceSmartTransform" + ], + "title": "instanceSmartTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "archiveSceneUI" + ], + "title": "archiveSceneUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "getSimilarMeshes" + ], + "title": "getSimilarMeshes", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "createBoundingBoxEachSelected" + ], + "title": "createBoundingBoxEachSelected", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "curveFromPositionEveryFrame" + ], + "title": "curveFromPositionEveryFrame", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "hair" + ], + "title": "hair", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectSoftSelection" + ], + "title": "selectSoftSelection", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "instanceLeafSmartTransform" + ], + "title": "instanceLeafSmartTransform", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "randomizeUVShellsSelectedObjects" + ], + "title": "randomizeUVShellsSelectedObjects", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "centerPivotGroup" + ], + "title": "centerPivotGroup", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnSelectedFaces" + ], + "title": "locatorsOnSelectedFaces", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "display" + ], + "title": "display", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "copyDeformers" + ], + "title": "copyDeformers", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnEdgeSelectionPrompt" + ], + "title": "locatorsOnEdgeSelectionPrompt", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "yeti" + ], + "title": "yeti", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectInReferenceEditor" + ], + "title": "selectInReferenceEditor", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "selectConstrainingObject" + ], + "title": "selectConstrainingObject", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "deformerSetRelationsUI" + ], + "title": "deformerSetRelationsUI", + "tooltip": "" + }, + { + "command": "", + "sourcetype": "file", + "tags": [ + "others", + "recreateBaseNodesForAllLatticeNodes" + ], + "title": "recreateBaseNodesForAllLatticeNodes", + "tooltip": "" + } + ] +} diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index f72dc07ed9..5349284e63 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -1,90 +1,64 @@ import sys +import os +import logging +import site from avalon.vendor.Qt import QtWidgets, QtCore +import maya.cmds as cmds + self = sys.modules[__name__] self._menu = "colorbleed" -self._parent = {widget.objectName(): widget for widget in - QtWidgets.QApplication.topLevelWidgets()}.get("MayaWindow") + +# set colorbleed scripts path in environment keys +os.environ["COLORBLEED_SCRIPTS"] = r"P:\pipeline\dev\git\cbMayaScripts\cbMayaScripts" + +log = logging.getLogger(__name__) -def install(): - # from . import interactive +def deferred(): - uninstall() + # todo: replace path with server / library path + site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") - def deferred(): + from scriptsmenu import launchformaya + import scriptsmenu.scriptsmenu as menu - import site - import os + log.info("Attempting to install ...") - # todo: replace path with server / library path - site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") + # load configuration of custom menu + config_path = os.path.join(os.path.dirname(__file__), "menu.json") + config = menu.load_configuration(config_path) - from scriptsmenu import launchformaya - import scriptsmenu.scriptsmenu as menu + # hack? + parent = launchformaya._maya_main_menubar() + cb_menu = menu.ScriptsMenu(title=self._menu.title(), parent=parent) - # load configuration of custon menu - config_path = os.path.join(os.path.dirname(__file__), "menu.json") - config = menu.load_configuration(config_path) + # register modifiers + modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier + cb_menu.register_callback(modifiers, launchformaya.to_shelf) - # create menu in appliction - cb_menu = launchformaya.main(title=self._menu, parent=self._parent) - - # apply configuration - menu.load_from_configuration(cb_menu, config) - - # cmds.menu(self._menu, - # label=self._menu.capitalize(), - # tearOff=True, - # parent="MayaWindow") - # - # # Modeling sub-menu - # cmds.menuItem("Modeling", - # label="Modeling", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Combine", command=interactive.combine) - # - # # Rigging sub-menu - # cmds.menuItem("Rigging", - # label="Rigging", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Auto Connect", command=interactive.auto_connect) - # cmds.menuItem("Clone (Local)", command=interactive.clone_localspace) - # cmds.menuItem("Clone (World)", command=interactive.clone_worldspace) - # cmds.menuItem("Clone (Special)", command=interactive.clone_special) - # cmds.menuItem("Create Follicle", command=interactive.follicle) - # - # # Animation sub-menu - # cmds.menuItem("Animation", - # label="Animation", - # tearOff=True, - # subMenu=True, - # parent=self._menu) - # - # cmds.menuItem("Set Defaults", command=interactive.set_defaults) - # - # cmds.setParent("..", menu=True) - # - # cmds.menuItem(divider=True) - # - # cmds.menuItem("Auto Connect", command=interactive.auto_connect_assets) - - # Allow time for uninstallation to finish. - QtCore.QTimer.singleShot(100, deferred) + # apply configuration + menu.load_from_configuration(cb_menu, config) def uninstall(): + + log.info("Attempting to uninstall ..") app = QtWidgets.QApplication.instance() widgets = dict((w.objectName(), w) for w in app.allWidgets()) menu = widgets.get(self._menu) if menu: - menu.deleteLater() - del(menu) + try: + menu.deleteLater() + del menu + except Exception as e: + log.error(e) + + +def install(): + + uninstall() + # Allow time for uninstallation to finish. + cmds.evalDeferred(deferred) diff --git a/colorbleed/plugins/maya/publish/__collect_instance_per_item.py b/colorbleed/plugins/maya/publish/__collect_instance_per_item.py deleted file mode 100644 index 7dd70d444a..0000000000 --- a/colorbleed/plugins/maya/publish/__collect_instance_per_item.py +++ /dev/null @@ -1,177 +0,0 @@ -from collections import defaultdict - -from maya import cmds - -import cbra.utils.maya.node_uuid as node_uuid -import cbra.lib - -import pyblish.api - - -class CollectInstancePerItem(pyblish.api.ContextPlugin): - """Collect instances from the Maya scene and breaks them down per item id - - An instance is identified by having an _INST suffix - and a .family user-defined attribute. - - All other user-defined attributes of the object set - is accessible within each instance's data. - - This collector breaks the instances down to each Item member it contains, - by using the IDs on the nodes in the instance it will split up the instance - into separate instances for each unique "item" id it finds. - - Note: - - Only breaks down based on children members and ignores parent members. - - Discards members without IDs. - - """ - - order = pyblish.api.CollectorOrder + 0.1 - hosts = ["maya"] - label = "Instance per Item" - - _include_families = ["colorbleed.look"] - - def process(self, context): - - invalid = list() - - for objset in cmds.ls("*_SET", - objectsOnly=True, - type='objectSet', - long=True, - recursive=True): # Include namespace - - try: - family = cmds.getAttr("{}.family".format(objset)) - except ValueError: - self.log.error("Found: %s found, but no family." % objset) - continue - - if family not in self._include_families: - continue - - # ignore referenced sets - if cmds.referenceQuery(objset, isNodeReferenced=True): - continue - - instances = self.build_instances(context, objset) - if not instances: - - # Log special error messages when objectSet is completely - # empty (has no members) to clarify to artists the root of - # their problem. - if not cmds.sets(objset, query=True): - self.log.error("Instance objectSet has no members: " - "{}".format(objset)) - - self.log.error("No instances retrieved from objectSet: " - "{}".format(objset)) - invalid.append(objset) - - if invalid: - raise RuntimeError("Invalid instances: {}".format(invalid)) - - # Sort context based on family - context[:] = sorted( - context, key=lambda instance: instance.data("family")) - - def build_instances(self, context, objset): - """Build the instances for a single instance objectSet - - Returns: - list: The constructed instances from the objectSet. - - """ - - self.log.info("Collecting: %s" % objset) - - short_name = objset.rsplit("|", 1)[-1].rsplit(":", 1)[-1] - - # Default data - default_data = {"name": short_name[:-5], - "subset": "default"} - - # Get user data from user defined attributes - user_data = dict() - for attr in cmds.listAttr(objset, userDefined=True): - try: - value = cmds.getAttr("{}.{}".format(objset, attr)) - user_data[attr] = value - except RuntimeError: - continue - - # Maintain nested object sets - members = cmds.sets(objset, query=True) - members = cmds.ls(members, long=True) - - children = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - - # Exclude intermediate objects - children = cmds.ls(children, noIntermediate=True, long=True) - - nodes = members + children - nodes = list(set(nodes)) - - # Group nodes using ids to an Item - nodes_id = node_uuid.build_cache(nodes, include_without_ids=True) - - # Log warning for nodes without ids - if None in nodes_id: - self.log.warning("Skipping nodes without ids: " - "{}".format(nodes_id[None])) - - # ignore nodes without ids - context.data["instancePerItemNodesWithoutId"] = nodes_id.pop(None, - None) - - item_groups = defaultdict(list) - - for id, nodes in nodes_id.iteritems(): - item_id = id.rsplit(":", 1)[0] - item_groups[item_id].extend(nodes) - - instances = list() - for item_id, item_nodes in item_groups.iteritems(): - - ctx = node_uuid.parse_id("{}:fake_node_uuid".format(item_id)) - - # Use itemPath to parse full blown context using official lib - ctx = cbra.lib.parse_context(ctx['itemPath']) - - item = ctx.get('item', None) - if item is None: - self.log.info("Unparsed item id: {}".format(item_id)) - self.log.error("Item can't be parsed and seems to be " - "non-existent. Was an asset renamed? Or your" - "project set incorrectly?") - raise RuntimeError("Item not parsed. See log for description.") - - instance = context.create_instance(objset) - - # Set the related members - instance[:] = item_nodes - instance.data['setMembers'] = item_nodes - - # Set defaults and user data - instance.data.update(default_data.copy()) - instance.data.update(user_data.copy()) - - # Override the label to be clear - name = instance.data['name'] - instance.data['label'] = "{0} ({1})".format(name, item) - - # Store that the instance is collected per item - instance.data['_instancePerItem'] = True - instance.data['_itemContext'] = ctx - - assert "family" in instance.data, "No family data in instance" - assert "name" in instance.data, ("No objectSet name data " - "in instance") - - instances.append(instance) - - return instances diff --git a/colorbleed/plugins/maya/publish/__collect_yeti_caches.py b/colorbleed/plugins/maya/publish/__collect_yeti_caches.py deleted file mode 100644 index a236836cdf..0000000000 --- a/colorbleed/plugins/maya/publish/__collect_yeti_caches.py +++ /dev/null @@ -1,156 +0,0 @@ -import os -import re -import pyseq -import glob - -import pyblish.api - -from maya import cmds - - -class SeletYetiCachesAction(pyblish.api.Action): - """Select the nodes related to the collected file textures""" - - label = "Select yeti nodes" - on = "succeeded" # This action is only available on a failed plug-in - icon = "search" # Icon from Awesome Icon - - def process(self, context, plugin): - - self.log.info("Finding textures..") - - # Get the errored instances - instances = [] - for result in context.data["results"]: - instance = result["instance"] - if instance is None: - continue - - instances.append(instance) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(instances, plugin) - - # Get the texture nodes from the instances - nodes = [] - for instance in instances: - texture_nodes = instance.data['yetiCaches'].keys() - nodes.extend(texture_nodes) - - # Ensure unique - nodes = list(set(nodes)) - - if nodes: - self.log.info("Selecting nodes: %s" % ", ".join(nodes)) - cmds.select(nodes, r=True, noExpand=True) - else: - self.log.info("No nodes found.") - cmds.select(deselect=True) - - -def get_sequence(filename, pattern="%04d"): - """Get pyseq sequence from filename - - Supports negative frame ranges like (-001, 0000, 0001 and -0001, 0000, 0001). - - Arguments: - filename (str): The full path to filename containing the given pattern. - pattern (str): The pattern to swap with the variable frame number. - - Returns: - pyseq.Sequence: file sequence. - - """ - - glob_pattern = filename.replace(pattern, "*") - - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] - - return pyseq.get_sequences(files) - - -class CollectYetiCaches(pyblish.api.InstancePlugin): - """Collect used yeti caches. - - Collects the file sequences from pgYetiMaya.cacheFileName - - """ - - order = pyblish.api.CollectorOrder + 0.495 - label = 'Yeti Caches' - families = ["colorbleed.groom"] - actions = [SeletYetiCachesAction] - - TYPES = {"pgYetiMaya": "cacheFileName"} - - def process(self, instance): - - # Get textures from sets - members = instance.data("setMembers") - members = cmds.ls(members, dag=True, shapes=True, type="pgYetiMaya", - noIntermediate=True, long=True) - if not members: - raise RuntimeError("Instance appears to be empty (no members)") - - # Collect only those cache frames that are required - # If handles are required it is assumed to already be included - # in the start frame and end frames. - # (e.g. using frame handle collector) - start_frame = instance.data("startFrame") - end_frame = instance.data("endFrame") - required = set(range(int(start_frame), int(end_frame) + 1)) - - history = cmds.listHistory(members) or [] - - resources = instance.data.get("resources", []) - yeti_caches = dict() - - for node_type, attr in self.TYPES.iteritems(): - for node in cmds.ls(history, type=node_type, long=True): - - attribute = "{0}.{1}".format(node, attr) - - # Source - source = cmds.getAttr(attribute) - if not source: - self.log.error("Node does not have a file set: " - "{0}".format(node)) - - # Collect the source as expanded path because that's also - # how the attribute must be 'set' for yeti nodes. - source = os.path.realpath(cmds.workspace(expandName=source)) - - # Collect the frames we need from the sequence - sequences = get_sequence(source) - files = list() - for sequence in sequences: - for index, frame in enumerate(sequence.frames()): - if frame not in required: - continue - - item = sequence[index] - files.append(item.path) - - # Define the resource - resource = {"tags": ["maya", "yeti", "attribute"], - "node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files, # required for resources - "subfolder": "caches" # optional for resources - } - - resources.append(resource) - - # For validations - yeti_caches[node] = {"attribute": attribute, - "source": source, - "sequences": sequences} - - # Store data on instance - instance.data['yetiCaches'] = yeti_caches - instance.data['resources'] = resources diff --git a/colorbleed/plugins/maya/publish/__extract_layout.py b/colorbleed/plugins/maya/publish/__extract_layout.py deleted file mode 100644 index 823337b2e1..0000000000 --- a/colorbleed/plugins/maya/publish/__extract_layout.py +++ /dev/null @@ -1,81 +0,0 @@ -import json - -from maya import cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context -import cbra.utils.maya.layout as layout - - -def get_upstream_hierarchy_fast(nodes): - """Passed in nodes must be long names!""" - - matched = set() - parents = [] - - for node in nodes: - hierarchy = node.split("|") - num = len(hierarchy) - for x in range(1, num-1): - parent = "|".join(hierarchy[:num-x]) - if parent in parents: - break - else: - parents.append(parent) - matched.add(parent) - - return parents - - -class ExtractLayout(colorbleed.api.Extractor): - """Extract Layout as both gpuCache and Alembic""" - - label = "Layout (gpuCache & alembic)" - hosts = ["maya"] - families = ["colorbleed.layout"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - - start = instance.data.get("startFrame", 1) - end = instance.data.get("endFrame", 1) - step = instance.data.get("step", 1.0) - placeholder = instance.data.get("placeholder", False) - write_color_sets = instance.data.get("writeColorSets", False) - renderable_only = instance.data.get("renderableOnly", False) - visible_only = instance.data.get("visibleOnly", False) - - layers = instance.data.get("animLayersActive", None) - if layers: - layers = json.loads(layers) - self.log.info("Publishing with animLayers active: " - "{0}".format(layers)) - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - - # Get children hierarchy - nodes = instance.data['setMembers'] - cmds.select(nodes, r=True, hierarchy=True) - hierarchy = cmds.ls(selection=True, long=True) - - with context.evaluation("off"): - with context.no_refresh(): - with context.active_anim_layers(layers): - layout.extract_layout(hierarchy, - dir_path, - start=start, - end=end, - step=step, - placeholder=placeholder, - write_color_sets=write_color_sets, - renderable_only=renderable_only, - visible_only=visible_only) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, dir_path)) diff --git a/colorbleed/plugins/maya/publish/__validate_layout_nodes.py b/colorbleed/plugins/maya/publish/__validate_layout_nodes.py deleted file mode 100644 index fda17e4433..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_layout_nodes.py +++ /dev/null @@ -1,91 +0,0 @@ -import os - -import maya.cmds as cmds - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cb.utils.python.decorators import memorize - - -def isclose(a, b, rel_tol=1e-9, abs_tol=0.0): - return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) - - -@memorize -def is_published_path(path): - """Return whether path is from a published file""" - - # Quick check (optimization) without going through the folder - # structure - if cbra.lib.DIR_PUBLISH.lower() not in path.lower(): - return False - - try: - context = cbra.lib.parse_context(path) - except RuntimeError: - context = dict() - - return all([context.get("family", None), - context.get("subset", None), - context.get("version", None)]) - - -class ValidateLayoutNodes(pyblish.api.InstancePlugin): - """Validates that layout nodes behave to certain rules - - Gpu caches in a layout may not have sub-frame offsets, like offsets with a - value after the decimal point. (e.g. 1.45) - - Gpu caches loaded in a layout MUST come from a published source that has - family and version. - - """ - - order = colorbleed.api.ValidateContentsOrder - label = 'Layout Nodes' - families = ['colorbleed.layout'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - caches = cmds.ls(instance, type="gpuCache", long=True) - - # Validate sub-frame offsets - invalid_offsets = list() - for cache in caches: - - offset = cmds.getAttr("{}.animOffset".format(cache)) - if not isclose(offset, round(offset)): - cls.log.warning("Invalid sub-frame offset on: %s" % cache) - invalid_offsets.append(cache) - - # Validate gpuCache paths are from published files - invalid_paths = list() - for cache in caches: - path = cmds.getAttr("{}.cacheFileName".format(cache)) - path = os.path.normpath(path) - if not is_published_path(path): - cls.log.warning("GpuCache path not from published file: " - "{0} -> {1}".format(cache, path)) - invalid_paths.append(cache) - - invalid = invalid_offsets + invalid_paths - - return invalid - - def process(self, instance): - - # Clear cache only once per publish. So we store a value on - # the context on the first instance so we clear only once. - name = self.__class__.__name__ - key = "_plugin_{0}_processed".format(name) - if not instance.context.data.get(key, False): - is_published_path.cache.clear() - instance.context.data[key] = True - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_related_node_ids.py b/colorbleed/plugins/maya/publish/__validate_related_node_ids.py deleted file mode 100644 index a79138168d..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_related_node_ids.py +++ /dev/null @@ -1,83 +0,0 @@ -import pyblish.api -import colorbleed.api - -import cbra.utils.maya.node_uuid as id_utils -import cbra.lib - - -class ValidateRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have related colorbleed ids. - - An ID is 'related' if its built in the current Item. - - Note that this doesn't ensure it's from the current Task. An ID created - from `lookdev` has the same relation to the Item as one coming from others, - like `rigging` or `modeling`. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.model'] - hosts = ['maya'] - label = 'Related Id Attributes' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @classmethod - def get_invalid(cls, instance): - """Return the member nodes that are invalid""" - - context = instance.context - current_file = context.data.get('currentFile', None) - if not current_file: - raise RuntimeError("No current file information: " - "{0}".format(current_file)) - - try: - context = cbra.lib.parse_context(current_file) - except RuntimeError, e: - cls.log.error("Can't generate UUIDs because scene isn't " - "in new-style pipeline: ".format(current_file)) - raise e - - def to_item(id): - """Split the item id part from a node id""" - return id.rsplit(":", 1)[0] - - # Generate a fake id in the current context to retrieve the item - # id prefix that should match with ids on the nodes - fake_node = "__node__" - ids = id_utils.generate_ids(context, [fake_node]) - id = ids[fake_node] - item_prefix = to_item(id) - - # Take only the ids with more than one member - invalid = list() - invalid_items = set() - for member in instance: - member_id = id_utils.get_id(member) - - # skip nodes without ids - if not member_id: - continue - - if not member_id.startswith(item_prefix): - invalid.append(member) - invalid_items.add(to_item(member_id)) - - # Log invalid item ids - if invalid_items: - for item_id in sorted(invalid_items): - cls.log.warning("Found invalid item id: {0}".format(item_id)) - - return invalid - - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Nodes found with non-related " - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py b/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py deleted file mode 100644 index 55d3a14377..0000000000 --- a/colorbleed/plugins/maya/publish/__validate_unique_ids_in_item.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -from collections import defaultdict - -import pyblish.api -import colorbleed.api - -import cbra.lib -from cbra.utils.maya.abc import get_alembic_ids -from cbra.utils.maya.node_uuid import get_id - - -def get_subset_path(context): - return os.path.join(context['itemPath'], - cbra.lib.DIR_PUBLISH, - context['family'], - context['subset']) - - -class ValidateUniqueIdsInItem(pyblish.api.InstancePlugin): - """Checks whether IDs are unique across other subsets - - This ensures a model to be published can't have ids - which are already present in another subset. For example - the "default" model can't have ids present in the "high" - subset. - - Note: - This will also invalidate the instance if it contains - nodes that are present in another instance in the scene. - So ensure the instance you're publishing actually has - the correct set members. - - """ - - order = colorbleed.api.ValidateMeshOrder - families = ['colorbleed.model'] - hosts = ['maya'] - label = 'Unique Ids in Item' - actions = [colorbleed.api.SelectInvalidAction] - optional = True - - @classmethod - def iter_invalid(cls, instance): - - verbose = instance.data.get("verbose", False) - - def _get_instance_ids(instance): - """Collect ids in an instance""" - nodes_per_id = defaultdict(list) - for node in instance: - node_id = get_id(node) - if node_id: - nodes_per_id[node_id].append(node) - return nodes_per_id - - nodes_per_id = _get_instance_ids(instance) - if not nodes_per_id: - return - - ids_lookup = set(nodes_per_id.keys()) - - instance_context = instance.data["instanceContext"] - instance_subset = instance.data['subset'] - - assert instance_context, "Instance must have 'instanceContext' data" - assert instance_subset, "Instance must have 'subset' data" - - subsets_checked = set() - subsets_checked.add(instance_subset) # we can skip this subset - - # Compare with all other *currently publishing instances* - # of family 'model' for this item - for other_instance in instance.context: - if other_instance is instance: - continue - - if other_instance.data['subset'] == instance_subset: - cls.log.error("Another instance has the same subset? " - "This should never happen.") - - if other_instance.data['family'] != "model": - continue - - if other_instance.data['instanceContext']['item'] != \ - instance_context['item']: - cls.log.error("Also publishing model for other item? " - "This should never happen.") - continue - other_ids = _get_instance_ids(other_instance).keys() - - # Perform comparison - intersection = ids_lookup.intersection(other_ids) - if intersection: - for node_id in intersection: - nodes = nodes_per_id[node_id] - for node in nodes: - yield node - - # Those that are invalid don't need to be checked again - ids_lookup.difference_update(other_ids) - - if not ids_lookup: - # Once we have no ids to check for anymore we can already - # return - return - - subsets_checked.add(other_instance.data['subset']) - - # Compare with all previously *published instances* - # of family 'model' for this item - ctx = instance_context.copy() - ctx['family'] = "model" - - published_subsets = cbra.lib.list_subsets(ctx) - published_subsets = set(x for x in published_subsets if - x != instance_subset) - - for published_subset in published_subsets: - ctx['subset'] = published_subset - ctx['subsetPath'] = get_subset_path(ctx) - - versions = cbra.lib.list_versions(ctx) - version = cbra.lib.find_highest_version(versions) - if not version: - cls.log.debug("No published version for " - "'model': {0}".format(published_subset)) - continue - - ctx['currentVersion'] = version - publish_abc = cbra.lib.get_filepath(ctx) + ".abc" - - if not os.path.exists(publish_abc): - cls.log.error("Published file to compare with does not exist: " - "{0}".format(publish_abc)) - continue - - if verbose: - cls.log.debug("Comparing with: {0}".format(publish_abc)) - - abc_ids = set(get_alembic_ids(publish_abc).values()) - - # Perform comparison - intersection = ids_lookup.intersection(abc_ids) - if intersection: - for node_id in intersection: - nodes = nodes_per_id[node_id] - for node in nodes: - yield node - - # Those that are invalid don't need to be checked again - ids_lookup.difference_update(abc_ids) - - if not ids_lookup: - # Once we have no ids to check for anymore we can already - # return - return - - return - - @classmethod - def get_invalid(cls, instance): - return list(cls.iter_invalid(instance)) - - def process(self, instance): - """Process all meshes""" - if any(self.iter_invalid(instance)): - raise RuntimeError("Invalid nodes found in {0}".format(instance)) diff --git a/colorbleed/plugins/maya/publish/_integrate_cb_asset.py b/colorbleed/plugins/maya/publish/_integrate_cb_asset.py deleted file mode 100644 index 61535e58db..0000000000 --- a/colorbleed/plugins/maya/publish/_integrate_cb_asset.py +++ /dev/null @@ -1,85 +0,0 @@ -import os -import shutil - -import pyblish_cb.lib -import colorbleed.api - - -class IntegrateColorbleedAssets(colorbleed.api.Integrator): - """Name and position instances on disk for instances. - - The files are transferred from the `extractDir` to the - computed `integrationDir` and are renamed as: - - "{item}_{family}_{subsetName}_{version}.{ext}" - - Assumptions: - - Each extracted instance is 1 file (no directories) - - """ - - label = "Asset" - families = ["colorbleed.model", "colorbleed.rig", "colorbleed.pointcache", - "colorbleed.proxy", "colorbleed.layout", "colorbleed.look", - "colorbleed.vrmeshReplace", "colorbleed.review", - "colorbleed.instancer", "colorbleed.camera", - "colorbleed.mayaAscii", - "colorbleed.furYeti"] - - def process(self, instance): - super(IntegrateColorbleedAssets, self).process(instance) - - self.log.info("Integrating {0}..".format(instance)) - - integration = pyblish_cb.lib.compute_integration(instance) - - # Store reference for upcoming plug-ins - instance.data["integrationDir"] = integration['path'] - instance.data["integrationVersion"] = integration['versionNum'] - - path = integration['path'] - data = integration.copy() - - try: - if not os.path.exists(path): - os.makedirs(path) - - self.log.info("Moving files to %s" % path) - - tmp = instance.data["extractDir"] - for src in (os.path.join(tmp, f) for f in os.listdir(tmp)): - - self.log.debug("Integrating %s" % src) - - # Source must be a file - if not os.path.isfile(src): - self.log.error("Source is not a file: {0}".format(src)) - continue - - # TODO(marcus): Consider files without extension - data["ext"] = src.split(".", 1)[-1] - dst = os.path.join(path, "{item}_" - "{family}_" - "{subsetName}_" - "{version}.{ext}".format( - **data)) - - # Copy - self.log.info("\"%s\" -> \"%s\"" % (src, dst)) - shutil.copyfile(src, dst) - - self.log.debug("Tagged %s with .Version" % path) - - try: - subset_path = os.path.dirname(path) - cquery.tag(subset_path, ".Subset") - self.log.debug("Tagged %s with .Subset" % subset_path) - except cquery.TagExists: - pass - - except OSError as e: - # If, for whatever reason, this instance did not get written. - instance.data.pop("integrationDir") - raise e - - except Exception as e: - raise Exception("An unknown error occured: %s" % e) From 9dfc7f97e83d3665ff128e4041d7bde770770fab Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 10 Jul 2017 11:57:28 +0200 Subject: [PATCH 0047/1249] added naming convention validator --- colorbleed.bat | 4 ---- colorbleed/plugins/maya/publish/validate_naming_convention.py | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 colorbleed.bat diff --git a/colorbleed.bat b/colorbleed.bat deleted file mode 100644 index 3bf3119e6a..0000000000 --- a/colorbleed.bat +++ /dev/null @@ -1,4 +0,0 @@ -:: Set paths to ensure plugins have access to the inhouse tools - -set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cb; -set PYTHONPATH=%PYTHONPATH%;P:\pipeline\dev\git\cbra; diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py index db4846b2bb..7dbf9ad3f7 100644 --- a/colorbleed/plugins/maya/publish/validate_naming_convention.py +++ b/colorbleed/plugins/maya/publish/validate_naming_convention.py @@ -7,6 +7,7 @@ import colorbleed.api class ValidateNamingConvention(pyblish.api.InstancePlugin): label = "" + families = ["colorbleed.model"] host = ["maya"] actions = [colorbleed.api.SelectInvalidAction] From b4b9960d01400dbe714bf5109aa69617537e8368 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:40:11 +0200 Subject: [PATCH 0048/1249] added list of accepted and ignored file types --- colorbleed/filetypes.py | 2 + .../plugins/maya/create/colorbleed_texture.py | 0 colorbleed/plugins/maya/publish/_debug.py | 4 -- ...t_look_textures.py => collect_textures.py} | 9 +-- .../plugins/maya/publish/extract_textures.py | 0 colorbleed/plugins/publish/pre_integrate.py | 68 +++++++++++++++++++ 6 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 colorbleed/filetypes.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_texture.py rename colorbleed/plugins/maya/publish/{collect_look_textures.py => collect_textures.py} (94%) create mode 100644 colorbleed/plugins/maya/publish/extract_textures.py create mode 100644 colorbleed/plugins/publish/pre_integrate.py diff --git a/colorbleed/filetypes.py b/colorbleed/filetypes.py new file mode 100644 index 0000000000..fbefeff9be --- /dev/null +++ b/colorbleed/filetypes.py @@ -0,0 +1,2 @@ +accepted_images_types = [".png", ".jpg", ".tga", ".tiff"] +ignored_images_types = [".pds"] diff --git a/colorbleed/plugins/maya/create/colorbleed_texture.py b/colorbleed/plugins/maya/create/colorbleed_texture.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/_debug.py b/colorbleed/plugins/maya/publish/_debug.py index 7919b1fda1..ef40126be1 100644 --- a/colorbleed/plugins/maya/publish/_debug.py +++ b/colorbleed/plugins/maya/publish/_debug.py @@ -13,7 +13,3 @@ class DebugPlugin(pyblish.api.InstancePlugin): self.log("\n\n----------------------") self.log("Instance") pprint.pprint(instance) - - self.log("\n\n----------------------") - self.log("Instance.data") - pprint.pprint(instance.data) diff --git a/colorbleed/plugins/maya/publish/collect_look_textures.py b/colorbleed/plugins/maya/publish/collect_textures.py similarity index 94% rename from colorbleed/plugins/maya/publish/collect_look_textures.py rename to colorbleed/plugins/maya/publish/collect_textures.py index a6636b24a2..a55e274da0 100644 --- a/colorbleed/plugins/maya/publish/collect_look_textures.py +++ b/colorbleed/plugins/maya/publish/collect_textures.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -import cb.utils.maya.shaders as shader +import cb.utils.maya.shaders as shaders TAGS = ["maya", "attribute", "look"] TAGS_LOOKUP = set(TAGS) @@ -74,7 +74,7 @@ class CollectLookTextures(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.498 label = 'Textures' - families = ["colorbleed.look"] + families = ["colorbleed.texture"] actions = [SelectTextureNodesAction] def process(self, instance): @@ -134,19 +134,20 @@ class CollectLookTextures(pyblish.api.InstancePlugin): # paths as the computed patterns source = computed_source.replace("\\", "/") - files = shader.get_file_node_files(node) + files = shaders.get_file_node_files(node) if not files: self.log.error("File node does not have a texture set: " "{0}".format(node)) return # Define the resource + # todo: find a way to generate the destination for the publisher resource = {"tags": TAGS[:], "node": node, "attribute": attribute, "source": source, # required for resources "files": files, # required for resources - "subfolder": "textures" # optional for resources + "subfolder": "textures", # optional for resources } return resource diff --git a/colorbleed/plugins/maya/publish/extract_textures.py b/colorbleed/plugins/maya/publish/extract_textures.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/publish/pre_integrate.py b/colorbleed/plugins/publish/pre_integrate.py new file mode 100644 index 0000000000..cb5eaa0656 --- /dev/null +++ b/colorbleed/plugins/publish/pre_integrate.py @@ -0,0 +1,68 @@ +import os +import logging +import shutil + +import maya.cmds as cmds + +import pyblish.api + +log = logging.getLogger(__name__) + + +class PostIntegrateAsset(pyblish.api.InstancePlugin): + """Resolve any dependency issies + + This plug-in resolves any paths which, if not updated might break + the published file. + + The order of families is important, when working with lookdev you want to + first publish the texture, update the texture paths in the nodes and then + publish the shading network. Same goes for file dependent assets. + """ + + label = "Post Intergrate Asset" + order = pyblish.api.IntegratorOrder + 0.1 + families = ["colorbleed.lookdev", "colorbleed.texture"] + + def process(self, instance): + + # get needed variables + version_folder = instance.data["versionFolder"] + family = instance.data["family"] + resources = instance.data("resources", []) + + self.log.info("Running post process for {}".format(instance.name)) + + if family == "colorbleed.texture": + texture_folder = os.path.join(version_folder, "textures") + self.remap_resource_nodes(resources, folder=texture_folder) + + elif family == "colorbleed.lookdev": + self.remap_resource_nodes(resources) + + # self.log.info("Removing temporary files and folders ...") + # if passed: + # stagingdir = instance.data["stagingDir"] + # shutil.rmtree(stagingdir) + + def remap_resource_nodes(self, resources, folder=None): + + self.log.info("Updating resource nodes ...") + for resource in resources: + source = resource["source"] + if folder: + fname = os.path.basename(source) + fpath = os.path.join(folder, fname) + else: + fpath = source + + node_attr = resource["attribute"] + print("UPDATING {} -> {}".format(node_attr, fpath)) + cmds.setAttr(node_attr, fpath, type="string") + + self.log.info("Saving file ...") + + cmds.file(save=True, type="mayaAscii") + + def remap_yeti_resource_nodes(self, node,): + pass From dcf6c21bfaacc9e44228099656f55541e884bbf3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:41:23 +0200 Subject: [PATCH 0049/1249] fixed bug in save callback --- colorbleed/maya/__init__.py | 80 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 7e1da1a190..6e74cd0980 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,9 +1,12 @@ import os import site +import uuid -from avalon import api as avalon +from avalon import maya, api as avalon from pyblish import api as pyblish +from maya import cmds + from . import menu PARENT_DIR = os.path.dirname(__file__) @@ -28,6 +31,11 @@ def install(): menu.install() + print("Installing callbacks ... ") + avalon.on("init", on_init) + avalon.on("new", on_new) + avalon.on("save", on_save) + def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) @@ -35,3 +43,73 @@ def uninstall(): avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) menu.uninstall() + + +def _set_uuid(node): + """Add cbId to `node` + Unless one already exists. + """ + + asset = os.environ["AVALON_ASSET"] + attr = "{0}.cbId".format(node) + + if not cmds.objExists(attr): + cmds.addAttr(node, longName="cbId", dataType="string") + _, uid = str(uuid.uuid4()).rsplit("-", 1) + cb_uid = "{}:{}:{}".format(asset, node, uid) + + cmds.setAttr(attr, cb_uid, type="string") + + +def on_init(): + avalon.logger.info("Running callback on init..") + + maya.commands.reset_frame_range() + maya.commands.reset_resolution() + + +def on_new(): + avalon.logger.info("Running callback on new..") + + # Load dependencies + cmds.loadPlugin("AbcExport.mll", quiet=True) + cmds.loadPlugin("AbcImport.mll", quiet=True) + + maya.commands.reset_frame_range() + maya.commands.reset_resolution() + + +def on_save(): + """Automatically add IDs to new nodes + Any transform of a mesh, without an existing ID, + is given one automatically on file save. + """ + + avalon.logger.info("Running callback on save..") + + defaults = ["initialShadingGroup", "initialParticleSE"] + + # the default items which always want to have an ID + types = ["mesh", "shadingEngine", "file"] + + # the items which need to pass the id to their parent + subtypes = ["nurbsCurve"] + + nodes = (set(cmds.ls(type=types, long=True)) - + set(cmds.ls(long=True, readOnly=True)) - + set(cmds.ls(long=True, lockedNodes=True))) + + transforms = set() + for n in cmds.ls(type=subtypes, long=True): + for r in cmds.listRelatives(n, parent=True, fullPath=True): + transforms.add(r) + + # merge transforms and nodes in one set to make sure every item + # is unique + nodes |= transforms + + # remove any default nodes + for node in nodes: + if node in defaults: + continue + _set_uuid(node) From 535ce23a75936cedd6e24dc1adf4297cd0fc8fd0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:44:34 +0200 Subject: [PATCH 0050/1249] cosmetics --- colorbleed/__init__.py | 3 ++- colorbleed/api.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index a158fcbee9..f29597a297 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -8,10 +8,11 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") def install(): publish_path = os.path.join(PLUGINS_DIR, "publish") - print("Registering global plug-ins..") + pyblish.register_plugin_path(publish_path) def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) + diff --git a/colorbleed/api.py b/colorbleed/api.py index 33924cb02b..531a63a50d 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -21,7 +21,9 @@ from .action import ( ) all = [ + # plugin classes "Extractor", + # ordering "ValidatePipelineOrder", "ValidateContentsOrder", "ValidateSceneOrder", From 91c3e6fcbfb37d719e5488690e1108ccad686b80 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:45:40 +0200 Subject: [PATCH 0051/1249] added a extra functions --- colorbleed/maya/lib.py | 106 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 175d664f7c..0ae2d442c8 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -7,9 +7,10 @@ import logging import os import json -log = logging.getLogger(__name__) +from maya import cmds, mel -from maya import cmds + +log = logging.getLogger(__name__) def maintained_selection(arg=None): @@ -249,6 +250,78 @@ def get_current_renderlayer(): return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) +@contextlib.contextmanager +def no_undo(flush=False): + """Disable the undo queue during the context + + Arguments: + flush (bool): When True the undo queue will be emptied when returning + from the context losing all undo history. Defaults to False. + + """ + original = cmds.undoInfo(query=True, state=True) + keyword = 'state' if flush else 'stateWithoutFlush' + + try: + cmds.undoInfo(**{keyword: False}) + yield + finally: + cmds.undoInfo(**{keyword: original}) + + +def polyConstraint(components, *args, **kwargs): + """Return the list of *components* with the constraints applied. + + A wrapper around Maya's `polySelectConstraint` to retrieve its results as + a list without altering selections. For a list of possible constraints + see `maya.cmds.polySelectConstraint` documentation. + + Arguments: + components (list): List of components of polygon meshes + + Returns: + list: The list of components filtered by the given constraints. + + """ + + kwargs.pop('mode', None) + + with no_undo(flush=False): + print("la") + with maintained_selection(): + print("po") + # Apply constraint using mode=2 (current and next) so + # it applies to the selection made before it; because just + # a `maya.cmds.select()` call will not trigger the constraint. + with reset_polySelectConstraint(): + print("do") + cmds.select(components, r=1) + cmds.polySelectConstraint(*args, mode=2, **kwargs) + result = cmds.ls(selection=True) + + return result + + +@contextlib.contextmanager +def reset_polySelectConstraint(reset=True): + """Context during which the given polyConstraint settings are disabled. + + The original settings are restored after the context. + + """ + + original = cmds.polySelectConstraint(query=True, stateString=True) + + try: + if reset: + # Reset all parameters + mel.eval("resetPolySelectConstraint;") + cmds.polySelectConstraint(disable=True) + yield + finally: + mel.eval(original) + + def is_visible(node, displayLayer=True, intermediateObject=True, @@ -343,7 +416,7 @@ _alembic_options = { def extract_alembic(file, startFrame=None, endFrame=None, - selection= True, + selection=True, uvWrite= True, eulerFilter= True, dataFormat="ogawa", @@ -482,3 +555,30 @@ def extract_alembic(file, log.debug("Extracted Alembic to: %s", file) return file + + +def maya_temp_folder(): + scene_dir = os.path.dirname(cmds.file(query=True, sceneName=True)) + tmp_dir = os.path.abspath(os.path.join(scene_dir, "..", "tmp")) + if not os.path.isdir(tmp_dir): + os.makedirs(tmp_dir) + + return tmp_dir + + +def remap_resource_nodes(resources, folder=None): + + log.info("Updating resource nodes ...") + for resource in resources: + source = resource["source"] + if folder: + fname = os.path.basename(source) + fpath = os.path.join(folder, fname) + else: + fpath = source + + node_attr = resource["attribute"] + cmds.setAttr(node_attr, fpath, type="string") + + log.info("Saving file ...") + cmds.file(save=True, type="mayaAscii") From dfe134ebbc6cf084f4a6111bd9ee369b465519ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:46:34 +0200 Subject: [PATCH 0052/1249] Integrated script menu at launch, menu config is wip --- colorbleed/maya/menu.json | 727 ++++++++++++++++++++++++-------------- colorbleed/maya/menu.py | 6 +- 2 files changed, 475 insertions(+), 258 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9c3cc40bba..9258617399 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,5 +1,6 @@ { "order": [ + "main", "Modeling", "Rigging", "Shading", @@ -11,46 +12,90 @@ "Pyblish", "Others" ], + "main": [ + { + "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", + "sourcetype": "file", + "title": "Version Up", + "tooltip": "Incremental save with a specific format" + }, + { + "command": "", + "sourcetype": "file", + "title": "Save scene", + "tooltip": "Save the scene based on a specific format" + }, + { + "title": "separator" + } + ], "Modeling": [ { "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", "sourcetype": "file", - "tags": ["modeling", "duplicate", "normalized"], + "tags": [ + "modeling", + "duplicate", + "normalized" + ], "title": "Duplicate Normalized", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", "sourcetype": "file", - "tags": ["modeling", "transfer", "uv"], + "tags": [ + "modeling", + "transfer", + "uv" + ], "title": "Transfer UVs", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", "sourcetype": "file", - "tags": ["modeling", "mirror", "symmetry"], + "tags": [ + "modeling", + "mirror", + "symmetry" + ], "title": "Mirror Symmetry", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI", "sourcetype": "file", - "tags": ["modeling", "select", "outline", "ui"], + "tags": [ + "modeling", + "select", + "outline", + "ui" + ], "title": "Select Outline UI", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", "sourcetype": "file", - "tags": ["modeling", "polygon", "uvset", "delete"], + "tags": [ + "modeling", + "polygon", + "uvset", + "delete" + ], "title": "polyDeleteOtherUVSets", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", "sourcetype": "file", - "tags": ["modeling", "combine", "polygon", "quick"], + "tags": [ + "modeling", + "combine", + "polygon", + "quick" + ], "title": "Polygon Combine Quick", "tooltip": "" }, @@ -77,7 +122,11 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", "sourcetype": "file", - "tags": ["modeling", "relax", "verts"], + "tags": [ + "modeling", + "relax", + "verts" + ], "title": "Polygon Relax Vertices", "tooltip": "" }, @@ -101,60 +150,104 @@ "tooltip": "" } ], - "Animation": [ { "command": "", "sourcetype": "", - "tags": ["animation", "attributes"], + "tags": [ + "animation", + "attributes" + ], "title": "Attributes", "tooltip": "", "items": [ { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes"], + "tags": [ + "animation", + "copy", + "attributes" + ], "title": "Copy Values", "tooltip": "Copy attribute values" - },{ + }, + { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "connections", "incoming"], + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "incoming" + ], "title": "Copy In Connections", "tooltip": "Copy incoming connections" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "connections", "out"], + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "out" + ], "title": "Copy Out Connections", "tooltip": "Copy outcoming connections" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "local"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "local" + ], "title": "Copy Local Transfroms", "tooltip": "Copy local transfroms" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "matrix"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "matrix" + ], "title": "Copy Matrix Transfroms", "tooltip": "Copy Matrix transfroms" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "UI"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI" + ], "title": "Copy Transforms UI", "tooltip": "Open the Copy Transforms UI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", "sourcetype": "file", - "tags": ["animation", "copy", "attributes", "transforms", "UI", "simple"], + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI", + "simple" + ], "title": "Simple Copy UI", "tooltip": "Open the simple Copy Transforms UI" } @@ -169,18 +262,28 @@ ], "title": "Optimize", "tooltip": "Optimization scripts", - "items":[ + "items": [ { "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", "sourcetype": "file", - "tags": ["animation", "hierarchy", "toggle", "freeze"], + "tags": [ + "animation", + "hierarchy", + "toggle", + "freeze" + ], "title": "Toggle Freeze Hierarchy", "tooltip": "Freeze and unfreeze hierarchy" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", "sourcetype": "file", - "tags": ["animation", "nucleus", "toggle", "parallel"], + "tags": [ + "animation", + "nucleus", + "toggle", + "parallel" + ], "title": "Toggle Parallel Nucleus", "tooltip": "Toggle parallel nucleus" } @@ -199,7 +302,10 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", "sourcetype": "file", - "tags": ["animation", "path"], + "tags": [ + "animation", + "path" + ], "title": "Path Animation", "tooltip": "" }, @@ -216,21 +322,34 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "sourcetype": "file", - "tags": ["animation", "world space", "bake"], + "tags": [ + "animation", + "world space", + "bake" + ], "title": "Bake To World Space", "tooltip": "Select the item which need to be baked to World Space" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", "sourcetype": "file", - "tags": ["animation", "gui", "simple", "capture"], + "tags": [ + "animation", + "gui", + "simple", + "capture" + ], "title": "Capture GUI - simple", "tooltip": "Simplified version of the Capture GUI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "sourcetype": "file", - "tags": ["animation", "optimizer", "scene"], + "tags": [ + "animation", + "optimizer", + "scene" + ], "title": "Animation Scene Optimizer", "tooltip": "Optimize animation in a scene" }, @@ -247,7 +366,12 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", "sourcetype": "file", - "tags": ["animation", "capture", "screenshot", "movie"], + "tags": [ + "animation", + "capture", + "screenshot", + "movie" + ], "title": "Capture GUI", "tooltip": "Render current camera to an image or movie" }, @@ -264,7 +388,11 @@ { "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", "sourcetype": "file", - "tags": ["animation", "curves", "scene"], + "tags": [ + "animation", + "curves", + "scene" + ], "title": "Select All Animation Curves", "tooltip": "Select all animation curves in the scene" }, @@ -289,385 +417,366 @@ "tooltip": "" } ], - "Rigging": [ { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", + "sourcetype": "python", "tags": [ "rigging", - "removeRotationAxis" + "addCurveBetween", + "python" ], - "title": "removeRotationAxis", - "tooltip": "" + "title": "Add Curve Between" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", + "sourcetype": "python", "tags": [ "rigging", - "mirrorCurveShape" + "averageSkinWeights", + "python" ], - "title": "mirrorCurveShape", - "tooltip": "" + "title": "Average Skin Weights" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", + "sourcetype": "python", "tags": [ "rigging", - "selectSkinclusterJointsFromSelectedMesh" + "cbSmoothSkinWeightUI", + "python" ], - "title": "selectSkinclusterJointsFromSelectedMesh", - "tooltip": "" + "title": "CB Smooth Skin Weight UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", + "sourcetype": "python", "tags": [ "rigging", - "channelBoxManagerUI" + "channelBoxManagerUI", + "python" ], - "title": "channelBoxManagerUI", - "tooltip": "" + "title": "Channel Box Manager UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", + "sourcetype": "python", "tags": [ "rigging", - "setJointOrientationFromCurrentRotation" + "characterAutorigger", + "python" ], - "title": "setJointOrientationFromCurrentRotation", - "tooltip": "" + "title": "Character Auto Rigger" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", + "sourcetype": "python", "tags": [ "rigging", - "regenerate_blendshape_targets" + "connectUI", + "python" ], - "title": "regenerate_blendshape_targets", - "tooltip": "" + "title": "Connect UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", + "sourcetype": "python", "tags": [ "rigging", - "freezeTransformToGroup" + "copySkinWeightsLocal", + "python" ], - "title": "freezeTransformToGroup", - "tooltip": "" + "title": "Copy Skin Weights Local" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", + "sourcetype": "python", "tags": [ "rigging", - "selectSkinclusterJointsFromSelectedComponents" + "createCenterLocator", + "python" ], - "title": "selectSkinclusterJointsFromSelectedComponents", - "tooltip": "" + "title": "Create Center Locator" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", + "sourcetype": "python", "tags": [ "rigging", - "superRelativeCluster" + "freezeTransformToGroup", + "python" ], - "title": "superRelativeCluster", - "tooltip": "" + "title": "Freeze Transform To Group" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", + "sourcetype": "python", "tags": [ "rigging", - "special" + "groupSelected", + "python" ], - "title": "special", - "tooltip": "" + "title": "Group Selected" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", + "sourcetype": "python", "tags": [ "rigging", - "averageSkinWeights" + "ikHandlePoleVectorLocator", + "python" ], - "title": "averageSkinWeights", - "tooltip": "" + "title": "IK Handle Pole Vector Locator" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", + "sourcetype": "python", "tags": [ "rigging", - "connectUI" + "jointOrientUI", + "python" ], - "title": "connectUI", - "tooltip": "" + "title": "Joint Orient UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", + "sourcetype": "python", "tags": [ "rigging", - "ikHandlePoleVectorLocator" + "jointsOnCurve", + "python" ], - "title": "ikHandlePoleVectorLocator", - "tooltip": "" + "title": "Joints On Curve" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", + "sourcetype": "python", "tags": [ "rigging", - "toggleSegmentScaleCompensate" + "mirrorCurveShape", + "python" ], - "title": "toggleSegmentScaleCompensate", - "tooltip": "" + "title": "Mirror Curve Shape" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", + "sourcetype": "python", "tags": [ "rigging", - "addCurveBetween" + "paintItNowUI", + "python" ], - "title": "addCurveBetween", - "tooltip": "" + "title": "Paint It Now UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", + "sourcetype": "python", "tags": [ "rigging", - "resetBindSelectedMeshes" + "parentScaleConstraint", + "python" ], - "title": "resetBindSelectedMeshes", - "tooltip": "" + "title": "Parent Scale Constraint" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", + "sourcetype": "python", "tags": [ "rigging", - "rapidRig" + "quickSetWeightsUI", + "python" ], - "title": "rapidRig", - "tooltip": "" + "title": "Quick Set Weights UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", + "sourcetype": "python", "tags": [ "rigging", - "paintItNowUI" + "rapidRig", + "python" ], - "title": "paintItNowUI", - "tooltip": "" + "title": "rapidRig" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", + "sourcetype": "python", "tags": [ "rigging", - "toggleSkinclusterDeformNormals" + "regenerate_blendshape_targets", + "python" ], - "title": "toggleSkinclusterDeformNormals", - "tooltip": "" + "title": "Regenerate Blendshape Targets" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", + "sourcetype": "python", "tags": [ "rigging", - "setSelectedJointsOrientationZero" + "removeRotationAxis", + "python" ], - "title": "setSelectedJointsOrientationZero", - "tooltip": "" + "title": "Remove Rotation Axis" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", + "sourcetype": "python", "tags": [ "rigging", - "groupSelected" + "resetBindSelectedMeshes", + "python" ], - "title": "groupSelected", - "tooltip": "" + "title": "Reset Bind Selected Meshes" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "python", "tags": [ "rigging", - "simpleControllerOnSelection" + "resetBindSelectedSkinJoints", + "python" ], - "title": "simpleControllerOnSelection", - "tooltip": "" + "title": "Reset Bind Selected Skin Joints" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "python", "tags": [ "rigging", - "resetBindSelectedSkinJoints" + "selectSkinclusterJointsFromSelectedComponents", + "python" ], - "title": "resetBindSelectedSkinJoints", - "tooltip": "" + "title": "Select Skincluster Joints From Selected Components" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "python", "tags": [ "rigging", - "quickSetWeightsUI" + "selectSkinclusterJointsFromSelectedMesh", + "python" ], - "title": "quickSetWeightsUI", - "tooltip": "" + "title": "Select Skincluster Joints From Selected Mesh" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "python", "tags": [ "rigging", - "simpleControllerOnSelectionHierarchy" + "setJointLabels", + "python" ], - "title": "simpleControllerOnSelectionHierarchy", - "tooltip": "" + "title": "setJointLabels" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "python", "tags": [ "rigging", - "setRotationOrderUI" + "setJointOrientationFromCurrentRotation", + "python" ], - "title": "setRotationOrderUI", - "tooltip": "" + "title": "Set Joint Orientation From Current Rotation" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "python", "tags": [ "rigging", - "jointOrientUI" + "setRotationOrderUI", + "python" ], - "title": "jointOrientUI", - "tooltip": "" + "title": "Set Rotation Order UI" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "python", "tags": [ "rigging", - "copySkinWeightsLocal" + "setSelectedJointsOrientationZero", + "python" ], - "title": "copySkinWeightsLocal", - "tooltip": "" + "title": "Set Selected Joints Orientation Zero" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", + "sourcetype": "python", "tags": [ "rigging", - "tfSmoothSkinWeight" + "simpleControllerOnSelection", + "python" ], - "title": "tfSmoothSkinWeight", - "tooltip": "" + "title": "Simple Controller On Selection" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", + "sourcetype": "python", "tags": [ "rigging", - "characterAutorigger" + "simpleControllerOnSelectionHierarchy", + "python" ], - "title": "characterAutorigger", - "tooltip": "" + "title": "Simple Controller On Selection Hierarchy" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", + "sourcetype": "python", "tags": [ "rigging", - "jointsOnCurve" + "superRelativeCluster", + "python" ], - "title": "jointsOnCurve", - "tooltip": "" + "title": "Super Relative Cluster" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", + "sourcetype": "python", "tags": [ "rigging", - "parentScaleConstraint" + "tfSmoothSkinWeight", + "python" ], - "title": "parentScaleConstraint", - "tooltip": "" + "title": "TF Smooth Skin Weight" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", + "sourcetype": "python", "tags": [ "rigging", - "rigRoss" + "toggleIntermediates", + "python" ], - "title": "rigRoss", - "tooltip": "" + "title": "Toggle Intermediates" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", + "sourcetype": "python", "tags": [ "rigging", - "createCenterLocator" + "toggleSegmentScaleCompensate", + "python" ], - "title": "createCenterLocator", - "tooltip": "" + "title": "Toggle Segment Scale Compensate" }, { - "command": "", - "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", + "sourcetype": "python", "tags": [ "rigging", - "setJointLabels" + "toggleSkinclusterDeformNormals", + "python" ], - "title": "setJointLabels", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "rigging", - "cbSmoothSkinWeightUI" - ], - "title": "cbSmoothSkinWeightUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleIntermediates" - ], - "title": "toggleIntermediates", - "tooltip": "" + "title": "Toggle Skincluster Deform Normals" } ], - "Shading": [ { "command": "", "sourcetype": "file", - "tags": ["shading", "vray"], + "tags": [ + "shading", + "vray" + ], "title": "VRay", "tooltip": "", "items": [ @@ -675,59 +784,107 @@ "title": "Import Proxies", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", "sourcetype": "file", - "tags": ["shading", "vray", "import","proxies"], + "tags": [ + "shading", + "vray", + "import", + "proxies" + ], "tooltip": "" }, - {"title": "separator"}, + { + "title": "separator" + }, { "title": "Select All GES", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selectAllGES.py"] + "tags": [ + "shading", + "vray", + "selectAllGES.py" + ] }, { "title": "Select All GES Under Selection", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selection","all", "GES"] + "tags": [ + "shading", + "vray", + "selection", + "all", + "GES" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Selection To VRay Mesh", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "selection","vraymesh"] + "tags": [ + "shading", + "vray", + "selection", + "vraymesh" + ] }, { "title": "Add VRay Round Edges Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "round edges", "attribute"] + "tags": [ + "shading", + "vray", + "round edges", + "attribute" + ] }, { "title": "Add Gamma", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add gamma"] + "tags": [ + "shading", + "vray", + "add gamma" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", "sourcetype": "file", "title": "Select Unconnected Shader Materials", - "tags": ["shading", "vray", "select", "vraymesh", "materials", "unconnected shader slots"], + "tags": [ + "shading", + "vray", + "select", + "vraymesh", + "materials", + "unconnected shader slots" + ], "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", "sourcetype": "file", "title": "Merge Similar VRay Mesh Materials", - "tags": ["shading", "vray", "Merge","VRayMesh", "Materials"], + "tags": [ + "shading", + "vray", + "Merge", + "VRayMesh", + "Materials" + ], "tooltip": "" }, { @@ -735,52 +892,94 @@ "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", "sourcetype": "file", "tooltip": "Creates two sided material for selected material and renames it", - "tags": ["shading", "vray", "two sided", "material"] + "tags": [ + "shading", + "vray", + "two sided", + "material" + ] }, { "title": "Create Two Sided Material For Selected", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", "sourcetype": "file", "tooltip": "Select material to create a two sided version from it", - "tags": ["shading", "vray", "Create2SidedMtlForSelectedMtl.py"] + "tags": [ + "shading", + "vray", + "Create2SidedMtlForSelectedMtl.py" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add OpenSubdiv Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add", "open subdiv", "attribute"] + "tags": [ + "shading", + "vray", + "add", + "open subdiv", + "attribute" + ] }, { "title": "Remove OpenSubdiv Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "remove","opensubdiv","attributee"] + "tags": [ + "shading", + "vray", + "remove", + "opensubdiv", + "attributee" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add Subdivision Attribute", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "addVraySubdivisionAttribute"] + "tags": [ + "shading", + "vray", + "addVraySubdivisionAttribute" + ] }, { "title": "Remove Subdivision Attribute.py", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "remove","subdivision", "attribute"] + "tags": [ + "shading", + "vray", + "remove", + "subdivision", + "attribute" + ] + }, + { + "title": "separator" }, - {"title": "separator"}, { "title": "Add Vray Object Ids", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "add", "object id"] + "tags": [ + "shading", + "vray", + "add", + "object id" + ] }, { "title": "Add Vray Material Ids", @@ -793,20 +992,32 @@ "addVrayMaterialIds.py" ] }, - {"title": "separator"}, + { + "title": "separator" + }, { "title": "Set Physical DOF Depth", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "physical","DOF ","Depth"] + "tags": [ + "shading", + "vray", + "physical", + "DOF ", + "Depth" + ] }, { "title": "Magic Vray Proxy UI", "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", "sourcetype": "file", "tooltip": "", - "tags": ["shading", "vray", "magicVrayProxyUI"] + "tags": [ + "shading", + "vray", + "magicVrayProxyUI" + ] } ] }, @@ -823,14 +1034,24 @@ { "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", "sourcetype": "file", - "tags": ["shading", "CLRImage", "textures", "preview"], + "tags": [ + "shading", + "CLRImage", + "textures", + "preview" + ], "title": "Set Texture Preview To CLRImage", "tooltip": "" }, { "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior", "sourcetype": "file", - "tags": ["shading", "fix", "DefaultShaderSet", "Behavior"], + "tags": [ + "shading", + "fix", + "DefaultShaderSet", + "Behavior" + ], "title": "fixDefaultShaderSetBehavior", "tooltip": "" }, @@ -875,7 +1096,6 @@ "tooltip": "" } ], - "Layout": [ { "command": "", @@ -998,7 +1218,6 @@ "tooltip": "" } ], - "Particles": [ { "command": "", @@ -1141,7 +1360,6 @@ "tooltip": "" } ], - "Cleanup": [ { "command": "", @@ -1314,7 +1532,6 @@ "tooltip": "" } ], - "Projects": [ { "command": "", @@ -1397,7 +1614,6 @@ "tooltip": "" } ], - "Pyblish": [ { "command": "", @@ -1500,7 +1716,6 @@ "tooltip": "" } ], - "Others": [ { "command": "", diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 5349284e63..4b09f87f1b 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -30,9 +30,11 @@ def deferred(): config_path = os.path.join(os.path.dirname(__file__), "menu.json") config = menu.load_configuration(config_path) - # hack? + # get Maya menubar parent = launchformaya._maya_main_menubar() - cb_menu = menu.ScriptsMenu(title=self._menu.title(), parent=parent) + cb_menu = menu.ScriptsMenu(objectName=self._menu, + title=self._menu.title(), + parent=parent) # register modifiers modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier From 7e243228f4abbf6a5b3cc5ac4c8938d2ff02e2b9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:53:38 +0200 Subject: [PATCH 0053/1249] changed order numder --- colorbleed/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 5adf7dbe24..63a8b0e278 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -1,6 +1,7 @@ import tempfile import pyblish.api + ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 @@ -17,7 +18,7 @@ class Extractor(pyblish.api.InstancePlugin): """ - order = pyblish.api.ExtractorOrder + order = 2.0 def staging_dir(self, instance): """Provide a temporary directory in which to store extracted files From bd240bb7267db8c54f1ae55727b2e9f20538f8c1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 11:54:55 +0200 Subject: [PATCH 0054/1249] added texture instances, look no longer has suffix INST --- colorbleed/plugins/maya/create/colorbleed_look.py | 2 +- colorbleed/plugins/maya/create/colorbleed_texture.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index b93673b523..3cfb7423c3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -7,7 +7,7 @@ class CreateLook(avalon.maya.Creator): name = "lookDefault" label = "Look Dev" - family = "colorbleed.look" + family = "colorbleed.lookdev" def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_texture.py b/colorbleed/plugins/maya/create/colorbleed_texture.py index e69de29bb2..1080f1c6a1 100644 --- a/colorbleed/plugins/maya/create/colorbleed_texture.py +++ b/colorbleed/plugins/maya/create/colorbleed_texture.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateTexture(avalon.maya.Creator): + """Polygonal geometry for animation""" + + name = "texturesDefault" + label = "Textures" + family = "colorbleed.texture" \ No newline at end of file From 22ffd263fe4fab69971442a381bfdfcf1a8b256a Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 20 Jul 2017 15:51:02 +0200 Subject: [PATCH 0055/1249] shot name is now set as asset in instance when rig is loaded in animation --- colorbleed/plugins/maya/load/load_rig.py | 36 ++++++++++++++++-------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 1332e63f4f..0138f56b76 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -1,4 +1,7 @@ +import os + from maya import cmds + from avalon import api @@ -18,12 +21,13 @@ class RigLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): + print("debug : {}\n{}\n".format(name, namespace)) nodes = cmds.file(self.fname, namespace=namespace, reference=True, returnNewNodes=True, groupReference=True, - groupName=namespace + ":" + name) + groupName="{}:{}".format(namespace, name)) # Store for post-process self[:] = nodes @@ -38,6 +42,7 @@ class RigLoader(api.Loader): # Better register this keyword, so that it can be used # elsewhere, such as in the Integrator plug-in, # without duplication. + output = next( (node for node in self if node.endswith("out_SET")), None) @@ -48,18 +53,25 @@ class RigLoader(api.Loader): assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." + # To ensure the asset under which is published is actually the shot + # not the asset to which the rig belongs to. + current_task = os.environ["AVALON_TASK"] + asset_name = context["asset"]["name"] + if current_task == "animate": + asset = "{}".format(os.environ["AVALON_ASSET"]) + else: + asset = "{}".format(asset_name) + with maya.maintained_selection(): cmds.select([output, controls], noExpand=True) - dependencies = [context["representation"]["_id"]] - asset = context["asset"]["name"] + "_" - # TODO(marcus): Hardcoding the family here, better separate this. - maya.create( - name=maya.unique_name(asset, suffix="_SET"), - asset=context["asset"]["name"], - family="colorbleed.animation", - options={"useSelection": True}, - data={ - "dependencies": " ".join(str(d) for d in dependencies) - }) + dependencies = [context["representation"]["_id"]] + dependencies = " ".join(str(d) for d in dependencies) + unique_name = maya.unique_name(asset_name, suffix="_SET") + + maya.create(name=unique_name, + asset=asset, + family="colorbleed.animation", + options={"useSelection": True}, + data={"dependencies": dependencies}) From bf8edf8d6e671c4b78cba3bf9b79be067a884522 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 21 Jul 2017 11:08:22 +0200 Subject: [PATCH 0056/1249] added cbId to attribute argumen --- ..._zero_edge.py => _validate_mesh_non_zero_edge.py} | 12 +++++------- colorbleed/plugins/maya/publish/extract_alembic.py | 9 ++++++++- 2 files changed, 13 insertions(+), 8 deletions(-) rename colorbleed/plugins/maya/publish/{validate_mesh_non_zero_edge.py => _validate_mesh_non_zero_edge.py} (83%) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py similarity index 83% rename from colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py rename to colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py index b706913592..02019da523 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py @@ -2,8 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api - -from pyblish_cb.tmp_utils import polyConstraint +import colorbleed.maya.lib as lib class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @@ -41,17 +40,16 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): edges = ['{0}.e[*]'.format(node) for node in meshes] # Filter by constraint on edge length - invalid = polyConstraint(edges, - t=0x8000, # type=edge - length=1, - lengthbound=(0, cls.__tolerance)) + invalid = lib.polyConstraint(edges, + t=0x8000, # type=edge + length=1, + lengthbound=(0, cls.__tolerance)) return invalid def process(self, instance): """Process all meshes""" invalid = self.get_invalid(instance) - if invalid: raise RuntimeError("Meshes found with zero " "edge length: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 2bde0bfe9a..83a87e7a6b 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -1,6 +1,8 @@ import os import copy +import maya.cmds as cmds + import avalon.maya import colorbleed.api from colorbleed.maya.lib import extract_alembic @@ -26,9 +28,14 @@ class ExtractAlembic(colorbleed.api.Extractor): path = os.path.join(parent_dir, filename) options = copy.deepcopy(instance.data) - options['selection'] = True + options["attr"] = ["cbId"] + # force elect items to ensure all items get exported by Alembic + members = instance.data("setMembers") + cmds.select(members) with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): extract_alembic(file=path, **options) + + cmds.select(clear=True) From c8cfc53f11e52324072c7cd5b4566dffbd8a3d7d Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 21 Jul 2017 14:20:26 +0200 Subject: [PATCH 0057/1249] removed unsupported flag from extract alembic call, added cbId to attr flag --- .../plugins/maya/publish/extract_alembic.py | 2 ++ .../plugins/maya/publish/extract_animation.py | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 83a87e7a6b..c3abe59244 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -33,6 +33,8 @@ class ExtractAlembic(colorbleed.api.Extractor): # force elect items to ensure all items get exported by Alembic members = instance.data("setMembers") + print members + cmds.select(members) with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index 48cb4711dc..f0241a79b4 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -1,4 +1,10 @@ +import os + +from maya import cmds + +import avalon.maya import colorbleed.api +from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAnimation(colorbleed.api.Extractor): @@ -14,10 +20,6 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): families = ["colorbleed.animation"] def process(self, instance): - import os - from maya import cmds - import avalon.maya - from colorbleed.maya.lib import extract_alembic # Collect the out set nodes out_sets = [node for node in instance if node.endswith("out_SET")] @@ -27,11 +29,13 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): out_set = out_sets[0] nodes = cmds.sets(out_set, query=True) - # Include all descendents + # Include all descendants nodes += cmds.listRelatives(nodes, allDescendents=True, fullPath=True) or [] + print("Exporting {} as alembic".format(nodes)) + # Collect the start and end including handles start = instance.data["startFrame"] end = instance.data["endFrame"] @@ -52,13 +56,10 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) - extract_alembic(file=path, **{ - "selection": True, - "frameRange": (start, end), - "writeVisibility": True, - "writeUV": True, - "step": instance.data.get("step", 1.0), - "attributePrefix": ("mb",) - }) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **{"step": instance.data.get("step", 1.0), + "attr": ["cbId"]}) self.log.info("Extracted {} to {}".format(instance, dirname)) From 6a6335c7137992c79187c71f70172c7224e3ef59 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:27:53 +0200 Subject: [PATCH 0058/1249] added assign_look logic --- colorbleed/maya/lib.py | 254 +++++++++++++++++- ...bic_attrs.py => _collect_alembic_attrs.py} | 0 2 files changed, 250 insertions(+), 4 deletions(-) rename colorbleed/plugins/maya/publish/{collect_alembic_attrs.py => _collect_alembic_attrs.py} (100%) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 0ae2d442c8..b4e4dec544 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1,17 +1,26 @@ """Standalone helper functions""" import re -import contextlib -from collections import OrderedDict -import logging import os +import bson import json +import logging +import contextlib +from collections import OrderedDict, defaultdict + +from avalon import maya, io from maya import cmds, mel log = logging.getLogger(__name__) +project = io.find_one({"type": "project", + "name": os.environ["AVALON_PROJECT"]}, + projection={"config.template.publish": True, + "_id": False}) +TEMPLATE = project["config"]["template"]["publish"] + def maintained_selection(arg=None): if arg is not None: @@ -522,7 +531,7 @@ def extract_alembic(file, valid_types = _alembic_options[key] if not isinstance(value, valid_types): raise TypeError("Alembic option unsupported type: " - "{0} (expected {1}}".format(value, valid_types)) + "{0} (expected {1})".format(value, valid_types)) # Format the job string from options job_args = list() @@ -549,6 +558,8 @@ def extract_alembic(file, log.debug("Extracting Alembic with job arguments: %s", job_str) # Perform extraction + print("Alembic Job Arguments : {}".format(job_str)) + cmds.AbcExport(j=job_str, verbose=verbose) if verbose: @@ -582,3 +593,238 @@ def remap_resource_nodes(resources, folder=None): log.info("Saving file ...") cmds.file(save=True, type="mayaAscii") + + +def _get_id(node): + """ + Get the `cbId` attribute of the given node + Args: + node (str): the name of the node to retrieve the attribute from + + Returns: + str + + """ + + if node is None: + return + + try: + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + except Exception as e: + log.warning(e) + return + + return attribute_value + + +def filter_by_id(nodes, uuids): + """Filter all nodes which match the UUIDs + + Args: + nodes (list): collection of nodes to check + uuids (list): a list of UUIDs which are linked to the shader + + Returns: + list: matching nodes + """ + + filtered_nodes = [] + for node in nodes: + if node is None: + continue + + if not cmds.attributeQuery("cbId", node=node, exists=True): + continue + + # Deformed shaped + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + if attribute_value not in uuids: + continue + + filtered_nodes.append(node) + + return filtered_nodes + + +def get_representation_file(representation, template=TEMPLATE): + """ + Rebuild the filepath of the representation's context + Args: + representation (dict): data of the registered in the database + template (str): the template to fill + + Returns: + str + + """ + context = representation["context"].copy() + context["root"] = os.environ["AVALON_ROOT"] + return template.format(**context) + + +def list_looks(asset_id): + """Return all look subsets for the given asset + + This assumes all look subsets start with "look*" in their names. + """ + + # # get all subsets with look leading in + # the name associated with the asset + subset = io.find({"parent": asset_id, + "type": "subset", + "name": {"$regex": "look*"}}) + + return list(subset) + + +def assign_look_by_version(nodes, version_id): + """Assign nodes a specific published look version by id. + + This assumes the nodes correspond with the asset. + + Args: + nodes(list): nodes to assign look to + version_id (bson.ObjectId) + + Returns: + None + """ + + # get representations of shader file and relationships + shader_file = io.find_one({"type": "representation", + "parent": version_id, + "name": "ma"}) + + shader_relations = io.find_one({"type": "representation", + "parent": version_id, + "name": "json"}) + + # Load file + shader_filepath = get_representation_file(shader_file) + shader_relation = get_representation_file(shader_relations) + + try: + existing_reference = cmds.file(shader_filepath, + query=True, + referenceNode=True) + except RuntimeError as e: + if e.message.rstrip() != "Cannot find the scene file.": + raise + + log.info("Loading lookdev for the first time..") + + # Define namespace + assetname = shader_file['context']['asset'] + ns_assetname = "{}_".format(assetname) + namespace = maya.unique_namespace(ns_assetname, + format="%03d", + suffix="_look") + + # Reference the look file + with maya.maintained_selection(): + shader_nodes = cmds.file(shader_filepath, + namespace=namespace, + reference=True, + returnNewNodes=True) + else: + log.info("Reusing existing lookdev..") + shader_nodes = cmds.referenceQuery(existing_reference, nodes=True) + + # Assign relationships + with open(shader_relation, "r") as f: + relationships = json.load(f) + + apply_shaders(relationships, shader_nodes, nodes) + + +def assign_look(nodes, subset="lookDefault"): + """Assigns a look to a node. + + Optimizes the nodes by grouping by asset id and finding + related subset by name. + + Args: + nodes (list): all nodes to assign the look to + subset (str): name of the subset to find + """ + + # Group all nodes per asset id + grouped = defaultdict(list) + for node in nodes: + colorbleed_id = cmds.getAttr("{}.cbId".format(node)) + asset_id = colorbleed_id.split(":")[0] + grouped[asset_id].append(node) + + for asset_id, asset_nodes in grouped.items(): + # create objectId for database + asset_id = bson.ObjectId(asset_id) + subset = io.find_one({"type": "subset", + "name": subset, + "parent": asset_id}) + + assert subset, "No subset found for {}".format(asset_id) + + # get last version + version = io.find_one({"parent": subset['_id'], + "type": "version", + "data.families": + {"$in":["colorbleed.lookdev"]} + }, + sort=[("name", -1)], + projection={"_id": True}) + + log.debug("Assigning look '{}' <{}> to nodes: {}".format(subset, + version, + asset_nodes)) + + assign_look_by_version(asset_nodes, version['_id']) + + +def apply_shaders(relationships, shader_nodes, nodes): + """Apply all shaders to the nodes based on the relationship data + + Args: + relationships (dict): shader to node relationships + shader_nodes (list): shader network nodes + nodes (list): nodes to assign to + + Returns: + None + """ + + shader_sets = relationships.get("sets", []) + shading_engines = cmds.ls(shader_nodes, type="shadingEngine", long=True) + assert len(shading_engines) > 0, ("Error in retrieving shading engine " + "from reference") + + # Pre-filter nodes and shader nodes + nodes_by_id = defaultdict(list) + shader_nodes_by_id = defaultdict(list) + for node in nodes: + _id = _get_id(node) + nodes_by_id[_id].append(node) + + for shader_node in shader_nodes: + _id = _get_id(shader_node) + shader_nodes_by_id[_id].append(shader_node) + + # get all nodes which we need to link per shader + for shader_set in shader_sets: + # collect shading engine + uuid = shader_set["uuid"] + shading_engine = shader_nodes_by_id.get(uuid, []) + assert len(shading_engine) == 1, ("Could not find the correct " + "shading engine with cbId " + "'{}'".format(uuid)) + + # collect members + filtered_nodes = list() + for member in shader_set["members"]: + member_uuid = member["uuid"] + members = nodes_by_id.get(member_uuid, []) + filtered_nodes.extend(members) + + cmds.sets(filtered_nodes, forceElement=shading_engine[0]) diff --git a/colorbleed/plugins/maya/publish/collect_alembic_attrs.py b/colorbleed/plugins/maya/publish/_collect_alembic_attrs.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_alembic_attrs.py rename to colorbleed/plugins/maya/publish/_collect_alembic_attrs.py From e0fa549f0b3ab5797a9a12760ec8dea12ea97f83 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:32:56 +0200 Subject: [PATCH 0059/1249] separated database logic from move logic --- colorbleed/plugins/publish/integrate_asset.py | 258 +++--------------- colorbleed/plugins/publish/pre_integrate.py | 257 ++++++++++++++--- 2 files changed, 264 insertions(+), 251 deletions(-) diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py index 457f0cfa67..7db26fdaab 100644 --- a/colorbleed/plugins/publish/integrate_asset.py +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -1,13 +1,15 @@ +import json import os import errno import shutil -from pprint import pformat + +import colorbleed.maya.lib as lib import pyblish.api -from avalon import api, io +from avalon import io -class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): +class IntegrateAsset(pyblish.api.InstancePlugin): """Write to files and metadata This plug-in exposes your data to others by encapsulating it @@ -30,225 +32,53 @@ class IntegrateMindbenderAsset(pyblish.api.InstancePlugin): """ label = "Integrate Asset" - order = pyblish.api.IntegratorOrder - families = [ - "colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.camera", - "colorbleed.lookdev", - "colorbleed.historyLookdev", - "colorbleed.group" - ] + order = pyblish.api.IntegratorOrder + 0.1 + families = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.texture", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, instance): - # Required environment variables - PROJECT = os.environ["AVALON_PROJECT"] - ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - SILO = os.environ["AVALON_SILO"] - LOCATION = os.getenv("AVALON_LOCATION") - # todo(marcus): avoid hardcoding labels in the integrator - representation_labels = {".ma": "Maya Ascii", - ".source": "Original source file", - ".abc": "Alembic"} + # get needed data + traffic = instance.data["traffic"] + representations = instance.data["representations"] + version_folder = instance.data["versionFolder"] + family = instance.data["family"] + resources = instance.data("resources", []) - context = instance.context - # Atomicity - # - # Guarantee atomic publishes - each asset contains - # an identical set of members. - # __ - # / o - # / \ - # | o | - # \ / - # o __/ - # - assert all(result["success"] for result in context.data["results"]), ( - "Atomicity not held, aborting.") + self.log.info("Registering {} items".format(len(representations))) + io.insert_many(representations) - # Assemble - # - # | - # v - # ---> <---- - # ^ - # | - # - stagingdir = instance.data.get("stagingDir") - assert stagingdir, ("Incomplete instance \"%s\": " - "Missing reference to staging area." % instance) + # moving files + for src_dest in traffic: + src, dest = src_dest + self.log.info("Copying file .. {} -> {}".format(src, dest)) + self.copy_file(src, dest) - self.log.debug("Establishing staging directory @ %s" % stagingdir) + if family == "colorbleed.texture": + try: + lib.remap_resource_nodes(resources, folder=version_folder) + except Exception as e: + self.log.error(e) - project = io.find_one({"type": "project"}) - asset = io.find_one({"name": ASSET}) + if family == "colorbleed.lookdev": + try: + tmp_dir = lib.maya_temp_folder() + resource_file = os.path.join(tmp_dir, "resources.json") + with open(resource_file, "r") as f: + resources = json.load(f) + lib.remap_resource_nodes(resources) + except Exception as e: + self.log.error(e) - assert all([project, asset]), ("Could not find current project or " - "asset '%s'" % ASSET) - - subset = self.get_subset(asset, instance) - - # get next version - latest_version = io.find_one({"type": "version", - "parent": subset["_id"]}, - {"name": True}, - sort=[("name", -1)]) - - next_version = 1 - if latest_version is not None: - next_version += latest_version["name"] - - self.log.debug("Next version: %i" % next_version) - - version_data = self.create_version_data(context, instance) - version = self.create_version(subset=subset, - version_number=next_version, - locations=[LOCATION], - data=version_data) - - self.log.debug("Creating version: %s" % pformat(version)) - version_id = io.insert_one(version).inserted_id - - # Write to disk - # _ - # | | - # _| |_ - # ____\ / - # |\ \ / \ - # \ \ v \ - # \ \________. - # \|________| - # - template_data = { - "root": api.registered_root(), - "project": PROJECT, - "silo": SILO, - "asset": ASSET, - "subset": subset["name"], - "version": version["name"], - } - - template_publish = project["config"]["template"]["publish"] - - for fname in os.listdir(stagingdir): - name, ext = os.path.splitext(fname) - template_data["representation"] = ext[1:] - - src = os.path.join(stagingdir, fname) - dst = template_publish.format(**template_data) - - # Backwards compatibility - if fname == ".metadata.json": - dirname = os.path.dirname(dst) - dst = os.path.join(dirname, ".metadata.json") - - self.log.info("Copying %s -> %s" % (src, dst)) - - # copy source to destination (library) - self.copy_file(src, dst) - - representation = { - "schema": "avalon-core:representation-2.0", - "type": "representation", - "parent": version_id, - "name": ext[1:], - "data": {"label": representation_labels.get(ext)}, - "dependencies": instance.data.get("dependencies", "").split(), - - # Imprint shortcut to context - # for performance reasons. - "context": { - "project": PROJECT, - "asset": ASSET, - "silo": SILO, - "subset": subset["name"], - "version": version["name"], - "representation": ext[1:] - } - } - - io.insert_one(representation) - - self.log.info("Successfully integrated \"%s\" to \"%s\"" % ( - instance, dst)) - - def get_subset(self, asset, instance): - - subset = io.find_one({"type": "subset", - "parent": asset["_id"], - "name": instance.data["subset"]}) - - if subset is None: - subset_name = instance.data["subset"] - self.log.info("Subset '%s' not found, creating.." % subset_name) - - _id = io.insert_one({ - "schema": "avalon-core:subset-2.0", - "type": "subset", - "name": subset_name, - "data": {}, - "parent": asset["_id"] - }).inserted_id - - subset = io.find_one({"_id": _id}) - - return subset - - def create_representation(self): - pass - - def create_version(self, subset, version_number, locations, data=None): - """ Copy given source to destination - - Arguments: - subset (dict): the registered subset of the asset - version_number (int): the version number - locations (list): the currently registered locations - """ - # Imprint currently registered location - version_locations = [location for location in locations if - location is not None] - - return {"schema": "avalon-core:version-2.0", - "type": "version", - "parent": subset["_id"], - "name": version_number, - "locations": version_locations, - "data": data} - - def create_version_data(self, context, instance): - """ - Create the data collection for th version - Args: - context (object): the current context - instance(object): the current instance being published - - Returns: - dict: the required information with instance.data as key - """ - - families = [] - current_families = instance.data.get("families", list()) - instance_family = instance.data.get("family", None) - - families += current_families - if instance_family is not None: - families.append(instance_family) - - # create relative source path for DB - relative_path = os.path.relpath(context.data["currentFile"], - api.registered_root()) - source = os.path.join("{root}", relative_path).replace("\\", "/") - - version_data = {"families": families, - "time": context.data["time"], - "author": context.data["user"], - "source": source, - "comment": context.data.get("comment")} - - return dict(instance.data, **version_data) + self.log.info("Removing temporary files and folders ...") + stagingdir = instance.data["stagingDir"] + shutil.rmtree(stagingdir) def copy_file(self, src, dst): """ Copy given source to destination diff --git a/colorbleed/plugins/publish/pre_integrate.py b/colorbleed/plugins/publish/pre_integrate.py index cb5eaa0656..a6c798914d 100644 --- a/colorbleed/plugins/publish/pre_integrate.py +++ b/colorbleed/plugins/publish/pre_integrate.py @@ -1,15 +1,15 @@ import os import logging -import shutil - -import maya.cmds as cmds import pyblish.api +from avalon import api, io +import colorbleed.filetypes as filetypes + log = logging.getLogger(__name__) -class PostIntegrateAsset(pyblish.api.InstancePlugin): +class PreIntegrateAsset(pyblish.api.InstancePlugin): """Resolve any dependency issies This plug-in resolves any paths which, if not updated might break @@ -20,49 +20,232 @@ class PostIntegrateAsset(pyblish.api.InstancePlugin): publish the shading network. Same goes for file dependent assets. """ - label = "Post Intergrate Asset" - order = pyblish.api.IntegratorOrder + 0.1 - families = ["colorbleed.lookdev", "colorbleed.texture"] + label = "Pre Intergrate Asset" + order = pyblish.api.IntegratorOrder + families = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.texture", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, instance): - # get needed variables - version_folder = instance.data["versionFolder"] - family = instance.data["family"] - resources = instance.data("resources", []) + # Required environment variables + PROJECT = os.environ["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] + SILO = os.environ["AVALON_SILO"] + LOCATION = os.getenv("AVALON_LOCATION") - self.log.info("Running post process for {}".format(instance.name)) + # todo(marcus): avoid hardcoding labels in the integrator + representation_labels = {".ma": "Maya Ascii", + ".source": "Original source file", + ".abc": "Alembic"} - if family == "colorbleed.texture": - texture_folder = os.path.join(version_folder, "textures") - self.remap_resource_nodes(resources, folder=texture_folder) + context = instance.context + # Atomicity + # + # Guarantee atomic publishes - each asset contains + # an identical set of members. + # __ + # / o + # / \ + # | o | + # \ / + # o __/ + # + assert all(result["success"] for result in context.data["results"]), ( + "Atomicity not held, aborting.") - elif family == "colorbleed.lookdev": - self.remap_resource_nodes(resources) + # Assemble + # + # | + # v + # ---> <---- + # ^ + # | + # + stagingdir = instance.data.get("stagingDir") + assert stagingdir, ("Incomplete instance \"%s\": " + "Missing reference to staging area." % instance) - # self.log.info("Removing temporary files and folders ...") - # if passed: - # stagingdir = instance.data["stagingDir"] - # shutil.rmtree(stagingdir) + # extra check if stagingDir actually exists and is available - def remap_resource_nodes(self, resources, folder=None): + self.log.debug("Establishing staging directory @ %s" % stagingdir) - self.log.info("Updating resource nodes ...") - for resource in resources: - source = resource["source"] - if folder: - fname = os.path.basename(source) - fpath = os.path.join(folder, fname) - else: - fpath = source + project = io.find_one({"type": "project"}) + asset = io.find_one({"name": ASSET}) - node_attr = resource["attribute"] - print("UPDATING {} -> {}".format(node_attr, fpath)) - cmds.setAttr(node_attr, fpath, type="string") + assert all([project, asset]), ("Could not find current project or " + "asset '%s'" % ASSET) - self.log.info("Saving file ...") + subset = self.get_subset(asset, instance) - cmds.file(save=True, type="mayaAscii") + # get next version + latest_version = io.find_one({"type": "version", + "parent": subset["_id"]}, + {"name": True}, + sort=[("name", -1)]) - def remap_yeti_resource_nodes(self, node,): - pass + next_version = 1 + if latest_version is not None: + next_version += latest_version["name"] + + self.log.debug("Next version: %i" % next_version) + + version_data = self.create_version_data(context, instance) + version = self.create_version(subset=subset, + version_number=next_version, + locations=[LOCATION], + data=version_data) + + self.log.debug("Creating version ...") + version_id = io.insert_one(version).inserted_id + + # Write to disk + # _ + # | | + # _| |_ + # ____\ / + # |\ \ / \ + # \ \ v \ + # \ \________. + # \|________| + # + root = api.registered_root() + template_data = {"root": root, + "project": PROJECT, + "silo": SILO, + "asset": ASSET, + "subset": subset["name"], + "version": version["name"]} + + template_publish = project["config"]["template"]["publish"] + + representations = [] + traffic = [] + staging_content = os.listdir(stagingdir) + for v, fname in enumerate(staging_content): + + name, ext = os.path.splitext(fname) + template_data["representation"] = ext[1:] + + src = os.path.join(stagingdir, fname) + dst = template_publish.format(**template_data) + if v == 0: + instance.data["versionFolder"] = os.path.dirname(dst) + + # Files to copy as if or to specific folder + if ext in filetypes.accepted_images_types: + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, fname) + + # Backwards compatibility + if fname == ".metadata.json": + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, fname) + + # copy source to destination (library) + traffic.append([src, dst]) + + representation = { + "schema": "avalon-core:representation-2.0", + "type": "representation", + "parent": version_id, + "name": ext[1:], + "data": {"label": representation_labels.get(ext)}, + "dependencies": instance.data.get("dependencies", "").split(), + + # Imprint shortcut to context + # for performance reasons. + "context": { + "project": PROJECT, + "asset": ASSET, + "silo": SILO, + "subset": subset["name"], + "version": version["name"], + "representation": ext[1:] + } + } + representations.append(representation) + + # store data for database and source / destinations + instance.data["representations"] = representations + instance.data["traffic"] = traffic + + return representations + + def get_subset(self, asset, instance): + + subset = io.find_one({"type": "subset", + "parent": asset["_id"], + "name": instance.data["subset"]}) + + if subset is None: + subset_name = instance.data["subset"] + self.log.info("Subset '%s' not found, creating.." % subset_name) + + _id = io.insert_one({ + "schema": "avalon-core:subset-2.0", + "type": "subset", + "name": subset_name, + "data": {}, + "parent": asset["_id"] + }).inserted_id + + subset = io.find_one({"_id": _id}) + + return subset + + def create_version(self, subset, version_number, locations, data=None): + """ Copy given source to destination + + Arguments: + subset (dict): the registered subset of the asset + version_number (int): the version number + locations (list): the currently registered locations + """ + # Imprint currently registered location + version_locations = [location for location in locations if + location is not None] + + return {"schema": "avalon-core:version-2.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "locations": version_locations, + "data": data} + + def create_version_data(self, context, instance): + """Create the data collection for th version + + Args: + context: the current context + instance: the current instance being published + + Returns: + dict: the required information with instance.data as key + """ + + families = [] + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + + families += current_families + if instance_family is not None: + families.append(instance_family) + + # create relative source path for DB + relative_path = os.path.relpath(context.data["currentFile"], + api.registered_root()) + source = os.path.join("{root}", relative_path).replace("\\", "/") + + version_data = {"families": families, + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment")} + + return dict(instance.data, **version_data) From 9a70383051acfb7251d69e3c4de721b42dc11324 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:37:03 +0200 Subject: [PATCH 0060/1249] improved collectors --- .../plugins/maya/publish/collect_instances.py | 27 ++- .../plugins/maya/publish/collect_look.py | 74 ++++++++- .../plugins/maya/publish/collect_textures.py | 157 ++++++++++++++++-- 3 files changed, 233 insertions(+), 25 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 498b4fb30c..bbcd8a56cb 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -3,7 +3,7 @@ from maya import cmds import pyblish.api -class CollectMindbenderInstances(pyblish.api.ContextPlugin): +class CollectInstances(pyblish.api.ContextPlugin): """Gather instances by objectSet and pre-defined attribute This collector takes into account assets that are associated with @@ -38,14 +38,24 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): """ - label = "Collect Mindbender Instances" + label = "Collect Instances" order = pyblish.api.CollectorOrder hosts = ["maya"] + isntance_order = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.texture", + "colorbleed.lookdev", + "colorbleed.historyLookdev", + "colorbleed.group"] def process(self, context): + objectset = cmds.ls("*.id", long=True, type="objectSet", recursive=True, objectsOnly=True) for objset in objectset: + self.log.info("Creating instance for {}".format(objset)) members = cmds.sets(objset, query=True) if members is None: @@ -61,7 +71,8 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # The developer is responsible for specifying # the family of each instance. - has_family = cmds.attributeQuery("family", node=objset, + has_family = cmds.attributeQuery("family", + node=objset, exists=True) assert has_family, "\"%s\" was missing a family" % objset @@ -70,7 +81,7 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # Apply each user defined attribute as data for attr in cmds.listAttr(objset, userDefined=True) or list(): try: - value = cmds.getAttr("{}.{}".format(objset, attr)) + value = cmds.getAttr("%s.%s" % (objset, attr)) except Exception: # Some attributes cannot be read directly, # such as mesh and color attributes. These @@ -82,9 +93,10 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # Collect members members = cmds.ls(members, long=True) or [] + children = cmds.listRelatives(members, allDescendents=True, - fullPath=True) + fullPath=True) or [] parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) @@ -99,6 +111,10 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): # user interface interested in visualising it. self.log.info("Found: \"%s\" " % instance.data["name"]) + context[:] = sorted(context) + + return context + def get_all_parents(self, nodes): """Get all parents by using string operations (optimization) @@ -108,6 +124,7 @@ class CollectMindbenderInstances(pyblish.api.ContextPlugin): Returns: list """ + parents = [] for node in nodes: splitted = node.split("|") diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 72946d8588..eff9c29f55 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,9 +1,9 @@ from maya import cmds -from cb.utils.maya import context -import cbra.utils.maya.node_uuid as id_utils import pyblish.api +from cb.utils.maya import context, shaders +import cbra.utils.maya.node_uuid as id_utils SHAPE_ATTRS = ["castsShadows", "receiveShadows", @@ -37,8 +37,8 @@ def get_look_attrs(node): valid = [attr for attr in attrs if attr in SHAPE_ATTRS] result.extend(valid) - if "mbID" in result: - result.remove("mbID") + if "cbId" in result: + result.remove("cbId") return result @@ -62,7 +62,7 @@ class CollectLook(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.look"] + families = ["colorbleed.lookdev"] label = "Collect Look" hosts = ["maya"] @@ -120,6 +120,9 @@ class CollectLook(pyblish.api.InstancePlugin): instance.data["lookData"] = {"attributes": attributes, "relationships": sets.values(), "sets": looksets} + # Collect textures + resources = [self.collect_resources(n) for n in cmds.ls(type="file")] + instance.data["resources"] = resources # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["sets"]: @@ -157,7 +160,8 @@ class CollectLook(pyblish.api.InstancePlugin): for objset in related_sets: if objset in sets: continue - unique_id = cmds.getAttr("%s.mbID" % objset) + + unique_id = cmds.getAttr("%s.cbId" % objset) sets[objset] = {"name": objset, "uuid": unique_id, "members": list()} @@ -253,11 +257,24 @@ class CollectLook(pyblish.api.InstancePlugin): if member in [m["name"] for m in objset_members]: return + # check node type, if mesh get parent! makes assigning shaders easier + if cmds.nodeType(node) == "mesh": + parent = cmds.listRelatives(node, parent=True, fullPath=True) + # a mesh NEEDS to have a parent in Maya logic, no reason for + # assertions or extra checking + parent = parent[0] + if cmds.attributeQuery("cbId", node=parent, exists=True): + node = parent + else: + self.log.error("Transform group of mesh '{}' has no attribute " + "'cbId', this is manditory") + return + if verbose: self.log.debug("Such as %s.." % member) member_data = {"name": node, - "uuid": cmds.getAttr("{}.mbID".format(node, ))} + "uuid": cmds.getAttr("{}.cbId".format(node))} # Include components information when components are assigned if components: @@ -304,3 +321,46 @@ class CollectLook(pyblish.api.InstancePlugin): attributes.append(data) return attributes + + def collect_resources(self, node, verbose=False): + """Collect the link to the file(s) used (resource) + Args: + node (str): name of the node + verbose (bool): enable debug information + + Returns: + dict + """ + + attribute = "{}.fileTextureName".format(node) + source = cmds.getAttr(attribute) + + # Get the computed file path (e.g. the one with the pattern + # in it) So we can reassign it this computed file path whenever + # we need to. + computed_attribute = "{}.computedFileTextureNamePattern".format(node) + computed_source = cmds.getAttr(computed_attribute) + if source != computed_source: + if verbose: + self.log.debug("File node computed pattern differs from " + "original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) + + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = computed_source.replace("\\", "/") + + files = shaders.get_file_node_files(node) + if not files: + self.log.error("File node does not have a texture set: " + "{0}".format(node)) + return + + # Define the resource + return {"node": node, + "attribute": attribute, + "source": source, # required for resources + "files": files} # required for resources diff --git a/colorbleed/plugins/maya/publish/collect_textures.py b/colorbleed/plugins/maya/publish/collect_textures.py index a55e274da0..bc1e1ede0f 100644 --- a/colorbleed/plugins/maya/publish/collect_textures.py +++ b/colorbleed/plugins/maya/publish/collect_textures.py @@ -1,7 +1,6 @@ from maya import cmds import pyblish.api - import cb.utils.maya.shaders as shaders TAGS = ["maya", "attribute", "look"] @@ -72,23 +71,37 @@ class CollectLookTextures(pyblish.api.InstancePlugin): """ - order = pyblish.api.CollectorOrder + 0.498 - label = 'Textures' + order = pyblish.api.CollectorOrder + 0.35 + label = 'Collect Look Textures' families = ["colorbleed.texture"] actions = [SelectTextureNodesAction] + IGNORE = ["out_SET", "controls_SET", "_INST"] + def process(self, instance): verbose = instance.data.get("verbose", False) - # Get textures from sets - sets = instance.data["lookData"]["sets"] - if not sets: - raise RuntimeError("No look sets found for the nodes in the " - "instance. %s" % sets) + # Get all texture nodes from the shader networks + sets = self.gather_sets(instance) + instance_members = {str(i) for i in cmds.ls(instance, long=True, + absoluteName=True)} + + self.log.info("Gathering set relations..") + for objset in sets: + self.log.debug("From %s.." % objset) + content = cmds.sets(objset, query=True) + objset_members = sets[objset]["members"] + for member in cmds.ls(content, long=True, absoluteName=True): + member_data = self.collect_member_data(member, + objset_members, + instance_members, + verbose) + if not member_data: + continue # Get the file nodes - history = cmds.listHistory(sets) or [] + history = cmds.listHistory(sets.keys()) or [] files = cmds.ls(history, type="file") files = list(set(files)) @@ -99,9 +112,44 @@ class CollectLookTextures(pyblish.api.InstancePlugin): continue resources.append(resource) - # Store resources instance.data['resources'] = resources + def gather_sets(self, instance): + """Gather all objectSets which are of importance for publishing + + It checks if all nodes in the instance are related to any objectSet + which need to be + + Args: + instance (list): all nodes to be published + + Returns: + dict + """ + + # Get view sets (so we can ignore those sets later) + sets = dict() + view_sets = set() + for panel in cmds.getPanel(type="modelPanel"): + view_set = cmds.modelEditor(panel, query=True, + viewObjects=True) + if view_set: + view_sets.add(view_set) + + for node in instance: + related_sets = self.get_related_sets(node, view_sets) + if not related_sets: + continue + + for objset in related_sets: + if objset in sets: + continue + unique_id = cmds.getAttr("%s.cbId" % objset) + sets[objset] = {"name": objset, + "uuid": unique_id, + "members": list()} + return sets + def collect_resources(self, node, verbose=False): """Collect the link to the file(s) used (resource) Args: @@ -112,6 +160,8 @@ class CollectLookTextures(pyblish.api.InstancePlugin): dict """ + # assure node includes full path + node = cmds.ls(node, long=True)[0] attribute = "{}.fileTextureName".format(node) source = cmds.getAttr(attribute) @@ -146,8 +196,89 @@ class CollectLookTextures(pyblish.api.InstancePlugin): "node": node, "attribute": attribute, "source": source, # required for resources - "files": files, # required for resources - "subfolder": "textures", # optional for resources - } + "files": files} # required for resources return resource + + def collect_member_data(self, member, objset_members, instance_members, + verbose=False): + """Get all information of the node + Args: + member (str): the name of the node to check + objset_members (list): the objectSet members + instance_members (set): the collected instance members + verbose (bool): get debug information + + Returns: + dict + + """ + + node, components = (member.rsplit(".", 1) + [None])[:2] + + # Only include valid members of the instance + if node not in instance_members: + if verbose: + self.log.info("Skipping member %s" % member) + return + + if member in [m["name"] for m in objset_members]: + return + + if verbose: + self.log.debug("Such as %s.." % member) + + member_data = {"name": node, + "uuid": cmds.getAttr("{}.cbId".format(node, ))} + + # Include components information when components are assigned + if components: + member_data["components"] = components + + return member_data + + def get_related_sets(self, node, view_sets): + """Get the sets which do not belong to any specific group + + Filters out based on: + - id attribute is NOT `pyblish.avalon.container` + - shapes and deformer shapes (alembic creates meshShapeDeformed) + - set name ends with any from a predefined list + - set in not in viewport set (isolate selected for example) + + Args: + node (str): name of the current not to check + """ + + ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] + + related_sets = cmds.listSets(object=node, extendToShape=False) + if not related_sets: + return [] + + # Ignore containers + sets = [s for s in related_sets if + not cmds.attributeQuery("id", node=s, exists=True) or + not cmds.getAttr("%s.id" % s) in ignored] + + # Exclude deformer sets + # Autodesk documentation on listSets command: + # type(uint) : Returns all sets in the scene of the given + # >>> type: + # >>> 1 - all rendering sets + # >>> 2 - all deformer sets + deformer_sets = cmds.listSets(object=node, extendToShape=False, + type=2) or [] + deformer_sets = set(deformer_sets) # optimize lookup + sets = [s for s in sets if s not in deformer_sets] + + # Ignore specifically named sets + sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] + + # Ignore viewport filter view sets (from isolate select and + # viewports) + sets = [s for s in sets if s not in view_sets] + + self.log.info("Found sets %s for %s" % (related_sets, node)) + + return sets \ No newline at end of file From 115a85f3914febd5825267e7dd1bd97139fa6f3d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:39:05 +0200 Subject: [PATCH 0061/1249] extended load_look, improved load_animation namespace --- colorbleed/plugins/maya/load/load_look.py | 160 ++++++++++++++++++++-- colorbleed/plugins/maya/load/load_rig.py | 22 ++- 2 files changed, 159 insertions(+), 23 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 411f2fbed5..c73e2b73a8 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -2,7 +2,7 @@ import os import json from maya import cmds -from avalon import api +from avalon import api, maya class LookLoader(api.Loader): @@ -17,7 +17,25 @@ class LookLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - from avalon import maya + """ + Load and try to ssign Lookdev to nodes based on relationship data + Args: + name: + namespace: + context: + data: + + Returns: + + """ + + # improve readability of the namespace + assetname = context["asset"]["name"] + ns_assetname = "{}_".format(assetname) + + namespace = maya.unique_namespace(ns_assetname, + format="%03d", + suffix="_look") try: existing_reference = cmds.file(self.fname, query=True, @@ -28,20 +46,16 @@ class LookLoader(api.Loader): self.log.info("Loading lookdev for the first time..") with maya.maintained_selection(): - nodes = cmds.file( - self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True - ) + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True) else: self.log.info("Reusing existing lookdev..") nodes = cmds.referenceQuery(existing_reference, nodes=True) - namespace = nodes[0].split(":", 1)[0] # Assign shaders self.fname = self.fname.rsplit(".", 1)[0] + ".json" - if not os.path.isfile(self.fname): self.log.warning("Look development asset " "has no relationship data.") @@ -50,6 +64,130 @@ class LookLoader(api.Loader): with open(self.fname) as f: relationships = json.load(f) - maya.apply_shaders(relationships, namespace) + # Get all nodes which belong to a matching name space + # Currently this is the safest way to get all the nodes + namespace_nodes = self.get_namespace_nodes(assetname) + self.apply_shaders(relationships, nodes, namespace_nodes) self[:] = nodes + + def apply_shaders(self, relationships, nodes, namespace_nodes): + """Apply all shaders to the nodes based on the relationship data + + Args: + relationships (dict): shader to node relationships + nodes (list): shader network nodes + namespace_nodes (list): nodes from linked to namespace + + Returns: + None + """ + + # attributes = relationships.get("attributes", []) + sets = relationships.get("sets", []) + + shading_engines = cmds.ls(nodes, type="shadingEngine", long=True) + assert len(shading_engines) > 0, ("Error in retrieving shading engine " + "from reference") + + # get all nodes which we need to link + for set in sets: + # collect all unique IDs of the set members + uuid = set["uuid"] + member_uuids = [member["uuid"] for member in set["members"]] + filtered_nodes = self.get_matching_nodes(namespace_nodes, + member_uuids) + shading_engine = self.get_matching_nodes(shading_engines, + [uuid]) + + assert len(shading_engine) == 1, ("Could not find the correct " + "shading engine with cbId " + "'{}'".format(uuid)) + + cmds.sets(filtered_nodes, forceElement=shading_engine[0]) + + def get_matching_nodes(self, nodes, uuids): + """Filter all nodes which match the UUIDs + + Args: + nodes (list): collection of nodes to check + uuids (list): a list of UUIDs which are linked to the shader + + Returns: + list: matching nodes + """ + + filtered_nodes = [] + for node in nodes: + if node is None: + continue + + if not cmds.attributeQuery("cbId", node=node, exists=True): + continue + + # Deformed shaped + attr = "{}.cbId".format(node) + attribute_value = cmds.getAttr(attr) + + if attribute_value not in uuids: + continue + + filtered_nodes.append(node) + + return filtered_nodes + + def get_namespace_nodes(self, assetname): + """ + Get all nodes of namespace `asset_*` and check if they have a shader + assigned, if not add to list + Args: + context (dict): current context of asset + + Returns: + list + + """ + + # types = ["transform", "mesh"] + list_nodes = [] + + namespaces = cmds.namespaceInfo(listOnlyNamespaces=True) + + # remove basic namespaces + namespaces.remove("UI") + namespaces.remove("shared") + + for ns in namespaces: + if not ns.startswith(assetname): + continue + # get reference nodes + ns_nodes = cmds.namespaceInfo(ns, listOnlyDependencyNodes=True) + # TODO: might need to extend the types + # check if any nodes are connected to something else than lambert1 + list_nodes = cmds.ls(ns_nodes, long=True) + unassigned_nodes = [self.has_default_shader(n) for n in list_nodes] + nodes = [n for n in unassigned_nodes if n is not None] + + list_nodes.extend(nodes) + + return set(list_nodes) + + def has_default_shader(self, node): + """Check if the nodes have `initialShadingGroup` shader assigned + + Args: + node (str): node to check + + Returns: + str + """ + + shaders = cmds.listConnections(node, type="shadingEngine") or [] + if "initialShadingGroup" in shaders: + # return transform node + transform = cmds.listRelatives(node, parent=True, type="transform", + fullPath=True) + if not transform: + return [] + + return transform[0] diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 0138f56b76..5604ec22c5 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -2,7 +2,7 @@ import os from maya import cmds -from avalon import api +from avalon import api, maya class RigLoader(api.Loader): @@ -21,7 +21,9 @@ class RigLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - print("debug : {}\n{}\n".format(name, namespace)) + + assetname = "{}_".format(context["asset"]["name"]) + unique_namespace = maya.unique_namespace(assetname, format="%03d") nodes = cmds.file(self.fname, namespace=namespace, reference=True, @@ -31,9 +33,8 @@ class RigLoader(api.Loader): # Store for post-process self[:] = nodes - if data.get("post_process", True): - self._post_process(name, namespace, context, data) + self._post_process(name, unique_namespace, context, data) def _post_process(self, name, namespace, context, data): from avalon import maya @@ -43,12 +44,10 @@ class RigLoader(api.Loader): # elsewhere, such as in the Integrator plug-in, # without duplication. - output = next( - (node for node in self - if node.endswith("out_SET")), None) - controls = next( - (node for node in self - if node.endswith("controls_SET")), None) + output = next((node for node in self if + node.endswith("out_SET")), None) + controls = next((node for node in self if + node.endswith("controls_SET")), None) assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." @@ -68,9 +67,8 @@ class RigLoader(api.Loader): # TODO(marcus): Hardcoding the family here, better separate this. dependencies = [context["representation"]["_id"]] dependencies = " ".join(str(d) for d in dependencies) - unique_name = maya.unique_name(asset_name, suffix="_SET") - maya.create(name=unique_name, + maya.create(name=namespace, asset=asset, family="colorbleed.animation", options={"useSelection": True}, From c4cf4b3917be6dcfe709cc90b56023c02417faba Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:40:46 +0200 Subject: [PATCH 0062/1249] improved loaders --- .../plugins/maya/load/load_animation.py | 60 +++++++++++-------- colorbleed/plugins/maya/load/load_model.py | 2 - 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index ed65b2f042..b29bbecece 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -1,4 +1,8 @@ -from avalon import api +import os + +from maya import cmds + +from avalon import api, maya class AbcLoader(api.Loader): @@ -13,12 +17,17 @@ class AbcLoader(api.Loader): color = "orange" def process(self, name, namespace, context, data): - from maya import cmds cmds.loadPlugin("AbcImport.mll", quiet=True) # Prevent identical alembic nodes from being shared # Create unique namespace for the cameras + # Get name from asset being loaded + assert "_" in name, "Naming convention not followed" + assetname = "{}_".format(name.split("_")[0]) + namespace = maya.unique_namespace(assetname, + format="%03d", + suffix="_abc") nodes = cmds.file(self.fname, namespace=namespace, sharedReferenceFile=False, @@ -27,8 +36,13 @@ class AbcLoader(api.Loader): reference=True, returnNewNodes=True) + # load colorbleed ID attribute + self.lock_id_attr(nodes) + self[:] = nodes + def lock_id_attr(self): + pass class CurvesLoader(api.Loader): """Specific loader of Curves for the avalon.animation family""" @@ -77,24 +91,19 @@ class CurvesLoader(api.Loader): ]) with maya.maintained_selection(): - cmds.select( - control_set, - replace=True, + cmds.select(control_set, + replace=True, + # Support controllers being embedded in + # additional selection sets. + noExpand=False) - # Support controllers being embedded in - # additional selection sets. - noExpand=False - ) - - nodes = cmds.file( - self.fname, - i=True, - type="atomImport", - renameAll=True, - namespace=namespace, - options=options, - returnNewNodes=True, - ) + nodes = cmds.file(self.fname, + i=True, + type="atomImport", + renameAll=True, + namespace=namespace, + options=options, + returnNewNodes=True) self[:] = nodes + cmds.sets(container, query=True) + [container] @@ -102,8 +111,7 @@ class CurvesLoader(api.Loader): self._post_process(name, namespace, context, data) def _post_process(self, name, namespace, context, data): - import os - from maya import cmds + from avalon import maya, io # Task-dependent post-process @@ -137,6 +145,7 @@ class CurvesLoader(api.Loader): cmds.select([output, controls], noExpand=True) dependencies = [context["representation"]["_id"]] + dependencies = " ".join(str(d) for d in dependencies) name = "anim{}_".format(dependency["name"].title()) # TODO(marcus): Hardcoding the family here, better separate this. @@ -144,8 +153,7 @@ class CurvesLoader(api.Loader): assert len(family) == 1, ("None or multiple animation " "families found") family = family[0] - maya.create( - name=maya.unique_name(name, suffix="_SET"), - family=family, - options={"useSelection": True}, - data={"dependencies": " ".join(str(d) for d in dependencies)}) + maya.create(name=maya.unique_name(name, suffix="_SET"), + family=family, + options={"useSelection": True}, + data={"dependencies": dependencies}) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 313048b1da..57d1676189 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -46,8 +46,6 @@ class ModelGPUCacheLoader(api.Loader): def process(self, name, namespace, context, data): - from maya import cmds - # todo: This will likely not be entirely safe with "containerize" # also this cannot work in the manager because it only works # on references at the moment! From eb61b4b2f05b482d7e98aa095b0f861cbe70be6b Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:42:30 +0200 Subject: [PATCH 0063/1249] improved extract to match integrator --- .../plugins/maya/publish/extract_alembic.py | 20 ++++++-- .../plugins/maya/publish/extract_look.py | 47 ++++++++----------- .../plugins/maya/publish/extract_textures.py | 41 ++++++++++++++++ 3 files changed, 76 insertions(+), 32 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index c3abe59244..b15380e1d5 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -16,9 +16,7 @@ class ExtractAlembic(colorbleed.api.Extractor): """ label = "Alembic" - families = ["colorbleed.model", - "colorbleed.pointcache", - "colorbleed.proxy"] + families = ["colorbleed.model", "colorbleed.pointcache"] optional = True def process(self, instance): @@ -27,13 +25,27 @@ class ExtractAlembic(colorbleed.api.Extractor): filename = "%s.abc" % instance.name path = os.path.join(parent_dir, filename) + attrPrefix = instance.data.get("attrPrefix", []) + attrPrefix.append("cb") + options = copy.deepcopy(instance.data) + options['attrPrefix'] = attrPrefix + + # Ensure visibility keys are written + options['writeVisibility'] = True + + # Write creases + options['writeCreases'] = True + + # Ensure UVs are written + options['uvWrite'] = True + options['selection'] = True options["attr"] = ["cbId"] # force elect items to ensure all items get exported by Alembic members = instance.data("setMembers") - print members + print "Members : {}".format(members) cmds.select(members) with avalon.maya.suspended_refresh(): diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 97c572565a..04c632e2d9 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -3,10 +3,11 @@ import json from maya import cmds +import pyblish.api import avalon.maya import colorbleed.api -import cb.utils.maya.context as context +from cb.utils.maya import context class ExtractLook(colorbleed.api.Extractor): @@ -18,9 +19,10 @@ class ExtractLook(colorbleed.api.Extractor): """ - label = "Look (Maya ASCII + JSON)" + label = "Extract Look (Maya ASCII + JSON)" hosts = ["maya"] - families = ["colorbleed.look"] + families = ["colorbleed.lookdev"] + order = pyblish.api.ExtractorOrder + 0.2 def process(self, instance): @@ -29,6 +31,7 @@ class ExtractLook(colorbleed.api.Extractor): maya_fname = "{0}.ma".format(instance.name) json_fname = "{0}.json".format(instance.name) + # Make texture dump folder maya_path = os.path.join(dir_path, maya_fname) json_path = os.path.join(dir_path, json_fname) @@ -40,40 +43,28 @@ class ExtractLook(colorbleed.api.Extractor): lookdata = instance.data["lookData"] sets = lookdata["sets"] - # Define the texture file node remapping - resource_remap = dict() - # required tags to be a look resource - required_tags = ["maya", "attribute", "look"] - resources = instance.data.get("resources", []) - for resource in resources: - resource_tags = resource.get("tags", []) - if all(tag in resource_tags for tag in required_tags): - node = resource['node'] - destination = resource['destination'] - resource_remap["{}.fileTextureName".format(node)] = destination - - # Extract in corect render layer + # Extract in correct render layer layer = instance.data.get("renderlayer", "defaultRenderLayer") with context.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with context.empty_sets(sets): - with context.attribute_values(resource_remap): - with avalon.maya.maintained_selection(): - cmds.select(sets, noExpand=True) - cmds.file(maya_path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) + with avalon.maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) # Write the JSON data self.log.info("Extract json..") data = {"attributes": lookdata["attributes"], "sets": lookdata["relationships"]} + with open(json_path, "w") as f: json.dump(data, f) diff --git a/colorbleed/plugins/maya/publish/extract_textures.py b/colorbleed/plugins/maya/publish/extract_textures.py index e69de29bb2..86380d4295 100644 --- a/colorbleed/plugins/maya/publish/extract_textures.py +++ b/colorbleed/plugins/maya/publish/extract_textures.py @@ -0,0 +1,41 @@ +import json +import os +import shutil + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +class ExtractTextures(colorbleed.api.Extractor): + + label = "Extract Textures" + hosts = ["maya"] + families = ["colorbleed.texture"] + order = pyblish.api.ExtractorOrder + 0.1 + + def process(self, instance): + + self.log.info("Extracting textures ...") + + dir_path = self.staging_dir(instance) + resources = instance.data["resources"] + for resource in resources: + self.copy_files(dir_path, resource["files"]) + + self.log.info("Storing cross instance information ...") + self.store_data(resources) + + def store_data(self, data): + tmp_dir = lib.maya_temp_folder() + tmp_file = os.path.join(tmp_dir, "resources.json") + with open(tmp_file, "w") as f: + json.dump(data, fp=f, + separators=[",", ":"], + ensure_ascii=False) + + def copy_files(self, dest, files): + for f in files: + fname = os.path.basename(f) + dest_file = os.path.join(dest, fname) + shutil.copy(f, dest_file) From 24e11ac0475a624b9d128dfabb8e58dbf6569ce9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:43:38 +0200 Subject: [PATCH 0064/1249] improved validators, added support for colorbleed IDs --- .../publish/validate_look_members_node_ids.py | 2 +- .../maya/publish/validate_resources.py | 6 ++--- .../maya/publish/validate_rig_contents.py | 23 +++++++++++------- .../maya/publish/validate_rig_controllers.py | 24 ++++++++++++++++++- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 819f287bf6..4e851e0b21 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -41,7 +41,7 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId invalid = list() for node in members: - if not cmds.getAttr("{}.mbID".format(node)): + if not cmds.getAttr("{}.cbId".format(node)): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_resources.py b/colorbleed/plugins/maya/publish/validate_resources.py index 57a9239905..2dc6f8c313 100644 --- a/colorbleed/plugins/maya/publish/validate_resources.py +++ b/colorbleed/plugins/maya/publish/validate_resources.py @@ -23,9 +23,7 @@ class ValidateResources(pyblish.api.InstancePlugin): def process(self, instance): for resource in instance.data.get('resources', []): - # Required data - assert "source" in resource - assert "destination" in resource - assert "files" in resource + assert "source" in resource, "No source found" + assert "files" in resource, "No files from source" assert all(os.path.exists(f) for f in resource['files']) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 5744806005..3ef1220e56 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -44,7 +44,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): output_content = cmds.sets("out_SET", query=True) or [] assert output_content, "Must have members in rig out_SET" - controls_content = cmds.set("controls_SET", query=True) or [] + controls_content = cmds.sets("controls_SET", query=True) or [] assert controls_content, "Must have members in rig controls_SET" root_node = cmds.ls(set_members, assemblies=True) @@ -56,15 +56,15 @@ class ValidateRigContents(pyblish.api.InstancePlugin): self.invalid_controls = self.validate_controls(controls_content, hierarchy) - if self.invalid_hierachy: + if self.invalid_hierarchy: self.log.error("Found nodes which reside outside of root group " "while they are set up for publishing." - "\n%s" % self.invalid_hierachy) + "\n%s" % self.invalid_hierarchy) error = True - if self.not_transforms: + if self.invalid_controls: self.log.error("Only transforms can be part of the controls_SET." - "\n%s" % self.not_transforms) + "\n%s" % self.invalid_controls) error = True if self.invalid_geometry: @@ -100,6 +100,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ errors = [] for node in nodes: + print node if node not in hierarchy: errors.append(node) return errors @@ -128,10 +129,12 @@ class ValidateRigContents(pyblish.api.InstancePlugin): # The user can add the shape node to the out_set, this will result # in none when querying allDescendents all_shapes = set_members + shapes + all_long_names = [cmds.ls(i, long=True)[0] for i in all_shapes] # geometry - invalid_shapes = self.validate_hierarchy(hierarchy, all_shapes) - self.invalid_hierachy.extend(invalid_shapes) + invalid_shapes = self.validate_hierarchy(hierarchy, + all_long_names) + self.invalid_hierarchy.extend(invalid_shapes) for shape in all_shapes: nodetype = cmds.nodeType(shape) if nodetype in self.ignore_nodes: @@ -157,8 +160,10 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ errors = [] - invalid_controllers = self.validate_hierarchy(hierarchy, set_members) - self.invalid_hierachy.extend(invalid_controllers) + all_long_names = [cmds.ls(i, long=True)[0] for i in set_members] + invalid_controllers = self.validate_hierarchy(hierarchy, + all_long_names) + self.invalid_hierarchy.extend(invalid_controllers) for node in set_members: nodetype = cmds.nodeType(node) if nodetype in self.ignore_nodes: diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index dd9f77006c..9b066f6351 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -1,8 +1,12 @@ +import logging + from maya import cmds import pyblish.api import colorbleed.api +log = logging.getLogger("Rig Controllers") + class ValidateRigControllers(pyblish.api.InstancePlugin): """Check if the controllers have the transformation attributes set to @@ -12,6 +16,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): label = "Rig Controllers" hosts = ["maya"] families = ["colorbleed.rig"] + actions = [colorbleed.api.RepairAction] def process(self, instance): @@ -21,7 +26,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): is_offset = list() controls = cmds.sets("controls_SET", query=True) - assert controls, "Must have controls in rig control_SET" + assert controls, "Must have controls in rig controls_SET" for control in controls: valid_keyed = self.validate_keyed_state(control) @@ -41,15 +46,18 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if is_keyed: self.log.error("No controls can be keyes. Failed :\n" "%s" % is_keyed) + error = True if is_offset: self.log.error("All controls default transformation values. " "Failed :\n%s" % is_offset) + error = True if not_locked: self.log.error("All controls must have visibility " "attribute locked. Failed :\n" "%s" % not_locked) + error = True if error: raise RuntimeError("Invalid rig controllers. See log for details.") @@ -78,3 +86,17 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if animation_curves: return False return True + + @classmethod + def repair(cls, instance): + + # lock all controllers in controls_SET + controls = cmds.sets("controls_SET", query=True) + for control in controls: + attr = "{}.visibility".format(control) + locked = cmds.getAttr(attr, lock=True) + if not locked: + print("Locking visibility for %s" % control) + cmds.setAttr(attr, lock=True) + + continue From d4e846946ecfd34345d0b6d90a0758e8c96839f3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:44:50 +0200 Subject: [PATCH 0065/1249] improved set_uuid on maya save --- colorbleed/maya/__init__.py | 39 +++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 6e74cd0980..3097683464 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,7 +2,7 @@ import os import site import uuid -from avalon import maya, api as avalon +from avalon import maya, io, api as avalon from pyblish import api as pyblish from maya import cmds @@ -45,22 +45,31 @@ def uninstall(): menu.uninstall() -def _set_uuid(node): +def _set_uuid(asset_id, node): """Add cbId to `node` Unless one already exists. """ - asset = os.environ["AVALON_ASSET"] attr = "{0}.cbId".format(node) - - if not cmds.objExists(attr): + if not cmds.attributeQuery("cbId", node=node, exists=True): cmds.addAttr(node, longName="cbId", dataType="string") _, uid = str(uuid.uuid4()).rsplit("-", 1) - cb_uid = "{}:{}:{}".format(asset, node, uid) + cb_uid = "{}:{}".format(asset_id, uid) cmds.setAttr(attr, cb_uid, type="string") +def _copy_uuid(source, target): + + source_attr = "{0}.cbId".format(source) + target_attr = "{0}.cbId".format(target) + if not cmds.attributeQuery("cbId", node=target, exists=True): + cmds.addAttr(target, longName="cbId", dataType="string") + + attribute_value = cmds.getAttr(source_attr) + cmds.setAttr(target_attr, attribute_value, type="string") + + def on_init(): avalon.logger.info("Running callback on init..") @@ -90,17 +99,20 @@ def on_save(): defaults = ["initialShadingGroup", "initialParticleSE"] # the default items which always want to have an ID - types = ["mesh", "shadingEngine", "file"] + types = ["mesh", "shadingEngine", "file", "nurbsCurve"] # the items which need to pass the id to their parent - subtypes = ["nurbsCurve"] - nodes = (set(cmds.ls(type=types, long=True)) - set(cmds.ls(long=True, readOnly=True)) - set(cmds.ls(long=True, lockedNodes=True))) transforms = set() - for n in cmds.ls(type=subtypes, long=True): + for n in cmds.ls(type=types, long=True): + # pass id to parent of node if in subtypes + relatives = cmds.listRelatives(n, parent=True, fullPath=True) + if not relatives: + continue + for r in cmds.listRelatives(n, parent=True, fullPath=True): transforms.add(r) @@ -108,8 +120,11 @@ def on_save(): # is unique nodes |= transforms - # remove any default nodes + # Lead with asset ID from the database + asset = os.environ["AVALON_ASSET"] + asset_id = io.find_one({"type": "asset", "name": asset}) + for node in nodes: if node in defaults: continue - _set_uuid(node) + _set_uuid(str(asset_id["_id"]), node) From beb06718b2d54a9102f155249c2087357bfc308f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 13:47:43 +0200 Subject: [PATCH 0066/1249] changed pipeline id name to cbId --- colorbleed/maya/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/commands.py b/colorbleed/maya/commands.py index b31e4abe64..c2943128a3 100644 --- a/colorbleed/maya/commands.py +++ b/colorbleed/maya/commands.py @@ -612,10 +612,10 @@ def auto_connect_assets(src, dst): break for input_transform in cmds.sets(in_set, query=True): - mbid = cmds.getAttr(input_transform + ".mbID") + mbid = cmds.getAttr(input_transform + ".cbId") input_shape = cmds.listRelatives(input_transform, shapes=True)[0] - for output_transform in lib.lsattr("mbID", value=mbid): + for output_transform in lib.lsattr("cbId", value=mbid): ref = cmds.referenceQuery(output_transform, referenceNode=True) if ref != src: From ec946f8c5b7739eec4996b370e927a08d75749f1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 15:35:02 +0200 Subject: [PATCH 0067/1249] changed warning to debug logger for `get_id` --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b4e4dec544..f1530fd109 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -613,7 +613,7 @@ def _get_id(node): attr = "{}.cbId".format(node) attribute_value = cmds.getAttr(attr) except Exception as e: - log.warning(e) + log.debug(e) return return attribute_value From 4c0160757b8228a0cd0b70d22ef64f0df0bbf86c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 15:35:31 +0200 Subject: [PATCH 0068/1249] added lib.assign_look to the loader --- colorbleed/plugins/maya/load/load_look.py | 68 +---------------------- 1 file changed, 2 insertions(+), 66 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index c73e2b73a8..1742983e84 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -3,6 +3,7 @@ import json from maya import cmds from avalon import api, maya +import colorbleed.maya.lib as lib class LookLoader(api.Loader): @@ -67,75 +68,10 @@ class LookLoader(api.Loader): # Get all nodes which belong to a matching name space # Currently this is the safest way to get all the nodes namespace_nodes = self.get_namespace_nodes(assetname) - self.apply_shaders(relationships, nodes, namespace_nodes) + lib.apply_shaders(relationships, nodes, namespace_nodes) self[:] = nodes - def apply_shaders(self, relationships, nodes, namespace_nodes): - """Apply all shaders to the nodes based on the relationship data - - Args: - relationships (dict): shader to node relationships - nodes (list): shader network nodes - namespace_nodes (list): nodes from linked to namespace - - Returns: - None - """ - - # attributes = relationships.get("attributes", []) - sets = relationships.get("sets", []) - - shading_engines = cmds.ls(nodes, type="shadingEngine", long=True) - assert len(shading_engines) > 0, ("Error in retrieving shading engine " - "from reference") - - # get all nodes which we need to link - for set in sets: - # collect all unique IDs of the set members - uuid = set["uuid"] - member_uuids = [member["uuid"] for member in set["members"]] - filtered_nodes = self.get_matching_nodes(namespace_nodes, - member_uuids) - shading_engine = self.get_matching_nodes(shading_engines, - [uuid]) - - assert len(shading_engine) == 1, ("Could not find the correct " - "shading engine with cbId " - "'{}'".format(uuid)) - - cmds.sets(filtered_nodes, forceElement=shading_engine[0]) - - def get_matching_nodes(self, nodes, uuids): - """Filter all nodes which match the UUIDs - - Args: - nodes (list): collection of nodes to check - uuids (list): a list of UUIDs which are linked to the shader - - Returns: - list: matching nodes - """ - - filtered_nodes = [] - for node in nodes: - if node is None: - continue - - if not cmds.attributeQuery("cbId", node=node, exists=True): - continue - - # Deformed shaped - attr = "{}.cbId".format(node) - attribute_value = cmds.getAttr(attr) - - if attribute_value not in uuids: - continue - - filtered_nodes.append(node) - - return filtered_nodes - def get_namespace_nodes(self, assetname): """ Get all nodes of namespace `asset_*` and check if they have a shader From e52310e63825be5bcc85bb943ad71b9420a1c3bc Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 17:50:48 +0200 Subject: [PATCH 0069/1249] added get_reference_node function --- colorbleed/maya/lib.py | 27 ++++++++++++------- .../plugins/publish/post_look_integrate.py | 0 2 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 colorbleed/plugins/publish/post_look_integrate.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f1530fd109..686a08c6fe 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -665,6 +665,21 @@ def get_representation_file(representation, template=TEMPLATE): return template.format(**context) +def get_reference_node(path): + """ + Get the reference node when the path is found being used in a reference + Args: + path (str): + + Returns: + + """ + node = cmds.file(path, query=True, referenceNode=True) + reference_path = cmds.referenceQuery(path, filename=True) + if os.path.normpath(path) == os.path.normpath(reference_path): + return node + + def list_looks(asset_id): """Return all look subsets for the given asset @@ -706,14 +721,8 @@ def assign_look_by_version(nodes, version_id): shader_filepath = get_representation_file(shader_file) shader_relation = get_representation_file(shader_relations) - try: - existing_reference = cmds.file(shader_filepath, - query=True, - referenceNode=True) - except RuntimeError as e: - if e.message.rstrip() != "Cannot find the scene file.": - raise - + reference_node = get_reference_node(shader_filepath) + if reference_node is None: log.info("Loading lookdev for the first time..") # Define namespace @@ -731,7 +740,7 @@ def assign_look_by_version(nodes, version_id): returnNewNodes=True) else: log.info("Reusing existing lookdev..") - shader_nodes = cmds.referenceQuery(existing_reference, nodes=True) + shader_nodes = cmds.referenceQuery(reference_node, nodes=True) # Assign relationships with open(shader_relation, "r") as f: diff --git a/colorbleed/plugins/publish/post_look_integrate.py b/colorbleed/plugins/publish/post_look_integrate.py new file mode 100644 index 0000000000..e69de29bb2 From a168dd644d97100cc0548a0adcf1b6787c5a099d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 25 Jul 2017 17:52:39 +0200 Subject: [PATCH 0070/1249] implemented get_reference_node, improved referencing multiple files with similar filename --- colorbleed/plugins/maya/load/load_look.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 1742983e84..9dffb3c550 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -30,6 +30,8 @@ class LookLoader(api.Loader): """ + reference_node = None + # improve readability of the namespace assetname = context["asset"]["name"] ns_assetname = "{}_".format(assetname) @@ -37,23 +39,22 @@ class LookLoader(api.Loader): namespace = maya.unique_namespace(ns_assetname, format="%03d", suffix="_look") - try: - existing_reference = cmds.file(self.fname, - query=True, - referenceNode=True) - except RuntimeError as e: - if e.message.rstrip() != "Cannot find the scene file.": - raise - self.log.info("Loading lookdev for the first time..") + try: + reference_node = lib.get_reference_node(self.fname) + except: + pass + + if reference_node is None: + self.log.info("Loading lookdev for the first time ...") with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, reference=True, returnNewNodes=True) else: - self.log.info("Reusing existing lookdev..") - nodes = cmds.referenceQuery(existing_reference, nodes=True) + self.log.info("Reusing existing lookdev ...") + nodes = cmds.referenceQuery(reference_node, nodes=True) # Assign shaders self.fname = self.fname.rsplit(".", 1)[0] + ".json" From 73d93b1bd1bee3b55143cdbcea14639c0ef7078e Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:50:35 +0200 Subject: [PATCH 0071/1249] added more items to menu --- colorbleed/maya/menu.json | 364 ++++++++++++++++---------------------- 1 file changed, 157 insertions(+), 207 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9258617399..4931063ee7 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -19,12 +19,6 @@ "title": "Version Up", "tooltip": "Incremental save with a specific format" }, - { - "command": "", - "sourcetype": "file", - "title": "Save scene", - "tooltip": "Save the scene based on a specific format" - }, { "title": "separator" } @@ -84,7 +78,7 @@ "uvset", "delete" ], - "title": "polyDeleteOtherUVSets", + "title": "Polygon Delete Other UV Sets", "tooltip": "" }, { @@ -112,10 +106,7 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", "sourcetype": "file", - "tags": [ - "modeling", - "polyDetachSeparate" - ], + "tags": ["modeling", "poly","detach","separate"], "title": "Polygon Detach and Separate", "tooltip": "" }, @@ -133,10 +124,7 @@ { "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", "sourcetype": "file", - "tags": [ - "modeling", - "polySelectEveryNthEdgeUI" - ], + "tags": ["modeling", "select","nth", "edge", "ui"], "title": "Select Every Nth Edge" }, { @@ -144,7 +132,7 @@ "sourcetype": "file", "tags": [ "modeling", - "djPFXUVs" + "djPFX", "UVs" ], "title": "dj PFX UVs", "tooltip": "" @@ -256,10 +244,7 @@ { "command": "", "sourcetype": "file", - "tags": [ - "animation", - "optimize" - ], + "tags": [], "title": "Optimize", "tooltip": "Optimization scripts", "items": [ @@ -297,7 +282,7 @@ "zvParentMaster" ], "title": "ZV Parent Master", - "tooltip": "Open ZV Parent Master UI" + "tooltip": "Open ZV Parent M UI" }, { "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", @@ -342,6 +327,18 @@ "title": "Capture GUI - simple", "tooltip": "Simplified version of the Capture GUI" }, + { + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "sourcetype": "file", + "tags": [ + "animation", + "capture", + "screenshot", + "movie" + ], + "title": "Capture GUI", + "tooltip": "Render current camera to an image or movie" + }, { "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "sourcetype": "file", @@ -363,18 +360,6 @@ "title": "Pose Library", "tooltip": "" }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "sourcetype": "file", - "tags": [ - "animation", - "capture", - "screenshot", - "movie" - ], - "title": "Capture GUI", - "tooltip": "Render current camera to an image or movie" - }, { "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", "sourcetype": "file", @@ -548,6 +533,66 @@ ], "title": "Joints On Curve" }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "python", + "tags": [ + "rigging", + "resetBindSelectedSkinJoints", + "python" + ], + "title": "Reset Bind Selected Skin Joints" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "python", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedComponents", + "python" + ], + "title": "Select Skincluster Joints From Selected Components" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "python", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedMesh", + "python" + ], + "title": "Select Skincluster Joints From Selected Mesh" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setJointLabels", + "python" + ], + "title": "Set Joint Labels" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setJointOrientationFromCurrentRotation", + "python" + ], + "title": "Set Joint Orientation From Current Rotation" + }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setSelectedJointsOrientationZero", + "python" + ], + "title": "Set Selected Joints Orientation Zero" + }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", "sourcetype": "python", @@ -558,6 +603,16 @@ ], "title": "Mirror Curve Shape" }, + { + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "python", + "tags": [ + "rigging", + "setRotationOrderUI", + "python" + ], + "title": "Set Rotation Order UI" + }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", "sourcetype": "python", @@ -596,7 +651,7 @@ "rapidRig", "python" ], - "title": "rapidRig" + "title": "Rapid Rig" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", @@ -628,76 +683,6 @@ ], "title": "Reset Bind Selected Meshes" }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "python", - "tags": [ - "rigging", - "resetBindSelectedSkinJoints", - "python" - ], - "title": "Reset Bind Selected Skin Joints" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "python", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedComponents", - "python" - ], - "title": "Select Skincluster Joints From Selected Components" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "python", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedMesh", - "python" - ], - "title": "Select Skincluster Joints From Selected Mesh" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setJointLabels", - "python" - ], - "title": "setJointLabels" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setJointOrientationFromCurrentRotation", - "python" - ], - "title": "Set Joint Orientation From Current Rotation" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setRotationOrderUI", - "python" - ], - "title": "Set Rotation Order UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "python", - "tags": [ - "rigging", - "setSelectedJointsOrientationZero", - "python" - ], - "title": "Set Selected Joints Orientation Zero" - }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", "sourcetype": "python", @@ -770,7 +755,7 @@ } ], "Shading": [ - { + { "command": "", "sourcetype": "file", "tags": [ @@ -1098,123 +1083,88 @@ ], "Layout": [ { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", "sourcetype": "file", - "tags": [ - "layout", - "alignDistributeUI" + "tags": ["layout", "align", "Distribute", "UI"], + "title": "Align Distribute UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", + "sourcetype": "file", + "tags": ["layout", "align", "UI", "Simple"], + "title": "Align Simple UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", + "sourcetype": "file", + "tags": ["layout", "center", "locator"], + "title": "Center Locator", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", + "sourcetype": "file", + "tags": ["layout", "average", "locator"], + "title": "Average Locator", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", + "sourcetype": "file", + "tags": ["layout", "select", "proximity", "ui" ], - "title": "alignDistributeUI", + "title": "Select Within Proximity UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", + "sourcetype": "file", + "tags": ["layout", "Duplicate", "Curve", "UI"], + "title": "Duplicate Curve UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", + "sourcetype": "file", + "tags": ["layout", "random", "Deselect", "UI"], + "title": "Random Deselect UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", + "sourcetype": "file", + "tags": ["layout", "multi", "reference"], + "title": "Multi Referencer UI", + "tooltip": "" + }, + { + "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", + "sourcetype": "file", + "tags": ["layout", "duplicate", "offset", "UI"], + "title": "Duplicate Offset UI", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "layout", - "alignSimpleUI" - ], - "title": "alignSimpleUI", + "tags": ["layout", "spPaint3d", "paint", "tool"], + "title": "SP Paint 3d", "tooltip": "" }, { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", "sourcetype": "file", - "tags": [ - "layout", - "center_locator" - ], - "title": "center_locator", + "tags": ["layout", "randomize","UI"], + "title": "Randomize UI", "tooltip": "" }, { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", "sourcetype": "file", - "tags": [ - "layout", - "average_locator" - ], - "title": "average_locator", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "selectWithinProximityUI" - ], - "title": "selectWithinProximityUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "dupCurveUI" - ], - "title": "dupCurveUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "randomDeselectUI" - ], - "title": "randomDeselectUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "multiReferencerUI" - ], - "title": "multiReferencerUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "duplicateOffsetUI" - ], - "title": "duplicateOffsetUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "spPaint3d" - ], - "title": "spPaint3d", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "randomizeUI" - ], - "title": "randomizeUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "layout", - "distributeWithinObjectUI" - ], - "title": "distributeWithinObjectUI", + "tags": ["layout", "distribute", "ObjectUI", "within"], + "title": "Distribute Within Object UI", "tooltip": "" } ], From 5eaf66c55cba67638571079f65ce2acd555cb8b1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:51:28 +0200 Subject: [PATCH 0072/1249] added post-integrate plugin - temp --- colorbleed/plugins/publish/integrate_asset.py | 24 +---------- .../plugins/publish/post_look_integrate.py | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py index 7db26fdaab..83678f7242 100644 --- a/colorbleed/plugins/publish/integrate_asset.py +++ b/colorbleed/plugins/publish/integrate_asset.py @@ -47,35 +47,15 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # get needed data traffic = instance.data["traffic"] representations = instance.data["representations"] - version_folder = instance.data["versionFolder"] - family = instance.data["family"] - resources = instance.data("resources", []) self.log.info("Registering {} items".format(len(representations))) io.insert_many(representations) # moving files - for src_dest in traffic: - src, dest = src_dest + for src, dest in traffic: self.log.info("Copying file .. {} -> {}".format(src, dest)) self.copy_file(src, dest) - if family == "colorbleed.texture": - try: - lib.remap_resource_nodes(resources, folder=version_folder) - except Exception as e: - self.log.error(e) - - if family == "colorbleed.lookdev": - try: - tmp_dir = lib.maya_temp_folder() - resource_file = os.path.join(tmp_dir, "resources.json") - with open(resource_file, "r") as f: - resources = json.load(f) - lib.remap_resource_nodes(resources) - except Exception as e: - self.log.error(e) - self.log.info("Removing temporary files and folders ...") stagingdir = instance.data["stagingDir"] shutil.rmtree(stagingdir) @@ -100,4 +80,4 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.critical("An unexpected error occurred.") raise - shutil.copy(src, dst) \ No newline at end of file + shutil.copy(src, dst) diff --git a/colorbleed/plugins/publish/post_look_integrate.py b/colorbleed/plugins/publish/post_look_integrate.py index e69de29bb2..e66828b3c8 100644 --- a/colorbleed/plugins/publish/post_look_integrate.py +++ b/colorbleed/plugins/publish/post_look_integrate.py @@ -0,0 +1,40 @@ +import json +import os + + +import colorbleed.maya.lib as lib + +import pyblish.api + + +class IntegrateAsset(pyblish.api.InstancePlugin): + """Remap source paths for lookdev and textures + + """ + + label = "Remap source paths" + order = pyblish.api.IntegratorOrder + 0.15 + families = ["colorbleed.lookdev", + "colorbleed.texture"] + + def process(self, instance): + + family = instance.data['family'] + resources = instance.data['resources'] + version_folder = instance.data['versionFolder'] + + if family == "colorbleed.texture": + try: + lib.remap_resource_nodes(resources, folder=version_folder) + except Exception as e: + self.log.error(e) + + if family == "colorbleed.lookdev": + try: + tmp_dir = lib.maya_temp_folder() + resource_file = os.path.join(tmp_dir, "resources.json") + with open(resource_file, "r") as f: + resources = json.load(f) + lib.remap_resource_nodes(resources) + except Exception as e: + self.log.error(e) From a3e704e5bbe7c920e1c2cad2fe9676f813529485 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:52:32 +0200 Subject: [PATCH 0073/1249] removed lock_id_attr function --- colorbleed/plugins/maya/load/load_animation.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index b29bbecece..9248b49287 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -37,12 +37,9 @@ class AbcLoader(api.Loader): returnNewNodes=True) # load colorbleed ID attribute - self.lock_id_attr(nodes) self[:] = nodes - def lock_id_attr(self): - pass class CurvesLoader(api.Loader): """Specific loader of Curves for the avalon.animation family""" From e9cf2e964b90b3d875744cf49c6d6c738c4fd8a1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 26 Jul 2017 10:54:17 +0200 Subject: [PATCH 0074/1249] docstrings and cosmetics --- colorbleed/maya/lib.py | 4 ++-- colorbleed/maya/menu.py | 3 +++ colorbleed/plugins/maya/load/load_look.py | 5 ++++- colorbleed/plugins/maya/publish/collect_look.py | 4 ++-- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 686a08c6fe..a2715863ee 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -669,10 +669,10 @@ def get_reference_node(path): """ Get the reference node when the path is found being used in a reference Args: - path (str): + path (str): the file path to check Returns: - + node (str): name of the reference node in question """ node = cmds.file(path, query=True, referenceNode=True) reference_path = cmds.referenceQuery(path, filename=True) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 4b09f87f1b..003c303097 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -24,6 +24,9 @@ def deferred(): from scriptsmenu import launchformaya import scriptsmenu.scriptsmenu as menu + reload(launchformaya) + reload(menu) + log.info("Attempting to install ...") # load configuration of custom menu diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 9dffb3c550..557d18a3c2 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -30,7 +30,7 @@ class LookLoader(api.Loader): """ - reference_node = None + # improve readability of the namespace assetname = context["asset"]["name"] @@ -40,6 +40,9 @@ class LookLoader(api.Loader): format="%03d", suffix="_look") + # try / except here is to ensure that the get_reference_node + # does not fail when the file doesn't exist yet + reference_node = None try: reference_node = lib.get_reference_node(self.fname) except: diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index eff9c29f55..8511914313 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -121,7 +121,7 @@ class CollectLook(pyblish.api.InstancePlugin): "relationships": sets.values(), "sets": looksets} # Collect textures - resources = [self.collect_resources(n) for n in cmds.ls(type="file")] + resources = [self.collect_resource(n) for n in cmds.ls(type="file")] instance.data["resources"] = resources # Log a warning when no relevant sets were retrieved for the look. @@ -322,7 +322,7 @@ class CollectLook(pyblish.api.InstancePlugin): return attributes - def collect_resources(self, node, verbose=False): + def collect_resource(self, node, verbose=False): """Collect the link to the file(s) used (resource) Args: node (str): name of the node From 6d7262d26b9b6b6fab3fd7498d0b22b7614cbded Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 27 Jul 2017 11:46:01 +0200 Subject: [PATCH 0075/1249] fixed assignement to alembic mesh, code improvement --- colorbleed/maya/lib.py | 64 ++++++++++------------- colorbleed/plugins/maya/load/load_look.py | 39 +++++++------- 2 files changed, 47 insertions(+), 56 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index a2715863ee..4a8fd2238c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -5,6 +5,7 @@ import os import bson import json import logging +import pprint import contextlib from collections import OrderedDict, defaultdict @@ -635,13 +636,9 @@ def filter_by_id(nodes, uuids): if node is None: continue - if not cmds.attributeQuery("cbId", node=node, exists=True): - continue + attribute_value = _get_id(node) - # Deformed shaped - attr = "{}.cbId".format(node) - attribute_value = cmds.getAttr(attr) - if attribute_value not in uuids: + if attribute_value not in uuids or attribute_value is None: continue filtered_nodes.append(node) @@ -792,48 +789,45 @@ def assign_look(nodes, subset="lookDefault"): assign_look_by_version(asset_nodes, version['_id']) -def apply_shaders(relationships, shader_nodes, nodes): - """Apply all shaders to the nodes based on the relationship data +def apply_shaders(relationships, shadernodes, nodes): + """Link shadingEngine to the right nodes based on relationship data + + Relationship data is constructed of a collection of `sets` and `attributes` + `sets` corresponds with the shaderEngines found in the lookdev. + Each set has the keys `name`, `members` and `uuid`, the `members` + hold a collection of node information `name` and `uuid`. Args: - relationships (dict): shader to node relationships - shader_nodes (list): shader network nodes - nodes (list): nodes to assign to + relationships (dict): relationship data + shadernodes (list): list of nodes of the shading engine + nodes (list): list of nodes to apply shader to Returns: None """ + # attributes = relationships.get("attributes", []) shader_sets = relationships.get("sets", []) - shading_engines = cmds.ls(shader_nodes, type="shadingEngine", long=True) + + if isinstance(nodes, set): + nodes = list(nodes) + + shading_engines = cmds.ls(shadernodes, type="shadingEngine", long=True) assert len(shading_engines) > 0, ("Error in retrieving shading engine " "from reference") - # Pre-filter nodes and shader nodes - nodes_by_id = defaultdict(list) - shader_nodes_by_id = defaultdict(list) - for node in nodes: - _id = _get_id(node) - nodes_by_id[_id].append(node) - - for shader_node in shader_nodes: - _id = _get_id(shader_node) - shader_nodes_by_id[_id].append(shader_node) - - # get all nodes which we need to link per shader + # get all nodes which we need to link + ns_nodes = cmds.ls(nodes, long=True) for shader_set in shader_sets: - # collect shading engine - uuid = shader_set["uuid"] - shading_engine = shader_nodes_by_id.get(uuid, []) + # collect all unique IDs of the set members + shader_uuid = shader_set["uuid"] + member_uuids = [member["uuid"] for member in shader_set["members"]] + + filtered_nodes = filter_by_id(ns_nodes, member_uuids) + shading_engine = filter_by_id(shading_engines, [shader_uuid]) + assert len(shading_engine) == 1, ("Could not find the correct " "shading engine with cbId " - "'{}'".format(uuid)) - - # collect members - filtered_nodes = list() - for member in shader_set["members"]: - member_uuid = member["uuid"] - members = nodes_by_id.get(member_uuid, []) - filtered_nodes.extend(members) + "'{}'".format(shader_uuid)) cmds.sets(filtered_nodes, forceElement=shading_engine[0]) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 557d18a3c2..567cdf4bf5 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -30,8 +30,6 @@ class LookLoader(api.Loader): """ - - # improve readability of the namespace assetname = context["asset"]["name"] ns_assetname = "{}_".format(assetname) @@ -88,29 +86,25 @@ class LookLoader(api.Loader): """ - # types = ["transform", "mesh"] list_nodes = [] - namespaces = cmds.namespaceInfo(listOnlyNamespaces=True) - # remove basic namespaces - namespaces.remove("UI") - namespaces.remove("shared") + namespaces = [ns for ns in cmds.namespaceInfo(listOnlyNamespaces=True) + if ns not in ["UI", "shared"] or not ns.endswith("look")] - for ns in namespaces: - if not ns.startswith(assetname): + for namespace in namespaces: + if not namespace.startswith(assetname): continue + + ns_nodes = cmds.namespaceInfo(namespace, + listOnlyDependencyNodes=True) # get reference nodes - ns_nodes = cmds.namespaceInfo(ns, listOnlyDependencyNodes=True) - # TODO: might need to extend the types - # check if any nodes are connected to something else than lambert1 - list_nodes = cmds.ls(ns_nodes, long=True) - unassigned_nodes = [self.has_default_shader(n) for n in list_nodes] - nodes = [n for n in unassigned_nodes if n is not None] + list_nodes.extend([self.has_default_shader(n) for n in ns_nodes]) - list_nodes.extend(nodes) + # ensure unique nodes and kick out any None types + result = [node for node in list_nodes if node is not None] - return set(list_nodes) + return result def has_default_shader(self, node): """Check if the nodes have `initialShadingGroup` shader assigned @@ -122,12 +116,15 @@ class LookLoader(api.Loader): str """ - shaders = cmds.listConnections(node, type="shadingEngine") or [] - if "initialShadingGroup" in shaders: + shaders = cmds.listConnections(node, type="shadingEngine") + if shaders is None or "initialShadingGroup" in shaders: # return transform node - transform = cmds.listRelatives(node, parent=True, type="transform", + transform = cmds.listRelatives(node, + parent=True, + type="transform", fullPath=True) + if not transform: - return [] + return return transform[0] From 62b92dfbb3ca94f497f105499c916409df62ec5e Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 10:12:54 +0200 Subject: [PATCH 0076/1249] added previous pipeline environment paths --- .../maya/publish/_validate_node_ids.py | 46 +++ .../publish/validate_filename_convention.py | 37 +++ .../publish/collect_resource_destination.py | 94 ++++++ colorbleed/plugins/publish/integrate.py | 302 ++++++++++++++++++ maya_environment.bat | 70 ++++ python_environment.bat | 30 ++ set_environment.bat | 33 ++ 7 files changed, 612 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/_validate_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_filename_convention.py create mode 100644 colorbleed/plugins/publish/collect_resource_destination.py create mode 100644 colorbleed/plugins/publish/integrate.py create mode 100644 maya_environment.bat create mode 100644 python_environment.bat create mode 100644 set_environment.bat diff --git a/colorbleed/plugins/maya/publish/_validate_node_ids.py b/colorbleed/plugins/maya/publish/_validate_node_ids.py new file mode 100644 index 0000000000..bfb47abe33 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_node_ids.py @@ -0,0 +1,46 @@ +import pyblish.api +import colorbleed.api + + +class ValidateNodeIds(pyblish.api.InstancePlugin): + """Validate nodes have colorbleed id attributes + + All look sets should have id attributes. + + """ + + label = 'Node Id Attributes' + families = ['colorbleed.look', 'colorbleed.model'] + hosts = ['maya'] + order = colorbleed.api.ValidatePipelineOrder + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + @staticmethod + def get_invalid(instance): + import maya.cmds as cmds + + nodes = instance.data["setMembers"] + + # Ensure all nodes have a cbId + data_id = {} + invalid = [] + for node in nodes: + try: + uuid = cmds.getAttr("{}.cbId".format(node)) + data_id[uuid] = node + if uuid in data_id: + invalid.append(node) + except RuntimeError: + pass + + return invalid + + def process(self, instance): + """Process all meshes""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Nodes found with invalid" + "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_filename_convention.py b/colorbleed/plugins/maya/publish/validate_filename_convention.py new file mode 100644 index 0000000000..7a9a44e02f --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_filename_convention.py @@ -0,0 +1,37 @@ +import re + +import pyblish.api +import colorbleed.api + + +class ValidateFileNameConvention(pyblish.api.InstancePlugin): + + label = "" + families = ["colorbleed.lookdev"] + host = ["maya"] + optional = True + + order = pyblish.api.ValidatorOrder + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + invalid = [] + # todo: change pattern to company standard + pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") + + nodes = list(instance) + for node in nodes: + match = pattern.match(node) + if not match: + invalid.append(node) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + self.log.error("Found invalid naming convention. Failed noted :\n" + "%s" % invalid) diff --git a/colorbleed/plugins/publish/collect_resource_destination.py b/colorbleed/plugins/publish/collect_resource_destination.py new file mode 100644 index 0000000000..814f6fb612 --- /dev/null +++ b/colorbleed/plugins/publish/collect_resource_destination.py @@ -0,0 +1,94 @@ +import pyblish.api +import os + +import avalon.io as io + + +class CollectResourceDestination(pyblish.api.InstancePlugin): + """This plug-ins displays the comment dialog box per default""" + + label = "Collect Resource Destination" + order = pyblish.api.CollectorOrder + 0.499 + + def process(self, instance): + + self.create_destination_template(instance) + + template_data = instance.data["assumedTemplateData"] + template = instance.data["template"] + + mock_template = template.format(**template_data) + + # For now assume resources end up in a "resources" folder in the + # published folder + mock_destination = os.path.join(os.path.dirname(mock_template), + "resources") + + # Clean the path + mock_destination = os.path.abspath(os.path.normpath(mock_destination)) + + # Define resource destination and transfers + resources = instance.data.get("resources", list()) + transfers = instance.data.get("transfers", list()) + for resource in resources: + + # Add destination to the resource + source_filename = os.path.basename(resource["source"]) + destination = os.path.join(mock_destination, source_filename) + resource['destination'] = destination + + # Collect transfers for the individual files of the resource + # e.g. all individual files of a cache or UDIM textures. + files = resource['files'] + for fsrc in files: + fname = os.path.basename(fsrc) + fdest = os.path.join(mock_destination, fname) + transfers.append([fsrc, fdest]) + + instance.data["resources"] = resources + instance.data["transfers"] = transfers + + def create_destination_template(self, instance): + """Create a filepath based on the current data available + + Example template: + {root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/ + {subset}.{representation} + Args: + instance: the instance to publish + + Returns: + file path (str) + """ + + # get all the stuff from the database + subset_name = instance.data["subset"] + project_name = os.environ["AVALON_PROJECT"] + + project = io.find_one({"type": "project", + "name": project_name}, + projection={"config": True}) + template = project["config"]["template"]["publish"] + + subset = io.find_one({"type": "subset", + "name": subset_name}) + + # assume there is no version yet, we start at `1` + version_number = 1 + if subset is not None: + version = io.find_one({"type": "version", + "parent": subset["_id"]}, + sort=[("name", -1)]) + # if there is a subset there ought to be version + version_number += version["name"] + + template_data = {"root": os.environ["AVALON_ROOT"], + "project": project_name, + "silo": os.environ["AVALON_SILO"], + "asset": instance.data["asset"], + "subset": subset_name, + "version": version_number, + "representation": "TEMP"} + + instance.data["assumedTemplateData"] = template_data + instance.data["template"] = template diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py new file mode 100644 index 0000000000..2c21866514 --- /dev/null +++ b/colorbleed/plugins/publish/integrate.py @@ -0,0 +1,302 @@ +import os +import logging +import shutil + +import errno +import pyblish.api +from avalon import api, io + + +log = logging.getLogger(__name__) + + +class IntegrateAsset(pyblish.api.InstancePlugin): + """Resolve any dependency issies + + This plug-in resolves any paths which, if not updated might break + the published file. + + The order of families is important, when working with lookdev you want to + first publish the texture, update the texture paths in the nodes and then + publish the shading network. Same goes for file dependent assets. + """ + + label = "Intergrate Asset" + order = pyblish.api.IntegratorOrder + families = ["colorbleed.model", + "colorbleed.rig", + "colorbleed.animation", + "colorbleed.camera", + "colorbleed.lookdev", + "colorbleed.texture", + "colorbleed.historyLookdev", + "colorbleed.group"] + + def process(self, instance): + + self.log.info("Integrating Asset in to the database ...") + + self.register(instance) + self.intergrate(instance) + + self.log.info("Removing temporary files and folders ...") + stagingdir = instance.data["stagingDir"] + shutil.rmtree(stagingdir) + + def register(self, instance): + + # Required environment variables + PROJECT = os.environ["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] + SILO = os.environ["AVALON_SILO"] + LOCATION = os.getenv("AVALON_LOCATION") + + # todo(marcus): avoid hardcoding labels in the integrator + representation_labels = {".ma": "Maya Ascii", + ".source": "Original source file", + ".abc": "Alembic"} + + context = instance.context + # Atomicity + # + # Guarantee atomic publishes - each asset contains + # an identical set of members. + # __ + # / o + # / \ + # | o | + # \ / + # o __/ + # + assert all(result["success"] for result in context.data["results"]), ( + "Atomicity not held, aborting.") + + # Assemble + # + # | + # v + # ---> <---- + # ^ + # | + # + stagingdir = instance.data.get("stagingDir") + assert stagingdir, ("Incomplete instance \"%s\": " + "Missing reference to staging area." % instance) + + # extra check if stagingDir actually exists and is available + + self.log.debug("Establishing staging directory @ %s" % stagingdir) + + project = io.find_one({"type": "project"}, + projection={"config.template.publish": True}) + + asset = io.find_one({"type": "asset", + "name": ASSET, + "parent": project["_id"]}) + + assert all([project, asset]), ("Could not find current project or " + "asset '%s'" % ASSET) + + subset = self.get_subset(asset, instance) + + # get next version + latest_version = io.find_one({"type": "version", + "parent": subset["_id"]}, + {"name": True}, + sort=[("name", -1)]) + + next_version = 1 + if latest_version is not None: + next_version += latest_version["name"] + + self.log.debug("Next version: %i" % next_version) + + version_data = self.create_version_data(context, instance) + version = self.create_version(subset=subset, + version_number=next_version, + locations=[LOCATION], + data=version_data) + + self.log.debug("Creating version ...") + version_id = io.insert_one(version).inserted_id + + # Write to disk + # _ + # | | + # _| |_ + # ____\ / + # |\ \ / \ + # \ \ v \ + # \ \________. + # \|________| + # + root = api.registered_root() + template_data = {"root": root, + "project": PROJECT, + "silo": SILO, + "asset": ASSET, + "subset": subset["name"], + "version": version["name"]} + + template_publish = project["config"]["template"]["publish"] + + representations = [] + staging_content = os.listdir(stagingdir) + for v, fname in enumerate(staging_content): + + name, ext = os.path.splitext(fname) + template_data["representation"] = ext[1:] + + src = os.path.join(stagingdir, fname) + dst = template_publish.format(**template_data) + + # Backwards compatibility + if fname == ".metadata.json": + dirname = os.path.dirname(dst) + dst = os.path.join(dirname, fname) + + # copy source to destination (library) + instance.data["transfers"].append([src, dst]) + + representation = { + "schema": "avalon-core:representation-2.0", + "type": "representation", + "parent": version_id, + "name": ext[1:], + "data": {"label": representation_labels.get(ext)}, + "dependencies": instance.data.get("dependencies", "").split(), + + # Imprint shortcut to context + # for performance reasons. + "context": { + "project": PROJECT, + "asset": ASSET, + "silo": SILO, + "subset": subset["name"], + "version": version["name"], + "representation": ext[1:] + } + } + representations.append(representation) + + # store data for database and source / destinations + instance.data["representations"] = representations + + return representations + + def intergrate(self, instance): + """Register the representations and move the files + + Through the stored `representations` and `transfers` + + Args: + instance: the instance to integrate + """ + + # get needed data + traffic = instance.data["transfers"] + representations = instance.data["representations"] + + self.log.info("Registering {} items".format(len(representations))) + io.insert_many(representations) + + # moving files + for src, dest in traffic: + self.log.info("Copying file .. {} -> {}".format(src, dest)) + self.copy_file(src, dest) + + + def copy_file(self, src, dst): + """ Copy given source to destination + + Arguments: + src (str): the source file which needs to be copied + dst (str): the destination of the sourc file + Returns: + None + """ + + dirname = os.path.dirname(dst) + try: + os.makedirs(dirname) + except OSError as e: + if e.errno == errno.EEXIST: + pass + else: + self.log.critical("An unexpected error occurred.") + raise + + shutil.copy(src, dst) + + def get_subset(self, asset, instance): + + subset = io.find_one({"type": "subset", + "parent": asset["_id"], + "name": instance.data["subset"]}) + + if subset is None: + subset_name = instance.data["subset"] + self.log.info("Subset '%s' not found, creating.." % subset_name) + + _id = io.insert_one({ + "schema": "avalon-core:subset-2.0", + "type": "subset", + "name": subset_name, + "data": {}, + "parent": asset["_id"] + }).inserted_id + + subset = io.find_one({"_id": _id}) + + return subset + + def create_version(self, subset, version_number, locations, data=None): + """ Copy given source to destination + + Arguments: + subset (dict): the registered subset of the asset + version_number (int): the version number + locations (list): the currently registered locations + """ + # Imprint currently registered location + version_locations = [location for location in locations if + location is not None] + + return {"schema": "avalon-core:version-2.0", + "type": "version", + "parent": subset["_id"], + "name": version_number, + "locations": version_locations, + "data": data} + + def create_version_data(self, context, instance): + """Create the data collection for th version + + Args: + context: the current context + instance: the current instance being published + + Returns: + dict: the required information with instance.data as key + """ + + families = [] + current_families = instance.data.get("families", list()) + instance_family = instance.data.get("family", None) + + families += current_families + if instance_family is not None: + families.append(instance_family) + + # create relative source path for DB + relative_path = os.path.relpath(context.data["currentFile"], + api.registered_root()) + source = os.path.join("{root}", relative_path).replace("\\", "/") + + version_data = {"families": families, + "time": context.data["time"], + "author": context.data["user"], + "source": source, + "comment": context.data.get("comment")} + + return dict(instance.data, **version_data) diff --git a/maya_environment.bat b/maya_environment.bat new file mode 100644 index 0000000000..efeb2d4063 --- /dev/null +++ b/maya_environment.bat @@ -0,0 +1,70 @@ +@echo OFF + +echo Entering Maya2016 environment... + +:: Environment: Maya +set CB_MAYA_VERSION=2016 +set CB_MAYA_SHARED=%CB_APP_SHARED%\maya_shared\%CB_MAYA_VERSION% + +if "%CB_MAYA_SHARED%" == "" ( + echo Error: "CB_MAYA_SHARED" not set + goto :eof +) + + +:: Colorbleed Maya +set PYTHONPATH=%CB_PIPELINE%\git\cbMayaScripts;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\inventory\python;%PYTHONPATH% + +:: Maya shared +set MAYA_PLUG_IN_PATH=%CB_MAYA_SHARED%\plugins;%MAYA_PLUGIN_PATH% +set MAYA_SHELF_PATH=%CB_MAYA_SHARED%\prefs\shelves;%MAYA_SHELF_PATH% +set MAYA_SCRIPT_PATH=%CB_MAYA_SHARED%\scripts;%MAYA_SCRIPT_PATH% +set XBMLANGPATH=%CB_MAYA_SHARED%\prefs\icons;%XBMLANGPATH% +set MAYA_PRESET_PATH=%CB_MAYA_SHARED%\prefs\attrPresets;%MAYA_PRESET_PATH% +set PYTHONPATH=%CB_MAYA_SHARED%\scripts;%PYTHONPATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules;%MAYA_MODULE_PATH% + +:: Additional modules +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\mGear_2016;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\SOuP;%MAYA_MODULE_PATH% +set MAYA_SHELF_PATH=%CB_MAYA_SHARED%\modules\SOuP\shelves;%MAYA_SHELF_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\pdipro35c_Maya2016x64;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\ovdb\maya\maya2016;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\cvshapeinverter;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Toolchefs;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Exocortex;%MAYA_MODULE_PATH% + +:: Miarmy +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Basefount\Miarmy;%MAYA_MODULE_PATH% +set PATH=%CB_MAYA_SHARED%\modules\Basefount\Miarmy\bin;%PATH% +set VRAY_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Basefount\Miarmy\bin\vray\vray_3.1_3.3_3.4\Maya2015and2016;%VRAY_PLUGINS_x64%; + +:: Yeti +set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64;%MAYA_MODULE_PATH% +set PATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%PATH%; +set VRAY_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%VRAY_PLUGINS_x64%; +set VRAY_FOR_MAYA2016_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%VRAY_FOR_MAYA2016_PLUGINS_x64%; +set REDSHIFT_MAYAEXTENSIONSPATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\plug-ins;%REDSHIFT_MAYAEXTENSIONSPATH% +set peregrinel_LICENSE=5053@CBserver + +:: maya-capture +set PYTHONPATH=%CB_PIPELINE%\git\maya-capture;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\maya-capture-gui;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\maya-capture-gui-cb;%PYTHONPATH% + +:: maya-matrix-deform +set PYTHONPATH=%CB_PIPELINE%\git\maya-matrix-deformers;%PYTHONPATH% +set MAYA_PLUG_IN_PATH=%CB_PIPELINE%\git\maya-matrix-deformers\plugin;%MAYA_PLUG_IN_PATH% + +:: rapid-rig +set XBMLANGPATH=%CB_MAYA_SHARED%\scripts\RapidRig_Modular_V02;%XBMLANGPATH% +set MAYA_SCRIPT_PATH=%CB_MAYA_SHARED%\scripts\RapidRig_Modular_V02;%MAYA_SCRIPT_PATH% + + +:: Fix Maya Playblast Color Management depth +set MAYA_FLOATING_POINT_RT_PLAYBLAST=1 + + +:: Fix V-ray forcing affinity to 100% +set VRAY_USE_THREAD_AFFINITY=0 \ No newline at end of file diff --git a/python_environment.bat b/python_environment.bat new file mode 100644 index 0000000000..50fafd391c --- /dev/null +++ b/python_environment.bat @@ -0,0 +1,30 @@ +@echo OFF +echo Entering Python environment... + +set CB_PYTHON_VERSION=2.7 + +where /Q python.exe +if ERRORLEVEL 1 ( + if EXIST C:\Python27\python.exe ( + echo Adding C:\Python27 to PATH + set "PATH=%PATH%;C:\Python27" + goto:has-python + ) else ( + echo Adding embedded python (pipeline) + set "PATH=%PATH%;%CB_APP_SHARED%\python\standalone\%CB_PYTHON_VERSION%\bin" + goto:has-python + ) +) +:has-python + +:: Python universal (non-compiled) +set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\universal\site-packages + +:: Python version/windows-specific +:: set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\win\%CB_PYTHON_VERSION% + +:: Python standalone (compiled to version) +if NOT "%CB_PYTHON_STANDALONE%" == "0" ( + echo Entering Python Standalone environment... + set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\standalone\%CB_PYTHON_VERSION%\site-packages +) diff --git a/set_environment.bat b/set_environment.bat new file mode 100644 index 0000000000..832d1de1d5 --- /dev/null +++ b/set_environment.bat @@ -0,0 +1,33 @@ +@echo off +echo Entering pipeline (raw development) environment... + +:: Initialize environment +set CB_PIPELINE=P:\pipeline\dev + +set CB_APP_SHARED=%CB_PIPELINE%\apps + +if "%CB_APP_SHARED%" == "" ( + echo Error: "CB_APP_SHARED" not set + goto :eof +) + +echo setting STORAGE.. +set STORAGE=P: + +set LAUNCHER_ROOT=%~dp0/launchers + +:: Core +echo Add cb core.. +set PYTHONPATH=%CB_PIPELINE%\git\cb;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\cbra;%PYTHONPATH% + +:: Extra +set PYTHONPATH=%CB_PIPELINE%\git\pyseq;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\Qt.py;%PYTHONPATH% + + +:: Ftrack-connect +::set PYTHONPATH=%CB_PIPELINE%\git\ftrack-connect\source;%PYTHONPATH% + +:: FFMPEG +set FFMPEG_PATH=%CB_APP_SHARED%\ffmpeg\bin\ffmpeg.exe \ No newline at end of file From c8821cbe05ab37eedbce667ebece5de79a3b5d56 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 10:27:17 +0200 Subject: [PATCH 0077/1249] updated collect look and model, improved extract look to ensure textures can be publish with lookdev --- .../plugins/maya/publish/collect_look.py | 14 ++++++-- .../plugins/maya/publish/collect_model.py | 2 +- .../plugins/maya/publish/extract_look.py | 33 ++++++++++++------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 8511914313..f11699dd8f 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,6 +1,10 @@ +import os +import pprint + from maya import cmds import pyblish.api +import avalon.io as io from cb.utils.maya import context, shaders import cbra.utils.maya.node_uuid as id_utils @@ -120,9 +124,15 @@ class CollectLook(pyblish.api.InstancePlugin): instance.data["lookData"] = {"attributes": attributes, "relationships": sets.values(), "sets": looksets} - # Collect textures - resources = [self.collect_resource(n) for n in cmds.ls(type="file")] + + # Collect file nodes used by shading engines + history = cmds.listHistory(looksets) + files = cmds.ls(history, type="file", long=True) + + # Collect textures, + resources = [self.collect_resource(n) for n in files] instance.data["resources"] = resources + # pprint.pprint(resources) # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["sets"]: diff --git a/colorbleed/plugins/maya/publish/collect_model.py b/colorbleed/plugins/maya/publish/collect_model.py index b1294c7824..c8324a8297 100644 --- a/colorbleed/plugins/maya/publish/collect_model.py +++ b/colorbleed/plugins/maya/publish/collect_model.py @@ -11,7 +11,7 @@ class CollectModelData(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.499 - label = 'Model Data' + label = 'Collect Model Data' families = ["colorbleed.model"] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 04c632e2d9..46d45400a4 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -39,26 +39,35 @@ class ExtractLook(colorbleed.api.Extractor): # Remove all members of the sets so they are not included in the # exported file by accident - self.log.info("Extract sets (Maya ASCII)..") + self.log.info("Extract sets (Maya ASCII) ...") lookdata = instance.data["lookData"] sets = lookdata["sets"] + resources = instance.data["resources"] + remap = {} + for resource in resources: + attr = resource['attribute'] + remap[attr] = resource['destination'] + + self.log.info("Finished remapping destinations ...") + # Extract in correct render layer layer = instance.data.get("renderlayer", "defaultRenderLayer") with context.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with context.empty_sets(sets): - with avalon.maya.maintained_selection(): - cmds.select(sets, noExpand=True) - cmds.file(maya_path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) + with context.attribute_values(remap): + with avalon.maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) # Write the JSON data self.log.info("Extract json..") From e4214bc40dd079843f09f8f4e72447ce00ca552b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 10:32:27 +0200 Subject: [PATCH 0078/1249] added colorbleed scripts menu --- colorbleed/maya/__init__.py | 32 ++--- colorbleed/maya/lib.py | 20 +++- colorbleed/maya/menu.json | 232 +++++++++++++++--------------------- colorbleed/maya/menu.py | 2 +- 4 files changed, 129 insertions(+), 157 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 3097683464..4dc6833923 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -88,7 +88,7 @@ def on_new(): maya.commands.reset_resolution() -def on_save(): +def on_save(nodes=None): """Automatically add IDs to new nodes Any transform of a mesh, without an existing ID, is given one automatically on file save. @@ -102,28 +102,28 @@ def on_save(): types = ["mesh", "shadingEngine", "file", "nurbsCurve"] # the items which need to pass the id to their parent - nodes = (set(cmds.ls(type=types, long=True)) - - set(cmds.ls(long=True, readOnly=True)) - - set(cmds.ls(long=True, lockedNodes=True))) + if not nodes: + nodes = (set(cmds.ls(type=types, long=True)) - + set(cmds.ls(long=True, readOnly=True)) - + set(cmds.ls(long=True, lockedNodes=True))) - transforms = set() - for n in cmds.ls(type=types, long=True): - # pass id to parent of node if in subtypes - relatives = cmds.listRelatives(n, parent=True, fullPath=True) - if not relatives: - continue + transforms = set() + for n in cmds.ls(type=types, long=True): + # pass id to parent of node if in subtypes + relatives = cmds.listRelatives(n, parent=True, fullPath=True) + if not relatives: + continue - for r in cmds.listRelatives(n, parent=True, fullPath=True): - transforms.add(r) + for r in cmds.listRelatives(n, parent=True, fullPath=True): + transforms.add(r) - # merge transforms and nodes in one set to make sure every item - # is unique - nodes |= transforms + # merge transforms and nodes in one set to make sure every item + # is unique + nodes |= transforms # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] asset_id = io.find_one({"type": "asset", "name": asset}) - for node in nodes: if node in defaults: continue diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 4a8fd2238c..722662cc6d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -5,7 +5,6 @@ import os import bson import json import logging -import pprint import contextlib from collections import OrderedDict, defaultdict @@ -671,7 +670,12 @@ def get_reference_node(path): Returns: node (str): name of the reference node in question """ - node = cmds.file(path, query=True, referenceNode=True) + try: + node = cmds.file(path, query=True, referenceNode=True) + except RuntimeError: + log.debug('Received file not loaded : "{}"'.format(path)) + return + reference_path = cmds.referenceQuery(path, filename=True) if os.path.normpath(path) == os.path.normpath(reference_path): return node @@ -760,9 +764,15 @@ def assign_look(nodes, subset="lookDefault"): # Group all nodes per asset id grouped = defaultdict(list) for node in nodes: - colorbleed_id = cmds.getAttr("{}.cbId".format(node)) - asset_id = colorbleed_id.split(":")[0] - grouped[asset_id].append(node) + colorbleed_id = _get_id(node) + if not colorbleed_id: + continue + + parts = colorbleed_id.split(":") + if len(parts) != 2: + continue + + grouped[parts[0]].append(node) for asset_id, asset_nodes in grouped.items(): # create objectId for database diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 4931063ee7..0b14de274b 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -58,7 +58,7 @@ "tooltip": "" }, { - "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI", + "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI.py", "sourcetype": "file", "tags": [ "modeling", @@ -405,351 +405,351 @@ "Rigging": [ { "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "addCurveBetween", - "python" + "file" ], "title": "Add Curve Between" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "averageSkinWeights", - "python" + "file" ], "title": "Average Skin Weights" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "cbSmoothSkinWeightUI", - "python" + "file" ], "title": "CB Smooth Skin Weight UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "channelBoxManagerUI", - "python" + "file" ], "title": "Channel Box Manager UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "characterAutorigger", - "python" + "file" ], "title": "Character Auto Rigger" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "connectUI", - "python" + "file" ], "title": "Connect UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "copySkinWeightsLocal", - "python" + "file" ], "title": "Copy Skin Weights Local" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "createCenterLocator", - "python" + "file" ], "title": "Create Center Locator" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "freezeTransformToGroup", - "python" + "file" ], "title": "Freeze Transform To Group" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "groupSelected", - "python" + "file" ], "title": "Group Selected" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "ikHandlePoleVectorLocator", - "python" + "file" ], "title": "IK Handle Pole Vector Locator" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "jointOrientUI", - "python" + "file" ], "title": "Joint Orient UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "jointsOnCurve", - "python" + "file" ], "title": "Joints On Curve" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "resetBindSelectedSkinJoints", - "python" + "file" ], "title": "Reset Bind Selected Skin Joints" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "selectSkinclusterJointsFromSelectedComponents", - "python" + "file" ], "title": "Select Skincluster Joints From Selected Components" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "selectSkinclusterJointsFromSelectedMesh", - "python" + "file" ], "title": "Select Skincluster Joints From Selected Mesh" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "setJointLabels", - "python" + "file" ], "title": "Set Joint Labels" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "setJointOrientationFromCurrentRotation", - "python" + "file" ], "title": "Set Joint Orientation From Current Rotation" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "setSelectedJointsOrientationZero", - "python" + "file" ], "title": "Set Selected Joints Orientation Zero" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "mirrorCurveShape", - "python" + "file" ], "title": "Mirror Curve Shape" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "setRotationOrderUI", - "python" + "file" ], "title": "Set Rotation Order UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "paintItNowUI", - "python" + "file" ], "title": "Paint It Now UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "parentScaleConstraint", - "python" + "file" ], "title": "Parent Scale Constraint" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "quickSetWeightsUI", - "python" + "file" ], "title": "Quick Set Weights UI" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "rapidRig", - "python" + "file" ], "title": "Rapid Rig" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "regenerate_blendshape_targets", - "python" + "file" ], "title": "Regenerate Blendshape Targets" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "removeRotationAxis", - "python" + "file" ], "title": "Remove Rotation Axis" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "resetBindSelectedMeshes", - "python" + "file" ], "title": "Reset Bind Selected Meshes" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "simpleControllerOnSelection", - "python" + "file" ], "title": "Simple Controller On Selection" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "simpleControllerOnSelectionHierarchy", - "python" + "file" ], "title": "Simple Controller On Selection Hierarchy" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "superRelativeCluster", - "python" + "file" ], "title": "Super Relative Cluster" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "tfSmoothSkinWeight", - "python" + "file" ], "title": "TF Smooth Skin Weight" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "toggleIntermediates", - "python" + "file" ], "title": "Toggle Intermediates" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "toggleSegmentScaleCompensate", - "python" + "file" ], "title": "Toggle Segment Scale Compensate" }, { "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", - "sourcetype": "python", + "sourcetype": "file", "tags": [ "rigging", "toggleSkinclusterDeformNormals", - "python" + "file" ], "title": "Toggle Skincluster Deform Normals" } @@ -1007,12 +1007,16 @@ ] }, { - "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUI.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", "sourcetype": "file", - "tags": [ - "shading", - "LightLinkUI" - ], + "tags": ["shading", "lookdev", "assign", "shaders", "auto"], + "title": "Assign lookDefault Shader", + "tooltip": "Assign the latest 'lookDefault' to assets without any lookdev in the scene" + }, + { + "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", + "sourcetype": "file", + "tags": ["shading", "light", "link", "ui"], "title": "Light Link UI", "tooltip": "" }, @@ -1029,7 +1033,7 @@ "tooltip": "" }, { - "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", "sourcetype": "file", "tags": [ "shading", @@ -1037,7 +1041,7 @@ "DefaultShaderSet", "Behavior" ], - "title": "fixDefaultShaderSetBehavior", + "title": "Fix Default Shader Set Behavior", "tooltip": "" }, { @@ -1147,7 +1151,7 @@ "tooltip": "" }, { - "command": "", + "command": "$COLORBLEED_SCRIPTS\\layout\\spPaint3d.py", "sourcetype": "file", "tags": ["layout", "spPaint3d", "paint", "tool"], "title": "SP Paint 3d", @@ -1232,60 +1236,42 @@ { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstancesWithAnimation" - ], + "tags": ["particles", "instancerToObjectsInstancesWithAnimation"], "title": "instancerToObjectsInstancesWithAnimation", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "objectsToParticles" - ], + "tags": ["particles", "objectsToParticles"], "title": "objectsToParticles", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "add_particle_cacheFile_attrs" - ], + "tags": ["particles", "add_particle_cacheFile_attrs"], "title": "add_particle_cacheFile_attrs", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "mergeParticleSystems" - ], + "tags": ["particles", "mergeParticleSystems"], "title": "mergeParticleSystems", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "particlesToLocators" - ], + "tags": ["particles", "particlesToLocators"], "title": "particlesToLocators", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsWithAnimation" - ], + "tags": ["particles", "instancerToObjectsWithAnimation"], "title": "instancerToObjectsWithAnimation", "tooltip": "" }, @@ -1314,80 +1300,56 @@ { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "selectIntermediateObjects" - ], + "tags": ["cleanup", "selectIntermediateObjects"], "title": "selectIntermediateObjects", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "resetViewportCache" - ], + "tags": ["cleanup", "resetViewportCache"], "title": "resetViewportCache", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "selectNonUniqueNames" - ], + "tags": ["cleanup", "selectNonUniqueNames"], "title": "selectNonUniqueNames", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "uniqifyNodeNames" - ], + "tags": ["cleanup", "uniqifyNodeNames"], "title": "uniqifyNodeNames", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "selectByType" - ], + "tags": ["cleanup", "selectByType"], "title": "selectByType", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "removeNamespaces" - ], + "tags": ["cleanup", "removeNamespaces"], "title": "removeNamespaces", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "autoRenameFileNodes" - ], + "tags": ["cleanup", "autoRenameFileNodes"], "title": "autoRenameFileNodes", "tooltip": "" }, { "command": "", "sourcetype": "file", - "tags": [ - "cleanup", - "remove_user_defined_attributes" - ], + "tags": ["cleanup", "remove_user_defined_attributes"], "title": "remove_user_defined_attributes", "tooltip": "" }, diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 003c303097..a59d0310c7 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -11,7 +11,7 @@ self = sys.modules[__name__] self._menu = "colorbleed" # set colorbleed scripts path in environment keys -os.environ["COLORBLEED_SCRIPTS"] = r"P:\pipeline\dev\git\cbMayaScripts\cbMayaScripts" +os.environ["COLORBLEED_SCRIPTS"] = "P:\pipeline\dev\git\cbMayaScripts\cbMayaScripts" log = logging.getLogger(__name__) From 2c8a4eca64a81705b5d06cb60fd10436010a666d Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 10:37:19 +0200 Subject: [PATCH 0079/1249] updated loader to create a nice namespace --- .../plugins/maya/load/load_animation.py | 1 - colorbleed/plugins/maya/load/load_model.py | 40 ++++++++++--------- colorbleed/plugins/maya/load/load_rig.py | 1 + 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index 6ef3d07110..e48b29ef62 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -23,7 +23,6 @@ class AbcLoader(api.Loader): # Create unique namespace for the cameras # Get name from asset being loaded - assert "_" in name, "Naming convention not followed" assetname = "{}_".format(name.split("_")[0]) namespace = maya.unique_namespace(assetname, format="%03d", diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index ad70ad3440..f81174c14e 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,7 +1,7 @@ -from maya import cmds +import maya.cmds as cmds from avalon import api -from avalon import maya +import avalon.maya class ModelLoader(api.Loader): @@ -10,22 +10,26 @@ class ModelLoader(api.Loader): families = ["colorbleed.model"] representations = ["ma"] - label = "Reference model" + label = "Reference Model" order = -10 icon = "code-fork" color = "orange" def process(self, name, namespace, context, data): - with maya.maintained_selection(): - nodes = cmds.file( - self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName="{}:{}".format(namespace, name) - ) + # Create a readable namespace + # Namespace should contain asset name and counter + # TEST_001{_descriptor} where `descriptor` can be `_abc` for example + assetname = "{}_".format(namespace.split("_")[0]) + namespace = avalon.maya.unique_namespace(assetname, format="%03d") + + with avalon.maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) self[:] = nodes @@ -54,15 +58,13 @@ class ModelGPUCacheLoader(api.Loader): cmds.loadPlugin("gpuCache", quiet=True) # Create transform with shape - transform = cmds.createNode("transform", - name=name) - cache = cmds.createNode("gpuCache", - parent=transform, - name="{0}Shape".format(name)) + node_name = "{0}Shape".format(name) + transform = cmds.createNode("transform", name=name) + cache = cmds.createNode("gpuCache", parent=transform, name=node_name) # Set the cache filepath - cmds.setAttr(cache + '.cacheFileName', path, type="string") - cmds.setAttr(cache + '.cacheGeomPath', "|", type="string") # root + cmds.setAttr('{}.cacheFileName'.format(cache), path, type="string") + cmds.setAttr('{}.cacheGeomPath'.format(cache), "|", type="string") # root # Select the transform cmds.select(transform, r=1) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 5604ec22c5..0dffefd84b 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -22,6 +22,7 @@ class RigLoader(api.Loader): def process(self, name, namespace, context, data): + assert "_" in name, "Naming convention not followed" assetname = "{}_".format(context["asset"]["name"]) unique_namespace = maya.unique_namespace(assetname, format="%03d") nodes = cmds.file(self.fname, From 816b9923982c094095ba857ced0e6a4f04051075 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 10:44:38 +0200 Subject: [PATCH 0080/1249] removed redundant modules --- colorbleed/plugins/publish/integrate_asset.py | 80 ------ .../plugins/publish/post_look_integrate.py | 40 --- colorbleed/plugins/publish/pre_integrate.py | 251 ------------------ 3 files changed, 371 deletions(-) delete mode 100644 colorbleed/plugins/publish/integrate_asset.py delete mode 100644 colorbleed/plugins/publish/post_look_integrate.py delete mode 100644 colorbleed/plugins/publish/pre_integrate.py diff --git a/colorbleed/plugins/publish/integrate_asset.py b/colorbleed/plugins/publish/integrate_asset.py deleted file mode 100644 index fd4469f871..0000000000 --- a/colorbleed/plugins/publish/integrate_asset.py +++ /dev/null @@ -1,80 +0,0 @@ -import os -import errno -import shutil - -import pyblish.api -from avalon import io - - -class IntegrateAsset(pyblish.api.InstancePlugin): - """Write to files and metadata - - This plug-in exposes your data to others by encapsulating it - into a new version. - - Schema: - Data is written in the following format. - ____________________ - | | - | version | - | ________________ | - | | | | - | | representation | | - | |________________| | - | | | | - | | ... | | - | |________________| | - |____________________| - - """ - - label = "Integrate Asset" - order = pyblish.api.IntegratorOrder + 0.1 - families = ["colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.camera", - "colorbleed.lookdev", - "colorbleed.texture", - "colorbleed.historyLookdev", - "colorbleed.group"] - - def process(self, instance): - - # get needed data - traffic = instance.data["traffic"] - representations = instance.data["representations"] - - self.log.info("Registering {} items".format(len(representations))) - io.insert_many(representations) - - # moving files - for src, dest in traffic: - self.log.info("Copying file .. {} -> {}".format(src, dest)) - self.copy_file(src, dest) - - self.log.info("Removing temporary files and folders ...") - stagingdir = instance.data["stagingDir"] - shutil.rmtree(stagingdir) - - def copy_file(self, src, dst): - """ Copy given source to destination - - Arguments: - src (str): the source file which needs to be copied - dst (str): the destination of the sourc file - Returns: - None - """ - - dirname = os.path.dirname(dst) - try: - os.makedirs(dirname) - except OSError as e: - if e.errno == errno.EEXIST: - pass - else: - self.log.critical("An unexpected error occurred.") - raise - - shutil.copy(src, dst) diff --git a/colorbleed/plugins/publish/post_look_integrate.py b/colorbleed/plugins/publish/post_look_integrate.py deleted file mode 100644 index e66828b3c8..0000000000 --- a/colorbleed/plugins/publish/post_look_integrate.py +++ /dev/null @@ -1,40 +0,0 @@ -import json -import os - - -import colorbleed.maya.lib as lib - -import pyblish.api - - -class IntegrateAsset(pyblish.api.InstancePlugin): - """Remap source paths for lookdev and textures - - """ - - label = "Remap source paths" - order = pyblish.api.IntegratorOrder + 0.15 - families = ["colorbleed.lookdev", - "colorbleed.texture"] - - def process(self, instance): - - family = instance.data['family'] - resources = instance.data['resources'] - version_folder = instance.data['versionFolder'] - - if family == "colorbleed.texture": - try: - lib.remap_resource_nodes(resources, folder=version_folder) - except Exception as e: - self.log.error(e) - - if family == "colorbleed.lookdev": - try: - tmp_dir = lib.maya_temp_folder() - resource_file = os.path.join(tmp_dir, "resources.json") - with open(resource_file, "r") as f: - resources = json.load(f) - lib.remap_resource_nodes(resources) - except Exception as e: - self.log.error(e) diff --git a/colorbleed/plugins/publish/pre_integrate.py b/colorbleed/plugins/publish/pre_integrate.py deleted file mode 100644 index a6c798914d..0000000000 --- a/colorbleed/plugins/publish/pre_integrate.py +++ /dev/null @@ -1,251 +0,0 @@ -import os -import logging - -import pyblish.api -from avalon import api, io -import colorbleed.filetypes as filetypes - - -log = logging.getLogger(__name__) - - -class PreIntegrateAsset(pyblish.api.InstancePlugin): - """Resolve any dependency issies - - This plug-in resolves any paths which, if not updated might break - the published file. - - The order of families is important, when working with lookdev you want to - first publish the texture, update the texture paths in the nodes and then - publish the shading network. Same goes for file dependent assets. - """ - - label = "Pre Intergrate Asset" - order = pyblish.api.IntegratorOrder - families = ["colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.camera", - "colorbleed.lookdev", - "colorbleed.texture", - "colorbleed.historyLookdev", - "colorbleed.group"] - - def process(self, instance): - - # Required environment variables - PROJECT = os.environ["AVALON_PROJECT"] - ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - SILO = os.environ["AVALON_SILO"] - LOCATION = os.getenv("AVALON_LOCATION") - - # todo(marcus): avoid hardcoding labels in the integrator - representation_labels = {".ma": "Maya Ascii", - ".source": "Original source file", - ".abc": "Alembic"} - - context = instance.context - # Atomicity - # - # Guarantee atomic publishes - each asset contains - # an identical set of members. - # __ - # / o - # / \ - # | o | - # \ / - # o __/ - # - assert all(result["success"] for result in context.data["results"]), ( - "Atomicity not held, aborting.") - - # Assemble - # - # | - # v - # ---> <---- - # ^ - # | - # - stagingdir = instance.data.get("stagingDir") - assert stagingdir, ("Incomplete instance \"%s\": " - "Missing reference to staging area." % instance) - - # extra check if stagingDir actually exists and is available - - self.log.debug("Establishing staging directory @ %s" % stagingdir) - - project = io.find_one({"type": "project"}) - asset = io.find_one({"name": ASSET}) - - assert all([project, asset]), ("Could not find current project or " - "asset '%s'" % ASSET) - - subset = self.get_subset(asset, instance) - - # get next version - latest_version = io.find_one({"type": "version", - "parent": subset["_id"]}, - {"name": True}, - sort=[("name", -1)]) - - next_version = 1 - if latest_version is not None: - next_version += latest_version["name"] - - self.log.debug("Next version: %i" % next_version) - - version_data = self.create_version_data(context, instance) - version = self.create_version(subset=subset, - version_number=next_version, - locations=[LOCATION], - data=version_data) - - self.log.debug("Creating version ...") - version_id = io.insert_one(version).inserted_id - - # Write to disk - # _ - # | | - # _| |_ - # ____\ / - # |\ \ / \ - # \ \ v \ - # \ \________. - # \|________| - # - root = api.registered_root() - template_data = {"root": root, - "project": PROJECT, - "silo": SILO, - "asset": ASSET, - "subset": subset["name"], - "version": version["name"]} - - template_publish = project["config"]["template"]["publish"] - - representations = [] - traffic = [] - staging_content = os.listdir(stagingdir) - for v, fname in enumerate(staging_content): - - name, ext = os.path.splitext(fname) - template_data["representation"] = ext[1:] - - src = os.path.join(stagingdir, fname) - dst = template_publish.format(**template_data) - if v == 0: - instance.data["versionFolder"] = os.path.dirname(dst) - - # Files to copy as if or to specific folder - if ext in filetypes.accepted_images_types: - dirname = os.path.dirname(dst) - dst = os.path.join(dirname, fname) - - # Backwards compatibility - if fname == ".metadata.json": - dirname = os.path.dirname(dst) - dst = os.path.join(dirname, fname) - - # copy source to destination (library) - traffic.append([src, dst]) - - representation = { - "schema": "avalon-core:representation-2.0", - "type": "representation", - "parent": version_id, - "name": ext[1:], - "data": {"label": representation_labels.get(ext)}, - "dependencies": instance.data.get("dependencies", "").split(), - - # Imprint shortcut to context - # for performance reasons. - "context": { - "project": PROJECT, - "asset": ASSET, - "silo": SILO, - "subset": subset["name"], - "version": version["name"], - "representation": ext[1:] - } - } - representations.append(representation) - - # store data for database and source / destinations - instance.data["representations"] = representations - instance.data["traffic"] = traffic - - return representations - - def get_subset(self, asset, instance): - - subset = io.find_one({"type": "subset", - "parent": asset["_id"], - "name": instance.data["subset"]}) - - if subset is None: - subset_name = instance.data["subset"] - self.log.info("Subset '%s' not found, creating.." % subset_name) - - _id = io.insert_one({ - "schema": "avalon-core:subset-2.0", - "type": "subset", - "name": subset_name, - "data": {}, - "parent": asset["_id"] - }).inserted_id - - subset = io.find_one({"_id": _id}) - - return subset - - def create_version(self, subset, version_number, locations, data=None): - """ Copy given source to destination - - Arguments: - subset (dict): the registered subset of the asset - version_number (int): the version number - locations (list): the currently registered locations - """ - # Imprint currently registered location - version_locations = [location for location in locations if - location is not None] - - return {"schema": "avalon-core:version-2.0", - "type": "version", - "parent": subset["_id"], - "name": version_number, - "locations": version_locations, - "data": data} - - def create_version_data(self, context, instance): - """Create the data collection for th version - - Args: - context: the current context - instance: the current instance being published - - Returns: - dict: the required information with instance.data as key - """ - - families = [] - current_families = instance.data.get("families", list()) - instance_family = instance.data.get("family", None) - - families += current_families - if instance_family is not None: - families.append(instance_family) - - # create relative source path for DB - relative_path = os.path.relpath(context.data["currentFile"], - api.registered_root()) - source = os.path.join("{root}", relative_path).replace("\\", "/") - - version_data = {"families": families, - "time": context.data["time"], - "author": context.data["user"], - "source": source, - "comment": context.data.get("comment")} - - return dict(instance.data, **version_data) From 0e7999adb15beab441ab7a4ab68c9e8753e7cc64 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 11:44:43 +0200 Subject: [PATCH 0081/1249] removed redudant modues, improved validate_unique_node_ids --- .../maya/publish/_validate_look_node_ids.py | 41 ------------------- .../publish/validate_naming_convention.py | 34 --------------- .../maya/publish/validate_unique_node_ids.py | 26 ++++++++---- 3 files changed, 17 insertions(+), 84 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_look_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/validate_naming_convention.py diff --git a/colorbleed/plugins/maya/publish/_validate_look_node_ids.py b/colorbleed/plugins/maya/publish/_validate_look_node_ids.py deleted file mode 100644 index fcb91701e7..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_look_node_ids.py +++ /dev/null @@ -1,41 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateLookNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have colorbleed id attributes - - All look sets should have id attributes. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.look'] - hosts = ['maya'] - label = 'Look Id Attributes' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @staticmethod - def get_invalid(instance): - import maya.cmds as cmds - - nodes = instance.data["lookSets"] - - # Ensure all nodes have a cbId - invalid = list() - for node in nodes: - uuid = cmds.attributeQuery("mbId", node=node, exists=True) - if not uuid: - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Nodes found without " - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_naming_convention.py b/colorbleed/plugins/maya/publish/validate_naming_convention.py deleted file mode 100644 index 7dbf9ad3f7..0000000000 --- a/colorbleed/plugins/maya/publish/validate_naming_convention.py +++ /dev/null @@ -1,34 +0,0 @@ -import re - -import pyblish.api -import colorbleed.api - - -class ValidateNamingConvention(pyblish.api.InstancePlugin): - - label = "" - families = ["colorbleed.model"] - host = ["maya"] - actions = [colorbleed.api.SelectInvalidAction] - - @staticmethod - def get_invalid(instance): - - invalid = [] - # todo: change pattern to company standard - pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") - - nodes = list(instance) - for node in nodes: - match = pattern.match(node) - if not match: - invalid.append(node) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - self.log.error("Found invalid naming convention. Failed noted :\n" - "%s" % invalid) diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index 8e3ccb8a43..9dbd62e7ff 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -10,14 +10,17 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): """Validate nodes have colorbleed id attributes""" order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.model'] - hosts = ['maya'] label = 'Unique Id Attributes' + hosts = ['maya'] + families = ['colorbleed.model', + 'colorbleed.lookdev', + 'colorbleed.rig'] + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] - @staticmethod - def get_invalid_dict(instance): + @classmethod + def get_invalid_dict(cls, instance): """Return a dictionary mapping of id key to list of member nodes""" uuid_attr = "cbId" @@ -25,18 +28,21 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): # Collect each id with their members ids = defaultdict(list) for member in instance: - has_attr = cmds.attributeQuery(uuid_attr, node=member, exists=True) - if not has_attr: + try: + object_id = cmds.getAttr("{}.{}".format(member, uuid_attr)) + except Exception as exception: + # Object will node have the attribute so skip + cls.log.debug(exception) continue - mbid = cmds.getAttr("{}.{}".format(member, uuid_attr)) - ids[mbid].append(member) + + ids[object_id].append(member) # Skip those without IDs (if everything should have an ID that should # be another validation) ids.pop(None, None) # Take only the ids with more than one member - invalid = dict((id, members) for id, members in ids.iteritems() if + invalid = dict((_id, members) for _id, members in ids.iteritems() if len(members) > 1) return invalid @@ -61,3 +67,5 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Nodes found with non-unique " "asset IDs: {0}".format(invalid)) + + From 63deb2a652d8831858649e775f1000e2570b6ae5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 11:45:46 +0200 Subject: [PATCH 0082/1249] improved Generate UUID action --- colorbleed/action.py | 88 +++++++++++--------------------------------- 1 file changed, 21 insertions(+), 67 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 382699035e..360b17ccc7 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,11 +1,9 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import -import os import uuid from maya import cmds - import pyblish.api @@ -164,7 +162,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): instance = result["instance"] errored_instances.append(instance) - # Apply pyblish.logic to get the instances for the plug-in + # Apply pyblish logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(errored_instances, plugin) # Get the nodes from the all instances that ran through this plug-in @@ -178,78 +176,34 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): self.log.info("No invalid nodes found.") return - # Ensure unique (process each node only once) + # Ensure unique ( process each node only once ) invalid = list(set(invalid)) # Parse context from current file - self.log.info("Parsing current context..") - print(">>> DEBUG CONTEXT :", context) - print(">>> DEBUG CONTEXT DATA:", context.data) + self.log.info("Updating node IDs ...") + # Update the attributes + self._update_id_attribute(invalid) - # # Generate and add the ids to the nodes - node_ids = self.generate_ids(context, invalid) - self.apply_ids(node_ids) self.log.info("Generated ids on nodes: {0}".format(invalid)) - def get_context(self, instance=None): + def _update_id_attribute(self, nodes): + """Delete the id attribute - PROJECT = os.environ["AVALON_PROJECT"] - ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - SILO = os.environ["AVALON_SILO"] - LOCATION = os.getenv("AVALON_LOCATION") - - return {"project": PROJECT, - "asset": ASSET, - "silo": SILO, - "location": LOCATION} - - def generate_ids(self, context, nodes): - """Generate cb UUIDs for nodes. - - The identifiers are formatted like: - assets:character/test:bluey:46D221D9-4150-8E49-6B17-43B04BFC26B6 - - This is a concatenation of: - - entity (shots or assets) - - folders (parent hierarchy) - - asset (the name of the asset) - - uuid (unique id for node in the scene) - - Raises: - RuntimeError: When context can't be parsed of the current asset - - Returns: - dict: node, uuid dictionary - - """ - - # Make a copy of the context - data = context.copy() - - # Define folders - - node_ids = dict() - for node in nodes: - # Generate a unique ID per node - data['uuid'] = uuid.uuid4() - unique_id = "{asset}:{item}:{uuid}".format(**data) - node_ids[node] = unique_id - - return node_ids - - def apply_ids(self, node_ids): - """Apply the created unique IDs to the node Args: - node_ids (dict): each node with a unique id - - Returns: - None + nodes (list): all nodes to remove the attribute from """ - attribute = "mbId" - for node, id in node_ids.items(): - # check if node has attribute - if not cmds.attributeQuery(attribute, node=node, exists=True): - cmds.addAttr(node, longName=attribute, dataType="string") + for node in nodes: + + # get the database asset id + attr = "{}.cbId".format(node) + id_attr = cmds.getAttr(attr) + asset_id = id_attr.split(":")[0] + + # create a new unique id + _, uid = str(uuid.uuid4()).rsplit("-", 1) + cb_uid = "{}:{}".format(asset_id, uid) + + # set the new id + cmds.setAttr(attr, cb_uid, type="string") - cmds.setAttr("{}.{}".format(node, attribute), id) From c2cb08df8387ad3ddc359c1c5d1f23faeae2ead5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 12:05:03 +0200 Subject: [PATCH 0083/1249] updated families to have abbreviation attribute --- colorbleed/plugin.py | 6 ++++++ colorbleed/plugins/maya/create/colorbleed_animation.py | 6 ++++-- colorbleed/plugins/maya/create/colorbleed_camera.py | 5 +++-- colorbleed/plugins/maya/create/colorbleed_groom.py | 5 +++-- colorbleed/plugins/maya/create/colorbleed_instancer.py | 5 +++-- colorbleed/plugins/maya/create/colorbleed_mayaascii.py | 5 +++-- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 63a8b0e278..6c7a7870b0 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -1,5 +1,6 @@ import tempfile import pyblish.api +import avalon.maya ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 @@ -8,6 +9,11 @@ ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 +class Creator(avalon.maya.Creator): + + abbreviation = "" + + class Extractor(pyblish.api.InstancePlugin): """Extractor base class. diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 2030042a01..d9bbfb3d7a 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,14 +1,16 @@ from collections import OrderedDict -import avalon.maya + +import colorbleed.plugin from colorbleed.maya import lib -class CreateAnimation(avalon.maya.Creator): +class CreateAnimation(colorbleed.plugin.Creator): """THe animated objects in the scene""" name = "animationDefault" label = "Animation" family = "colorbleed.animation" + abbreviation = "anim" def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 749945caf3..50fd85949f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,14 +1,15 @@ from collections import OrderedDict -import avalon.maya +import colorbleed.plugin from colorbleed.maya import lib -class CreateCamera(avalon.maya.Creator): +class CreateCamera(colorbleed.plugin.Creator): """Single baked camera extraction""" name = "cameraDefault" label = "Camera" family = "colorbleed.camera" + abbreviation = "cam" def __init__(self, *args, **kwargs): super(CreateCamera, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_groom.py b/colorbleed/plugins/maya/create/colorbleed_groom.py index 6f8d0f72f7..82815c7dc7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_groom.py +++ b/colorbleed/plugins/maya/create/colorbleed_groom.py @@ -1,9 +1,10 @@ -import avalon.maya +import colorbleed.plugin -class CreateGroom(avalon.maya.Creator): +class CreateGroom(colorbleed.plugin.Creator): """Hair / fur definition for an asset""" name = "groomDefault" label = "Groom" family = "colorbleed.groom" + abbreviation = "groom" diff --git a/colorbleed/plugins/maya/create/colorbleed_instancer.py b/colorbleed/plugins/maya/create/colorbleed_instancer.py index 4721253752..4ed159d626 100644 --- a/colorbleed/plugins/maya/create/colorbleed_instancer.py +++ b/colorbleed/plugins/maya/create/colorbleed_instancer.py @@ -1,9 +1,10 @@ -import avalon.maya +import colorbleed.plugin -class CreateInstance(avalon.maya.Creator): +class CreateInstance(colorbleed.plugin.Creator): """Maya instancer using cached particles""" name = "instanceDefault" label = "Instance" family = "colorbleed.instance" + abbreviation = "inst" diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py index 2c19e13711..4a29c4dac1 100644 --- a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py +++ b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py @@ -1,9 +1,10 @@ -import avalon.maya +import colorbleed.plugin -class CreateMayaAscii(avalon.maya.Creator): +class CreateMayaAscii(colorbleed.plugin.Creator): """Raw Maya Ascii file of the item(s)""" name = "mayaAscii" label = "Maya Ascii" family = "colorbleed.mayaAscii" + abbreviation = "ascii" From f35818264b30d6391cd817a62e4d19cfa9f18c21 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 2 Aug 2017 14:53:27 +0200 Subject: [PATCH 0084/1249] removed colorbleed creator override, restored families --- colorbleed/plugin.py | 5 ----- colorbleed/plugins/maya/create/colorbleed_animation.py | 8 ++++---- colorbleed/plugins/maya/create/colorbleed_camera.py | 5 ++--- colorbleed/plugins/maya/create/colorbleed_groom.py | 5 ++--- colorbleed/plugins/maya/create/colorbleed_instancer.py | 5 ++--- colorbleed/plugins/maya/create/colorbleed_mayaascii.py | 5 ++--- 6 files changed, 12 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 6c7a7870b0..70b2c76c6f 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -9,11 +9,6 @@ ValidateSceneOrder = pyblish.api.ValidatorOrder + 0.2 ValidateMeshOrder = pyblish.api.ValidatorOrder + 0.3 -class Creator(avalon.maya.Creator): - - abbreviation = "" - - class Extractor(pyblish.api.InstancePlugin): """Extractor base class. diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index d9bbfb3d7a..1703cfe59f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,16 +1,15 @@ from collections import OrderedDict -import colorbleed.plugin +import avalon.maya from colorbleed.maya import lib -class CreateAnimation(colorbleed.plugin.Creator): +class CreateAnimation(avalon.maya.Creator): """THe animated objects in the scene""" name = "animationDefault" label = "Animation" - family = "colorbleed.animation" - abbreviation = "anim" + family = "colorbleed.anim" def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -34,3 +33,4 @@ class CreateAnimation(colorbleed.plugin.Creator): data["visibleOnly"] = False self.data = data + self.options["abbreviation"] = self.abbreviation \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 50fd85949f..749945caf3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,15 +1,14 @@ from collections import OrderedDict -import colorbleed.plugin +import avalon.maya from colorbleed.maya import lib -class CreateCamera(colorbleed.plugin.Creator): +class CreateCamera(avalon.maya.Creator): """Single baked camera extraction""" name = "cameraDefault" label = "Camera" family = "colorbleed.camera" - abbreviation = "cam" def __init__(self, *args, **kwargs): super(CreateCamera, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_groom.py b/colorbleed/plugins/maya/create/colorbleed_groom.py index 82815c7dc7..6f8d0f72f7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_groom.py +++ b/colorbleed/plugins/maya/create/colorbleed_groom.py @@ -1,10 +1,9 @@ -import colorbleed.plugin +import avalon.maya -class CreateGroom(colorbleed.plugin.Creator): +class CreateGroom(avalon.maya.Creator): """Hair / fur definition for an asset""" name = "groomDefault" label = "Groom" family = "colorbleed.groom" - abbreviation = "groom" diff --git a/colorbleed/plugins/maya/create/colorbleed_instancer.py b/colorbleed/plugins/maya/create/colorbleed_instancer.py index 4ed159d626..4721253752 100644 --- a/colorbleed/plugins/maya/create/colorbleed_instancer.py +++ b/colorbleed/plugins/maya/create/colorbleed_instancer.py @@ -1,10 +1,9 @@ -import colorbleed.plugin +import avalon.maya -class CreateInstance(colorbleed.plugin.Creator): +class CreateInstance(avalon.maya.Creator): """Maya instancer using cached particles""" name = "instanceDefault" label = "Instance" family = "colorbleed.instance" - abbreviation = "inst" diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py index 4a29c4dac1..2c19e13711 100644 --- a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py +++ b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py @@ -1,10 +1,9 @@ -import colorbleed.plugin +import avalon.maya -class CreateMayaAscii(colorbleed.plugin.Creator): +class CreateMayaAscii(avalon.maya.Creator): """Raw Maya Ascii file of the item(s)""" name = "mayaAscii" label = "Maya Ascii" family = "colorbleed.mayaAscii" - abbreviation = "ascii" From 8becf73791d560325914b4afd2a94f62cb434f69 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 3 Aug 2017 15:36:53 +0200 Subject: [PATCH 0085/1249] improved functions and ready for look assignment tool --- colorbleed/maya/__init__.py | 3 +- colorbleed/maya/lib.py | 54 ++++++++++------- colorbleed/plugins/maya/load/load_look.py | 58 +------------------ .../plugins/maya/publish/collect_look.py | 5 -- 4 files changed, 36 insertions(+), 84 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 4dc6833923..300688cc7b 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -99,7 +99,8 @@ def on_save(nodes=None): defaults = ["initialShadingGroup", "initialParticleSE"] # the default items which always want to have an ID - types = ["mesh", "shadingEngine", "file", "nurbsCurve"] + # objectSets include: shading engines, vray object properties + types = ["mesh", "objectSet", "file", "nurbsCurve"] # the items which need to pass the id to their parent if not nodes: diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 722662cc6d..7f6a21a66e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -740,7 +740,7 @@ def assign_look_by_version(nodes, version_id): reference=True, returnNewNodes=True) else: - log.info("Reusing existing lookdev..") + log.info("Reusing existing lookdev '{}'".format(reference_node)) shader_nodes = cmds.referenceQuery(reference_node, nodes=True) # Assign relationships @@ -768,26 +768,29 @@ def assign_look(nodes, subset="lookDefault"): if not colorbleed_id: continue - parts = colorbleed_id.split(":") - if len(parts) != 2: - continue - + parts = colorbleed_id.split(":", 1) grouped[parts[0]].append(node) for asset_id, asset_nodes in grouped.items(): # create objectId for database - asset_id = bson.ObjectId(asset_id) - subset = io.find_one({"type": "subset", - "name": subset, - "parent": asset_id}) + try: + asset_id = bson.ObjectId(asset_id) + except Exception: + log.warning("Asset ID is not compatible with bson") + continue + subset_data = io.find_one({"type": "subset", + "name": subset, + "parent": asset_id}) - assert subset, "No subset found for {}".format(asset_id) + if not subset_data: + log.warning("No subset '{}' found for {}".format(subset, asset_id)) + continue # get last version - version = io.find_one({"parent": subset['_id'], + version = io.find_one({"parent": subset_data['_id'], "type": "version", "data.families": - {"$in":["colorbleed.lookdev"]} + {"$in": ["colorbleed.lookdev"]} }, sort=[("name", -1)], projection={"_id": True}) @@ -816,28 +819,37 @@ def apply_shaders(relationships, shadernodes, nodes): None """ - # attributes = relationships.get("attributes", []) shader_sets = relationships.get("sets", []) - if isinstance(nodes, set): - nodes = list(nodes) - shading_engines = cmds.ls(shadernodes, type="shadingEngine", long=True) - assert len(shading_engines) > 0, ("Error in retrieving shading engine " + assert len(shading_engines) > 0, ("Error in retrieving shading engines " "from reference") - # get all nodes which we need to link - ns_nodes = cmds.ls(nodes, long=True) + # region compute lookup + ns_nodes_by_id = defaultdict(list) + for node in nodes: + ns_nodes_by_id[_get_id(node)].append(node) + + shading_engines_by_id = defaultdict(list) + for shad in shading_engines: + shading_engines_by_id[_get_id(shad)].append(shad) + # endregion + + # region assign for shader_set in shader_sets: # collect all unique IDs of the set members shader_uuid = shader_set["uuid"] member_uuids = [member["uuid"] for member in shader_set["members"]] - filtered_nodes = filter_by_id(ns_nodes, member_uuids) - shading_engine = filter_by_id(shading_engines, [shader_uuid]) + filtered_nodes = list() + for uuid in member_uuids: + filtered_nodes.extend(ns_nodes_by_id[uuid]) + shading_engine = shading_engines_by_id[shader_uuid] assert len(shading_engine) == 1, ("Could not find the correct " "shading engine with cbId " "'{}'".format(shader_uuid)) cmds.sets(filtered_nodes, forceElement=shading_engine[0]) + + # endregion diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 567cdf4bf5..d0b05d1252 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -69,62 +69,6 @@ class LookLoader(api.Loader): # Get all nodes which belong to a matching name space # Currently this is the safest way to get all the nodes - namespace_nodes = self.get_namespace_nodes(assetname) - lib.apply_shaders(relationships, nodes, namespace_nodes) + lib.apply_shaders(relationships, nodes) self[:] = nodes - - def get_namespace_nodes(self, assetname): - """ - Get all nodes of namespace `asset_*` and check if they have a shader - assigned, if not add to list - Args: - context (dict): current context of asset - - Returns: - list - - """ - - list_nodes = [] - - # remove basic namespaces - namespaces = [ns for ns in cmds.namespaceInfo(listOnlyNamespaces=True) - if ns not in ["UI", "shared"] or not ns.endswith("look")] - - for namespace in namespaces: - if not namespace.startswith(assetname): - continue - - ns_nodes = cmds.namespaceInfo(namespace, - listOnlyDependencyNodes=True) - # get reference nodes - list_nodes.extend([self.has_default_shader(n) for n in ns_nodes]) - - # ensure unique nodes and kick out any None types - result = [node for node in list_nodes if node is not None] - - return result - - def has_default_shader(self, node): - """Check if the nodes have `initialShadingGroup` shader assigned - - Args: - node (str): node to check - - Returns: - str - """ - - shaders = cmds.listConnections(node, type="shadingEngine") - if shaders is None or "initialShadingGroup" in shaders: - # return transform node - transform = cmds.listRelatives(node, - parent=True, - type="transform", - fullPath=True) - - if not transform: - return - - return transform[0] diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index f11699dd8f..73deec968b 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,11 +1,6 @@ -import os -import pprint - from maya import cmds import pyblish.api -import avalon.io as io - from cb.utils.maya import context, shaders import cbra.utils.maya.node_uuid as id_utils From afd490ce33c2204d189b66f6f66918cc44fdd4dc Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 3 Aug 2017 15:51:53 +0200 Subject: [PATCH 0086/1249] addes explicit bson error catching in assign_look --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7f6a21a66e..547335cf6a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -775,7 +775,7 @@ def assign_look(nodes, subset="lookDefault"): # create objectId for database try: asset_id = bson.ObjectId(asset_id) - except Exception: + except bson.errors.InvalidId: log.warning("Asset ID is not compatible with bson") continue subset_data = io.find_one({"type": "subset", From e95d5507d2f80bbbe5a73151f960350160d80537 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 12:06:41 +0200 Subject: [PATCH 0087/1249] Added create attribute function --- colorbleed/maya/lib.py | 69 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 547335cf6a..fcd144182b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -21,6 +21,24 @@ project = io.find_one({"type": "project", "_id": False}) TEMPLATE = project["config"]["template"]["publish"] +ATTRIBUTE_DICT = {"int": {"attributeType": "long"}, + "str": {"dataType": "string"}, + "unicode": {"dataType": "string"}, + "float": {"attributeType": "double"}, + "bool": {"attributeType": "bool"}} + +SHAPE_ATTRS = ["castsShadows", + "receiveShadows", + "motionBlur", + "primaryVisibility", + "smoothShading", + "visibleInReflections", + "visibleInRefractions", + "doubleSided", + "opposite"] + +SHAPE_ATTRS = set(SHAPE_ATTRS) + def maintained_selection(arg=None): if arg is not None: @@ -681,6 +699,53 @@ def get_reference_node(path): return node +def process_attribute_change(attribute_data, node): + """Adjust attributes based on the value from the attribute data + + Args: + attribute_data (dict): attribute as key with value as value + node (str): name of the node + + Returns: + None + """ + + for attribute, value in attribute_data.items(): + value_type = type(value).__name__ + kwargs = ATTRIBUTE_DICT[value_type] + if not cmds.attributeQuery(attribute, node=node, exists=True): + log.debug("Creating attribute '{}' on " + "'{}'".format(attribute, node)) + cmds.addAttr(node, longName=attribute, **kwargs) + + node_attr = "{}.{}".format(node, attribute) + if "dataType" in kwargs: + attr_type = kwargs["dataType"] + cmds.setAttr(node_attr, value, type=attr_type) + else: + cmds.setAttr(node_attr, value) + + +def apply_attributes(attributes, nodes_by_id): + """Alter the attributes to match the state when publishing + + Apply attribute settings from the publish to the node in the scene based + on the UUID which is stored in the cbId attribute. + + Args: + attributes (list): list of dictionaries + nodes_by_id (dict): collection of nodes based on UUID + {uuid: [node, node]} + + """ + + for attr_data in attributes: + nodes = nodes_by_id[attr_data["uuid"]] + attr_value = attr_data["attributes"] + for node in nodes: + process_attribute_change(attr_value, node) + + def list_looks(asset_id): """Return all look subsets for the given asset @@ -819,6 +884,7 @@ def apply_shaders(relationships, shadernodes, nodes): None """ + attributes = relationships.get("attributes", []) shader_sets = relationships.get("sets", []) shading_engines = cmds.ls(shadernodes, type="shadingEngine", long=True) @@ -851,5 +917,6 @@ def apply_shaders(relationships, shadernodes, nodes): "'{}'".format(shader_uuid)) cmds.sets(filtered_nodes, forceElement=shading_engine[0]) - # endregion + + apply_attributes(attributes, ns_nodes_by_id) From 8b2a129066ee1453338155f0082cc5bc62675b9b Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 12:07:57 +0200 Subject: [PATCH 0088/1249] added version validation for assumed template data --- colorbleed/plugins/maya/publish/collect_look.py | 1 - colorbleed/plugins/publish/integrate.py | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 73deec968b..4b1a16b194 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -127,7 +127,6 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect textures, resources = [self.collect_resource(n) for n in files] instance.data["resources"] = resources - # pprint.pprint(resources) # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["sets"]: diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 2c21866514..9a1c81df68 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -109,7 +109,17 @@ class IntegrateAsset(pyblish.api.InstancePlugin): if latest_version is not None: next_version += latest_version["name"] - self.log.debug("Next version: %i" % next_version) + self.log.info("Verifying version from assumed destination") + + assumed_data = instance.data["assumedTemplateData"] + assumed_version = assumed_data["version"] + if assumed_version != next_version: + raise AttributeError("Assumed version 'v{0:03d}' does not match" + "next version in database " + "('v{1:03d}')".format(assumed_version, + next_version)) + + self.log.debug("Next version: v{0:03d}".format(next_version)) version_data = self.create_version_data(context, instance) version = self.create_version(subset=subset, From 52e20f63ff573656ebd5644835faf72f9bdb426c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 13:55:08 +0200 Subject: [PATCH 0089/1249] removed node argument in on_save, improved function --- colorbleed/maya/__init__.py | 43 +++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 300688cc7b..792952eeef 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -88,7 +88,7 @@ def on_new(): maya.commands.reset_resolution() -def on_save(nodes=None): +def on_save(): """Automatically add IDs to new nodes Any transform of a mesh, without an existing ID, is given one automatically on file save. @@ -98,29 +98,33 @@ def on_save(nodes=None): defaults = ["initialShadingGroup", "initialParticleSE"] - # the default items which always want to have an ID - # objectSets include: shading engines, vray object properties - types = ["mesh", "objectSet", "file", "nurbsCurve"] + ignore = set(cmds.ls(long=True, readOnly=True)) + locked = set(cmds.ls(long=True, lockedNodes=True)) + ignore |= locked + + types = ["shadingEngine", "file"] + shape_types = ["mesh", "nurbsCurve"] # the items which need to pass the id to their parent - if not nodes: - nodes = (set(cmds.ls(type=types, long=True)) - - set(cmds.ls(long=True, readOnly=True)) - - set(cmds.ls(long=True, lockedNodes=True))) + transforms = set() + nodes = set(cmds.ls(type=types, long=True)) + for n in cmds.ls(type=shape_types, long=True): + if n in defaults: + continue - transforms = set() - for n in cmds.ls(type=types, long=True): - # pass id to parent of node if in subtypes - relatives = cmds.listRelatives(n, parent=True, fullPath=True) - if not relatives: - continue + # generate id on parent of shape nodes + parents = cmds.listRelatives(n, parent=True, fullPath=True) + if not parents: + raise RuntimeError("Bug! Shape has no parent: {0}".format(n)) - for r in cmds.listRelatives(n, parent=True, fullPath=True): - transforms.add(r) + for parent in parents: + transforms.add(parent) - # merge transforms and nodes in one set to make sure every item - # is unique - nodes |= transforms + # Add the collected transform to the nodes + nodes |= transforms + + # Remove the ignored nodes + nodes -= ignore # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] @@ -129,3 +133,4 @@ def on_save(nodes=None): if node in defaults: continue _set_uuid(str(asset_id["_id"]), node) + From 3e22683eed7a9f2634a0748a57ac6f3e1f8c8472 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 13:58:37 +0200 Subject: [PATCH 0090/1249] cosmetics --- colorbleed/plugins/publish/integrate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 9a1c81df68..07038cdb1b 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -21,7 +21,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): publish the shading network. Same goes for file dependent assets. """ - label = "Intergrate Asset" + label = "Integrate Asset" order = pyblish.api.IntegratorOrder families = ["colorbleed.model", "colorbleed.rig", @@ -37,7 +37,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.info("Integrating Asset in to the database ...") self.register(instance) - self.intergrate(instance) + self.integrate(instance) self.log.info("Removing temporary files and folders ...") stagingdir = instance.data["stagingDir"] @@ -194,7 +194,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): return representations - def intergrate(self, instance): + def integrate(self, instance): """Register the representations and move the files Through the stored `representations` and `transfers` From 5ef5bd15a44849910af9fb5316886a08e5e4a218 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 15:12:40 +0200 Subject: [PATCH 0091/1249] both transform and shape get ID for lookdev purposes --- colorbleed/filetypes.py | 2 -- colorbleed/maya/__init__.py | 40 ++++++++++++++----------------------- 2 files changed, 15 insertions(+), 27 deletions(-) delete mode 100644 colorbleed/filetypes.py diff --git a/colorbleed/filetypes.py b/colorbleed/filetypes.py deleted file mode 100644 index fbefeff9be..0000000000 --- a/colorbleed/filetypes.py +++ /dev/null @@ -1,2 +0,0 @@ -accepted_images_types = [".png", ".jpg", ".tga", ".tiff"] -ignored_images_types = [".pds"] diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 792952eeef..8bf5764a0a 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -70,14 +70,14 @@ def _copy_uuid(source, target): cmds.setAttr(target_attr, attribute_value, type="string") -def on_init(): +def on_init(_): avalon.logger.info("Running callback on init..") maya.commands.reset_frame_range() maya.commands.reset_resolution() -def on_new(): +def on_new(_): avalon.logger.info("Running callback on new..") # Load dependencies @@ -88,7 +88,7 @@ def on_new(): maya.commands.reset_resolution() -def on_save(): +def on_save(_): """Automatically add IDs to new nodes Any transform of a mesh, without an existing ID, is given one automatically on file save. @@ -96,32 +96,22 @@ def on_save(): avalon.logger.info("Running callback on save..") - defaults = ["initialShadingGroup", "initialParticleSE"] + # establish set of nodes to ignore + ignore = set(["initialShadingGroup", "initialParticleSE"]) + ignore |= set(cmds.ls(long=True, readOnly=True)) + ignore |= set(cmds.ls(long=True, lockedNodes=True)) - ignore = set(cmds.ls(long=True, readOnly=True)) - locked = set(cmds.ls(long=True, lockedNodes=True)) - ignore |= locked - - types = ["shadingEngine", "file"] - shape_types = ["mesh", "nurbsCurve"] + types = ["shadingEngine", "file", "mesh", "nurbsCurve"] # the items which need to pass the id to their parent - transforms = set() nodes = set(cmds.ls(type=types, long=True)) - for n in cmds.ls(type=shape_types, long=True): - if n in defaults: - continue - - # generate id on parent of shape nodes - parents = cmds.listRelatives(n, parent=True, fullPath=True) - if not parents: - raise RuntimeError("Bug! Shape has no parent: {0}".format(n)) - - for parent in parents: - transforms.add(parent) # Add the collected transform to the nodes - nodes |= transforms + transforms = cmds.listRelatives(list(nodes), + parent=True, + fullPath=True) or [] + + nodes |= set(transforms) # Remove the ignored nodes nodes -= ignore @@ -129,8 +119,8 @@ def on_save(): # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] asset_id = io.find_one({"type": "asset", "name": asset}) + + # generate the ids for node in nodes: - if node in defaults: - continue _set_uuid(str(asset_id["_id"]), node) From b1151924ad9be3e56f9216c81ca4f26b0f4f230a Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 16:18:58 +0200 Subject: [PATCH 0092/1249] added explicit attribute check --- .../plugins/maya/publish/validate_unique_node_ids.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index 9dbd62e7ff..edae718c02 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -28,13 +28,10 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): # Collect each id with their members ids = defaultdict(list) for member in instance: - try: - object_id = cmds.getAttr("{}.{}".format(member, uuid_attr)) - except Exception as exception: - # Object will node have the attribute so skip - cls.log.debug(exception) + if not cmds.attributeQuery(uuid_attr, node=member, exists=True): continue + object_id = cmds.getAttr("{}.{}".format(member, uuid_attr)) ids[object_id].append(member) # Skip those without IDs (if everything should have an ID that should From ac6b2c271e5ff17c20407cc40c068f58ac3cc438 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 16:25:12 +0200 Subject: [PATCH 0093/1249] removed redundant module --- colorbleed/maya/lib.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index fcd144182b..9d1419fe5a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -637,32 +637,6 @@ def _get_id(node): return attribute_value -def filter_by_id(nodes, uuids): - """Filter all nodes which match the UUIDs - - Args: - nodes (list): collection of nodes to check - uuids (list): a list of UUIDs which are linked to the shader - - Returns: - list: matching nodes - """ - - filtered_nodes = [] - for node in nodes: - if node is None: - continue - - attribute_value = _get_id(node) - - if attribute_value not in uuids or attribute_value is None: - continue - - filtered_nodes.append(node) - - return filtered_nodes - - def get_representation_file(representation, template=TEMPLATE): """ Rebuild the filepath of the representation's context From 8731c4f507bd6ff4df6e1e8e42f664a6d427f6a2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 17:40:44 +0200 Subject: [PATCH 0094/1249] namespace resolved, fixed name in creator --- colorbleed/plugins/maya/create/colorbleed_animation.py | 5 ++--- colorbleed/plugins/maya/load/load_animation.py | 7 +------ colorbleed/plugins/maya/load/load_rig.py | 3 +-- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 1703cfe59f..68f7ec2744 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -9,7 +9,7 @@ class CreateAnimation(avalon.maya.Creator): name = "animationDefault" label = "Animation" - family = "colorbleed.anim" + family = "colorbleed.animation" def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -32,5 +32,4 @@ class CreateAnimation(avalon.maya.Creator): # frame range. data["visibleOnly"] = False - self.data = data - self.options["abbreviation"] = self.abbreviation \ No newline at end of file + self.data = data \ No newline at end of file diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index e48b29ef62..8f05b7ac7b 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -23,10 +23,7 @@ class AbcLoader(api.Loader): # Create unique namespace for the cameras # Get name from asset being loaded - assetname = "{}_".format(name.split("_")[0]) - namespace = maya.unique_namespace(assetname, - format="%03d", - suffix="_abc") + namespace = "{name}_abc".format(name=name) nodes = cmds.file(self.fname, namespace=namespace, sharedReferenceFile=False, @@ -51,8 +48,6 @@ class CurvesLoader(api.Loader): icon = "question" def process(self, name, namespace, context, data): - from maya import cmds - from avalon import maya cmds.loadPlugin("atomImportExport.mll", quiet=True) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 0dffefd84b..f314468d2c 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -22,7 +22,6 @@ class RigLoader(api.Loader): def process(self, name, namespace, context, data): - assert "_" in name, "Naming convention not followed" assetname = "{}_".format(context["asset"]["name"]) unique_namespace = maya.unique_namespace(assetname, format="%03d") nodes = cmds.file(self.fname, @@ -62,8 +61,8 @@ class RigLoader(api.Loader): else: asset = "{}".format(asset_name) + cmds.select([output, controls], noExpand=True) with maya.maintained_selection(): - cmds.select([output, controls], noExpand=True) # TODO(marcus): Hardcoding the family here, better separate this. dependencies = [context["representation"]["_id"]] From 4a2f30d56ad36550d3075aa58509d3c1da759868 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 17:51:53 +0200 Subject: [PATCH 0095/1249] Cosmetics for error messages --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 9d1419fe5a..5b1972ddf0 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -665,7 +665,7 @@ def get_reference_node(path): try: node = cmds.file(path, query=True, referenceNode=True) except RuntimeError: - log.debug('Received file not loaded : "{}"'.format(path)) + log.debug('File is not referenced : "{}"'.format(path)) return reference_path = cmds.referenceQuery(path, filename=True) From e71da0000cdbf98d83a271f9143e21728eb4fc69 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 4 Aug 2017 18:02:30 +0200 Subject: [PATCH 0096/1249] improved apply_attributes function --- colorbleed/maya/lib.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5b1972ddf0..22134c04e5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -673,31 +673,34 @@ def get_reference_node(path): return node -def process_attribute_change(attribute_data, node): +def set_attribute(attribute, value, node): """Adjust attributes based on the value from the attribute data + If an attribute does not exists on the target it will be added with + the dataType being controlled by the value type. + Args: - attribute_data (dict): attribute as key with value as value + attribute (str): name of the attribute to change + value: the value to change to attribute to node (str): name of the node Returns: None """ - for attribute, value in attribute_data.items(): - value_type = type(value).__name__ - kwargs = ATTRIBUTE_DICT[value_type] - if not cmds.attributeQuery(attribute, node=node, exists=True): - log.debug("Creating attribute '{}' on " - "'{}'".format(attribute, node)) - cmds.addAttr(node, longName=attribute, **kwargs) + value_type = type(value).__name__ + kwargs = ATTRIBUTE_DICT[value_type] + if not cmds.attributeQuery(attribute, node=node, exists=True): + log.debug("Creating attribute '{}' on " + "'{}'".format(attribute, node)) + cmds.addAttr(node, longName=attribute, **kwargs) - node_attr = "{}.{}".format(node, attribute) - if "dataType" in kwargs: - attr_type = kwargs["dataType"] - cmds.setAttr(node_attr, value, type=attr_type) - else: - cmds.setAttr(node_attr, value) + node_attr = "{}.{}".format(node, attribute) + if "dataType" in kwargs: + attr_type = kwargs["dataType"] + cmds.setAttr(node_attr, value, type=attr_type) + else: + cmds.setAttr(node_attr, value) def apply_attributes(attributes, nodes_by_id): @@ -714,10 +717,10 @@ def apply_attributes(attributes, nodes_by_id): """ for attr_data in attributes: - nodes = nodes_by_id[attr_data["uuid"]] + node = nodes_by_id[attr_data["uuid"]] attr_value = attr_data["attributes"] - for node in nodes: - process_attribute_change(attr_value, node) + for attr, value in attr_value: + set_attribute(attr, value, node) def list_looks(asset_id): From 31e02669aba3255d502ca22465b95e4ecc785b0d Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 09:45:07 +0200 Subject: [PATCH 0097/1249] fixed dict iteration for attr_value --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 22134c04e5..9469168e79 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -719,7 +719,7 @@ def apply_attributes(attributes, nodes_by_id): for attr_data in attributes: node = nodes_by_id[attr_data["uuid"]] attr_value = attr_data["attributes"] - for attr, value in attr_value: + for attr, value in attr_value.items(): set_attribute(attr, value, node) From a712214f4cfd65e9d282da2a90cbff0f95e92582 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 10:20:29 +0200 Subject: [PATCH 0098/1249] added explanaition for split, ensure unique namespace --- colorbleed/plugins/maya/load/load_animation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index 8f05b7ac7b..058dc85562 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -23,7 +23,16 @@ class AbcLoader(api.Loader): # Create unique namespace for the cameras # Get name from asset being loaded - namespace = "{name}_abc".format(name=name) + # Assuming name is subset name from the animation, we split the number + # suffix from the name to ensure the namespace is unique + name = name.split("_")[0] + namespace = maya.unique_namespace("{}_".format(name), + format="%03d", + suffix="_abc") + + # hero_001 (abc) + # asset_counter{optional} + nodes = cmds.file(self.fname, namespace=namespace, sharedReferenceFile=False, @@ -33,7 +42,6 @@ class AbcLoader(api.Loader): returnNewNodes=True) # load colorbleed ID attribute - self[:] = nodes From 8dd3099fe4cbee9518f2881ac147d54c21618aca Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 10:41:35 +0200 Subject: [PATCH 0099/1249] disabled file name validation --- ...te_filename_convention.py => _validate_filename_convention.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/publish/{validate_filename_convention.py => _validate_filename_convention.py} (100%) diff --git a/colorbleed/plugins/maya/publish/validate_filename_convention.py b/colorbleed/plugins/maya/publish/_validate_filename_convention.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_filename_convention.py rename to colorbleed/plugins/maya/publish/_validate_filename_convention.py From b90b169079fbc2c7d799dedc26ca2fcf79a87bae Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 12:06:56 +0200 Subject: [PATCH 0100/1249] added containerise to look loader --- colorbleed/maya/commands.py | 637 --------------------------------- colorbleed/maya/interactive.py | 288 --------------- colorbleed/maya/lib.py | 21 +- 3 files changed, 16 insertions(+), 930 deletions(-) delete mode 100644 colorbleed/maya/commands.py delete mode 100644 colorbleed/maya/interactive.py diff --git a/colorbleed/maya/commands.py b/colorbleed/maya/commands.py deleted file mode 100644 index c2943128a3..0000000000 --- a/colorbleed/maya/commands.py +++ /dev/null @@ -1,637 +0,0 @@ -"""Used for scripting - -These are used in other scripts and mostly require explicit input, -such as which specific nodes they apply to. - -For interactive use, see :mod:`interactive.py` - -""" - -import sys - -from maya import cmds - -from . import lib - -if sys.version_info[0] == 3: - basestring = str - -# Flags -LocalSpace = 1 << 0 -WorldSpace = 1 << 1 - - -def auto_connect2(src, dst): - """Connect to `dst` based on what `dst` is and `src` has available - - TODO: Offer optionbox of choices when multiple inputs are possible. - For example, connecting a mesh to a wrap node could either - go to driverMesh, or baseMesh. - - """ - - to_from = { - "mesh": ( - ["mesh", (".outMesh", ".inMesh")], - ), - "nurbsSurface": ( - ["nurbsSurface", (".local", ".create")], - ), - "nurbsCurve": ( - ["nurbsCurve", (".local", ".create")], - ), - "decomposeMatrix": ( - ["transform", (".worldMatrix", ".inputMatrix")], - ), - "transform": ( - [ - "transform", ( - (".translate", ".rotate", ".scale"), - (".translate", ".rotate", ".scale")) - ], - [ - "decomposeMatrix", ( - (".outTranslate", ".outRotate", ".outScale"), - (".translate", ".rotate", ".scale")) - ], - ), - "objectSet": ( - ["dagNode", (".message", ".dagSetMembers")], - ["entity", (".message", ".dnSetMembers")], - ), - } - - support = next( - (to_from[to] for to in to_from - if to in cmds.nodeType(dst, inherited=True)), None - ) - - if not support: - # Guess, based on available inputs, - # what is the closest match - print("Guessing..") - pass - - assert support, "No supported outputs for '%s'" % (cmds.nodeType(src)) - - out_, in_ = next( - (typ for typ in support - if typ[0] in cmds.nodeType(src, inherited=True)), (None, None) - ) - - assert in_ and out_, "No matching attributes found for %s" % src - - if not isinstance(in_, tuple): - in_ = (in_,) - - if not isinstance(out_, tuple): - out_ = (out_,) - - assert len(in_) == len(out_) - - map(lambda io: cmds.connectAttr(src + io[0], - dst + io[1], - force=True), zip(out_, in_)) - - -def auto_connect(src, dst): - """Connect `src` to `dst` via the most likely input and output - - Usage: - >>> # Create cube and transfer mesh into new shape - >>> shape = cmds.createNode("mesh", name="newShape") - >>> transform, generator = cmds.polyCube(name="original") - >>> auto_connect(generator, shape) - >>> cmds.delete(transform) - - """ - - out_ = { - "mesh": ".outMesh", - "nurbsSurface": ".local", - "nurbsCurve": ".local", - "decomposeMatrix": (".outTranslate", - ".outRotate", - ".outScale"), - "transform": (".translate", - ".rotate", - ".scale", - ".visibility") - } - - in_ = { - "mesh": ".inMesh", - "nurbsSurface": ".create", - "nurbsCurve": ".create", - "decomposeMatrix": "inputMatrix", - "transform": (".translate", - ".rotate", - ".scale", - ".visibility"), - "objectSet": ["dnSetMembers", "dgSetMembers"] - } - - try: - in_ = in_[cmds.nodeType(dst)] - except KeyError: - in_ = next((attr for attr in (".input", - ".inputGeometry") - if cmds.objExists(dst + attr)), None) - - try: - out_ = out_[cmds.nodeType(src)] - except KeyError: - out_ = next((attr for attr in (".output", - ".outputGeometry") - if cmds.objExists(src + attr)), None) - - assert in_ and out_, "No matching attributes found for %s" % src - - if not isinstance(in_, tuple): - in_ = (in_,) - - if not isinstance(out_, tuple): - out_ = (out_,) - - assert len(in_) == len(out_) - - map(lambda io: cmds.connectAttr(src + io[0], - dst + io[1], - force=True), zip(out_, in_)) - - -@lib.maintained_selection -def match_transform(src, dst): - """Transform `src` to `dst`, taking worldspace into account - - Arguments: - src (str): Absolute path to source transform - dst (str): Absolute path to destination transform - - """ - - try: - parent = cmds.listRelatives(src, parent=True)[0] - except Exception: - parent = None - - node_decompose = cmds.createNode("decomposeMatrix") - node_multmatrix = cmds.createNode("multMatrix") - - connections = { - dst + ".worldMatrix": node_multmatrix + ".matrixIn[0]", - node_multmatrix + ".matrixSum": node_decompose + ".inputMatrix", - node_decompose + ".outputTranslate": src + ".translate", - node_decompose + ".outputRotate": src + ".rotate", - node_decompose + ".outputScale": src + ".scale", - } - - if parent: - connections.update({ - parent + ".worldInverseMatrix": node_multmatrix + ".matrixIn[1]" - }) - - for s, d in connections.iteritems(): - cmds.connectAttr(s, d, force=True) - - cmds.refresh() - - cmds.delete([node_decompose, node_multmatrix]) - - -def connect_shapes(src, dst): - """Connect geometry of `src` to source geometry of dst - - Arguments: - src (str): Name of source shape - dst (list): Names of destination nodes - - """ - - out_attr = None - - if cmds.nodeType(src) == "mesh": - out_attr = ".outMesh" - - elif cmds.nodeType(src) in ("nurbsSurface", "nurbsCurve"): - out_attr = ".local" - - else: - for wildcard in (".output",): - if cmds.objExists(src + wildcard): - out_attr = wildcard - break - - if not out_attr: - return cmds.warning("Could not detect output of %s" % src) - - for target in dst: - in_attr = None - - if cmds.nodeType(target) == "mesh": - in_attr = ".inMesh" - - elif cmds.nodeType(target) in ("nurbsSurface", "nurbsCurve"): - in_attr = ".create" - - else: - # Support unspecific nodes with common input attributes - for support, wildcard in (("mesh", ".inputPolymesh"), - ("mesh", ".inputMesh"), - ("mesh", ".inputGeometry")): - if cmds.objExists(target + wildcard): - if not cmds.nodeType(src) == support: - cmds.warning("Could not connect: %s -> %s" % (src, - target)) - break - - in_attr = wildcard - break - - if not in_attr: - cmds.warning("Could not detect input of %s" % target) - continue - - try: - cmds.connectAttr(src + out_attr, - target + in_attr, - force=True) - except Exception as e: - cmds.warning("Could not connect: %s%s -> %s%s (%s)" % ( - src, out_attr, - target, in_attr, e) - ) - - -def connect_transform(driver, driven, source=WorldSpace, compensate=False): - """Connect translation, rotation and scale via decomposeMatrix - - Arguments: - driver (str): Absolute path to driver - driven (str): Absolute path to driven - source (str, optional): Either WorldSpace or LocalSpace, - default WorldSpace - compensate (bool, optional): Whether or not to take into account - the current transform, default False. - - Returns: - output (list): Newly created nodes - - """ - - outputattr = ".matrix" if source == LocalSpace else ".worldMatrix[0]" - - assert cmds.objExists(driver), "%s not found" % driver - assert cmds.objExists(driven), "%s not found" % driven - - decompose = driver + "_decompose" - output = [decompose] - - if not cmds.objExists(decompose): - decompose = cmds.createNode("decomposeMatrix", name=decompose) - - if compensate: - - multMatrix = cmds.createNode( - "multMatrix", name=driver + "_multMatrix") - - # Compensate for drivens parentMatrix. - cmds.connectAttr(driver + outputattr, - multMatrix + ".matrixIn[0]") - cmds.connectAttr(driven + ".parentInverseMatrix", - multMatrix + ".matrixIn[1]") - cmds.connectAttr(multMatrix + ".matrixSum", - decompose + ".inputMatrix") - - output.append(multMatrix) - else: - cmds.connectAttr(driver + outputattr, - decompose + ".inputMatrix") - - # Drive driven with compensated driver. - cmds.connectAttr(decompose + ".outputTranslate", driven + ".t") - cmds.connectAttr(decompose + ".outputRotate", driven + ".r") - cmds.connectAttr(decompose + ".outputScale", driven + ".s") - - return output - - -def clone(shape, worldspace=False): - """Clone `shape` - - Arguments: - shape (str): Absolute path to shape - worldspace (bool, optional): Whether or not to consider worldspace - - Returns: - node (str): Newly created clone - - """ - - type = cmds.nodeType(shape) - assert type in ("mesh", "nurbsSurface", "nurbsCurve"), ( - "clone() works on polygonal and nurbs surfaces") - - src, dst = { - "mesh": (".outMesh", ".inMesh"), - "nurbsSurface": (".local", ".create"), - "nurbsCurve": (".local", ".create"), - }[type] - - nodetype = cmds.nodeType(shape) - - name = lib.unique(name=shape.rsplit("|")[-1]) - clone = cmds.createNode(nodetype, name=name) - - cmds.connectAttr(shape + src, clone + dst, force=True) - - if worldspace: - transform = cmds.createNode("transformGeometry", - name=name + "_transformGeometry") - - cmds.connectAttr(shape + src, - transform + ".inputGeometry", force=True) - cmds.connectAttr(shape + ".worldMatrix[0]", - transform + ".transform", force=True) - cmds.connectAttr(transform + ".outputGeometry", - clone + dst, force=True) - - # Assign default shader - cmds.sets(clone, addElement="initialShadingGroup") - - return clone - - -def combine(nodes): - """Produce a new mesh with the contents of `nodes` - - Arguments: - nodes (list): Path to shapes - - """ - - unite = cmds.createNode("polyUnite", n=nodes[0] + "_polyUnite") - - count = 0 - for node in nodes: - # Are we dealing with transforms, or shapes directly? - shapes = cmds.listRelatives(node, shapes=True) or [node] - - for shape in shapes: - try: - cmds.connectAttr(shape + ".outMesh", - unite + ".inputPoly[%s]" % count, force=True) - cmds.connectAttr(shape + ".worldMatrix", - unite + ".inputMat[%s]" % count, force=True) - count += 1 - - except Exception: - cmds.warning("'%s' is not a polygonal mesh" % shape) - - if count: - output = cmds.createNode("mesh", n=nodes[0] + "_combinedShape") - cmds.connectAttr(unite + ".output", output + ".inMesh", force=True) - return output - - else: - cmds.delete(unite) - return None - - -def transfer_outgoing_connections(src, dst): - """Connect outgoing connections from `src` to `dst` - - Connections that cannot be made are ignored. - - Arguments: - src (str): Absolute path to source node - dst (str): Absolute path to destination node - - """ - - for destination in cmds.listConnections(src, - source=False, - plugs=True) or []: - for source in cmds.listConnections(destination, - destination=False, - plugs=True) or []: - try: - cmds.connectAttr(source.replace(src, dst), - destination, force=True) - except RuntimeError: - continue - - -def parent_group(source, transferTransform=True): - """Create and transfer transforms to parent group""" - assert cmds.objExists(source), "%s does not exist" % source - assert cmds.nodeType(source) == "transform", ( - "%s must be transform" % source) - - parent = cmds.listRelatives(source, parent=True) - - if transferTransform: - group = cmds.createNode("transform", n="%s_parent" % source) - match_transform(group, source) - - try: - cmds.parent(source, group) - except Exception: - cmds.warning("Failed to parent child under new parent") - cmds.delete(group) - - if parent: - cmds.parent(group, parent[0]) - - else: - cmds.select(source) - group = cmds.group(n="%s_parent" % source) - - return group - - -def _output_node(source, type, suffix): - newname = lib.unique(name=source.rsplit("_", 1)[0] + suffix) - - node = cmds.createNode(type) - node = [cmds.listRelatives(node, parent=True) or node][0] - node = cmds.rename(node, newname) - - try: - cmds.parent(node, source) - match_transform(node, source) - - except Exception: - cmds.warning("Could not create %s" % node) - cmds.delete(node) - - return node - - -def output_locator(source, suffix="_LOC"): - """Create child locator - - Arguments: - source (str): Parent node - suffix (str): Suffix of output - - """ - - return _output_node(source, "locator", suffix) - - -def output_joint(source, suffix="_JNT"): - """Create child joint - - Arguments: - source (str): Parent node - suffix (str): Suffix of output - - """ - - return _output_node(source, "joint", suffix) - - -def follicle(shape, u=0, v=0, name=""): - """Attach follicle to "shape" at specified "u" and "v" values""" - - type = cmds.nodeType(shape) - assert type in ("mesh", "nurbsSurface"), ( - "follicle() works on polygonal meshes and nurbs") - - src, dst = { - "mesh": (".outMesh", ".inputMesh"), - "nurbsSurface": (".local", ".inputSurface") - }[type] - - follicle = cmds.createNode("follicle", name=name + "Shape") - transform = cmds.listRelatives(follicle, parent=True)[0] - - cmds.setAttr(follicle + ".parameterU", u) - cmds.setAttr(follicle + ".parameterV", v) - - cmds.connectAttr(follicle + ".outTranslate", transform + ".translate") - cmds.connectAttr(follicle + ".outRotate", transform + ".rotate") - cmds.connectAttr(shape + ".worldMatrix[0]", follicle + ".inputWorldMatrix") - cmds.connectAttr(shape + src, follicle + dst, force=True) - - return transform - - -def connect_matching_attributes(source, target): - """Connect matching attributes from source to target - - Arguments: - source (str): Absolute path to node from which to connect - target (str): Target node - - Example: - >>> # Select two matching nodes - >>> source = cmds.createNode("transform", name="source") - >>> target = cmds.createNode("transform", name="target") - >>> cmds.select([source, target], replace=True) - >>> source, target = cmds.ls(selection=True) - >>> connect_matching_attributes(source, target) - - """ - - dsts = cmds.listAttr(target, keyable=True) - for src in cmds.listAttr(source, keyable=True): - if src not in dsts: - continue - - try: - src = "." + src - cmds.connectAttr(source + src, - target + src, - force=True) - except RuntimeError as e: - cmds.warning("Could not connect %s: %s" % (src, e)) - - -def create_ncloth(input_mesh): - """Replace Create nCloth menu item - - This performs the identical option of nCloth -> Create nCloth - with the following changes. - - 1. Input mesh not made intermediate - 2. Current mesh and shape named "currentMesh" - - Arguments: - input_mesh (str): Path to shape - - """ - - assert cmds.nodeType(input_mesh) == "mesh", ( - "%s was not of type mesh" % input_mesh) - - nucleus = cmds.createNode("nucleus", name="nucleus1") - ncloth = cmds.createNode("nCloth", name="nClothShape1") - current_mesh = cmds.createNode("mesh", name="currentMesh") - - cmds.connectAttr(input_mesh + ".worldMesh[0]", ncloth + ".inputMesh") - cmds.connectAttr(ncloth + ".outputMesh", current_mesh + ".inMesh") - cmds.connectAttr("time1.outTime", nucleus + ".currentTime") - cmds.connectAttr("time1.outTime", ncloth + ".currentTime") - cmds.connectAttr(ncloth + ".currentState", nucleus + ".inputActive[0]") - cmds.connectAttr(ncloth + ".startState", nucleus + ".inputActiveStart[0]") - cmds.connectAttr(nucleus + ".outputObjects[0]", ncloth + ".nextState") - cmds.connectAttr(nucleus + ".startFrame", ncloth + ".startFrame") - - # Assign default shader - cmds.sets(current_mesh, addElement="initialShadingGroup") - - return current_mesh - - -def enhanced_parent(child, parent): - if "shape" in cmds.nodeType(child, inherited=True): - cmds.parent(relative=True, shape=True) - else: - cmds.parent(child, parent) - - -def auto_connect_assets(src, dst): - """Attempt to automatically two assets - - Arguments: - src (str): Name of source reference node - dst (str): Name of destination reference node - - Raises: - StopIteration on missing in_SET - - """ - - in_set = None - - for node in cmds.referenceQuery(dst, nodes=True): - if node.endswith("in_SET"): - in_set = node - break - - for input_transform in cmds.sets(in_set, query=True): - mbid = cmds.getAttr(input_transform + ".cbId") - input_shape = cmds.listRelatives(input_transform, shapes=True)[0] - - for output_transform in lib.lsattr("cbId", value=mbid): - - ref = cmds.referenceQuery(output_transform, referenceNode=True) - if ref != src: - continue - - print("Connecting %s -> %s" % (output_transform, input_transform)) - output_shape = cmds.listRelatives(output_transform, shapes=True)[0] - - try: - auto_connect(output_transform, input_transform) - except RuntimeError: - # Already connected - pass - - try: - auto_connect(output_shape, input_shape) - except RuntimeError: - # Already connected - pass diff --git a/colorbleed/maya/interactive.py b/colorbleed/maya/interactive.py deleted file mode 100644 index 740870e0d2..0000000000 --- a/colorbleed/maya/interactive.py +++ /dev/null @@ -1,288 +0,0 @@ -"""Interactive functionality - -These depend on user selection in Maya, and may be used as-is. They -implement the functionality in :mod:`commands.py`. - -Each of these functions take `*args` as argument, because when used -in a Maya menu an additional argument is passed with metadata about -what state the button was pressed in. None of this data is used here. - -""" - -from maya import cmds, mel -from . import commands, lib - - -def connect_shapes(*args): - """Connect the first selection to the last selection(s)""" - selection = cmds.ls(selection=True) - - src = selection.pop(0) - commands.connect_shapes(src, dst=selection) - - -def combine(*args): - """Combine currently selected meshes - - This differs from the default Maya combine in that it - retains the original mesh and produces a new mesh with the result. - - """ - - commands.combine(cmds.ls(sl=1)) - - -def read_selected_channels(*args): - """Return a list of selected channels in the Channel Box""" - channelbox = mel.eval("global string $gChannelBoxName; " - "$temp=$gChannelBoxName;") - return cmds.channelBox(channelbox, - query=True, - selectedMainAttributes=True) or [] - - -def set_defaults(*args): - """Set currently selected values from channel box to their default value - - If no channel is selected, default all keyable attributes. - - """ - - for node in cmds.ls(selection=True): - selected_channels = read_selected_channels() - for channel in (selected_channels or - cmds.listAttr(node, keyable=True)): - try: - default = cmds.attributeQuery(channel, - node=node, - listDefault=True)[0] - except Exception: - continue - - else: - cmds.setAttr(node + "." + channel, default) - - -def transfer_outgoing_connections(*args): - """Connect outgoing connections from first to second selected node""" - - try: - src, dst = cmds.ls(selection=True) - except ValueError: - return cmds.warning("Select source and destination nodes") - - commands.transfer_outgoing_connections(src, dst) - - -def clone_special(*args): - """Clone in localspace, and preserve user-defined attributes""" - - for transform in cmds.ls(selection=True, long=True): - if cmds.nodeType(transform) != "transform": - cmds.warning("Skipping '%s', not a `transform`" % transform) - continue - - shape = _find_shape(transform) - type = cmds.nodeType(shape) - - if type not in ("mesh", "nurbsSurface", "nurbsCurve"): - cmds.warning("Skipping '{transform}': cannot clone nodes " - "of type '{type}'".format(**locals())) - continue - - cloned = commands.clone(shape, worldspace=False) - new_transform = cmds.listRelatives(cloned, - parent=True, - fullPath=True)[0] - - new_transform = cmds.rename(new_transform, - new_transform.rsplit(":", 1)[-1]) - - for attr in cmds.listAttr(transform, - userDefined=True) or list(): - try: - cmds.addAttr(new_transform, longName=attr, dataType="string") - except Exception: - continue - - value = cmds.getAttr(transform + "." + attr) - cmds.setAttr(new_transform + "." + attr, value, type="string") - - # Connect visibility - cmds.connectAttr(transform + ".visibility", - new_transform + ".visibility") - - -def clone_worldspace(*args): - return _clone(worldspace=True) - - -def clone_localspace(*args): - return _clone(worldspace=False) - - -def _clone(worldspace=False): - """Clone selected objects in viewport - - Arguments: - worldspace (bool): Whether or not to append a transformGeometry to - resulting clone. - - """ - - clones = list() - - for node in cmds.ls(selection=True, long=True): - shape = _find_shape(node) - type = cmds.nodeType(shape) - - if type not in ("mesh", "nurbsSurface", "nurbsCurve"): - cmds.warning("Skipping '{node}': cannot clone nodes " - "of type '{type}'".format(**locals())) - continue - - cloned = commands.clone(shape, worldspace=worldspace) - clones.append(cloned) - - if not clones: - return - - # Select newly created transform nodes in the viewport - transforms = list() - - for clone in clones: - transform = cmds.listRelatives(clone, parent=True, fullPath=True)[0] - transforms.append(transform) - - cmds.select(transforms, replace=True) - - -def _find_shape(element): - """Return shape of given 'element' - - Supports components, meshes, and surfaces - - Arguments: - element (str): Path to component, mesh or surface - - Returns: - str of path if found, None otherwise - - """ - - # Get either shape or transform, based on element-type - node = cmds.ls(element, objectsOnly=True, long=True)[0] - - if cmds.nodeType(node) == "transform": - try: - return cmds.listRelatives(node, shapes=True, fullPath=True)[0] - except IndexError: - return cmds.warning("Could not find shape in %s" % element) - else: - return node - - -def connect_matching_attributes_from_selection(*args): - try: - source, target = cmds.ls(sl=True) - except ValueError: - raise ValueError("Select (1) source and (2) target nodes only.") - - return commands.connect_matching_attributes(source, target) - - -def auto_connect(*args): - """Connect `src` to `dst` via the most likely input and output""" - try: - commands.auto_connect(*cmds.ls(selection=True)) - except TypeError: - cmds.warning("Select only source and destination nodes.") - - -def create_ncloth(): - selection = cmds.ls(selection=True)[0] - - input_mesh = cmds.listRelatives(selection, shapes=True)[0] - current_mesh = commands.create_ncloth(input_mesh) - - # Optionally append suffix - comp = selection.rsplit("_", 1) - suffix = ("_" + comp[-1]) if len(comp) > 1 else "" - - cmds.rename(current_mesh, "currentMesh%sShape" % suffix) - - # Mimic default nCloth command - cmds.hide(selection) - - -def follicle(*args): - supported = ["mesh", "nurbsSurface"] - selection = cmds.ls(sl=1) - - new_follicles = [] - for sel in selection: - uv = lib.uv_from_element(sel) - - geometry_shape = lib.shape_from_element(sel) - geometry_transform = cmds.listRelatives(geometry_shape, parent=True)[0] - - # Figure out output connection - inputs = [".inputMesh", ".inputSurface"] - outputs = [".outMesh", ".local"] - - failed = False - type = cmds.nodeType(geometry_shape) - if type not in supported: - failed = True - shapes = cmds.listRelatives(geometry_shape, shapes=True) - - if shapes: - geometry_shape = shapes[0] - type = cmds.nodeType(geometry_shape) - if type in supported: - failed = False - - if failed: - cmds.error("Skipping '%s': Type not accepted" % type) - return - - input = inputs[supported.index(type)] - output = outputs[supported.index(type)] - - # Make follicle - follicle = cmds.createNode("follicle", - name=geometry_transform + "_follicleShape1") - follicle_transform = cmds.listRelatives(follicle, parent=True)[0] - follicle_transform = cmds.rename(follicle_transform, - geometry_transform + "_follicle1") - - # Set U and V value - cmds.setAttr(follicle + ".parameterU", uv[0]) - cmds.setAttr(follicle + ".parameterV", uv[1]) - - # Make the connections - cmds.connectAttr(follicle + ".outTranslate", - follicle_transform + ".translate") - cmds.connectAttr(follicle + ".outRotate", - follicle_transform + ".rotate") - cmds.connectAttr(geometry_shape + output, - follicle + input) - - # Select last - new_follicles.append(follicle_transform) - - # Select newly created follicles - if new_follicles: - cmds.select(new_follicles, r=1) - - return new_follicles - - -def auto_connect_assets(*args): - references = cmds.ls(selection=True, type="reference") - - if not len(references) == 2: - raise RuntimeError("Select source and destination " - "reference nodes, in that order.") - - return commands.auto_connect_assets(*references) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 9469168e79..9583cce86b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -8,10 +8,10 @@ import logging import contextlib from collections import OrderedDict, defaultdict -from avalon import maya, io - from maya import cmds, mel +from avalon import maya, io + log = logging.getLogger(__name__) @@ -717,10 +717,11 @@ def apply_attributes(attributes, nodes_by_id): """ for attr_data in attributes: - node = nodes_by_id[attr_data["uuid"]] + nodes = nodes_by_id[attr_data["uuid"]] attr_value = attr_data["attributes"] - for attr, value in attr_value.items(): - set_attribute(attr, value, node) + for node in nodes: + for attr, value in attr_value.items(): + set_attribute(attr, value, node) def list_looks(asset_id): @@ -784,6 +785,16 @@ def assign_look_by_version(nodes, version_id): else: log.info("Reusing existing lookdev '{}'".format(reference_node)) shader_nodes = cmds.referenceQuery(reference_node, nodes=True) + namespace = cmds.referenceQuery(reference_node, namespace=True) + + # containerise like avalon (for manager) + # give re + context = {"representation": shader_file} + subset_name = shader_file["context"]["subset"] + maya.containerise(name=subset_name, + namespace=namespace, + nodes=shader_nodes, + context=context) # Assign relationships with open(shader_relation, "r") as f: From ac3ed4dd7128bfcfc0a8fb3886f6c4e740f393bf Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 12:10:12 +0200 Subject: [PATCH 0101/1249] fixed missing args in assign_look --- colorbleed/plugins/maya/load/load_look.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index d0b05d1252..12edc082a7 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -69,6 +69,7 @@ class LookLoader(api.Loader): # Get all nodes which belong to a matching name space # Currently this is the safest way to get all the nodes - lib.apply_shaders(relationships, nodes) + # Pass empty list as nodes to assign to in order to only load + lib.apply_shaders(relationships, nodes, []) self[:] = nodes From 394054e6d55ad54ea7a8147a72d6edff7d938c39 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 12:10:37 +0200 Subject: [PATCH 0102/1249] finishing code for future testing --- .../maya/publish/_validate_filename_convention.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/_validate_filename_convention.py b/colorbleed/plugins/maya/publish/_validate_filename_convention.py index 7a9a44e02f..74c4592bce 100644 --- a/colorbleed/plugins/maya/publish/_validate_filename_convention.py +++ b/colorbleed/plugins/maya/publish/_validate_filename_convention.py @@ -1,4 +1,7 @@ import re +import os + +import maya.cmds as cmds import pyblish.api import colorbleed.api @@ -21,9 +24,15 @@ class ValidateFileNameConvention(pyblish.api.InstancePlugin): # todo: change pattern to company standard pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") - nodes = list(instance) + nodes = cmds.ls(instance, type="file") for node in nodes: - match = pattern.match(node) + # get texture path + texture = cmds.getAttr("{}.fileTextureName".format(node)) + if not texture: + self.log.error("") + invalid.append(node) + filename = os.path.split(os.path.basename(texture))[0] + match = pattern.match(filename) if not match: invalid.append(node) From 70806a568b627748349d6479f9d7cccb12a4023a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 15:15:57 +0200 Subject: [PATCH 0103/1249] resolved issue with Vray Object Property sets --- colorbleed/maya/lib.py | 7 ++++--- colorbleed/plugins/maya/publish/collect_look.py | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 9583cce86b..e14381a5ff 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -865,7 +865,8 @@ def apply_shaders(relationships, shadernodes, nodes): Args: relationships (dict): relationship data - shadernodes (list): list of nodes of the shading engine + shadernodes (list): list of nodes of the shading objectSets (includes + VRayObjectProperties and shadingEngines) nodes (list): list of nodes to apply shader to Returns: @@ -875,7 +876,7 @@ def apply_shaders(relationships, shadernodes, nodes): attributes = relationships.get("attributes", []) shader_sets = relationships.get("sets", []) - shading_engines = cmds.ls(shadernodes, type="shadingEngine", long=True) + shading_engines = cmds.ls(shadernodes, type="objectSet", long=True) assert len(shading_engines) > 0, ("Error in retrieving shading engines " "from reference") @@ -901,7 +902,7 @@ def apply_shaders(relationships, shadernodes, nodes): shading_engine = shading_engines_by_id[shader_uuid] assert len(shading_engine) == 1, ("Could not find the correct " - "shading engine with cbId " + "objectSet with cbId " "'{}'".format(shader_uuid)) cmds.sets(filtered_nodes, forceElement=shading_engine[0]) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 4b1a16b194..8f5ff22897 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -115,6 +115,8 @@ class CollectLook(pyblish.api.InstancePlugin): attributes = self.collect_attributes_changed(instance) looksets = cmds.ls(sets.keys(), absoluteName=True, long=True) + self.log.info("Found the following sets: {}".format(looksets)) + # Store data on the instance instance.data["lookData"] = {"attributes": attributes, "relationships": sets.values(), @@ -124,7 +126,7 @@ class CollectLook(pyblish.api.InstancePlugin): history = cmds.listHistory(looksets) files = cmds.ls(history, type="file", long=True) - # Collect textures, + # Collect textures resources = [self.collect_resource(n) for n in files] instance.data["resources"] = resources @@ -318,11 +320,9 @@ class CollectLook(pyblish.api.InstancePlugin): attribute = "{}.{}".format(node, attr) node_attributes[attr] = cmds.getAttr(attribute) - data = {"name": node, - "uuid": id_utils.get_id(node), - "attributes": node_attributes} - - attributes.append(data) + attributes.append({"name": node, + "uuid": id_utils.get_id(node), + "attributes": node_attributes}) return attributes From c93868d3055d840dafb9e8ab4f6ec49f3cfbfc5d Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 7 Aug 2017 15:54:46 +0200 Subject: [PATCH 0104/1249] cosmetics in error messages --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index e14381a5ff..c73d1f412a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -877,7 +877,7 @@ def apply_shaders(relationships, shadernodes, nodes): shader_sets = relationships.get("sets", []) shading_engines = cmds.ls(shadernodes, type="objectSet", long=True) - assert len(shading_engines) > 0, ("Error in retrieving shading engines " + assert len(shading_engines) > 0, ("Error in retrieving objectSets " "from reference") # region compute lookup From 7ef29e37f67f763c62704fe97795d4dd43cba93c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 15:07:14 +0200 Subject: [PATCH 0105/1249] improved validating look, improved save function, added filter_out_nodes --- colorbleed/maya/__init__.py | 33 +++------ colorbleed/maya/lib.py | 44 ++++++++++-- .../maya/publish/_validate_node_ids.py | 46 ------------- .../plugins/maya/publish/collect_look.py | 38 +++++------ .../maya/publish/validate_frame_range.py | 5 +- .../maya/publish/validate_look_contents.py | 68 +++++++++++++++---- ...lidate_look_default_shaders_connections.py | 2 +- .../publish/validate_look_deformed_shapes.py | 2 +- .../validate_look_ignore_color_space.py | 2 +- .../publish/validate_look_members_node_ids.py | 55 +++++++-------- .../publish/validate_look_members_unique.py | 19 +++--- .../validate_look_no_default_shaders.py | 66 +++++++----------- .../publish/validate_look_node_unique_ids.py | 6 +- .../maya/publish/validate_model_content.py | 2 - .../plugins/maya/publish/validate_node_ids.py | 51 ++++++++++++++ .../maya/publish/validate_unique_node_ids.py | 25 ++++--- ...tion.py => collect_assumed_destination.py} | 15 ++-- colorbleed/plugins/publish/collect_comment.py | 2 +- 18 files changed, 271 insertions(+), 210 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_node_ids.py create mode 100644 colorbleed/plugins/maya/publish/validate_node_ids.py rename colorbleed/plugins/publish/{collect_resource_destination.py => collect_assumed_destination.py} (87%) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 8bf5764a0a..94f315faee 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -8,6 +8,7 @@ from pyblish import api as pyblish from maya import cmds from . import menu +from . import lib PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) @@ -96,31 +97,19 @@ def on_save(_): avalon.logger.info("Running callback on save..") - # establish set of nodes to ignore - ignore = set(["initialShadingGroup", "initialParticleSE"]) - ignore |= set(cmds.ls(long=True, readOnly=True)) - ignore |= set(cmds.ls(long=True, lockedNodes=True)) - - types = ["shadingEngine", "file", "mesh", "nurbsCurve"] - - # the items which need to pass the id to their parent - nodes = set(cmds.ls(type=types, long=True)) - - # Add the collected transform to the nodes - transforms = cmds.listRelatives(list(nodes), - parent=True, - fullPath=True) or [] - - nodes |= set(transforms) - - # Remove the ignored nodes - nodes -= ignore + types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] + type_nodes = set(cmds.ls(type=types, long=True)) + nodes = lib.filter_out_nodes(type_nodes, + defaults=True, + referenced_nodes=True) # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] - asset_id = io.find_one({"type": "asset", "name": asset}) + asset_id = io.find_one({"type": "asset", "name": asset}, + projection={"_id": True}) # generate the ids - for node in nodes: - _set_uuid(str(asset_id["_id"]), node) + for node in nodes: + print node + _set_uuid(str(asset_id["_id"]), node) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c73d1f412a..343847a4fb 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -613,7 +613,43 @@ def remap_resource_nodes(resources, folder=None): cmds.file(save=True, type="mayaAscii") -def _get_id(node): +def filter_out_nodes(nodes, defaults=False, referenced_nodes=False): + """Filter out any node which are locked (reference) or readOnly + + Args: + nodes (set): nodes to filter + locked (bool): set True to filter out lockedNodes + readonly (bool): set True to filter out readOnly + Returns: + nodes (list): list of filtered nodes + """ + # establish set of nodes to ignore + # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove + # default nodes and reference nodes + + ignore = set() + if referenced_nodes: + ignore |= set(cmds.ls(long=True, referencedNodes=referenced_nodes)) + + if defaults: + ignore |= set(cmds.ls(long=True, defaultNodes=defaults)) + + # The items which need to pass the id to their parent + # Add the collected transform to the nodes + dag = cmds.ls(list(nodes), + type="dagNode", + long=True) # query only dag nodes + transforms = cmds.listRelatives(dag, + parent=True, + fullPath=True) or [] + + nodes |= set(transforms) + nodes -= ignore # Remove the ignored nodes + + return nodes + + +def get_id(node): """ Get the `cbId` attribute of the given node Args: @@ -817,7 +853,7 @@ def assign_look(nodes, subset="lookDefault"): # Group all nodes per asset id grouped = defaultdict(list) for node in nodes: - colorbleed_id = _get_id(node) + colorbleed_id = get_id(node) if not colorbleed_id: continue @@ -883,11 +919,11 @@ def apply_shaders(relationships, shadernodes, nodes): # region compute lookup ns_nodes_by_id = defaultdict(list) for node in nodes: - ns_nodes_by_id[_get_id(node)].append(node) + ns_nodes_by_id[get_id(node)].append(node) shading_engines_by_id = defaultdict(list) for shad in shading_engines: - shading_engines_by_id[_get_id(shad)].append(shad) + shading_engines_by_id[get_id(shad)].append(shad) # endregion # region assign diff --git a/colorbleed/plugins/maya/publish/_validate_node_ids.py b/colorbleed/plugins/maya/publish/_validate_node_ids.py deleted file mode 100644 index bfb47abe33..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_node_ids.py +++ /dev/null @@ -1,46 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have colorbleed id attributes - - All look sets should have id attributes. - - """ - - label = 'Node Id Attributes' - families = ['colorbleed.look', 'colorbleed.model'] - hosts = ['maya'] - order = colorbleed.api.ValidatePipelineOrder - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @staticmethod - def get_invalid(instance): - import maya.cmds as cmds - - nodes = instance.data["setMembers"] - - # Ensure all nodes have a cbId - data_id = {} - invalid = [] - for node in nodes: - try: - uuid = cmds.getAttr("{}.cbId".format(node)) - data_id[uuid] = node - if uuid in data_id: - invalid.append(node) - except RuntimeError: - pass - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Nodes found with invalid" - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 8f5ff22897..d8f7ec08f9 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api +import colorbleed.maya.lib as lib from cb.utils.maya import context, shaders -import cbra.utils.maya.node_uuid as id_utils SHAPE_ATTRS = ["castsShadows", "receiveShadows", @@ -13,6 +13,7 @@ SHAPE_ATTRS = ["castsShadows", "visibleInRefractions", "doubleSided", "opposite"] + SHAPE_ATTRS = set(SHAPE_ATTRS) @@ -167,10 +168,10 @@ class CollectLook(pyblish.api.InstancePlugin): if objset in sets: continue - unique_id = cmds.getAttr("%s.cbId" % objset) sets[objset] = {"name": objset, - "uuid": unique_id, + "uuid": lib.get_id(objset), "members": list()} + return sets def get_related_sets(self, node, view_sets): @@ -185,8 +186,12 @@ class CollectLook(pyblish.api.InstancePlugin): Args: node (str): name of the current not to check """ + defaults = ["initialShadingGroup", + "defaultLightSet", + "defaultObjectSet"] - ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] + ignored = ["pyblish.avalon.instance", + "pyblish.avalon.container"] related_sets = cmds.listSets(object=node, extendToShape=False) if not related_sets: @@ -206,6 +211,7 @@ class CollectLook(pyblish.api.InstancePlugin): deformer_sets = cmds.listSets(object=node, extendToShape=False, type=2) or [] + deformer_sets = set(deformer_sets) # optimize lookup sets = [s for s in sets if s not in deformer_sets] @@ -215,8 +221,9 @@ class CollectLook(pyblish.api.InstancePlugin): # Ignore viewport filter view sets (from isolate select and # viewports) sets = [s for s in sets if s not in view_sets] + sets = [s for s in sets if s not in defaults] - self.log.info("Found sets %s for %s" % (related_sets, node)) + self.log.info("Found sets %s for %s" % (sets, node)) return sets @@ -263,21 +270,14 @@ class CollectLook(pyblish.api.InstancePlugin): if member in [m["name"] for m in objset_members]: return - # check node type, if mesh get parent! makes assigning shaders easier + # check node type, if mesh get parent! if cmds.nodeType(node) == "mesh": - parent = cmds.listRelatives(node, parent=True, fullPath=True) - # a mesh NEEDS to have a parent in Maya logic, no reason for - # assertions or extra checking - parent = parent[0] - if cmds.attributeQuery("cbId", node=parent, exists=True): - node = parent - else: - self.log.error("Transform group of mesh '{}' has no attribute " - "'cbId', this is manditory") - return + # A mesh will always have a transform node in Maya logic + node = cmds.listRelatives(node, parent=True, fullPath=True)[0] - if verbose: - self.log.debug("Such as %s.." % member) + if not cmds.attributeQuery("cbId", node=node, exists=True): + self.log.error("Node '{}' has no attribute 'cbId'".format(node)) + return member_data = {"name": node, "uuid": cmds.getAttr("{}.cbId".format(node))} @@ -321,7 +321,7 @@ class CollectLook(pyblish.api.InstancePlugin): node_attributes[attr] = cmds.getAttr(attribute) attributes.append({"name": node, - "uuid": id_utils.get_id(node), + "uuid": cmds.getAttr("{}.cbId".format(node)), "attributes": node_attributes}) return attributes diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index 7072bebdf4..bbc3f935aa 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -15,9 +15,10 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): """ + label = "Validate Frame Range" order = colorbleed.api.ValidateContentsOrder - - label = "Frame Range" + families = ["colorbleed.animation", + "colorbleed.render"] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index c32dfc130f..1dc6a7c849 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -11,28 +11,72 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look Contents' + actions = [colorbleed.api.SelectInvalidAction] + + invalid = [] + errors = [] def process(self, instance): """Process all the nodes in the instance""" - error = False - - attributes = ["sets", - "relationships", - "attributes"] - if not instance[:]: raise RuntimeError("Instance is empty") - # Required look data + self.get_invalid(instance) + + if self.errors: + error_string = "\n".join(self.errors) + raise RuntimeError("Invalid look content. " + "Errors : {}".format(error_string)) + + @classmethod + def get_invalid(cls, instance): + + invalid_attr = list(cls.validate_lookdata_attributes(instance)) + invalid_rels = list(cls.validate_relationships(instance)) + + invalid = invalid_attr + invalid_rels + + return invalid + + @classmethod + def validate_lookdata_attributes(cls, instance): + """Check if the lookData has the required attributes + + Args: + instance + + """ + + invalid = set() + + attributes = ["sets", "relationships", "attributes"] lookdata = instance.data["lookData"] for attr in attributes: if attr not in lookdata: - self.log.error("No %s found in data" % attr) - error = True + cls.errors.append("Look Data has no attribute " + "'{}'".format(attr)) + invalid.add(instance.name) - if error: - raise RuntimeError("Invalid look content. See log for details.") + return invalid + + @classmethod + def validate_relationships(cls, instance): + """Validate and update lookData relationships""" + + invalid = set() + + relationships = instance.data["lookData"]["relationships"] + for relationship in relationships: + look_name = relationship["name"] + for key, value in relationship.items(): + if value is None: + cls.errors.append("{} has invalid attribite " + "'{}'".format(look_name, key)) + + invalid.add(look_name) + + return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py index 30ad75c482..f24011d3c9 100644 --- a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py +++ b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py @@ -15,7 +15,7 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look Default Shader Connections' diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py index f688072972..b2d400e5ff 100644 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -102,7 +102,7 @@ class ValidateLookDeformedShapes(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look deformed shapes' actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory] diff --git a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py index 58f0bb2f23..432f6844d6 100644 --- a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py +++ b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py @@ -18,7 +18,7 @@ class ValidateLookIgnoreColorSpace(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look RAW Ignore color space' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 4e851e0b21..7c256f8399 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,14 +1,13 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib -class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): +class ValidateLookMembersHaveId(pyblish.api.InstancePlugin): """Validate look members have colorbleed id attributes Looks up the contents of the look to see if all its members have - colorbleed id attributes so they can be connected correctly. + Colorbleed Id attributes so they can be connected correctly. When invalid it's very likely related to the model not having the id attributes that it should have. These should have been generated in the @@ -17,35 +16,12 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] - label = 'Look Members Id Attributes' + label = 'Look Members Have ID Attribute' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] - @staticmethod - def get_invalid(instance): - - # Get all members from the sets - members = [] - relations = instance.data["lookData"]["relationships"] - for sg in relations: - sg_members = sg['members'] - sg_members = [member['name'] for member in sg_members] - members.extend(sg_members) - - # Get all sets - - members = list(set(members)) - - # Ensure all nodes have a cbId - invalid = list() - for node in members: - if not cmds.getAttr("{}.cbId".format(node)): - invalid.append(node) - - return invalid - def process(self, instance): """Process all meshes""" @@ -53,3 +29,24 @@ class ValidateLookMembersNodeIds(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Members found without " "asset IDs: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + + # Get all members from the sets + members = [] + relations = instance.data["lookData"]["relationships"] + for relation in relations: + members = [member['name'] for member in relation['members']] + members.extend(members) + + # Get all sets + members = list(set(members)) + + # Ensure all nodes have a cbId + invalid = list() + for node in members: + if not lib.get_id(node): + invalid.append(node) + + return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 48dce8b8d7..6d85ce5339 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -15,8 +15,9 @@ def get_unique_id(node): return unique_id -class ValidateLookMembersUnique(pyblish.api.InstancePlugin): - """Validate members of look are unique. +class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): + """Validate the relational nodes of the look data to ensure every node is + unique. This ensures the same id is not present as more than one node in the look. @@ -29,9 +30,10 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.look'] + label = 'Non Duplicate Relationship Members (ID)' hosts = ['maya'] - label = 'Look Members Unique' + families = ['colorbleed.lookdev'] + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] @@ -42,8 +44,7 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): members = [] relationships = instance.data["lookData"]["relationships"] for sg in relationships: - sg_members = sg['members'] - sg_members = [member['name'] for member in sg_members] + sg_members = [member['name'] for member in sg['members']] members.extend(sg_members) # Ensure we don't have components but the objects @@ -68,9 +69,7 @@ class ValidateLookMembersUnique(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - print self.actions - invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Members found without " - "asset IDs: {0}".format(invalid)) + raise RuntimeError("Members found without asset IDs: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 44b5a3331b..4f33e15b1c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -22,65 +22,51 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = colorbleed.api.ValidateContentsOrder + 0.01 + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look No Default Shaders' actions = [colorbleed.api.SelectInvalidAction] @classmethod - def get_invalid_sets(cls, instance): + def get_invalid(cls, instance): + invalid = [] disallowed = ["lambert1", "initialShadingGroup", "initialParticleSE", "particleCloud1"] - disallowed = set(disallowed) - # Check among the sets - lookdata = instance.data["lookData"] - sets = lookdata['sets'] - lookup = set(sets) - intersect = lookup.intersection(disallowed) - if intersect: - cls.log.error("Default shaders found in the " - "look: {0}".format(list(intersect))) - return list(intersect) + setmembers = instance.data["setMembers"] + members = cmds.listRelatives(setmembers, + allDescendents=True, + type="shape") - # Check among history/inputs of the sets - history = cmds.listHistory(sets) or [] - lookup = set(history) + for member in members: - intersect = lookup.intersection(disallowed) - if intersect: - cls.log.error("Default shaders found in the history of the " - "look: {0}".format(list(intersect))) - return list(intersect) + # get connection + # listConnections returns a list or None + shading_engine = cmds.listConnections(member, type="shadingEngine") + if not shading_engine: + cls.log.error("Detected shape without shading engine : " + "'{}'".format(member)) + invalid.append(member) + continue - return list() - - @classmethod - def get_invalid(cls, instance): - - shaders = cls.get_invalid_sets(instance) - nodes = instance[:] - - # Get members of the shaders - all = set() - for shader in shaders: - members = cmds.sets(shader, query=True) or [] - members = cmds.ls(members, long=True) - all.update(members) - - # Get the instance nodes among the shader members - invalid = all.intersection(nodes) - invalid = list(invalid) + # retrieve the shading engine out of the list + shading_engine = shading_engine[0] + if shading_engine in disallowed: + cls.log.error("Member connected to a disallows objectSet: " + "'{}'".format(member)) + else: + continue return invalid def process(self, instance): """Process all the nodes in the instance""" - sets = self.get_invalid_sets(instance) + # sets = self.get_invalid_sets(instance) + sets = self.get_invalid(instance) if sets: raise RuntimeError("Invalid shaders found: {0}".format(sets)) diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index 653e8a347c..251ecbf24f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -12,7 +12,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.look'] + families = ['colorbleed.lookdev'] hosts = ['maya'] label = 'Look Id Unique Attributes' actions = [colorbleed.api.SelectInvalidAction, @@ -28,8 +28,8 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): invalid = list() for node in nodes: unique_id = None - if cmds.attributeQuery("mbId", node=node, exists=True): - unique_id = cmds.getAttr("{}.mbId".format(node)) + if cmds.attributeQuery("cbId", node=node, exists=True): + unique_id = cmds.getAttr("{}.cbId".format(node)) if not unique_id: continue diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 7797054c27..75959f66b0 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -24,8 +24,6 @@ class ValidateModelContent(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - pprint.pprint(instance.data) - content_instance = instance.data.get("setMembers", None) if not content_instance: cls.log.error("Instance has no nodes!") diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py new file mode 100644 index 0000000000..37e0bda5c9 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -0,0 +1,51 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + +from colorbleed.maya import lib + + +class ValidateNodeIDs(pyblish.api.InstancePlugin): + """Validate nodes have a Colorbleed Id + + """ + + order = colorbleed.api.ValidatePipelineOrder + label = 'Node Ids (ID)' + hosts = ['maya'] + families = ["colorbleed.model", + "colorbleed.lookdev", + "colorbleed.rig"] + + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes found without " + "IDs: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" + invalid = list() + + # TODO: Implement check on only nodes like on_save callback. + instance_shape = cmds.ls(instance, type="shape") + + # We do want to check the referenced nodes as we it might be + # part of the end product + nodes = lib.filter_out_nodes(set(instance_shape), defaults=True) + for node in nodes: + if not lib.get_id(node): + invalid.append(node) + + return invalid + + + diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index edae718c02..3de16d5a15 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -1,20 +1,22 @@ from collections import defaultdict -import maya.cmds as cmds - import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib -class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have colorbleed id attributes""" +class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): + """Validate the nodes in the instance have a unique Colorbleed Id + + Here we ensure that what has been added to the instance is unique + """ order = colorbleed.api.ValidatePipelineOrder - label = 'Unique Id Attributes' + label = 'Non Duplicate Instance Members (ID)' hosts = ['maya'] - families = ['colorbleed.model', - 'colorbleed.lookdev', - 'colorbleed.rig'] + families = ["colorbleed.model", + "colorbleed.lookdev", + "colorbleed.rig"] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] @@ -23,15 +25,12 @@ class ValidateUniqueNodeIds(pyblish.api.InstancePlugin): def get_invalid_dict(cls, instance): """Return a dictionary mapping of id key to list of member nodes""" - uuid_attr = "cbId" - # Collect each id with their members ids = defaultdict(list) for member in instance: - if not cmds.attributeQuery(uuid_attr, node=member, exists=True): + object_id = lib.get_id(member) + if not object_id: continue - - object_id = cmds.getAttr("{}.{}".format(member, uuid_attr)) ids[object_id].append(member) # Skip those without IDs (if everything should have an ID that should diff --git a/colorbleed/plugins/publish/collect_resource_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py similarity index 87% rename from colorbleed/plugins/publish/collect_resource_destination.py rename to colorbleed/plugins/publish/collect_assumed_destination.py index 814f6fb612..47aec83ea1 100644 --- a/colorbleed/plugins/publish/collect_resource_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -4,10 +4,10 @@ import os import avalon.io as io -class CollectResourceDestination(pyblish.api.InstancePlugin): +class CollectAssumedDestination(pyblish.api.InstancePlugin): """This plug-ins displays the comment dialog box per default""" - label = "Collect Resource Destination" + label = "Collect Assumed Destination" order = pyblish.api.CollectorOrder + 0.499 def process(self, instance): @@ -63,15 +63,22 @@ class CollectResourceDestination(pyblish.api.InstancePlugin): # get all the stuff from the database subset_name = instance.data["subset"] + asset_name = instance.data["asset"] project_name = os.environ["AVALON_PROJECT"] project = io.find_one({"type": "project", "name": project_name}, projection={"config": True}) + template = project["config"]["template"]["publish"] + asset = io.find_one({"type": "asset", + "name": asset_name, + "parent": project["_id"]}) + subset = io.find_one({"type": "subset", - "name": subset_name}) + "name": subset_name, + "parent": asset["_id"]}) # assume there is no version yet, we start at `1` version_number = 1 @@ -85,7 +92,7 @@ class CollectResourceDestination(pyblish.api.InstancePlugin): template_data = {"root": os.environ["AVALON_ROOT"], "project": project_name, "silo": os.environ["AVALON_SILO"], - "asset": instance.data["asset"], + "asset": asset_name, "subset": subset_name, "version": version_number, "representation": "TEMP"} diff --git a/colorbleed/plugins/publish/collect_comment.py b/colorbleed/plugins/publish/collect_comment.py index e3da4486c4..416e505e17 100644 --- a/colorbleed/plugins/publish/collect_comment.py +++ b/colorbleed/plugins/publish/collect_comment.py @@ -4,7 +4,7 @@ import pyblish.api class CollectMindbenderComment(pyblish.api.ContextPlugin): """This plug-ins displays the comment dialog box per default""" - label = "Collect Mindbender Time" + label = "Collect Comment" order = pyblish.api.CollectorOrder def process(self, context): From f0aafec1faec80da9beeb7e5160d6aef1b2b0b22 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 17:09:52 +0200 Subject: [PATCH 0106/1249] improved get_id function --- colorbleed/maya/lib.py | 8 +-- .../maya/publish/validate_node_ids_related.py | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_node_ids_related.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 343847a4fb..28d2c43129 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -663,14 +663,10 @@ def get_id(node): if node is None: return - try: - attr = "{}.cbId".format(node) - attribute_value = cmds.getAttr(attr) - except Exception as e: - log.debug(e) + if not cmds.attributeQuery("cbId", node=node, exists=True): return - return attribute_value + return cmds.getAttr("{}.cbId".format(node)) def get_representation_file(representation, template=TEMPLATE): diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py new file mode 100644 index 0000000000..37e0bda5c9 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -0,0 +1,51 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + +from colorbleed.maya import lib + + +class ValidateNodeIDs(pyblish.api.InstancePlugin): + """Validate nodes have a Colorbleed Id + + """ + + order = colorbleed.api.ValidatePipelineOrder + label = 'Node Ids (ID)' + hosts = ['maya'] + families = ["colorbleed.model", + "colorbleed.lookdev", + "colorbleed.rig"] + + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes found without " + "IDs: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" + invalid = list() + + # TODO: Implement check on only nodes like on_save callback. + instance_shape = cmds.ls(instance, type="shape") + + # We do want to check the referenced nodes as we it might be + # part of the end product + nodes = lib.filter_out_nodes(set(instance_shape), defaults=True) + for node in nodes: + if not lib.get_id(node): + invalid.append(node) + + return invalid + + + From d9734e5f19e03743d1506b8006261aa7994dcea9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 17:11:09 +0200 Subject: [PATCH 0107/1249] updated get_invalid to validate nodes from instance --- colorbleed/plugins/maya/publish/validate_node_ids.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 37e0bda5c9..7ae19dfe1a 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -33,14 +33,12 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): """Return the member nodes that are invalid""" + invalid = list() - # TODO: Implement check on only nodes like on_save callback. - instance_shape = cmds.ls(instance, type="shape") - - # We do want to check the referenced nodes as we it might be + # We do want to check the referenced nodes as it might be # part of the end product - nodes = lib.filter_out_nodes(set(instance_shape), defaults=True) + nodes = lib.filter_out_nodes(set(instance[:]), defaults=True) for node in nodes: if not lib.get_id(node): invalid.append(node) From 67a3f63ceef4f581d7822b662e649f40fccef236 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 17:12:36 +0200 Subject: [PATCH 0108/1249] improved get_invalid to verify node id is related to asset id of instance --- .../maya/publish/validate_node_ids_related.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index 37e0bda5c9..eb9f736fc9 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -1,18 +1,18 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api +import avalon.io as io + from colorbleed.maya import lib -class ValidateNodeIDs(pyblish.api.InstancePlugin): - """Validate nodes have a Colorbleed Id +class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): + """Validate nodes have a related Colorbleed Id to the instance asset """ order = colorbleed.api.ValidatePipelineOrder - label = 'Node Ids (ID)' + label = 'Node Ids Related (ID)' hosts = ['maya'] families = ["colorbleed.model", "colorbleed.lookdev", @@ -22,27 +22,33 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): colorbleed.api.GenerateUUIDsOnInvalidAction] def process(self, instance): - """Process all meshes""" - + """Process all nodes in instance (incl. hierarchy)""" # Ensure all nodes have a cbId invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Nodes found without " - "IDs: {0}".format(invalid)) + raise RuntimeError("Nodes IDs found that are not related to asset " + "'{}' : {}".format(instance.data['asset'], + invalid)) @classmethod def get_invalid(cls, instance): """Return the member nodes that are invalid""" invalid = list() - # TODO: Implement check on only nodes like on_save callback. - instance_shape = cmds.ls(instance, type="shape") + asset = instance.data['asset'] + asset_data = io.find_one({"name": asset, + "type": "asset"}, + projection={"_id": True}) + asset_id = str(asset_data['_id']) # We do want to check the referenced nodes as we it might be # part of the end product - nodes = lib.filter_out_nodes(set(instance_shape), defaults=True) - for node in nodes: - if not lib.get_id(node): + for node in instance: + _id = lib.get_id(node) + if not _id: + continue + node_asset_id = _id.split(":", 1)[0] + if node_asset_id != asset_id: invalid.append(node) return invalid From 4c091e8eb1b831c7cbe733c21e6a97c57aa3ad56 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 17:16:36 +0200 Subject: [PATCH 0109/1249] cosmetics --- colorbleed/plugins/maya/publish/validate_node_ids_related.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index eb9f736fc9..84c28c6427 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -22,7 +22,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): colorbleed.api.GenerateUUIDsOnInvalidAction] def process(self, instance): - """Process all nodes in instance (incl. hierarchy)""" + """Process all nodes in instance (including hierarchy)""" # Ensure all nodes have a cbId invalid = self.get_invalid(instance) if invalid: @@ -44,9 +44,11 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as we it might be # part of the end product for node in instance: + _id = lib.get_id(node) if not _id: continue + node_asset_id = _id.split(":", 1)[0] if node_asset_id != asset_id: invalid.append(node) From 1569ad2d3e0ba574680d15e579dc1e58f3b04d1c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 17:18:20 +0200 Subject: [PATCH 0110/1249] cosmetics --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 28d2c43129..73f25bbe68 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -618,8 +618,8 @@ def filter_out_nodes(nodes, defaults=False, referenced_nodes=False): Args: nodes (set): nodes to filter - locked (bool): set True to filter out lockedNodes - readonly (bool): set True to filter out readOnly + defaults (bool): set True to filter out default nodes + referenced_nodes (bool): set True to filter out reference nodes Returns: nodes (list): list of filtered nodes """ From 52a36f3c94224c95503f282ae12f452a205680bb Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 8 Aug 2017 18:05:35 +0200 Subject: [PATCH 0111/1249] added list of default cameras --- .../maya/publish/validate_look_contents.py | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 1dc6a7c849..81a22812b4 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,3 +1,5 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api @@ -34,11 +36,13 @@ class ValidateLookContents(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + """Get all invalid nodes""" - invalid_attr = list(cls.validate_lookdata_attributes(instance)) - invalid_rels = list(cls.validate_relationships(instance)) + attributes = list(cls.validate_lookdata_attributes(instance)) + relationships = list(cls.validate_relationships(instance)) + non_defaults = cls.validate_non_defaults(instance) - invalid = invalid_attr + invalid_rels + invalid = attributes + relationships + non_defaults return invalid @@ -74,9 +78,35 @@ class ValidateLookContents(pyblish.api.InstancePlugin): look_name = relationship["name"] for key, value in relationship.items(): if value is None: - cls.errors.append("{} has invalid attribite " + cls.errors.append("{} has invalid attribute " "'{}'".format(look_name, key)) invalid.add(look_name) return invalid + + @classmethod + def validate_non_defaults(cls, instance): + """Check if instance content items are part of the default nodes""" + + invalid = [] + cams = cmds.ls(['frontShape', 'perspShape', 'sideShape', 'topShape'], + long=True) + defaults = cmds.ls(long=True, defaultNodes=True) + defaults.extend(cams) + + for node in cmds.ls(instance[:], long=True): + # might be a transform of a default item listed + if cmds.nodeType(node) == "transform": + node = cmds.listRelatives(node, + children=True, + fullPath=True)[0] or None + + print node, node in defaults + + if node in defaults: + invalid.append(node) + cls.errors.append("'{}' is part of Maya default " + "nodes".format(node)) + + return invalid From 2dd251473c472e1f03a7943124460e1afb3ae99c Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 12:02:16 +0200 Subject: [PATCH 0112/1249] improved validation --- .../maya/publish/validate_look_contents.py | 22 +++++++++++-------- .../plugins/maya/publish/validate_node_ids.py | 17 ++++++++++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 81a22812b4..9f0323c6cc 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -90,19 +90,23 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """Check if instance content items are part of the default nodes""" invalid = [] - cams = cmds.ls(['frontShape', 'perspShape', 'sideShape', 'topShape'], - long=True) - defaults = cmds.ls(long=True, defaultNodes=True) - defaults.extend(cams) + cams = ["perspShape", "topShape", "frontShape", "sideShape"] + cameras = cmds.ls(cams, long=True) + references = cmds.ls(referencedNodes=True) + default_nodes = cmds.ls(defaultNodes=True, long=True) + + defaults = list(set(cameras + references + default_nodes)) for node in cmds.ls(instance[:], long=True): # might be a transform of a default item listed if cmds.nodeType(node) == "transform": - node = cmds.listRelatives(node, - children=True, - fullPath=True)[0] or None - - print node, node in defaults + children = cmds.listRelatives(node, + children=True, + fullPath=True) + if children: + node = children + else: + continue if node in defaults: invalid.append(node) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 7ae19dfe1a..b3a1644e93 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -12,7 +12,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - label = 'Node Ids (ID)' + label = 'Instance Nodes Have ID' hosts = ['maya'] families = ["colorbleed.model", "colorbleed.lookdev", @@ -41,9 +41,22 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): nodes = lib.filter_out_nodes(set(instance[:]), defaults=True) for node in nodes: if not lib.get_id(node): + + # check if siblings have IDs + valid_hierarchy = cls.validate_children(node) + if valid_hierarchy: + continue + invalid.append(node) return invalid + @staticmethod + def validate_children(node): + """Validate the children of the node if the ID is not present""" - + children = cmds.listRelatives(node, children=True) + for child in children: + if lib.get_id(child): + return True + return False From 1000a03fef44ac33ee1c726b8453edf52b7fdd8e Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 12:06:07 +0200 Subject: [PATCH 0113/1249] improved get_invalid function --- .../maya/publish/validate_look_members_node_ids.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 7c256f8399..de406e3f42 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -34,14 +34,10 @@ class ValidateLookMembersHaveId(pyblish.api.InstancePlugin): def get_invalid(cls, instance): # Get all members from the sets - members = [] + members = set() relations = instance.data["lookData"]["relationships"] for relation in relations: - members = [member['name'] for member in relation['members']] - members.extend(members) - - # Get all sets - members = list(set(members)) + members.update([member['name'] for member in relation['members']]) # Ensure all nodes have a cbId invalid = list() From b5f009d231615788e87d3949d14906713cfbf1e0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 12:06:43 +0200 Subject: [PATCH 0114/1249] removed print --- colorbleed/maya/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 94f315faee..071d2e91fe 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -111,5 +111,4 @@ def on_save(_): # generate the ids for node in nodes: - print node _set_uuid(str(asset_id["_id"]), node) From 42d7779ce0555c4508e77d0c6b6a1134d85a47b5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 12:08:31 +0200 Subject: [PATCH 0115/1249] improved get_invalid --- .../plugins/maya/publish/validate_look_members_node_ids.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index de406e3f42..942dbd996f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -40,9 +40,6 @@ class ValidateLookMembersHaveId(pyblish.api.InstancePlugin): members.update([member['name'] for member in relation['members']]) # Ensure all nodes have a cbId - invalid = list() - for node in members: - if not lib.get_id(node): - invalid.append(node) + invalid = [m for m in members if not lib.get_id(m)] return invalid From 6442c16964e1fb14e8e8bdcd3d06844a0470f615 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 12:34:28 +0200 Subject: [PATCH 0116/1249] improved functions, list comprehension --- ...lidate_look_default_shaders_connections.py | 17 +++++------- .../publish/validate_look_members_unique.py | 26 +++++-------------- .../publish/validate_look_node_unique_ids.py | 13 +++------- .../maya/publish/validate_no_animation.py | 14 +++++----- .../plugins/maya/publish/validate_node_ids.py | 13 ++-------- 5 files changed, 25 insertions(+), 58 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py index f24011d3c9..dce90dc0df 100644 --- a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py +++ b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py @@ -1,3 +1,5 @@ +from maya import cmds + import pyblish.api import colorbleed.api @@ -20,11 +22,10 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): label = 'Look Default Shader Connections' # The default connections to check - DEFAULTS = [ - ("initialShadingGroup.surfaceShader", "lambert1"), - ("initialParticleSE.surfaceShader", "lambert1"), - ("initialParticleSE.volumeShader", "particleCloud1") - ] + DEFAULTS = [("initialShadingGroup.surfaceShader", "lambert1"), + ("initialParticleSE.surfaceShader", "lambert1"), + ("initialParticleSE.volumeShader", "particleCloud1") + ] def process(self, instance): @@ -33,19 +34,15 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): # the family is not present in an instance. key = "__validate_look_default_shaders_connections_checked" context = instance.context - is_run = context.data.get(key, - False) + is_run = context.data.get(key, False) if is_run: return else: context.data[key] = True # Process as usual - from maya import cmds - invalid = list() for plug, input_node in self.DEFAULTS: - inputs = cmds.listConnections(plug, source=True, destination=False) or None diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 6d85ce5339..41ffed93c2 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -4,15 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api - - -def get_unique_id(node): - attr = 'cbId' - unique_id = None - has_attribute = cmds.attributeQuery(attr, node=node, exists=True) - if has_attribute: - unique_id = cmds.getAttr("{}.{}".format(node, attr)) - return unique_id +import colorbleed.maya.lib as lib class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): @@ -43,26 +35,22 @@ class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): # Get all members from the sets members = [] relationships = instance.data["lookData"]["relationships"] - for sg in relationships: - sg_members = [member['name'] for member in sg['members']] - members.extend(sg_members) + for relationship in relationships: + members.extend([i['name'] for i in relationship['members']]) # Ensure we don't have components but the objects - members = cmds.ls(members, objectsOnly=True, long=True) - members = list(set(members)) + members = set(cmds.ls(members, objectsOnly=True, long=True)) + members = list(members) # Group members per id id_nodes = defaultdict(set) for node in members: - node_id = get_unique_id(node) + node_id = lib.get_id(node) if not node_id: continue id_nodes[node_id].add(node) - invalid = list() - for nodes in id_nodes.itervalues(): - if len(nodes) > 1: - invalid.extend(nodes) + invalid = [n for n in id_nodes.itervalues() if len(n) > 1] return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index 251ecbf24f..d4fd034710 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -1,9 +1,8 @@ from collections import defaultdict -import maya.cmds as cmds - import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): @@ -25,19 +24,13 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): # Ensure all nodes have a cbId id_sets = defaultdict(list) - invalid = list() for node in nodes: - unique_id = None - if cmds.attributeQuery("cbId", node=node, exists=True): - unique_id = cmds.getAttr("{}.cbId".format(node)) + unique_id = lib.get_id(node) if not unique_id: continue - id_sets[unique_id].append(node) - for unique_id, nodes in id_sets.iteritems(): - if len(nodes) > 1: - invalid.extend(nodes) + invalid = [n for n in id_sets.itervalues() if len(n) > 1] return invalid diff --git a/colorbleed/plugins/maya/publish/validate_no_animation.py b/colorbleed/plugins/maya/publish/validate_no_animation.py index 6c57183d31..8b0da47d89 100644 --- a/colorbleed/plugins/maya/publish/validate_no_animation.py +++ b/colorbleed/plugins/maya/publish/validate_no_animation.py @@ -20,11 +20,16 @@ class ValidateNoAnimation(pyblish.api.Validator): optional = True actions = [colorbleed.api.SelectInvalidAction] + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Keyframes found: {0}".format(invalid)) + @staticmethod def get_invalid(instance): nodes = instance[:] - if not nodes: return [] @@ -33,10 +38,3 @@ class ValidateNoAnimation(pyblish.api.Validator): return list(set(cmds.listConnections(curves))) return [] - - def process(self, instance): - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Keyframes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index b3a1644e93..2a9d9ea25f 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -34,20 +34,11 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): def get_invalid(cls, instance): """Return the member nodes that are invalid""" - invalid = list() - # We do want to check the referenced nodes as it might be # part of the end product nodes = lib.filter_out_nodes(set(instance[:]), defaults=True) - for node in nodes: - if not lib.get_id(node): - - # check if siblings have IDs - valid_hierarchy = cls.validate_children(node) - if valid_hierarchy: - continue - - invalid.append(node) + invalid = [n for n in nodes if not lib.get_id(n) + and not cls.validate_children(n)] return invalid From d19223e26abf3bd0855b3273b9a24360b35b4c0b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 13:46:37 +0200 Subject: [PATCH 0117/1249] added single shader validation --- .../publish/validate_look_single_shader.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_look_single_shader.py diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py new file mode 100644 index 0000000000..fabdfb1a6d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -0,0 +1,61 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateSingleShader(pyblish.api.InstancePlugin): + """Validate default shaders in the scene have their default connections. + + For example the lambert1 could potentially be disconnected from the + initialShadingGroup. As such it's not lambert1 that will be identified + as the default shader which can have unpredictable results. + + To fix the default connections need to be made again. See the logs for + more details on which connections are missing. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.lookdev'] + hosts = ['maya'] + label = 'Look Single Shader Per Shape' + + # The default connections to check + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found shapes which have multiple " + "shadingEngines connected." + "\n{}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + invalid = [] + shape_types = ["numrbsCurve", "nurbsSurface", "mesh"] + + # Get all shapes from the instance + shapes = set() + for node in instance[:]: + + nodetype = cmds.nodeType(node) + if nodetype in shape_types: + shapes.add(node) + + elif nodetype == "transform": + shape = cmds.listRelatives(node, children=True, + type="shape", fullPath=True) + if not shape: + continue + shapes.add(shape[0]) + + # Check the number of connected shadingEngines per shape + for shape in shapes: + shading_engines = cmds.listConnections(shape, + destination=True, + type="shadingEngine") + if len(shading_engines) > 1: + invalid.append(shape) + + return invalid From fea156097833655787c9f9b252fac2633a425e59 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 16:34:29 +0200 Subject: [PATCH 0118/1249] look validation improvements --- colorbleed/maya/__init__.py | 7 +- colorbleed/maya/lib.py | 20 ++++-- .../plugins/maya/publish/collect_look.py | 8 ++- .../maya/publish/validate_look_contents.py | 71 +++++-------------- .../publish/validate_look_members_node_ids.py | 22 +++--- .../validate_look_no_default_shaders.py | 17 +++-- .../publish/validate_look_single_shader.py | 2 +- .../plugins/maya/publish/validate_node_ids.py | 18 ++--- 8 files changed, 66 insertions(+), 99 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 071d2e91fe..94288646a0 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -97,11 +97,8 @@ def on_save(_): avalon.logger.info("Running callback on save..") - types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] - type_nodes = set(cmds.ls(type=types, long=True)) - nodes = lib.filter_out_nodes(type_nodes, - defaults=True, - referenced_nodes=True) + nodes = lib.get_id_required_nodes(defaults=True, + referenced_nodes=True) # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 73f25bbe68..29160a339c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -613,7 +613,7 @@ def remap_resource_nodes(resources, folder=None): cmds.file(save=True, type="mayaAscii") -def filter_out_nodes(nodes, defaults=False, referenced_nodes=False): +def get_id_required_nodes(defaults=True, referenced_nodes=True): """Filter out any node which are locked (reference) or readOnly Args: @@ -621,11 +621,13 @@ def filter_out_nodes(nodes, defaults=False, referenced_nodes=False): defaults (bool): set True to filter out default nodes referenced_nodes (bool): set True to filter out reference nodes Returns: - nodes (list): list of filtered nodes + nodes (set): list of filtered nodes """ - # establish set of nodes to ignore + + # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove # default nodes and reference nodes + camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] ignore = set() if referenced_nodes: @@ -633,16 +635,26 @@ def filter_out_nodes(nodes, defaults=False, referenced_nodes=False): if defaults: ignore |= set(cmds.ls(long=True, defaultNodes=defaults)) + ignore |= set(cmds.ls(camera_shapes, long=True)) + + # establish set of nodes to ignore + types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] + + # We *always* ignore intermediate shapes, so we filter them out + # directly + nodes = cmds.ls(type=types, long=True, noIntermediate=True) # The items which need to pass the id to their parent # Add the collected transform to the nodes - dag = cmds.ls(list(nodes), + dag = cmds.ls(nodes, type="dagNode", long=True) # query only dag nodes + transforms = cmds.listRelatives(dag, parent=True, fullPath=True) or [] + nodes = set(nodes) nodes |= set(transforms) nodes -= ignore # Remove the ignored nodes diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index d8f7ec08f9..4b76a17ce3 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -123,9 +123,11 @@ class CollectLook(pyblish.api.InstancePlugin): "relationships": sets.values(), "sets": looksets} - # Collect file nodes used by shading engines - history = cmds.listHistory(looksets) - files = cmds.ls(history, type="file", long=True) + # Collect file nodes used by shading engines (if we have any) + files = list() + if looksets: + history = cmds.listHistory(looksets) + files = cmds.ls(history, type="file", long=True) # Collect textures resources = [self.collect_resource(n) for n in files] diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 9f0323c6cc..70aec3c70c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,5 +1,3 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api @@ -15,34 +13,27 @@ class ValidateLookContents(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.lookdev'] hosts = ['maya'] - label = 'Look Contents' + label = 'Look Data Contents' actions = [colorbleed.api.SelectInvalidAction] - invalid = [] - errors = [] - def process(self, instance): """Process all the nodes in the instance""" if not instance[:]: raise RuntimeError("Instance is empty") - self.get_invalid(instance) - - if self.errors: - error_string = "\n".join(self.errors) - raise RuntimeError("Invalid look content. " - "Errors : {}".format(error_string)) + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid look content") @classmethod def get_invalid(cls, instance): """Get all invalid nodes""" attributes = list(cls.validate_lookdata_attributes(instance)) - relationships = list(cls.validate_relationships(instance)) - non_defaults = cls.validate_non_defaults(instance) + relationships = list(cls.validate_relationship_ids(instance)) - invalid = attributes + relationships + non_defaults + invalid = attributes + relationships return invalid @@ -61,14 +52,18 @@ class ValidateLookContents(pyblish.api.InstancePlugin): lookdata = instance.data["lookData"] for attr in attributes: if attr not in lookdata: - cls.errors.append("Look Data has no attribute " - "'{}'".format(attr)) + cls.log.error("Look Data has no attribute " + "'{}'".format(attr)) invalid.add(instance.name) + if not lookdata["relationships"] or not lookdata["sets"]: + cls.log.error("Look has no `relationship` or `sets`") + invalid.add(instance.name) + return invalid @classmethod - def validate_relationships(cls, instance): + def validate_relationship_ids(cls, instance): """Validate and update lookData relationships""" invalid = set() @@ -76,41 +71,9 @@ class ValidateLookContents(pyblish.api.InstancePlugin): relationships = instance.data["lookData"]["relationships"] for relationship in relationships: look_name = relationship["name"] - for key, value in relationship.items(): - if value is None: - cls.errors.append("{} has invalid attribute " - "'{}'".format(look_name, key)) - - invalid.add(look_name) - - return invalid - - @classmethod - def validate_non_defaults(cls, instance): - """Check if instance content items are part of the default nodes""" - - invalid = [] - cams = ["perspShape", "topShape", "frontShape", "sideShape"] - cameras = cmds.ls(cams, long=True) - references = cmds.ls(referencedNodes=True) - default_nodes = cmds.ls(defaultNodes=True, long=True) - - defaults = list(set(cameras + references + default_nodes)) - - for node in cmds.ls(instance[:], long=True): - # might be a transform of a default item listed - if cmds.nodeType(node) == "transform": - children = cmds.listRelatives(node, - children=True, - fullPath=True) - if children: - node = children - else: - continue - - if node in defaults: - invalid.append(node) - cls.errors.append("'{}' is part of Maya default " - "nodes".format(node)) + uuid = relationship["uuid"] + if not uuid: + cls.errors.append("{} has invalid ID ") + invalid.add(look_name) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 942dbd996f..f168ba3b39 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,9 +1,11 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api import colorbleed.maya.lib as lib -class ValidateLookMembersHaveId(pyblish.api.InstancePlugin): +class ValidateLookMembers(pyblish.api.InstancePlugin): """Validate look members have colorbleed id attributes Looks up the contents of the look to see if all its members have @@ -18,28 +20,28 @@ class ValidateLookMembersHaveId(pyblish.api.InstancePlugin): order = colorbleed.api.ValidatePipelineOrder families = ['colorbleed.lookdev'] hosts = ['maya'] - label = 'Look Members Have ID Attribute' + label = 'Look Members' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" - invalid = self.get_invalid(instance) - if invalid: + invalid_ids = self.get_invalid(instance) + if invalid_ids: raise RuntimeError("Members found without " - "asset IDs: {0}".format(invalid)) + "asset IDs: {0}".format(invalid_ids)) @classmethod def get_invalid(cls, instance): - # Get all members from the sets members = set() - relations = instance.data["lookData"]["relationships"] - for relation in relations: + relationships = instance.data["lookData"]["relationships"] + for relation in relationships: members.update([member['name'] for member in relation['members']]) - # Ensure all nodes have a cbId invalid = [m for m in members if not lib.get_id(m)] + if invalid: + raise RuntimeError("Found invalid nodes.\nNo ID : " + "{}".format(invalid)) - return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 4f33e15b1c..5042d01796 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -37,16 +37,15 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): "initialParticleSE", "particleCloud1"] - setmembers = instance.data["setMembers"] - members = cmds.listRelatives(setmembers, + members = cmds.listRelatives(instance, allDescendents=True, - type="shape") - + shapes=True, + noIntermediate=True) or [] for member in members: # get connection # listConnections returns a list or None - shading_engine = cmds.listConnections(member, type="shadingEngine") + shading_engine = cmds.listConnections(member, type="objectSet") if not shading_engine: cls.log.error("Detected shape without shading engine : " "'{}'".format(member)) @@ -58,6 +57,7 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): if shading_engine in disallowed: cls.log.error("Member connected to a disallows objectSet: " "'{}'".format(member)) + invalid.append(member) else: continue @@ -66,7 +66,6 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" - # sets = self.get_invalid_sets(instance) - sets = self.get_invalid(instance) - if sets: - raise RuntimeError("Invalid shaders found: {0}".format(sets)) + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid shaders found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py index fabdfb1a6d..1d780bc302 100644 --- a/colorbleed/plugins/maya/publish/validate_look_single_shader.py +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -54,7 +54,7 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): for shape in shapes: shading_engines = cmds.listConnections(shape, destination=True, - type="shadingEngine") + type="shadingEngine") or [] if len(shading_engines) > 1: invalid.append(shape) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 2a9d9ea25f..a5d8ce2d83 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -36,18 +36,10 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as it might be # part of the end product - nodes = lib.filter_out_nodes(set(instance[:]), defaults=True) - invalid = [n for n in nodes if not lib.get_id(n) - and not cls.validate_children(n)] + id_nodes = lib.get_id_required_nodes(defaults=True, + referenced_nodes=False) + + nodes = instance[:] + invalid = [n for n in nodes if n in id_nodes and not lib.get_id(n)] return invalid - - @staticmethod - def validate_children(node): - """Validate the children of the node if the ID is not present""" - - children = cmds.listRelatives(node, children=True) - for child in children: - if lib.get_id(child): - return True - return False From d439c3605d5aa3e648f21980f1a3fdf911813cc5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 16:51:53 +0200 Subject: [PATCH 0119/1249] changed get_id_required_nodes --- colorbleed/maya/__init__.py | 4 ++-- colorbleed/maya/lib.py | 9 ++++----- colorbleed/plugins/maya/publish/validate_node_ids.py | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 94288646a0..2ca8040158 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -97,8 +97,8 @@ def on_save(_): avalon.logger.info("Running callback on save..") - nodes = lib.get_id_required_nodes(defaults=True, - referenced_nodes=True) + nodes = lib.get_id_required_nodes(defaults=False, + referenced_nodes=False) # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 29160a339c..fbbd444b1a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -613,27 +613,26 @@ def remap_resource_nodes(resources, folder=None): cmds.file(save=True, type="mayaAscii") -def get_id_required_nodes(defaults=True, referenced_nodes=True): +def get_id_required_nodes(defaults=False, referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly Args: nodes (set): nodes to filter - defaults (bool): set True to filter out default nodes + defaults (bool): set to False to filter out default nodes referenced_nodes (bool): set True to filter out reference nodes Returns: nodes (set): list of filtered nodes """ - # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove # default nodes and reference nodes camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] ignore = set() - if referenced_nodes: + if not referenced_nodes: ignore |= set(cmds.ls(long=True, referencedNodes=referenced_nodes)) - if defaults: + if not defaults: ignore |= set(cmds.ls(long=True, defaultNodes=defaults)) ignore |= set(cmds.ls(camera_shapes, long=True)) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index a5d8ce2d83..6df5bea4e1 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -36,8 +36,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as it might be # part of the end product - id_nodes = lib.get_id_required_nodes(defaults=True, - referenced_nodes=False) + id_nodes = lib.get_id_required_nodes(defaults=False, + referenced_nodes=True) nodes = instance[:] invalid = [n for n in nodes if n in id_nodes and not lib.get_id(n)] From eff87c7b3f7c73a1ce0466681709c21918127569 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 16:52:07 +0200 Subject: [PATCH 0120/1249] removed maya import --- colorbleed/plugins/maya/publish/validate_node_ids.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 6df5bea4e1..fe2a0cb6a8 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -1,5 +1,3 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api From e703fa97ee49cdf7720465a88dfcdfc85b4cde0d Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 16:55:31 +0200 Subject: [PATCH 0121/1249] removed unused function --- colorbleed/maya/lib.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index fbbd444b1a..425c959770 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -595,24 +595,6 @@ def maya_temp_folder(): return tmp_dir -def remap_resource_nodes(resources, folder=None): - - log.info("Updating resource nodes ...") - for resource in resources: - source = resource["source"] - if folder: - fname = os.path.basename(source) - fpath = os.path.join(folder, fname) - else: - fpath = source - - node_attr = resource["attribute"] - cmds.setAttr(node_attr, fpath, type="string") - - log.info("Saving file ...") - cmds.file(save=True, type="mayaAscii") - - def get_id_required_nodes(defaults=False, referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly From 39557270920af557054b6bc117d314df5f19f050 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 9 Aug 2017 17:16:37 +0200 Subject: [PATCH 0122/1249] cosmetics --- colorbleed/maya/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 425c959770..0edf3010cf 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -599,7 +599,6 @@ def get_id_required_nodes(defaults=False, referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly Args: - nodes (set): nodes to filter defaults (bool): set to False to filter out default nodes referenced_nodes (bool): set True to filter out reference nodes Returns: From a324e5db91b2fa80375ef426ca8534ec56fa6018 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 10:46:08 +0200 Subject: [PATCH 0123/1249] refactored collector and validators to match new lookdata formatting --- .../plugins/maya/publish/collect_look.py | 8 ++- .../maya/publish/validate_look_contents.py | 16 +++--- .../publish/validate_look_members_node_ids.py | 5 +- .../publish/validate_look_members_unique.py | 2 +- .../publish/validate_look_node_unique_ids.py | 53 ++++++++++++++----- 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 4b76a17ce3..19c63c7dda 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -120,8 +120,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Store data on the instance instance.data["lookData"] = {"attributes": attributes, - "relationships": sets.values(), - "sets": looksets} + "relationships": sets} # Collect file nodes used by shading engines (if we have any) files = list() @@ -134,7 +133,7 @@ class CollectLook(pyblish.api.InstancePlugin): instance.data["resources"] = resources # Log a warning when no relevant sets were retrieved for the look. - if not instance.data["lookData"]["sets"]: + if not instance.data["lookData"]["relationships"]: self.log.warning("No sets found for the nodes in the instance: " "%s" % instance[:]) @@ -170,8 +169,7 @@ class CollectLook(pyblish.api.InstancePlugin): if objset in sets: continue - sets[objset] = {"name": objset, - "uuid": lib.get_id(objset), + sets[objset] = {"uuid": lib.get_id(objset), "members": list()} return sets diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 70aec3c70c..15ba682540 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -48,7 +48,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid = set() - attributes = ["sets", "relationships", "attributes"] + attributes = ["relationships", "attributes"] lookdata = instance.data["lookData"] for attr in attributes: if attr not in lookdata: @@ -56,8 +56,10 @@ class ValidateLookContents(pyblish.api.InstancePlugin): "'{}'".format(attr)) invalid.add(instance.name) - if not lookdata["relationships"] or not lookdata["sets"]: - cls.log.error("Look has no `relationship` or `sets`") + # Validate at least one single relationship is collected + if not lookdata["relationships"]: + cls.log.error("Look '{}' has no `relationship` or " + "`sets`".format(instance.name)) invalid.add(instance.name) return invalid @@ -69,11 +71,11 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid = set() relationships = instance.data["lookData"]["relationships"] - for relationship in relationships: - look_name = relationship["name"] - uuid = relationship["uuid"] + for objectset, members in relationships.items(): + uuid = members["uuid"] if not uuid: - cls.errors.append("{} has invalid ID ") + look_name = members["name"] + cls.log.error("{} has invalid ID ".format(look_name)) invalid.add(look_name) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index f168ba3b39..92c33bf9b5 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,5 +1,3 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api import colorbleed.maya.lib as lib @@ -37,11 +35,10 @@ class ValidateLookMembers(pyblish.api.InstancePlugin): members = set() relationships = instance.data["lookData"]["relationships"] - for relation in relationships: + for relation in relationships.values(): members.update([member['name'] for member in relation['members']]) invalid = [m for m in members if not lib.get_id(m)] if invalid: raise RuntimeError("Found invalid nodes.\nNo ID : " "{}".format(invalid)) - diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 41ffed93c2..1dfac3f3f1 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -35,7 +35,7 @@ class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): # Get all members from the sets members = [] relationships = instance.data["lookData"]["relationships"] - for relationship in relationships: + for relationship in relationships.values(): members.extend([i['name'] for i in relationship['members']]) # Ensure we don't have components but the objects diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py index d4fd034710..fbfc007a9a 100644 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py @@ -1,3 +1,4 @@ +import pprint from collections import defaultdict import pyblish.api @@ -17,20 +18,44 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] - @staticmethod - def get_invalid(instance): + @classmethod + def get_invalid(cls, instance): - nodes = instance.data["lookData"]["sets"] + invalid = [] + uuids_dict = defaultdict(list) - # Ensure all nodes have a cbId - id_sets = defaultdict(list) - for node in nodes: - unique_id = lib.get_id(node) - if not unique_id: - continue - id_sets[unique_id].append(node) + relationships = instance.data["lookData"]["relationships"] + pprint.pprint(relationships) + for objectset, relationship in relationships.items(): + cls.log.info("Validating lookData for '%s'" % objectset) + # check if node has UUID and this matches with found node + for member in relationship["members"]: + node = member["name"] + member_uuid = member["uuid"] + uuid_query = lib.get_id(node) - invalid = [n for n in id_sets.itervalues() if len(n) > 1] + if not member_uuid: + cls.log.error("No UUID found for '{}'".format(node)) + invalid.append(node) + continue + + if uuid_query != member_uuid: + cls.log.error("UUID in lookData does not match with " + "queried UUID of '{}'".format(node)) + invalid.append(node) + continue + + # check if the uuid has already passed through the check + # if so it means its a duplicate. + uuids_dict[objectset].append(uuid_query) + + for objectset, member_uuids in uuids_dict.items(): + stored = len(member_uuids) + unique = len(set(member_uuids)) + if unique != stored: + rel_members = relationships[objectset]["members"] + invalid.extend([i["name"] for i in rel_members if + i["uuid"] not in unique]) return invalid @@ -39,5 +64,7 @@ class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Nodes found without " - "asset IDs: {0}".format(invalid)) + for item in invalid: + self.log.error("Invalid node : %s" % item) + raise RuntimeError("Nodes found without unique " + "IDs, see records") From 76873371bf6aad2ae46e2eb2bfb0ae700b23070d Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 10:52:17 +0200 Subject: [PATCH 0124/1249] removed runtime error form get_invalid --- colorbleed/plugins/maya/publish/collect_look.py | 2 +- .../maya/publish/validate_look_members_node_ids.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 19c63c7dda..d9f1b8067f 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -67,7 +67,7 @@ class CollectLook(pyblish.api.InstancePlugin): hosts = ["maya"] # Ignore specifically named sets (check with endswith) - IGNORE = ["out_SET", "controls_SET", "_INST"] + IGNORE = ["out_SET", "controls_SET", "_INST", "_CON"] def process(self, instance): """Collect the Look in the instance with the correct layer settings""" diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 92c33bf9b5..9ecc8cd5fa 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -27,8 +27,8 @@ class ValidateLookMembers(pyblish.api.InstancePlugin): invalid_ids = self.get_invalid(instance) if invalid_ids: - raise RuntimeError("Members found without " - "asset IDs: {0}".format(invalid_ids)) + raise RuntimeError("Found invalid nodes.\nNo ID : " + "{}".format(invalid_ids)) @classmethod def get_invalid(cls, instance): @@ -39,6 +39,6 @@ class ValidateLookMembers(pyblish.api.InstancePlugin): members.update([member['name'] for member in relation['members']]) invalid = [m for m in members if not lib.get_id(m)] - if invalid: - raise RuntimeError("Found invalid nodes.\nNo ID : " - "{}".format(invalid)) + + return invalid + From ea80950b3091e7da6c8d60592b60f1a8a0884771 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 13:53:53 +0200 Subject: [PATCH 0125/1249] removed import --- colorbleed/plugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 70b2c76c6f..84e64f6392 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -1,7 +1,5 @@ import tempfile import pyblish.api -import avalon.maya - ValidatePipelineOrder = pyblish.api.ValidatorOrder + 0.05 ValidateContentsOrder = pyblish.api.ValidatorOrder + 0.1 From 4a2d28b1e17327570194cb97f49947cace2b2877 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 13:54:19 +0200 Subject: [PATCH 0126/1249] updated function ards --- colorbleed/maya/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 2ca8040158..2a28f61684 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -97,8 +97,7 @@ def on_save(_): avalon.logger.info("Running callback on save..") - nodes = lib.get_id_required_nodes(defaults=False, - referenced_nodes=False) + nodes = lib.get_id_required_nodes(referenced_nodes=False) # Lead with asset ID from the database asset = os.environ["AVALON_ASSET"] From c0cfc0bc03b185998ee44f2ebab22182a03ee8bb Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 13:55:03 +0200 Subject: [PATCH 0127/1249] updated look assignment function --- colorbleed/maya/lib.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 0edf3010cf..6d05450e02 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -595,11 +595,10 @@ def maya_temp_folder(): return tmp_dir -def get_id_required_nodes(defaults=False, referenced_nodes=False): +def get_id_required_nodes(referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly Args: - defaults (bool): set to False to filter out default nodes referenced_nodes (bool): set True to filter out reference nodes Returns: nodes (set): list of filtered nodes @@ -611,11 +610,11 @@ def get_id_required_nodes(defaults=False, referenced_nodes=False): ignore = set() if not referenced_nodes: - ignore |= set(cmds.ls(long=True, referencedNodes=referenced_nodes)) + ignore |= set(cmds.ls(long=True, referencedNodes=True)) - if not defaults: - ignore |= set(cmds.ls(long=True, defaultNodes=defaults)) - ignore |= set(cmds.ls(camera_shapes, long=True)) + # list all defaultNodes to filter out from the rest + ignore |= set(cmds.ls(long=True, defaultNodes=True)) + ignore |= set(cmds.ls(camera_shapes, long=True)) # establish set of nodes to ignore types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] @@ -626,9 +625,7 @@ def get_id_required_nodes(defaults=False, referenced_nodes=False): # The items which need to pass the id to their parent # Add the collected transform to the nodes - dag = cmds.ls(nodes, - type="dagNode", - long=True) # query only dag nodes + dag = cmds.ls(nodes, type="dagNode", long=True) # query only dag nodes transforms = cmds.listRelatives(dag, parent=True, @@ -636,6 +633,7 @@ def get_id_required_nodes(defaults=False, referenced_nodes=False): nodes = set(nodes) nodes |= set(transforms) + nodes -= ignore # Remove the ignored nodes return nodes @@ -898,7 +896,7 @@ def apply_shaders(relationships, shadernodes, nodes): """ attributes = relationships.get("attributes", []) - shader_sets = relationships.get("sets", []) + shader_data = relationships.get("relationships", {}) shading_engines = cmds.ls(shadernodes, type="objectSet", long=True) assert len(shading_engines) > 0, ("Error in retrieving objectSets " @@ -915,10 +913,10 @@ def apply_shaders(relationships, shadernodes, nodes): # endregion # region assign - for shader_set in shader_sets: + for shader, data in shader_data.items(): # collect all unique IDs of the set members - shader_uuid = shader_set["uuid"] - member_uuids = [member["uuid"] for member in shader_set["members"]] + shader_uuid = data["uuid"] + member_uuids = [member["uuid"] for member in data["members"]] filtered_nodes = list() for uuid in member_uuids: From 1d94a9f0b24014cd101edad76166f1dabf844bc3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 13:56:44 +0200 Subject: [PATCH 0128/1249] updated lookdev validations and extractor --- .../plugins/maya/publish/extract_look.py | 4 +-- .../maya/publish/validate_look_contents.py | 34 ++++++++++++++++--- .../plugins/maya/publish/validate_node_ids.py | 8 ++--- .../maya/publish/validate_rig_contents.py | 1 - 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 46d45400a4..eb7e7228d0 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -41,7 +41,7 @@ class ExtractLook(colorbleed.api.Extractor): # exported file by accident self.log.info("Extract sets (Maya ASCII) ...") lookdata = instance.data["lookData"] - sets = lookdata["sets"] + sets = lookdata["relationships"].keys() resources = instance.data["resources"] remap = {} @@ -72,7 +72,7 @@ class ExtractLook(colorbleed.api.Extractor): # Write the JSON data self.log.info("Extract json..") data = {"attributes": lookdata["attributes"], - "sets": lookdata["relationships"]} + "relationships": lookdata["relationships"]} with open(json_path, "w") as f: json.dump(data, f) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 15ba682540..1bdd3d0aef 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,5 +1,8 @@ +import maya.cmds as cmds + import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib class ValidateLookContents(pyblish.api.InstancePlugin): @@ -24,16 +27,21 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Invalid look content") + raise RuntimeError("'{}' has invalid look " + "content".format(instance.name)) @classmethod def get_invalid(cls, instance): """Get all invalid nodes""" + cls.log.info("Validating look content for " + "'{}'".format(instance.name)) + + instance_items = cls.validate_instance_items(instance) attributes = list(cls.validate_lookdata_attributes(instance)) relationships = list(cls.validate_relationship_ids(instance)) - invalid = attributes + relationships + invalid = instance_items + attributes + relationships return invalid @@ -58,8 +66,8 @@ class ValidateLookContents(pyblish.api.InstancePlugin): # Validate at least one single relationship is collected if not lookdata["relationships"]: - cls.log.error("Look '{}' has no `relationship` or " - "`sets`".format(instance.name)) + cls.log.error("Look '{}' has no " + "`relationship`".format(instance.name)) invalid.add(instance.name) return invalid @@ -74,8 +82,24 @@ class ValidateLookContents(pyblish.api.InstancePlugin): for objectset, members in relationships.items(): uuid = members["uuid"] if not uuid: - look_name = members["name"] + look_name = objectset cls.log.error("{} has invalid ID ".format(look_name)) invalid.add(look_name) return invalid + + @classmethod + def validate_instance_items(cls, instance): + + required_nodes = lib.get_id_required_nodes(referenced_nodes=False) + + invalid = [node for node in instance if node in required_nodes + and not lib.get_id(node)] + if invalid: + nr_of_invalid = len(invalid) + cls.log.error("Found {} nodes without ID: {}".format(nr_of_invalid, + invalid)) + return invalid + + + diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index fe2a0cb6a8..733af05d4f 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -34,10 +34,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as it might be # part of the end product - id_nodes = lib.get_id_required_nodes(defaults=False, - referenced_nodes=True) - - nodes = instance[:] - invalid = [n for n in nodes if n in id_nodes and not lib.get_id(n)] + id_nodes = lib.get_id_required_nodes(referenced_nodes=True) + invalid = [n for n in instance[:] if n in id_nodes + and not lib.get_id(n)] return invalid diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 3ef1220e56..d07fafe362 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -100,7 +100,6 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ errors = [] for node in nodes: - print node if node not in hierarchy: errors.append(node) return errors From bb52fca88f7b1c5dd15699c3c37aaf5c5d5c2f3e Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 14:23:05 +0200 Subject: [PATCH 0129/1249] changed items() to values() --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 6d05450e02..8b7d04ef87 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -913,7 +913,7 @@ def apply_shaders(relationships, shadernodes, nodes): # endregion # region assign - for shader, data in shader_data.items(): + for data in shader_data.values(): # collect all unique IDs of the set members shader_uuid = data["uuid"] member_uuids = [member["uuid"] for member in data["members"]] From 1da91e6751c436aacecdd7ddf887cb0dc9730b65 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 16:46:40 +0200 Subject: [PATCH 0130/1249] resolved issue with checking sets content --- colorbleed/maya/lib.py | 78 ++++++++++++++++++ .../plugins/maya/publish/collect_look.py | 80 ++----------------- .../maya/publish/validate_look_contents.py | 4 +- .../validate_look_no_default_shaders.py | 55 +++++++------ .../maya/publish/validate_look_sets.py | 73 +++++++++++++++++ 5 files changed, 188 insertions(+), 102 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_look_sets.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8b7d04ef87..2db57b32a6 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -931,3 +931,81 @@ def apply_shaders(relationships, shadernodes, nodes): # endregion apply_attributes(attributes, ns_nodes_by_id) + + +def get_isolate_view_sets(): + """ + + + """ + + view_sets = set() + for panel in cmds.getPanel(type="modelPanel"): + view_set = cmds.modelEditor(panel, query=True, viewObjects=True) + if view_set: + view_sets.add(view_set) + + return view_sets + + +def get_related_sets(node): + """Return objectSets that are relationships for a look for `node`. + + Filters out based on: + - id attribute is NOT `pyblish.avalon.container` + - shapes and deformer shapes (alembic creates meshShapeDeformed) + - set name ends with any from a predefined list + - set in not in viewport set (isolate selected for example) + + Args: + node (str): name of the current not to check + + Returns: + list: The related sets + + """ + + # Ignore specific suffices + ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] + + # Default nodes to ignore + defaults = ["initialShadingGroup", + "defaultLightSet", + "defaultObjectSet"] + + # Ids to ignore + ignored = ["pyblish.avalon.instance", + "pyblish.avalon.container"] + + view_sets = get_isolate_view_sets() + + related_sets = cmds.listSets(object=node, extendToShape=False) + if not related_sets: + return [] + + # Ignore `avalon.container` + sets = [s for s in related_sets if + not cmds.attributeQuery("id", node=s, exists=True) or + not cmds.getAttr("%s.id" % s) in ignored] + + # Exclude deformer sets + # Autodesk documentation on listSets command: + # type(uint) : Returns all sets in the scene of the given + # >>> type: + # >>> 1 - all rendering sets + # >>> 2 - all deformer sets + deformer_sets = cmds.listSets(object=node, + extendToShape=False, + type=2) or [] + deformer_sets = set(deformer_sets) # optimize lookup + sets = [s for s in sets if s not in deformer_sets] + + # Ignore when the set has a specific suffix + sets = [s for s in sets if not any(s.endswith(x) for x in ignore_suffices)] + + # Ignore viewport filter view sets (from isolate select and + # viewports) + sets = [s for s in sets if s not in view_sets] + sets = [s for s in sets if s not in defaults] + + return sets diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index d9f1b8067f..731fd127c8 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -66,9 +66,6 @@ class CollectLook(pyblish.api.InstancePlugin): label = "Collect Look" hosts = ["maya"] - # Ignore specifically named sets (check with endswith) - IGNORE = ["out_SET", "controls_SET", "_INST", "_CON"] - def process(self, instance): """Collect the Look in the instance with the correct layer settings""" @@ -90,16 +87,14 @@ class CollectLook(pyblish.api.InstancePlugin): sets = self.gather_sets(instance) # Lookup with absolute names (from root namespace) - instance_lookup = set([str(x) for x in cmds.ls(instance, - long=True, - absoluteName=True)]) + instance_lookup = set([str(x) for x in cmds.ls(instance, long=True)]) self.log.info("Gathering set relations..") for objset in sets: self.log.debug("From %s.." % objset) content = cmds.sets(objset, query=True) objset_members = sets[objset]["members"] - for member in cmds.ls(content, long=True, absoluteName=True): + for member in cmds.ls(content, long=True): member_data = self.collect_member_data(member, objset_members, instance_lookup, @@ -114,7 +109,7 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Gathering attribute changes to instance members..") attributes = self.collect_attributes_changed(instance) - looksets = cmds.ls(sets.keys(), absoluteName=True, long=True) + looksets = cmds.ls(sets.keys(), long=True) self.log.info("Found the following sets: {}".format(looksets)) @@ -152,19 +147,16 @@ class CollectLook(pyblish.api.InstancePlugin): dict """ - # Get view sets (so we can ignore those sets later) sets = dict() - view_sets = set() - for panel in cmds.getPanel(type="modelPanel"): - view_set = cmds.modelEditor(panel, query=True, viewObjects=True) - if view_set: - view_sets.add(view_set) for node in instance: - related_sets = self.get_related_sets(node, view_sets) + + related_sets = lib.get_related_sets(node) if not related_sets: continue + self.log.info("Found sets %s for %s", related_sets, node) + for objset in related_sets: if objset in sets: continue @@ -174,59 +166,6 @@ class CollectLook(pyblish.api.InstancePlugin): return sets - def get_related_sets(self, node, view_sets): - """Get the sets which do not belong to any specific group - - Filters out based on: - - id attribute is NOT `pyblish.avalon.container` - - shapes and deformer shapes (alembic creates meshShapeDeformed) - - set name ends with any from a predefined list - - set in not in viewport set (isolate selected for example) - - Args: - node (str): name of the current not to check - """ - defaults = ["initialShadingGroup", - "defaultLightSet", - "defaultObjectSet"] - - ignored = ["pyblish.avalon.instance", - "pyblish.avalon.container"] - - related_sets = cmds.listSets(object=node, extendToShape=False) - if not related_sets: - return [] - - # Ignore `avalon.container` - sets = [s for s in related_sets if - not cmds.attributeQuery("id", node=s, exists=True) or - not cmds.getAttr("%s.id" % s) in ignored] - - # Exclude deformer sets - # Autodesk documentation on listSets command: - # type(uint) : Returns all sets in the scene of the given - # >>> type: - # >>> 1 - all rendering sets - # >>> 2 - all deformer sets - deformer_sets = cmds.listSets(object=node, - extendToShape=False, - type=2) or [] - - deformer_sets = set(deformer_sets) # optimize lookup - sets = [s for s in sets if s not in deformer_sets] - - # Ignore specifically named sets - sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] - - # Ignore viewport filter view sets (from isolate select and - # viewports) - sets = [s for s in sets if s not in view_sets] - sets = [s for s in sets if s not in defaults] - - self.log.info("Found sets %s for %s" % (sets, node)) - - return sets - def remove_sets_without_members(self, sets): """Remove any set which does not have any members @@ -270,11 +209,6 @@ class CollectLook(pyblish.api.InstancePlugin): if member in [m["name"] for m in objset_members]: return - # check node type, if mesh get parent! - if cmds.nodeType(node) == "mesh": - # A mesh will always have a transform node in Maya logic - node = cmds.listRelatives(node, parent=True, fullPath=True)[0] - if not cmds.attributeQuery("cbId", node=node, exists=True): self.log.error("Node '{}' has no attribute 'cbId'".format(node)) return diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 1bdd3d0aef..1d9d192521 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,5 +1,3 @@ -import maya.cmds as cmds - import pyblish.api import colorbleed.api import colorbleed.maya.lib as lib @@ -67,7 +65,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): # Validate at least one single relationship is collected if not lookdata["relationships"]: cls.log.error("Look '{}' has no " - "`relationship`".format(instance.name)) + "`relationships`".format(instance.name)) invalid.add(instance.name) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 5042d01796..5fc8ea660f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -31,41 +31,44 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - invalid = [] - disallowed = ["lambert1", - "initialShadingGroup", - "initialParticleSE", - "particleCloud1"] + disallowed = set(["lambert1", + "initialShadingGroup", + "initialParticleSE", + "particleCloud1"]) - members = cmds.listRelatives(instance, - allDescendents=True, - shapes=True, - noIntermediate=True) or [] - for member in members: + invalid = set() + for node in instance: # get connection # listConnections returns a list or None - shading_engine = cmds.listConnections(member, type="objectSet") - if not shading_engine: - cls.log.error("Detected shape without shading engine : " - "'{}'".format(member)) - invalid.append(member) - continue + object_sets = cmds.listConnections(node, type="objectSet") or [] - # retrieve the shading engine out of the list - shading_engine = shading_engine[0] - if shading_engine in disallowed: - cls.log.error("Member connected to a disallows objectSet: " - "'{}'".format(member)) - invalid.append(member) - else: - continue + # Ensure the shape in the instances have at least a single shader + # connected if it *can* have a shader, like a `surfaceShape` in + # Maya. + if (cmds.objectType(node, isAType="surfaceShape") and + not cmds.ls(object_sets, type="shadingEngine")): + cls.log.error("Detected shape without shading engine: " + "'{}'".format(node)) + invalid.add(node) - return invalid + # Check for any disallowed connections + if any(s in disallowed for s in object_sets): + + # Explicitly log each individual "wrong" connection. + for s in object_sets: + if s in disallowed: + cls.log.error("Node has unallowed connection to " + "'{}': {}".format(s, node)) + + invalid.add(node) + + return list(invalid) def process(self, instance): """Process all the nodes in the instance""" invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Invalid shaders found: {0}".format(invalid)) + raise RuntimeError("Invalid node relationships found: " + "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py new file mode 100644 index 0000000000..56f104a18b --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -0,0 +1,73 @@ +from colorbleed.maya import lib + +import pyblish.api +import colorbleed.api + + +class ValidateLookSets(pyblish.api.InstancePlugin): + """Validate if any sets are missing from the instance and look data + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.lookdev'] + hosts = ['maya'] + label = 'Look Sets' + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + """Process all the nodes in the instance""" + + if not instance[:]: + raise RuntimeError("Instance is empty") + + self.log.info("Validation '{}'".format(instance.name)) + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("'{}' has invalid look " + "content".format(instance.name)) + + @classmethod + def get_invalid(cls, instance): + """Get all invalid nodes""" + + cls.log.info("Validating look content for " + "'{}'".format(instance.name)) + + lookdata = instance.data["lookData"] + relationships = lookdata["relationships"] + + invalid = [] + for node in instance: + sets = lib.get_related_sets(node) + if not sets: + continue + + missing_sets = [s for s in sets if s not in relationships] + if missing_sets: + # A set of this node is not coming along, this is wrong! + cls.log.error("Missing sets '{}' for node " + "'{}'".format(missing_sets, node)) + invalid.append(node) + continue + + # Ensure the node is in the sets that are collected + for shaderset, data in relationships.items(): + if shaderset not in sets: + # no need to check for a set if the node + # isn't in it anyway + continue + + member_nodes = [member['name'] for member in data['members']] + if node not in member_nodes: + # The node is not found in the collected set + # relationships + cls.log.error("Missing '{}' in collected set node " + "'{}'".format(node, shaderset)) + invalid.append(node) + + continue + + return invalid + + From ef542916e348812df7267d0fe629cfa86e853c7e Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 10 Aug 2017 16:47:52 +0200 Subject: [PATCH 0131/1249] added deadline plugins for configuration --- .../plugins/maya/publish/submit_deadline.py | 206 ++++++++++++++++++ .../plugins/publish/validate_deadline_done.py | 57 +++++ 2 files changed, 263 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/submit_deadline.py create mode 100644 colorbleed/plugins/publish/validate_deadline_done.py diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py new file mode 100644 index 0000000000..108bd562b3 --- /dev/null +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -0,0 +1,206 @@ +import pyblish.api + + +class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): + """Submit available render layers to Deadline + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable AVALON_DEADLINE + + """ + + label = "Submit to Deadline" + order = pyblish.api.IntegratorOrder + hosts = ["maya"] + families = ["mindbender.renderlayer"] + + def process(self, instance): + import os + import json + import shutil + import getpass + + from maya import cmds + + from avalon import api + from avalon.vendor import requests + + assert api.Session["AVALON_DEADLINE"], "Requires AVALON_DEADLINE" + + context = instance.context + workspace = context.data["workspaceDir"] + fpath = context.data["currentFile"] + fname = os.path.basename(fpath) + name, ext = os.path.splitext(fname) + comment = context.data.get("comment", "") + dirname = os.path.join(workspace, "renders", name) + + try: + os.makedirs(dirname) + except OSError: + pass + + # E.g. http://192.168.0.1:8082/api/jobs + url = api.Session["AVALON_DEADLINE"] + "/api/jobs" + + # Documentation for keys available at: + # https://docs.thinkboxsoftware.com + # /products/deadline/8.0/1_User%20Manual/manual + # /manual-submission.html#job-info-file-options + payload = { + "JobInfo": { + # Top-level group name + "BatchName": fname, + + # Job name, as seen in Monitor + "Name": "%s - %s" % (fname, instance.name), + + # Arbitrary username, for visualisation in Monitor + "UserName": getpass.getuser(), + + "Plugin": "MayaBatch", + "Frames": "{start}-{end}x{step}".format( + start=int(instance.data["startFrame"]), + end=int(instance.data["endFrame"]), + step=int(instance.data["byFrameStep"]), + ), + + "Comment": comment, + + # Optional, enable double-click to preview rendered + # frames from Deadline Monitor + "OutputFilename0": self.preview_fname(instance), + }, + "PluginInfo": { + # Input + "SceneFile": fpath, + + # Output directory and filename + "OutputFilePath": dirname, + "OutputFilePrefix": "/", + + # Mandatory for Deadline + "Version": cmds.about(version=True), + + # Only render layers are considered renderable in this pipeline + "UsingRenderLayers": True, + + # Render only this layer + "RenderLayer": instance.name, + + # Determine which renderer to use from the file itself + "Renderer": "file", + + # Resolve relative references + "ProjectPath": workspace, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Include critical variables with submission + environment = dict({ + # This will trigger `userSetup.py` on the slave + # such that proper initialisation happens the same + # way as it does on a local machine. + # TODO(marcus): This won't work if the slaves don't + # have accesss to these paths, such as if slaves are + # running Linux and the submitter is on Windows. + "PYTHONPATH": os.getenv("PYTHONPATH", ""), + + }, **api.Session) + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + # Include optional render globals + payload["JobInfo"].update( + instance.data.get("renderGlobals", {}) + ) + + self.preflight_check(instance) + + self.log.info("Submitting..") + self.log.info(json.dumps( + payload, indent=4, sort_keys=True) + ) + + response = requests.post(url, json=payload) + + if response.ok: + # Write metadata for publish + fname = os.path.join(dirname, instance.name + ".json") + data = { + "submission": payload, + "session": api.Session, + "instance": instance.data, + "jobs": [ + response.json() + ], + } + + with open(fname, "w") as f: + json.dump(data, f, indent=4, sort_keys=True) + + else: + try: + shutil.rmtree(dirname) + except OSError: + # This is nice-to-have, but not critical to the operation + pass + + raise Exception(response.text) + + def preview_fname(self, instance): + """Return outputted filename with #### for padding + + Passing the absolute path to Deadline enables Deadline Monitor + to provide the user with a Job Output menu option. + + Deadline requires the path to be formatted with # in place of numbers. + + From + /path/to/render.0000.png + To + /path/to/render.####.png + + """ + + from maya import cmds + + # We'll need to take tokens into account + fname = cmds.renderSettings( + firstImageName=True, + fullPath=True, + layer=instance.name + )[0] + + try: + # Assume `c:/some/path/filename.0001.exr` + # TODO(marcus): Bulletproof this, the user may have + # chosen a different format for the outputted filename. + fname, padding, suffix = fname.rsplit(".", 2) + fname = ".".join([fname, "#" * len(padding), suffix]) + self.log.info("Assuming renders end up @ %s" % fname) + except ValueError: + fname = "" + self.log.info("Couldn't figure out where renders go") + + return fname + + def preflight_check(self, instance): + for key in ("startFrame", "endFrame", "byFrameStep"): + value = instance.data[key] + + if int(value) == value: + continue + + self.log.warning( + "%f=%d was rounded off to nearest integer" + % (value, int(value)) + ) diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py new file mode 100644 index 0000000000..1ab539eefa --- /dev/null +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -0,0 +1,57 @@ +import pyblish.api + + +class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): + """Ensure render is finished before publishing the resulting images""" + + label = "Rendered Successfully" + order = pyblish.api.ValidatorOrder + hosts = ["shell"] + families = ["mindbender.imagesequence"] + optional = True + + def process(self, instance): + from avalon import api + from avalon.vendor import requests + + # From Deadline documentation + # https://docs.thinkboxsoftware.com/products/deadline/8.0/ + # 1_User%20Manual/manual/rest-jobs.html#job-property-values + states = { + 0: "Unknown", + 1: "Active", + 2: "Suspended", + 3: "Completed", + 4: "Failed", + 6: "Pending", + } + + url = api.Session["AVALON_DEADLINE"] + "/api/jobs?JobID=%s" + + for job in instance.data["metadata"]["jobs"]: + response = requests.get(url % job["_id"]) + + if response.ok: + data = response.json()[0] + state = states.get(data["Stat"]) + + if state in (None, "Unknown"): + raise Exception("State of this render is unknown") + + elif state == "Active": + raise Exception("This render is still currently active") + + elif state == "Suspended": + raise Exception("This render is suspended") + + elif state == "Failed": + raise Exception("This render was not successful") + + elif state == "Pending": + raise Exception("This render is pending") + else: + self.log.info("%s was rendered successfully" % instance) + + else: + raise Exception("Could not determine the current status " + " of this render") From 90baa1ec281a266b08203a9f709642802ba89d78 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 11:38:45 +0200 Subject: [PATCH 0132/1249] removed redudant plugin, simplified checks and cosmetics --- .../maya/publish/validate_look_contents.py | 32 ++++----- .../publish/validate_look_members_node_ids.py | 21 +++--- .../publish/validate_look_members_unique.py | 54 ++++++-------- .../publish/validate_look_node_unique_ids.py | 70 ------------------- .../maya/publish/validate_look_sets.py | 5 +- .../maya/publish/validate_unique_node_ids.py | 23 +++--- 6 files changed, 59 insertions(+), 146 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 1d9d192521..b3cb90803c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -22,7 +22,6 @@ class ValidateLookContents(pyblish.api.InstancePlugin): if not instance[:]: raise RuntimeError("Instance is empty") - invalid = self.get_invalid(instance) if invalid: raise RuntimeError("'{}' has invalid look " @@ -35,11 +34,14 @@ class ValidateLookContents(pyblish.api.InstancePlugin): cls.log.info("Validating look content for " "'{}'".format(instance.name)) + # check if data has the right attributes and content + attributes = cls.validate_lookdata_attributes(instance) + # check the looks for ID + looks = cls.validate_looks(instance) + # check the uuid of the instance nodes instance_items = cls.validate_instance_items(instance) - attributes = list(cls.validate_lookdata_attributes(instance)) - relationships = list(cls.validate_relationship_ids(instance)) - invalid = instance_items + attributes + relationships + invalid = looks + instance_items + attributes return invalid @@ -68,21 +70,17 @@ class ValidateLookContents(pyblish.api.InstancePlugin): "`relationships`".format(instance.name)) invalid.add(instance.name) - return invalid + return list(invalid) @classmethod - def validate_relationship_ids(cls, instance): - """Validate and update lookData relationships""" + def validate_looks(cls, instance): - invalid = set() - - relationships = instance.data["lookData"]["relationships"] - for objectset, members in relationships.items(): - uuid = members["uuid"] - if not uuid: - look_name = objectset - cls.log.error("{} has invalid ID ".format(look_name)) - invalid.add(look_name) + looks = instance.data["lookData"]["relationships"] + invalid = [] + for name, data in looks.items(): + if not data["uuid"]: + cls.log.error("Look '{}' has no UUID".format(name)) + invalid.append(name) return invalid @@ -90,7 +88,6 @@ class ValidateLookContents(pyblish.api.InstancePlugin): def validate_instance_items(cls, instance): required_nodes = lib.get_id_required_nodes(referenced_nodes=False) - invalid = [node for node in instance if node in required_nodes and not lib.get_id(node)] if invalid: @@ -100,4 +97,3 @@ class ValidateLookContents(pyblish.api.InstancePlugin): return invalid - diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py index 9ecc8cd5fa..c6a914b304 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py @@ -1,24 +1,19 @@ import pyblish.api import colorbleed.api -import colorbleed.maya.lib as lib class ValidateLookMembers(pyblish.api.InstancePlugin): """Validate look members have colorbleed id attributes - Looks up the contents of the look to see if all its members have - Colorbleed Id attributes so they can be connected correctly. - - When invalid it's very likely related to the model not having the id - attributes that it should have. These should have been generated in the - work files for the model/rig/fur or alike. + Loops up all relationship members and check if all the members have the + cbId (colorbleed id) and return all the nodes who fail the test. """ order = colorbleed.api.ValidatePipelineOrder families = ['colorbleed.lookdev'] hosts = ['maya'] - label = 'Look Members' + label = 'Look Members (ID)' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] @@ -33,12 +28,12 @@ class ValidateLookMembers(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - members = set() relationships = instance.data["lookData"]["relationships"] - for relation in relationships.values(): - members.update([member['name'] for member in relation['members']]) + members = [] + for relationship in relationships.values(): + members.extend(relationship["members"]) - invalid = [m for m in members if not lib.get_id(m)] + # get the name of the node when there is no UUID + invalid = [m["name"] for m in members if not m["uuid"]] return invalid - diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 1dfac3f3f1..9391b086dd 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -1,17 +1,14 @@ from collections import defaultdict -from maya import cmds - import pyblish.api import colorbleed.api -import colorbleed.maya.lib as lib -class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): +class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): """Validate the relational nodes of the look data to ensure every node is unique. - This ensures the same id is not present as more than one node in the look. + This ensures the all member ids are unique. That means there's only ever one of a specific node inside the look to be published. For example if you'd have a loaded 3x the same tree and by @@ -22,38 +19,13 @@ class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - label = 'Non Duplicate Relationship Members (ID)' + label = 'Unique Relationship Members (ID)' hosts = ['maya'] families = ['colorbleed.lookdev'] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] - @staticmethod - def get_invalid(instance): - - # Get all members from the sets - members = [] - relationships = instance.data["lookData"]["relationships"] - for relationship in relationships.values(): - members.extend([i['name'] for i in relationship['members']]) - - # Ensure we don't have components but the objects - members = set(cmds.ls(members, objectsOnly=True, long=True)) - members = list(members) - - # Group members per id - id_nodes = defaultdict(set) - for node in members: - node_id = lib.get_id(node) - if not node_id: - continue - id_nodes[node_id].add(node) - - invalid = [n for n in id_nodes.itervalues() if len(n) > 1] - - return invalid - def process(self, instance): """Process all meshes""" @@ -61,3 +33,23 @@ class ValidateNonDuplicateRelationshipMembers(pyblish.api.InstancePlugin): if invalid: raise RuntimeError("Members found without asset IDs: " "{0}".format(invalid)) + + @staticmethod + def get_invalid(instance): + + # Get all members from the sets + id_nodes = defaultdict(list) + relationships = instance.data["lookData"]["relationships"] + for relationship in relationships.values(): + for member in relationship['members']: + node_id = member["uuid"] + node = member["name"] + id_nodes[node_id].append(node) + + # check if any id has more than 1 node + invalid = [] + for nodes in id_nodes.values(): + if len(nodes) > 1: + invalid.extend(nodes) + + return invalid diff --git a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py b/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py deleted file mode 100644 index fbfc007a9a..0000000000 --- a/colorbleed/plugins/maya/publish/validate_look_node_unique_ids.py +++ /dev/null @@ -1,70 +0,0 @@ -import pprint -from collections import defaultdict - -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - - -class ValidateLookNodeUniqueIds(pyblish.api.InstancePlugin): - """Validate look sets have unique colorbleed id attributes - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.lookdev'] - hosts = ['maya'] - label = 'Look Id Unique Attributes' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.RepairAction] - - @classmethod - def get_invalid(cls, instance): - - invalid = [] - uuids_dict = defaultdict(list) - - relationships = instance.data["lookData"]["relationships"] - pprint.pprint(relationships) - for objectset, relationship in relationships.items(): - cls.log.info("Validating lookData for '%s'" % objectset) - # check if node has UUID and this matches with found node - for member in relationship["members"]: - node = member["name"] - member_uuid = member["uuid"] - uuid_query = lib.get_id(node) - - if not member_uuid: - cls.log.error("No UUID found for '{}'".format(node)) - invalid.append(node) - continue - - if uuid_query != member_uuid: - cls.log.error("UUID in lookData does not match with " - "queried UUID of '{}'".format(node)) - invalid.append(node) - continue - - # check if the uuid has already passed through the check - # if so it means its a duplicate. - uuids_dict[objectset].append(uuid_query) - - for objectset, member_uuids in uuids_dict.items(): - stored = len(member_uuids) - unique = len(set(member_uuids)) - if unique != stored: - rel_members = relationships[objectset]["members"] - invalid.extend([i["name"] for i in rel_members if - i["uuid"] not in unique]) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - if invalid: - for item in invalid: - self.log.error("Invalid node : %s" % item) - raise RuntimeError("Nodes found without unique " - "IDs, see records") diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 56f104a18b..d897d586e2 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -34,15 +34,16 @@ class ValidateLookSets(pyblish.api.InstancePlugin): cls.log.info("Validating look content for " "'{}'".format(instance.name)) - lookdata = instance.data["lookData"] - relationships = lookdata["relationships"] + relationships = instance.data["lookData"]["relationships"] invalid = [] for node in instance: + # get the connected objectSets of the node sets = lib.get_related_sets(node) if not sets: continue + # check if any objectSets are not present ion the relationships missing_sets = [s for s in sets if s not in relationships] if missing_sets: # A set of this node is not coming along, this is wrong! diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index 3de16d5a15..d08775f5b0 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -21,6 +21,16 @@ class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId + invalid = self.get_invalid_dict(instance) + if invalid: + raise RuntimeError("Nodes found with non-unique " + "asset IDs: {0}".format(invalid)) + + @classmethod def get_invalid_dict(cls, instance): """Return a dictionary mapping of id key to list of member nodes""" @@ -53,15 +63,4 @@ class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): for members in invalid_dict.itervalues(): invalid.extend(members) - return invalid - - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId - invalid = self.get_invalid_dict(instance) - if invalid: - raise RuntimeError("Nodes found with non-unique " - "asset IDs: {0}".format(invalid)) - - + return invalid \ No newline at end of file From a3831c7895d359235e11d53a6f117338c8419ee0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 12:09:20 +0200 Subject: [PATCH 0133/1249] removed instnace id --- .../maya/publish/validate_look_contents.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index b3cb90803c..f57c057a8a 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -38,10 +38,8 @@ class ValidateLookContents(pyblish.api.InstancePlugin): attributes = cls.validate_lookdata_attributes(instance) # check the looks for ID looks = cls.validate_looks(instance) - # check the uuid of the instance nodes - instance_items = cls.validate_instance_items(instance) - invalid = looks + instance_items + attributes + invalid = looks + attributes return invalid @@ -83,17 +81,3 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid.append(name) return invalid - - @classmethod - def validate_instance_items(cls, instance): - - required_nodes = lib.get_id_required_nodes(referenced_nodes=False) - invalid = [node for node in instance if node in required_nodes - and not lib.get_id(node)] - if invalid: - nr_of_invalid = len(invalid) - cls.log.error("Found {} nodes without ID: {}".format(nr_of_invalid, - invalid)) - return invalid - - From 5fbf5dc0721fa5eb7836fd5b4d510c230bef8b8b Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 12:11:25 +0200 Subject: [PATCH 0134/1249] Improved docstrings --- colorbleed/plugins/maya/publish/validate_look_contents.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index f57c057a8a..4f62fdd2ac 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,13 +1,14 @@ import pyblish.api import colorbleed.api -import colorbleed.maya.lib as lib class ValidateLookContents(pyblish.api.InstancePlugin): """Validate look instance contents - This is invalid when the collection was unable to collect the required - data for a look to be published correctly. + Rules: + * Look data must have `relationships` and `attributes` keys. + * At least one relationship must be collection. + * All relationship object sets at least have an ID value """ From 3d8bb4d980b05d478f275f53066a669ccefd66ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 12:20:16 +0200 Subject: [PATCH 0135/1249] removed debugging and redundant if statement --- colorbleed/plugins/maya/publish/validate_look_sets.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index d897d586e2..ab77206e56 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -18,10 +18,6 @@ class ValidateLookSets(pyblish.api.InstancePlugin): def process(self, instance): """Process all the nodes in the instance""" - if not instance[:]: - raise RuntimeError("Instance is empty") - - self.log.info("Validation '{}'".format(instance.name)) invalid = self.get_invalid(instance) if invalid: raise RuntimeError("'{}' has invalid look " From 1026e6e5f9ad4b8c0c5bb2d51a1cc1be7843be32 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 12:35:02 +0200 Subject: [PATCH 0136/1249] updated docstrings --- colorbleed/plugins/maya/publish/validate_look_sets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index ab77206e56..95dc266f97 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -7,6 +7,16 @@ import colorbleed.api class ValidateLookSets(pyblish.api.InstancePlugin): """Validate if any sets are missing from the instance and look data + A node might have a relationship with a shader but has no Colorbleed ID. + Because it is missing the ID it has not been collected in the instance. + When the relationship needs to be maintained the artist might need to + create a different* relationship or ensure the node has the Colorbleed ID. + + * The relationship might be too broad (assigned to top node if hierarchy). + This can be countered by creating the relationship on the shape or its + transform. + In essence, ensure item the shader is assigned to has the Colorbleed ID! + """ order = colorbleed.api.ValidateContentsOrder From aaedaf135f73082d5af3eafc6e9181b6fd1e76bf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 11 Aug 2017 14:57:56 +0200 Subject: [PATCH 0137/1249] Support running correctly from server --- colorbleed/maya/__init__.py | 5 ----- colorbleed/maya/menu.py | 4 ---- maya_environment.bat | 5 +++++ set_environment.bat | 3 --- 4 files changed, 5 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 2a28f61684..60266cef75 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -21,11 +21,6 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") def install(): - # add local pipeline library to the paths - site.addsitedir(r"P:\pipeline\dev\git\cb") - site.addsitedir(r"C:\Users\User\Documents\development\cbra") - site.addsitedir(r"C:\Users\User\Documents\development\pyblish-cb") - pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index a59d0310c7..de7b252554 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -10,16 +10,12 @@ import maya.cmds as cmds self = sys.modules[__name__] self._menu = "colorbleed" -# set colorbleed scripts path in environment keys -os.environ["COLORBLEED_SCRIPTS"] = "P:\pipeline\dev\git\cbMayaScripts\cbMayaScripts" - log = logging.getLogger(__name__) def deferred(): # todo: replace path with server / library path - site.addsitedir("C:\Users\User\Documents\development\scriptsmenu\python") from scriptsmenu import launchformaya import scriptsmenu.scriptsmenu as menu diff --git a/maya_environment.bat b/maya_environment.bat index efeb2d4063..ceb50bbd74 100644 --- a/maya_environment.bat +++ b/maya_environment.bat @@ -12,6 +12,11 @@ if "%CB_MAYA_SHARED%" == "" ( ) +:: For scripts menu tool +set PYTHONPATH=%CB_PIPELINE%\git\scriptsmenu;%PYTHONPATH% +set CB_SCRIPTS=%CB_PIPELINE%\git\cbMayaScripts\cbMayaScripts +set COLORBLEED_SCRIPTS=%CB_SCRIPTS% + :: Colorbleed Maya set PYTHONPATH=%CB_PIPELINE%\git\cbMayaScripts;%PYTHONPATH% set PYTHONPATH=%CB_PIPELINE%\git\inventory\python;%PYTHONPATH% diff --git a/set_environment.bat b/set_environment.bat index 832d1de1d5..6af25d9d6a 100644 --- a/set_environment.bat +++ b/set_environment.bat @@ -14,8 +14,6 @@ if "%CB_APP_SHARED%" == "" ( echo setting STORAGE.. set STORAGE=P: -set LAUNCHER_ROOT=%~dp0/launchers - :: Core echo Add cb core.. set PYTHONPATH=%CB_PIPELINE%\git\cb;%PYTHONPATH% @@ -25,7 +23,6 @@ set PYTHONPATH=%CB_PIPELINE%\git\cbra;%PYTHONPATH% set PYTHONPATH=%CB_PIPELINE%\git\pyseq;%PYTHONPATH% set PYTHONPATH=%CB_PIPELINE%\git\Qt.py;%PYTHONPATH% - :: Ftrack-connect ::set PYTHONPATH=%CB_PIPELINE%\git\ftrack-connect\source;%PYTHONPATH% From 37b5603cd95f16366c3113a50e1ff6ee1416a29a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 11 Aug 2017 15:02:06 +0200 Subject: [PATCH 0138/1249] Fix incorrectly making containers when reusing a loaded lookdev upon assignments --- colorbleed/maya/lib.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2db57b32a6..50dbfed14e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -804,20 +804,21 @@ def assign_look_by_version(nodes, version_id): namespace=namespace, reference=True, returnNewNodes=True) + + # containerise like avalon (for manager) + # give along a fake "context" with only `representation` + # because `maya.containerise` only used that key anyway + context = {"representation": shader_file} + subset_name = shader_file["context"]["subset"] + maya.containerise(name=subset_name, + namespace=namespace, + nodes=shader_nodes, + context=context) else: log.info("Reusing existing lookdev '{}'".format(reference_node)) shader_nodes = cmds.referenceQuery(reference_node, nodes=True) namespace = cmds.referenceQuery(reference_node, namespace=True) - # containerise like avalon (for manager) - # give re - context = {"representation": shader_file} - subset_name = shader_file["context"]["subset"] - maya.containerise(name=subset_name, - namespace=namespace, - nodes=shader_nodes, - context=context) - # Assign relationships with open(shader_relation, "r") as f: relationships = json.load(f) From a06b078a26d0e5f30480d600c28536e2d95557d9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 11 Aug 2017 16:04:11 +0200 Subject: [PATCH 0139/1249] fixed issue with unable to load colorbleed menu --- colorbleed/maya/menu.py | 5 ----- maya_environment.bat | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index de7b252554..1aaf3bc372 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -1,7 +1,6 @@ import sys import os import logging -import site from avalon.vendor.Qt import QtWidgets, QtCore @@ -15,14 +14,10 @@ log = logging.getLogger(__name__) def deferred(): - # todo: replace path with server / library path from scriptsmenu import launchformaya import scriptsmenu.scriptsmenu as menu - reload(launchformaya) - reload(menu) - log.info("Attempting to install ...") # load configuration of custom menu diff --git a/maya_environment.bat b/maya_environment.bat index ceb50bbd74..a7850a7f07 100644 --- a/maya_environment.bat +++ b/maya_environment.bat @@ -13,7 +13,7 @@ if "%CB_MAYA_SHARED%" == "" ( :: For scripts menu tool -set PYTHONPATH=%CB_PIPELINE%\git\scriptsmenu;%PYTHONPATH% +set PYTHONPATH=%CB_PIPELINE%\git\scriptsmenu\python;%PYTHONPATH% set CB_SCRIPTS=%CB_PIPELINE%\git\cbMayaScripts\cbMayaScripts set COLORBLEED_SCRIPTS=%CB_SCRIPTS% From 932ae63de301c3ab034e0167c26418d9627362eb Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 15 Aug 2017 14:19:58 +0200 Subject: [PATCH 0140/1249] added collectors for rendering --- .../maya/publish/collect_maya_workspace.py | 30 ++++++ .../maya/publish/collect_renderlayers.py | 101 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_maya_workspace.py create mode 100644 colorbleed/plugins/maya/publish/collect_renderlayers.py diff --git a/colorbleed/plugins/maya/publish/collect_maya_workspace.py b/colorbleed/plugins/maya/publish/collect_maya_workspace.py new file mode 100644 index 0000000000..3feff69362 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_maya_workspace.py @@ -0,0 +1,30 @@ +import os + +import pyblish.api + +from maya import cmds + + +class CollectMayaWorkspace(pyblish.api.ContextPlugin): + """Inject the current workspace into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Maya Workspace" + + hosts = ['maya'] + version = (0, 1, 0) + + def process(self, context): + workspace = cmds.workspace(rootDirectory=True, query=True) + if not workspace: + # Project has not been set. Files will + # instead end up next to the working file. + workspace = cmds.workspace(dir=True, query=True) + + # Maya returns forward-slashes by default + normalised = os.path.normpath(workspace) + + context.set_data('workspaceDir', value=normalised) + + # For backwards compatibility + context.set_data('workspace_dir', value=normalised) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py new file mode 100644 index 0000000000..58340736f8 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -0,0 +1,101 @@ +import os +from maya import cmds + +import pyblish.api + +from avalon import maya, api + +class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): + """Gather instances by active render layers""" + + order = pyblish.api.CollectorOrder + hosts = ["maya"] + label = "Render Layers" + + def process(self, context): + + registered_root = api.registered_root() + asset_name = os.environ["AVALON_ASSET"] + + current_file = context.data["currentFile"] + relative_file = current_file.replace(registered_root, "{root}") + source_file = relative_file.replace("\\", "/") + + renderlayers = cmds.ls(type="renderLayer") + for layer in renderlayers: + if layer.endswith("defaultRenderLayer"): + continue + + data = {"family": "Render Layers", + "families": ["colorbleed.renderlayer"], + "publish": cmds.getAttr("{}.renderable".format(layer)), + + "startFrame": self.get_render_attribute("startFrame"), + "endFrame": self.get_render_attribute("endFrame"), + "byFrameStep": self.get_render_attribute("byFrameStep"), + "renderer": self.get_render_attribute("currentRenderer"), + + # instance subset + "asset": asset_name, + "subset": layer, + "setMembers": layer, + + # "time": context.data["time"], + "author": context.data["user"], + "source": source_file} + + # Apply each user defined attribute as data + for attr in cmds.listAttr(layer, userDefined=True) or list(): + try: + value = cmds.getAttr("{}.{}".format(layer, attr)) + except Exception: + # Some attributes cannot be read directly, + # such as mesh and color attributes. These + # are considered non-essential to this + # particular publishing pipeline. + value = None + + data[attr] = value + + # Include (optional) global settings + # TODO(marcus): Take into account layer overrides + try: + avalon_globals = maya.lsattr("id", "avalon.renderglobals")[0] + except IndexError: + pass + else: + _globals = maya.read(avalon_globals) + data["renderGlobals"] = self.get_global_overrides(_globals) + + instance = context.create_instance(layer) + instance.data.update(data) + + def get_render_attribute(self, attr): + return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) + + def get_global_overrides(self, globals): + """ + Get all overrides with a value, skip those without + + Here's the kicker. These globals override defaults in the submission + integrator, but an empty value means no overriding is made. + Otherwise, Frames would override the default frames set under globals. + + Args: + globals (dict) collection of render globals + + Returns: + dict: only overrides with values + """ + keys = ["pool", "group", "frames", "priority"] + read_globals = {} + for key in keys: + value = globals[key] + if not value: + continue + read_globals[key.capitalize()] = value + + if not read_globals: + self.log.info("Submitting without overrides") + + return read_globals \ No newline at end of file From 36811882f5379042cc0589357a67fa9f62231683 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 15 Aug 2017 14:21:29 +0200 Subject: [PATCH 0141/1249] added submit deadline module, added fallback ip for webservice --- .../plugins/maya/publish/submit_deadline.py | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 108bd562b3..5c27dde016 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -1,3 +1,13 @@ +import os +import json +import shutil +import getpass + +from maya import cmds + +from avalon import api +from avalon.vendor import requests + import pyblish.api @@ -12,20 +22,12 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["maya"] - families = ["mindbender.renderlayer"] + families = ["colorbleed.renderlayer"] def process(self, instance): - import os - import json - import shutil - import getpass - from maya import cmds - - from avalon import api - from avalon.vendor import requests - - assert api.Session["AVALON_DEADLINE"], "Requires AVALON_DEADLINE" + deadline = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") + assert deadline is not None, "Requires AVALON_DEADLINE" context = instance.context workspace = context.data["workspaceDir"] @@ -41,7 +43,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): pass # E.g. http://192.168.0.1:8082/api/jobs - url = api.Session["AVALON_DEADLINE"] + "/api/jobs" + url = "{}/api/jobs".format(deadline) + print "Got Deadline URL : {}".format(url) # Documentation for keys available at: # https://docs.thinkboxsoftware.com @@ -119,22 +122,19 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): }) # Include optional render globals - payload["JobInfo"].update( - instance.data.get("renderGlobals", {}) - ) + payload["JobInfo"].update(instance.data.get("renderGlobals", {})) self.preflight_check(instance) self.log.info("Submitting..") - self.log.info(json.dumps( - payload, indent=4, sort_keys=True) - ) + self.log.info(json.dumps(payload, indent=4, sort_keys=True)) response = requests.post(url, json=payload) if response.ok: + print "Got positive response from {}".format(url) # Write metadata for publish - fname = os.path.join(dirname, instance.name + ".json") + fname = os.path.join(dirname, "{}.json".format(instance.name)) data = { "submission": payload, "session": api.Session, @@ -148,6 +148,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): json.dump(data, f, indent=4, sort_keys=True) else: + print "Got negative response from {}".format(url) try: shutil.rmtree(dirname) except OSError: @@ -171,14 +172,10 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): """ - from maya import cmds - # We'll need to take tokens into account - fname = cmds.renderSettings( - firstImageName=True, - fullPath=True, - layer=instance.name - )[0] + fname = cmds.renderSettings(firstImageName=True, + fullPath=True, + layer=instance.name)[0] try: # Assume `c:/some/path/filename.0001.exr` From 5e92007254ebce39eab91ee22fb520e59948a5f2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:18:55 +0200 Subject: [PATCH 0142/1249] added functions for getting the global overrides --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 58340736f8..92a7595905 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -5,6 +5,7 @@ import pyblish.api from avalon import maya, api + class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): """Gather instances by active render layers""" @@ -40,7 +41,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): "subset": layer, "setMembers": layer, - # "time": context.data["time"], + "time": api.time(), "author": context.data["user"], "source": source_file} From eebfcedeeb66881eb4525957a329c2fad553b5bc Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:20:50 +0200 Subject: [PATCH 0143/1249] updated preview_fname function --- .../plugins/maya/publish/submit_deadline.py | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 5c27dde016..2d093d4653 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -1,4 +1,5 @@ import os +import re import json import shutil import getpass @@ -11,6 +12,61 @@ from avalon.vendor import requests import pyblish.api +def get_padding_length(filename): + """ + + >>> get_padding_length("sequence.v004.0001.exr", default=None) + 4 + >>> get_padding_length("sequence.-001.exr", default=None) + 4 + >>> get_padding_length("sequence.v005.exr", default=None) + None + + Retrieve the padding length by retrieving the frame number from a file. + + Args: + filename (str): the explicit filename, e.g.: sequence.0001.exr + + Returns: + int + """ + + padding_match = re.search(r"\.(-?\d+)", filename) + if padding_match: + length = len(padding_match.group(2)) + else: + raise AttributeError("Could not find padding length in " + "'{}'".format(filename)) + + return length + + +def get_vray_extension(): + """Retrieve the extension which has been set in the VRay settings + + Will return None if the current renderer is not VRay + + Returns: + str + """ + + ext = None + if cmds.getAttr("defaultRenderGlobals.currentRenderer") == "vray": + # check for vray settings node + settings_node = cmds.ls("vraySettings", type="VRaySettingsNode") + if not settings_node: + raise AttributeError("Could not find a VRay Settings Node, " + "to ensure the node exists open the " + "Render Settings window") + + # get the extension + image_format = cmds.getAttr("vraySettings.imageFormatStr") + if image_format and image_format != ext: + ext = ".{}".format(image_format) + + return ext + + class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): """Submit available render layers to Deadline @@ -44,7 +100,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline) - print "Got Deadline URL : {}".format(url) # Documentation for keys available at: # https://docs.thinkboxsoftware.com @@ -132,7 +187,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): response = requests.post(url, json=payload) if response.ok: - print "Got positive response from {}".format(url) # Write metadata for publish fname = os.path.join(dirname, "{}.json".format(instance.name)) data = { @@ -148,7 +202,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): json.dump(data, f, indent=4, sort_keys=True) else: - print "Got negative response from {}".format(url) try: shutil.rmtree(dirname) except OSError: @@ -181,16 +234,26 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Assume `c:/some/path/filename.0001.exr` # TODO(marcus): Bulletproof this, the user may have # chosen a different format for the outputted filename. - fname, padding, suffix = fname.rsplit(".", 2) - fname = ".".join([fname, "#" * len(padding), suffix]) + directory = os.path.dirname(fname) + basename = os.path.basename(fname) + name, padding, ext = basename.rsplit(".", 2) + + # check if the extension is correct for vray + ext = get_vray_extension() or ext + padding_length = get_padding_length(basename) + padding = "#" * padding_length + fname = ".".join([name, padding, ext]) self.log.info("Assuming renders end up @ %s" % fname) + file_name = os.path.join(directory, fname) except ValueError: - fname = "" + file_name = "" self.log.info("Couldn't figure out where renders go") - return fname + return file_name def preflight_check(self, instance): + """Ensure the startFrame, endFrame and byFrameStep are integers""" + for key in ("startFrame", "endFrame", "byFrameStep"): value = instance.data[key] From a47ec33221ffb4eae02aad3fa3e6189e98950954 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:21:33 +0200 Subject: [PATCH 0144/1249] added colorbleed image sequence --- colorbleed/plugins/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 07038cdb1b..357ed080fb 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -30,7 +30,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.lookdev", "colorbleed.texture", "colorbleed.historyLookdev", - "colorbleed.group"] + "colorbleed.group", + "colorbleed.imagesequence"] def process(self, instance): From e09202d6a0182eef49430948f3168f8069f0de36 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:24:04 +0200 Subject: [PATCH 0145/1249] added colorbleed.imagesequence --- colorbleed/plugins/publish/validate_deadline_done.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py index 1ab539eefa..a5b420fb05 100644 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -7,7 +7,7 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): label = "Rendered Successfully" order = pyblish.api.ValidatorOrder hosts = ["shell"] - families = ["mindbender.imagesequence"] + families = ["colorbleed.imagesequence"] optional = True def process(self, instance): From 296d07fdda78686b71e713f978f0e88428136143 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:28:32 +0200 Subject: [PATCH 0146/1249] moved imports to the top of the module --- colorbleed/plugins/publish/collect_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_time.py b/colorbleed/plugins/publish/collect_time.py index 9536c610d6..9f1a7da07f 100644 --- a/colorbleed/plugins/publish/collect_time.py +++ b/colorbleed/plugins/publish/collect_time.py @@ -1,4 +1,5 @@ import pyblish.api +from avalon import api class CollectMindbenderTime(pyblish.api.ContextPlugin): @@ -8,5 +9,4 @@ class CollectMindbenderTime(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder def process(self, context): - from avalon import api context.data["time"] = api.time() From 25d60b8562e716254462869db454fa83ab485528 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 11:55:28 +0200 Subject: [PATCH 0147/1249] fixed OutputFileName0 input --- colorbleed/plugins/maya/publish/submit_deadline.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 2d093d4653..842f668b5e 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -33,7 +33,7 @@ def get_padding_length(filename): padding_match = re.search(r"\.(-?\d+)", filename) if padding_match: - length = len(padding_match.group(2)) + length = len(padding_match.group()) else: raise AttributeError("Could not find padding length in " "'{}'".format(filename)) @@ -62,7 +62,7 @@ def get_vray_extension(): # get the extension image_format = cmds.getAttr("vraySettings.imageFormatStr") if image_format and image_format != ext: - ext = ".{}".format(image_format) + ext = "{}".format(image_format) return ext @@ -127,7 +127,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": self.preview_fname(instance), + "OutputFilename0": self.preview_fname(instance, dirname), }, "PluginInfo": { # Input @@ -210,7 +210,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) - def preview_fname(self, instance): + def preview_fname(self, instance, dirname): """Return outputted filename with #### for padding Passing the absolute path to Deadline enables Deadline Monitor @@ -234,7 +234,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Assume `c:/some/path/filename.0001.exr` # TODO(marcus): Bulletproof this, the user may have # chosen a different format for the outputted filename. - directory = os.path.dirname(fname) basename = os.path.basename(fname) name, padding, ext = basename.rsplit(".", 2) @@ -244,7 +243,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): padding = "#" * padding_length fname = ".".join([name, padding, ext]) self.log.info("Assuming renders end up @ %s" % fname) - file_name = os.path.join(directory, fname) + file_name = os.path.join(dirname, instance.name, fname) except ValueError: file_name = "" self.log.info("Couldn't figure out where renders go") From 84ef2926ae0d0ee567b77e6758faae3643c3e485 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 17:57:39 +0200 Subject: [PATCH 0148/1249] added image sequence collector --- .../plugins/publish/collect_imagesequences.py | 57 +++++++++++++++++++ .../publish/collect_shell_workspace.py | 14 +++++ 2 files changed, 71 insertions(+) create mode 100644 colorbleed/plugins/publish/collect_imagesequences.py create mode 100644 colorbleed/plugins/publish/collect_shell_workspace.py diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py new file mode 100644 index 0000000000..bdb703f421 --- /dev/null +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -0,0 +1,57 @@ +import pyblish.api + + +class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): + """Gather image sequnences from working directory""" + + order = pyblish.api.CollectorOrder + hosts = ["shell"] + label = "Image Sequences" + + def process(self, context): + import os + import json + from avalon.vendor import clique + + workspace = context.data["workspaceDir"] + + base, dirs, _ = next(os.walk(workspace)) + for renderlayer in dirs: + abspath = os.path.join(base, renderlayer) + files = os.listdir(abspath) + collections, remainder = clique.assemble(files, minimum_items=1) + assert not remainder, ( + "There shouldn't have been a remainder for '%s': " + "%s" % (renderlayer, remainder)) + + # Maya 2017 compatibility, it inexplicably prefixes layers + # with "rs_" without warning. + compatpath = os.path.join(base, renderlayer.split("rs_", 1)[-1]) + + for fname in (abspath, compatpath): + try: + with open(fname + ".json") as f: + metadata = json.load(f) + break + + except OSError: + continue + + else: + raise Exception("%s was not published correctly " + "(missing metadata)" % renderlayer) + + for collection in collections: + instance = context.create_instance(str(collection)) + + data = dict(metadata["instance"], **{ + "name": instance.name, + "family": "Image Sequences", + "families": ["colorbleed.imagesequence"], + "subset": collection.head[:-1], + "stagingDir": os.path.join(workspace, renderlayer), + "files": [list(collection)], + "metadata": metadata + }) + + instance.data.update(data) diff --git a/colorbleed/plugins/publish/collect_shell_workspace.py b/colorbleed/plugins/publish/collect_shell_workspace.py new file mode 100644 index 0000000000..566b348ab9 --- /dev/null +++ b/colorbleed/plugins/publish/collect_shell_workspace.py @@ -0,0 +1,14 @@ +import os +import pyblish.api + + +class CollectShellWorkspace(pyblish.api.ContextPlugin): + """Inject the current workspace into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Shell Workspace" + + hosts = ["shell"] + + def process(self, context): + context.data["workspaceDir"] = os.getcwd() From 0d8656d2bda4acf4b2dce8d61a16ce1330663f2d Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 17:59:03 +0200 Subject: [PATCH 0149/1249] change AVALON_ROOT to AVALON_PROJECTS --- colorbleed/plugins/publish/collect_assumed_destination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py index 47aec83ea1..ca31c2e841 100644 --- a/colorbleed/plugins/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -89,7 +89,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): # if there is a subset there ought to be version version_number += version["name"] - template_data = {"root": os.environ["AVALON_ROOT"], + template_data = {"root": os.environ["AVALON_PROJECTS"], "project": project_name, "silo": os.environ["AVALON_SILO"], "asset": asset_name, From 8d17fbc7db54136345bdba4c6031e57d8c39ceb2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 16 Aug 2017 18:01:02 +0200 Subject: [PATCH 0150/1249] ensured backup connection to local - temp fix --- colorbleed/plugins/publish/validate_deadline_done.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py index a5b420fb05..9f1362c34c 100644 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -25,11 +25,12 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): 4: "Failed", 6: "Pending", } - - url = api.Session["AVALON_DEADLINE"] + "/api/jobs?JobID=%s" + deadline = api.Session.get("AVALON_DEADLINE", "https://localhost:8082") + url = "{}/api/jobs?JobID=%s".format(deadline) for job in instance.data["metadata"]["jobs"]: response = requests.get(url % job["_id"]) + self.log.info(response) if response.ok: data = response.json()[0] From 0c8cb0f7ddaaeba2466877f780b6e4c42f4460aa Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:40:05 +0200 Subject: [PATCH 0151/1249] raising value error when data is none, assertion for env var --- .../publish/collect_current_shell_file.py | 0 .../plugins/publish/validate_deadline_done.py | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 colorbleed/plugins/publish/collect_current_shell_file.py diff --git a/colorbleed/plugins/publish/collect_current_shell_file.py b/colorbleed/plugins/publish/collect_current_shell_file.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py index 9f1362c34c..a3e90cdf30 100644 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -7,7 +7,7 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): label = "Rendered Successfully" order = pyblish.api.ValidatorOrder hosts = ["shell"] - families = ["colorbleed.imagesequence"] + families = ["mindbender.imagesequence"] optional = True def process(self, instance): @@ -25,17 +25,23 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): 4: "Failed", 6: "Pending", } - deadline = api.Session.get("AVALON_DEADLINE", "https://localhost:8082") - url = "{}/api/jobs?JobID=%s".format(deadline) + + assert "AVALON_DEADLINE" in api.Session, ("Environment variable " + "missing from current " + "session: AVALON_DEADLINE") + avalon_deadline = api.Session["AVALON_DEADLINE"] + url = "{}/api/jobs?JobID=%s".format(avalon_deadline) for job in instance.data["metadata"]["jobs"]: response = requests.get(url % job["_id"]) - self.log.info(response) if response.ok: - data = response.json()[0] - state = states.get(data["Stat"]) + data = response.json() + assert data, ValueError("Can't find information about " + "this Deadline job: " + "{}".format(job["_id"])) + state = states.get(data[0]["Stat"]) if state in (None, "Unknown"): raise Exception("State of this render is unknown") From 7931e8e9c087ac1b6884553cb96083c9ba275229 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:42:51 +0200 Subject: [PATCH 0152/1249] updated extractor, adding filenames for instance.data[files] --- .../plugins/maya/publish/extract_alembic.py | 6 +- .../plugins/maya/publish/extract_animation.py | 7 ++- .../maya/publish/extract_camera_baked.py | 10 ++- .../maya/publish/extract_camera_raw.py | 5 ++ .../plugins/maya/publish/extract_instancer.py | 5 ++ .../maya/publish/extract_layout_mayaAscii.py | 5 ++ .../plugins/maya/publish/extract_look.py | 6 ++ .../maya/publish/extract_maya_ascii_raw.py | 5 ++ .../plugins/maya/publish/extract_particles.py | 5 ++ .../plugins/maya/publish/extract_rig.py | 5 ++ .../maya/publish/extract_yeti_nodes.py | 2 + .../plugins/maya/publish/submit_deadline.py | 61 ++++++++++++++----- 12 files changed, 103 insertions(+), 19 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 1b0a50d735..641e4c7d66 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -45,7 +45,6 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): # force elect items to ensure all items get exported by Alembic members = instance.data("setMembers") - print "Members : {}".format(members) cmds.select(members) with avalon.maya.suspended_refresh(): @@ -54,4 +53,9 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): cmds.select(nodes, replace=True, noExpand=True) extract_alembic(file=path, **options) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + cmds.select(clear=True) diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index f0241a79b4..bb6f7903e5 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -34,8 +34,6 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): allDescendents=True, fullPath=True) or [] - print("Exporting {} as alembic".format(nodes)) - # Collect the start and end including handles start = instance.data["startFrame"] end = instance.data["endFrame"] @@ -62,4 +60,9 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): **{"step": instance.data.get("step", 1.0), "attr": ["cbId"]}) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_baked.py b/colorbleed/plugins/maya/publish/extract_camera_baked.py index 6e93beb082..01645279ef 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_baked.py +++ b/colorbleed/plugins/maya/publish/extract_camera_baked.py @@ -50,8 +50,9 @@ class ExtractCameraBaked(colorbleed.api.Extractor): families = ["colorbleed.camera"] def process(self, instance): - nodetype = 'camera' + file_names = [] + nodetype = 'camera' # Define extract output file path dir_path = self.staging_dir(instance) alembic_as_baked = instance.data("cameraBakedAlembic", True) @@ -80,6 +81,7 @@ class ExtractCameraBaked(colorbleed.api.Extractor): # Perform maya ascii extraction filename = "{0}.ma".format(instance.name) + file_names.append(filename) path = os.path.join(dir_path, filename) self.log.info("Performing extraction..") @@ -100,6 +102,7 @@ class ExtractCameraBaked(colorbleed.api.Extractor): # Perform alembic extraction filename = "{0}.abc".format(instance.name) + file_names.append(filename) path = os.path.join(dir_path, filename) if alembic_as_baked: @@ -136,5 +139,10 @@ class ExtractCameraBaked(colorbleed.api.Extractor): # Delete the baked camera (using transform to leave no trace) cmds.delete(baked) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].extend(file_names) + self.log.info("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_raw.py b/colorbleed/plugins/maya/publish/extract_camera_raw.py index 18636ae39c..c1c4eb3ba7 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_raw.py +++ b/colorbleed/plugins/maya/publish/extract_camera_raw.py @@ -43,4 +43,9 @@ class ExtractCameraRaw(colorbleed.api.Extractor): shader=False, expressions=False) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_instancer.py b/colorbleed/plugins/maya/publish/extract_instancer.py index aa336aebb1..0f81651766 100644 --- a/colorbleed/plugins/maya/publish/extract_instancer.py +++ b/colorbleed/plugins/maya/publish/extract_instancer.py @@ -119,6 +119,11 @@ class ExtractInstancerMayaAscii(colorbleed.api.Extractor): shader=False, expressions=False) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py index 1128780186..e4d45e1194 100644 --- a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py @@ -34,5 +34,10 @@ class ExtractLayoutMayaAscii(colorbleed.api.Extractor): expressions=True, constructionHistory=True) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index eb7e7228d0..f938cdb6d7 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -77,5 +77,11 @@ class ExtractLook(colorbleed.api.Extractor): with open(json_path, "w") as f: json.dump(data, f) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(maya_fname) + instance.data["files"].append(json_fname) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, maya_path)) diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py index 528520a0f9..faa99035ae 100644 --- a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py @@ -46,4 +46,9 @@ class ExtractMayaAsciiRaw(colorbleed.api.Extractor): preserveReferences=True, constructionHistory=True) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_particles.py b/colorbleed/plugins/maya/publish/extract_particles.py index 96cb71a2de..852813a87c 100644 --- a/colorbleed/plugins/maya/publish/extract_particles.py +++ b/colorbleed/plugins/maya/publish/extract_particles.py @@ -39,5 +39,10 @@ class ExtractParticlesMayaAscii(colorbleed.api.Extractor): shader=False, expressions=False) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_rig.py b/colorbleed/plugins/maya/publish/extract_rig.py index 7b6db28838..7db6adfe43 100644 --- a/colorbleed/plugins/maya/publish/extract_rig.py +++ b/colorbleed/plugins/maya/publish/extract_rig.py @@ -34,4 +34,9 @@ class ExtractColorbleedRig(colorbleed.api.Extractor): expressions=True, constructionHistory=True) + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py index 9f24f0dacb..a6df25ed81 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py @@ -59,5 +59,7 @@ class ExtractFurYeti(colorbleed.api.Extractor): constructionHistory=False, shader=False) + instance.data["files"] = [filename] + self.log.info("Extracted instance '{0}' to: {1}".format( instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 842f668b5e..c2ccd6e828 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -41,17 +41,24 @@ def get_padding_length(filename): return length -def get_vray_extension(): +def get_renderer_variables(): """Retrieve the extension which has been set in the VRay settings Will return None if the current renderer is not VRay Returns: - str + dict """ - ext = None - if cmds.getAttr("defaultRenderGlobals.currentRenderer") == "vray": + ext = "" + filename_prefix = "" + # padding = 4 + + renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") + if renderer == "vray": + + # padding = cmds.getAttr("vraySettings.fileNamePadding") + # check for vray settings node settings_node = cmds.ls("vraySettings", type="VRaySettingsNode") if not settings_node: @@ -61,10 +68,30 @@ def get_vray_extension(): # get the extension image_format = cmds.getAttr("vraySettings.imageFormatStr") - if image_format and image_format != ext: - ext = "{}".format(image_format) + if image_format: + ext = "{}".format(image_format.split(" ")[0]) - return ext + prefix = cmds.getAttr("vraySettings.fileNamePrefix") + if prefix: + filename_prefix = prefix + + # insert other renderer logic here + + # fall back to default + if renderer.lower().startswith("maya"): + # get the extension, getAttr defaultRenderGlobals.imageFormat + # returns index number + first_filename = cmds.renderSettings(fullPath=True, + firstImageName=True)[0] + ext = os.path.splitext(os.path.basename(first_filename))[-1].strip(".") + + # get padding and filename prefix + # padding = cmds.getAttr("defaultRenderGlobals.extensionPadding") + prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") + if prefix: + filename_prefix = prefix + + return {"ext": ext, "filename_prefix": filename_prefix} class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): @@ -98,6 +125,13 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): except OSError: pass + # get the variables depending on the renderer + render_variables = get_renderer_variables() + output_file_prefix = render_variables["filename_prefix"] + output_filename_0 = self.preview_fname(instance, + dirname, + render_variables["ext"]) + # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(deadline) @@ -127,7 +161,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": self.preview_fname(instance, dirname), + "OutputFilename0": output_filename_0, }, "PluginInfo": { # Input @@ -135,7 +169,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Output directory and filename "OutputFilePath": dirname, - "OutputFilePrefix": "/", + "OutputFilePrefix": output_file_prefix, # Mandatory for Deadline "Version": cmds.about(version=True), @@ -210,7 +244,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) - def preview_fname(self, instance, dirname): + def preview_fname(self, instance, dirname, extension): """Return outputted filename with #### for padding Passing the absolute path to Deadline enables Deadline Monitor @@ -237,11 +271,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): basename = os.path.basename(fname) name, padding, ext = basename.rsplit(".", 2) - # check if the extension is correct for vray - ext = get_vray_extension() or ext - padding_length = get_padding_length(basename) - padding = "#" * padding_length - fname = ".".join([name, padding, ext]) + padding_format = "#" * len(padding) + fname = ".".join([name, padding_format, extension]) self.log.info("Assuming renders end up @ %s" % fname) file_name = os.path.join(dirname, instance.name, fname) except ValueError: From ddceb3d4f6fa69fa6cb6038580ef9d0af33670f0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:43:37 +0200 Subject: [PATCH 0153/1249] synced intergrator with mindbender config (files) --- colorbleed/plugins/publish/integrate.py | 97 +++++++++++++++---------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 357ed080fb..17285effce 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -40,9 +40,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.register(instance) self.integrate(instance) - self.log.info("Removing temporary files and folders ...") - stagingdir = instance.data["stagingDir"] - shutil.rmtree(stagingdir) + # TODO: Decide how to clean up? And when? + # self.log.info("Removing temporary files and folders ...") + # stagingdir = instance.data["stagingDir"] + # shutil.rmtree(stagingdir) def register(self, instance): @@ -52,11 +53,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): SILO = os.environ["AVALON_SILO"] LOCATION = os.getenv("AVALON_LOCATION") - # todo(marcus): avoid hardcoding labels in the integrator - representation_labels = {".ma": "Maya Ascii", - ".source": "Original source file", - ".abc": "Alembic"} - context = instance.context # Atomicity # @@ -151,30 +147,66 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_publish = project["config"]["template"]["publish"] + # Find the representations to transfer amongst the files + # Each should be a single representation (as such, a single extension) representations = [] - staging_content = os.listdir(stagingdir) - for v, fname in enumerate(staging_content): + for files in instance.data["files"]: - name, ext = os.path.splitext(fname) - template_data["representation"] = ext[1:] + # Collection + # _______ + # |______|\ + # | |\| + # | || + # | || + # | || + # |_______| + # + if isinstance(files, list): + collection = files - src = os.path.join(stagingdir, fname) - dst = template_publish.format(**template_data) + # Assert that each member has identical suffix + _, ext = os.path.splitext(collection[0]) + assert all(ext == os.path.splitext(name)[1] + for name in collection), ( + "Files had varying suffixes, this is a bug" + ) - # Backwards compatibility - if fname == ".metadata.json": - dirname = os.path.dirname(dst) - dst = os.path.join(dirname, fname) + template_data["representation"] = ext[1:] - # copy source to destination (library) - instance.data["transfers"].append([src, dst]) + for fname in collection: + src = os.path.join(stagingdir, fname) + dst = os.path.join( + template_publish.format(**template_data), + fname + ) + + instance.data["transfers"].append([src, dst]) + + else: + # Single file + # _______ + # | |\ + # | | + # | | + # | | + # |_______| + # + fname = files + _, ext = os.path.splitext(fname) + + template_data["representation"] = ext[1:] + + src = os.path.join(stagingdir, fname) + dst = template_publish.format(**template_data) + + instance.data["transfers"].append([src, dst]) representation = { "schema": "avalon-core:representation-2.0", "type": "representation", "parent": version_id, "name": ext[1:], - "data": {"label": representation_labels.get(ext)}, + "data": {}, "dependencies": instance.data.get("dependencies", "").split(), # Imprint shortcut to context @@ -190,33 +222,24 @@ class IntegrateAsset(pyblish.api.InstancePlugin): } representations.append(representation) - # store data for database and source / destinations - instance.data["representations"] = representations - - return representations + self.log.info("Registering {} items".format(len(representations))) + io.insert_many(representations) def integrate(self, instance): - """Register the representations and move the files + """Move the files - Through the stored `representations` and `transfers` + Through `instance.data["transfers"]` Args: instance: the instance to integrate """ - # get needed data - traffic = instance.data["transfers"] - representations = instance.data["representations"] + transfers = instance.data["transfers"] - self.log.info("Registering {} items".format(len(representations))) - io.insert_many(representations) - - # moving files - for src, dest in traffic: + for src, dest in transfers: self.log.info("Copying file .. {} -> {}".format(src, dest)) self.copy_file(src, dest) - def copy_file(self, src, dst): """ Copy given source to destination @@ -281,7 +304,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "data": data} def create_version_data(self, context, instance): - """Create the data collection for th version + """Create the data collection for the version Args: context: the current context From d01427c4962d6b973573806c8c4701373b188cc3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:44:53 +0200 Subject: [PATCH 0154/1249] fixed variable called before declared --- colorbleed/plugins/publish/collect_assumed_destination.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py index ca31c2e841..a853499d67 100644 --- a/colorbleed/plugins/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -5,7 +5,7 @@ import avalon.io as io class CollectAssumedDestination(pyblish.api.InstancePlugin): - """This plug-ins displays the comment dialog box per default""" + """Generate the assumed destination path where the file will be stored""" label = "Collect Assumed Destination" order = pyblish.api.CollectorOrder + 0.499 @@ -81,12 +81,14 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): "parent": asset["_id"]}) # assume there is no version yet, we start at `1` + version = None version_number = 1 if subset is not None: version = io.find_one({"type": "version", "parent": subset["_id"]}, sort=[("name", -1)]) - # if there is a subset there ought to be version + # if there is a subset there ought to be version + if version is not None: version_number += version["name"] template_data = {"root": os.environ["AVALON_PROJECTS"], From 54aedca926e2e1e39969a64a82915b35eb7220b1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:50:54 +0200 Subject: [PATCH 0155/1249] cosmetics --- colorbleed/plugins/publish/collect_imagesequences.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index bdb703f421..e1e3707ecc 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -19,7 +19,10 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): for renderlayer in dirs: abspath = os.path.join(base, renderlayer) files = os.listdir(abspath) - collections, remainder = clique.assemble(files, minimum_items=1) + pattern = clique.PATTERNS["frames"] + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) assert not remainder, ( "There shouldn't have been a remainder for '%s': " "%s" % (renderlayer, remainder)) @@ -30,7 +33,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): for fname in (abspath, compatpath): try: - with open(fname + ".json") as f: + with open("{}.json".format(fname)) as f: metadata = json.load(f) break @@ -43,6 +46,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): for collection in collections: instance = context.create_instance(str(collection)) + self.log.info("Collection: %s" % list(collection)) data = dict(metadata["instance"], **{ "name": instance.name, From 0cd7b6f537b7b18702ba79169ce1a635639f84f7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:51:17 +0200 Subject: [PATCH 0156/1249] dependency for command line publishing --- .../plugins/publish/collect_current_shell_file.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/colorbleed/plugins/publish/collect_current_shell_file.py b/colorbleed/plugins/publish/collect_current_shell_file.py index e69de29bb2..a467459bc8 100644 --- a/colorbleed/plugins/publish/collect_current_shell_file.py +++ b/colorbleed/plugins/publish/collect_current_shell_file.py @@ -0,0 +1,14 @@ +import os +import pyblish.api + + +class CollectCurrentShellFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Current File" + hosts = ["shell"] + + def process(self, context): + """Inject the current working file""" + context.data["currentFile"] = os.path.join(os.getcwd(), "") From 6621cbfc8c333b7cf0a4fbf5b6c2124247e7aa21 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 10:58:59 +0200 Subject: [PATCH 0157/1249] updated assertion error --- colorbleed/plugins/publish/validate_deadline_done.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py index a3e90cdf30..1729d1e2a0 100644 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -27,8 +27,7 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): } assert "AVALON_DEADLINE" in api.Session, ("Environment variable " - "missing from current " - "session: AVALON_DEADLINE") + "missing: 'AVALON_DEADLINE'") avalon_deadline = api.Session["AVALON_DEADLINE"] url = "{}/api/jobs?JobID=%s".format(avalon_deadline) From e19cb51009993e260a65e1b52cc550eeb393cca8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 11:12:44 +0200 Subject: [PATCH 0158/1249] fixed import issue with loaders --- colorbleed/plugins/maya/load/load_animation.py | 6 ++++-- colorbleed/plugins/maya/load/load_look.py | 8 +++++--- colorbleed/plugins/maya/load/load_model.py | 10 +++++----- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index 058dc85562..c8c13ee006 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -1,8 +1,7 @@ import os -from maya import cmds -from avalon import api, maya +from avalon import api class AbcLoader(api.Loader): @@ -18,6 +17,9 @@ class AbcLoader(api.Loader): def process(self, name, namespace, context, data): + import maya.cmds as cmds + from avalon import maya + cmds.loadPlugin("AbcImport.mll", quiet=True) # Prevent identical alembic nodes from being shared # Create unique namespace for the cameras diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 12edc082a7..d4a539cbc2 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -1,9 +1,7 @@ import os import json -from maya import cmds -from avalon import api, maya -import colorbleed.maya.lib as lib +from avalon import api class LookLoader(api.Loader): @@ -30,6 +28,10 @@ class LookLoader(api.Loader): """ + import maya.cmds as cmds + from avalon import maya + import colorbleed.maya.lib as lib + # improve readability of the namespace assetname = context["asset"]["name"] ns_assetname = "{}_".format(assetname) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index f81174c14e..6875125541 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,7 +1,4 @@ -import maya.cmds as cmds - from avalon import api -import avalon.maya class ModelLoader(api.Loader): @@ -17,13 +14,16 @@ class ModelLoader(api.Loader): def process(self, name, namespace, context, data): + import maya.cmds as cmds + from avalon import maya + # Create a readable namespace # Namespace should contain asset name and counter # TEST_001{_descriptor} where `descriptor` can be `_abc` for example assetname = "{}_".format(namespace.split("_")[0]) - namespace = avalon.maya.unique_namespace(assetname, format="%03d") + namespace = maya.unique_namespace(assetname, format="%03d") - with avalon.maya.maintained_selection(): + with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, reference=True, From f4fcdfb148eb5aba47f9c00e4dc2e7bd9b20aea4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 16:04:59 +0200 Subject: [PATCH 0159/1249] open image sequence with plugin --- .../plugins/maya/load/load_imagesequence.py | 49 +++++++++++++++++++ colorbleed/plugins/publish/cleanup.py | 0 2 files changed, 49 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_imagesequence.py create mode 100644 colorbleed/plugins/publish/cleanup.py diff --git a/colorbleed/plugins/maya/load/load_imagesequence.py b/colorbleed/plugins/maya/load/load_imagesequence.py new file mode 100644 index 0000000000..27e24f4782 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_imagesequence.py @@ -0,0 +1,49 @@ +import sys +import os +import subprocess + +from avalon import api + + +def open(filepath): + """Open file with system default executable""" + if sys.platform.startswith('darwin'): + subprocess.call(('open', filepath)) + elif os.name == 'nt': + os.startfile(filepath) + elif os.name == 'posix': + subprocess.call(('xdg-open', filepath)) + + +class OpenImageSequence(api.Loader): + """Open Image Sequence with system default""" + + families = ["colorbleed.imagesequence"] + representations = ["*"] + + label = "Open sequence" + order = -10 + icon = "play-circle" + color = "orange" + + def process(self, name, namespace, context, data): + + directory = self.fname + from avalon.vendor import clique + + pattern = clique.PATTERNS["frames"] + files = os.listdir(directory) + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) + + assert not remainder, ("There shouldn't have been a remainder for " + "'%s': %s" % (directory, remainder)) + + seqeunce = collections[0] + first_image = list(seqeunce)[0] + filepath = os.path.normpath(os.path.join(directory, first_image)) + + self.log.info("Opening : {}".format(filepath)) + + open(filepath) diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/publish/cleanup.py new file mode 100644 index 0000000000..e69de29bb2 From e0d9cecf7c0325cdcfb738dab585d5addd38fd98 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 16:05:21 +0200 Subject: [PATCH 0160/1249] initial commit for cleanup plugin, non active --- colorbleed/plugins/publish/cleanup.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/publish/cleanup.py index e69de29bb2..58e86d9415 100644 --- a/colorbleed/plugins/publish/cleanup.py +++ b/colorbleed/plugins/publish/cleanup.py @@ -0,0 +1,20 @@ +import os + +from pyblish import api + + +class CleanUp(api.InstancePlugin): + """Cleans up the staging directory after a successful publish + + """ + + order = api.IntegratorOrder + 10 + + def process(self, instance): + return + + def clean_up(self, instance): + staging_dir = instance.get("stagingDir", None) + if staging_dir and os.path.exists(staging_dir): + self.log.info("Removing temporary folder ...") + os.rmdir(staging_dir) From d0644d816430164e576ddc77c19b4992e8aa5c91 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 18 Aug 2017 16:07:27 +0200 Subject: [PATCH 0161/1249] cosmetics --- colorbleed/plugins/publish/integrate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 17285effce..08f82e827e 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -223,6 +223,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): representations.append(representation) self.log.info("Registering {} items".format(len(representations))) + io.insert_many(representations) def integrate(self, instance): From 6b28501b4c69e0716ef92870ad68691a978aea43 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 5 Sep 2017 11:58:05 +0200 Subject: [PATCH 0162/1249] simplified deferred function --- colorbleed/maya/menu.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 1aaf3bc372..330507d90e 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -14,28 +14,25 @@ log = logging.getLogger(__name__) def deferred(): - - from scriptsmenu import launchformaya - import scriptsmenu.scriptsmenu as menu + import scriptsmenu.launchformaya as launchformaya + import scriptsmenu.scriptsmenu as scriptsmenu log.info("Attempting to install ...") # load configuration of custom menu config_path = os.path.join(os.path.dirname(__file__), "menu.json") - config = menu.load_configuration(config_path) + config = scriptsmenu.load_configuration(config_path) - # get Maya menubar - parent = launchformaya._maya_main_menubar() - cb_menu = menu.ScriptsMenu(objectName=self._menu, - title=self._menu.title(), - parent=parent) + # run the launcher for Maya menu + cb_menu = launchformaya.main(title=self._menu.title(), + objectName=self._menu) # register modifiers modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier cb_menu.register_callback(modifiers, launchformaya.to_shelf) # apply configuration - menu.load_from_configuration(cb_menu, config) + cb_menu.build_from_configuration(cb_menu, config) def uninstall(): From 8b518fcf1bcf789be15dc7a9cb37a18b9d38d03a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 5 Sep 2017 11:58:27 +0200 Subject: [PATCH 0163/1249] restructured config to match new logic --- colorbleed/maya/menu.json | 3687 +++++++++++++++++++------------------ 1 file changed, 1865 insertions(+), 1822 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 0b14de274b..0fb4d6f339 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,1823 +1,1866 @@ -{ - "order": [ - "main", - "Modeling", - "Rigging", - "Shading", - "Animation", - "Layout", - "Particles", - "Cleanup", - "Projects", - "Pyblish", - "Others" - ], - "main": [ - { - "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", - "sourcetype": "file", - "title": "Version Up", - "tooltip": "Incremental save with a specific format" - }, - { - "title": "separator" - } - ], - "Modeling": [ - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", - "sourcetype": "file", - "tags": [ - "modeling", - "duplicate", - "normalized" - ], - "title": "Duplicate Normalized", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", - "sourcetype": "file", - "tags": [ - "modeling", - "transfer", - "uv" - ], - "title": "Transfer UVs", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", - "sourcetype": "file", - "tags": [ - "modeling", - "mirror", - "symmetry" - ], - "title": "Mirror Symmetry", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI.py", - "sourcetype": "file", - "tags": [ - "modeling", - "select", - "outline", - "ui" - ], - "title": "Select Outline UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", - "sourcetype": "file", - "tags": [ - "modeling", - "polygon", - "uvset", - "delete" - ], - "title": "Polygon Delete Other UV Sets", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", - "sourcetype": "file", - "tags": [ - "modeling", - "combine", - "polygon", - "quick" - ], - "title": "Polygon Combine Quick", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "modeling", - "separateMeshPerShader" - ], - "title": "Separate Mesh Per Shader", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", - "sourcetype": "file", - "tags": ["modeling", "poly","detach","separate"], - "title": "Polygon Detach and Separate", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", - "sourcetype": "file", - "tags": [ - "modeling", - "relax", - "verts" - ], - "title": "Polygon Relax Vertices", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", - "sourcetype": "file", - "tags": ["modeling", "select","nth", "edge", "ui"], - "title": "Select Every Nth Edge" - }, - { - "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", - "sourcetype": "file", - "tags": [ - "modeling", - "djPFX", "UVs" - ], - "title": "dj PFX UVs", - "tooltip": "" - } - ], - "Animation": [ - { - "command": "", - "sourcetype": "", - "tags": [ - "animation", - "attributes" - ], - "title": "Attributes", - "tooltip": "", - "items": [ - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes" - ], - "title": "Copy Values", - "tooltip": "Copy attribute values" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "incoming" - ], - "title": "Copy In Connections", - "tooltip": "Copy incoming connections" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "out" - ], - "title": "Copy Out Connections", - "tooltip": "Copy outcoming connections" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "local" - ], - "title": "Copy Local Transfroms", - "tooltip": "Copy local transfroms" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "matrix" - ], - "title": "Copy Matrix Transfroms", - "tooltip": "Copy Matrix transfroms" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI" - ], - "title": "Copy Transforms UI", - "tooltip": "Open the Copy Transforms UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI", - "simple" - ], - "title": "Simple Copy UI", - "tooltip": "Open the simple Copy Transforms UI" - } - ] - }, - { - "command": "", - "sourcetype": "file", - "tags": [], - "title": "Optimize", - "tooltip": "Optimization scripts", - "items": [ - { - "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", - "sourcetype": "file", - "tags": [ - "animation", - "hierarchy", - "toggle", - "freeze" - ], - "title": "Toggle Freeze Hierarchy", - "tooltip": "Freeze and unfreeze hierarchy" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", - "sourcetype": "file", - "tags": [ - "animation", - "nucleus", - "toggle", - "parallel" - ], - "title": "Toggle Parallel Nucleus", - "tooltip": "Toggle parallel nucleus" - } - ] - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", - "sourcetype": "file", - "tags": [ - "animation", - "zvParentMaster" - ], - "title": "ZV Parent Master", - "tooltip": "Open ZV Parent M UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", - "sourcetype": "file", - "tags": [ - "animation", - "path" - ], - "title": "Path Animation", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", - "sourcetype": "file", - "tags": [ - "animation", - "timeStepper" - ], - "title": "TimeStepper", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", - "sourcetype": "file", - "tags": [ - "animation", - "world space", - "bake" - ], - "title": "Bake To World Space", - "tooltip": "Select the item which need to be baked to World Space" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "gui", - "simple", - "capture" - ], - "title": "Capture GUI - simple", - "tooltip": "Simplified version of the Capture GUI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "sourcetype": "file", - "tags": [ - "animation", - "capture", - "screenshot", - "movie" - ], - "title": "Capture GUI", - "tooltip": "Render current camera to an image or movie" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", - "sourcetype": "file", - "tags": [ - "animation", - "optimizer", - "scene" - ], - "title": "Animation Scene Optimizer", - "tooltip": "Optimize animation in a scene" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", - "sourcetype": "file", - "tags": [ - "animation", - "poseLibrary" - ], - "title": "Pose Library", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", - "sourcetype": "file", - "tags": [ - "animation", - "key_amplifier_ui" - ], - "title": "Key Amplifier UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "sourcetype": "file", - "tags": [ - "animation", - "curves", - "scene" - ], - "title": "Select All Animation Curves", - "tooltip": "Select all animation curves in the scene" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "offsetSelectedObjectsUI" - ], - "title": "Offset Selected Object UI", - "tooltip": "Offset selected objects" - }, - { - "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "tweenMachineUI" - ], - "title": "Tween Machine UI", - "tooltip": "" - } - ], - "Rigging": [ - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", - "sourcetype": "file", - "tags": [ - "rigging", - "addCurveBetween", - "file" - ], - "title": "Add Curve Between" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", - "sourcetype": "file", - "tags": [ - "rigging", - "averageSkinWeights", - "file" - ], - "title": "Average Skin Weights" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "cbSmoothSkinWeightUI", - "file" - ], - "title": "CB Smooth Skin Weight UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "channelBoxManagerUI", - "file" - ], - "title": "Channel Box Manager UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", - "sourcetype": "file", - "tags": [ - "rigging", - "characterAutorigger", - "file" - ], - "title": "Character Auto Rigger" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "connectUI", - "file" - ], - "title": "Connect UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", - "sourcetype": "file", - "tags": [ - "rigging", - "copySkinWeightsLocal", - "file" - ], - "title": "Copy Skin Weights Local" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", - "sourcetype": "file", - "tags": [ - "rigging", - "createCenterLocator", - "file" - ], - "title": "Create Center Locator" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", - "sourcetype": "file", - "tags": [ - "rigging", - "freezeTransformToGroup", - "file" - ], - "title": "Freeze Transform To Group" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", - "sourcetype": "file", - "tags": [ - "rigging", - "groupSelected", - "file" - ], - "title": "Group Selected" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", - "sourcetype": "file", - "tags": [ - "rigging", - "ikHandlePoleVectorLocator", - "file" - ], - "title": "IK Handle Pole Vector Locator" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "jointOrientUI", - "file" - ], - "title": "Joint Orient UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", - "sourcetype": "file", - "tags": [ - "rigging", - "jointsOnCurve", - "file" - ], - "title": "Joints On Curve" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "file", - "tags": [ - "rigging", - "resetBindSelectedSkinJoints", - "file" - ], - "title": "Reset Bind Selected Skin Joints" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedComponents", - "file" - ], - "title": "Select Skincluster Joints From Selected Components" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedMesh", - "file" - ], - "title": "Select Skincluster Joints From Selected Mesh" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setJointLabels", - "file" - ], - "title": "Set Joint Labels" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setJointOrientationFromCurrentRotation", - "file" - ], - "title": "Set Joint Orientation From Current Rotation" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setSelectedJointsOrientationZero", - "file" - ], - "title": "Set Selected Joints Orientation Zero" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", - "sourcetype": "file", - "tags": [ - "rigging", - "mirrorCurveShape", - "file" - ], - "title": "Mirror Curve Shape" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setRotationOrderUI", - "file" - ], - "title": "Set Rotation Order UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "paintItNowUI", - "file" - ], - "title": "Paint It Now UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", - "sourcetype": "file", - "tags": [ - "rigging", - "parentScaleConstraint", - "file" - ], - "title": "Parent Scale Constraint" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "quickSetWeightsUI", - "file" - ], - "title": "Quick Set Weights UI" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", - "sourcetype": "file", - "tags": [ - "rigging", - "rapidRig", - "file" - ], - "title": "Rapid Rig" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", - "sourcetype": "file", - "tags": [ - "rigging", - "regenerate_blendshape_targets", - "file" - ], - "title": "Regenerate Blendshape Targets" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", - "sourcetype": "file", - "tags": [ - "rigging", - "removeRotationAxis", - "file" - ], - "title": "Remove Rotation Axis" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", - "sourcetype": "file", - "tags": [ - "rigging", - "resetBindSelectedMeshes", - "file" - ], - "title": "Reset Bind Selected Meshes" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", - "sourcetype": "file", - "tags": [ - "rigging", - "simpleControllerOnSelection", - "file" - ], - "title": "Simple Controller On Selection" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", - "sourcetype": "file", - "tags": [ - "rigging", - "simpleControllerOnSelectionHierarchy", - "file" - ], - "title": "Simple Controller On Selection Hierarchy" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", - "sourcetype": "file", - "tags": [ - "rigging", - "superRelativeCluster", - "file" - ], - "title": "Super Relative Cluster" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", - "sourcetype": "file", - "tags": [ - "rigging", - "tfSmoothSkinWeight", - "file" - ], - "title": "TF Smooth Skin Weight" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleIntermediates", - "file" - ], - "title": "Toggle Intermediates" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleSegmentScaleCompensate", - "file" - ], - "title": "Toggle Segment Scale Compensate" - }, - { - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleSkinclusterDeformNormals", - "file" - ], - "title": "Toggle Skincluster Deform Normals" - } - ], - "Shading": [ +[ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\_save_scene_incremental.py", + "sourcetype": "file", + "title": "Version Up", + "tooltip": "Incremental save with a specific format" + }, + { + "type": "separator" + }, + { + "type": "menu", + "title": "Modeling", + "items": [ { - "command": "", - "sourcetype": "file", - "tags": [ - "shading", - "vray" - ], - "title": "VRay", - "tooltip": "", - "items": [ - { - "title": "Import Proxies", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", - "sourcetype": "file", - "tags": [ - "shading", - "vray", - "import", - "proxies" - ], - "tooltip": "" - }, - { - "title": "separator" - }, - { - "title": "Select All GES", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "selectAllGES.py" - ] - }, - { - "title": "Select All GES Under Selection", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "selection", - "all", - "GES" - ] - }, - { - "title": "separator" - }, - { - "title": "Selection To VRay Mesh", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "selection", - "vraymesh" - ] - }, - { - "title": "Add VRay Round Edges Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "round edges", - "attribute" - ] - }, - { - "title": "Add Gamma", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add gamma" - ] - }, - { - "title": "separator" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", - "sourcetype": "file", - "title": "Select Unconnected Shader Materials", - "tags": [ - "shading", - "vray", - "select", - "vraymesh", - "materials", - "unconnected shader slots" - ], - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", - "sourcetype": "file", - "title": "Merge Similar VRay Mesh Materials", - "tags": [ - "shading", - "vray", - "Merge", - "VRayMesh", - "Materials" - ], - "tooltip": "" - }, - { - "title": "Create Two Sided Material", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", - "sourcetype": "file", - "tooltip": "Creates two sided material for selected material and renames it", - "tags": [ - "shading", - "vray", - "two sided", - "material" - ] - }, - { - "title": "Create Two Sided Material For Selected", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", - "sourcetype": "file", - "tooltip": "Select material to create a two sided version from it", - "tags": [ - "shading", - "vray", - "Create2SidedMtlForSelectedMtl.py" - ] - }, - { - "title": "separator" - }, - { - "title": "Add OpenSubdiv Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "open subdiv", - "attribute" - ] - }, - { - "title": "Remove OpenSubdiv Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "opensubdiv", - "attributee" - ] - }, - { - "title": "separator" - }, - { - "title": "Add Subdivision Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVraySubdivisionAttribute" - ] - }, - { - "title": "Remove Subdivision Attribute.py", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "subdivision", - "attribute" - ] - }, - { - "title": "separator" - }, - { - "title": "Add Vray Object Ids", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "object id" - ] - }, - { - "title": "Add Vray Material Ids", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVrayMaterialIds.py" - ] - }, - { - "title": "separator" - }, - { - "title": "Set Physical DOF Depth", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "physical", - "DOF ", - "Depth" - ] - }, - { - "title": "Magic Vray Proxy UI", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "magicVrayProxyUI" - ] - } - ] - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", - "sourcetype": "file", - "tags": ["shading", "lookdev", "assign", "shaders", "auto"], - "title": "Assign lookDefault Shader", - "tooltip": "Assign the latest 'lookDefault' to assets without any lookdev in the scene" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", - "sourcetype": "file", - "tags": ["shading", "light", "link", "ui"], - "title": "Light Link UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", - "sourcetype": "file", - "tags": [ - "shading", - "CLRImage", - "textures", - "preview" - ], - "title": "Set Texture Preview To CLRImage", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", - "sourcetype": "file", - "tags": [ - "shading", - "fix", - "DefaultShaderSet", - "Behavior" - ], - "title": "Fix Default Shader Set Behavior", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", - "sourcetype": "file", - "tags": [ - "shading", - "fixSelectedShapesReferenceAssignments" - ], - "title": "Fix Shapes Reference Assignments", - "tooltip": "Select shapes to fix the reference assignments" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", - "sourcetype": "file", - "tags": [ - "shading", - "selectLambert1Members" - ], - "title": "Select Lambert1 Members", - "tooltip": "Selects all objects which have the Lambert1 shader assigned" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", - "sourcetype": "file", - "tags": [ - "shading", - "selectShapesWithoutShader" - ], - "title": "Select Shapes Without Shader", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", - "sourcetype": "file", - "tags": [ - "shading", - "fixRenderLayerOutAdjustmentErrors" - ], - "title": "Fix RenderLayerOut Adjustment Errors", - "tooltip": "" - } - ], - "Layout": [ - { - "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "Distribute", "UI"], - "title": "Align Distribute UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", - "sourcetype": "file", - "tags": ["layout", "align", "UI", "Simple"], - "title": "Align Simple UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", - "sourcetype": "file", - "tags": ["layout", "center", "locator"], - "title": "Center Locator", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", - "sourcetype": "file", - "tags": ["layout", "average", "locator"], - "title": "Average Locator", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", - "sourcetype": "file", - "tags": ["layout", "select", "proximity", "ui" - ], - "title": "Select Within Proximity UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", - "sourcetype": "file", - "tags": ["layout", "Duplicate", "Curve", "UI"], - "title": "Duplicate Curve UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", - "sourcetype": "file", - "tags": ["layout", "random", "Deselect", "UI"], - "title": "Random Deselect UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", - "sourcetype": "file", - "tags": ["layout", "multi", "reference"], - "title": "Multi Referencer UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", - "sourcetype": "file", - "tags": ["layout", "duplicate", "offset", "UI"], - "title": "Duplicate Offset UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\spPaint3d.py", - "sourcetype": "file", - "tags": ["layout", "spPaint3d", "paint", "tool"], - "title": "SP Paint 3d", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", - "sourcetype": "file", - "tags": ["layout", "randomize","UI"], - "title": "Randomize UI", - "tooltip": "" - }, - { - "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", - "sourcetype": "file", - "tags": ["layout", "distribute", "ObjectUI", "within"], - "title": "Distribute Within Object UI", - "tooltip": "" - } - ], - "Particles": [ - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjects" - ], - "title": "instancerToObjects", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstances" - ], - "title": "instancerToObjectsInstances", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "objectsToParticlesAndInstancerCleanSource" - ], - "title": "objectsToParticlesAndInstancerCleanSource", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "particleComponentsToLocators" - ], - "title": "particleComponentsToLocators", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "objectsToParticlesAndInstancer" - ], - "title": "objectsToParticlesAndInstancer", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "spawnParticlesOnMesh" - ], - "title": "spawnParticlesOnMesh", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsInstancesWithAnimation"], - "title": "instancerToObjectsInstancesWithAnimation", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "objectsToParticles"], - "title": "objectsToParticles", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "add_particle_cacheFile_attrs"], - "title": "add_particle_cacheFile_attrs", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "mergeParticleSystems"], - "title": "mergeParticleSystems", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "particlesToLocators"], - "title": "particlesToLocators", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["particles", "instancerToObjectsWithAnimation"], - "title": "instancerToObjectsWithAnimation", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "killSelectedParticles" - ], - "title": "killSelectedParticles", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "particles", - "clearInitialState" - ], - "title": "clearInitialState", - "tooltip": "" - } - ], - "Cleanup": [ - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "selectIntermediateObjects"], - "title": "selectIntermediateObjects", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "resetViewportCache"], - "title": "resetViewportCache", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "selectNonUniqueNames"], - "title": "selectNonUniqueNames", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "uniqifyNodeNames"], - "title": "uniqifyNodeNames", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "selectByType"], - "title": "selectByType", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "removeNamespaces"], - "title": "removeNamespaces", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "autoRenameFileNodes"], - "title": "autoRenameFileNodes", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": ["cleanup", "remove_user_defined_attributes"], - "title": "remove_user_defined_attributes", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnknownNodes" - ], - "title": "removeUnknownNodes", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "colorbleedRename" - ], - "title": "colorbleedRename", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnloadedReferences" - ], - "title": "removeUnloadedReferences", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "referenceEditsUI" - ], - "title": "referenceEditsUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "renameShapesToTransform" - ], - "title": "renameShapesToTransform", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeReferencesFailedEdits" - ], - "title": "removeReferencesFailedEdits", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "reorderUI" - ], - "title": "reorderUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "pastedCleaner" - ], - "title": "pastedCleaner", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "cleanup", - "deleteGhostIntermediateObjects" - ], - "title": "deleteGhostIntermediateObjects", - "tooltip": "" - } - ], - "Projects": [ - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "fifa" - ], - "title": "fifa", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "beakbuds" - ], - "title": "beakbuds", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "redeemer" - ], - "title": "redeemer", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "bjorn" - ], - "title": "bjorn", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "aldi" - ], - "title": "aldi", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "eneco" - ], - "title": "eneco", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "duurzame_verpakking" - ], - "title": "duurzame_verpakking", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "bunch" - ], - "title": "bunch", - "tooltip": "" - } - ], - "Pyblish": [ - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "layout" - ], - "title": "layout", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "look" - ], - "title": "look", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "submit_to_deadline" - ], - "title": "submit_to_deadline", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "instance_creator" - ], - "title": "instance_creator", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "utilities" - ], - "title": "utilities", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "loader" - ], - "title": "loader", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "animation" - ], - "title": "animation", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "lighting" - ], - "title": "lighting", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "inventory_ui" - ], - "title": "inventory_ui", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "pyblish", - "explorer" - ], - "title": "explorer", - "tooltip": "" - } - ], - "Others": [ - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "instanceSmartTransform" - ], - "title": "instanceSmartTransform", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "archiveSceneUI" - ], - "title": "archiveSceneUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "getSimilarMeshes" - ], - "title": "getSimilarMeshes", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "createBoundingBoxEachSelected" - ], - "title": "createBoundingBoxEachSelected", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "curveFromPositionEveryFrame" - ], - "title": "curveFromPositionEveryFrame", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "hair" - ], - "title": "hair", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "selectSoftSelection" - ], - "title": "selectSoftSelection", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "instanceLeafSmartTransform" - ], - "title": "instanceLeafSmartTransform", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "randomizeUVShellsSelectedObjects" - ], - "title": "randomizeUVShellsSelectedObjects", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "centerPivotGroup" - ], - "title": "centerPivotGroup", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "locatorsOnSelectedFaces" - ], - "title": "locatorsOnSelectedFaces", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "display" - ], - "title": "display", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "copyDeformers" - ], - "title": "copyDeformers", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "locatorsOnEdgeSelectionPrompt" - ], - "title": "locatorsOnEdgeSelectionPrompt", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "yeti" - ], - "title": "yeti", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "selectInReferenceEditor" - ], - "title": "selectInReferenceEditor", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "selectConstrainingObject" - ], - "title": "selectConstrainingObject", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "deformerSetRelationsUI" - ], - "title": "deformerSetRelationsUI", - "tooltip": "" - }, - { - "command": "", - "sourcetype": "file", - "tags": [ - "others", - "recreateBaseNodesForAllLatticeNodes" - ], - "title": "recreateBaseNodesForAllLatticeNodes", - "tooltip": "" - } - ] -} + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", + "sourcetype": "file", + "tags": [ + "modeling", + "duplicate", + "normalized" + ], + "title": "Duplicate Normalized", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", + "sourcetype": "file", + "tags": [ + "modeling", + "transfer", + "uv" + ], + "title": "Transfer UVs", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", + "sourcetype": "file", + "tags": [ + "modeling", + "mirror", + "symmetry" + ], + "title": "Mirror Symmetry", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI.py", + "sourcetype": "file", + "tags": [ + "modeling", + "select", + "outline", + "ui" + ], + "title": "Select Outline UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", + "sourcetype": "file", + "tags": [ + "modeling", + "polygon", + "uvset", + "delete" + ], + "title": "Polygon Delete Other UV Sets", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", + "sourcetype": "file", + "tags": [ + "modeling", + "combine", + "polygon", + "quick" + ], + "title": "Polygon Combine Quick", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "modeling", + "separateMeshPerShader" + ], + "title": "Separate Mesh Per Shader", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", + "sourcetype": "file", + "tags": [ + "modeling", + "poly", + "detach", + "separate" + ], + "title": "Polygon Detach and Separate", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", + "sourcetype": "file", + "tags": [ + "modeling", + "relax", + "verts" + ], + "title": "Polygon Relax Vertices", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "sourcetype": "file", + "tags": [ + "modeling", + "select", + "nth", + "edge", + "ui" + ], + "title": "Select Every Nth Edge" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", + "sourcetype": "file", + "tags": [ + "modeling", + "djPFX", + "UVs" + ], + "title": "dj PFX UVs", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Rigging", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", + "sourcetype": "file", + "tags": [ + "rigging", + "addCurveBetween", + "file" + ], + "title": "Add Curve Between" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", + "sourcetype": "file", + "tags": [ + "rigging", + "average", + "skin weights", + "file" + ], + "title": "Average Skin Weights" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "cbSmoothSkinWeightUI", + "file" + ], + "title": "CB Smooth Skin Weight UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "channelBoxManagerUI", + "file" + ], + "title": "Channel Box Manager UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", + "sourcetype": "file", + "tags": [ + "rigging", + "characterAutorigger", + "file" + ], + "title": "Character Auto Rigger" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "connectUI", + "file" + ], + "title": "Connect UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", + "sourcetype": "file", + "tags": [ + "rigging", + "copySkinWeightsLocal", + "file" + ], + "title": "Copy Skin Weights Local" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", + "sourcetype": "file", + "tags": [ + "rigging", + "createCenterLocator", + "file" + ], + "title": "Create Center Locator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", + "sourcetype": "file", + "tags": [ + "rigging", + "freezeTransformToGroup", + "file" + ], + "title": "Freeze Transform To Group" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", + "sourcetype": "file", + "tags": [ + "rigging", + "groupSelected", + "file" + ], + "title": "Group Selected" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", + "sourcetype": "file", + "tags": [ + "rigging", + "ikHandlePoleVectorLocator", + "file" + ], + "title": "IK Handle Pole Vector Locator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "jointOrientUI", + "file" + ], + "title": "Joint Orient UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", + "sourcetype": "file", + "tags": [ + "rigging", + "jointsOnCurve", + "file" + ], + "title": "Joints On Curve" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedSkinJoints", + "file" + ], + "title": "Reset Bind Selected Skin Joints" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedComponents", + "file" + ], + "title": "Select Skincluster Joints From Selected Components" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "file", + "tags": [ + "rigging", + "selectSkinclusterJointsFromSelectedMesh", + "file" + ], + "title": "Select Skincluster Joints From Selected Mesh" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointLabels", + "file" + ], + "title": "Set Joint Labels" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "file", + "tags": [ + "rigging", + "setJointOrientationFromCurrentRotation", + "file" + ], + "title": "Set Joint Orientation From Current Rotation" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "file", + "tags": [ + "rigging", + "setSelectedJointsOrientationZero", + "file" + ], + "title": "Set Selected Joints Orientation Zero" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", + "sourcetype": "file", + "tags": [ + "rigging", + "mirrorCurveShape", + "file" + ], + "title": "Mirror Curve Shape" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "setRotationOrderUI", + "file" + ], + "title": "Set Rotation Order UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "paintItNowUI", + "file" + ], + "title": "Paint It Now UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", + "sourcetype": "file", + "tags": [ + "rigging", + "parentScaleConstraint", + "file" + ], + "title": "Parent Scale Constraint" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", + "sourcetype": "file", + "tags": [ + "rigging", + "quickSetWeightsUI", + "file" + ], + "title": "Quick Set Weights UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", + "sourcetype": "file", + "tags": [ + "rigging", + "rapidRig", + "file" + ], + "title": "Rapid Rig" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", + "sourcetype": "file", + "tags": [ + "rigging", + "regenerate_blendshape_targets", + "file" + ], + "title": "Regenerate Blendshape Targets" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", + "sourcetype": "file", + "tags": [ + "rigging", + "removeRotationAxis", + "file" + ], + "title": "Remove Rotation Axis" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", + "sourcetype": "file", + "tags": [ + "rigging", + "resetBindSelectedMeshes", + "file" + ], + "title": "Reset Bind Selected Meshes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelection", + "file" + ], + "title": "Simple Controller On Selection" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", + "sourcetype": "file", + "tags": [ + "rigging", + "simpleControllerOnSelectionHierarchy", + "file" + ], + "title": "Simple Controller On Selection Hierarchy" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", + "sourcetype": "file", + "tags": [ + "rigging", + "superRelativeCluster", + "file" + ], + "title": "Super Relative Cluster" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", + "sourcetype": "file", + "tags": [ + "rigging", + "tfSmoothSkinWeight", + "file" + ], + "title": "TF Smooth Skin Weight" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleIntermediates", + "file" + ], + "title": "Toggle Intermediates" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSegmentScaleCompensate", + "file" + ], + "title": "Toggle Segment Scale Compensate" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", + "sourcetype": "file", + "tags": [ + "rigging", + "toggleSkinclusterDeformNormals", + "file" + ], + "title": "Toggle Skincluster Deform Normals" + } + ] + }, + { + "type": "menu", + "title": "Shading", + "items": [ + { + "type": "menu", + "title": "VRay", + "items": [ + { + "type": "action", + "title": "Import Proxies", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "sourcetype": "file", + "tags": ["shading", "vray", "import", "proxies"], + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Select All GES", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "select All GES" + ] + }, + { + "type": "action", + "title": "Select All GES Under Selection", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "select", "all", "GES"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Selection To VRay Mesh", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "selection", "vraymesh"] + }, + { + "type": "action", + "title": "Add VRay Round Edges Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "round edges", "attribute"] + }, + { + "type": "action", + "title": "Add Gamma", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", "vray", "add gamma"] + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "sourcetype": "file", + "title": "Select Unconnected Shader Materials", + "tags": [ + "shading", + "vray", + "select", + "vraymesh", + "materials", + "unconnected shader slots" + ], + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "sourcetype": "file", + "title": "Merge Similar VRay Mesh Materials", + "tags": [ + "shading", + "vray", + "Merge", + "VRayMesh", + "Materials" + ], + "tooltip": "" + }, + { + "type": "action", + "title": "Create Two Sided Material", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "sourcetype": "file", + "tooltip": "Creates two sided material for selected material and renames it", + "tags": [ + "shading", + "vray", + "two sided", + "material" + ] + }, + { + "type": "action", + "title": "Create Two Sided Material For Selected", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "sourcetype": "file", + "tooltip": "Select material to create a two sided version from it", + "tags": [ + "shading", + "vray", + "Create2SidedMtlForSelectedMtl.py" + ] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "add", + "open subdiv", + "attribute" + ] + }, + { + "type": "action", + "title": "Remove OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "remove", + "opensubdiv", + "attributee" + ] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add Subdivision Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "addVraySubdivisionAttribute" + ] + }, + { + "type": "action", + "title": "Remove Subdivision Attribute.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "remove", + "subdivision", + "attribute" + ] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add Vray Object Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "add", + "object id" + ] + }, + { + "type": "action", + "title": "Add Vray Material Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "addVrayMaterialIds.py" + ] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Set Physical DOF Depth", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "physical", + "DOF ", + "Depth" + ] + }, + { + "type": "action", + "title": "Magic Vray Proxy UI", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "magicVrayProxyUI" + ] + } + ] + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", + "sourcetype": "file", + "tags": [ + "shading", + "lookdev", + "assign", + "shaders", + "auto" + ], + "title": "Assign lookDefault Shader", + "tooltip": "Assign the latest 'lookDefault' to assets without any lookdev in the scene" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", + "sourcetype": "file", + "tags": [ + "shading", + "light", + "link", + "ui" + ], + "title": "Light Link UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "sourcetype": "file", + "tags": [ + "shading", + "CLRImage", + "textures", + "preview" + ], + "title": "Set Texture Preview To CLRImage", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", + "sourcetype": "file", + "tags": [ + "shading", + "fix", + "DefaultShaderSet", + "Behavior" + ], + "title": "Fix Default Shader Set Behavior", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "sourcetype": "file", + "tags": [ + "shading", + "fix", + "Selected", + "Shapes", + "Reference", + "Assignments" + ], + "title": "Fix Shapes Reference Assignments", + "tooltip": "Select shapes to fix the reference assignments" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectLambert1Members" + ], + "title": "Select Lambert1 Members", + "tooltip": "Selects all objects which have the Lambert1 shader assigned" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "sourcetype": "file", + "tags": [ + "shading", + "selectShapesWithoutShader" + ], + "title": "Select Shapes Without Shader", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "sourcetype": "file", + "tags": [ + "shading", + "fixRenderLayerOutAdjustmentErrors" + ], + "title": "Fix RenderLayerOut Adjustment Errors", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Animation", + "items": [ + { + "type": "menu", + "title": "Attributes", + "tooltip": "", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes" + ], + "title": "Copy Values", + "tooltip": "Copy attribute values" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "incoming" + ], + "title": "Copy In Connections", + "tooltip": "Copy incoming connections" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "connections", + "out" + ], + "title": "Copy Out Connections", + "tooltip": "Copy outcoming connections" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "local" + ], + "title": "Copy Local Transfroms", + "tooltip": "Copy local transfroms" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "matrix" + ], + "title": "Copy Matrix Transfroms", + "tooltip": "Copy Matrix transfroms" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI" + ], + "title": "Copy Transforms UI", + "tooltip": "Open the Copy Transforms UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "sourcetype": "file", + "tags": [ + "animation", + "copy", + "attributes", + "transforms", + "UI", + "simple" + ], + "title": "Simple Copy UI", + "tooltip": "Open the simple Copy Transforms UI" + } + ] + }, + { + "type": "menu", + "title": "Optimize", + "tooltip": "Optimization scripts", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "sourcetype": "file", + "tags": [ + "animation", + "hierarchy", + "toggle", + "freeze" + ], + "title": "Toggle Freeze Hierarchy", + "tooltip": "Freeze and unfreeze hierarchy" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "sourcetype": "file", + "tags": [ + "animation", + "nucleus", + "toggle", + "parallel" + ], + "title": "Toggle Parallel Nucleus", + "tooltip": "Toggle parallel nucleus" + } + ] + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", + "sourcetype": "file", + "tags": [ + "animation", + "zvParentMaster" + ], + "title": "ZV Parent Master", + "tooltip": "Open ZV Parent M UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", + "sourcetype": "file", + "tags": [ + "animation", + "path" + ], + "title": "Path Animation", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", + "sourcetype": "file", + "tags": [ + "animation", + "timeStepper" + ], + "title": "TimeStepper", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Layout", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "align", + "Distribute", + "UI" + ], + "title": "Align Distribute UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "align", + "UI", + "Simple" + ], + "title": "Align Simple UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", + "sourcetype": "file", + "tags": [ + "layout", + "center", + "locator" + ], + "title": "Center Locator", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", + "sourcetype": "file", + "tags": [ + "layout", + "average", + "locator" + ], + "title": "Average Locator", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "select", + "proximity", + "ui" + ], + "title": "Select Within Proximity UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "Duplicate", + "Curve", + "UI" + ], + "title": "Duplicate Curve UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "random", + "Deselect", + "UI" + ], + "title": "Random Deselect UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "multi", + "reference" + ], + "title": "Multi Referencer UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "duplicate", + "offset", + "UI" + ], + "title": "Duplicate Offset UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\spPaint3d.py", + "sourcetype": "file", + "tags": [ + "layout", + "spPaint3d", + "paint", + "tool" + ], + "title": "SP Paint 3d", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "randomize", + "UI" + ], + "title": "Randomize UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", + "sourcetype": "file", + "tags": [ + "layout", + "distribute", + "ObjectUI", + "within" + ], + "title": "Distribute Within Object UI", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Particles", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjects.py", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjects" + ], + "title": "Instancer To Objects", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstances.py", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstances" + ], + "title": "Instancer To Objects Instances", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", + "sourcetype": "file", + "tags": ["particles", "objects", "Particles", "Instancer", "Clean", "Source"], + "title": "Objects To Particles & Instancer - Clean Source", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\particleComponentsToLocators.py", + "sourcetype": "file", + "tags": ["particles", "components", "locators"], + "title": "Particle Components To Locators", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", + "sourcetype": "file", + "tags": [ + "particles", "objects", "particles", "instancer"], + "title": "Objects To Particles And Instancer", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\spawnParticlesOnMesh.py", + "sourcetype": "file", + "tags": ["particles", "spawn","on","mesh"], + "title": "Spawn Particles On Mesh", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsInstancesWithAnimation" + ], + "title": "Instancer To Objects Instances With Animation", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticles.py", + "sourcetype": "file", + "tags": [ + "particles", + "objectsToParticles" + ], + "title": "Objects To Particles", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", + "sourcetype": "file", + "tags": [ + "particles", + "add_particle_cacheFile_attrs" + ], + "title": "Add Particle CacheFile Attributes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\mergeParticleSystems.py", + "sourcetype": "file", + "tags": [ + "particles", + "mergeParticleSystems" + ], + "title": "Merge Particle Systems", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\particlesToLocators.py", + "sourcetype": "file", + "tags": [ + "particles", + "particlesToLocators" + ], + "title": "Particles To Locators", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", + "sourcetype": "file", + "tags": [ + "particles", + "instancerToObjectsWithAnimation" + ], + "title": "Instancer To Objects With Animation", + "tooltip": "" + }, + {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\clearInitialState.py", + "sourcetype": "file", + "tags": [ + "particles", + "clearInitialState" + ], + "title": "Clear Initial State", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\killSelectedParticles.py", + "sourcetype": "file", + "tags": [ + "particles", + "killSelectedParticles" + ], + "title": "Kill Selected Particles", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Cleanup", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectByType.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectByType" + ], + "title": "Select By Type", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectIntermediateObjects.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "selectIntermediateObjects" + ], + "title": "Select Intermediate Objects", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectNonUniqueNames.py", + "sourcetype": "file", + "tags": ["cleanup", "select", "non unique", "names"], + "title": "Select Non Unique Names", + "tooltip": "" + }, + {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\resetViewportCache.py", + "sourcetype": "file", + "tags": ["cleanup", "reset","viewport", "cache"], + "title": "Reset Viewport Cache", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\uniqifyNodeNames.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "uniqifyNodeNames" + ], + "title": "Uniqify Node Names", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeNamespaces.py", + "sourcetype": "file", + "tags": ["cleanup", "remove", "namespaces"], + "title": "Remove Namespaces", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\autoRenameFileNodes.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "auto", "rename","filenodes" + ], + "title": "Auto Rename File Nodes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "remove_user_defined_attributes" + ], + "title": "Remove User Defined Attributes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnknownNodes" + ], + "title": "removeUnknownNodes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\colorbleedRename.py", + "sourcetype": "file", + "tags": ["cleanup", "rename", "ui"], + "title": "Colorbleed Renamer", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnloadedReferences" + ], + "title": "Remove Unloaded References", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\referenceEditsUI.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "referenceEditsUI" + ], + "title": "Reference Edits UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\renameShapesToTransform.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "renameShapesToTransform" + ], + "title": "Rename Shapes To Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeReferencesFailedEdits" + ], + "title": "Remove References Failed Edits", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\reorderUI.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "reorderUI" + ], + "title": "Reorder UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\pastedCleaner.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "pastedCleaner" + ], + "title": "Pasted Cleaner", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "deleteGhostIntermediateObjects" + ], + "title": "Delete Ghost Intermediate Objects", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Projects", + "items": [ + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "fifa" + ], + "title": "fifa", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "beakbuds" + ], + "title": "beakbuds", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "redeemer" + ], + "title": "redeemer", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bjorn" + ], + "title": "bjorn", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "aldi" + ], + "title": "aldi", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "eneco" + ], + "title": "eneco", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "duurzame_verpakking" + ], + "title": "duurzame_verpakking", + "tooltip": "" + }, + { + "type": "action", + "command": "", + "sourcetype": "file", + "tags": [ + "projects", + "bunch" + ], + "title": "bunch", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "title": "Others", + "items": [ + { + "type": "menu", + "sourcetype": "file", + "title": "Yeti", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", + "sourcetype": "file", + "tags": ["others", "yeti", "cache", "selected"], + "title": "Cache Selected Yeti Nodes", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "command": "", + "title": "Hair", + "tooltip": "", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", + "sourcetype": "file", + "tags": ["others", "selectSoftSelection"], + "title": "Select Soft Selection", + "tooltip": "" + } + ] + }, + { + "type": "menu", + "command": "$COLORBLEED_SCRIPTS\\others\\display", + "sourcetype": "file", + "tags": [ + "others", + "display" + ], + "title": "Display", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", + "sourcetype": "file", + "tags": ["others", "wireframe","selected","objects"], + "title": "Wireframe Selected Objects", + "tooltip": "" + } + ] + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\archiveSceneUI.py", + "sourcetype": "file", + "tags": [ + "others", + "archiveSceneUI" + ], + "title": "Archive Scene UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\getSimilarMeshes.py", + "sourcetype": "file", + "tags": [ + "others", + "getSimilarMeshes" + ], + "title": "Get Similar Meshes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\createBoundingBoxEachSelected.py", + "sourcetype": "file", + "tags": [ + "others", + "createBoundingBoxEachSelected" + ], + "title": "Create BoundingBox Each Selected", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\curveFromPositionEveryFrame.py", + "sourcetype": "file", + "tags": [ + "others", + "curveFromPositionEveryFrame" + ], + "title": "Curve From Position", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\", + "sourcetype": "file", + "tags": ["others", "instance","leaf", "smart", "transform"], + "title": "Instance Leaf Smart Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\instanceSmartTransform.py", + "sourcetype": "file", + "tags": ["others", "instance", "smart", "transform"], + "title": "Instance Smart Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", + "sourcetype": "file", + "tags": [ + "others", + "randomizeUVShellsSelectedObjects" + ], + "title": "Randomize UV Shells", + "tooltip": "Select objects before running action" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\centerPivotGroup.py", + "sourcetype": "file", + "tags": [ + "others", + "centerPivotGroup" + ], + "title": "Center Pivot Group", + "tooltip": "" + }, + {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnSelectedFaces.py", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnSelectedFaces" + ], + "title": "Locators On Selected Faces", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", + "sourcetype": "file", + "tags": [ + "others", + "locatorsOnEdgeSelectionPrompt" + ], + "title": "Locators On Edge Selection Prompt", + "tooltip": "" + }, + {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\copyDeformers.py", + "sourcetype": "file", + "tags": [ + "others", + "copyDeformers" + ], + "title": "Copy Deformers", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\selectInReferenceEditor.py", + "sourcetype": "file", + "tags": [ + "others", + "selectInReferenceEditor" + ], + "title": "Select In Reference Editor", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\selectConstrainingObject.py", + "sourcetype": "file", + "tags": [ + "others", + "selectConstrainingObject" + ], + "title": "Select Constraining Object", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\deformerSetRelationsUI.py", + "sourcetype": "file", + "tags": [ + "others", + "deformerSetRelationsUI" + ], + "title": "Deformer Set Relations UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", + "sourcetype": "file", + "tags": ["others", "recreate","base", "nodes", "lattice"], + "title": "Recreate Base Nodes For Lattice Nodes", + "tooltip": "" + } + ] + } +] \ No newline at end of file From db83b66ee02ff96ca2b4728b05f041bd8c70fb7a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 5 Sep 2017 16:58:34 +0200 Subject: [PATCH 0164/1249] changed deadline variable to AVALON_DEADLINE --- colorbleed/plugins/maya/publish/submit_deadline.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index c2ccd6e828..72dca12c33 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -109,8 +109,9 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): def process(self, instance): - deadline = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") - assert deadline is not None, "Requires AVALON_DEADLINE" + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE is not None, "Requires AVALON_DEADLINE" context = instance.context workspace = context.data["workspaceDir"] @@ -133,7 +134,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): render_variables["ext"]) # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(deadline) + url = "{}/api/jobs".format(AVALON_DEADLINE) # Documentation for keys available at: # https://docs.thinkboxsoftware.com From 23771d526e66994fd71539c0cd046946853d50d7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 5 Sep 2017 16:59:38 +0200 Subject: [PATCH 0165/1249] match submit_deadline with AVALON_DEADLINE variable --- colorbleed/plugins/publish/validate_deadline_done.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py index 1729d1e2a0..6b0a5525e6 100644 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ b/colorbleed/plugins/publish/validate_deadline_done.py @@ -28,8 +28,8 @@ class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): assert "AVALON_DEADLINE" in api.Session, ("Environment variable " "missing: 'AVALON_DEADLINE'") - avalon_deadline = api.Session["AVALON_DEADLINE"] - url = "{}/api/jobs?JobID=%s".format(avalon_deadline) + AVALON_DEADLINE = api.Session["AVALON_DEADLINE"] + url = "{}/api/jobs?JobID=%s".format(AVALON_DEADLINE) for job in instance.data["metadata"]["jobs"]: response = requests.get(url % job["_id"]) From ca872244135f22829ae4b0a957aea6e41179e747 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 11:45:06 +0200 Subject: [PATCH 0166/1249] filters out ilrBakeLayer type nodes --- colorbleed/maya/lib.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 50dbfed14e..7c6635624e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -623,10 +623,12 @@ def get_id_required_nodes(referenced_nodes=False): # directly nodes = cmds.ls(type=types, long=True, noIntermediate=True) + # Remove Turtle from the result of `cmds.ls` + nodes = [n for n in nodes if n not in cmds.ls(type="ilrBakeLayer")] + # The items which need to pass the id to their parent # Add the collected transform to the nodes dag = cmds.ls(nodes, type="dagNode", long=True) # query only dag nodes - transforms = cmds.listRelatives(dag, parent=True, fullPath=True) or [] @@ -959,7 +961,7 @@ def get_related_sets(node): - set in not in viewport set (isolate selected for example) Args: - node (str): name of the current not to check + node (str): name of the current node to check Returns: list: The related sets @@ -970,13 +972,10 @@ def get_related_sets(node): ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] # Default nodes to ignore - defaults = ["initialShadingGroup", - "defaultLightSet", - "defaultObjectSet"] + defaults = ["initialShadingGroup", "defaultLightSet", "defaultObjectSet"] # Ids to ignore - ignored = ["pyblish.avalon.instance", - "pyblish.avalon.container"] + ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] view_sets = get_isolate_view_sets() From 7bd5412fb0062684f65270b188ad6e2c583d9409 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 12:52:12 +0200 Subject: [PATCH 0167/1249] fixed import issues --- colorbleed/plugins/maya/load/load_model.py | 3 +++ colorbleed/plugins/maya/load/load_rig.py | 10 +--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 6875125541..91a67b0fd6 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -46,6 +46,9 @@ class ModelGPUCacheLoader(api.Loader): def process(self, name, namespace, context, data): + import maya.cmds as cmds + # from avalon import maya + # todo: This will likely not be entirely safe with "containerize" # also this cannot work in the manager because it only works # on references at the moment! diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index f314468d2c..9fb0ed9981 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -52,15 +52,7 @@ class RigLoader(api.Loader): assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." - # To ensure the asset under which is published is actually the shot - # not the asset to which the rig belongs to. - current_task = os.environ["AVALON_TASK"] - asset_name = context["asset"]["name"] - if current_task == "animate": - asset = "{}".format(os.environ["AVALON_ASSET"]) - else: - asset = "{}".format(asset_name) - + asset = os.environ["AVALON_ASSET"] cmds.select([output, controls], noExpand=True) with maya.maintained_selection(): From b1f18aab6411cc7d264dfda68f291049750d8682 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 12:56:00 +0200 Subject: [PATCH 0168/1249] inserted use of lib.get_id, added info logging --- .../plugins/maya/publish/collect_look.py | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 731fd127c8..1fc6dcc8bf 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -84,7 +84,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Discover related object sets self.log.info("Gathering sets..") - sets = self.gather_sets(instance) + sets = self.gather_sets(cmds.ls(instance, shapes=True)) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, long=True)]) @@ -148,21 +148,16 @@ class CollectLook(pyblish.api.InstancePlugin): """ sets = dict() - for node in instance: - related_sets = lib.get_related_sets(node) if not related_sets: continue - self.log.info("Found sets %s for %s", related_sets, node) - for objset in related_sets: if objset in sets: continue - sets[objset] = {"uuid": lib.get_id(objset), - "members": list()} + sets[objset] = {"uuid": lib.get_id(objset), "members": list()} return sets @@ -178,8 +173,8 @@ class CollectLook(pyblish.api.InstancePlugin): for objset, data in sets.items(): if not data['members']: - self.log.debug("Removing redundant set information: " - "%s" % objset) + self.log.info("Removing redundant set information: " + "%s" % objset) sets.pop(objset) return sets @@ -213,8 +208,7 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.error("Node '{}' has no attribute 'cbId'".format(node)) return - member_data = {"name": node, - "uuid": cmds.getAttr("{}.cbId".format(node))} + member_data = {"name": node, "uuid": lib.get_id(node)} # Include components information when components are assigned if components: @@ -242,6 +236,9 @@ class CollectLook(pyblish.api.InstancePlugin): attributes = [] for node in instance: + # get history to ignore original shapes + cmds.listHistory(node) + # Collect changes to "custom" attributes node_attrs = get_look_attrs(node) @@ -251,11 +248,13 @@ class CollectLook(pyblish.api.InstancePlugin): node_attributes = {} for attr in node_attrs: + if not cmds.attributeQuery(attr, node=node, exists=True): + continue attribute = "{}.{}".format(node, attr) node_attributes[attr] = cmds.getAttr(attribute) attributes.append({"name": node, - "uuid": cmds.getAttr("{}.cbId".format(node)), + "uuid": lib.get_id(node), "attributes": node_attributes}) return attributes From e94069ae11c73f3fb8b59d871fb5b74877c91928 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 12:59:58 +0200 Subject: [PATCH 0169/1249] mayor fixes for plugins --- .../maya/publish/collect_renderlayers.py | 1 + .../plugins/maya/publish/extract_alembic.py | 3 +- .../plugins/maya/publish/extract_animation.py | 6 +- .../maya/publish/validate_frame_range.py | 28 +++----- .../maya/publish/validate_layout_node_ids.py | 5 +- .../publish/validate_layout_shape_node_ids.py | 9 ++- .../validate_layout_unique_node_ids.py | 5 +- .../publish/validate_look_deformed_shapes.py | 12 ++-- .../validate_look_no_default_shaders.py | 34 ++++++---- .../maya/publish/validate_look_sets.py | 62 +++++++++-------- .../maya/publish/validate_rig_controllers.py | 45 +++++++++---- ...alidate_rig_pointcache_related_node_ids.py | 66 +++++++------------ .../publish/collect_assumed_destination.py | 4 ++ maya_environment.bat | 2 +- set_environment.bat | 5 +- 15 files changed, 156 insertions(+), 131 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 92a7595905..aa0bdb58fe 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -12,6 +12,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder hosts = ["maya"] label = "Render Layers" + optional = True def process(self, context): diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 641e4c7d66..cc07051b4a 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -16,7 +16,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): """ label = "Alembic" - families = ["colorbleed.model", "colorbleed.pointcache"] + families = ["colorbleed.model", + "colorbleed.pointcache"] optional = True def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index bb6f7903e5..68ed1763f5 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -58,7 +58,11 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): startFrame=start, endFrame=end, **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"]}) + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "uvWrite": True, + "selection": True}) if "files" not in instance.data: instance.data["files"] = list() diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index bbc3f935aa..0ceb67d398 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -26,26 +26,16 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): end = instance.data.get("endFrame", None) handles = instance.data.get("handles", None) - # Check if any of the values are present. If not we'll assume the - # current instance does not require any time values. - if all(value is None for value in [start, end, handles]): - self.log.debug("No time values for this instance. " - "(Missing `startFrame`, `endFrame` or `handles`)") - return + # Check if any of the values are present + if any(value is None for value in [start, end]): + raise ValueError("No time values for this instance. " + "(Missing `startFrame` or `endFrame`)") - # If only one of the two raise an error, it will require both. - has_start = int(start is not None) - has_end = int(end is not None) - if has_start + has_end == 1: - raise RuntimeError("Only a start frame or an end frame is provided" - " instead of both.") - - if has_start and has_end: - self.log.info("Comparing start (%s) and end (%s)" % (start, end)) - if start > end: - raise RuntimeError("The start frame is a higher value " - "than the end frame: " - "{0}>{1}".format(start, end)) + self.log.info("Comparing start (%s) and end (%s)" % (start, end)) + if start > end: + raise RuntimeError("The start frame is a higher value " + "than the end frame: " + "{0}>{1}".format(start, end)) if handles is not None: if handles < 0.0: diff --git a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py index 6ea48efa41..99db79419b 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_layout_node_ids.py @@ -1,7 +1,6 @@ import pyblish.api import colorbleed.api - -import cbra.utils.maya.node_uuid as id_utils +import colorbleed.maya.lib as lib class ValidateLayoutNodeIds(pyblish.api.InstancePlugin): @@ -29,7 +28,7 @@ class ValidateLayoutNodeIds(pyblish.api.InstancePlugin): invalid = [] for node in non_referenced: - if not id_utils.get_id(node): + if not lib.get_id(node): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py index a36be266c6..3987fc0d30 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py @@ -2,8 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api - -import cbra.utils.maya.node_uuid as id_utils +import colorbleed.maya.lib as lib def get_id_from_history(node): @@ -22,7 +21,7 @@ def get_id_from_history(node): similar = cmds.ls(history, exactType=nodeType, long=True) for node in similar: - id = id_utils.get_id(node) + id = lib.get_id(node) if id: return id @@ -76,7 +75,7 @@ class CopyUUIDsFromHistoryAction(pyblish.api.Action): mapping[shape] = id # Add the ids to the nodes - id_utils.add_ids(mapping) + # id_utils.add_ids(mapping) self.log.info("Generated ids on nodes: {0}".format(mapping.values())) @@ -136,7 +135,7 @@ class ValidateLayoutShapeNodeIds(pyblish.api.InstancePlugin): invalid = [] for node in non_referenced: - if not id_utils.get_id(node): + if not lib.get_id(node): invalid.append(node) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py index 6a4e389c98..93653d3620 100644 --- a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py @@ -1,7 +1,6 @@ import pyblish.api import colorbleed.api - -import cbra.utils.maya.node_uuid as id_utils +import colorbleed.maya.lib as lib class ValidateLayoutUniqueNodeIds(pyblish.api.InstancePlugin): @@ -28,7 +27,7 @@ class ValidateLayoutUniqueNodeIds(pyblish.api.InstancePlugin): from collections import defaultdict ids = defaultdict(list) for member in members: - id = id_utils.get_id(member) + id = lib.get_id(member) ids[id].append(member) # Skip those without IDs (if everything should have an ID that should diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py index b2d400e5ff..1016c972a0 100644 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -2,8 +2,9 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib -from cbra.utils.maya.node_uuid import get_id, add_ids +# from cbra.utils.maya.node_uuid import add_ids def get_deformed_history_id_mapping(shapes): @@ -33,7 +34,8 @@ def get_deformed_history_id_mapping(shapes): deformed_shapes = [x for x in deformed_shapes if not is_referenced(x)] # Shapes without id - deformed_shapes_without_id = [x for x in deformed_shapes if not get_id(x)] + deformed_shapes_without_id = [x for x in deformed_shapes + if not lib.get_id(x)] mapping = {} for shape in deformed_shapes_without_id: @@ -45,7 +47,7 @@ def get_deformed_history_id_mapping(shapes): continue for history_shape in history_shapes: - id = get_id(history_shape) + id = lib.get_id(history_shape) if not id: continue @@ -81,11 +83,11 @@ class CopyUUIDsFromHistory(pyblish.api.Action): mapping = get_deformed_history_id_mapping(invalid) for destination, source in mapping.items(): - ids_map[destination] = get_id(source) + ids_map[destination] = lib.get_id(source) if not ids_map: return - add_ids(ids_map) + self.log.info(ids_map) class ValidateLookDeformedShapes(pyblish.api.InstancePlugin): diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 5fc8ea660f..f2693f6b4c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -28,13 +28,29 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): label = 'Look No Default Shaders' actions = [colorbleed.api.SelectInvalidAction] + def process(self, instance): + """Process all the nodes in the instance""" + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid node relationships found: " + "{0}".format(invalid)) + @classmethod def get_invalid(cls, instance): + disallowed = ["lambert1", "initialShadingGroup", + "initialParticleSE", "particleCloud1"] + disallowed = set(disallowed) - disallowed = set(["lambert1", - "initialShadingGroup", - "initialParticleSE", - "particleCloud1"]) + # Check if there are any skinClusters present + # If so ensure nodes which are skinned + intermediate = [] + skinclusters = cmds.ls(type="skinCluster") + cls.log.info("Found skinClusters, will skip original shapes") + if skinclusters: + intermediate += cmds.ls(intermediateObjects=True, + shapes=True, + long=True) invalid = set() for node in instance: @@ -48,6 +64,8 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): # Maya. if (cmds.objectType(node, isAType="surfaceShape") and not cmds.ls(object_sets, type="shadingEngine")): + if node in intermediate: + continue cls.log.error("Detected shape without shading engine: " "'{}'".format(node)) invalid.add(node) @@ -64,11 +82,3 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): invalid.add(node) return list(invalid) - - def process(self, instance): - """Process all the nodes in the instance""" - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Invalid node relationships found: " - "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 95dc266f97..7fcb10c1ec 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -3,6 +3,8 @@ from colorbleed.maya import lib import pyblish.api import colorbleed.api +from cb.utils.maya import context + class ValidateLookSets(pyblish.api.InstancePlugin): """Validate if any sets are missing from the instance and look data @@ -41,40 +43,44 @@ class ValidateLookSets(pyblish.api.InstancePlugin): "'{}'".format(instance.name)) relationships = instance.data["lookData"]["relationships"] - invalid = [] - for node in instance: - # get the connected objectSets of the node - sets = lib.get_related_sets(node) - if not sets: - continue - # check if any objectSets are not present ion the relationships - missing_sets = [s for s in sets if s not in relationships] - if missing_sets: - # A set of this node is not coming along, this is wrong! - cls.log.error("Missing sets '{}' for node " - "'{}'".format(missing_sets, node)) - invalid.append(node) - continue - - # Ensure the node is in the sets that are collected - for shaderset, data in relationships.items(): - if shaderset not in sets: - # no need to check for a set if the node - # isn't in it anyway + layer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(layer): + for node in instance: + # get the connected objectSets of the node + sets = lib.get_related_sets(node) + if not sets: continue - member_nodes = [member['name'] for member in data['members']] - if node not in member_nodes: - # The node is not found in the collected set - # relationships - cls.log.error("Missing '{}' in collected set node " - "'{}'".format(node, shaderset)) + # check if any objectSets are not present ion the relationships + missing_sets = [s for s in sets if s not in relationships] + if missing_sets: + # A set of this node is not coming along, this is wrong! + cls.log.error("Missing sets '{}' for node " + "'{}'".format(missing_sets, node)) invalid.append(node) - continue + # Ensure the node is in the sets that are collected + for shaderset, data in relationships.items(): + if shaderset not in sets: + # no need to check for a set if the node + # isn't in it anyway + continue + + member_nodes = [member['name'] for member in data['members']] + if node not in member_nodes: + # The node is not found in the collected set + # relationships + cls.log.error("Missing '{}' in collected set node " + "'{}'".format(node, shaderset)) + invalid.append(node) + + continue + return invalid - + @classmethod + def repair(cls, context, instance): + pass diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 9b066f6351..373ec9ccbb 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -16,11 +16,20 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): label = "Rig Controllers" hosts = ["maya"] families = ["colorbleed.rig"] - actions = [colorbleed.api.RepairAction] + actions = [colorbleed.api.RepairAction, + colorbleed.api.SelectInvalidAction] def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError('{} failed, see log ' + 'information'.format(self.label)) + + @classmethod + def get_invalid(cls, instance): error = False + invalid = [] is_keyed = list() not_locked = list() is_offset = list() @@ -29,7 +38,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): assert controls, "Must have controls in rig controls_SET" for control in controls: - valid_keyed = self.validate_keyed_state(control) + valid_keyed = cls.validate_keyed_state(control) if not valid_keyed: is_keyed.append(control) @@ -39,30 +48,34 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if not locked: not_locked.append(control) - valid_transforms = self.validate_transforms(control) + valid_transforms = cls.validate_transforms(control) if not valid_transforms: is_offset.append(control) if is_keyed: - self.log.error("No controls can be keyes. Failed :\n" + cls.log.error("No controls can be keyes. Failed :\n" "%s" % is_keyed) error = True if is_offset: - self.log.error("All controls default transformation values. " + cls.log.error("All controls default transformation values. " "Failed :\n%s" % is_offset) error = True if not_locked: - self.log.error("All controls must have visibility " + cls.log.error("All controls must have visibility " "attribute locked. Failed :\n" "%s" % not_locked) error = True if error: - raise RuntimeError("Invalid rig controllers. See log for details.") + invalid = is_keyed + not_locked + is_offset + cls.log.error("Invalid rig controllers. See log for details.") - def validate_transforms(self, control): + return invalid + + @staticmethod + def validate_transforms(control): tolerance = 1e-30 identity = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, @@ -71,10 +84,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): matrix = cmds.xform(control, query=True, matrix=True, objectSpace=True) if not all(abs(x - y) < tolerance for x, y in zip(identity, matrix)): + log.error("%s matrix : %s" % (control, matrix)) return False return True - def validate_keyed_state(self, control): + @staticmethod + def validate_keyed_state(control): """Check if the control has an animation curve attached Args: control: @@ -90,13 +105,21 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): + identity = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + # lock all controllers in controls_SET controls = cmds.sets("controls_SET", query=True) for control in controls: + log.info("Repairing visibility") attr = "{}.visibility".format(control) locked = cmds.getAttr(attr, lock=True) if not locked: - print("Locking visibility for %s" % control) + log.info("Locking visibility for %s" % control) cmds.setAttr(attr, lock=True) - continue + log.info("Repairing matrix") + if not cls.validate_transforms(control): + cmds.xform(control, matrix=identity, objectSpace=True) diff --git a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py index 8564752cf7..68894b2063 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py @@ -2,9 +2,8 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api - -import cbra.lib -import cbra.utils.maya.node_uuid as id_utils +import colorbleed.maya.lib as lib +import avalon.io as io class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): @@ -27,6 +26,17 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): ignore_types = ("constraint",) + def process(self, instance): + """Process all meshes""" + + print ">>>", instance.data + + # Ensure all nodes have a cbId + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes found with non-related " + "asset IDs: {0}".format(invalid)) + @classmethod def get_pointcache_nodes(cls, instance): @@ -57,49 +67,23 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - # Get a full context from the instance context - context = instance.data['instanceContext'] - item_path = context['itemPath'] - context = cbra.lib.parse_context(item_path) + invalid_items = [] + + # get asset id nodes = cls.get_pointcache_nodes(instance) - - def to_item(id): - """Split the item id part from a node id""" - return id.rsplit(":", 1)[0] - - # Generate a fake id in the current context to retrieve the item - # id prefix that should match with ids on the nodes - fake_node = "__node__" - ids = id_utils.generate_ids(context, [fake_node]) - id = ids[fake_node] - item_prefix = to_item(id) - - # Parse the invalid - invalid = list() - invalid_items = set() - for member in nodes: - member_id = id_utils.get_id(member) - - # skip nodes without ids - if not member_id: - continue - - if not member_id.startswith(item_prefix): - invalid.append(member) - invalid_items.add(to_item(member_id)) + for node in nodes: + node_id = lib.get_id(node) + if not node_id: + invalid_items.append(node) # Log invalid item ids if invalid_items: for item_id in sorted(invalid_items): cls.log.warning("Found invalid item id: {0}".format(item_id)) - return invalid + return invalid_items - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Nodes found with non-related " - "asset IDs: {0}".format(invalid)) + @staticmethod + def to_item(id): + """Split the item id part from a node id""" + return id.rsplit(":", 1)[0] diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py index a853499d67..d57039e8fb 100644 --- a/colorbleed/plugins/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -76,6 +76,9 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): "name": asset_name, "parent": project["_id"]}) + assert asset, ("No asset found by the name '{}' " + "in project '{}'".format(asset_name, project_name)) + subset = io.find_one({"type": "subset", "name": subset_name, "parent": asset["_id"]}) @@ -87,6 +90,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): version = io.find_one({"type": "version", "parent": subset["_id"]}, sort=[("name", -1)]) + # if there is a subset there ought to be version if version is not None: version_number += version["name"] diff --git a/maya_environment.bat b/maya_environment.bat index a7850a7f07..3a7151ba56 100644 --- a/maya_environment.bat +++ b/maya_environment.bat @@ -13,7 +13,7 @@ if "%CB_MAYA_SHARED%" == "" ( :: For scripts menu tool -set PYTHONPATH=%CB_PIPELINE%\git\scriptsmenu\python;%PYTHONPATH% +set PYTHONPATH=%C:\Users\User\Documents\development\scriptsmenu\python;%PYTHONPATH% set CB_SCRIPTS=%CB_PIPELINE%\git\cbMayaScripts\cbMayaScripts set COLORBLEED_SCRIPTS=%CB_SCRIPTS% diff --git a/set_environment.bat b/set_environment.bat index 6af25d9d6a..75f96c7e17 100644 --- a/set_environment.bat +++ b/set_environment.bat @@ -27,4 +27,7 @@ set PYTHONPATH=%CB_PIPELINE%\git\Qt.py;%PYTHONPATH% ::set PYTHONPATH=%CB_PIPELINE%\git\ftrack-connect\source;%PYTHONPATH% :: FFMPEG -set FFMPEG_PATH=%CB_APP_SHARED%\ffmpeg\bin\ffmpeg.exe \ No newline at end of file +set FFMPEG_PATH=%CB_APP_SHARED%\ffmpeg\bin\ffmpeg.exe + +:: Latest tools - might be unstable +set AVALON_EARLY_ADOPTER=1 \ No newline at end of file From 4a09ffd0f10cf12128bc1b685c02ef5416af8125 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 11 Sep 2017 15:43:26 +0200 Subject: [PATCH 0170/1249] Clarify docstring about the IDs being generated on scene save. --- colorbleed/plugins/maya/publish/validate_node_ids.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 733af05d4f..b4a219c2ae 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -5,7 +5,11 @@ from colorbleed.maya import lib class ValidateNodeIDs(pyblish.api.InstancePlugin): - """Validate nodes have a Colorbleed Id + """Validate nodes have a Colorbleed Id. + + When IDs are missing from nodes *save your scene* and they should be + automatically generated because IDs are created on non-referenced nodes + in Maya upon scene save. """ From 98381b177b09aa3e5c1c2fb4e13d831ee105b570 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 16:39:08 +0200 Subject: [PATCH 0171/1249] added capture gui --- colorbleed/maya/menu.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 0fb4d6f339..a0ba7724e2 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,7 +1,14 @@ [ { "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\_save_scene_incremental.py", + "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", + "sourcetype": "file", + "title": "Version Up", + "tooltip": "Incremental save with a specific format" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\save_scene_incremental.py", "sourcetype": "file", "title": "Version Up", "tooltip": "Incremental save with a specific format" @@ -1029,6 +1036,17 @@ } ] }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "sourcetype": "file", + "tags": [ + "animation", + "capture", "screen", "playblast" + ], + "title": "Capture UI", + "tooltip": "Capture the viewport to the required media format" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", From 334860177fda8802014deec4141c598e3eb8d63b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 17:19:38 +0200 Subject: [PATCH 0172/1249] added animation scripts --- colorbleed/maya/menu.json | 114 +++++++++++++++++++++++++++++--------- 1 file changed, 89 insertions(+), 25 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index a0ba7724e2..e4f14177d3 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -804,7 +804,7 @@ "auto" ], "title": "Assign lookDefault Shader", - "tooltip": "Assign the latest 'lookDefault' to assets without any lookdev in the scene" + "tooltip": "Assign the latest `lookDefault` to assets without any lookdev in the scene" }, { "type": "action", @@ -1037,48 +1037,112 @@ ] }, { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "sourcetype": "file", + "command": "$COLORBLEED_SCRITPS\\animation\\bakeSelectedToWorldSpace.py", "tags": [ "animation", - "capture", "screen", "playblast" + "bakeSelectedToWorldSpace.py" ], - "title": "Capture UI", - "tooltip": "Capture the viewport to the required media format" + "title": "Bakeselectedtoworldspace.Py", + "type": "file" }, { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", - "sourcetype": "file", + "command": "$COLORBLEED_SCRITPS\\animation\\timeStepper.py", "tags": [ "animation", - "zvParentMaster" + "timeStepper.py" ], - "title": "ZV Parent Master", - "tooltip": "Open ZV Parent M UI" + "title": "Timestepper.Py", + "type": "file" }, { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", - "sourcetype": "file", + "command": "$COLORBLEED_SCRITPS\\animation\\capture_ui.py", "tags": [ "animation", - "path" + "capture_ui.py" ], - "title": "Path Animation", - "tooltip": "" + "title": "Capture_Ui.Py", + "type": "file" }, { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", - "sourcetype": "file", + "command": "$COLORBLEED_SCRITPS\\animation\\tweenMachineUI.py", "tags": [ "animation", - "timeStepper" + "tweenMachineUI.py" ], - "title": "TimeStepper", - "tooltip": "" + "title": "Tweenmachineui.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\selectAllAnimationCurves.py", + "tags": [ + "animation", + "selectAllAnimationCurves.py" + ], + "title": "Selectallanimationcurves.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\simplePlayblastUI.py", + "tags": [ + "animation", + "simplePlayblastUI.py" + ], + "title": "Simpleplayblastui.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\pathAnimation.py", + "tags": [ + "animation", + "pathAnimation.py" + ], + "title": "Pathanimation.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\offsetSelectedObjectsUI.py", + "tags": [ + "animation", + "offsetSelectedObjectsUI.py" + ], + "title": "Offsetselectedobjectsui.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\key_amplifier_ui.py", + "tags": [ + "animation", + "key_amplifier_ui.py" + ], + "title": "Key_Amplifier_Ui.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\anim_scene_optimizer.py", + "tags": [ + "animation", + "anim_scene_optimizer.py" + ], + "title": "Anim_Scene_Optimizer.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\zvParentMaster.py", + "tags": [ + "animation", + "zvParentMaster.py" + ], + "title": "Zvparentmaster.Py", + "type": "file" + }, + { + "command": "$COLORBLEED_SCRITPS\\animation\\poseLibrary.py", + "tags": [ + "animation", + "poseLibrary.py" + ], + "title": "Poselibrary.Py", + "type": "file" } ] }, From f79f6861fd09e1d30d169c095236bf3490842698 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 17:30:40 +0200 Subject: [PATCH 0173/1249] fixed error in menu item type --- colorbleed/maya/menu.json | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index e4f14177d3..f67680fdb6 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1037,112 +1037,124 @@ ] }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\bakeSelectedToWorldSpace.py", "tags": [ "animation", "bakeSelectedToWorldSpace.py" ], "title": "Bakeselectedtoworldspace.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\timeStepper.py", "tags": [ "animation", "timeStepper.py" ], "title": "Timestepper.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\capture_ui.py", "tags": [ "animation", "capture_ui.py" ], "title": "Capture_Ui.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\tweenMachineUI.py", "tags": [ "animation", "tweenMachineUI.py" ], "title": "Tweenmachineui.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\selectAllAnimationCurves.py", "tags": [ "animation", "selectAllAnimationCurves.py" ], "title": "Selectallanimationcurves.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\simplePlayblastUI.py", "tags": [ "animation", "simplePlayblastUI.py" ], "title": "Simpleplayblastui.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\pathAnimation.py", "tags": [ "animation", "pathAnimation.py" ], "title": "Pathanimation.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\offsetSelectedObjectsUI.py", "tags": [ "animation", "offsetSelectedObjectsUI.py" ], "title": "Offsetselectedobjectsui.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\key_amplifier_ui.py", "tags": [ "animation", "key_amplifier_ui.py" ], "title": "Key_Amplifier_Ui.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\anim_scene_optimizer.py", "tags": [ "animation", "anim_scene_optimizer.py" ], "title": "Anim_Scene_Optimizer.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\zvParentMaster.py", "tags": [ "animation", "zvParentMaster.py" ], "title": "Zvparentmaster.Py", - "type": "file" + "type": "action" }, { + "sourcetype": "file", "command": "$COLORBLEED_SCRITPS\\animation\\poseLibrary.py", "tags": [ "animation", "poseLibrary.py" ], "title": "Poselibrary.Py", - "type": "file" + "type": "action" } ] }, From f9483892a10fddebe7de6c937d1e1c5e2506a372 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 17:48:05 +0200 Subject: [PATCH 0174/1249] fixed typo env key --- colorbleed/maya/menu.json | 48 +++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index f67680fdb6..dfad55f85e 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1038,122 +1038,122 @@ }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\bakeSelectedToWorldSpace.py", + "command": "COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "tags": [ "animation", "bakeSelectedToWorldSpace.py" ], - "title": "Bakeselectedtoworldspace.Py", + "title": "Bakeselectedtoworldspace", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\timeStepper.py", + "command": "COLORBLEED_SCRIPTS\\animation\\timeStepper.py", "tags": [ "animation", "timeStepper.py" ], - "title": "Timestepper.Py", + "title": "Timestepper", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\capture_ui.py", + "command": "COLORBLEED_SCRIPTS\\animation\\capture_ui.py", "tags": [ "animation", "capture_ui.py" ], - "title": "Capture_Ui.Py", + "title": "Capture_Ui", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\tweenMachineUI.py", + "command": "COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", "tags": [ "animation", "tweenMachineUI.py" ], - "title": "Tweenmachineui.Py", + "title": "Tweenmachineui", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\selectAllAnimationCurves.py", + "command": "COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", "tags": [ "animation", "selectAllAnimationCurves.py" ], - "title": "Selectallanimationcurves.Py", + "title": "Selectallanimationcurves", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\simplePlayblastUI.py", + "command": "COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", "tags": [ "animation", "simplePlayblastUI.py" ], - "title": "Simpleplayblastui.Py", + "title": "Simpleplayblastui", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\pathAnimation.py", + "command": "COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", "tags": [ "animation", "pathAnimation.py" ], - "title": "Pathanimation.Py", + "title": "Pathanimation", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\offsetSelectedObjectsUI.py", + "command": "COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", "tags": [ "animation", "offsetSelectedObjectsUI.py" ], - "title": "Offsetselectedobjectsui.Py", + "title": "Offsetselectedobjectsui", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\key_amplifier_ui.py", + "command": "COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", "tags": [ "animation", "key_amplifier_ui.py" ], - "title": "Key_Amplifier_Ui.Py", + "title": "Key_Amplifier_Ui", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\anim_scene_optimizer.py", + "command": "COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "tags": [ "animation", "anim_scene_optimizer.py" ], - "title": "Anim_Scene_Optimizer.Py", + "title": "Anim_Scene_Optimizer", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\zvParentMaster.py", + "command": "COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", "tags": [ "animation", "zvParentMaster.py" ], - "title": "Zvparentmaster.Py", + "title": "Zvparentmaster", "type": "action" }, { "sourcetype": "file", - "command": "$COLORBLEED_SCRITPS\\animation\\poseLibrary.py", + "command": "COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", "tags": [ "animation", "poseLibrary.py" ], - "title": "Poselibrary.Py", + "title": "Poselibrary", "type": "action" } ] From 518f8a09ab3599678be500123769d78bb65711a5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 17:51:30 +0200 Subject: [PATCH 0175/1249] nice titles --- colorbleed/maya/menu.json | 65 +++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index dfad55f85e..5e44599215 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1041,19 +1041,18 @@ "command": "COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "tags": [ "animation", - "bakeSelectedToWorldSpace.py" + "bake","selection", "worldspace" ], - "title": "Bakeselectedtoworldspace", + "title": "Bake Selected To Worldspace", "type": "action" }, { "sourcetype": "file", "command": "COLORBLEED_SCRIPTS\\animation\\timeStepper.py", "tags": [ - "animation", - "timeStepper.py" + "animation", "time","stepper" ], - "title": "Timestepper", + "title": "Time Stepper", "type": "action" }, { @@ -1063,27 +1062,7 @@ "animation", "capture_ui.py" ], - "title": "Capture_Ui", - "type": "action" - }, - { - "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", - "tags": [ - "animation", - "tweenMachineUI.py" - ], - "title": "Tweenmachineui", - "type": "action" - }, - { - "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "tags": [ - "animation", - "selectAllAnimationCurves.py" - ], - "title": "Selectallanimationcurves", + "title": "Capture UI", "type": "action" }, { @@ -1093,7 +1072,27 @@ "animation", "simplePlayblastUI.py" ], - "title": "Simpleplayblastui", + "title": "Simple Playblast UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", + "tags": [ + "animation", + "tweenMachineUI.py" + ], + "title": "Tween Machine UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "tags": [ + "animation", + "selectAllAnimationCurves.py" + ], + "title": "Select All Animation Curves", "type": "action" }, { @@ -1103,7 +1102,7 @@ "animation", "pathAnimation.py" ], - "title": "Pathanimation", + "title": "Path Animation", "type": "action" }, { @@ -1113,7 +1112,7 @@ "animation", "offsetSelectedObjectsUI.py" ], - "title": "Offsetselectedobjectsui", + "title": "Offset Selected Objects UI", "type": "action" }, { @@ -1121,9 +1120,9 @@ "command": "COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", "tags": [ "animation", - "key_amplifier_ui.py" + "key", "amplifier" ], - "title": "Key_Amplifier_Ui", + "title": "Key Amplifier UI", "type": "action" }, { @@ -1143,7 +1142,7 @@ "animation", "zvParentMaster.py" ], - "title": "Zvparentmaster", + "title": "ZV Parent Master", "type": "action" }, { @@ -1153,7 +1152,7 @@ "animation", "poseLibrary.py" ], - "title": "Poselibrary", + "title": "Pose Library", "type": "action" } ] From 449ed50ba1347c151c52a42b3eac5228e68b0490 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 11 Sep 2017 18:00:42 +0200 Subject: [PATCH 0176/1249] fixed missing token --- colorbleed/maya/menu.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 5e44599215..ed403559c3 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1038,17 +1038,17 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", "tags": [ "animation", - "bake","selection", "worldspace" + "bake","selection", "worldspace.py" ], "title": "Bake Selected To Worldspace", "type": "action" }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\timeStepper.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", "tags": [ "animation", "time","stepper" ], @@ -1057,7 +1057,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", "tags": [ "animation", "capture_ui.py" @@ -1067,7 +1067,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", "tags": [ "animation", "simplePlayblastUI.py" @@ -1077,7 +1077,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", "tags": [ "animation", "tweenMachineUI.py" @@ -1087,7 +1087,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", "tags": [ "animation", "selectAllAnimationCurves.py" @@ -1097,7 +1097,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", "tags": [ "animation", "pathAnimation.py" @@ -1107,7 +1107,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", "tags": [ "animation", "offsetSelectedObjectsUI.py" @@ -1117,7 +1117,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", "tags": [ "animation", "key", "amplifier" @@ -1127,7 +1127,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", "tags": [ "animation", "anim_scene_optimizer.py" @@ -1137,7 +1137,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", "tags": [ "animation", "zvParentMaster.py" @@ -1147,7 +1147,7 @@ }, { "sourcetype": "file", - "command": "COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", + "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", "tags": [ "animation", "poseLibrary.py" From 197b8edc40200391627e66e89ed617bf610a25ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 09:55:53 +0200 Subject: [PATCH 0177/1249] removed double entry, removed projects menu --- colorbleed/maya/menu.json | 106 +------------------------------------- 1 file changed, 1 insertion(+), 105 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index ed403559c3..a40337f05b 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -6,13 +6,6 @@ "title": "Version Up", "tooltip": "Incremental save with a specific format" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\save_scene_incremental.py", - "sourcetype": "file", - "title": "Version Up", - "tooltip": "Incremental save with a specific format" - }, { "type": "separator" }, @@ -1039,10 +1032,7 @@ { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", - "tags": [ - "animation", - "bake","selection", "worldspace.py" - ], + "tags": ["animation", "bake","selection", "worldspace.py"], "title": "Bake Selected To Worldspace", "type": "action" }, @@ -1647,100 +1637,6 @@ } ] }, - { - "type": "menu", - "title": "Projects", - "items": [ - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "fifa" - ], - "title": "fifa", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "beakbuds" - ], - "title": "beakbuds", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "redeemer" - ], - "title": "redeemer", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "bjorn" - ], - "title": "bjorn", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "aldi" - ], - "title": "aldi", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "eneco" - ], - "title": "eneco", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "duurzame_verpakking" - ], - "title": "duurzame_verpakking", - "tooltip": "" - }, - { - "type": "action", - "command": "", - "sourcetype": "file", - "tags": [ - "projects", - "bunch" - ], - "title": "bunch", - "tooltip": "" - } - ] - }, { "type": "menu", "title": "Others", From a2bdf93bf78c8d4c398db19a97fc19fad2962279 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 10:01:22 +0200 Subject: [PATCH 0178/1249] cosmetics --- colorbleed/maya/menu.json | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index a40337f05b..776e014e9e 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1039,59 +1039,42 @@ { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", - "tags": [ - "animation", "time","stepper" - ], + "tags": ["animation", "time","stepper"], "title": "Time Stepper", "type": "action" }, { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "tags": [ - "animation", - "capture_ui.py" - ], + "tags": ["animation", "capture", "ui", "screen", "movie", "image"], "title": "Capture UI", "type": "action" }, { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", - "tags": [ - "animation", - "simplePlayblastUI.py" - ], + "tags": ["animation", "simple", "playblast", "ui"], "title": "Simple Playblast UI", "type": "action" }, { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", - "tags": [ - "animation", - "tweenMachineUI.py" - ], + "tags": ["animation", "tween", "machine"], "title": "Tween Machine UI", "type": "action" }, { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "tags": [ - "animation", - "selectAllAnimationCurves.py" - ], + "tags": ["animation", "select", "curves"], "title": "Select All Animation Curves", "type": "action" }, { "sourcetype": "file", "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", - "tags": [ - "animation", - "pathAnimation.py" - ], + "tags": ["animation", "path", "along"], "title": "Path Animation", "type": "action" }, From 1d7a43e5ca973f83277dc6fe17e6a20b66de696b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 12 Sep 2017 10:57:00 +0200 Subject: [PATCH 0179/1249] Add `popup()` function for the menu so we can trigger it to pop-up --- colorbleed/maya/menu.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 330507d90e..236879e061 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -2,7 +2,7 @@ import sys import os import logging -from avalon.vendor.Qt import QtWidgets, QtCore +from avalon.vendor.Qt import QtWidgets, QtCore, QtGui import maya.cmds as cmds @@ -12,6 +12,15 @@ self._menu = "colorbleed" log = logging.getLogger(__name__) +def _get_menu(): + """Return the menu instance if it currently exists in Maya""" + + app = QtWidgets.QApplication.instance() + widgets = dict((w.objectName(), w) for w in app.allWidgets()) + menu = widgets.get(self._menu) + return menu + + def deferred(): import scriptsmenu.launchformaya as launchformaya @@ -37,12 +46,11 @@ def deferred(): def uninstall(): - log.info("Attempting to uninstall ..") - app = QtWidgets.QApplication.instance() - widgets = dict((w.objectName(), w) for w in app.allWidgets()) - menu = widgets.get(self._menu) + menu = _get_menu() if menu: + log.info("Attempting to uninstall ..") + try: menu.deleteLater() del menu @@ -55,3 +63,12 @@ def install(): uninstall() # Allow time for uninstallation to finish. cmds.evalDeferred(deferred) + + +def popup(): + """Pop-up the existing menu near the mouse cursor""" + menu = _get_menu() + + cursor = QtGui.QCursor() + point = cursor.pos() + menu.exec_(point) From 8c66e4ac06220971ffd84f502c8662e576ed223c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 12 Sep 2017 11:00:11 +0200 Subject: [PATCH 0180/1249] Remove environment bat files, they are replaced by the environments in the `.toml` files --- maya_environment.bat | 75 ------------------------------------------ python_environment.bat | 30 ----------------- set_environment.bat | 33 ------------------- 3 files changed, 138 deletions(-) delete mode 100644 maya_environment.bat delete mode 100644 python_environment.bat delete mode 100644 set_environment.bat diff --git a/maya_environment.bat b/maya_environment.bat deleted file mode 100644 index 3a7151ba56..0000000000 --- a/maya_environment.bat +++ /dev/null @@ -1,75 +0,0 @@ -@echo OFF - -echo Entering Maya2016 environment... - -:: Environment: Maya -set CB_MAYA_VERSION=2016 -set CB_MAYA_SHARED=%CB_APP_SHARED%\maya_shared\%CB_MAYA_VERSION% - -if "%CB_MAYA_SHARED%" == "" ( - echo Error: "CB_MAYA_SHARED" not set - goto :eof -) - - -:: For scripts menu tool -set PYTHONPATH=%C:\Users\User\Documents\development\scriptsmenu\python;%PYTHONPATH% -set CB_SCRIPTS=%CB_PIPELINE%\git\cbMayaScripts\cbMayaScripts -set COLORBLEED_SCRIPTS=%CB_SCRIPTS% - -:: Colorbleed Maya -set PYTHONPATH=%CB_PIPELINE%\git\cbMayaScripts;%PYTHONPATH% -set PYTHONPATH=%CB_PIPELINE%\git\inventory\python;%PYTHONPATH% - -:: Maya shared -set MAYA_PLUG_IN_PATH=%CB_MAYA_SHARED%\plugins;%MAYA_PLUGIN_PATH% -set MAYA_SHELF_PATH=%CB_MAYA_SHARED%\prefs\shelves;%MAYA_SHELF_PATH% -set MAYA_SCRIPT_PATH=%CB_MAYA_SHARED%\scripts;%MAYA_SCRIPT_PATH% -set XBMLANGPATH=%CB_MAYA_SHARED%\prefs\icons;%XBMLANGPATH% -set MAYA_PRESET_PATH=%CB_MAYA_SHARED%\prefs\attrPresets;%MAYA_PRESET_PATH% -set PYTHONPATH=%CB_MAYA_SHARED%\scripts;%PYTHONPATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules;%MAYA_MODULE_PATH% - -:: Additional modules -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\mGear_2016;%MAYA_MODULE_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\SOuP;%MAYA_MODULE_PATH% -set MAYA_SHELF_PATH=%CB_MAYA_SHARED%\modules\SOuP\shelves;%MAYA_SHELF_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\pdipro35c_Maya2016x64;%MAYA_MODULE_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\ovdb\maya\maya2016;%MAYA_MODULE_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\cvshapeinverter;%MAYA_MODULE_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Toolchefs;%MAYA_MODULE_PATH% -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Exocortex;%MAYA_MODULE_PATH% - -:: Miarmy -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Basefount\Miarmy;%MAYA_MODULE_PATH% -set PATH=%CB_MAYA_SHARED%\modules\Basefount\Miarmy\bin;%PATH% -set VRAY_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Basefount\Miarmy\bin\vray\vray_3.1_3.3_3.4\Maya2015and2016;%VRAY_PLUGINS_x64%; - -:: Yeti -set MAYA_MODULE_PATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64;%MAYA_MODULE_PATH% -set PATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%PATH%; -set VRAY_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%VRAY_PLUGINS_x64%; -set VRAY_FOR_MAYA2016_PLUGINS_x64=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\bin;%VRAY_FOR_MAYA2016_PLUGINS_x64%; -set REDSHIFT_MAYAEXTENSIONSPATH=%CB_MAYA_SHARED%\modules\Yeti-v2.1.5_Maya2016-windows64\plug-ins;%REDSHIFT_MAYAEXTENSIONSPATH% -set peregrinel_LICENSE=5053@CBserver - -:: maya-capture -set PYTHONPATH=%CB_PIPELINE%\git\maya-capture;%PYTHONPATH% -set PYTHONPATH=%CB_PIPELINE%\git\maya-capture-gui;%PYTHONPATH% -set PYTHONPATH=%CB_PIPELINE%\git\maya-capture-gui-cb;%PYTHONPATH% - -:: maya-matrix-deform -set PYTHONPATH=%CB_PIPELINE%\git\maya-matrix-deformers;%PYTHONPATH% -set MAYA_PLUG_IN_PATH=%CB_PIPELINE%\git\maya-matrix-deformers\plugin;%MAYA_PLUG_IN_PATH% - -:: rapid-rig -set XBMLANGPATH=%CB_MAYA_SHARED%\scripts\RapidRig_Modular_V02;%XBMLANGPATH% -set MAYA_SCRIPT_PATH=%CB_MAYA_SHARED%\scripts\RapidRig_Modular_V02;%MAYA_SCRIPT_PATH% - - -:: Fix Maya Playblast Color Management depth -set MAYA_FLOATING_POINT_RT_PLAYBLAST=1 - - -:: Fix V-ray forcing affinity to 100% -set VRAY_USE_THREAD_AFFINITY=0 \ No newline at end of file diff --git a/python_environment.bat b/python_environment.bat deleted file mode 100644 index 50fafd391c..0000000000 --- a/python_environment.bat +++ /dev/null @@ -1,30 +0,0 @@ -@echo OFF -echo Entering Python environment... - -set CB_PYTHON_VERSION=2.7 - -where /Q python.exe -if ERRORLEVEL 1 ( - if EXIST C:\Python27\python.exe ( - echo Adding C:\Python27 to PATH - set "PATH=%PATH%;C:\Python27" - goto:has-python - ) else ( - echo Adding embedded python (pipeline) - set "PATH=%PATH%;%CB_APP_SHARED%\python\standalone\%CB_PYTHON_VERSION%\bin" - goto:has-python - ) -) -:has-python - -:: Python universal (non-compiled) -set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\universal\site-packages - -:: Python version/windows-specific -:: set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\win\%CB_PYTHON_VERSION% - -:: Python standalone (compiled to version) -if NOT "%CB_PYTHON_STANDALONE%" == "0" ( - echo Entering Python Standalone environment... - set PYTHONPATH=%PYTHONPATH%;%CB_APP_SHARED%\python\standalone\%CB_PYTHON_VERSION%\site-packages -) diff --git a/set_environment.bat b/set_environment.bat deleted file mode 100644 index 75f96c7e17..0000000000 --- a/set_environment.bat +++ /dev/null @@ -1,33 +0,0 @@ -@echo off -echo Entering pipeline (raw development) environment... - -:: Initialize environment -set CB_PIPELINE=P:\pipeline\dev - -set CB_APP_SHARED=%CB_PIPELINE%\apps - -if "%CB_APP_SHARED%" == "" ( - echo Error: "CB_APP_SHARED" not set - goto :eof -) - -echo setting STORAGE.. -set STORAGE=P: - -:: Core -echo Add cb core.. -set PYTHONPATH=%CB_PIPELINE%\git\cb;%PYTHONPATH% -set PYTHONPATH=%CB_PIPELINE%\git\cbra;%PYTHONPATH% - -:: Extra -set PYTHONPATH=%CB_PIPELINE%\git\pyseq;%PYTHONPATH% -set PYTHONPATH=%CB_PIPELINE%\git\Qt.py;%PYTHONPATH% - -:: Ftrack-connect -::set PYTHONPATH=%CB_PIPELINE%\git\ftrack-connect\source;%PYTHONPATH% - -:: FFMPEG -set FFMPEG_PATH=%CB_APP_SHARED%\ffmpeg\bin\ffmpeg.exe - -:: Latest tools - might be unstable -set AVALON_EARLY_ADOPTER=1 \ No newline at end of file From 69dca8754a9f6841420dc28b8e7a826cf88b8436 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 11:27:56 +0200 Subject: [PATCH 0181/1249] removed optional toggle from model extractors --- colorbleed/plugins/maya/publish/extract_alembic.py | 1 - colorbleed/plugins/maya/publish/extract_model.py | 1 - 2 files changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index cc07051b4a..1d82632e36 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -18,7 +18,6 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): label = "Alembic" families = ["colorbleed.model", "colorbleed.pointcache"] - optional = True def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/extract_model.py b/colorbleed/plugins/maya/publish/extract_model.py index de9dcfa6f1..e1be53d59a 100644 --- a/colorbleed/plugins/maya/publish/extract_model.py +++ b/colorbleed/plugins/maya/publish/extract_model.py @@ -26,7 +26,6 @@ class ExtractModel(colorbleed.api.Extractor): label = "Model (Maya ASCII)" hosts = ["maya"] families = ["colorbleed.model"] - optional = True def process(self, instance): From 0ac70e23b55fb33ea9b909878cf0587af3040dda Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 13:14:09 +0200 Subject: [PATCH 0182/1249] disabled validator for rigging family --- .../plugins/maya/publish/validate_mesh_no_negative_scale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py index 9c032d730a..13e8ea54bc 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py @@ -18,7 +18,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): order = colorbleed.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.proxy', 'colorbleed.rig', 'colorbleed.model'] + families = ['colorbleed.proxy', 'colorbleed.model'] label = 'Mesh No Negative Scale' actions = [colorbleed.api.SelectInvalidAction] From d190150548d08f59dd18d6082f2c52cf060b6e69 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 12 Sep 2017 14:00:56 +0200 Subject: [PATCH 0183/1249] Avoid irlBakeLayer warning being spammed a lot --- colorbleed/maya/lib.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7c6635624e..f5ca8396b5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -604,6 +604,13 @@ def get_id_required_nodes(referenced_nodes=False): nodes (set): list of filtered nodes """ + def _node_type_exists(node_type): + try: + cmds.nodeType(node_type, isTypeName=True) + return True + except RuntimeError: + return False + # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove # default nodes and reference nodes camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] @@ -616,6 +623,11 @@ def get_id_required_nodes(referenced_nodes=False): ignore |= set(cmds.ls(long=True, defaultNodes=True)) ignore |= set(cmds.ls(camera_shapes, long=True)) + # Remove Turtle from the result of `cmds.ls` if Turtle is loaded + # TODO: This should be a less specific check for a single plug-in. + if _node_type_exists("ilrBakeLayer"): + ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) + # establish set of nodes to ignore types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] @@ -623,9 +635,6 @@ def get_id_required_nodes(referenced_nodes=False): # directly nodes = cmds.ls(type=types, long=True, noIntermediate=True) - # Remove Turtle from the result of `cmds.ls` - nodes = [n for n in nodes if n not in cmds.ls(type="ilrBakeLayer")] - # The items which need to pass the id to their parent # Add the collected transform to the nodes dag = cmds.ls(nodes, type="dagNode", long=True) # query only dag nodes From 0e619ea377a72d08013954a451b5634b5974888a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 16:55:03 +0200 Subject: [PATCH 0184/1249] disabled families for future development --- .../maya/create/{colorbleed_groom.py => _colorbleed_groom.py} | 0 .../create/{colorbleed_instancer.py => _colorbleed_instancer.py} | 0 .../maya/create/{colorbleed_layout.py => _colorbleed_layout.py} | 0 .../create/{colorbleed_mayaascii.py => _colorbleed_mayaascii.py} | 0 .../{colorbleed_pointcache.py => _colorbleed_pointcache.py} | 0 .../maya/create/{colorbleed_texture.py => _colorbleed_texture.py} | 0 .../maya/create/{colorbleed_yetifur.py => _colorbleed_yetifur.py} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/create/{colorbleed_groom.py => _colorbleed_groom.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_instancer.py => _colorbleed_instancer.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_layout.py => _colorbleed_layout.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_mayaascii.py => _colorbleed_mayaascii.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_pointcache.py => _colorbleed_pointcache.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_texture.py => _colorbleed_texture.py} (100%) rename colorbleed/plugins/maya/create/{colorbleed_yetifur.py => _colorbleed_yetifur.py} (100%) diff --git a/colorbleed/plugins/maya/create/colorbleed_groom.py b/colorbleed/plugins/maya/create/_colorbleed_groom.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_groom.py rename to colorbleed/plugins/maya/create/_colorbleed_groom.py diff --git a/colorbleed/plugins/maya/create/colorbleed_instancer.py b/colorbleed/plugins/maya/create/_colorbleed_instancer.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_instancer.py rename to colorbleed/plugins/maya/create/_colorbleed_instancer.py diff --git a/colorbleed/plugins/maya/create/colorbleed_layout.py b/colorbleed/plugins/maya/create/_colorbleed_layout.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_layout.py rename to colorbleed/plugins/maya/create/_colorbleed_layout.py diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/_colorbleed_mayaascii.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_mayaascii.py rename to colorbleed/plugins/maya/create/_colorbleed_mayaascii.py diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/_colorbleed_pointcache.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_pointcache.py rename to colorbleed/plugins/maya/create/_colorbleed_pointcache.py diff --git a/colorbleed/plugins/maya/create/colorbleed_texture.py b/colorbleed/plugins/maya/create/_colorbleed_texture.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_texture.py rename to colorbleed/plugins/maya/create/_colorbleed_texture.py diff --git a/colorbleed/plugins/maya/create/colorbleed_yetifur.py b/colorbleed/plugins/maya/create/_colorbleed_yetifur.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_yetifur.py rename to colorbleed/plugins/maya/create/_colorbleed_yetifur.py From cf82b2d0a94eaa1ef653ae651e01b201d94f332e Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 18:37:38 +0200 Subject: [PATCH 0185/1249] Deprecate (and disable) raw camera extraction --- .../publish/{extract_camera_raw.py => _extract_camera_raw.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/publish/{extract_camera_raw.py => _extract_camera_raw.py} (100%) diff --git a/colorbleed/plugins/maya/publish/extract_camera_raw.py b/colorbleed/plugins/maya/publish/_extract_camera_raw.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_camera_raw.py rename to colorbleed/plugins/maya/publish/_extract_camera_raw.py From 32fffa9aa4a97aba52ad489860fe901d68c2b722 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 18:52:59 +0200 Subject: [PATCH 0186/1249] Separate Camera extraction for Alembic and Maya Ascii to separate plug-ins, implement the usage of a "bakeToWorldSpace" attribute. Note: The Camera mayaAscii extractor does not support `bakeToWorldSpace=False` yet. See the TODO comment. --- .../maya/publish/extract_camera_alembic.py | 82 ++++++++++ .../maya/publish/extract_camera_baked.py | 148 ------------------ .../maya/publish/extract_camera_mayaAscii.py | 134 ++++++++++++++++ 3 files changed, 216 insertions(+), 148 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/extract_camera_alembic.py delete mode 100644 colorbleed/plugins/maya/publish/extract_camera_baked.py create mode 100644 colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py new file mode 100644 index 0000000000..418ba8ad12 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -0,0 +1,82 @@ +import os + +from maya import cmds + +import avalon.maya +import colorbleed.api + +import cb.utils.maya.context as context + + +class ExtractCameraAlembic(colorbleed.api.Extractor): + """Extract a Camera as Alembic. + + The cameras gets baked to world space by default. Only when the instance's + `bakeToWorldSpace` is set to False it will include its full hierarchy. + + The extracted Maya ascii file gets "massaged" removing the uuid values + so they are valid for older versions of Fusion (e.g. 6.4) + + """ + + label = "Camera (Alembic)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + + # get settings + framerange = [instance.data.get("startFrame", 1), + instance.data.get("endFrame", 1)] + handles = instance.data.get("handles", 0) + step = instance.data.get("step", 1.0) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) + + # get cameras + members = instance.data['setMembers'] + cameras = cmds.ls(members, leaf=True, shapes=True, long=True, + dag=True, type="camera") + + # validate required settings + assert len(cameras) == 1, "Not a single camera found in extraction" + assert isinstance(step, float), "Step must be a float value" + camera = cameras[0] + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.abc".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform alembic extraction + with avalon.maya.maintained_selection(): + cmds.select(camera, replace=True, noExpand=True) + + # Enforce forward slashes for AbcExport because we're + # embedding it into a job string + path = path.replace("\\", "/") + + job_str = ' -selection -dataFormat "ogawa" ' + job_str += ' -attrPrefix cb' + job_str += ' -frameRange {0} {1} '.format(framerange[0] - handles, + framerange[1] + handles) + + if bake_to_worldspace: + transform = cmds.listRelatives(camera, + parent=True, + fullPath=True)[0] + job_str += ' -worldSpace -root {0}'.format(transform) + + job_str += ' -file "{0}"'.format(path) + job_str += ' -step {0} '.format(step) + + with context.evaluation("off"): + with context.no_refresh(): + cmds.AbcExport(j=job_str, verbose=False) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_baked.py b/colorbleed/plugins/maya/publish/extract_camera_baked.py deleted file mode 100644 index 01645279ef..0000000000 --- a/colorbleed/plugins/maya/publish/extract_camera_baked.py +++ /dev/null @@ -1,148 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context -from cb.utils.maya.animation import bakeToWorldSpace - - -def massage_ma_file(path): - """Clean up .ma file for backwards compatibility. - - Massage the .ma of baked camera to stay - backwards compatible with older versions - of Fusion (6.4) - - """ - # Get open file's lines - f = open(path, "r+") - lines = f.readlines() - f.seek(0) # reset to start of file - - # Rewrite the file - for line in lines: - # Skip all 'rename -uid' lines - stripped = line.strip() - if stripped.startswith("rename -uid "): - continue - - f.write(line) - - f.truncate() # remove remainder - f.close() - - -class ExtractCameraBaked(colorbleed.api.Extractor): - """Extract as Maya Ascii and Alembic a baked camera. - - The cameras gets baked to world space and then extracted. - - The extracted Maya ascii file gets "massaged" removing the uuid values - so they are valid for older versions of Fusion (e.g. 6.4) - - """ - - label = "Camera Baked (Maya Ascii + Alembic)" - hosts = ["maya"] - families = ["colorbleed.camera"] - - def process(self, instance): - - file_names = [] - nodetype = 'camera' - # Define extract output file path - dir_path = self.staging_dir(instance) - alembic_as_baked = instance.data("cameraBakedAlembic", True) - - # get cameras - members = instance.data['setMembers'] - cameras = cmds.ls(members, leaf=True, shapes=True, - dag=True, type=nodetype) - - # Bake the cameras - transforms = cmds.listRelatives(cameras, parent=True, - fullPath=True) or [] - - framerange = [instance.data.get("startFrame", 1), - instance.data.get("endFrame", 1)] - - self.log.info("Performing camera bakes for: {0}".format(transforms)) - with context.evaluation("off"): - with context.no_refresh(): - baked = bakeToWorldSpace(transforms, frameRange=framerange) - - # Extract using the shape so it includes that and its hierarchy - # above. Otherwise Alembic takes only the transform - baked_shapes = cmds.ls(baked, type=nodetype, dag=True, - shapes=True, long=True) - - # Perform maya ascii extraction - filename = "{0}.ma".format(instance.name) - file_names.append(filename) - path = os.path.join(dir_path, filename) - - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - cmds.select(baked_shapes, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=False, - shader=False, - expressions=False) - - massage_ma_file(path) - - # Perform alembic extraction - filename = "{0}.abc".format(instance.name) - file_names.append(filename) - path = os.path.join(dir_path, filename) - - if alembic_as_baked: - abc_shapes = baked_shapes - else: - # get cameras in the instance - members = instance.data['setMembers'] - abc_shapes = cmds.ls(members, leaf=True, shapes=True, dag=True, - long=True, type=nodetype) - - # Whenever the camera was baked and Maya's scene time warp was enabled - # then we want to disable it whenever we publish the baked camera - # otherwise we'll get double the scene time warping. But whenever - # we *do not* publish a baked camera we want to keep it enabled. This - # way what the artist has in the scene visually represents the output. - with context.timewarp(state=not alembic_as_baked): - with avalon.maya.maintained_selection(): - cmds.select(abc_shapes, replace=True, noExpand=True) - - # Enforce forward slashes for AbcExport because we're - # embedding it into a job string - path = path.replace("\\", "/") - - job_str = ' -selection -dataFormat "ogawa" ' - job_str += ' -attrPrefix cb' - job_str += ' -frameRange {0} {1} '.format(framerange[0], - framerange[1]) - job_str += ' -file "{0}"'.format(path) - - with context.evaluation("off"): - with context.no_refresh(): - cmds.AbcExport(j=job_str, verbose=False) - - # Delete the baked camera (using transform to leave no trace) - cmds.delete(baked) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].extend(file_names) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py new file mode 100644 index 0000000000..35a9d431c0 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -0,0 +1,134 @@ +import os + +from maya import cmds + +import avalon.maya +import colorbleed.api + +import cb.utils.maya.context as context +from cb.utils.maya.animation import bakeToWorldSpace + + +def massage_ma_file(path): + """Clean up .ma file for backwards compatibility. + + Massage the .ma of baked camera to stay + backwards compatible with older versions + of Fusion (6.4) + + """ + # Get open file's lines + f = open(path, "r+") + lines = f.readlines() + f.seek(0) # reset to start of file + + # Rewrite the file + for line in lines: + # Skip all 'rename -uid' lines + stripped = line.strip() + if stripped.startswith("rename -uid "): + continue + + f.write(line) + + f.truncate() # remove remainder + f.close() + + +class ExtractCameraMayaAscii(colorbleed.api.Extractor): + """Extract a Camera as Maya Ascii. + + This will create a duplicate of the camera that will be baked *with + substeps and handles for the required frames. This temporary duplicate + will be published. + + The cameras gets baked to world space by default. Only when the instance's + `bakeToWorldSpace` is set to False it will include its full hierarchy. + + Note: + The extracted Maya ascii file gets "massaged" removing the uuid values + so they are valid for older versions of Fusion (e.g. 6.4) + + """ + + label = "Camera (Maya Ascii)" + hosts = ["maya"] + families = ["colorbleed.camera"] + + def process(self, instance): + + # get settings + framerange = [instance.data.get("startFrame", 1), + instance.data.get("endFrame", 1)] + handles = instance.data.get("handles", 0) + step = instance.data.get("step", 1.0) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) + + # TODO: Implement a bake to non-world space + # Currently it will always bake the resulting camera to world-space + # and it does not allow to include the parent hierarchy, even though + # with `bakeToWorldSpace` set to False it should include its hierarchy + # to be correct with the family implementation. + if not bake_to_worldspace: + self.log.warning("Camera (Maya Ascii) export only supports world" + "space baked camera extractions. The disabled " + "bake to world space is ignored...") + + # get cameras + members = instance.data['setMembers'] + cameras = cmds.ls(members, leaf=True, shapes=True, long=True, + dag=True, type="camera") + + range_with_handles = [framerange[0] - handles, + framerange[1] + handles] + + # validate required settings + assert len(cameras) == 1, "Not a single camera found in extraction" + assert isinstance(step, float), "Step must be a float value" + camera = cameras[0] + transform = cmds.listRelatives(camera, parent=True, fullPath=True) + + # Define extract output file path + dir_path = self.staging_dir(instance) + filename = "{0}.ma".format(instance.name) + path = os.path.join(dir_path, filename) + + # Perform extraction + self.log.info("Performing camera bakes for: {0}".format(transform)) + with avalon.maya.maintained_selection(): + with context.evaluation("off"): + with context.no_refresh(): + baked = bakeToWorldSpace(transform, + frameRange=range_with_handles, + step=step) + baked_shapes = cmds.ls(baked, + type="camera", + dag=True, + shapes=True, + long=True) + + self.log.info("Performing extraction..") + cmds.select(baked_shapes, noExpand=True) + cmds.file(path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + constructionHistory=False, + channels=True, # allow animation + constraints=False, + shader=False, + expressions=False) + + # Delete the baked hierarchy + cmds.delete(baked) + + massage_ma_file(path) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extracted instance '{0}' to: {1}".format( + instance.name, path)) From c6ce4a6b3050b28b968bb1690be580e50f3a3f80 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 18:54:18 +0200 Subject: [PATCH 0187/1249] Improve docstrings --- colorbleed/plugins/maya/publish/extract_camera_alembic.py | 3 --- colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index 418ba8ad12..16d6d94e76 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -14,9 +14,6 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): The cameras gets baked to world space by default. Only when the instance's `bakeToWorldSpace` is set to False it will include its full hierarchy. - The extracted Maya ascii file gets "massaged" removing the uuid values - so they are valid for older versions of Fusion (e.g. 6.4) - """ label = "Camera (Alembic)" diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index 35a9d431c0..c2c1f32b01 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -38,7 +38,7 @@ def massage_ma_file(path): class ExtractCameraMayaAscii(colorbleed.api.Extractor): """Extract a Camera as Maya Ascii. - This will create a duplicate of the camera that will be baked *with + This will create a duplicate of the camera that will be baked *with* substeps and handles for the required frames. This temporary duplicate will be published. From 79eda9501530be994282d3da7085b37b461d1854 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 12 Sep 2017 18:54:44 +0200 Subject: [PATCH 0188/1249] Add `bakeToWorldSpace` attribute to camera creator --- colorbleed/plugins/maya/create/colorbleed_camera.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 749945caf3..0029380d34 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -19,4 +19,8 @@ class CreateCamera(avalon.maya.Creator): for key, value in animation_data.items(): data[key] = value + # Bake to world space by default, when this is False it will also + # include the parent hierarchy in the baked results + data['bakeToWorldSpace'] = True + self.data = data From 01fa02551fc603e592c0978076acd5c4f55dd1e8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 14 Sep 2017 11:38:42 +0200 Subject: [PATCH 0189/1249] inserted temporary fix for conflict with http.py --- colorbleed/maya/__init__.py | 53 +++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 60266cef75..23364da156 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,11 +1,11 @@ import os -import site import uuid +from maya import cmds + from avalon import maya, io, api as avalon from pyblish import api as pyblish -from maya import cmds from . import menu from . import lib @@ -19,6 +19,52 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") +# This is a temporary solution with the http.py clash with six.py +# Maya has added paths to the PYTHONPATH which are redundant as +# many of them are paths from a pacakge, example: +# "/some/awesome/package/core/" which should be "/some/awesome/package" + + +def _remove_from_paths(paths, keyword, stitch=False): + """Remove any paths which contain the given keyword + + >>> paths = ["foo\\foo\\foo.py", "foo\\foobar.py", "\\bar\\bar\\foo"] + >>> _remove_from_paths(paths, keyword="bar") + ["foo\\foo\\foo.py"] + + >>> paths = ["foo\\bar\\foobar.py", "foo\\foobar.py", "\\banana\\pie\\delicious"] + >>> _remove_from_paths(paths, keyword="pie", stitch=True) + "foo\\bar\\foobar.py;foo\\foobar.py" + + Args: + paths(list) : a list of file paths + keyword(str) : the word to check for + stitch(bool) : recreate a full string for PYTHONPATH + + Returns: + str + Only when stitch is set to True does the function return a string + """ + + paths = [path for path in paths if keyword not in path] + if stitch: + return os.pathsep.join(paths) + + +def remove_googleapiclient(): + """Remove any paths which contain `googleclientapi`""" + + keyword = "googleclientapi" + # remove from sys.path + # _remove_from_paths(sys.path, keyword) + + # reconstruct pythonpaths + pythonpaths = os.environ["PYTHONPATH"].split(os.pathsep) + result = _remove_from_paths(pythonpaths, keyword, stitch=True) + + os.environ["PYTHONPATH"] = result + + def install(): pyblish.register_plugin_path(PUBLISH_PATH) @@ -32,6 +78,9 @@ def install(): avalon.on("new", on_new) avalon.on("save", on_save) + if cmds.about(version=True) == "2018": + remove_googleapiclient() + def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) From e39762fb7d686bf0f6f35b388a029aaf69bfb240 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 14 Sep 2017 13:49:03 +0200 Subject: [PATCH 0190/1249] error in keyword --- colorbleed/maya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 23364da156..d90957b7f5 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -54,7 +54,7 @@ def _remove_from_paths(paths, keyword, stitch=False): def remove_googleapiclient(): """Remove any paths which contain `googleclientapi`""" - keyword = "googleclientapi" + keyword = "googleapiclient" # remove from sys.path # _remove_from_paths(sys.path, keyword) From 3ea55955c6b74d0c5977328da5d63b7ebce14a76 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 15 Sep 2017 15:59:02 +0200 Subject: [PATCH 0191/1249] Log a debug message when no nodes found to assign to for a shading engine --- colorbleed/maya/lib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f5ca8396b5..49ac143c3b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -939,7 +939,11 @@ def apply_shaders(relationships, shadernodes, nodes): "objectSet with cbId " "'{}'".format(shader_uuid)) - cmds.sets(filtered_nodes, forceElement=shading_engine[0]) + if filtered_nodes: + cmds.sets(filtered_nodes, forceElement=shading_engine[0]) + else: + log.debug("No nodes found for shading engine " + "'{0}'".format(shading_engine[0])) # endregion apply_attributes(attributes, ns_nodes_by_id) From 58347ca4867b9fc84200a7ee0f260989baa07743 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 15 Sep 2017 16:11:55 +0200 Subject: [PATCH 0192/1249] Raise log to warning level --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 49ac143c3b..de1ca9cb20 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -942,8 +942,8 @@ def apply_shaders(relationships, shadernodes, nodes): if filtered_nodes: cmds.sets(filtered_nodes, forceElement=shading_engine[0]) else: - log.debug("No nodes found for shading engine " - "'{0}'".format(shading_engine[0])) + log.warning("No nodes found for shading engine " + "'{0}'".format(shading_engine[0])) # endregion apply_attributes(attributes, ns_nodes_by_id) From 1adf3d3d0367a70bd90c219f6c4b11971732a691 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 15 Sep 2017 16:32:47 +0200 Subject: [PATCH 0193/1249] Add "set filename prefix" action --- colorbleed/maya/menu.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 776e014e9e..77cf89ba76 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -785,6 +785,22 @@ } ] }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", + "sourcetype": "file", + "tags": [ + "shading", + "lookdev", + "assign", + "shaders", + "prefix", + "filename", + "render" + ], + "title": "Set filename prefix", + "tooltip": "Set the render file name prefix." + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", From 1cb14e1628b9ee7f42629f210787f2edfc200e41 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 10:55:02 +0200 Subject: [PATCH 0194/1249] removed unused variable --- colorbleed/maya/lib.py | 1 - .../_validate_look_members_node_ids.py | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f5ca8396b5..b20d329778 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -828,7 +828,6 @@ def assign_look_by_version(nodes, version_id): else: log.info("Reusing existing lookdev '{}'".format(reference_node)) shader_nodes = cmds.referenceQuery(reference_node, nodes=True) - namespace = cmds.referenceQuery(reference_node, namespace=True) # Assign relationships with open(shader_relation, "r") as f: diff --git a/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py new file mode 100644 index 0000000000..805fcdf207 --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py @@ -0,0 +1,39 @@ +import pyblish.api +import colorbleed.api + + +class ValidateLookMembers(pyblish.api.InstancePlugin): + """Validate look members have colorbleed id attributes + + Looks up all relationship members and check if all the members have the + cbId (colorbleed id) and return all the nodes who fail the test. + + """ + + order = colorbleed.api.ValidatePipelineOrder + families = ['colorbleed.lookdev'] + hosts = ['maya'] + label = 'Look Members (ID)' + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.GenerateUUIDsOnInvalidAction] + + def process(self, instance): + """Process all meshes""" + + invalid_ids = self.get_invalid(instance) + if invalid_ids: + raise RuntimeError("Found invalid nodes.\nNo ID : " + "{}".format(invalid_ids)) + + @classmethod + def get_invalid(cls, instance): + + relationships = instance.data["lookData"]["relationships"] + members = [] + for relationship in relationships.values(): + members.extend(relationship["members"]) + + # get the name of the node when there is no UUID + invalid = [m["name"] for m in members if not m["uuid"]] + + return invalid From aef176881753fedd71ddc9caedafc16e289bf10d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:02:33 +0200 Subject: [PATCH 0195/1249] fixed error in passing on instance items --- colorbleed/plugins/maya/publish/collect_look.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 1fc6dcc8bf..8ce3f0101a 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,5 +1,5 @@ from maya import cmds - +import pprint import pyblish.api import colorbleed.maya.lib as lib from cb.utils.maya import context, shaders @@ -69,9 +69,7 @@ class CollectLook(pyblish.api.InstancePlugin): def process(self, instance): """Collect the Look in the instance with the correct layer settings""" - layer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(layer): - self.log.info("Checking out layer: {0}".format(layer)) + with context.renderlayer("defaultRenderLayer"): self.collect(instance) def collect(self, instance): @@ -84,7 +82,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Discover related object sets self.log.info("Gathering sets..") - sets = self.gather_sets(cmds.ls(instance, shapes=True)) + sets = self.gather_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set([str(x) for x in cmds.ls(instance, long=True)]) @@ -111,7 +109,7 @@ class CollectLook(pyblish.api.InstancePlugin): attributes = self.collect_attributes_changed(instance) looksets = cmds.ls(sets.keys(), long=True) - self.log.info("Found the following sets: {}".format(looksets)) + self.log.info("Found the following sets:\n{}".format(looksets)) # Store data on the instance instance.data["lookData"] = {"attributes": attributes, From e39e5dba70cfead7b6648f2277bc7b08ff62d34e Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:05:49 +0200 Subject: [PATCH 0196/1249] changed name of validator, improved docstrings, changed defaultdict(list) to defaultdict(set) --- .../publish/validate_look_members_unique.py | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 9391b086dd..65ebeda5c3 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -8,18 +8,19 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): """Validate the relational nodes of the look data to ensure every node is unique. - This ensures the all member ids are unique. + This ensures the all member ids are unique. Every node id must be from + a single node in the scene. That means there's only ever one of a specific node inside the look to be published. For example if you'd have a loaded 3x the same tree and by accident you're trying to publish them all together in a single look that - would be invalid, because they are the same tree it should be included + would be invalid, because they are the same tree. It should be included inside the look instance only once. """ order = colorbleed.api.ValidatePipelineOrder - label = 'Unique Relationship Members (ID)' + label = 'Look members unique' hosts = ['maya'] families = ['colorbleed.lookdev'] @@ -31,22 +32,39 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Members found without asset IDs: " + raise RuntimeError("Members found without non-unique IDs: " "{0}".format(invalid)) @staticmethod def get_invalid(instance): + """ + Check all the relationship members of the objectSets + + Example of the lookData relationships: + {"uuid": 59b2bb27bda2cb2776206dd8:79ab0a63ffdf, + "members":[{"uuid": 59b2bb27bda2cb2776206dd8:1b158cc7496e, + "name": |model_GRP|body_GES|body_GESShape} + ..., + ...]} + + Args: + instance: + + Returns: + + """ # Get all members from the sets - id_nodes = defaultdict(list) + id_nodes = defaultdict(set) relationships = instance.data["lookData"]["relationships"] + for relationship in relationships.values(): for member in relationship['members']: node_id = member["uuid"] node = member["name"] - id_nodes[node_id].append(node) + id_nodes[node_id].add(node) - # check if any id has more than 1 node + # Check if any id has more than 1 node invalid = [] for nodes in id_nodes.values(): if len(nodes) > 1: From c59a74c584329b6c25166139f3df92723c4e73ef Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:12:46 +0200 Subject: [PATCH 0197/1249] improved get_invalid function, updated docstrings, more explicit error --- .../publish/validate_look_single_shader.py | 45 ++++++++----------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py index 1d780bc302..497b356a13 100644 --- a/colorbleed/plugins/maya/publish/validate_look_single_shader.py +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -5,14 +5,9 @@ import colorbleed.api class ValidateSingleShader(pyblish.api.InstancePlugin): - """Validate default shaders in the scene have their default connections. + """Validate all nurbsSurfaces and meshes have exactly one shader assigned. - For example the lambert1 could potentially be disconnected from the - initialShadingGroup. As such it's not lambert1 that will be identified - as the default shader which can have unpredictable results. - - To fix the default connections need to be made again. See the logs for - more details on which connections are missing. + This will error if a shape has no shaders or more than one shader. """ @@ -26,36 +21,32 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found shapes which have multiple " - "shadingEngines connected." + raise RuntimeError("Found shapes which don't have a single shader " + "assigned: " "\n{}".format(invalid)) @classmethod def get_invalid(cls, instance): - invalid = [] - shape_types = ["numrbsCurve", "nurbsSurface", "mesh"] # Get all shapes from the instance - shapes = set() - for node in instance[:]: - - nodetype = cmds.nodeType(node) - if nodetype in shape_types: - shapes.add(node) - - elif nodetype == "transform": - shape = cmds.listRelatives(node, children=True, - type="shape", fullPath=True) - if not shape: - continue - shapes.add(shape[0]) + shapes = cmds.ls(instance, type=["nurbsSurface", "mesh"], long=True) # Check the number of connected shadingEngines per shape + no_shaders = [] + more_than_one_shaders = [] for shape in shapes: shading_engines = cmds.listConnections(shape, destination=True, type="shadingEngine") or [] - if len(shading_engines) > 1: - invalid.append(shape) + if not shading_engines: + no_shaders.append(shape) + elif len(shading_engines) > 1: + more_than_one_shaders.append(shape) - return invalid + if no_shaders: + cls.log.error("No shaders found on: {}".format(no_shaders)) + if more_than_one_shaders: + cls.log.error("More than one shader found on: " + "{}".format(more_than_one_shaders)) + + return no_shaders + more_than_one_shaders From a81beafd1bc3c61107f85d9866e0213400eae1b9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:14:37 +0200 Subject: [PATCH 0198/1249] improved docstrings --- colorbleed/plugins/maya/publish/validate_node_ids_related.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index 84c28c6427..ba896c210c 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -7,7 +7,7 @@ from colorbleed.maya import lib class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): - """Validate nodes have a related Colorbleed Id to the instance asset + """Validate nodes have a related Colorbleed Id to the instance.data[asset] """ From aa46524592e7a830ccd215f9eea55d5f311158f6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:17:14 +0200 Subject: [PATCH 0199/1249] explictly use defaultRenderLayer --- colorbleed/plugins/maya/publish/validate_look_sets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 7fcb10c1ec..53fb04fb1e 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -45,8 +45,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): relationships = instance.data["lookData"]["relationships"] invalid = [] - layer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(layer): + with context.renderlayer("defaultRenderLayer"): for node in instance: # get the connected objectSets of the node sets = lib.get_related_sets(node) @@ -69,7 +68,8 @@ class ValidateLookSets(pyblish.api.InstancePlugin): # isn't in it anyway continue - member_nodes = [member['name'] for member in data['members']] + member_nodes = [member['name'] for member in + data['members']] if node not in member_nodes: # The node is not found in the collected set # relationships From 742cd51d7c75446f1a3ce25149469af35510160a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Sep 2017 11:18:21 +0200 Subject: [PATCH 0200/1249] removed redundant module, nodes without ID are not being picked by the collector --- .../publish/validate_look_members_node_ids.py | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_look_members_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py deleted file mode 100644 index c6a914b304..0000000000 --- a/colorbleed/plugins/maya/publish/validate_look_members_node_ids.py +++ /dev/null @@ -1,39 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateLookMembers(pyblish.api.InstancePlugin): - """Validate look members have colorbleed id attributes - - Loops up all relationship members and check if all the members have the - cbId (colorbleed id) and return all the nodes who fail the test. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.lookdev'] - hosts = ['maya'] - label = 'Look Members (ID)' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - def process(self, instance): - """Process all meshes""" - - invalid_ids = self.get_invalid(instance) - if invalid_ids: - raise RuntimeError("Found invalid nodes.\nNo ID : " - "{}".format(invalid_ids)) - - @classmethod - def get_invalid(cls, instance): - - relationships = instance.data["lookData"]["relationships"] - members = [] - for relationship in relationships.values(): - members.extend(relationship["members"]) - - # get the name of the node when there is no UUID - invalid = [m["name"] for m in members if not m["uuid"]] - - return invalid From 551eea9166703fc07e9d357cced4a1af45efee83 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 21 Sep 2017 10:32:10 +0200 Subject: [PATCH 0201/1249] added colorbleed.mayaAscii to families --- .../{_colorbleed_mayaascii.py => colorbleed_mayaascii.py} | 0 colorbleed/plugins/publish/integrate.py | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) rename colorbleed/plugins/maya/create/{_colorbleed_mayaascii.py => colorbleed_mayaascii.py} (100%) diff --git a/colorbleed/plugins/maya/create/_colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py similarity index 100% rename from colorbleed/plugins/maya/create/_colorbleed_mayaascii.py rename to colorbleed/plugins/maya/create/colorbleed_mayaascii.py diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 08f82e827e..f348deb654 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -31,7 +31,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.texture", "colorbleed.historyLookdev", "colorbleed.group", - "colorbleed.imagesequence"] + "colorbleed.imagesequence", + "colorbleed.mayaAscii"] def process(self, instance): From 41b5d50f8bf13b6eb9ed3b431f3036048ed1f808 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 21 Sep 2017 10:33:47 +0200 Subject: [PATCH 0202/1249] renames label to Look --- colorbleed/plugins/maya/create/colorbleed_look.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index e6f5751c53..e4e5fab9a4 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -6,8 +6,8 @@ from colorbleed.maya import lib class CreateLook(avalon.maya.Creator): """Polygonal geometry for animation""" - name = "lookDefault" - label = "Look Dev" + name = "look" + label = "Look" family = "colorbleed.lookdev" def __init__(self, *args, **kwargs): From a22a97d2acf07cbe421f3a066293bfaec1087514 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 21 Sep 2017 10:34:57 +0200 Subject: [PATCH 0203/1249] cosmetics --- .../maya/publish/validate_rig_controllers.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 373ec9ccbb..465c6d3f7b 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -10,7 +10,7 @@ log = logging.getLogger("Rig Controllers") class ValidateRigControllers(pyblish.api.InstancePlugin): """Check if the controllers have the transformation attributes set to - default values, locked vibisility attributes and are not keyed + default values, locked visibility attributes and are not keyed """ order = colorbleed.api.ValidateContentsOrder + 0.05 label = "Rig Controllers" @@ -34,8 +34,9 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): not_locked = list() is_offset = list() - controls = cmds.sets("controls_SET", query=True) - assert controls, "Must have controls in rig controls_SET" + controllers_sets = [i for i in instance if i == "controls_SET"] + controls = cmds.sets(controllers_sets, query=True) + assert controls, "Must have 'controls_SET' in rig instance" for control in controls: valid_keyed = cls.validate_keyed_state(control) @@ -54,7 +55,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if is_keyed: cls.log.error("No controls can be keyes. Failed :\n" - "%s" % is_keyed) + "%s" % is_keyed) error = True if is_offset: @@ -64,8 +65,8 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): if not_locked: cls.log.error("All controls must have visibility " - "attribute locked. Failed :\n" - "%s" % not_locked) + "attribute locked. Failed :\n" + "%s" % not_locked) error = True if error: From d76b42a6b4d27810f0c234b1b249aee8eab679a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 12:56:15 +0200 Subject: [PATCH 0204/1249] Implement pointcache family (untested; wip!) --- ...pointcache.py => colorbleed_pointcache.py} | 17 ++--- .../plugins/maya/publish/extract_alembic.py | 70 +++++++++---------- ...ate_animation_out_set_related_node_ids.py} | 10 +-- .../plugins/maya/publish/validate_node_ids.py | 1 + 4 files changed, 46 insertions(+), 52 deletions(-) rename colorbleed/plugins/maya/create/{_colorbleed_pointcache.py => colorbleed_pointcache.py} (67%) rename colorbleed/plugins/maya/publish/{validate_rig_pointcache_related_node_ids.py => validate_animation_out_set_related_node_ids.py} (91%) diff --git a/colorbleed/plugins/maya/create/_colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py similarity index 67% rename from colorbleed/plugins/maya/create/_colorbleed_pointcache.py rename to colorbleed/plugins/maya/create/colorbleed_pointcache.py index c24dac0b22..4dd5e11cde 100644 --- a/colorbleed/plugins/maya/create/_colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -1,8 +1,7 @@ from collections import OrderedDict -from maya import cmds - import avalon.maya +from colorbleed.maya import lib class CreatePointCache(avalon.maya.Creator): @@ -18,15 +17,9 @@ class CreatePointCache(avalon.maya.Creator): # create an ordered dict with the existing data first data = OrderedDict(**self.data) - # get scene values as defaults - start = cmds.playbackOptions(query=True, animationStartTime=True) - end = cmds.playbackOptions(query=True, animationEndTime=True) - - # build attributes - data["startFrame"] = start - data["endFrame"] = end - data["handles"] = 1 - data["step"] = 1.0 + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + data[key] = value # Write vertex colors with the geometry. data["writeColorSets"] = False @@ -39,4 +32,4 @@ class CreatePointCache(avalon.maya.Creator): # frame range. data["visibleOnly"] = False - self.data = data + self.data = data \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_alembic.py index 1d82632e36..2ddc9c76e9 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_alembic.py @@ -1,5 +1,4 @@ import os -import copy from maya import cmds @@ -8,54 +7,55 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic -class ExtractColorbleedAlembic(colorbleed.api.Extractor): - """Extract Alembic Cache +class ExtractColorbleedAnimation(colorbleed.api.Extractor): + """Produce an alembic of just point positions and normals. - This extracts an Alembic cache using the `-selection` flag to minimize - the extracted content to solely what was Collected into the instance. + Positions and normals are preserved, but nothing more, + for plain and predictable point caches. """ - label = "Alembic" - families = ["colorbleed.model", - "colorbleed.pointcache"] + + label = "Extract Pointcache" + hosts = ["maya"] + families = ["colorbleed.pointcache"] def process(self, instance): + nodes = instance.data['setMembers'] + + # Collect the start and end including handles + start = instance.data["startFrame"] + end = instance.data["endFrame"] + handles = instance.data.get("handles", 0) + if handles: + start -= handles + end += handles + + self.log.info("Extracting animation..") + dirname = self.staging_dir(instance) + + self.log.info("nodes: %s" % str(nodes)) + parent_dir = self.staging_dir(instance) - filename = "%s.abc" % instance.name + filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) - attrPrefix = instance.data.get("attrPrefix", []) - attrPrefix.append("cb") - - options = copy.deepcopy(instance.data) - options['attrPrefix'] = attrPrefix - - # Ensure visibility keys are written - options['writeVisibility'] = True - - # Write creases - options['writeCreases'] = True - - # Ensure UVs are written - options['uvWrite'] = True - - options['selection'] = True - options["attr"] = ["cbId"] - - # force elect items to ensure all items get exported by Alembic - members = instance.data("setMembers") - - cmds.select(members) with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): - nodes = instance[:] - cmds.select(nodes, replace=True, noExpand=True) - extract_alembic(file=path, **options) + cmds.select(nodes, noExpand=True) + extract_alembic(file=path, + startFrame=start, + endFrame=end, + **{"step": instance.data.get("step", 1.0), + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "uvWrite": True, + "selection": True}) if "files" not in instance.data: instance.data["files"] = list() instance.data["files"].append(filename) - cmds.select(clear=True) + self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py similarity index 91% rename from colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py rename to colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 68894b2063..0d056b9b9d 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_pointcache_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -6,7 +6,7 @@ import colorbleed.maya.lib as lib import avalon.io as io -class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): +class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): """Validate rig out_SET nodes have related ids to current context An ID is 'related' if its built in the current Item. @@ -18,9 +18,9 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.rigpointcache', 'colorbleed.pointcache'] + families = ['colorbleed.animation'] hosts = ['maya'] - label = 'Rig Pointcache Related Node Ids' + label = 'Animation Out Set Related Node Ids' actions = [colorbleed.api.SelectInvalidAction] optional = True @@ -29,8 +29,6 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - print ">>>", instance.data - # Ensure all nodes have a cbId invalid = self.get_invalid(instance) if invalid: @@ -76,6 +74,8 @@ class ValidateRigPointcacheRelatedNodeIds(pyblish.api.InstancePlugin): if not node_id: invalid_items.append(node) + # TODO: Should we check whether the ids are related to the rig's asset? + # Log invalid item ids if invalid_items: for item_id in sorted(invalid_items): diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index b4a219c2ae..f4499708fc 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -18,6 +18,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["colorbleed.model", "colorbleed.lookdev", + "colorbleed.pointcache", "colorbleed.rig"] actions = [colorbleed.api.SelectInvalidAction, From d39f2dc96241c2ffb8ae10168c0bf47582cf81bf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 13:13:36 +0200 Subject: [PATCH 0205/1249] Fix extracting alembic with `writeCreases` in Maya 2018+, fix False options for boolean vaues in extracting alembic --- colorbleed/maya/lib.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 64c03b2723..6103facc2b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -551,6 +551,11 @@ def extract_alembic(file, raise TypeError("Alembic option unsupported type: " "{0} (expected {1})".format(value, valid_types)) + # The `writeCreases` argument was changed to `autoSubd` in Maya 2018+ + maya_version = int(cmds.about(version=True)) + if maya_version >= 2018: + options['autoSubd'] = options.pop('writeCreases', False) + # Format the job string from options job_args = list() for key, value in options.items(): @@ -558,7 +563,9 @@ def extract_alembic(file, for entry in value: job_args.append("-{} {}".format(key, entry)) elif isinstance(value, bool): - job_args.append("-{0}".format(key)) + # Add only when state is set to True + if value: + job_args.append("-{0}".format(key)) else: job_args.append("-{0} {1}".format(key, value)) From dabd2d66683fc41e90511b171b6a06baf79857d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 13:13:52 +0200 Subject: [PATCH 0206/1249] Support loading `colorbleed.pointcache` --- colorbleed/plugins/maya/load/load_animation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index c8c13ee006..b0679665fe 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -7,7 +7,9 @@ from avalon import api class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", "colorbleed.camera"] + families = ["colorbleed.animation", + "colorbleed.camera", + "colorbleed.pointcache"] representations = ["abc"] label = "Reference animation" From c861a2ad361070e6b63c19849f632e5693ef50fe Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 13:14:13 +0200 Subject: [PATCH 0207/1249] Clarify why set is being skipped in logged message, because it's empty --- colorbleed/plugins/maya/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index bbcd8a56cb..a4d565a0f2 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -59,7 +59,7 @@ class CollectInstances(pyblish.api.ContextPlugin): members = cmds.sets(objset, query=True) if members is None: - self.log.info("Skipped following Set: \"%s\" " % objset) + self.log.info("Skipped empty Set: \"%s\" " % objset) continue if not cmds.attributeQuery("id", node=objset, exists=True): From d89b318e5730710c564b7e9fd979fa4e09b003ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 13:14:46 +0200 Subject: [PATCH 0208/1249] Fix `colorbleed.pointcache` being extracted correctly and actually being integrated --- .../publish/{extract_alembic.py => extract_pointcache.py} | 4 ++-- colorbleed/plugins/publish/integrate.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) rename colorbleed/plugins/maya/publish/{extract_alembic.py => extract_pointcache.py} (94%) diff --git a/colorbleed/plugins/maya/publish/extract_alembic.py b/colorbleed/plugins/maya/publish/extract_pointcache.py similarity index 94% rename from colorbleed/plugins/maya/publish/extract_alembic.py rename to colorbleed/plugins/maya/publish/extract_pointcache.py index 2ddc9c76e9..f29f7846bf 100644 --- a/colorbleed/plugins/maya/publish/extract_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,7 +7,7 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic -class ExtractColorbleedAnimation(colorbleed.api.Extractor): +class ExtractColorbleedPointcache(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -21,7 +21,7 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): def process(self, instance): - nodes = instance.data['setMembers'] + nodes = instance[:] # Collect the start and end including handles start = instance.data["startFrame"] diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index f348deb654..43792ec12f 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -26,6 +26,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): families = ["colorbleed.model", "colorbleed.rig", "colorbleed.animation", + "colorbleed.pointcache", "colorbleed.camera", "colorbleed.lookdev", "colorbleed.texture", From b46b40806bc307b5cfad6ec76a7ccad0fca29330 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 21 Sep 2017 13:33:32 +0200 Subject: [PATCH 0209/1249] Remove `colorbleed.layout` plug-ins, since the family is not supported in any way. --- .../{extract_layout_mayaAscii.py => _extract_layout_mayaAscii.py} | 0 .../{validate_layout_content.py => _validate_layout_content.py} | 0 .../{validate_layout_node_ids.py => _validate_layout_node_ids.py} | 0 ..._no_transforms.py => _validate_layout_parent_no_transforms.py} | 0 ...ayout_shape_node_ids.py => _validate_layout_shape_node_ids.py} | 0 ...out_unique_node_ids.py => _validate_layout_unique_node_ids.py} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/publish/{extract_layout_mayaAscii.py => _extract_layout_mayaAscii.py} (100%) rename colorbleed/plugins/maya/publish/{validate_layout_content.py => _validate_layout_content.py} (100%) rename colorbleed/plugins/maya/publish/{validate_layout_node_ids.py => _validate_layout_node_ids.py} (100%) rename colorbleed/plugins/maya/publish/{validate_layout_parent_no_transforms.py => _validate_layout_parent_no_transforms.py} (100%) rename colorbleed/plugins/maya/publish/{validate_layout_shape_node_ids.py => _validate_layout_shape_node_ids.py} (100%) rename colorbleed/plugins/maya/publish/{validate_layout_unique_node_ids.py => _validate_layout_unique_node_ids.py} (100%) diff --git a/colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py b/colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_layout_mayaAscii.py rename to colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py diff --git a/colorbleed/plugins/maya/publish/validate_layout_content.py b/colorbleed/plugins/maya/publish/_validate_layout_content.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_layout_content.py rename to colorbleed/plugins/maya/publish/_validate_layout_content.py diff --git a/colorbleed/plugins/maya/publish/validate_layout_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_layout_node_ids.py rename to colorbleed/plugins/maya/publish/_validate_layout_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py b/colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_layout_parent_no_transforms.py rename to colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py diff --git a/colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_layout_shape_node_ids.py rename to colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_layout_unique_node_ids.py rename to colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py From 017fe73609b20b290f09c72f8e470407fef85260 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 21 Sep 2017 13:50:45 +0200 Subject: [PATCH 0210/1249] added setdress family and collector --- .../plugins/maya/create/colorbleed_setdress.py | 9 +++++++++ colorbleed/plugins/maya/publish/collect_setdress.py | 13 +++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 colorbleed/plugins/maya/create/colorbleed_setdress.py create mode 100644 colorbleed/plugins/maya/publish/collect_setdress.py diff --git a/colorbleed/plugins/maya/create/colorbleed_setdress.py b/colorbleed/plugins/maya/create/colorbleed_setdress.py new file mode 100644 index 0000000000..45f3ffcf84 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_setdress.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateSetdress(avalon.maya.Creator): + """Skeleton and controls for manipulation of the geometry""" + + name = "setDress" + label = "Setdress" + family = "colorbleed.setdress" diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py new file mode 100644 index 0000000000..dda47cd7d5 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -0,0 +1,13 @@ +from maya import cmds + +import pyblish.api + + +class CollectSetdress(pyblish.api.InstancePlugin): + + order = pyblish.api.CollectorOrder + 0.499 + label = 'Collect Model Data' + families = ["colorbleed.setdress"] + + def process(self, instance): + pass From f2e5e330974bf223bcca715e1d7063839fa607c1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 21 Sep 2017 14:08:27 +0200 Subject: [PATCH 0211/1249] added project manager to CB menu, temp location - needs to move to Avalon menu --- colorbleed/maya/menu.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 77cf89ba76..d9728c2245 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -6,6 +6,13 @@ "title": "Version Up", "tooltip": "Incremental save with a specific format" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\avalon\\launch_manager.py", + "sourcetype": "file", + "title": "Project Manager", + "tooltip": "Add assets to the project" + }, { "type": "separator" }, From 492a7f468ad3045e4ba05444c5c6f54f5e6b6644 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 11:38:58 +0200 Subject: [PATCH 0212/1249] added Maya Ascii loader --- .../plugins/maya/load/load_mayaascii.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_mayaascii.py diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/colorbleed/plugins/maya/load/load_mayaascii.py new file mode 100644 index 0000000000..7bc75fca45 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_mayaascii.py @@ -0,0 +1,34 @@ +from avalon import api + + +class MayaAsciiLoader(api.Loader): + """Load the model""" + + families = ["colorbleed.mayaAscii"] + representations = ["ma"] + + label = "Reference Maya Ascii" + order = -10 + icon = "code-fork" + color = "orange" + + def process(self, name, namespace, context, data): + + import maya.cmds as cmds + from avalon import maya + + # Create a readable namespace + # Namespace should contain asset name and counter + # TEST_001{_descriptor} where `descriptor` can be `_abc` for example + assetname = "{}_".format(namespace.split("_")[0]) + namespace = maya.unique_namespace(assetname, format="%03d") + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + self[:] = nodes From 930a2ec3424a58ad74cf6f0b29ecd7b9f95c1129 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 22 Sep 2017 12:02:42 +0200 Subject: [PATCH 0213/1249] Implement set frame range actions --- colorbleed/plugins/maya/load/actions.py | 78 +++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 colorbleed/plugins/maya/load/actions.py diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py new file mode 100644 index 0000000000..9e54195294 --- /dev/null +++ b/colorbleed/plugins/maya/load/actions.py @@ -0,0 +1,78 @@ +"""A module containing generic loader actions that will display in the Loader. + +""" + +from avalon import api + + +class SetFrameRangeLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", + "colorbleed.camera", + "colorbleed.pointcache"] + representations = ["abc"] + + label = "Set frame range" + order = 11 + icon = "clock-o" + color = "white" + + def process(self, name, namespace, context, data): + + import maya.cmds as cmds + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("startFrame", None) + end = version_data.get("endFrame", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + cmds.playbackOptions(minTime=start, + maxTime=end, + animationStartTime=start, + animationEndTime=end) + + +class SetFrameRangeWithHandlesLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", + "colorbleed.camera", + "colorbleed.pointcache"] + representations = ["abc"] + + label = "Set frame range (with handles)" + order = 12 + icon = "clock-o" + color = "white" + + def process(self, name, namespace, context, data): + + import maya.cmds as cmds + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("startFrame", None) + end = version_data.get("endFrame", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + # Include handles + handles = version_data.get("handles", 0) + start -= handles + end += handles + + cmds.playbackOptions(minTime=start, + maxTime=end, + animationStartTime=start, + animationEndTime=end) From 591fb615913cf45d1e6f4b8325f91531e4e03f95 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 12:23:27 +0200 Subject: [PATCH 0214/1249] renamed family correct, error in lookdev pipeline --- colorbleed/plugins/maya/create/colorbleed_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index e4e5fab9a4..9ac1cbe934 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -8,7 +8,7 @@ class CreateLook(avalon.maya.Creator): name = "look" label = "Look" - family = "colorbleed.lookdev" + family = "colorbleed.look" def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) From ac1a82fef19acf918bec7d6ab71c40681b1b4024 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 13:57:44 +0200 Subject: [PATCH 0215/1249] fixed issue with colorbleed.look --- colorbleed/maya/lib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 6103facc2b..3d231d050a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -880,17 +880,17 @@ def assign_look(nodes, subset="lookDefault"): continue # get last version + # with backwards compatibility version = io.find_one({"parent": subset_data['_id'], "type": "version", "data.families": - {"$in": ["colorbleed.lookdev"]} + {"$in": ["colorbleed.look"]} }, sort=[("name", -1)], - projection={"_id": True}) + projection={"_id": True, "name": True}) - log.debug("Assigning look '{}' <{}> to nodes: {}".format(subset, - version, - asset_nodes)) + log.debug("Assigning look '{}' ".format(subset, + version["name"])) assign_look_by_version(asset_nodes, version['_id']) From 67b24bd5086449caf8216984d9647bc52fe61628 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 13:58:51 +0200 Subject: [PATCH 0216/1249] renamed family lookdev to look, updated plugins --- colorbleed/maya/lib.py | 2 +- colorbleed/plugins/maya/load/load_look.py | 2 +- .../plugins/maya/publish/_validate_filename_convention.py | 2 +- .../plugins/maya/publish/_validate_look_members_node_ids.py | 2 +- colorbleed/plugins/maya/publish/collect_instances.py | 2 +- colorbleed/plugins/maya/publish/collect_look.py | 2 +- colorbleed/plugins/maya/publish/extract_look.py | 2 +- colorbleed/plugins/maya/publish/validate_look_contents.py | 2 +- .../maya/publish/validate_look_default_shaders_connections.py | 2 +- .../plugins/maya/publish/validate_look_deformed_shapes.py | 2 +- .../plugins/maya/publish/validate_look_ignore_color_space.py | 2 +- colorbleed/plugins/maya/publish/validate_look_members_unique.py | 2 +- .../plugins/maya/publish/validate_look_no_default_shaders.py | 2 +- colorbleed/plugins/maya/publish/validate_look_sets.py | 2 +- colorbleed/plugins/maya/publish/validate_look_single_shader.py | 2 +- colorbleed/plugins/maya/publish/validate_node_ids.py | 2 +- colorbleed/plugins/maya/publish/validate_node_ids_related.py | 2 +- colorbleed/plugins/maya/publish/validate_unique_node_ids.py | 2 +- colorbleed/plugins/publish/integrate.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b20d329778..62e694e334 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -876,7 +876,7 @@ def assign_look(nodes, subset="lookDefault"): version = io.find_one({"parent": subset_data['_id'], "type": "version", "data.families": - {"$in": ["colorbleed.lookdev"]} + {"$in": ["colorbleed.look"]} }, sort=[("name", -1)], projection={"_id": True}) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index d4a539cbc2..6a265b64ce 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -7,7 +7,7 @@ from avalon import api class LookLoader(api.Loader): """Specific loader for lookdev""" - families = ["colorbleed.lookdev"] + families = ["colorbleed.look"] representations = ["ma"] label = "Reference look" diff --git a/colorbleed/plugins/maya/publish/_validate_filename_convention.py b/colorbleed/plugins/maya/publish/_validate_filename_convention.py index 74c4592bce..a53fd72ab8 100644 --- a/colorbleed/plugins/maya/publish/_validate_filename_convention.py +++ b/colorbleed/plugins/maya/publish/_validate_filename_convention.py @@ -10,7 +10,7 @@ import colorbleed.api class ValidateFileNameConvention(pyblish.api.InstancePlugin): label = "" - families = ["colorbleed.lookdev"] + families = ["colorbleed.look"] host = ["maya"] optional = True diff --git a/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py index 805fcdf207..c9e2a99cea 100644 --- a/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py +++ b/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py @@ -11,7 +11,7 @@ class ValidateLookMembers(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Members (ID)' actions = [colorbleed.api.SelectInvalidAction, diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index bbcd8a56cb..79ed6bbeee 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -46,7 +46,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "colorbleed.animation", "colorbleed.camera", "colorbleed.texture", - "colorbleed.lookdev", + "colorbleed.look", "colorbleed.historyLookdev", "colorbleed.group"] diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 8ce3f0101a..54799160f7 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -62,7 +62,7 @@ class CollectLook(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.lookdev"] + families = ["colorbleed.look"] label = "Collect Look" hosts = ["maya"] diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index f938cdb6d7..4e4ffd9938 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -21,7 +21,7 @@ class ExtractLook(colorbleed.api.Extractor): label = "Extract Look (Maya ASCII + JSON)" hosts = ["maya"] - families = ["colorbleed.lookdev"] + families = ["colorbleed.look"] order = pyblish.api.ExtractorOrder + 0.2 def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 4f62fdd2ac..751b3b8ab1 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -13,7 +13,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Data Contents' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py index dce90dc0df..dc8e7446c9 100644 --- a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py +++ b/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py @@ -17,7 +17,7 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Default Shader Connections' diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py index 1016c972a0..0ccc38e81a 100644 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -104,7 +104,7 @@ class ValidateLookDeformedShapes(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look deformed shapes' actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory] diff --git a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py index 432f6844d6..58f0bb2f23 100644 --- a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py +++ b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py @@ -18,7 +18,7 @@ class ValidateLookIgnoreColorSpace(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look RAW Ignore color space' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index 65ebeda5c3..a42410c123 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -22,7 +22,7 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): order = colorbleed.api.ValidatePipelineOrder label = 'Look members unique' hosts = ['maya'] - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index f2693f6b4c..39b4bd6cb8 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -23,7 +23,7 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder + 0.01 - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look No Default Shaders' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 53fb04fb1e..23d03830c5 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -22,7 +22,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Sets' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py index 497b356a13..af3aa26bbe 100644 --- a/colorbleed/plugins/maya/publish/validate_look_single_shader.py +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -12,7 +12,7 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.lookdev'] + families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index b4a219c2ae..f8b5c4f404 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -17,7 +17,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): label = 'Instance Nodes Have ID' hosts = ['maya'] families = ["colorbleed.model", - "colorbleed.lookdev", + "colorbleed.look", "colorbleed.rig"] actions = [colorbleed.api.SelectInvalidAction, diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index ba896c210c..1aced342c7 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -15,7 +15,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): label = 'Node Ids Related (ID)' hosts = ['maya'] families = ["colorbleed.model", - "colorbleed.lookdev", + "colorbleed.look", "colorbleed.rig"] actions = [colorbleed.api.SelectInvalidAction, diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py index d08775f5b0..5fceecba71 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_unique_node_ids.py @@ -15,7 +15,7 @@ class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): label = 'Non Duplicate Instance Members (ID)' hosts = ['maya'] families = ["colorbleed.model", - "colorbleed.lookdev", + "colorbleed.look", "colorbleed.rig"] actions = [colorbleed.api.SelectInvalidAction, diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index f348deb654..6efbec13e0 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -27,7 +27,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.rig", "colorbleed.animation", "colorbleed.camera", - "colorbleed.lookdev", + "colorbleed.look", "colorbleed.texture", "colorbleed.historyLookdev", "colorbleed.group", From dc37c5b3243e2305dfc4f19e9718199dcd686f43 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 14:58:23 +0200 Subject: [PATCH 0217/1249] added model to pointcache extractor, rename pointcache to alembic --- colorbleed/plugins/maya/publish/extract_pointcache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index f29f7846bf..88252c4d52 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,7 +7,7 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic -class ExtractColorbleedPointcache(colorbleed.api.Extractor): +class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -17,7 +17,8 @@ class ExtractColorbleedPointcache(colorbleed.api.Extractor): label = "Extract Pointcache" hosts = ["maya"] - families = ["colorbleed.pointcache"] + families = ["colorbleed.pointcache", + "colorbleed.model"] def process(self, instance): From 34c14c27d3aecaead6c41b9ba5051626a16cded1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 15:14:25 +0200 Subject: [PATCH 0218/1249] improved name of extractor --- colorbleed/plugins/maya/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 88252c4d52..bd80029010 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -15,7 +15,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): """ - label = "Extract Pointcache" + label = "Extract Pointcache (Alembic)" hosts = ["maya"] families = ["colorbleed.pointcache", "colorbleed.model"] From f2989d0a902cfa6ac1066875dac5028bdb238e6e Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Sep 2017 15:15:50 +0200 Subject: [PATCH 0219/1249] Removed possible large log message --- colorbleed/plugins/maya/publish/extract_animation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index 68ed1763f5..abc9be046f 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -45,8 +45,6 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): self.log.info("Extracting animation..") dirname = self.staging_dir(instance) - self.log.info("nodes: %s" % str(nodes)) - parent_dir = self.staging_dir(instance) filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) From d5cdf2215666569cc716fe60e2ce5642542d556a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 25 Sep 2017 14:16:59 +0200 Subject: [PATCH 0220/1249] added explicit load for AbcImport / Export, moved function to lib, removed unused fuction --- colorbleed/maya/__init__.py | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index d90957b7f5..b586027829 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,5 +1,4 @@ import os -import uuid from maya import cmds @@ -18,6 +17,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "maya", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") +LOAD_AT_START = ["AbcImport", "AbcExport"] # This is a temporary solution with the http.py clash with six.py # Maya has added paths to the PYTHONPATH which are redundant as @@ -73,6 +73,12 @@ def install(): menu.install() + # Add any needed plugins + for plugin in LOAD_AT_START: + if cmds.pluginInfo(plugin, query=True, loaded=True): + continue + cmds.loadPlugin(plugin, quiet=True) + print("Installing callbacks ... ") avalon.on("init", on_init) avalon.on("new", on_new) @@ -90,31 +96,6 @@ def uninstall(): menu.uninstall() -def _set_uuid(asset_id, node): - """Add cbId to `node` - Unless one already exists. - """ - - attr = "{0}.cbId".format(node) - if not cmds.attributeQuery("cbId", node=node, exists=True): - cmds.addAttr(node, longName="cbId", dataType="string") - _, uid = str(uuid.uuid4()).rsplit("-", 1) - cb_uid = "{}:{}".format(asset_id, uid) - - cmds.setAttr(attr, cb_uid, type="string") - - -def _copy_uuid(source, target): - - source_attr = "{0}.cbId".format(source) - target_attr = "{0}.cbId".format(target) - if not cmds.attributeQuery("cbId", node=target, exists=True): - cmds.addAttr(target, longName="cbId", dataType="string") - - attribute_value = cmds.getAttr(source_attr) - cmds.setAttr(target_attr, attribute_value, type="string") - - def on_init(_): avalon.logger.info("Running callback on init..") @@ -149,6 +130,5 @@ def on_save(_): projection={"_id": True}) # generate the ids - for node in nodes: - _set_uuid(str(asset_id["_id"]), node) + lib.set_id(str(asset_id["_id"]), node) From a73f529d039d92e7f0470d28a7dd94c94df3bcdd Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 25 Sep 2017 14:17:47 +0200 Subject: [PATCH 0221/1249] Added set_id and remove_id --- colorbleed/maya/lib.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 3d231d050a..b22e5a3f5c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2,6 +2,8 @@ import re import os +import uuid + import bson import json import logging @@ -677,6 +679,30 @@ def get_id(node): return cmds.getAttr("{}.cbId".format(node)) +def set_id(asset_id, node): + """Add cbId to `node` unless one already exists. + + Args: + asset_id (str): the unique asset code from the database + node (str): the node to add the "cbId" on + + Returns: + None + """ + + attr = "{0}.cbId".format(node) + if not cmds.attributeQuery("cbId", node=node, exists=True): + cmds.addAttr(node, longName="cbId", dataType="string") + _, uid = str(uuid.uuid4()).rsplit("-", 1) + cb_uid = "{}:{}".format(asset_id, uid) + cmds.setAttr(attr, cb_uid, type="string") + + +def remove_id(node): + if cmds.attributeQuery("cbId", node=node, exists=True): + cmds.deleteAttr("{}.cbId".format(node)) + + def get_representation_file(representation, template=TEMPLATE): """ Rebuild the filepath of the representation's context From a2a10970d68c5d1f5ff8426c639de5597803d842 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 25 Sep 2017 14:18:16 +0200 Subject: [PATCH 0222/1249] added script for asset_id:uuid control --- colorbleed/maya/menu.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index d9728c2245..eb26bb0f08 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1545,6 +1545,14 @@ "title": "Remove User Defined Attributes", "tooltip": "" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\update_asset_id.py", + "sourcetype": "file", + "tags":["cleanup", "update", "database", "asset", "id"], + "title": "Update Asset ID", + "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", @@ -1744,13 +1752,13 @@ }, { "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\", + "command": "$COLORBLEED_SCRIPTS\\others\\instanceLeafSmartTransform.py", "sourcetype": "file", "tags": ["others", "instance","leaf", "smart", "transform"], "title": "Instance Leaf Smart Transform", "tooltip": "" }, - { + { "type": "action", "command": "$COLORBLEED_SCRIPTS\\others\\instanceSmartTransform.py", "sourcetype": "file", From 1c3ca17654af4f7be160c3669a9c0ca61305c8ac Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 25 Sep 2017 15:04:25 +0200 Subject: [PATCH 0223/1249] fixed out of sync --- colorbleed/maya/menu.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 236879e061..9cc5b5911d 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -47,7 +47,6 @@ def deferred(): def uninstall(): menu = _get_menu() - if menu: log.info("Attempting to uninstall ..") @@ -60,6 +59,10 @@ def uninstall(): def install(): + if cmds.about(batch=True): + print("Skipping colorbleed.menu initialization in batch mode..") + return + uninstall() # Allow time for uninstallation to finish. cmds.evalDeferred(deferred) From 63aa1e834ad54536834e0beecb7ce59b0ac01f90 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 25 Sep 2017 15:04:53 +0200 Subject: [PATCH 0224/1249] Added print for deadline --- colorbleed/maya/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index b586027829..9cdf6e7d14 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -75,6 +75,7 @@ def install(): # Add any needed plugins for plugin in LOAD_AT_START: + print "Loading %s" % plugin if cmds.pluginInfo(plugin, query=True, loaded=True): continue cmds.loadPlugin(plugin, quiet=True) From dd197fee9acb93d8b89fd5bbe0ad95600ce8b013 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 26 Sep 2017 16:50:17 +0200 Subject: [PATCH 0225/1249] added logging, force load specific plugins at install --- colorbleed/maya/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 9cdf6e7d14..a1afac47ac 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,4 +1,5 @@ import os +import logging from maya import cmds @@ -9,6 +10,8 @@ from pyblish import api as pyblish from . import menu from . import lib +log = logging.getLogger("colorbleed.maya") + PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -17,7 +20,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "maya", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") -LOAD_AT_START = ["AbcImport", "AbcExport"] +LOAD_AT_START = ["AbcImport", "AbcExport", "mtoa"] # This is a temporary solution with the http.py clash with six.py # Maya has added paths to the PYTHONPATH which are redundant as @@ -75,12 +78,12 @@ def install(): # Add any needed plugins for plugin in LOAD_AT_START: - print "Loading %s" % plugin + log.info("Loading %s" % plugin) if cmds.pluginInfo(plugin, query=True, loaded=True): continue cmds.loadPlugin(plugin, quiet=True) - print("Installing callbacks ... ") + log.info("Installing callbacks ... ") avalon.on("init", on_init) avalon.on("new", on_new) avalon.on("save", on_save) From 0b1ff1aaba0120e782ac3fa47017b5a305f08fd2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 27 Sep 2017 09:59:48 +0200 Subject: [PATCH 0226/1249] Move `step` argument forward in the alembic job argument --- colorbleed/plugins/maya/publish/extract_camera_alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index 16d6d94e76..2b18ced96f 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -56,6 +56,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): job_str += ' -attrPrefix cb' job_str += ' -frameRange {0} {1} '.format(framerange[0] - handles, framerange[1] + handles) + job_str += ' -step {0} '.format(step) if bake_to_worldspace: transform = cmds.listRelatives(camera, @@ -64,7 +65,6 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): job_str += ' -worldSpace -root {0}'.format(transform) job_str += ' -file "{0}"'.format(path) - job_str += ' -step {0} '.format(step) with context.evaluation("off"): with context.no_refresh(): From 089947972a12e73cec0c349cb4583e7d9ff4d270 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:12:47 +0200 Subject: [PATCH 0227/1249] Re-enable validate mesh non-zero edge validator --- ...idate_mesh_non_zero_edge.py => validate_mesh_non_zero_edge.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/maya/publish/{_validate_mesh_non_zero_edge.py => validate_mesh_non_zero_edge.py} (100%) diff --git a/colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py similarity index 100% rename from colorbleed/plugins/maya/publish/_validate_mesh_non_zero_edge.py rename to colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py From 5b02d5ff359a38cb360da6042e0863e5fe945d3f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:14:38 +0200 Subject: [PATCH 0228/1249] Remove backwards compatibility (we never used this older naming convention) --- colorbleed/plugins/maya/publish/collect_maya_workspace.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_maya_workspace.py b/colorbleed/plugins/maya/publish/collect_maya_workspace.py index 3feff69362..1250ea438f 100644 --- a/colorbleed/plugins/maya/publish/collect_maya_workspace.py +++ b/colorbleed/plugins/maya/publish/collect_maya_workspace.py @@ -25,6 +25,3 @@ class CollectMayaWorkspace(pyblish.api.ContextPlugin): normalised = os.path.normpath(workspace) context.set_data('workspaceDir', value=normalised) - - # For backwards compatibility - context.set_data('workspace_dir', value=normalised) From b19c827a23c35e761e5e74dbf0bdfd43a945ccec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:20:26 +0200 Subject: [PATCH 0229/1249] Implement validate step size for camera, pointcache and animation families --- .../plugins/maya/publish/validate_step_size.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_step_size.py b/colorbleed/plugins/maya/publish/validate_step_size.py index 097bb1399d..7267d99a35 100644 --- a/colorbleed/plugins/maya/publish/validate_step_size.py +++ b/colorbleed/plugins/maya/publish/validate_step_size.py @@ -11,7 +11,9 @@ class ValidateStepSize(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder label = 'Step size' - families = ['colorbleed.layout'] + families = ['colorbleed.camera', + 'colorbleed.pointcache', + 'colorbleed.animation'] actions = [colorbleed.api.SelectInvalidAction] MIN = 0.01 @@ -20,20 +22,17 @@ class ValidateStepSize(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - objset = instance.data['setMembers'] - - invalid = set() + objset = instance.data['name'] step = instance.data.get("step", 1.0) + if step < cls.MIN or step > cls.MAX: cls.log.warning("Step size is outside of valid range: {0} " "(valid: {1} to {2})".format(step, cls.MIN, cls.MAX)) - invalid.add(objset) + return objset - invalid = list(invalid) - - return invalid + return [] def process(self, instance): From dc43bf8f6cfa5c64addb36cc23a24332f7371604 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:20:49 +0200 Subject: [PATCH 0230/1249] Fix typo in family name `avalon` > `colorbleed` --- colorbleed/plugins/maya/publish/validate_single_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index 566a332106..ad394f159e 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -12,7 +12,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.rig', 'colorbleed.layout', 'avalon.animation'] + families = ['colorbleed.rig', 'colorbleed.layout', 'colorbleed.animation'] label = 'Single Assembly' def process(self, instance): From cb0b2611a285d12a4e127961093a81a061a9f6cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:23:54 +0200 Subject: [PATCH 0231/1249] Correct the family name from `colorbleed.animation` to `colorbleed.camera` --- colorbleed/plugins/maya/publish/validate_no_default_camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_no_default_camera.py b/colorbleed/plugins/maya/publish/validate_no_default_camera.py index cce4433836..6bdb830f0b 100644 --- a/colorbleed/plugins/maya/publish/validate_no_default_camera.py +++ b/colorbleed/plugins/maya/publish/validate_no_default_camera.py @@ -14,7 +14,7 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.animation'] + families = ['colorbleed.camera'] version = (0, 1, 0) label = "No Default Cameras" actions = [colorbleed.api.SelectInvalidAction] From 46d007c59a310e8530b4be05a81289ef2e1c8a12 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:25:32 +0200 Subject: [PATCH 0232/1249] Remove `validate_namespace_empty` because it doesn't matter if a work model scene has empty namespaces at all, they will never be extracted anyway since it doesn't contain any meshes anyway. --- .../maya/publish/validate_namespace_empty.py | 39 ------------------- 1 file changed, 39 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_namespace_empty.py diff --git a/colorbleed/plugins/maya/publish/validate_namespace_empty.py b/colorbleed/plugins/maya/publish/validate_namespace_empty.py deleted file mode 100644 index 7f413c2fd7..0000000000 --- a/colorbleed/plugins/maya/publish/validate_namespace_empty.py +++ /dev/null @@ -1,39 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class ValidateNamespaceEmpty(pyblish.api.ContextPlugin): - """Validate there are no empty namespaces in the scene. - - This is a scene wide validation that filters out "UI" and "shared" - namespaces that exist by default in Maya and are mostly hidden. - - A namespace that has other namespaces in it is *not* considered empty. - Only those that have no children namespaces or nodes is considered empty. - - """ - - order = colorbleed.api.ValidateSceneOrder - hosts = ["maya"] - families = ["colorbleed.model"] - label = "No Empty Namespaces" - - def process(self, context): - """Process the Context""" - all_namespaces = cmds.namespaceInfo(":", - listOnlyNamespaces=True, - recurse=True) - non_internal_namespaces = [ns for ns in all_namespaces - if ns not in ["UI", "shared"]] - - invalid = [] - for namespace in non_internal_namespaces: - namespace_content = cmds.namespaceInfo(namespace, - listNamespace=True, - recurse=True) - if not namespace_content: - invalid.append(namespace) - - assert not invalid, ("Empty namespaces found: {0}".format(invalid)) From 6fe427e631747cb3ad8d5a9b32442e52262bef73 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:28:14 +0200 Subject: [PATCH 0233/1249] Correct the docstring, it's not actually checking upstream in hierarchy. --- colorbleed/plugins/maya/publish/validate_camera_contents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_camera_contents.py b/colorbleed/plugins/maya/publish/validate_camera_contents.py index 47ed978296..09f5d5392b 100644 --- a/colorbleed/plugins/maya/publish/validate_camera_contents.py +++ b/colorbleed/plugins/maya/publish/validate_camera_contents.py @@ -9,8 +9,8 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): A Camera instance may only hold a SINGLE camera's transform, nothing else. - It may hold a "locator" as shape, but different shapes are in the up- - and down + It may hold a "locator" as shape, but different shapes are down the + hierarchy. """ From 696abd688eb428590f4821a1fc876e4c82a5d295 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:29:25 +0200 Subject: [PATCH 0234/1249] Add validate_frame_range to `colorbleed.camera` and `colorbleed.pointcache` --- colorbleed/plugins/maya/publish/validate_frame_range.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index 0ceb67d398..c5a88d5f22 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -18,6 +18,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): label = "Validate Frame Range" order = colorbleed.api.ValidateContentsOrder families = ["colorbleed.animation", + "colorbleed.pointcache", + "colorbleed.camera", "colorbleed.render"] def process(self, instance): From 4158c409a3d584424eb432386f37cf8eea017780 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 28 Sep 2017 14:29:30 +0200 Subject: [PATCH 0235/1249] removed compatibility fix, moves to avalon-core --- colorbleed/maya/__init__.py | 40 ------------------------------------- 1 file changed, 40 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index a1afac47ac..30ff3630e6 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -28,46 +28,6 @@ LOAD_AT_START = ["AbcImport", "AbcExport", "mtoa"] # "/some/awesome/package/core/" which should be "/some/awesome/package" -def _remove_from_paths(paths, keyword, stitch=False): - """Remove any paths which contain the given keyword - - >>> paths = ["foo\\foo\\foo.py", "foo\\foobar.py", "\\bar\\bar\\foo"] - >>> _remove_from_paths(paths, keyword="bar") - ["foo\\foo\\foo.py"] - - >>> paths = ["foo\\bar\\foobar.py", "foo\\foobar.py", "\\banana\\pie\\delicious"] - >>> _remove_from_paths(paths, keyword="pie", stitch=True) - "foo\\bar\\foobar.py;foo\\foobar.py" - - Args: - paths(list) : a list of file paths - keyword(str) : the word to check for - stitch(bool) : recreate a full string for PYTHONPATH - - Returns: - str - Only when stitch is set to True does the function return a string - """ - - paths = [path for path in paths if keyword not in path] - if stitch: - return os.pathsep.join(paths) - - -def remove_googleapiclient(): - """Remove any paths which contain `googleclientapi`""" - - keyword = "googleapiclient" - # remove from sys.path - # _remove_from_paths(sys.path, keyword) - - # reconstruct pythonpaths - pythonpaths = os.environ["PYTHONPATH"].split(os.pathsep) - result = _remove_from_paths(pythonpaths, keyword, stitch=True) - - os.environ["PYTHONPATH"] = result - - def install(): pyblish.register_plugin_path(PUBLISH_PATH) From 95f199209b26d5746ec66ecca4e106bc1e94827f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:30:38 +0200 Subject: [PATCH 0236/1249] Fix typo --- .../plugins/maya/publish/validate_instance_has_members.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_instance_has_members.py b/colorbleed/plugins/maya/publish/validate_instance_has_members.py index b5787172c0..d209505378 100644 --- a/colorbleed/plugins/maya/publish/validate_instance_has_members.py +++ b/colorbleed/plugins/maya/publish/validate_instance_has_members.py @@ -15,7 +15,7 @@ class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): invalid = list() if not instance.data["setMembers"]: - objectset_name = instance.data['subset'] + objectset_name = instance.data['name'] invalid.append(objectset_name) return invalid From 10075f83d37e517523ba45ed9e6a663dfb73e8e4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:32:07 +0200 Subject: [PATCH 0237/1249] Refactor class name (mindbender > colorbleed) --- colorbleed/plugins/publish/collect_comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_comment.py b/colorbleed/plugins/publish/collect_comment.py index 416e505e17..a246b7eaba 100644 --- a/colorbleed/plugins/publish/collect_comment.py +++ b/colorbleed/plugins/publish/collect_comment.py @@ -1,7 +1,7 @@ import pyblish.api -class CollectMindbenderComment(pyblish.api.ContextPlugin): +class CollectColorbleedComment(pyblish.api.ContextPlugin): """This plug-ins displays the comment dialog box per default""" label = "Collect Comment" From dfd42bfaf9d3194243960b9420e85aa08fc5b531 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:33:47 +0200 Subject: [PATCH 0238/1249] Remove non-existing families and sort the list alphabetically --- colorbleed/plugins/publish/integrate.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 3e947b6a06..3cd484028d 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -23,17 +23,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder - families = ["colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.pointcache", + families = ["colorbleed.animation", "colorbleed.camera", - "colorbleed.look", - "colorbleed.texture", - "colorbleed.historyLookdev", - "colorbleed.group", "colorbleed.imagesequence", - "colorbleed.mayaAscii"] + "colorbleed.look", + "colorbleed.mayaAscii" + "colorbleed.model", + "colorbleed.pointcache", + "colorbleed.rig"] def process(self, instance): From a552d452aae8cb35576567c3a0c21f11307a0a6d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:35:41 +0200 Subject: [PATCH 0239/1249] Refactor label to be more generic instead of `mindbender` --- colorbleed/plugins/publish/collect_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_time.py b/colorbleed/plugins/publish/collect_time.py index 9f1a7da07f..d4fa658425 100644 --- a/colorbleed/plugins/publish/collect_time.py +++ b/colorbleed/plugins/publish/collect_time.py @@ -5,7 +5,7 @@ from avalon import api class CollectMindbenderTime(pyblish.api.ContextPlugin): """Store global time at the time of publish""" - label = "Collect Mindbender Time" + label = "Collect Current Time" order = pyblish.api.CollectorOrder def process(self, context): From 19cc3af18ecf6b546c2cb592ea5e67c60bcbe649 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:38:46 +0200 Subject: [PATCH 0240/1249] For now assume we'll always validate scene units no matter what family when host == "maya" --- colorbleed/plugins/maya/publish/validate_maya_units.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 383562cef2..81dfceb19f 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -9,10 +9,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): order = colorbleed.api.ValidateSceneOrder label = "Maya Units" - families = ["colorbleed.rig", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.curves"] + hosts = ['maya'] actions = [colorbleed.api.RepairContextAction] def process(self, context): From abe2dcc253a4f3e7b13918ad45193de124ce2c0c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:40:39 +0200 Subject: [PATCH 0241/1249] Remove non-stable gpuCache loader for models (for stable release) --- colorbleed/plugins/maya/load/load_model.py | 44 +--------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 91a67b0fd6..15637c9e81 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -31,46 +31,4 @@ class ModelLoader(api.Loader): groupReference=True, groupName="{}:{}".format(namespace, name)) - self[:] = nodes - - -class ModelGPUCacheLoader(api.Loader): - """Import a GPU Cache""" - - families = ["colorbleed.model"] - representations = ["abc"] - - label = "Import GPU Cache" - order = -1 - icon = "download" - - def process(self, name, namespace, context, data): - - import maya.cmds as cmds - # from avalon import maya - - # todo: This will likely not be entirely safe with "containerize" - # also this cannot work in the manager because it only works - # on references at the moment! - # especially in cases of duplicating the gpu cache node this will - # mess up the "containered" workflow in the avalon core for maya - print("WARNING: Importing gpuCaches isn't fully tested yet") - - path = self.fname - - cmds.loadPlugin("gpuCache", quiet=True) - - # Create transform with shape - node_name = "{0}Shape".format(name) - transform = cmds.createNode("transform", name=name) - cache = cmds.createNode("gpuCache", parent=transform, name=node_name) - - # Set the cache filepath - cmds.setAttr('{}.cacheFileName'.format(cache), path, type="string") - cmds.setAttr('{}.cacheGeomPath'.format(cache), "|", type="string") # root - - # Select the transform - cmds.select(transform, r=1) - - # Store the created nodes - self[:] = [transform, cache] + self[:] = nodes \ No newline at end of file From 4db7e2f2bba97fdf7efbd109ae50bd3552745be8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:41:29 +0200 Subject: [PATCH 0242/1249] Remove untested/unstable CurvesLoader --- .../plugins/maya/load/load_animation.py | 114 +----------------- 1 file changed, 1 insertion(+), 113 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index b0679665fe..b9f187655d 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -46,116 +46,4 @@ class AbcLoader(api.Loader): returnNewNodes=True) # load colorbleed ID attribute - self[:] = nodes - - -class CurvesLoader(api.Loader): - """Specific loader of Curves for the avalon.animation family""" - - families = ["colorbleed.animation"] - representations = ["curves"] - - label = "Import curves" - order = -1 - icon = "question" - - def process(self, name, namespace, context, data): - - cmds.loadPlugin("atomImportExport.mll", quiet=True) - - rig = context["representation"]["dependencies"][0] - container = maya.load(rig, - name=name, - namespace=namespace, - # Skip creation of Animation instance - post_process=False) - - try: - control_set = next( - node for node in cmds.sets(container, query=True) - if node.endswith("controls_SET") - ) - except StopIteration: - raise TypeError("%s is missing controls_SET") - - options = ";".join([ - "", - "", - "targetTime=3", - "option=insert", - "match=hierarchy", - "selected=selectedOnly", - "search=", - "replace=", - "prefix=", - "suffix=", - "mapFile=", - ]) - - with maya.maintained_selection(): - cmds.select(control_set, - replace=True, - # Support controllers being embedded in - # additional selection sets. - noExpand=False) - - nodes = cmds.file(self.fname, - i=True, - type="atomImport", - renameAll=True, - namespace=namespace, - options=options, - returnNewNodes=True) - - self[:] = nodes + cmds.sets(container, query=True) + [container] - - if data.get("post_process", True): - self._post_process(name, namespace, context, data) - - def _post_process(self, name, namespace, context, data): - - from avalon import maya, io - - # Task-dependent post-process - if os.getenv("AVALON_TASK") != "animate": - return self.log.info( - "No animation instance created due to task != animate" - ) - - # Find associated rig to these curves - try: - dependency = context["representation"]["dependencies"][0] - except (KeyError, IndexError): - return self.log.warning("No dependencies found for %s" % name) - - dependency = io.find_one({"_id": io.ObjectId(dependency)}) - _, _, dependency, _ = io.parenthood(dependency) - - # TODO(marcus): We are hardcoding the name "out_SET" here. - # Better register this keyword, so that it can be used - # elsewhere, such as in the Integrator plug-in, - # without duplication. - output = next((node for node in self - if node.endswith("out_SET")), None) - controls = next((node for node in self - if node.endswith("controls_SET")), None) - - assert output, "No out_SET in rig, this is a bug." - assert controls, "No controls_SET in rig, this is a bug." - - with maya.maintained_selection(): - cmds.select([output, controls], noExpand=True) - - dependencies = [context["representation"]["_id"]] - dependencies = " ".join(str(d) for d in dependencies) - name = "anim{}_".format(dependency["name"].title()) - - # TODO(marcus): Hardcoding the family here, better separate this. - family = [f for f in self.families if f.endswith("animation")] - assert len(family) == 1, ("None or multiple animation " - "families found") - family = family[0] - maya.create(name=maya.unique_name(name, suffix="_SET"), - family=family, - options={"useSelection": True}, - data={"dependencies": dependencies}) + self[:] = nodes \ No newline at end of file From df287f411c22cc830cdb97805412469975247889 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:43:16 +0200 Subject: [PATCH 0243/1249] Add to docstring why this workaround plug-in is implemented --- colorbleed/plugins/maya/publish/collect_model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_model.py b/colorbleed/plugins/maya/publish/collect_model.py index c8324a8297..cd780acc7f 100644 --- a/colorbleed/plugins/maya/publish/collect_model.py +++ b/colorbleed/plugins/maya/publish/collect_model.py @@ -7,6 +7,11 @@ class CollectModelData(pyblish.api.InstancePlugin): """Collect model data Ensures always only a single frame is extracted (current frame). + + Note: + This is a workaround so that the `colorbleed.model` family can use the + same pointcache extractor implementation as animation and pointcaches. + This always enforces the "current" frame to be published. """ From 36166f53320f65b4bb5e523f75df15efe1baf95b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 14:44:30 +0200 Subject: [PATCH 0244/1249] Massive cleanup removing untested/unstable families or plug-ins --- .../plugins/maya/create/_colorbleed_groom.py | 9 - .../maya/create/_colorbleed_instancer.py | 9 - .../plugins/maya/create/_colorbleed_layout.py | 37 --- .../maya/create/_colorbleed_texture.py | 9 - .../maya/create/_colorbleed_yetifur.py | 22 -- .../maya/create/colorbleed_setdress.py | 9 - .../plugins/maya/load/load_historylook.py | 28 -- .../maya/publish/_collect_alembic_attrs.py | 24 -- colorbleed/plugins/maya/publish/_debug.py | 15 - .../maya/publish/_extract_camera_raw.py | 51 ---- .../maya/publish/_extract_layout_mayaAscii.py | 43 --- .../plugins/maya/publish/_extract_metadata.py | 21 -- .../plugins/maya/publish/_integrate_files.py | 47 --- .../publish/_validate_filename_convention.py | 46 --- .../maya/publish/_validate_layout_content.py | 87 ------ .../maya/publish/_validate_layout_node_ids.py | 43 --- .../_validate_layout_parent_no_transforms.py | 71 ----- .../_validate_layout_shape_node_ids.py | 149 --------- .../_validate_layout_unique_node_ids.py | 63 ---- .../_validate_look_members_node_ids.py | 39 --- .../maya/publish/_validate_rig_node_ids.py | 62 ---- .../_validate_rig_pointcache_node_ids.py | 85 ------ ...idate_rig_pointcache_shape_render_stats.py | 86 ------ .../maya/publish/collect_instancer_caches.py | 56 ---- .../maya/publish/collect_instancer_history.py | 63 ---- .../maya/publish/collect_particles_history.py | 47 --- .../plugins/maya/publish/collect_setdress.py | 13 - .../plugins/maya/publish/collect_textures.py | 284 ------------------ .../plugins/maya/publish/extract_instancer.py | 157 ---------- .../plugins/maya/publish/extract_particles.py | 48 --- .../plugins/maya/publish/extract_textures.py | 41 --- .../maya/publish/extract_yeti_nodes.py | 65 ---- .../publish/validate_gpuCache_not_empty.py | 87 ------ .../validate_mesh_no_negative_scale.py | 2 +- .../maya/publish/validate_no_unknown_nodes.py | 2 +- .../maya/publish/validate_nodes_visible.py | 43 --- .../validate_references_no_failed_edits.py | 112 ------- .../maya/publish/validate_single_assembly.py | 2 +- .../publish/validate_yeti_cache_frames.py | 90 ------ .../validate_yeti_cache_non_publish.py | 43 --- .../validate_yeti_cache_unique_filenames.py | 81 ----- .../plugins/publish/validate_deadline_done.py | 63 ---- 42 files changed, 3 insertions(+), 2351 deletions(-) delete mode 100644 colorbleed/plugins/maya/create/_colorbleed_groom.py delete mode 100644 colorbleed/plugins/maya/create/_colorbleed_instancer.py delete mode 100644 colorbleed/plugins/maya/create/_colorbleed_layout.py delete mode 100644 colorbleed/plugins/maya/create/_colorbleed_texture.py delete mode 100644 colorbleed/plugins/maya/create/_colorbleed_yetifur.py delete mode 100644 colorbleed/plugins/maya/create/colorbleed_setdress.py delete mode 100644 colorbleed/plugins/maya/load/load_historylook.py delete mode 100644 colorbleed/plugins/maya/publish/_collect_alembic_attrs.py delete mode 100644 colorbleed/plugins/maya/publish/_debug.py delete mode 100644 colorbleed/plugins/maya/publish/_extract_camera_raw.py delete mode 100644 colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py delete mode 100644 colorbleed/plugins/maya/publish/_extract_metadata.py delete mode 100644 colorbleed/plugins/maya/publish/_integrate_files.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_filename_convention.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_layout_content.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_layout_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_rig_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py delete mode 100644 colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py delete mode 100644 colorbleed/plugins/maya/publish/collect_instancer_caches.py delete mode 100644 colorbleed/plugins/maya/publish/collect_instancer_history.py delete mode 100644 colorbleed/plugins/maya/publish/collect_particles_history.py delete mode 100644 colorbleed/plugins/maya/publish/collect_setdress.py delete mode 100644 colorbleed/plugins/maya/publish/collect_textures.py delete mode 100644 colorbleed/plugins/maya/publish/extract_instancer.py delete mode 100644 colorbleed/plugins/maya/publish/extract_particles.py delete mode 100644 colorbleed/plugins/maya/publish/extract_textures.py delete mode 100644 colorbleed/plugins/maya/publish/extract_yeti_nodes.py delete mode 100644 colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py delete mode 100644 colorbleed/plugins/maya/publish/validate_nodes_visible.py delete mode 100644 colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py delete mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py delete mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py delete mode 100644 colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py delete mode 100644 colorbleed/plugins/publish/validate_deadline_done.py diff --git a/colorbleed/plugins/maya/create/_colorbleed_groom.py b/colorbleed/plugins/maya/create/_colorbleed_groom.py deleted file mode 100644 index 6f8d0f72f7..0000000000 --- a/colorbleed/plugins/maya/create/_colorbleed_groom.py +++ /dev/null @@ -1,9 +0,0 @@ -import avalon.maya - - -class CreateGroom(avalon.maya.Creator): - """Hair / fur definition for an asset""" - - name = "groomDefault" - label = "Groom" - family = "colorbleed.groom" diff --git a/colorbleed/plugins/maya/create/_colorbleed_instancer.py b/colorbleed/plugins/maya/create/_colorbleed_instancer.py deleted file mode 100644 index 4721253752..0000000000 --- a/colorbleed/plugins/maya/create/_colorbleed_instancer.py +++ /dev/null @@ -1,9 +0,0 @@ -import avalon.maya - - -class CreateInstance(avalon.maya.Creator): - """Maya instancer using cached particles""" - - name = "instanceDefault" - label = "Instance" - family = "colorbleed.instance" diff --git a/colorbleed/plugins/maya/create/_colorbleed_layout.py b/colorbleed/plugins/maya/create/_colorbleed_layout.py deleted file mode 100644 index 2731ee8542..0000000000 --- a/colorbleed/plugins/maya/create/_colorbleed_layout.py +++ /dev/null @@ -1,37 +0,0 @@ -from collections import OrderedDict -import avalon.maya -from colorbleed.maya import lib - - -class CreateLayout(avalon.maya.Creator): - """The layout of a episode / sequence / shot """ - - name = "layoutDefault" - label = "Layout" - family = "colorbleed.layout" - - def __init__(self, *args, **kwargs): - super(CreateLayout, self).__init__(*args, **kwargs) - - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) - - # get basic animation data : start / end / handles / steps - for key, value in lib.collect_animation_data().items(): - data[key] = value - - # Write vertex colors with the geometry. - data["writeColorSets"] = False - - # Write GPU cache as placeholder cube in stead of full data - data["placeholder"] = False - - # Include only renderable visible shapes. - # Skips locators and empty transforms - data["renderableOnly"] = False - - # Include only nodes that are visible at least once during the - # frame range. - data["visibleOnly"] = False - - self.data = data diff --git a/colorbleed/plugins/maya/create/_colorbleed_texture.py b/colorbleed/plugins/maya/create/_colorbleed_texture.py deleted file mode 100644 index 1080f1c6a1..0000000000 --- a/colorbleed/plugins/maya/create/_colorbleed_texture.py +++ /dev/null @@ -1,9 +0,0 @@ -import avalon.maya - - -class CreateTexture(avalon.maya.Creator): - """Polygonal geometry for animation""" - - name = "texturesDefault" - label = "Textures" - family = "colorbleed.texture" \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/_colorbleed_yetifur.py b/colorbleed/plugins/maya/create/_colorbleed_yetifur.py deleted file mode 100644 index ffd0d2c31d..0000000000 --- a/colorbleed/plugins/maya/create/_colorbleed_yetifur.py +++ /dev/null @@ -1,22 +0,0 @@ -from collections import OrderedDict -import avalon.maya -from colorbleed.maya import lib - - -class CreateYetiFur(avalon.maya.Creator): - """Cached yeti fur extraction""" - - name = "yetiFur" - label = "Yeti Fur" - family = "colorbleed.yetifur" - - def __init__(self, *args, **kwargs): - super(CreateYetiFur, self).__init__(*args, **kwargs) - - # get scene values as defaults - data = OrderedDict(**self.data) - animation_data = lib.collect_animation_data() - for key, value in animation_data.items(): - data[key] = value - - self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_setdress.py b/colorbleed/plugins/maya/create/colorbleed_setdress.py deleted file mode 100644 index 45f3ffcf84..0000000000 --- a/colorbleed/plugins/maya/create/colorbleed_setdress.py +++ /dev/null @@ -1,9 +0,0 @@ -import avalon.maya - - -class CreateSetdress(avalon.maya.Creator): - """Skeleton and controls for manipulation of the geometry""" - - name = "setDress" - label = "Setdress" - family = "colorbleed.setdress" diff --git a/colorbleed/plugins/maya/load/load_historylook.py b/colorbleed/plugins/maya/load/load_historylook.py deleted file mode 100644 index fb18c561c0..0000000000 --- a/colorbleed/plugins/maya/load/load_historylook.py +++ /dev/null @@ -1,28 +0,0 @@ -from maya import cmds -from avalon import api - - -class HistoryLookLoader(api.Loader): - """Specific loader for lookdev""" - - families = ["colorbleed.historyLookdev"] - representations = ["ma"] - - label = "Reference look history" - order = -10 - icon = "code-fork" - color = "orange" - - def process(self, name, namespace, context, data): - from avalon import maya - with maya.maintained_selection(): - nodes = cmds.file( - self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName=namespace + ":" + name - ) - - self[:] = nodes diff --git a/colorbleed/plugins/maya/publish/_collect_alembic_attrs.py b/colorbleed/plugins/maya/publish/_collect_alembic_attrs.py deleted file mode 100644 index c16c75a5bb..0000000000 --- a/colorbleed/plugins/maya/publish/_collect_alembic_attrs.py +++ /dev/null @@ -1,24 +0,0 @@ -import pyblish.api - - -class CollectAlembicCBAttrs(pyblish.api.InstancePlugin): - """Collects settings for the Alembic extractor""" - - order = pyblish.api.CollectorOrder + 0.499 - families = ['colorbleed.model', 'colorbleed.pointcache'] - label = "Alembic Colorbleed Attrs" - - def process(self, instance): - - attrPrefix = instance.data.get("attrPrefix", []) - attrPrefix.append("cb") - instance.data['attrPrefix'] = attrPrefix - - # Ensure visibility keys are written - instance.data['writeVisibility'] = True - - # Write creases - instance.data['writeCreases'] = True - - # Ensure UVs are written - instance.data['uvWrite'] = True diff --git a/colorbleed/plugins/maya/publish/_debug.py b/colorbleed/plugins/maya/publish/_debug.py deleted file mode 100644 index ef40126be1..0000000000 --- a/colorbleed/plugins/maya/publish/_debug.py +++ /dev/null @@ -1,15 +0,0 @@ -import pyblish.api - - -class DebugPlugin(pyblish.api.InstancePlugin): - - label = "Debug" - order = pyblish.api.IntegratorOrder - 0.4 - - def process(self, instance): - - import pprint - - self.log("\n\n----------------------") - self.log("Instance") - pprint.pprint(instance) diff --git a/colorbleed/plugins/maya/publish/_extract_camera_raw.py b/colorbleed/plugins/maya/publish/_extract_camera_raw.py deleted file mode 100644 index c1c4eb3ba7..0000000000 --- a/colorbleed/plugins/maya/publish/_extract_camera_raw.py +++ /dev/null @@ -1,51 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - - -class ExtractCameraRaw(colorbleed.api.Extractor): - """Extract as Maya Ascii - - Includes constraints and channels - - """ - - label = "Camera Raw (Maya Ascii)" - hosts = ["maya"] - families = ["colorbleed.camera"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.raw.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - # get cameras - cameras = cmds.ls(instance.data['setMembers'], leaf=True, - shapes=True, dag=True, type='camera') - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - cmds.select(cameras, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=True, - shader=False, - expressions=False) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].append(filename) - - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py b/colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py deleted file mode 100644 index e4d45e1194..0000000000 --- a/colorbleed/plugins/maya/publish/_extract_layout_mayaAscii.py +++ /dev/null @@ -1,43 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - - -class ExtractLayoutMayaAscii(colorbleed.api.Extractor): - """Extract as Maya Ascii""" - - label = "Layout (Maya ASCII)" - hosts = ["maya"] - families = ["colorbleed.layout"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - cmds.select(instance, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=True, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].append(filename) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/_extract_metadata.py b/colorbleed/plugins/maya/publish/_extract_metadata.py deleted file mode 100644 index 6c5ff8cf61..0000000000 --- a/colorbleed/plugins/maya/publish/_extract_metadata.py +++ /dev/null @@ -1,21 +0,0 @@ -import os -import json -import colorbleed.api - - -class ExtractMetadata(colorbleed.api.Extractor): - """Extract origin metadata from scene""" - - label = "Metadata" - - def process(self, instance): - - temp_dir = self.staging_dir(instance) - temp_file = os.path.join(temp_dir, "metadata.meta") - - metadata = instance.data("metadata") - self.log.info("Extracting %s" % metadata) - with open(temp_file, "w") as f: - json.dump(metadata, f, indent=2, sort_keys=True) - - self.log.info("Written to %s" % temp_file) diff --git a/colorbleed/plugins/maya/publish/_integrate_files.py b/colorbleed/plugins/maya/publish/_integrate_files.py deleted file mode 100644 index 65105797cc..0000000000 --- a/colorbleed/plugins/maya/publish/_integrate_files.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import shutil -import pyblish.api - -import colorbleed.api - - -class IntegrateFiles(colorbleed.api.Integrator): - """Integrate Files - - Copies the transfer queue to the destinations. - - """ - - order = pyblish.api.IntegratorOrder + 0.1 - label = "Transfer Files" - - def process(self, instance): - """Copy textures from srcPath to destPath - - The files should be stored in the "integrateFiles" data on the instance. Each item in the - list should be a dictionary with 'srcPath' and 'destPath' key values. - - - srcPath: Source path (must be absolute!) - - destPath: Destination path (can be relative) - - """ - super(IntegrateFiles, self).process(instance) - - # Get unique texture transfers - # (since different nodes might load same texture) - transfers = instance.data.get("transfers", []) - - for src, dest in transfers: - - self.log.info("Copying: {0} -> {1}".format(src, dest)) - - # Source is destination - if os.path.normpath(dest) == os.path.normpath(src): - self.log.info("Skip copy of resource file: {0}".format(src)) - continue - - # Ensure folder exists - folder = os.path.dirname(dest) - if not os.path.exists(folder): - os.makedirs(folder) - shutil.copyfile(src, dest) diff --git a/colorbleed/plugins/maya/publish/_validate_filename_convention.py b/colorbleed/plugins/maya/publish/_validate_filename_convention.py deleted file mode 100644 index a53fd72ab8..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_filename_convention.py +++ /dev/null @@ -1,46 +0,0 @@ -import re -import os - -import maya.cmds as cmds - -import pyblish.api -import colorbleed.api - - -class ValidateFileNameConvention(pyblish.api.InstancePlugin): - - label = "" - families = ["colorbleed.look"] - host = ["maya"] - optional = True - - order = pyblish.api.ValidatorOrder - actions = [colorbleed.api.SelectInvalidAction] - - @staticmethod - def get_invalid(instance): - - invalid = [] - # todo: change pattern to company standard - pattern = re.compile("[a-zA-Z]+_[A-Z]{3}") - - nodes = cmds.ls(instance, type="file") - for node in nodes: - # get texture path - texture = cmds.getAttr("{}.fileTextureName".format(node)) - if not texture: - self.log.error("") - invalid.append(node) - filename = os.path.split(os.path.basename(texture))[0] - match = pattern.match(filename) - if not match: - invalid.append(node) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - self.log.error("Found invalid naming convention. Failed noted :\n" - "%s" % invalid) diff --git a/colorbleed/plugins/maya/publish/_validate_layout_content.py b/colorbleed/plugins/maya/publish/_validate_layout_content.py deleted file mode 100644 index c7859492fc..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_layout_content.py +++ /dev/null @@ -1,87 +0,0 @@ -import pyblish.api -import maya.cmds as cmds -import colorbleed.api -import avalon.maya - -import cb.utils.maya.dag as dag -import cbra.utils.maya.layout as layout - - -class ValidateLayoutContent(pyblish.api.InstancePlugin): - """Validates that layout contains at least a gpuCache or mesh shape node - - Also validates that (at the current frame that this is tested at) at least - a single shape is visible. - - Without any shape nodes the layout would simply cache 'nothing' visually - and would seem redundant. - - Note: Theoretically this validation does disable the possibility to just - cache some "transforms" to be used elsewhere. As such currently the - 'layout' family is only intended to be used for visual shapes. - - """ - - order = colorbleed.api.ValidateContentsOrder - label = 'Layout Content' - families = ['colorbleed.layout'] - - def process(self, instance): - - placeholder = instance.data.get("placeholder", False) - - # Ensure any meshes or gpuCaches in instance - if not cmds.ls(instance, type=("mesh", "gpuCache", "nurbsCurve"), - long=True): - raise RuntimeError( - "Layout has no mesh, gpuCache or nurbsCurve children: " - "{0}".format(instance)) - - # Ensure at least any extract nodes readily available after filtering - with avalon.maya.maintained_selection(): - - nodes = instance.data['setMembers'] - cmds.select(nodes, r=1, hierarchy=True) - hierarchy = cmds.ls(sl=True, long=True) - extract_nodes = layout.filter_nodes(hierarchy) - - if not extract_nodes: - self.log.info("Set members: {0}".format(nodes)) - self.log.info("Hierarchy: {0}".format(hierarchy)) - raise RuntimeError("No nodes to extract after " - "filtering: {0}".format(extract_nodes)) - - # If no meshes in layout the gpuCache command will crash as such - # we consider this invalid, unless "placeholder" is set to True - meshes = cmds.ls(cmds.ls(extract_nodes, - dag=True, - leaf=True, - shapes=True, - noIntermediate=True, - long=True), - type=("mesh", "gpuCache"), - long=True) - if not meshes and not placeholder: - raise RuntimeError("No meshes in layout. " - "Set placeholder to True on instance to allow " - "extraction without meshes") - - # Ensure at least one MESH shape is visible - extract_shapes = cmds.ls(extract_nodes, - shapes=True, - long=True) - - if not placeholder: - # We validate that at least one shape is visible to avoid erroneous - # extractions of invisible-only content. - for shape in extract_shapes: - if dag.is_visible(shape, - displayLayer=False, - intermediateObject=True, - visibility=True, - parentHidden=True): - break - else: - raise RuntimeError("No extract shape is visible. " - "Layout requires at least one " - "shape to be visible.") diff --git a/colorbleed/plugins/maya/publish/_validate_layout_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_node_ids.py deleted file mode 100644 index 99db79419b..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_layout_node_ids.py +++ /dev/null @@ -1,43 +0,0 @@ -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - - -class ValidateLayoutNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have colorbleed id attributes - - All non-referenced transform nodes in the hierarchy should have unique IDs - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.layout'] - hosts = ['maya'] - label = 'Layout Transform Ids' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @staticmethod - def get_invalid(instance): - - from maya import cmds - - nodes = cmds.ls(instance, type='transform', long=True) - referenced = cmds.ls(nodes, referencedNodes=True, long=True) - non_referenced = set(nodes) - set(referenced) - - invalid = [] - for node in non_referenced: - if not lib.get_id(node): - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Transforms (non-referenced) found in layout " - "without asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py b/colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py deleted file mode 100644 index b0d3c00097..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_layout_parent_no_transforms.py +++ /dev/null @@ -1,71 +0,0 @@ -import maya.cmds as cmds - -import pyblish.api -import colorbleed.api - -from cb.utils.maya.core import getHighestInHierarchy, iterParents - -_IDENTITY = [1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0] - -_ATTRS = ['tx', 'ty', 'tz', - 'rx', 'ry', 'rz', - 'sx', 'sy', 'sz', - 'shearXY', 'shearXZ', 'shearYZ'] - - -def is_identity(node, tolerance=1e-30): - mat = cmds.xform(node, query=True, matrix=True, objectSpace=True) - if not all(abs(x - y) < tolerance for x, y in zip(_IDENTITY, mat)): - return False - return True - - -def is_animated(node): - return any(cmds.listConnections("{}.{}".format(node, attr), source=True, - destination=False) for attr in _ATTRS) - - -class ValidateLayoutParentNoTransforms(pyblish.api.InstancePlugin): - """Validate layout parents have no transformations. - - The parent nodes above the extracted layout contents MUST have zero - transformation (no offsets in translate, rotate, scale) for this pass - validly. - - This is required to ensure no offsets are lacking from extracted caches. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.layout'] - hosts = ['maya'] - label = 'Layout No Parent Transforms' - actions = [colorbleed.api.SelectInvalidAction] - - @staticmethod - def get_invalid(instance): - - invalid = [] - - # Get highest in hierarchy - nodes = instance.data["setMembers"] - highest = getHighestInHierarchy(nodes) - - for node in highest: - for parent in iterParents(node): - if not is_identity(parent) or is_animated(parent): - invalid.append(parent) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Transforms (non-referenced) found in layout " - "without asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py deleted file mode 100644 index 3987fc0d30..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_layout_shape_node_ids.py +++ /dev/null @@ -1,149 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - - -def get_id_from_history(node): - """Return the ID from the first node in the history of the same type - - If the node itself has an ID that will be returned. If no ID found None is - returned. - - Returns: - str: The id on first node in history - - """ - - nodeType = cmds.nodeType(node) - history = cmds.listHistory(node, leaf=False) or [] - similar = cmds.ls(history, exactType=nodeType, long=True) - - for node in similar: - id = lib.get_id(node) - if id: - return id - - -class CopyUUIDsFromHistoryAction(pyblish.api.Action): - """Copy UUIDs from the history of a node. - - This allows a deformed Shape to take its UUID from the original shape. - - """ - - label = "Copy UUIDs from History" - on = "failed" # This action is only available on a failed plug-in - icon = "wrench" # Icon from Awesome Icon - - def process(self, context, plugin): - - self.log.info("Finding bad nodes..") - - # Get the errored instances - errored_instances = [] - for result in context.data["results"]: - if result["error"] is not None and result["instance"] is not None: - if result["error"]: - instance = result["instance"] - errored_instances.append(instance) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - - # Get the nodes from the all instances that ran through this plug-in - invalid = [] - for instance in instances: - invalid_nodes = plugin.get_invalid(instance) - invalid.extend(invalid_nodes) - - # Ensure unique - invalid = list(set(invalid)) - - if not invalid: - self.log.info("No invalid nodes found.") - return - - # Generate a mapping of UUIDs using history - mapping = dict() - for shape in invalid: - id = get_id_from_history(shape) - if not id: - self.log.info("No ID found in history of: {0}".format(shape)) - continue - mapping[shape] = id - - # Add the ids to the nodes - # id_utils.add_ids(mapping) - self.log.info("Generated ids on nodes: {0}".format(mapping.values())) - - -class ValidateLayoutShapeNodeIds(pyblish.api.InstancePlugin): - """Validate shapes nodes have colorbleed id attributes - - All non-referenced transforms in the hierarchy should have unique IDs. - This does not check for unique shape ids to allow a same non-referenced - shape in the output (e.g. when multiple of the same characters are in - the scene with a deformer on it). - - How? - - This usually happens when a node was created locally and did not come - from a correctly published asset. - - In the case you're entirely sure you still want to publish the shapes - you can forcefully generate ids for them. USE WITH CARE! Select the - nodes (shapes!) and run: - > scripts > pyblish > utilities > regenerate_uuids - - Why? - - The pipeline needs the ids to be able to identify "what" an object is. - When it knows that it's able to correctly assign its shaders or do all - kinds of other magic with it! - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.layout'] - hosts = ['maya'] - label = 'Layout Shape Ids' - actions = [colorbleed.api.SelectInvalidAction, - CopyUUIDsFromHistoryAction] - - @staticmethod - def get_invalid(instance): - - nodes = cmds.ls(instance, shapes=True, long=True) - referenced = cmds.ls(nodes, referencedNodes=True, long=True) - non_referenced = set(nodes) - set(referenced) - - # Ignore specific node types - # `deformFunc` = deformer shapes - IGNORED = ("gpuCache", - "constraint", - "lattice", - "baseLattice", - "geometryFilter", - "deformFunc", - "locator") - - ignored_nodes = cmds.ls(list(non_referenced), type=IGNORED, long=True) - if ignored_nodes: - non_referenced -= set(ignored_nodes) - - invalid = [] - for node in non_referenced: - if not lib.get_id(node): - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Shapes (non-referenced) found in layout " - "without asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py b/colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py deleted file mode 100644 index 93653d3620..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_layout_unique_node_ids.py +++ /dev/null @@ -1,63 +0,0 @@ -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - - -class ValidateLayoutUniqueNodeIds(pyblish.api.InstancePlugin): - """Validate nodes have unique colorbleed id attributes""" - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.layout'] - hosts = ['maya'] - label = 'Layout Transform Unique Ids' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - @staticmethod - def get_invalid_dict(instance): - """Return a dictionary mapping of id key to list of member nodes""" - from maya import cmds - - nodes = cmds.ls(instance, type='transform', long=True) - referenced = cmds.ls(nodes, referencedNodes=True, long=True) - non_referenced = set(nodes) - set(referenced) - members = non_referenced - - # Collect each id with their members - from collections import defaultdict - ids = defaultdict(list) - for member in members: - id = lib.get_id(member) - ids[id].append(member) - - # Skip those without IDs (if everything should have an ID that should - # be another validation) - ids.pop(None, None) - - # Take only the ids with more than one member - invalid = dict((id, members) for id, members in ids.iteritems() if - len(members) > 1) - return invalid - - @classmethod - def get_invalid(cls, instance): - """Return the member nodes that are invalid""" - - invalid_dict = cls.get_invalid_dict(instance) - - # Take only the ids with more than one member - invalid = list() - for members in invalid_dict.itervalues(): - invalid.extend(members) - - return invalid - - def process(self, instance): - """Process all meshes""" - - # Ensure all nodes have a cbId - invalid = self.get_invalid_dict(instance) - - if invalid: - raise RuntimeError("Transforms found with non-unique " - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py b/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py deleted file mode 100644 index c9e2a99cea..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_look_members_node_ids.py +++ /dev/null @@ -1,39 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateLookMembers(pyblish.api.InstancePlugin): - """Validate look members have colorbleed id attributes - - Looks up all relationship members and check if all the members have the - cbId (colorbleed id) and return all the nodes who fail the test. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.look'] - hosts = ['maya'] - label = 'Look Members (ID)' - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] - - def process(self, instance): - """Process all meshes""" - - invalid_ids = self.get_invalid(instance) - if invalid_ids: - raise RuntimeError("Found invalid nodes.\nNo ID : " - "{}".format(invalid_ids)) - - @classmethod - def get_invalid(cls, instance): - - relationships = instance.data["lookData"]["relationships"] - members = [] - for relationship in relationships.values(): - members.extend(relationship["members"]) - - # get the name of the node when there is no UUID - invalid = [m["name"] for m in members if not m["uuid"]] - - return invalid diff --git a/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py b/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py deleted file mode 100644 index 19142caac9..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_rig_node_ids.py +++ /dev/null @@ -1,62 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class ValidateRigNodeIds(pyblish.api.InstancePlugin): - """Validate nodes in instance have colorbleed id attributes - - To fix this use the action to select the invalid nodes. Identify these - are nodes created locally to the rig; if they are not they should've gotten - their ID elsewhere! This is important, because then you should NOT fix it - in your scene but earlier in the pipeline. If these invalid nodes are local - to your rig then you should generate ids for them. - - For Dummies: - For the pipeline it's important in further stages to identify exactly - "what nodes is what node". Basically it saying: Hey! It's me! To - accompany that each node stores an ID, like its own passport. This - validator will tell you if there are nodes that have no such - passport (ID). - - Warning: - This does NOT validate the IDs are unique in the instance. - - """ - - order = colorbleed.api.ValidatePipelineOrder - families = ['colorbleed.rig', - 'colorbleed.rigcontrols', - "colorbleed.rigpointcache"] - hosts = ['maya'] - label = 'Rig Id Attributes' - actions = [colorbleed.api.SelectInvalidAction] - - # includes: yeti grooms and v-ray fur, etc. - TYPES = ("transform", "mesh", "nurbsCurve", "geometryShape") - - @staticmethod - def get_invalid(instance): - - # filter to nodes of specific types - dag = cmds.ls(instance, noIntermediate=True, - long=True, type=ValidateRigNodeIds.TYPES) - - # Ensure all nodes have a cbId - invalid = list() - for node in dag: - # todo: refactor `mbId` when attribute is updated - uuid = cmds.attributeQuery("mbId", node=node, exists=True) - if not uuid: - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Nodes found without " - "asset IDs: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py deleted file mode 100644 index 8dcee25aeb..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_node_ids.py +++ /dev/null @@ -1,85 +0,0 @@ -from collections import defaultdict - -import pyblish.api -import colorbleed.api - - -class ValidateRigPointcacheNodeIds(pyblish.api.InstancePlugin): - """Validate rig out_SET nodes have ids - - The nodes in a rig's out_SET must all have node IDs - that are all unique. - - Geometry in a rig should be using published model's geometry. - As such when this validation doesn't pass it means you're using - local newly created nodes that are not coming from a published - model file. Ensure you update the ids from the model. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.rig', "colorbleed.rigpointcache"] - hosts = ['maya'] - label = 'Rig Pointcache Node Ids' - actions = [colorbleed.api.SelectInvalidAction] - - ignore_types = ("constraint",) - - @classmethod - def get_invalid(cls, instance): - from maya import cmds - - # Get out_SET - sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'out_SET'] - - nodes = list() - for s in pointcache_sets: - members = cmds.sets(s, query=True) - members = cmds.ls(members, long=True) # ensure long names - descendents = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - descendents = cmds.ls(descendents, noIntermediate=True, long=True) - hierarchy = members + descendents - nodes.extend(hierarchy) - - # ignore certain node types (e.g. constraints) - ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) - if ignore: - ignore = set(ignore) - nodes = [node for node in nodes if node not in ignore] - - # Missing ids - missing = list() - ids = defaultdict(list) - for node in nodes: - has_id = cmds.attributeQuery("mbId", node=node, exists=True) - if not has_id: - missing.append(node) - continue - - uuid = cmds.getAttr("{}.mbId".format(node)) - ids[uuid].append(node) - - non_uniques = list() - for uuid, nodes in ids.iteritems(): - if len(nodes) > 1: - non_uniques.extend(nodes) - - if missing: - cls.log.warning("Missing node ids: {0}".format(missing)) - - if non_uniques: - cls.log.warning("Non unique node ids: {0}".format(non_uniques)) - - invalid = missing + non_uniques - return invalid - - def process(self, instance): - """Process all meshes""" - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Missing or non-unique node IDs: " - "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py b/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py deleted file mode 100644 index 0f38f79744..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_rig_pointcache_shape_render_stats.py +++ /dev/null @@ -1,86 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class ValidateRigPointcacheShapeRenderStats(pyblish.api.Validator): - """Ensure all render stats are set to the default values.""" - - order = colorbleed.api.ValidateMeshOrder - families = ['colorbleed.model'] - hosts = ['maya'] - category = 'model' - optional = False - version = (0, 1, 0) - label = 'Rig Pointcache Shape Default Render Stats' - actions = [colorbleed.api.SelectInvalidAction] - - defaults = {'castsShadows': 1, - 'receiveShadows': 1, - 'motionBlur': 1, - 'primaryVisibility': 1, - 'smoothShading': 1, - 'visibleInReflections': 1, - 'visibleInRefractions': 1, - 'doubleSided': 1, - 'opposite': 0} - - ignore_types = ("constraint",) - - @classmethod - def get_pointcache_nodes(cls, instance): - - # Get out_SET - sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'out_SET'] - - nodes = list() - for s in pointcache_sets: - members = cmds.sets(s, q=1) - members = cmds.ls(members, long=True) # ensure long names - descendents = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - descendents = cmds.ls(descendents, - noIntermediate=True, - long=True) - hierarchy = members + descendents - nodes.extend(hierarchy) - - # ignore certain node types (e.g. constraints) - ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) - if ignore: - ignore = set(ignore) - nodes = [node for node in nodes if node not in ignore] - - return nodes - - @classmethod - def get_invalid(cls, instance): - # It seems the "surfaceShape" and those derived from it have - # `renderStat` attributes. - - nodes = cls.get_pointcache_nodes(instance) - - shapes = cmds.ls(nodes, long=True, type='surfaceShape') - invalid = [] - for shape in shapes: - for attr, requiredValue in \ - ValidateRigPointcacheShapeRenderStats.defaults.iteritems(): - - if cmds.attributeQuery(attr, node=shape, exists=True): - value = cmds.getAttr('{node}.{attr}'.format(node=shape, - attr=attr)) - if value != requiredValue: - invalid.append(shape) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - - if invalid: - raise ValueError("Shapes with non-standard renderStats " - "found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/collect_instancer_caches.py b/colorbleed/plugins/maya/publish/collect_instancer_caches.py deleted file mode 100644 index cb59893fdf..0000000000 --- a/colorbleed/plugins/maya/publish/collect_instancer_caches.py +++ /dev/null @@ -1,56 +0,0 @@ -import maya.cmds as cmds - -import pyblish.api - - -class CollectInstancerCaches(pyblish.api.InstancePlugin): - """For an Instancer collect the history. - - This would collect its particles with nucleus and cacheFile - - """ - - order = pyblish.api.CollectorOrder + 0.495 - families = ['colorbleed.instancer'] - label = "Instancer Cache Files" - - def process(self, instance): - - members = instance.data.get("exactExportMembers", None) - assert members, "Instancer must have members" - - resources = instance.data.get("resources", []) - caches = cmds.ls(members, type="cacheFile") - - errors = False - for cache in caches: - - self.log.debug("Collecting cache files for: {0}".format(cache)) - - files = cmds.cacheFile(cache, query=True, fileName=True) - - # Ensure there are any files and the cacheFile is linked - # correctly. - if not files: - errors = True - self.log.error("Cache has no files: %s" % cache) - continue - - source = files[0] # The first file is the .xml file - - # TODO: Filter the files to only contain the required frame range. - - resource = {"tags": ["maya", "node", "cacheFile"], - "node": cache, - "source": source, - "files": files, - "subfolder": "caches"} - - resources.append(resource) - - # Store on the instance - instance.data['resources'] = resources - - if errors: - raise RuntimeError("Errors during collecting caches. " - "Are the caches linked correctly?") diff --git a/colorbleed/plugins/maya/publish/collect_instancer_history.py b/colorbleed/plugins/maya/publish/collect_instancer_history.py deleted file mode 100644 index dc9cc841ba..0000000000 --- a/colorbleed/plugins/maya/publish/collect_instancer_history.py +++ /dev/null @@ -1,63 +0,0 @@ -import pyblish.api -import maya.cmds as cmds - - -class CollectInstancerHistory(pyblish.api.InstancePlugin): - """For an Instancer collect the history. - - This would collect its particles with nucleus and cacheFile - - """ - - order = pyblish.api.CollectorOrder + 0.49 - families = ['colorbleed.instancer'] - label = "Instancer History" - - def process(self, instance): - - members = instance.data["setMembers"] - - # Include history of the instancer - instancers = cmds.ls(members, type="instancer") - if not instancers: - self.log.info("No instancers found") - return - - export = instancers[:] - - # Get the required inputs of the particles from history - history = cmds.listHistory(instancers) or [] - particles = cmds.ls(history, type="nParticle") - export.extend(particles) - if particles: - self.log.info("Particles: {0}".format(particles)) - - particles_history = cmds.listHistory(particles) or [] - self.log.debug("Particle history: {0}".format(particles_history)) - - nucleus = cmds.ls(particles_history, long=True, type="nucleus") - self.log.info("Collected nucleus: {0}".format(nucleus)) - export.extend(nucleus) - - caches = cmds.ls(particles_history, long=True, type="cacheFile") - self.log.info("Collected caches: {0}".format(caches)) - export.extend(caches) - - # Collect input shapes for the instancer - for instancer in cmds.ls(instancers, exactType="instancer", long=True): - attr = "{}.inputHierarchy".format(instancer) - inputs = cmds.listConnections(attr, source=True, - destination=False) or [] - export.extend(inputs) - - # Add it to the instance - data = instance[:] - data.extend(export) - # Ensure unique objects only - data = list(set(data)) - self.log.info("Setting members to {0}".format(data)) - instance[:] = data - - # Store the recommended export selection so the export can do it - # accordingly - instance.data["exactExportMembers"] = export diff --git a/colorbleed/plugins/maya/publish/collect_particles_history.py b/colorbleed/plugins/maya/publish/collect_particles_history.py deleted file mode 100644 index b562737f8f..0000000000 --- a/colorbleed/plugins/maya/publish/collect_particles_history.py +++ /dev/null @@ -1,47 +0,0 @@ -import maya.cmds as cmds - -import pyblish.api - - -class CollectParticlesHistory(pyblish.api.InstancePlugin): - """For a Particle system collect the history. - - This would collect its nucleus and cache files. - - """ - - order = pyblish.api.CollectorOrder + 0.499 - families = ['colorbleed.particles'] - label = "Particles History" - - def process(self, instance): - - # Include history of the instancer - particles = cmds.ls(instance, dag=True, shapes=True, - leaf=True, long=True) - particles = cmds.ls(particles, type="nParticle", long=True) - if not particles: - self.log.info("No particles found") - return - - export = particles - - # Get the required inputs of the particles from its history - particles_history = cmds.listHistory(particles) or [] - if particles_history: - nucleus = cmds.ls(particles_history, type="nucleus") - export.extend(nucleus) - caches = cmds.ls(particles_history, type="cacheFile") - export.extend(caches) - - # Add it to the instance - data = instance[:] - data.extend(export) - # Ensure unique objects only - data = list(set(data)) - self.log.info("Setting members to {0}".format(data)) - instance[:] = data - - # Store the recommended export selection so the export can do it - # accordingly - instance.data["exactExportMembers"] = export diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py deleted file mode 100644 index dda47cd7d5..0000000000 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ /dev/null @@ -1,13 +0,0 @@ -from maya import cmds - -import pyblish.api - - -class CollectSetdress(pyblish.api.InstancePlugin): - - order = pyblish.api.CollectorOrder + 0.499 - label = 'Collect Model Data' - families = ["colorbleed.setdress"] - - def process(self, instance): - pass diff --git a/colorbleed/plugins/maya/publish/collect_textures.py b/colorbleed/plugins/maya/publish/collect_textures.py deleted file mode 100644 index bc1e1ede0f..0000000000 --- a/colorbleed/plugins/maya/publish/collect_textures.py +++ /dev/null @@ -1,284 +0,0 @@ -from maya import cmds - -import pyblish.api -import cb.utils.maya.shaders as shaders - -TAGS = ["maya", "attribute", "look"] -TAGS_LOOKUP = set(TAGS) - - -class SelectTextureNodesAction(pyblish.api.Action): - """Select the nodes related to the collected file textures""" - - label = "Select texture nodes" - on = "succeeded" # This action is only available on a failed plug-in - icon = "search" # Icon from Awesome Icon - - def process(self, context, plugin): - - self.log.info("Finding textures..") - - # Get the errored instances - instances = [] - for result in context.data["results"]: - instance = result["instance"] - if instance is None: - continue - - instances.append(instance) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(instances, plugin) - - # Get the texture nodes from the instances - nodes = [] - for instance in instances: - for resource in instance.data.get("resources", []): - if self.is_texture_resource(resource): - node = resource['node'] - nodes.append(node) - - # Ensure unique - nodes = list(set(nodes)) - - if nodes: - self.log.info("Selecting texture nodes: %s" % ", ".join(nodes)) - cmds.select(nodes, r=True, noExpand=True) - else: - self.log.info("No texture nodes found.") - cmds.select(deselect=True) - - def is_texture_resource(self, resource): - """Return whether the resource is a texture""" - - tags = resource.get("tags", []) - if not TAGS_LOOKUP.issubset(tags): - return False - - if resource.get("subfolder", None) != "textures": - return False - - if "node" not in resource: - return False - - return True - - -class CollectLookTextures(pyblish.api.InstancePlugin): - """Collect look textures - - Includes the link from source to destination. - - """ - - order = pyblish.api.CollectorOrder + 0.35 - label = 'Collect Look Textures' - families = ["colorbleed.texture"] - actions = [SelectTextureNodesAction] - - IGNORE = ["out_SET", "controls_SET", "_INST"] - - def process(self, instance): - - verbose = instance.data.get("verbose", False) - - # Get all texture nodes from the shader networks - sets = self.gather_sets(instance) - instance_members = {str(i) for i in cmds.ls(instance, long=True, - absoluteName=True)} - - self.log.info("Gathering set relations..") - for objset in sets: - self.log.debug("From %s.." % objset) - content = cmds.sets(objset, query=True) - objset_members = sets[objset]["members"] - for member in cmds.ls(content, long=True, absoluteName=True): - member_data = self.collect_member_data(member, - objset_members, - instance_members, - verbose) - if not member_data: - continue - - # Get the file nodes - history = cmds.listHistory(sets.keys()) or [] - files = cmds.ls(history, type="file") - files = list(set(files)) - - resources = instance.data.get("resources", []) - for node in files: - resource = self.collect_resources(node, verbose) - if not resource: - continue - resources.append(resource) - - instance.data['resources'] = resources - - def gather_sets(self, instance): - """Gather all objectSets which are of importance for publishing - - It checks if all nodes in the instance are related to any objectSet - which need to be - - Args: - instance (list): all nodes to be published - - Returns: - dict - """ - - # Get view sets (so we can ignore those sets later) - sets = dict() - view_sets = set() - for panel in cmds.getPanel(type="modelPanel"): - view_set = cmds.modelEditor(panel, query=True, - viewObjects=True) - if view_set: - view_sets.add(view_set) - - for node in instance: - related_sets = self.get_related_sets(node, view_sets) - if not related_sets: - continue - - for objset in related_sets: - if objset in sets: - continue - unique_id = cmds.getAttr("%s.cbId" % objset) - sets[objset] = {"name": objset, - "uuid": unique_id, - "members": list()} - return sets - - def collect_resources(self, node, verbose=False): - """Collect the link to the file(s) used (resource) - Args: - node (str): name of the node - verbose (bool): enable debug information - - Returns: - dict - """ - - # assure node includes full path - node = cmds.ls(node, long=True)[0] - attribute = "{}.fileTextureName".format(node) - source = cmds.getAttr(attribute) - - # Get the computed file path (e.g. the one with the pattern - # in it) So we can reassign it this computed file path whenever - # we need to. - - computed_attribute = "{}.computedFileTextureNamePattern".format(node) - computed_source = cmds.getAttr(computed_attribute) - if source != computed_source: - if verbose: - self.log.debug("File node computed pattern differs from " - "original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) - - # We replace backslashes with forward slashes because V-Ray - # can't handle the UDIM files with the backslashes in the - # paths as the computed patterns - source = computed_source.replace("\\", "/") - - files = shaders.get_file_node_files(node) - if not files: - self.log.error("File node does not have a texture set: " - "{0}".format(node)) - return - - # Define the resource - # todo: find a way to generate the destination for the publisher - resource = {"tags": TAGS[:], - "node": node, - "attribute": attribute, - "source": source, # required for resources - "files": files} # required for resources - - return resource - - def collect_member_data(self, member, objset_members, instance_members, - verbose=False): - """Get all information of the node - Args: - member (str): the name of the node to check - objset_members (list): the objectSet members - instance_members (set): the collected instance members - verbose (bool): get debug information - - Returns: - dict - - """ - - node, components = (member.rsplit(".", 1) + [None])[:2] - - # Only include valid members of the instance - if node not in instance_members: - if verbose: - self.log.info("Skipping member %s" % member) - return - - if member in [m["name"] for m in objset_members]: - return - - if verbose: - self.log.debug("Such as %s.." % member) - - member_data = {"name": node, - "uuid": cmds.getAttr("{}.cbId".format(node, ))} - - # Include components information when components are assigned - if components: - member_data["components"] = components - - return member_data - - def get_related_sets(self, node, view_sets): - """Get the sets which do not belong to any specific group - - Filters out based on: - - id attribute is NOT `pyblish.avalon.container` - - shapes and deformer shapes (alembic creates meshShapeDeformed) - - set name ends with any from a predefined list - - set in not in viewport set (isolate selected for example) - - Args: - node (str): name of the current not to check - """ - - ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] - - related_sets = cmds.listSets(object=node, extendToShape=False) - if not related_sets: - return [] - - # Ignore containers - sets = [s for s in related_sets if - not cmds.attributeQuery("id", node=s, exists=True) or - not cmds.getAttr("%s.id" % s) in ignored] - - # Exclude deformer sets - # Autodesk documentation on listSets command: - # type(uint) : Returns all sets in the scene of the given - # >>> type: - # >>> 1 - all rendering sets - # >>> 2 - all deformer sets - deformer_sets = cmds.listSets(object=node, extendToShape=False, - type=2) or [] - deformer_sets = set(deformer_sets) # optimize lookup - sets = [s for s in sets if s not in deformer_sets] - - # Ignore specifically named sets - sets = [s for s in sets if not any(s.endswith(x) for x in self.IGNORE)] - - # Ignore viewport filter view sets (from isolate select and - # viewports) - sets = [s for s in sets if s not in view_sets] - - self.log.info("Found sets %s for %s" % (related_sets, node)) - - return sets \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/extract_instancer.py b/colorbleed/plugins/maya/publish/extract_instancer.py deleted file mode 100644 index 0f81651766..0000000000 --- a/colorbleed/plugins/maya/publish/extract_instancer.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -import contextlib - -import maya.cmds as cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context - - -def _set_cache_file_path(node, path): - """Forces a cacheFile.cachePath attribute to be set to path. - - When the given path does not exist Maya will raise an error - when using `maya.cmds.setAttr` to set the "cachePath" attribute. - - Arguments: - node (str): Name of cacheFile node. - path (str): Path value to set. - - """ - - path = str(path) - - # Temporary unique attribute name - attr = "__tmp_path" - while cmds.attributeQuery(attr, node=node, exists=True): - attr += "_" - - # Create the temporary attribute, set its value and connect - # it to the `.cachePath` attribute to force the value to be - # set and applied without errors. - cmds.addAttr(node, longName=attr, dataType="string") - plug = "{0}.{1}".format(node, attr) - try: - cmds.setAttr(plug, path, type="string") - cmds.connectAttr(plug, - "{0}.cachePath".format(node), - force=True) - finally: - # Ensure the temporary attribute is deleted - cmds.deleteAttr(plug) - - -@contextlib.contextmanager -def cache_file_paths(mapping): - """Set the cacheFile paths during context. - - This is a workaround context manager that allows - to set the .cachePath attribute to a folder that - doesn't actually exist since using regular - `maya.cmds.setAttr` results in an error. - - Arguments: - mapping (dict): node -> path mapping - - """ - - # Store the original values - original = dict() - for node in mapping: - original[node] = cmds.getAttr("{}.cachePath".format(node)) - - try: - for node, path in mapping.items(): - _set_cache_file_path(node, path) - yield - finally: - for node, path in original.items(): - _set_cache_file_path(node, path) - - -def is_cache_resource(resource): - """Return whether resource is a cacheFile resource""" - start_tags = ["maya", "node", "cacheFile"] - required = set(start_tags) - tags = resource.get("tags", []) - return required.issubset(tags) - - -class ExtractInstancerMayaAscii(colorbleed.api.Extractor): - """Extract as Maya Ascii""" - - label = "Instancer (Maya Ascii)" - hosts = ["maya"] - families = ["colorbleed.instancer"] - - # TODO: Find other solution than expanding vars to fix lack of support - # TODO: of cacheFile - - def process(self, instance): - - export = instance.data("exactExportMembers") - - # Set up cacheFile path remapping. - resources = instance.data.get("resources", []) - attr_remap, cache_remap = self.process_resources(resources) - - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - with cache_file_paths(cache_remap): - with context.attribute_values(attr_remap): - cmds.select(export, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=False, - shader=False, - expressions=False) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].append(filename) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) - - def process_resources(self, resources): - - attr_remap = dict() - cache_remap = dict() - for resource in resources: - if not is_cache_resource(resource): - continue - - node = resource['node'] - destination = resource['destination'] - - folder = os.path.dirname(destination) - fname = os.path.basename(destination) - if fname.endswith(".xml"): - fname = fname[:-4] - - # Ensure the folder path ends with a slash - if not folder.endswith("\\") and not folder.endswith("/"): - folder += "/" - - # Set path and name - attr_remap["{0}.cacheName".format(node)] = os.path.expandvars( - fname) - cache_remap[node] = os.path.expandvars(folder) - - self.log.info("Mapping {0} to {1}".format(node, destination)) - - return attr_remap, cache_remap diff --git a/colorbleed/plugins/maya/publish/extract_particles.py b/colorbleed/plugins/maya/publish/extract_particles.py deleted file mode 100644 index 852813a87c..0000000000 --- a/colorbleed/plugins/maya/publish/extract_particles.py +++ /dev/null @@ -1,48 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - - -class ExtractParticlesMayaAscii(colorbleed.api.Extractor): - """Extract as Maya Ascii""" - - label = "Particles (Maya Ascii)" - hosts = ["maya"] - families = ["colorbleed.particles"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - export = instance.data("exactExportMembers") - - # TODO: Transfer cache files and relink temporarily on the particles - - # Perform extraction - self.log.info("Performing extraction..") - with avalon.maya.maintained_selection(): - cmds.select(export, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - channels=True, # allow animation - constraints=False, - shader=False, - expressions=False) - - if "files" not in instance.data: - instance.data["files"] = list() - - instance.data["files"].append(filename) - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/extract_textures.py b/colorbleed/plugins/maya/publish/extract_textures.py deleted file mode 100644 index 86380d4295..0000000000 --- a/colorbleed/plugins/maya/publish/extract_textures.py +++ /dev/null @@ -1,41 +0,0 @@ -import json -import os -import shutil - -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - - -class ExtractTextures(colorbleed.api.Extractor): - - label = "Extract Textures" - hosts = ["maya"] - families = ["colorbleed.texture"] - order = pyblish.api.ExtractorOrder + 0.1 - - def process(self, instance): - - self.log.info("Extracting textures ...") - - dir_path = self.staging_dir(instance) - resources = instance.data["resources"] - for resource in resources: - self.copy_files(dir_path, resource["files"]) - - self.log.info("Storing cross instance information ...") - self.store_data(resources) - - def store_data(self, data): - tmp_dir = lib.maya_temp_folder() - tmp_file = os.path.join(tmp_dir, "resources.json") - with open(tmp_file, "w") as f: - json.dump(data, fp=f, - separators=[",", ":"], - ensure_ascii=False) - - def copy_files(self, dest, files): - for f in files: - fname = os.path.basename(f) - dest_file = os.path.join(dest, fname) - shutil.copy(f, dest_file) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py b/colorbleed/plugins/maya/publish/extract_yeti_nodes.py deleted file mode 100644 index a6df25ed81..0000000000 --- a/colorbleed/plugins/maya/publish/extract_yeti_nodes.py +++ /dev/null @@ -1,65 +0,0 @@ -import os - -from maya import cmds - -import avalon.maya -import colorbleed.api - -import cb.utils.maya.context as context - - -class ExtractFurYeti(colorbleed.api.Extractor): - """Extract as Yeti nodes""" - - label = "Yeti Nodes" - hosts = ["maya"] - families = ["colorbleed.groom"] - - def process(self, instance): - - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.ma".format(instance.name) - path = os.path.join(dir_path, filename) - - # Perform extraction - self.log.info("Performing extraction..") - - # Get only the shape contents we need in such a way that we avoid - # taking along intermediateObjects - members = instance.data("setMembers") - members = cmds.ls(members, - dag=True, - shapes=True, - type="pgYetiMaya", - noIntermediate=True, - long=True) - - # Remap cache files names and ensure fileMode is set to load from cache - resource_remap = dict() - # required tags to be a yeti resource - required_tags = ["maya", "yeti", "attribute"] - resources = instance.data.get("resources", []) - for resource in resources: - resource_tags = resource.get("tags", []) - if all(tag in resource_tags for tag in required_tags): - attribute = resource['attribute'] - destination = resource['destination'] - resource_remap[attribute] = destination - - # Perform extraction - with avalon.maya.maintained_selection(): - with context.attribute_values(resource_remap): - cmds.select(members, r=True, noExpand=True) - cmds.file(path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - constructionHistory=False, - shader=False) - - instance.data["files"] = [filename] - - self.log.info("Extracted instance '{0}' to: {1}".format( - instance.name, path)) diff --git a/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py b/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py deleted file mode 100644 index 61d67f967c..0000000000 --- a/colorbleed/plugins/maya/publish/validate_gpuCache_not_empty.py +++ /dev/null @@ -1,87 +0,0 @@ -import re -from collections import defaultdict - -import maya.cmds as cmds - -import pyblish.api -import colorbleed.api - - -def get_gpu_cache_subnodes(cache): - """Return the amount of subnodes in the cache - - This uses `maya.cmds.gpuCache(showStats=True)` and parses - the resulting stats for the amount of internal sub nodes. - - Args: - cache (str): gpuCache node name. - - Returns: - int: Amount of subnodes in loaded gpuCache - - Raises: - TypeError: when `cache` is not a gpuCache object type. - RuntimeError: when `maya.cmds.gpuCache(showStats=True)` - does not return stats from which we can parse the - amount of subnodes. - """ - - # Ensure gpuCache - if not cmds.objectType(cache, isType="gpuCache"): - raise TypeError("Node is not a gpuCache: {0}".format(cache)) - - stats = cmds.gpuCache(cache, query=True, showStats=True) - for line in stats.splitlines(): - match = re.search('nb of internal sub nodes: ([0-9]+)$', line) - if match: - return int(match.group(1)) - - raise RuntimeError("Couldn't parse amount of subnodes " - "in cache stats: {0}".format(cache)) - - -def get_empty_gpu_caches(caches): - empty = list() - - # Group caches per path (optimization) so - # we check each file only once - caches_per_path = defaultdict(list) - for cache in caches: - path = cmds.getAttr(cache + ".cacheFileName") - caches_per_path[path].append(cache) - - # We consider the cache empty if its stats - # result in 0 subnodes - for path, path_caches in caches_per_path.items(): - - cache = path_caches[0] - num = get_gpu_cache_subnodes(cache) - if num == 0: - empty.extend(path_caches) - - return empty - - -class ValidateGPUCacheNotEmpty(pyblish.api.InstancePlugin): - """Validates that gpuCaches have at least one visible shape in them. - - This is tested using the `maya.cmds.gpuCache(cache, showStats=True)` - command. - """ - - order = colorbleed.api.ValidateContentsOrder - label = 'GpuCache has subnodes' - families = ['colorbleed.layout'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - caches = cmds.ls(instance, type="gpuCache", long=True) - invalid = get_empty_gpu_caches(caches) - - return invalid - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Invalid nodes found: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py index 13e8ea54bc..91fc720cbe 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py @@ -18,7 +18,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): order = colorbleed.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.proxy', 'colorbleed.model'] + families = ['colorbleed.model'] label = 'Mesh No Negative Scale' actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py index 6e4c0028db..221e8f8b61 100644 --- a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py +++ b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py @@ -17,7 +17,7 @@ class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model', 'colorbleed.layout', 'colorbleed.rig'] + families = ['colorbleed.model', 'colorbleed.rig'] optional = True label = "Unknown Nodes" actions = [colorbleed.api.SelectInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_nodes_visible.py b/colorbleed/plugins/maya/publish/validate_nodes_visible.py deleted file mode 100644 index fc55fadbc2..0000000000 --- a/colorbleed/plugins/maya/publish/validate_nodes_visible.py +++ /dev/null @@ -1,43 +0,0 @@ -import pyblish.api -import colorbleed.api - -from maya import cmds -import cb.utils.maya.dag as dag - - -class ValidateNodesVisible(pyblish.api.InstancePlugin): - """Validate all shape nodes are currently visible. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.furYeti'] - hosts = ['maya'] - label = "Nodes Visible" - actions = [colorbleed.api.SelectInvalidAction] - - @staticmethod - def get_invalid(instance): - - members = instance.data["setMembers"] - members = cmds.ls(members, - dag=True, - shapes=True, - long=True, - noIntermediate=True) - - invalid = [] - for node in members: - if not dag.is_visible(node, displayLayer=False): - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all the nodes in the instance 'objectSet'""" - - invalid = self.get_invalid(instance) - - if invalid: - raise ValueError("Instance contains invisible shapes: " - "{0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py b/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py deleted file mode 100644 index 397bf24a6e..0000000000 --- a/colorbleed/plugins/maya/publish/validate_references_no_failed_edits.py +++ /dev/null @@ -1,112 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class RepairFailedEditsAction(pyblish.api.Action): - label = "Remove failed edits" - on = "failed" # This action is only available on a failed plug-in - icon = "wrench" # Icon from Awesome Icon - - def process(self, context, plugin): - self.log.info("Finding bad nodes..") - - # Get the errored instances - errored_instances = [] - for result in context.data["results"]: - if result["error"] is not None and result["instance"] is not None: - if result["error"]: - instance = result["instance"] - errored_instances.append(instance) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - - # Get the nodes from the all instances that ran through this plug-in - invalid = [] - for instance in instances: - invalid_nodes = plugin.get_invalid(instance) - invalid.extend(invalid_nodes) - - if not invalid: - self.log.info("No invalid nodes found.") - return - - for ref in invalid: - self.log.info("Remove failed edits for: {0}".format(ref)) - cmds.referenceEdit(ref, - removeEdits=True, - failedEdits=True, - successfulEdits=False) - self.log.info("Removed failed edits") - - -class ValidateReferencesNoFailedEdits(pyblish.api.InstancePlugin): - """Validate that all referenced nodes' reference nodes don't have failed - reference edits. - - Failed reference edits can happen if you apply a change to a referenced - object in the scene and then change the source of the reference - (referenced file) to remove the object. The reference edit can't be - applied to the node because it is missing, hence a "failed edit". This - could unnecessarily bloat file sizes and degrade load/save speed. - - To investigate reference edits you can "List edits" on a reference - and look for those edits that appear as failed. Usually failed edits - are near the bottom of the list. - - """ - - order = colorbleed.api.ValidateContentsOrder - hosts = ['maya'] - families = ['colorbleed.layout'] - category = 'layout' - optional = True - version = (0, 1, 0) - label = 'References Failed Edits' - actions = [colorbleed.api.SelectInvalidAction, - RepairFailedEditsAction] - - @staticmethod - def get_invalid(instance): - """Return invalid reference nodes in the instance - - Terminology: - reference node: The node that is the actual reference containing - the nodes (type: reference) - referenced nodes: The nodes contained within the reference - (type: any type of nodes) - - """ - referenced_nodes = cmds.ls(instance, referencedNodes=True, long=True) - if not referenced_nodes: - return list() - - # Get reference nodes from referenced nodes - # (note that reference_nodes != referenced_nodes) - reference_nodes = set() - for node in referenced_nodes: - reference_node = cmds.referenceQuery(node, referenceNode=True) - if reference_node: - reference_nodes.add(reference_node) - - # Check for failed edits on each reference node. - invalid = [] - for reference_node in reference_nodes: - failed_edits = cmds.referenceQuery(reference_node, - editNodes=True, - failedEdits=True, - successfulEdits=False) - if failed_edits: - invalid.append(reference_node) - - return invalid - - def process(self, instance): - """Process all the nodes in the instance""" - - invalid = self.get_invalid(instance) - if invalid: - raise ValueError("Reference nodes found with failed " - "reference edits: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index ad394f159e..179073f95d 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -12,7 +12,7 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.rig', 'colorbleed.layout', 'colorbleed.animation'] + families = ['colorbleed.rig', 'colorbleed.animation'] label = 'Single Assembly' def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py deleted file mode 100644 index 6b03deb8dc..0000000000 --- a/colorbleed/plugins/maya/publish/validate_yeti_cache_frames.py +++ /dev/null @@ -1,90 +0,0 @@ -import os - -import pyblish.api -import colorbleed.api - - -class ValidateYetiCacheFrames(pyblish.api.InstancePlugin): - """Validates Yeti nodes have existing cache frames""" - - order = colorbleed.api.ValidateContentsOrder - label = 'Yeti Cache Frames' - families = ['colorbleed.furYeti'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - # Check if all frames cache exists for given node. - start_frame = instance.data.get("startFrame") - end_frame = instance.data.get("endFrame") - required = range(int(start_frame), int(end_frame) + 1) - - yeti_caches = instance.data.get('yetiCaches', {}) - invalid = [] - - for node, data in yeti_caches.items(): - cls.log.info("Validating node: {0}".format(node)) - - source = data.get("source", None) - sequences = data.get("sequences", []) - - if not source: - invalid.append(node) - cls.log.warning("Node has no cache file name set: " - "{0}".format(node)) - continue - - folder = os.path.dirname(source) - if not folder or not os.path.exists(folder): - invalid.append(node) - cls.log.warning("Cache folder does not exist: " - "{0} {1}".format(node, folder)) - continue - - if not sequences: - invalid.append(node) - cls.log.warning("Sequence does not exist: " - "{0} {1}".format(node, source)) - continue - - if len(sequences) != 1: - invalid.append(node) - cls.log.warning("More than one sequence found? " - "{0} {1}".format(node, source)) - cls.log.warning("Found caches: {0}".format(sequences)) - continue - - sequence = sequences[0] - - start = sequence.start() - end = sequence.end() - if start > start_frame or end < end_frame: - invalid.append(node) - cls.log.warning("Sequence does not have enough " - "frames: {0}-{1} (requires: {2}-{3})" - "".format(start, end, - start_frame, - end_frame)) - continue - - # Ensure all frames are present - missing = set(sequence.missing()) - required_missing = [x for x in required if x in missing] - if required_missing: - - invalid.append(node) - cls.log.warning("Sequence is missing required frames: " - "{0}".format(required_missing)) - continue - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - - if invalid: - self.log.error("Invalid nodes: {0}".format(invalid)) - raise RuntimeError("Invalid yeti nodes in instance. " - "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py deleted file mode 100644 index e37f3ecfb7..0000000000 --- a/colorbleed/plugins/maya/publish/validate_yeti_cache_non_publish.py +++ /dev/null @@ -1,43 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateYetiCacheNonPublish(pyblish.api.InstancePlugin): - """Validates Yeti caches are not published FROM published caches""" - - order = colorbleed.api.ValidateContentsOrder - label = 'Yeti Cache Non Publish' - families = ['colorbleed.furYeti'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - import cbra.lib - - invalid = list() - for node, data in instance.data['yetiCaches'].items(): - - source = data['source'] - - # Published folder has at least "publish" in its path - if "publish" not in source.lower(): - continue - - try: - context = cbra.lib.parse_context(source) - except RuntimeError: - continue - - if "family" in context or "subset" in context: - invalid.append(node) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - self.log.error("Invalid nodes: {0}".format(invalid)) - raise RuntimeError("Invalid yeti nodes in instance. " - "See logs for details.") diff --git a/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py b/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py deleted file mode 100644 index 3fd45e7558..0000000000 --- a/colorbleed/plugins/maya/publish/validate_yeti_cache_unique_filenames.py +++ /dev/null @@ -1,81 +0,0 @@ -import os -import re -from collections import defaultdict - -import pyblish.api -import colorbleed.api - -import maya.cmds as cmds - - -class ValidateYetiCacheUniqueFilenames(pyblish.api.InstancePlugin): - """Validates Yeti nodes in instance have unique filename patterns. - - This is to ensure Yeti caches in a single instance don't overwrite each - other's files when published to a single flat folder structure. - - For example: - cache1: path/to/arm.%04d.fur - cache2: other/path/to/arm.%04d.fur - - Both these caches point to unique files, though they have the same filename - pattern. When copied to a single folder they would overwrite each other, - and as such are considered invalid. To fix this rename the caches filenames - to be unique, like `left_arm.%04d.fur` and `right_arm.%04d.fur`. - - """ - - order = colorbleed.api.ValidateContentsOrder - label = 'Yeti Cache Unique Filenames' - families = ['colorbleed.furYeti'] - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - members = instance.data["setMembers"] - shapes = cmds.ls(members, dag=True, leaf=True, shapes=True, long=True) - yeti_nodes = cmds.ls(shapes, type="pgYetiMaya", long=True) - - if not yeti_nodes: - raise RuntimeError("No pgYetiMaya nodes in instance.") - - def _to_pattern(path): - """Path to pattern that pyseq.get_sequences can use""" - return re.sub(r"([0-9]+|%[0-9]+d)(.fur)$", r"[0-9]*\2", path) - - invalid = list() - - # Collect cache patterns - cache_patterns = defaultdict(list) - for node in yeti_nodes: - - path = cmds.getAttr(node + ".cacheFileName") - if not path: - invalid.append(node) - cls.log.warning("Node has no cache file name set: " - "{0}".format(node)) - continue - - filename = os.path.basename(path) - pattern = _to_pattern(filename) - - cache_patterns[pattern].append(node) - - # Identify non-unique cache patterns - for pattern, nodes in cache_patterns.iteritems(): - if len(nodes) > 1: - cls.log.warning("Nodes have same filename pattern ({0}): " - "{1}".format(pattern, nodes)) - invalid.extend(nodes) - - return invalid - - def process(self, instance): - - invalid = self.get_invalid(instance) - - if invalid: - self.log.error("Invalid nodes: {0}".format(invalid)) - raise RuntimeError("Invalid yeti nodes in instance. " - "See logs for details.") diff --git a/colorbleed/plugins/publish/validate_deadline_done.py b/colorbleed/plugins/publish/validate_deadline_done.py deleted file mode 100644 index 6b0a5525e6..0000000000 --- a/colorbleed/plugins/publish/validate_deadline_done.py +++ /dev/null @@ -1,63 +0,0 @@ -import pyblish.api - - -class ValidateMindbenderDeadlineDone(pyblish.api.InstancePlugin): - """Ensure render is finished before publishing the resulting images""" - - label = "Rendered Successfully" - order = pyblish.api.ValidatorOrder - hosts = ["shell"] - families = ["mindbender.imagesequence"] - optional = True - - def process(self, instance): - from avalon import api - from avalon.vendor import requests - - # From Deadline documentation - # https://docs.thinkboxsoftware.com/products/deadline/8.0/ - # 1_User%20Manual/manual/rest-jobs.html#job-property-values - states = { - 0: "Unknown", - 1: "Active", - 2: "Suspended", - 3: "Completed", - 4: "Failed", - 6: "Pending", - } - - assert "AVALON_DEADLINE" in api.Session, ("Environment variable " - "missing: 'AVALON_DEADLINE'") - AVALON_DEADLINE = api.Session["AVALON_DEADLINE"] - url = "{}/api/jobs?JobID=%s".format(AVALON_DEADLINE) - - for job in instance.data["metadata"]["jobs"]: - response = requests.get(url % job["_id"]) - - if response.ok: - data = response.json() - assert data, ValueError("Can't find information about " - "this Deadline job: " - "{}".format(job["_id"])) - - state = states.get(data[0]["Stat"]) - if state in (None, "Unknown"): - raise Exception("State of this render is unknown") - - elif state == "Active": - raise Exception("This render is still currently active") - - elif state == "Suspended": - raise Exception("This render is suspended") - - elif state == "Failed": - raise Exception("This render was not successful") - - elif state == "Pending": - raise Exception("This render is pending") - else: - self.log.info("%s was rendered successfully" % instance) - - else: - raise Exception("Could not determine the current status " - " of this render") From 76f5e5936e1cf3ec7bc0a7f145194fa4c3af5e9e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:08:10 +0200 Subject: [PATCH 0245/1249] Remove mindbender `.travis.yml` --- .travis.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ee263058c2..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: python - -sudo: required -dist: trusty - -python: - - 2.7 - -services: - - docker - -install: - - git clone https://github.com/getavalon/core.git - - docker run --name mindbender-mongo -d mongo - - docker build -t pyblish/mindbender -f Dockerfile-maya2016 . - -script: - - > - PYTHONPATH=$(pwd)/core - docker run - --rm - -v $(pwd):/workspace - --link mindbender-mongo:mongo - -e COVERALLS_REPO_TOKEN - -e TRAVIS_JOB_ID - -e MINDBENDER_MONGO=mongodb://mongo:27017 - pyblish/mindbender From 0d21128e1ed3b5e4b242b83d88c793f24d86fb7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:11:37 +0200 Subject: [PATCH 0246/1249] Fix docstrings --- colorbleed/action.py | 8 ++++---- .../plugins/maya/publish/validate_look_deformed_shapes.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 360b17ccc7..5566518588 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -45,8 +45,8 @@ def get_errored_plugins_from_data(context): class RepairAction(pyblish.api.Action): """Repairs the action - To retrieve the invalid nodes this assumes a static `repair(instance)` - method is available on the plugin. + To process the repairing this requires a static `repair(instance)` method + is available on the plugin. """ label = "Repair" @@ -71,8 +71,8 @@ class RepairAction(pyblish.api.Action): class RepairContextAction(pyblish.api.Action): """Repairs the action - To retrieve the invalid nodes this assumes a static `repair(instance)` - method is available on the plugin. + To process the repairing this requires a static `repair(instance)` method + is available on the plugin. """ label = "Repair Context" diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py index 0ccc38e81a..1e458b557e 100644 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py @@ -60,7 +60,7 @@ def get_deformed_history_id_mapping(shapes): class CopyUUIDsFromHistory(pyblish.api.Action): """Repairs the action - To retrieve the invalid nodes this assumes a static `repair(instance)` + To retrieve the invalid nodes this assumes a static `get_invalid(instance)` method is available on the plugin. """ From 42860b5a2b7cfc4059ab16b43f187b7031f5a660 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:14:09 +0200 Subject: [PATCH 0247/1249] Remove maya2018 compatibility fix since it's implemented in avalon core. --- colorbleed/maya/__init__.py | 48 ------------------------------------- 1 file changed, 48 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index a1afac47ac..8c861f8817 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -22,51 +22,6 @@ CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") LOAD_AT_START = ["AbcImport", "AbcExport", "mtoa"] -# This is a temporary solution with the http.py clash with six.py -# Maya has added paths to the PYTHONPATH which are redundant as -# many of them are paths from a pacakge, example: -# "/some/awesome/package/core/" which should be "/some/awesome/package" - - -def _remove_from_paths(paths, keyword, stitch=False): - """Remove any paths which contain the given keyword - - >>> paths = ["foo\\foo\\foo.py", "foo\\foobar.py", "\\bar\\bar\\foo"] - >>> _remove_from_paths(paths, keyword="bar") - ["foo\\foo\\foo.py"] - - >>> paths = ["foo\\bar\\foobar.py", "foo\\foobar.py", "\\banana\\pie\\delicious"] - >>> _remove_from_paths(paths, keyword="pie", stitch=True) - "foo\\bar\\foobar.py;foo\\foobar.py" - - Args: - paths(list) : a list of file paths - keyword(str) : the word to check for - stitch(bool) : recreate a full string for PYTHONPATH - - Returns: - str - Only when stitch is set to True does the function return a string - """ - - paths = [path for path in paths if keyword not in path] - if stitch: - return os.pathsep.join(paths) - - -def remove_googleapiclient(): - """Remove any paths which contain `googleclientapi`""" - - keyword = "googleapiclient" - # remove from sys.path - # _remove_from_paths(sys.path, keyword) - - # reconstruct pythonpaths - pythonpaths = os.environ["PYTHONPATH"].split(os.pathsep) - result = _remove_from_paths(pythonpaths, keyword, stitch=True) - - os.environ["PYTHONPATH"] = result - def install(): @@ -88,9 +43,6 @@ def install(): avalon.on("new", on_new) avalon.on("save", on_save) - if cmds.about(version=True) == "2018": - remove_googleapiclient() - def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) From 50aaa96ecb40ab437c1a0eefc42c8d3ede98cdbf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:29:36 +0200 Subject: [PATCH 0248/1249] Fix `mtoa` errors on launch by loading deferred, cleanup id generation by moving logic into `maya/lib.py` --- colorbleed/maya/__init__.py | 51 +++++++++++++------------------------ 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 8c861f8817..481a45692d 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,12 +1,13 @@ import os import logging +from functools import partial +from maya import utils from maya import cmds -from avalon import maya, io, api as avalon +from avalon import api as avalon from pyblish import api as pyblish - from . import menu from . import lib @@ -20,27 +21,16 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "maya", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") -LOAD_AT_START = ["AbcImport", "AbcExport", "mtoa"] - def install(): - pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) menu.install() - # Add any needed plugins - for plugin in LOAD_AT_START: - log.info("Loading %s" % plugin) - if cmds.pluginInfo(plugin, query=True, loaded=True): - continue - cmds.loadPlugin(plugin, quiet=True) - log.info("Installing callbacks ... ") avalon.on("init", on_init) - avalon.on("new", on_new) avalon.on("save", on_save) @@ -55,19 +45,19 @@ def uninstall(): def on_init(_): avalon.logger.info("Running callback on init..") - maya.commands.reset_frame_range() - maya.commands.reset_resolution() + def force_load_deferred(plugin): + """Load a plug-in deferred so it runs after UI has initialized""" + try: + utils.executeDeferred(partial(cmds.loadPlugin, + plugin, + quiet=True)) + except RuntimeError, e: + log.warning("Can't load plug-in: " + "{0} - {1}".format(plugin, e)) - -def on_new(_): - avalon.logger.info("Running callback on new..") - - # Load dependencies - cmds.loadPlugin("AbcExport.mll", quiet=True) - cmds.loadPlugin("AbcImport.mll", quiet=True) - - maya.commands.reset_frame_range() - maya.commands.reset_resolution() + # Set up all plug-ins + for plugin in ["AbcImport", "AbcExport", "mtoa"]: + force_load_deferred(plugin) def on_save(_): @@ -78,13 +68,6 @@ def on_save(_): avalon.logger.info("Running callback on save..") + # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) - - # Lead with asset ID from the database - asset = os.environ["AVALON_ASSET"] - asset_id = io.find_one({"type": "asset", "name": asset}, - projection={"_id": True}) - - # generate the ids - for node in nodes: - lib.set_id(str(asset_id["_id"]), node) + lib.generate_ids(nodes) From ab6b5503944b1124f3f90478524535ce139c1af7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:29:36 +0200 Subject: [PATCH 0249/1249] Fix `mtoa` errors on launch by loading deferred, cleanup id generation by moving logic into `maya/lib.py` --- colorbleed/maya/__init__.py | 51 +++++++++++++------------------------ colorbleed/maya/lib.py | 12 +++++++++ 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 8c861f8817..481a45692d 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,12 +1,13 @@ import os import logging +from functools import partial +from maya import utils from maya import cmds -from avalon import maya, io, api as avalon +from avalon import api as avalon from pyblish import api as pyblish - from . import menu from . import lib @@ -20,27 +21,16 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "maya", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "maya", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "maya", "create") -LOAD_AT_START = ["AbcImport", "AbcExport", "mtoa"] - def install(): - pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) menu.install() - # Add any needed plugins - for plugin in LOAD_AT_START: - log.info("Loading %s" % plugin) - if cmds.pluginInfo(plugin, query=True, loaded=True): - continue - cmds.loadPlugin(plugin, quiet=True) - log.info("Installing callbacks ... ") avalon.on("init", on_init) - avalon.on("new", on_new) avalon.on("save", on_save) @@ -55,19 +45,19 @@ def uninstall(): def on_init(_): avalon.logger.info("Running callback on init..") - maya.commands.reset_frame_range() - maya.commands.reset_resolution() + def force_load_deferred(plugin): + """Load a plug-in deferred so it runs after UI has initialized""" + try: + utils.executeDeferred(partial(cmds.loadPlugin, + plugin, + quiet=True)) + except RuntimeError, e: + log.warning("Can't load plug-in: " + "{0} - {1}".format(plugin, e)) - -def on_new(_): - avalon.logger.info("Running callback on new..") - - # Load dependencies - cmds.loadPlugin("AbcExport.mll", quiet=True) - cmds.loadPlugin("AbcImport.mll", quiet=True) - - maya.commands.reset_frame_range() - maya.commands.reset_resolution() + # Set up all plug-ins + for plugin in ["AbcImport", "AbcExport", "mtoa"]: + force_load_deferred(plugin) def on_save(_): @@ -78,13 +68,6 @@ def on_save(_): avalon.logger.info("Running callback on save..") + # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) - - # Lead with asset ID from the database - asset = os.environ["AVALON_ASSET"] - asset_id = io.find_one({"type": "asset", "name": asset}, - projection={"_id": True}) - - # generate the ids - for node in nodes: - lib.set_id(str(asset_id["_id"]), node) + lib.generate_ids(nodes) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b22e5a3f5c..c6f9fc2a6d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -679,6 +679,18 @@ def get_id(node): return cmds.getAttr("{}.cbId".format(node)) +def generate_ids(nodes): + """Assign a new id of the current active context to the nodes""" + + # Get the asset ID from the database for the asset of current context + asset = os.environ["AVALON_ASSET"] + asset_id = io.find_one({"type": "asset", "name": asset}, + projection={"_id": True}) + + for node in nodes: + set_id(asset_id["_id"], node) + + def set_id(asset_id, node): """Add cbId to `node` unless one already exists. From b60edf3ae0be6adaca7a418b1423e05f7297b0d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Sep 2017 16:57:08 +0200 Subject: [PATCH 0250/1249] Remove redundant line (default for `optional` is False) --- colorbleed/plugins/maya/publish/validate_shape_render_stats.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py index 36f9e2caa4..87b716650d 100644 --- a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py +++ b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py @@ -11,7 +11,6 @@ class ValidateShapeRenderStats(pyblish.api.Validator): hosts = ['maya'] families = ['colorbleed.model'] category = 'model' - optional = False version = (0, 1, 0) label = 'Shape Default Render Stats' actions = [colorbleed.api.SelectInvalidAction, From 3699b5b1fc162f18453537cd927f2d6244bbd62b Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 29 Sep 2017 17:19:34 +0200 Subject: [PATCH 0251/1249] implement own workspace file --- res/workspace.mel | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 res/workspace.mel diff --git a/res/workspace.mel b/res/workspace.mel new file mode 100644 index 0000000000..81d7f628a5 --- /dev/null +++ b/res/workspace.mel @@ -0,0 +1,74 @@ +//Maya 2018 Project Definition + +workspace -fr "fluidCache" "cache/nCache/fluid"; +workspace -fr "JT_ATF" "data"; +workspace -fr "images" "images"; +workspace -fr "offlineEdit" "scenes/edits"; +workspace -fr "STEP_ATF Export" "data"; +workspace -fr "furShadowMap" "renderData/fur/furShadowMap"; +workspace -fr "INVENTOR_ATF Export" "data"; +workspace -fr "SVG" "data"; +workspace -fr "scripts" "scripts"; +workspace -fr "STL_ATF" "data"; +workspace -fr "DAE_FBX" "data"; +workspace -fr "shaders" "renderData/shaders"; +workspace -fr "NX_ATF" "data"; +workspace -fr "furFiles" "renderData/fur/furFiles"; +workspace -fr "CATIAV5_ATF Export" "data"; +workspace -fr "OBJ" "data"; +workspace -fr "PARASOLID_ATF Export" "data"; +workspace -fr "FBX export" "data"; +workspace -fr "furEqualMap" "renderData/fur/furEqualMap"; +workspace -fr "BIF" "data"; +workspace -fr "DAE_FBX export" "data"; +workspace -fr "CATIAV5_ATF" "data"; +workspace -fr "SAT_ATF Export" "data"; +workspace -fr "movie" "movies"; +workspace -fr "ASS Export" "data"; +workspace -fr "move" "data"; +workspace -fr "mayaAscii" "scenes"; +workspace -fr "autoSave" "autosave"; +workspace -fr "NX_ATF Export" "data"; +workspace -fr "sound" "sound"; +workspace -fr "mayaBinary" "scenes"; +workspace -fr "timeEditor" "Time Editor"; +workspace -fr "DWG_ATF" "data"; +workspace -fr "JT_ATF Export" "data"; +workspace -fr "iprImages" "renderData/iprImages"; +workspace -fr "FBX" "data"; +workspace -fr "renderData" "renderData"; +workspace -fr "CATIAV4_ATF" "data"; +workspace -fr "fileCache" "cache/nCache"; +workspace -fr "eps" "data"; +workspace -fr "3dPaintTextures" "sourceimages/3dPaintTextures"; +workspace -fr "STL_ATF Export" "data"; +workspace -fr "mel" "scripts"; +workspace -fr "translatorData" "data"; +workspace -fr "particles" "cache/particles"; +workspace -fr "scene" "scenes"; +workspace -fr "SAT_ATF" "data"; +workspace -fr "PROE_ATF" "data"; +workspace -fr "WIRE_ATF Export" "data"; +workspace -fr "sourceImages" "sourceimages"; +workspace -fr "clips" "clips"; +workspace -fr "furImages" "renderData/fur/furImages"; +workspace -fr "INVENTOR_ATF" "data"; +workspace -fr "STEP_ATF" "data"; +workspace -fr "DWG_ATF Export" "data"; +workspace -fr "depth" "renderData/depth"; +workspace -fr "sceneAssembly" "sceneAssembly"; +workspace -fr "IGES_ATF Export" "data"; +workspace -fr "PARASOLID_ATF" "data"; +workspace -fr "IGES_ATF" "data"; +workspace -fr "teClipExports" "Time Editor/Clip Exports"; +workspace -fr "ASS" "data"; +workspace -fr "audio" "sound"; +workspace -fr "bifrostCache" "cache/bifrost"; +workspace -fr "Alembic" "data"; +workspace -fr "illustrator" "data"; +workspace -fr "diskCache" "data"; +workspace -fr "WIRE_ATF" "data"; +workspace -fr "templates" "assets"; +workspace -fr "OBJexport" "data"; +workspace -fr "furAttrMap" "renderData/fur/furAttrMap"; +workspace -fr "alembicCache" "cache/alembic"; \ No newline at end of file From 9b8265a3da1751d1616aedb2e5778315d9bf4c13 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 29 Sep 2017 17:25:27 +0200 Subject: [PATCH 0252/1249] stripped rs_ from renderlayer node --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index aa0bdb58fe..0026a7347c 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -28,6 +28,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): if layer.endswith("defaultRenderLayer"): continue + layername = layer.split("rs_", 1)[-1] data = {"family": "Render Layers", "families": ["colorbleed.renderlayer"], "publish": cmds.getAttr("{}.renderable".format(layer)), @@ -39,7 +40,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): # instance subset "asset": asset_name, - "subset": layer, + "subset": layername, "setMembers": layer, "time": api.time(), @@ -69,7 +70,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): _globals = maya.read(avalon_globals) data["renderGlobals"] = self.get_global_overrides(_globals) - instance = context.create_instance(layer) + instance = context.create_instance(layername) instance.data.update(data) def get_render_attribute(self, attr): From ddb0e600e9847ce4c6b5195c6b51f296f2afa0a8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 29 Sep 2017 17:25:56 +0200 Subject: [PATCH 0253/1249] ensure renderlayer node is passed on --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 72dca12c33..e267c42dce 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -179,7 +179,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "UsingRenderLayers": True, # Render only this layer - "RenderLayer": instance.name, + "RenderLayer": instance.data["setMembers"], # Determine which renderer to use from the file itself "Renderer": "file", From ab3a6de69c330d37e7cb8e7ada83c02666048197 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 09:36:05 +0200 Subject: [PATCH 0254/1249] Added renderlayer context function --- colorbleed/maya/lib.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c6f9fc2a6d..b035e9b037 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -106,6 +106,19 @@ def _maintained_selection_context(): noExpand=True) +@contextlib.contextmanager +def renderlayer(layer): + """Set the renderlayer during the context""" + + original = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + + try: + cmds.editRenderLayerGlobals(currentRenderLayer=layer) + yield + finally: + cmds.editRenderLayerGlobals(currentRenderLayer=original) + + def unique(name): assert isinstance(name, basestring), "`name` must be string" From 980a3b884c7138ca840b96b04010e27976749b1f Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 10:13:31 +0200 Subject: [PATCH 0255/1249] added get_renderer fucntion --- colorbleed/maya/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b035e9b037..437daf7625 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -119,6 +119,11 @@ def renderlayer(layer): cmds.editRenderLayerGlobals(currentRenderLayer=original) +def get_renderer(layer): + renderlayer(layer) + return cmds.getAttr("defaultRenderGlobals.currentRenderer") + + def unique(name): assert isinstance(name, basestring), "`name` must be string" From dbec37f3e1bdb77ac6c4704dc6c0a654297cf792 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 10:14:00 +0200 Subject: [PATCH 0256/1249] make use of context managed function for renderer query --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 0026a7347c..971c9b582f 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api from avalon import maya, api +import colorbleed.maya.lib as lib class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): @@ -36,7 +37,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): "startFrame": self.get_render_attribute("startFrame"), "endFrame": self.get_render_attribute("endFrame"), "byFrameStep": self.get_render_attribute("byFrameStep"), - "renderer": self.get_render_attribute("currentRenderer"), + "renderer": lib.get_renderer(layer), # instance subset "asset": asset_name, From 1631a20cdee097796692376b891f0e20be159300 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 11:33:58 +0200 Subject: [PATCH 0257/1249] generate preview filename for deadlin --- .../plugins/maya/publish/submit_deadline.py | 130 ++++++------------ 1 file changed, 41 insertions(+), 89 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index e267c42dce..7561d300c5 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -1,5 +1,4 @@ import os -import re import json import shutil import getpass @@ -11,34 +10,18 @@ from avalon.vendor import requests import pyblish.api +import colorbleed.maya.lib as lib -def get_padding_length(filename): - """ - >>> get_padding_length("sequence.v004.0001.exr", default=None) - 4 - >>> get_padding_length("sequence.-001.exr", default=None) - 4 - >>> get_padding_length("sequence.v005.exr", default=None) - None - - Retrieve the padding length by retrieving the frame number from a file. - - Args: - filename (str): the explicit filename, e.g.: sequence.0001.exr - - Returns: - int - """ - - padding_match = re.search(r"\.(-?\d+)", filename) - if padding_match: - length = len(padding_match.group()) - else: - raise AttributeError("Could not find padding length in " - "'{}'".format(filename)) - - return length +RENDER_ATTRIBUTES = {"vray": + {"node": "vraySettings", + "prefix": "fileNamePrefix", + "padding": "fileNamePadding"}, + "arnold": + {"node": "defaultRenderGlobals", + "prefix": "imageFilePrefix", + "padding": "extensionPadding"} + } def get_renderer_variables(): @@ -50,48 +33,26 @@ def get_renderer_variables(): dict """ - ext = "" - filename_prefix = "" - # padding = 4 + renderer = lib.get_renderer(lib.get_current_renderlayer()) + render_attrs = RENDER_ATTRIBUTES[renderer] - renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - if renderer == "vray": + filename_prefix = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["prefix"])) - # padding = cmds.getAttr("vraySettings.fileNamePadding") + filename_padding = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["padding"])) - # check for vray settings node - settings_node = cmds.ls("vraySettings", type="VRaySettingsNode") - if not settings_node: - raise AttributeError("Could not find a VRay Settings Node, " - "to ensure the node exists open the " - "Render Settings window") + filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] - # get the extension - image_format = cmds.getAttr("vraySettings.imageFormatStr") - if image_format: - ext = "{}".format(image_format.split(" ")[0]) + # get the extension, getAttr defaultRenderGlobals.imageFormat + # returns index number + filename_base = os.path.basename(filename_0) + extension = os.path.splitext(filename_base)[-1].strip(".") - prefix = cmds.getAttr("vraySettings.fileNamePrefix") - if prefix: - filename_prefix = prefix - - # insert other renderer logic here - - # fall back to default - if renderer.lower().startswith("maya"): - # get the extension, getAttr defaultRenderGlobals.imageFormat - # returns index number - first_filename = cmds.renderSettings(fullPath=True, - firstImageName=True)[0] - ext = os.path.splitext(os.path.basename(first_filename))[-1].strip(".") - - # get padding and filename prefix - # padding = cmds.getAttr("defaultRenderGlobals.extensionPadding") - prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") - if prefix: - filename_prefix = prefix - - return {"ext": ext, "filename_prefix": filename_prefix} + return {"ext": extension, + "filename_prefix": filename_prefix, + "padding": filename_padding, + "filename_0": filename_0} class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): @@ -117,9 +78,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): workspace = context.data["workspaceDir"] fpath = context.data["currentFile"] fname = os.path.basename(fpath) - name, ext = os.path.splitext(fname) comment = context.data.get("comment", "") - dirname = os.path.join(workspace, "renders", name) + dirname = os.path.join(workspace, "renders", instance.name) try: os.makedirs(dirname) @@ -128,9 +88,9 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # get the variables depending on the renderer render_variables = get_renderer_variables() - output_file_prefix = render_variables["filename_prefix"] output_filename_0 = self.preview_fname(instance, dirname, + render_variables["padding"], render_variables["ext"]) # E.g. http://192.168.0.1:8082/api/jobs @@ -170,7 +130,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Output directory and filename "OutputFilePath": dirname, - "OutputFilePrefix": output_file_prefix, + "OutputFilePrefix": render_variables["filename_prefix"], # Mandatory for Deadline "Version": cmds.about(version=True), @@ -245,7 +205,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) - def preview_fname(self, instance, dirname, extension): + def preview_fname(self, instance, folder, padding, ext): """Return outputted filename with #### for padding Passing the absolute path to Deadline enables Deadline Monitor @@ -258,29 +218,21 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): To /path/to/render.####.png + Args: + instance: instance to be published + folder (str): folder to which will be written + padding (int): padding length + ext(str): file extension + + Returns: + str + """ - # We'll need to take tokens into account - fname = cmds.renderSettings(firstImageName=True, - fullPath=True, - layer=instance.name)[0] + padded_basename = "{}.{}.{}".format(instance.name, "#" * padding, ext) + preview_fname = os.path.join(folder, padded_basename) - try: - # Assume `c:/some/path/filename.0001.exr` - # TODO(marcus): Bulletproof this, the user may have - # chosen a different format for the outputted filename. - basename = os.path.basename(fname) - name, padding, ext = basename.rsplit(".", 2) - - padding_format = "#" * len(padding) - fname = ".".join([name, padding_format, extension]) - self.log.info("Assuming renders end up @ %s" % fname) - file_name = os.path.join(dirname, instance.name, fname) - except ValueError: - file_name = "" - self.log.info("Couldn't figure out where renders go") - - return file_name + return preview_fname def preflight_check(self, instance): """Ensure the startFrame, endFrame and byFrameStep are integers""" From e8baa358bcea6f5cdbfde36ad22c08e58001c264 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 12:44:40 +0200 Subject: [PATCH 0258/1249] hardcodes output file prefix --- .../plugins/maya/publish/submit_deadline.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 7561d300c5..03663231c2 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -2,6 +2,7 @@ import os import json import shutil import getpass +import pprint from maya import cmds @@ -16,7 +17,8 @@ import colorbleed.maya.lib as lib RENDER_ATTRIBUTES = {"vray": {"node": "vraySettings", "prefix": "fileNamePrefix", - "padding": "fileNamePadding"}, + "padding": "fileNamePadding", + "ext": "imageFormatStr"}, "arnold": {"node": "defaultRenderGlobals", "prefix": "imageFilePrefix", @@ -36,9 +38,6 @@ def get_renderer_variables(): renderer = lib.get_renderer(lib.get_current_renderlayer()) render_attrs = RENDER_ATTRIBUTES[renderer] - filename_prefix = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["prefix"])) - filename_padding = cmds.getAttr("{}.{}".format(render_attrs["node"], render_attrs["padding"])) @@ -47,7 +46,15 @@ def get_renderer_variables(): # get the extension, getAttr defaultRenderGlobals.imageFormat # returns index number filename_base = os.path.basename(filename_0) - extension = os.path.splitext(filename_base)[-1].strip(".") + if renderer == "vray": + # Maya's renderSettings function does not resolved V-Ray extension + # in the + extension = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["ext"])) + filename_prefix = "//" + else: + extension = os.path.splitext(filename_base)[-1].strip(".") + filename_prefix = "//" return {"ext": extension, "filename_prefix": filename_prefix, @@ -72,6 +79,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") + assert AVALON_DEADLINE is not None, "Requires AVALON_DEADLINE" context = instance.context @@ -79,7 +87,10 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): fpath = context.data["currentFile"] fname = os.path.basename(fpath) comment = context.data.get("comment", "") - dirname = os.path.join(workspace, "renders", instance.name) + # force a folder depth with + layer = instance.name + scene = os.path.splitext(fname)[0] + dirname = os.path.join(workspace, "renders") try: os.makedirs(dirname) @@ -88,7 +99,9 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # get the variables depending on the renderer render_variables = get_renderer_variables() - output_filename_0 = self.preview_fname(instance, + # following hardcoded "//" + output_filename_0 = self.preview_fname(scene, + layer, dirname, render_variables["padding"], render_variables["ext"]) @@ -205,7 +218,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) - def preview_fname(self, instance, folder, padding, ext): + def preview_fname(self, scene, layer, folder, padding, ext): """Return outputted filename with #### for padding Passing the absolute path to Deadline enables Deadline Monitor @@ -219,7 +232,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): /path/to/render.####.png Args: - instance: instance to be published + layer: name of the current layer to be rendered folder (str): folder to which will be written padding (int): padding length ext(str): file extension @@ -229,8 +242,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): """ - padded_basename = "{}.{}.{}".format(instance.name, "#" * padding, ext) - preview_fname = os.path.join(folder, padded_basename) + padded_basename = "{}.{}.{}".format(layer, "#" * padding, ext) + preview_fname = os.path.join(folder, scene, layer, padded_basename) return preview_fname From db42d603db86aee7eedada7c46148b086cd67756 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 12:49:14 +0200 Subject: [PATCH 0259/1249] cosmetics --- colorbleed/plugins/maya/publish/submit_deadline.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 03663231c2..f1a2288b08 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -43,18 +43,19 @@ def get_renderer_variables(): filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] - # get the extension, getAttr defaultRenderGlobals.imageFormat - # returns index number - filename_base = os.path.basename(filename_0) if renderer == "vray": # Maya's renderSettings function does not resolved V-Ray extension - # in the + # Getting the extension for VRay settings node extension = cmds.getAttr("{}.{}".format(render_attrs["node"], render_attrs["ext"])) filename_prefix = "//" else: + # Get the extension, getAttr defaultRenderGlobals.imageFormat + # returns an index number. + filename_base = os.path.basename(filename_0) extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = "//" + filename_prefix = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["prefix"])) return {"ext": extension, "filename_prefix": filename_prefix, From 9d60ba753a121e35df59ac03d249e176fd37995c Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 12:53:50 +0200 Subject: [PATCH 0260/1249] cosmetics --- colorbleed/plugins/maya/publish/submit_deadline.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index f1a2288b08..19d935a97f 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -2,7 +2,6 @@ import os import json import shutil import getpass -import pprint from maya import cmds @@ -88,8 +87,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): fpath = context.data["currentFile"] fname = os.path.basename(fpath) comment = context.data.get("comment", "") - # force a folder depth with - layer = instance.name scene = os.path.splitext(fname)[0] dirname = os.path.join(workspace, "renders") @@ -98,11 +95,11 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): except OSError: pass - # get the variables depending on the renderer + # Get the variables depending on the renderer render_variables = get_renderer_variables() # following hardcoded "//" output_filename_0 = self.preview_fname(scene, - layer, + instance.name, dirname, render_variables["padding"], render_variables["ext"]) From 5e379f75d2d161fa75a76622d49d22cdd53609c7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 14:32:34 +0200 Subject: [PATCH 0261/1249] removed return, logging error only --- colorbleed/plugins/maya/publish/collect_look.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 54799160f7..29717dc54f 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,5 +1,4 @@ from maya import cmds -import pprint import pyblish.api import colorbleed.maya.lib as lib from cb.utils.maya import context, shaders @@ -122,8 +121,7 @@ class CollectLook(pyblish.api.InstancePlugin): files = cmds.ls(history, type="file", long=True) # Collect textures - resources = [self.collect_resource(n) for n in files] - instance.data["resources"] = resources + instance.data["resources"] = [self.collect_resource(n) for n in files] # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["relationships"]: @@ -289,10 +287,8 @@ class CollectLook(pyblish.api.InstancePlugin): source = computed_source.replace("\\", "/") files = shaders.get_file_node_files(node) - if not files: - self.log.error("File node does not have a texture set: " - "{0}".format(node)) - return + if len(files) == 0: + self.log.error("No valid files found".format(node)) # Define the resource return {"node": node, From 9ad9803e3242163195836cd521f1c2c2077044be Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 14:33:01 +0200 Subject: [PATCH 0262/1249] added check for files for filenodes --- .../maya/publish/validate_look_contents.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 751b3b8ab1..132a76286a 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -39,8 +39,10 @@ class ValidateLookContents(pyblish.api.InstancePlugin): attributes = cls.validate_lookdata_attributes(instance) # check the looks for ID looks = cls.validate_looks(instance) + # check if file nodes have valid files + files = cls.validate_files(instance) - invalid = looks + attributes + invalid = looks + attributes + files return invalid @@ -82,3 +84,19 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid.append(name) return invalid + + @classmethod + def validate_files(cls, instance): + + invalid = [] + + resources = instance.data.get("resources", []) + for resource in resources: + files = resource["files"] + if len(files) == 0: + node = resource["node"] + cls.log.error("File node '%s' use no or non-existing " + "files" % node) + invalid.append(node) + + return invalid \ No newline at end of file From a3a71f78546ee2207f6eecbf3f60f1d92f897e62 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 14:46:12 +0200 Subject: [PATCH 0263/1249] cosmetics in error log --- colorbleed/plugins/maya/publish/validate_look_contents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 132a76286a..6e14b3af24 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -95,7 +95,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): files = resource["files"] if len(files) == 0: node = resource["node"] - cls.log.error("File node '%s' use no or non-existing " + cls.log.error("File node '%s' uses no or non-existing " "files" % node) invalid.append(node) From b0dbc6823452e6f9c831ddf841cc10a18c5ebadd Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 14:46:38 +0200 Subject: [PATCH 0264/1249] cosmetics in error log --- colorbleed/plugins/maya/publish/collect_look.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 29717dc54f..6e4e17c535 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -84,7 +84,7 @@ class CollectLook(pyblish.api.InstancePlugin): sets = self.gather_sets(instance) # Lookup with absolute names (from root namespace) - instance_lookup = set([str(x) for x in cmds.ls(instance, long=True)]) + instance_lookup = set(cmds.ls(instance, long=True)) self.log.info("Gathering set relations..") for objset in sets: @@ -288,7 +288,7 @@ class CollectLook(pyblish.api.InstancePlugin): files = shaders.get_file_node_files(node) if len(files) == 0: - self.log.error("No valid files found".format(node)) + self.log.error("No valid files found from node `%s`" % node) # Define the resource return {"node": node, From 9dec69fc996a8acc861ea5127059a7362c68d2fb Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 14:47:19 +0200 Subject: [PATCH 0265/1249] added kwargs noIntermediate to ignore intermediate shapes when collecting instances --- colorbleed/plugins/maya/publish/collect_instances.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 1bcafa2351..58da0e4ea9 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -96,7 +96,8 @@ class CollectInstances(pyblish.api.ContextPlugin): children = cmds.listRelatives(members, allDescendents=True, - fullPath=True) or [] + fullPath=True, + noIntermediate=True) or [] parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) From 71c72ccbbb03f9d392f8fd58736bc0ea994c781b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 2 Oct 2017 16:42:25 +0200 Subject: [PATCH 0266/1249] fixed load plugin issue with missing Alembic --- colorbleed/maya/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 481a45692d..43fd9d7d8f 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -55,9 +55,9 @@ def on_init(_): log.warning("Can't load plug-in: " "{0} - {1}".format(plugin, e)) - # Set up all plug-ins - for plugin in ["AbcImport", "AbcExport", "mtoa"]: - force_load_deferred(plugin) + cmds.loadPlugin("AbcImport", quiet=True) + cmds.loadPlugin("AbcExport", quiet=True) + force_load_deferred("mtoa") def on_save(_): From b0ee6fc0ab4f5217fa0df9f559188c27fe47476d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 12:19:56 +0200 Subject: [PATCH 0267/1249] fixed issue with missing comma --- colorbleed/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 3cd484028d..2c96d69556 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -27,7 +27,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.camera", "colorbleed.imagesequence", "colorbleed.look", - "colorbleed.mayaAscii" + "colorbleed.mayaAscii", "colorbleed.model", "colorbleed.pointcache", "colorbleed.rig"] From 7d9be8a405f08e47a5921b0b72ddbd8a0970b1b3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 12:20:11 +0200 Subject: [PATCH 0268/1249] clean up --- colorbleed/plugins/maya/publish/collect_instances.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 58da0e4ea9..cfeb2d4098 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -41,14 +41,6 @@ class CollectInstances(pyblish.api.ContextPlugin): label = "Collect Instances" order = pyblish.api.CollectorOrder hosts = ["maya"] - isntance_order = ["colorbleed.model", - "colorbleed.rig", - "colorbleed.animation", - "colorbleed.camera", - "colorbleed.texture", - "colorbleed.look", - "colorbleed.historyLookdev", - "colorbleed.group"] def process(self, context): From 8c3fc367907b02842ded2a26ad49ad2e144edc4a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 16:15:17 +0200 Subject: [PATCH 0269/1249] added render settings validator --- .../maya/publish/validate_rendersettings.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_rendersettings.py diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py new file mode 100644 index 0000000000..29dd1abf47 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -0,0 +1,47 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api + + +class ValidateResources(pyblish.api.InstancePlugin): + """Validates the globar render settings + + * File Name Prefix must be as followed: + * vray: renders/// + * arnold: renders/// + * default: renders/// + + * Frame Padding must be: + * default: 4 + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Render Settings" + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + + renderer = cmds.getAttr("defeaultRenderGlobals.currentRenderer") + + default_padding = 4 + default_prefix = { + "vray": "renders//_/", + "arnold": "renders//_/" + } + + if renderer == "vray": + prefix = cmds.getAttr("vraySettings.fileNamePrefix") + padding = cmds.getAttr("vraySettings.fileNamePadding") + else: + prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") + padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") + + filename_prefix = default_prefix[renderer] + assert padding == default_padding, AttributeError( + "Expecting padding of 4 ( 0000 )" + ) + assert prefix == filename_prefix, AttributeError( + "Wrong file name prefix, expecting %s" % filename_prefix + ) \ No newline at end of file From 259e8888b0e75fc697977edbd27c3c950ea0a2ef Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 16:35:49 +0200 Subject: [PATCH 0270/1249] ensured images will be stored in renders --- res/workspace.mel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/workspace.mel b/res/workspace.mel index 81d7f628a5..60fda895d5 100644 --- a/res/workspace.mel +++ b/res/workspace.mel @@ -2,7 +2,7 @@ workspace -fr "fluidCache" "cache/nCache/fluid"; workspace -fr "JT_ATF" "data"; -workspace -fr "images" "images"; +workspace -fr "images" "renders"; workspace -fr "offlineEdit" "scenes/edits"; workspace -fr "STEP_ATF Export" "data"; workspace -fr "furShadowMap" "renderData/fur/furShadowMap"; From 1048e11efb1983ad1e060f5c5f9a12eec0e6e1cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 3 Oct 2017 17:31:07 +0200 Subject: [PATCH 0271/1249] Remove debugging statements --- colorbleed/maya/lib.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c6f9fc2a6d..e24f321fea 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -316,14 +316,11 @@ def polyConstraint(components, *args, **kwargs): kwargs.pop('mode', None) with no_undo(flush=False): - print("la") with maintained_selection(): - print("po") # Apply constraint using mode=2 (current and next) so # it applies to the selection made before it; because just # a `maya.cmds.select()` call will not trigger the constraint. with reset_polySelectConstraint(): - print("do") cmds.select(components, r=1) cmds.polySelectConstraint(*args, mode=2, **kwargs) result = cmds.ls(selection=True) From 3de6ac23f1923b2446301b3845895e07f2217561 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 3 Oct 2017 17:34:35 +0200 Subject: [PATCH 0272/1249] Remove buggy maintained selection, instead use the one from avalon.maya --- colorbleed/maya/lib.py | 66 +----------------------------------------- 1 file changed, 1 insertion(+), 65 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index e24f321fea..64c8f844e6 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -42,70 +42,6 @@ SHAPE_ATTRS = ["castsShadows", SHAPE_ATTRS = set(SHAPE_ATTRS) -def maintained_selection(arg=None): - if arg is not None: - return _maintained_selection_context() - else: - return _maintained_selection_decorator(arg) - - -def _maintained_selection_decorator(func): - """Function decorator to maintain the selection once called - - Example: - >>> @_maintained_selection - ... def my_function(): - ... # Modify selection - ... cmds.select(clear=True) - ... - >>> # Selection restored - - """ - - def wrapper(*args, **kwargs): - previous_selection = cmds.ls(selection=True) - try: - return func(*args, **kwargs) - finally: - if previous_selection: - cmds.select(previous_selection, - replace=True, - noExpand=True) - else: - cmds.select(deselect=True, - noExpand=True) - - return wrapper - - -@contextlib.contextmanager -def _maintained_selection_context(): - """Maintain selection during context - - Example: - >>> scene = cmds.file(new=True, force=True) - >>> node = cmds.createNode("transform", name="Test") - >>> cmds.select("persp") - >>> with maintained_selection(): - ... cmds.select("Test", replace=True) - >>> "Test" in cmds.ls(selection=True) - False - - """ - - previous_selection = cmds.ls(selection=True) - try: - yield - finally: - if previous_selection: - cmds.select(previous_selection, - replace=True, - noExpand=True) - else: - cmds.select(deselect=True, - noExpand=True) - - def unique(name): assert isinstance(name, basestring), "`name` must be string" @@ -316,7 +252,7 @@ def polyConstraint(components, *args, **kwargs): kwargs.pop('mode', None) with no_undo(flush=False): - with maintained_selection(): + with maya.maintained_selection(): # Apply constraint using mode=2 (current and next) so # it applies to the selection made before it; because just # a `maya.cmds.select()` call will not trigger the constraint. From db90ae1676ab140ce85a8019e4ad066add86cb43 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 17:45:27 +0200 Subject: [PATCH 0273/1249] fixed typo --- colorbleed/plugins/maya/publish/validate_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 29dd1abf47..1c2302e966 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -23,7 +23,7 @@ class ValidateResources(pyblish.api.InstancePlugin): def process(self, instance): - renderer = cmds.getAttr("defeaultRenderGlobals.currentRenderer") + renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") default_padding = 4 default_prefix = { @@ -44,4 +44,4 @@ class ValidateResources(pyblish.api.InstancePlugin): ) assert prefix == filename_prefix, AttributeError( "Wrong file name prefix, expecting %s" % filename_prefix - ) \ No newline at end of file + ) From e7833e26a16b33aaf81f37c0c580a5d927893f4f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 17:49:26 +0200 Subject: [PATCH 0274/1249] udpated expected output path --- colorbleed/plugins/maya/publish/submit_deadline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 19d935a97f..ea96fb486c 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -45,16 +45,14 @@ def get_renderer_variables(): if renderer == "vray": # Maya's renderSettings function does not resolved V-Ray extension # Getting the extension for VRay settings node - extension = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["ext"])) - filename_prefix = "//" + extension = cmds.getAttr("vraySettings.imageFormatStr") + filename_prefix = "/_/" else: # Get the extension, getAttr defaultRenderGlobals.imageFormat # returns an index number. filename_base = os.path.basename(filename_0) extension = os.path.splitext(filename_base)[-1].strip(".") - filename_prefix = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["prefix"])) + filename_prefix = "/_/" return {"ext": extension, "filename_prefix": filename_prefix, @@ -97,7 +95,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Get the variables depending on the renderer render_variables = get_renderer_variables() - # following hardcoded "//" + # following hardcoded "renders//_/" output_filename_0 = self.preview_fname(scene, instance.name, dirname, @@ -241,7 +239,9 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): """ padded_basename = "{}.{}.{}".format(layer, "#" * padding, ext) - preview_fname = os.path.join(folder, scene, layer, padded_basename) + scene_layer_folder = "{}_{}".format(scene, layer) + preview_fname = os.path.join(folder, scene, scene_layer_folder, + layer, padded_basename) return preview_fname From e2d4a9a93d1bf82004b132047e8b6c96c3cfae07 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 3 Oct 2017 17:49:57 +0200 Subject: [PATCH 0275/1249] updated prefix string --- colorbleed/plugins/maya/publish/validate_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 1c2302e966..4995bf915a 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -27,8 +27,8 @@ class ValidateResources(pyblish.api.InstancePlugin): default_padding = 4 default_prefix = { - "vray": "renders//_/", - "arnold": "renders//_/" + "vray": "/_/", + "arnold": "/_/" } if renderer == "vray": From 6d8e990ce217e26dde0d0e653b797deb8c41f80d Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 4 Oct 2017 12:20:21 +0200 Subject: [PATCH 0276/1249] resolved missing functions after merge --- colorbleed/maya/lib.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 64c8f844e6..014fa0039b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -211,8 +211,22 @@ def collect_animation_data(): return data -def get_current_renderlayer(): - return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) +@contextlib.contextmanager +def renderlayer(layer): + """Set the renderlayer during the context""" + + original = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + + try: + cmds.editRenderLayerGlobals(currentRenderLayer=layer) + yield + finally: + cmds.editRenderLayerGlobals(currentRenderLayer=original) + + +def get_renderer(layer): + renderlayer(layer) + return cmds.getAttr("defaultRenderGlobals.currentRenderer") @contextlib.contextmanager From e9099d1a1feb25b13d6e73c0f14bac1162165e17 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 4 Oct 2017 12:48:05 +0200 Subject: [PATCH 0277/1249] fixed context in get_renderer --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 014fa0039b..32bf200610 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -225,8 +225,8 @@ def renderlayer(layer): def get_renderer(layer): - renderlayer(layer) - return cmds.getAttr("defaultRenderGlobals.currentRenderer") + with renderlayer(layer): + return cmds.getAttr("defaultRenderGlobals.currentRenderer") @contextlib.contextmanager From 66677eb8425acb2b6ae0006b5664a212ae4ee2e5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 4 Oct 2017 13:59:26 +0200 Subject: [PATCH 0278/1249] restore get_current_renderlayer function --- colorbleed/maya/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 32bf200610..968560b461 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -229,6 +229,10 @@ def get_renderer(layer): return cmds.getAttr("defaultRenderGlobals.currentRenderer") +def get_current_renderlayer(): + return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + + @contextlib.contextmanager def no_undo(flush=False): """Disable the undo queue during the context From 18078a15679ea5ddbb83a940413b235c34af5de8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Oct 2017 17:40:30 +0200 Subject: [PATCH 0279/1249] Fix typo in docstring --- colorbleed/plugins/maya/publish/validate_rendersettings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 4995bf915a..c2f6b1238c 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -5,7 +5,7 @@ import colorbleed.api class ValidateResources(pyblish.api.InstancePlugin): - """Validates the globar render settings + """Validates the global render settings * File Name Prefix must be as followed: * vray: renders/// From b00fb1c14a2780e713b0e52018ec0ec1a02ada1b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Oct 2017 17:45:38 +0200 Subject: [PATCH 0280/1249] Remove redundant AttributeError; it does nothing --- colorbleed/plugins/maya/publish/validate_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index c2f6b1238c..d69bebc686 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -39,9 +39,9 @@ class ValidateResources(pyblish.api.InstancePlugin): padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") filename_prefix = default_prefix[renderer] - assert padding == default_padding, AttributeError( + assert padding == default_padding, ( "Expecting padding of 4 ( 0000 )" ) - assert prefix == filename_prefix, AttributeError( + assert prefix == filename_prefix, ( "Wrong file name prefix, expecting %s" % filename_prefix ) From db34b11c26bcc8cdfffec48e0e14ad70df85fb1f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Oct 2017 18:02:30 +0200 Subject: [PATCH 0281/1249] Improve ValidateRenderSettings; fix class name, fix checking prefix within the layer, remove redundant AttributeError --- .../maya/publish/validate_rendersettings.py | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index d69bebc686..56fa9197fa 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -2,15 +2,16 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib -class ValidateResources(pyblish.api.InstancePlugin): +class ValidateRenderSettings(pyblish.api.InstancePlugin): """Validates the global render settings * File Name Prefix must be as followed: - * vray: renders/// - * arnold: renders/// - * default: renders/// + * vray: // + * arnold: // + * default: // * Frame Padding must be: * default: 4 @@ -21,27 +22,31 @@ class ValidateResources(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] - def process(self, instance): - - renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - - default_padding = 4 - default_prefix = { + DEFAULT_PADDING = 4 + DEFAULT_PREFIX = { "vray": "/_/", "arnold": "/_/" - } + } - if renderer == "vray": - prefix = cmds.getAttr("vraySettings.fileNamePrefix") - padding = cmds.getAttr("vraySettings.fileNamePadding") - else: - prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") - padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") + def process(self, instance): - filename_prefix = default_prefix[renderer] - assert padding == default_padding, ( - "Expecting padding of 4 ( 0000 )" - ) + renderer = instance.data['renderer'] + layer_node = instance.data['setMembers'] + + # Collect the filename prefix in the renderlayer + with lib.renderlayer(layer_node): + if renderer == "vray": + prefix = cmds.getAttr("vraySettings.fileNamePrefix") + padding = cmds.getAttr("vraySettings.fileNamePadding") + else: + prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") + padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") + + filename_prefix = self.DEFAULT_PREFIX[renderer] assert prefix == filename_prefix, ( "Wrong file name prefix, expecting %s" % filename_prefix ) + assert padding == self.DEFAULT_PADDING, ( + "Expecting padding of {} ( {} )".format(self.DEFAULT_PADDING, + "0"*self.DEFAULT_PADDING) + ) From 98edcac479bd4c722ad3e46da1df4f12147eb176 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Oct 2017 18:02:52 +0200 Subject: [PATCH 0282/1249] Implement ValidateRenderImageRule --- .../publish/validate_render_image_rule.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_render_image_rule.py diff --git a/colorbleed/plugins/maya/publish/validate_render_image_rule.py b/colorbleed/plugins/maya/publish/validate_render_image_rule.py new file mode 100644 index 0000000000..c16b5ecc3a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_render_image_rule.py @@ -0,0 +1,26 @@ +import maya.mel as mel + +import pyblish.api +import colorbleed.api + + +def get_file_rule(rule): + """Workaround for a bug in python with cmds.workspace""" + return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) + + +class ValidateRenderImageRule(pyblish.api.ContextPlugin): + """Validates "images" file rule is set to "renders/" + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Images File Rule (Workspace)" + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, context): + + assert get_file_rule("images") == "renders", ( + "Workspace's `images` file rule must be set to: renders" + ) From f73f2541810010c987b976f5333771fed65ac71b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:46:47 +0200 Subject: [PATCH 0283/1249] added new creator class for setdress --- .../maya/create/colorbleed_setdress.py | 9 ++++++ colorbleed/plugins/maya/load/load_setdress.py | 32 +++++++++++++++++++ .../maya/load/load_setdress_rebuild.py | 0 .../plugins/maya/publish/collect_setdress.py | 0 .../plugins/maya/publish/extract_setdress.py | 0 5 files changed, 41 insertions(+) create mode 100644 colorbleed/plugins/maya/create/colorbleed_setdress.py create mode 100644 colorbleed/plugins/maya/load/load_setdress.py create mode 100644 colorbleed/plugins/maya/load/load_setdress_rebuild.py create mode 100644 colorbleed/plugins/maya/publish/collect_setdress.py create mode 100644 colorbleed/plugins/maya/publish/extract_setdress.py diff --git a/colorbleed/plugins/maya/create/colorbleed_setdress.py b/colorbleed/plugins/maya/create/colorbleed_setdress.py new file mode 100644 index 0000000000..fe0c64dd40 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_setdress.py @@ -0,0 +1,9 @@ +import avalon.maya + + +class CreateSetDress(avalon.maya.Creator): + """THe animated objects in the scene""" + + name = "setdress" + label = "Set Dress" + family = "colorbleed.setdress" diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/colorbleed/plugins/maya/load/load_setdress.py new file mode 100644 index 0000000000..aaf6834f2f --- /dev/null +++ b/colorbleed/plugins/maya/load/load_setdress.py @@ -0,0 +1,32 @@ +from avalon import api + + +class SetDressAlembicLoader(api.Loader): + """Load the setdress as alembic""" + + families = ["colorbleed.setdress"] + representations = ["abc"] + + label = "Load Alembic" + order = -10 + icon = "code-fork" + color = "orange" + + def process(self, name, namespace, context, data): + + import maya.cmds as cmds + from avalon import maya + + namespace = maya.unique_namespace("{}_".format(name), + format="%03d", + suffix="_abc") + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py new file mode 100644 index 0000000000..e69de29bb2 From 57467b6ea681a3840b70ee396e85989970cc1d9a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:47:34 +0200 Subject: [PATCH 0284/1249] loader to recreate setdress in its entirely --- .../maya/load/load_setdress_rebuild.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index e69de29bb2..852410b502 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -0,0 +1,46 @@ +import pprint + +from avalon import api, io, pipeline +from avalon.tools.cbloader import lib + + +class SetDressRebuild(api.Loader): + + families = ["colorbleed.setdress"] + representations = ["abc"] + + label = "Rebuild Set Dress" + order = -9 + icon = "code-fork" + color = "orange" + + def process(self, name, namespace, context, data): + + import json + # from maya import cmds + + print ">>>", lib.__file__ + + # Ensure + data_file = self.fname.replace(".abc", ".json") + with open(data_file, "r") as fp: + build_data = json.load(fp) + + pprint.pprint(build_data) + for _id, instances in build_data.items(): + # Rebuild filename + for inst in instances: + nodes = self.run_loader(_id) + # cmds.xform(nodes, matrix=inst["matrix"]) + + def run_loader(self, _id): + # get all registered plugins + obj_id = io.ObjectId(_id) + loader_inst = lib.iter_loaders(obj_id) + if loader_inst is None: + raise RuntimeError("Could not find matching loader") + + # strip the generator layer from the found loader + loader = list(loader_inst)[0] + context = lib.get_representation_context(obj_id) + loader.process(**context) From c8cd19957f2de4a395edc129c450c9c3482e54ef Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:47:59 +0200 Subject: [PATCH 0285/1249] loader for abc reference of setdress --- colorbleed/plugins/maya/load/load_setdress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/colorbleed/plugins/maya/load/load_setdress.py index aaf6834f2f..2ad1a58339 100644 --- a/colorbleed/plugins/maya/load/load_setdress.py +++ b/colorbleed/plugins/maya/load/load_setdress.py @@ -1,4 +1,4 @@ -from avalon import api +from avalon import api, io class SetDressAlembicLoader(api.Loader): From 7969169db42d82f23686545012f95ad227442312 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:48:16 +0200 Subject: [PATCH 0286/1249] added setdress to integration --- colorbleed/plugins/publish/integrate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 2c96d69556..43b29edc63 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -30,6 +30,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.mayaAscii", "colorbleed.model", "colorbleed.pointcache", + "colorbleed.setdress", "colorbleed.rig"] def process(self, instance): From ced6eaa1563b00c38ee581b10a06eb57af1e8b7b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:48:34 +0200 Subject: [PATCH 0287/1249] added collector for setdress --- .../plugins/maya/publish/collect_setdress.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index e69de29bb2..c3865726b3 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -0,0 +1,64 @@ +from collections import defaultdict +import pyblish.api + +from maya import cmds, mel +from avalon import maya as amaya + + +class CollectSetDress(pyblish.api.InstancePlugin): + """Collect all relevant setdress items + + Collected data: + + * File name + * Compatible loader + * Matrix per instance + * Namespace + """ + + order = pyblish.api.CollectorOrder + 0.49 + label = "Set Dress" + families = ["colorbleed.setdress"] + + def process(self, instance): + + # Find containers + containers = amaya.ls() + + # Get all content from the instance + instance_lookup = set(cmds.ls(instance, type="transform", long=True)) + alembic_data = defaultdict(list) + + for container in containers: + + members = cmds.sets(container["objectName"], query=True) + transforms = cmds.ls(members, type="transform", long=True) + if not transforms: + self.log.warning("Container is invalid, missing transform:" + "%s", container["objectName"]) + continue + if len(transforms) > 1: + self.log.warning("Container is invalid, more than one " + "transform: %s", container['objectName']) + continue + + root = transforms[0] + if root not in instance_lookup: + continue + + representation_id = container["representation"] + matrix = cmds.xform(root, query=True, matrix=True) + + # Gather info for new data entry + reference_node = cmds.ls(members, type="reference")[0] + namespace = cmds.referenceQuery(reference_node, namespace=True) + alembic_data[representation_id].append({ + "loader": container["loader"], + "matrix": matrix, + "namespace": namespace + }) + + instance.data["scenedata"] = dict(alembic_data) + + def get_file_rule(self, rule): + return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) From c3fca3a4004f722f16243b8e7131337db028c7ee Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:49:21 +0200 Subject: [PATCH 0288/1249] cosmetics --- colorbleed/plugins/maya/load/load_model.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 15637c9e81..7df8a0421c 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -17,6 +17,9 @@ class ModelLoader(api.Loader): import maya.cmds as cmds from avalon import maya + # Ensure Alembic is loaded + cmds.loadPlugin("AbcImport", quiet=True) + # Create a readable namespace # Namespace should contain asset name and counter # TEST_001{_descriptor} where `descriptor` can be `_abc` for example @@ -31,4 +34,4 @@ class ModelLoader(api.Loader): groupReference=True, groupName="{}:{}".format(namespace, name)) - self[:] = nodes \ No newline at end of file + self[:] = nodes From 70571e6610116c25822a40a2a87564703def7fd6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:50:10 +0200 Subject: [PATCH 0289/1249] added default value for start and endframe if dict has no value --- colorbleed/plugins/maya/publish/extract_pointcache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index bd80029010..727649ccf5 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -25,8 +25,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): nodes = instance[:] # Collect the start and end including handles - start = instance.data["startFrame"] - end = instance.data["endFrame"] + start = instance.data.get("startFrame", 1) + end = instance.data.get("endFrame", 1) handles = instance.data.get("handles", 0) if handles: start -= handles From d333caaf5809c37c88afc94c2685d8923e3fcaef Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:50:37 +0200 Subject: [PATCH 0290/1249] added setdress extractor --- .../plugins/maya/publish/extract_setdress.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index e69de29bb2..5e1d13abef 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -0,0 +1,54 @@ +import json + +import os + +import colorbleed.api +from colorbleed.maya.lib import extract_alembic + +from maya import cmds + + +class ExtractSetDress(colorbleed.api.Extractor): + """Produce an alembic of just point positions and normals. + + Positions and normals are preserved, but nothing more, + for plain and predictable point caches. + + """ + + label = "Extract Set Dress" + hosts = ["maya"] + families = ["colorbleed.setdress"] + + def process(self, instance): + + # Dump json + self.log.info("Dumping scene data for debugging ..") + + data = instance.data + + self.log.info("Extracting point cache") + + parent_dir = self.staging_dir(instance) + filename = "{}.abc".format(instance.name) + path = os.path.join(parent_dir, filename) + json_filename = "{}.json".format(instance.name) + json_path = os.path.join(parent_dir, json_filename) + + with open(json_path, "w") as fp: + json.dump(data["scenedata"], fp, ensure_ascii=False, indent=4) + + cmds.select(instance) + + # Run basic alembic exporter + extract_alembic(file=path, + startFrame=1.0, + endFrame=1.0, + **{"step": 1.0, + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "uvWrite": True, + "selection": True}) + + instance.data["files"] = [json_path, path] From 23f1c2467d465e8d2f7e6fa0fd746c62fe3f9472 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 14:58:29 +0200 Subject: [PATCH 0291/1249] implemented discussed `.get(value, def_value)` --- .../plugins/maya/publish/validate_rendersettings.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 56fa9197fa..be1911d119 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -23,10 +23,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): families = ["colorbleed.renderlayer"] DEFAULT_PADDING = 4 - DEFAULT_PREFIX = { - "vray": "/_/", - "arnold": "/_/" - } + RENDERER_PREFIX = {"vray": "/_/"} + DEFAULT_PREFIX = "/_/" def process(self, instance): @@ -42,9 +40,9 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") - filename_prefix = self.DEFAULT_PREFIX[renderer] - assert prefix == filename_prefix, ( - "Wrong file name prefix, expecting %s" % filename_prefix + fname_prefix = self.RENDERER_PREFIX.get(renderer, self.DEFAULT_PREFIX) + assert prefix == fname_prefix, ( + "Wrong file name prefix, expecting %s" % fname_prefix ) assert padding == self.DEFAULT_PADDING, ( "Expecting padding of {} ( {} )".format(self.DEFAULT_PADDING, From c25f7c92dd8facc38f5410385fd7e86d7ebf5931 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 9 Oct 2017 16:29:42 +0200 Subject: [PATCH 0292/1249] restored switching from render layers in collect_look --- colorbleed/plugins/maya/publish/collect_look.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 6e4e17c535..02aa15c835 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -68,7 +68,7 @@ class CollectLook(pyblish.api.InstancePlugin): def process(self, instance): """Collect the Look in the instance with the correct layer settings""" - with context.renderlayer("defaultRenderLayer"): + with context.renderlayer(instance.data["renderlayer"]): self.collect(instance) def collect(self, instance): @@ -98,6 +98,7 @@ class CollectLook(pyblish.api.InstancePlugin): verbose) if not member_data: continue + sets[objset]["members"].append(member_data) # Remove sets that didn't have any members assigned in the end From 7e922275921a87a900ad3d5da654228b457ff08b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:13:22 +0200 Subject: [PATCH 0293/1249] added assign look ui to menu --- colorbleed/maya/menu.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index eb26bb0f08..e29c231c79 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -808,6 +808,20 @@ "title": "Set filename prefix", "tooltip": "Set the render file name prefix." }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\assign_look_ui.py", + "sourcetype": "file", + "tags": [ + "shading", + "lookdev", + "assign", + "shaders", + "auto" + ], + "title": "Assign Look UI", + "tooltip": "Open the Assign Look UI for custom look assignment" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", From 606459f1d4850263517fb4e597eeb97d4bc6e68b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:17:51 +0200 Subject: [PATCH 0294/1249] added matrix constant, added function for container transforms --- colorbleed/maya/lib.py | 93 ++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 968560b461..d752ca8e3e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -13,6 +13,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel from avalon import maya, io +from cb.utils.maya import core log = logging.getLogger(__name__) @@ -42,6 +43,42 @@ SHAPE_ATTRS = ["castsShadows", SHAPE_ATTRS = set(SHAPE_ATTRS) +DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + +# The maya alembic export types +_alembic_options = { + "startFrame": float, + "endFrame": float, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "eulerFilter": bool, + "frameRelativeSample": float, + "noNormals": bool, + "renderableOnly": bool, + "step": float, + "stripNamespaces": bool, + "uvWrite": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeVisibility": bool, + "writeColorSets": bool, + "writeFaceSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "dataFormat": str, + "root": (list, tuple), + "attr": (list, tuple), + "attrPrefix": (list, tuple), + "userAttr": (list, tuple), + "melPerFrameCallback": str, + "melPostJobCallback": str, + "pythonPerFrameCallback": str, + "pythonPostJobCallback": str, + "selection": bool +} + + def unique(name): assert isinstance(name, basestring), "`name` must be string" @@ -362,37 +399,6 @@ def is_visible(node, return True -# The maya alembic export types -_alembic_options = { - "startFrame": float, - "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame - "eulerFilter": bool, - "frameRelativeSample": float, - "noNormals": bool, - "renderableOnly": bool, - "step": float, - "stripNamespaces": bool, - "uvWrite": bool, - "wholeFrameGeo": bool, - "worldSpace": bool, - "writeVisibility": bool, - "writeColorSets": bool, - "writeFaceSets": bool, - "writeCreases": bool, # Maya 2015 Ext1+ - "dataFormat": str, - "root": (list, tuple), - "attr": (list, tuple), - "attrPrefix": (list, tuple), - "userAttr": (list, tuple), - "melPerFrameCallback": str, - "melPostJobCallback": str, - "pythonPerFrameCallback": str, - "pythonPostJobCallback": str, - "selection": bool -} - - def extract_alembic(file, startFrame=None, endFrame=None, @@ -1017,3 +1023,28 @@ def get_related_sets(node): sets = [s for s in sets if s not in defaults] return sets + + +def get_container_transfroms(container, members=None, root=False): + """Retrieve the root node of the container content + + When a container is created through a Loader the content + of the file will be grouped under a transform. The name of the root + transform is stored in the container information + + Args: + container (dict): the container + members (list): optional and convenience argument + + Returns: + root (str): highest node in hierarchy + """ + + if not members: + members = cmds.sets(container["objectName"], query=True) + + results = cmds.ls(members, type="transform", long=True) + if root: + results = core.getHighestInHierarchy(results)[0] + + return results From fc0933f0827d3dbcc121df41e037195a08e93144 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:18:25 +0200 Subject: [PATCH 0295/1249] implemented matrix constant from lib --- .../maya/publish/validate_rig_controllers.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 465c6d3f7b..f504a0f47e 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib log = logging.getLogger("Rig Controllers") @@ -78,13 +79,10 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @staticmethod def validate_transforms(control): tolerance = 1e-30 - identity = [1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0] matrix = cmds.xform(control, query=True, matrix=True, objectSpace=True) - if not all(abs(x - y) < tolerance for x, y in zip(identity, matrix)): + if not all(abs(x - y) < tolerance for x, y in zip(lib.DEFAULT_MATRIX, + matrix)): log.error("%s matrix : %s" % (control, matrix)) return False return True @@ -106,11 +104,6 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - identity = [1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0] - # lock all controllers in controls_SET controls = cmds.sets("controls_SET", query=True) for control in controls: @@ -123,4 +116,6 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): log.info("Repairing matrix") if not cls.validate_transforms(control): - cmds.xform(control, matrix=identity, objectSpace=True) + cmds.xform(control, + matrix=lib.DEFAULT_MATRIX, + objectSpace=True) From 9145371a2633e9eb325af493226521e845195b44 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:19:12 +0200 Subject: [PATCH 0296/1249] added collector for setdress --- .../plugins/maya/publish/collect_setdress.py | 55 +++++++++++++------ 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index c3865726b3..be3c3b9092 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -3,6 +3,7 @@ import pyblish.api from maya import cmds, mel from avalon import maya as amaya +from colorbleed.maya import lib class CollectSetDress(pyblish.api.InstancePlugin): @@ -27,38 +28,56 @@ class CollectSetDress(pyblish.api.InstancePlugin): # Get all content from the instance instance_lookup = set(cmds.ls(instance, type="transform", long=True)) - alembic_data = defaultdict(list) + data = defaultdict(list) for container in containers: - members = cmds.sets(container["objectName"], query=True) - transforms = cmds.ls(members, type="transform", long=True) - if not transforms: - self.log.warning("Container is invalid, missing transform:" - "%s", container["objectName"]) - continue - if len(transforms) > 1: - self.log.warning("Container is invalid, more than one " - "transform: %s", container['objectName']) - continue - - root = transforms[0] + transforms = lib.get_container_transfroms(container, members) + root = lib.get_container_transfroms(container, members, root=True) if root not in instance_lookup: continue representation_id = container["representation"] - matrix = cmds.xform(root, query=True, matrix=True) + shapes = [m for m in members if + cmds.objectType(m, isAType="shape") is True] + + look_ids = [self.get_look_id(shape) for shape in shapes] + + # Support for re-opened setdress scenes where the only connected + # transform node is the root node. This is due to how references + # are loaded in combination with the containers. + + matrix_data = self.get_matrix_data(transforms) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] namespace = cmds.referenceQuery(reference_node, namespace=True) - alembic_data[representation_id].append({ + data[representation_id].append({ "loader": container["loader"], - "matrix": matrix, - "namespace": namespace + "matrix": matrix_data, + "namespace": namespace, + "look_id": look_ids }) - instance.data["scenedata"] = dict(alembic_data) + instance.data["scenedata"] = dict(data) def get_file_rule(self, rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) + + def get_matrix_data(self, members): + + matrix_data = [] + for member in members: + self.log.info(member) + matrix = cmds.xform(member, query=True, matrix=True) + matrix_data.append(matrix) + + return matrix_data + + def get_look_id(self, node): + """Get the look id of the assigned shader""" + shad_engine = cmds.listConnections(node, type="shadingEngine") + if not shad_engine or shad_engine == "initialShadingEngine": + return + + return cmds.getAttr("{}.cbId".format(shad_engine[0])).split(":")[0] From f53184c193a92af3ce759fb55707f76bd927feec Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:19:59 +0200 Subject: [PATCH 0297/1249] added setdress loader --- .../maya/load/load_setdress_rebuild.py | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index 852410b502..5a422c9667 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -1,7 +1,4 @@ -import pprint - -from avalon import api, io, pipeline -from avalon.tools.cbloader import lib +from avalon import api class SetDressRebuild(api.Loader): @@ -17,30 +14,39 @@ class SetDressRebuild(api.Loader): def process(self, name, namespace, context, data): import json - # from maya import cmds - - print ">>>", lib.__file__ + from maya import cmds + from avalon.tools.cbloader import lib + from colorbleed.maya import lib as clib # Ensure data_file = self.fname.replace(".abc", ".json") with open(data_file, "r") as fp: build_data = json.load(fp) - pprint.pprint(build_data) - for _id, instances in build_data.items(): - # Rebuild filename + for representation_id, instances in build_data.items(): + + # Find the corresponding loader + loaders = list(lib.iter_loaders(representation_id)) + + # Ensure context can be passed on for inst in instances: - nodes = self.run_loader(_id) - # cmds.xform(nodes, matrix=inst["matrix"]) + # Get the uses loader + Loader = next((x for x in loaders + if x.__name__ == inst['loader']), + None) - def run_loader(self, _id): - # get all registered plugins - obj_id = io.ObjectId(_id) - loader_inst = lib.iter_loaders(obj_id) - if loader_inst is None: - raise RuntimeError("Could not find matching loader") + if Loader is None: + self.log.warning("Loader is missing: %s. Skipping %s", + inst['loader'], inst) + continue - # strip the generator layer from the found loader - loader = list(loader_inst)[0] - context = lib.get_representation_context(obj_id) - loader.process(**context) + # Run the loader + container = lib.run_loader(Loader, representation_id, + namespace=inst['namespace']) + + # Apply transformations + container_data = {"objectName": container} + transforms = clib.get_container_transfroms(container_data, + root=True) + for transform, matrix in zip(transforms, inst["matrix"]): + cmds.xform(transform, matrix=matrix) From 74b100b1138c12e376444522db0153c72ad32361 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 10:20:22 +0200 Subject: [PATCH 0298/1249] added setdress extract --- .../plugins/maya/publish/extract_setdress.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index 5e1d13abef..4be7dcbe29 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -22,22 +22,20 @@ class ExtractSetDress(colorbleed.api.Extractor): def process(self, instance): - # Dump json - self.log.info("Dumping scene data for debugging ..") - - data = instance.data - - self.log.info("Extracting point cache") - parent_dir = self.staging_dir(instance) filename = "{}.abc".format(instance.name) path = os.path.join(parent_dir, filename) json_filename = "{}.json".format(instance.name) json_path = os.path.join(parent_dir, json_filename) - with open(json_path, "w") as fp: - json.dump(data["scenedata"], fp, ensure_ascii=False, indent=4) + self.log.info("Dumping scene data for debugging ..") + with open(json_path, "w") as filepath: + json.dump(instance.data["scenedata"], + filepath, + ensure_ascii=False, + indent=4) + self.log.info("Extracting point cache ..") cmds.select(instance) # Run basic alembic exporter @@ -52,3 +50,5 @@ class ExtractSetDress(colorbleed.api.Extractor): "selection": True}) instance.data["files"] = [json_path, path] + + cmds.select(clear=True) From 640e8e6eb71f899b80ea8146ee532b18852688c5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 11:08:24 +0200 Subject: [PATCH 0299/1249] string to bson.ObjectId conversion by default --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index d752ca8e3e..cdfc0353bd 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -767,7 +767,7 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset - subset = io.find({"parent": asset_id, + subset = io.find({"parent": bson.ObjectId(asset_id), "type": "subset", "name": {"$regex": "look*"}}) From 3a4d89c389ee779dddd53b9eec30a94ea4c93655 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 14:26:02 +0200 Subject: [PATCH 0300/1249] integrate is all string, sorting transforms --- .../plugins/maya/publish/collect_setdress.py | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index be3c3b9092..942e91c090 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -5,6 +5,8 @@ from maya import cmds, mel from avalon import maya as amaya from colorbleed.maya import lib +import pprint + class CollectSetDress(pyblish.api.InstancePlugin): """Collect all relevant setdress items @@ -32,31 +34,23 @@ class CollectSetDress(pyblish.api.InstancePlugin): for container in containers: members = cmds.sets(container["objectName"], query=True) - transforms = lib.get_container_transfroms(container, members) - root = lib.get_container_transfroms(container, members, root=True) + transforms = lib.get_container_transforms(container, members) + root = lib.get_container_transforms(container, transforms, + root=True) if root not in instance_lookup: continue - representation_id = container["representation"] - shapes = [m for m in members if - cmds.objectType(m, isAType="shape") is True] - - look_ids = [self.get_look_id(shape) for shape in shapes] - - # Support for re-opened setdress scenes where the only connected - # transform node is the root node. This is due to how references - # are loaded in combination with the containers. - - matrix_data = self.get_matrix_data(transforms) + # retrieve all matrix data + matrix_data = self.get_matrix_data(sorted(transforms)) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] namespace = cmds.referenceQuery(reference_node, namespace=True) + representation_id = container["representation"] data[representation_id].append({ "loader": container["loader"], "matrix": matrix_data, - "namespace": namespace, - "look_id": look_ids + "namespace": namespace }) instance.data["scenedata"] = dict(data) @@ -66,18 +60,12 @@ class CollectSetDress(pyblish.api.InstancePlugin): def get_matrix_data(self, members): - matrix_data = [] - for member in members: - self.log.info(member) + matrix_data = {} + for idx, member in enumerate(members): matrix = cmds.xform(member, query=True, matrix=True) - matrix_data.append(matrix) + if matrix == lib.DEFAULT_MATRIX: + continue + + matrix_data[str(idx)] = matrix return matrix_data - - def get_look_id(self, node): - """Get the look id of the assigned shader""" - shad_engine = cmds.listConnections(node, type="shadingEngine") - if not shad_engine or shad_engine == "initialShadingEngine": - return - - return cmds.getAttr("{}.cbId".format(shad_engine[0])).split(":")[0] From 5fc5c26e6e268ea4b2d0b0860063de1ac39f3e1c Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 14:26:51 +0200 Subject: [PATCH 0301/1249] sorting tranforms to match collector data --- .../maya/load/load_setdress_rebuild.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index 5a422c9667..6a4e9aa316 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -7,7 +7,7 @@ class SetDressRebuild(api.Loader): representations = ["abc"] label = "Rebuild Set Dress" - order = -9 + order = -10 icon = "code-fork" color = "orange" @@ -31,8 +31,8 @@ class SetDressRebuild(api.Loader): # Ensure context can be passed on for inst in instances: # Get the uses loader - Loader = next((x for x in loaders - if x.__name__ == inst['loader']), + Loader = next((x for x in loaders if + x.__name__ == inst['loader']), None) if Loader is None: @@ -41,12 +41,18 @@ class SetDressRebuild(api.Loader): continue # Run the loader - container = lib.run_loader(Loader, representation_id, - namespace=inst['namespace']) + namespace = inst['namespace'].strip(":") + container = lib.run_loader(Loader, + representation_id, + namespace=namespace) # Apply transformations + if not inst["matrix"]: + continue + container_data = {"objectName": container} - transforms = clib.get_container_transfroms(container_data, - root=True) - for transform, matrix in zip(transforms, inst["matrix"]): - cmds.xform(transform, matrix=matrix) + transforms = clib.get_container_transforms(container_data) + # Force sort order, similar to collector + transforms = sorted(transforms) + for idx, matrix in inst["matrix"].items(): + cmds.xform(transforms[int(idx)], matrix=matrix) From d9bfbaea98a2a52c7112f41a3b192308cdff1eb8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 14:28:45 +0200 Subject: [PATCH 0302/1249] cosmetics --- colorbleed/maya/lib.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index cdfc0353bd..db89b7c403 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -805,8 +805,7 @@ def assign_look_by_version(nodes, version_id): log.info("Loading lookdev for the first time..") # Define namespace - assetname = shader_file['context']['asset'] - ns_assetname = "{}_".format(assetname) + ns_assetname = "{}_".format(shader_file['context']['asset']) namespace = maya.unique_namespace(ns_assetname, format="%03d", suffix="_look") @@ -822,8 +821,7 @@ def assign_look_by_version(nodes, version_id): # give along a fake "context" with only `representation` # because `maya.containerise` only used that key anyway context = {"representation": shader_file} - subset_name = shader_file["context"]["subset"] - maya.containerise(name=subset_name, + maya.containerise(name=shader_file["context"]["subset"], namespace=namespace, nodes=shader_nodes, context=context) @@ -1025,7 +1023,7 @@ def get_related_sets(node): return sets -def get_container_transfroms(container, members=None, root=False): +def get_container_transforms(container, members=None, root=False): """Retrieve the root node of the container content When a container is created through a Loader the content @@ -1037,7 +1035,7 @@ def get_container_transfroms(container, members=None, root=False): members (list): optional and convenience argument Returns: - root (str): highest node in hierarchy + root (list / str): highest node in hierarchy """ if not members: From 8ca4117cbdb4fbf76199ded11e64ff5f71791df2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 15:06:17 +0200 Subject: [PATCH 0303/1249] reduced data for version in database --- colorbleed/plugins/publish/integrate.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 43b29edc63..e26d45350b 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -319,9 +319,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): current_families = instance.data.get("families", list()) instance_family = instance.data.get("family", None) - families += current_families if instance_family is not None: families.append(instance_family) + families += current_families # create relative source path for DB relative_path = os.path.relpath(context.data["currentFile"], @@ -334,4 +334,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "source": source, "comment": context.data.get("comment")} - return dict(instance.data, **version_data) + # Include optional data if present in + optionals = ["startFrame", "endFrame", "step", "handles"] + for key in optionals: + if key in instance.data: + version_data[key] = instance.data[key] + + return version_data From be74fc99217982a6643c29d519fe175b17d0d3ea Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 15:07:45 +0200 Subject: [PATCH 0304/1249] removed type conversion for key in matrix_data --- colorbleed/plugins/maya/publish/collect_setdress.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 942e91c090..cdd4f851a1 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -42,6 +42,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): # retrieve all matrix data matrix_data = self.get_matrix_data(sorted(transforms)) + pprint.pprint(matrix_data) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] @@ -65,7 +66,6 @@ class CollectSetDress(pyblish.api.InstancePlugin): matrix = cmds.xform(member, query=True, matrix=True) if matrix == lib.DEFAULT_MATRIX: continue - - matrix_data[str(idx)] = matrix + matrix_data[idx] = matrix return matrix_data From de3c663adeb91686a68a7a1f907ba663a7f03c11 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 15:10:26 +0200 Subject: [PATCH 0305/1249] removed indent from json dump, remove scenedata after extract --- colorbleed/plugins/maya/publish/extract_setdress.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index 4be7dcbe29..5d0e5cdb49 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -30,10 +30,7 @@ class ExtractSetDress(colorbleed.api.Extractor): self.log.info("Dumping scene data for debugging ..") with open(json_path, "w") as filepath: - json.dump(instance.data["scenedata"], - filepath, - ensure_ascii=False, - indent=4) + json.dump(instance.data["scenedata"], filepath, ensure_ascii=False) self.log.info("Extracting point cache ..") cmds.select(instance) @@ -51,4 +48,7 @@ class ExtractSetDress(colorbleed.api.Extractor): instance.data["files"] = [json_path, path] + # Remove data + instance.data.pop("scenedata", None) + cmds.select(clear=True) From 9bea6535a1200e9d68389bce7f775de50fe08403 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 11 Oct 2017 15:10:45 +0200 Subject: [PATCH 0306/1249] removed prints --- colorbleed/plugins/maya/publish/collect_setdress.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index cdd4f851a1..96c3d6a477 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -42,7 +42,6 @@ class CollectSetDress(pyblish.api.InstancePlugin): # retrieve all matrix data matrix_data = self.get_matrix_data(sorted(transforms)) - pprint.pprint(matrix_data) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] From cec5b6908033a5d8e58d6e32736c3b18c04336a6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 12 Oct 2017 11:16:04 +0200 Subject: [PATCH 0307/1249] removed namespace altering, falling back to default --- .../{load_animation.py => load_alembic.py} | 52 +++++++++++-------- colorbleed/plugins/maya/load/load_look.py | 8 --- .../plugins/maya/load/load_mayaascii.py | 6 --- colorbleed/plugins/maya/load/load_model.py | 9 ---- colorbleed/plugins/maya/load/load_rig.py | 4 +- colorbleed/plugins/maya/load/load_setdress.py | 32 ------------ .../maya/load/load_setdress_rebuild.py | 32 +++++++----- 7 files changed, 50 insertions(+), 93 deletions(-) rename colorbleed/plugins/maya/load/{load_animation.py => load_alembic.py} (50%) delete mode 100644 colorbleed/plugins/maya/load/load_setdress.py diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_alembic.py similarity index 50% rename from colorbleed/plugins/maya/load/load_animation.py rename to colorbleed/plugins/maya/load/load_alembic.py index b9f187655d..6cea82a832 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -1,18 +1,14 @@ -import os - - from avalon import api -class AbcLoader(api.Loader): +class AnimationLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", "colorbleed.camera", "colorbleed.pointcache"] - representations = ["abc"] - label = "Reference animation" + representations = ["abc"] order = -10 icon = "code-fork" color = "orange" @@ -20,23 +16,8 @@ class AbcLoader(api.Loader): def process(self, name, namespace, context, data): import maya.cmds as cmds - from avalon import maya cmds.loadPlugin("AbcImport.mll", quiet=True) - # Prevent identical alembic nodes from being shared - # Create unique namespace for the cameras - - # Get name from asset being loaded - # Assuming name is subset name from the animation, we split the number - # suffix from the name to ensure the namespace is unique - name = name.split("_")[0] - namespace = maya.unique_namespace("{}_".format(name), - format="%03d", - suffix="_abc") - - # hero_001 (abc) - # asset_counter{optional} - nodes = cmds.file(self.fname, namespace=namespace, sharedReferenceFile=False, @@ -45,5 +26,30 @@ class AbcLoader(api.Loader): reference=True, returnNewNodes=True) - # load colorbleed ID attribute - self[:] = nodes \ No newline at end of file + self[:] = nodes + + +class SetDressAlembicLoader(api.Loader): + """Load the setdress as alembic""" + + families = ["colorbleed.setdress"] + label = "Reference Alembic" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def process(self, name, namespace, context, data): + + import maya.cmds as cmds + + cmds.loadPlugin("AbcImport.mll", quiet=True) + nodes = cmds.file(self.fname, + namespace=namespace, + sharedReferenceFile=False, + groupReference=True, + groupName="{}:{}".format(namespace, name), + reference=True, + returnNewNodes=True) + + self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 6a265b64ce..03fe7fe7b5 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -32,14 +32,6 @@ class LookLoader(api.Loader): from avalon import maya import colorbleed.maya.lib as lib - # improve readability of the namespace - assetname = context["asset"]["name"] - ns_assetname = "{}_".format(assetname) - - namespace = maya.unique_namespace(ns_assetname, - format="%03d", - suffix="_look") - # try / except here is to ensure that the get_reference_node # does not fail when the file doesn't exist yet reference_node = None diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/colorbleed/plugins/maya/load/load_mayaascii.py index 7bc75fca45..bce8ab611e 100644 --- a/colorbleed/plugins/maya/load/load_mayaascii.py +++ b/colorbleed/plugins/maya/load/load_mayaascii.py @@ -17,12 +17,6 @@ class MayaAsciiLoader(api.Loader): import maya.cmds as cmds from avalon import maya - # Create a readable namespace - # Namespace should contain asset name and counter - # TEST_001{_descriptor} where `descriptor` can be `_abc` for example - assetname = "{}_".format(namespace.split("_")[0]) - namespace = maya.unique_namespace(assetname, format="%03d") - with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 7df8a0421c..366cb93288 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -17,15 +17,6 @@ class ModelLoader(api.Loader): import maya.cmds as cmds from avalon import maya - # Ensure Alembic is loaded - cmds.loadPlugin("AbcImport", quiet=True) - - # Create a readable namespace - # Namespace should contain asset name and counter - # TEST_001{_descriptor} where `descriptor` can be `_abc` for example - assetname = "{}_".format(namespace.split("_")[0]) - namespace = maya.unique_namespace(assetname, format="%03d") - with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 9fb0ed9981..894dadf6bb 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -22,8 +22,6 @@ class RigLoader(api.Loader): def process(self, name, namespace, context, data): - assetname = "{}_".format(context["asset"]["name"]) - unique_namespace = maya.unique_namespace(assetname, format="%03d") nodes = cmds.file(self.fname, namespace=namespace, reference=True, @@ -34,7 +32,7 @@ class RigLoader(api.Loader): # Store for post-process self[:] = nodes if data.get("post_process", True): - self._post_process(name, unique_namespace, context, data) + self._post_process(name, namespace, context, data) def _post_process(self, name, namespace, context, data): from avalon import maya diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/colorbleed/plugins/maya/load/load_setdress.py deleted file mode 100644 index 2ad1a58339..0000000000 --- a/colorbleed/plugins/maya/load/load_setdress.py +++ /dev/null @@ -1,32 +0,0 @@ -from avalon import api, io - - -class SetDressAlembicLoader(api.Loader): - """Load the setdress as alembic""" - - families = ["colorbleed.setdress"] - representations = ["abc"] - - label = "Load Alembic" - order = -10 - icon = "code-fork" - color = "orange" - - def process(self, name, namespace, context, data): - - import maya.cmds as cmds - from avalon import maya - - namespace = maya.unique_namespace("{}_".format(name), - format="%03d", - suffix="_abc") - - with maya.maintained_selection(): - nodes = cmds.file(self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName="{}:{}".format(namespace, name)) - - self[:] = nodes diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index 6a4e9aa316..00544bf938 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -4,10 +4,10 @@ from avalon import api class SetDressRebuild(api.Loader): families = ["colorbleed.setdress"] - representations = ["abc"] + representations = ["json"] label = "Rebuild Set Dress" - order = -10 + order = -9 icon = "code-fork" color = "orange" @@ -17,12 +17,14 @@ class SetDressRebuild(api.Loader): from maya import cmds from avalon.tools.cbloader import lib from colorbleed.maya import lib as clib + from cb.utils.maya import core - # Ensure - data_file = self.fname.replace(".abc", ".json") - with open(data_file, "r") as fp: + with open(self.fname, "r") as fp: build_data = json.load(fp) + cmds.namespace(add=namespace) + + new_transforms = [] for representation_id, instances in build_data.items(): # Find the corresponding loader @@ -30,7 +32,7 @@ class SetDressRebuild(api.Loader): # Ensure context can be passed on for inst in instances: - # Get the uses loader + # Get the used loader Loader = next((x for x in loaders if x.__name__ == inst['loader']), None) @@ -41,18 +43,24 @@ class SetDressRebuild(api.Loader): continue # Run the loader - namespace = inst['namespace'].strip(":") + instance_ns = ":".join([namespace, + inst['namespace'].strip(":")]) container = lib.run_loader(Loader, representation_id, - namespace=namespace) - - # Apply transformations - if not inst["matrix"]: - continue + namespace=instance_ns) container_data = {"objectName": container} transforms = clib.get_container_transforms(container_data) # Force sort order, similar to collector transforms = sorted(transforms) + for idx, matrix in inst["matrix"].items(): cmds.xform(transforms[int(idx)], matrix=matrix) + + # Store the created container + self.append(container) + new_transforms.extend(transforms) + + roots = core.getHighestInHierarchy(new_transforms) + cmds.group(roots, name="{}:{}".format(namespace, + context['subset']['name'])) From bf289ab75bc26918c20b825ff757dc4cef541ff1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 12 Oct 2017 11:17:48 +0200 Subject: [PATCH 0308/1249] stripping namespace from the root `:` --- colorbleed/plugins/maya/publish/collect_setdress.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 96c3d6a477..42bf040f47 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -5,8 +5,6 @@ from maya import cmds, mel from avalon import maya as amaya from colorbleed.maya import lib -import pprint - class CollectSetDress(pyblish.api.InstancePlugin): """Collect all relevant setdress items @@ -50,7 +48,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): data[representation_id].append({ "loader": container["loader"], "matrix": matrix_data, - "namespace": namespace + "namespace": namespace.strip(":") }) instance.data["scenedata"] = dict(data) From b40af5d190436c3a60c8de72efe8b047d59749b2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 13 Oct 2017 17:45:53 +0200 Subject: [PATCH 0309/1249] implemented monkey code --- .../maya/load/load_setdress_rebuild.py | 62 +++++-------------- 1 file changed, 14 insertions(+), 48 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index 00544bf938..b616d42893 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -1,5 +1,9 @@ +import site + from avalon import api +site.addsitedir(r"C:\Users\User\Documents\development\research\setdress") + class SetDressRebuild(api.Loader): @@ -13,54 +17,16 @@ class SetDressRebuild(api.Loader): def process(self, name, namespace, context, data): - import json from maya import cmds - from avalon.tools.cbloader import lib - from colorbleed.maya import lib as clib - from cb.utils.maya import core + import avalon.maya as amaya - with open(self.fname, "r") as fp: - build_data = json.load(fp) + context_ns = context["subset"]["name"] + with amaya.maintained_selection(): + file_nodes = cmds.file(self.fname, + namespace=context_ns, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(context_ns, name)) - cmds.namespace(add=namespace) - - new_transforms = [] - for representation_id, instances in build_data.items(): - - # Find the corresponding loader - loaders = list(lib.iter_loaders(representation_id)) - - # Ensure context can be passed on - for inst in instances: - # Get the used loader - Loader = next((x for x in loaders if - x.__name__ == inst['loader']), - None) - - if Loader is None: - self.log.warning("Loader is missing: %s. Skipping %s", - inst['loader'], inst) - continue - - # Run the loader - instance_ns = ":".join([namespace, - inst['namespace'].strip(":")]) - container = lib.run_loader(Loader, - representation_id, - namespace=instance_ns) - - container_data = {"objectName": container} - transforms = clib.get_container_transforms(container_data) - # Force sort order, similar to collector - transforms = sorted(transforms) - - for idx, matrix in inst["matrix"].items(): - cmds.xform(transforms[int(idx)], matrix=matrix) - - # Store the created container - self.append(container) - new_transforms.extend(transforms) - - roots = core.getHighestInHierarchy(new_transforms) - cmds.group(roots, name="{}:{}".format(namespace, - context['subset']['name'])) + self[:] = file_nodes From f07e0a31a4e5515285e9d15b401d321406c08b0c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 13 Oct 2017 17:46:47 +0200 Subject: [PATCH 0310/1249] cosmetics --- colorbleed/plugins/maya/publish/collect_setdress.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 42bf040f47..885cdcaf2a 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -33,7 +33,8 @@ class CollectSetDress(pyblish.api.InstancePlugin): for container in containers: members = cmds.sets(container["objectName"], query=True) transforms = lib.get_container_transforms(container, members) - root = lib.get_container_transforms(container, transforms, + root = lib.get_container_transforms(container, + transforms, root=True) if root not in instance_lookup: continue @@ -57,6 +58,16 @@ class CollectSetDress(pyblish.api.InstancePlugin): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) def get_matrix_data(self, members): + """Get the matrix of all members when they are not default + + Each matrix which differs from the default will be stored in a + dictionary + + Args: + members (list): list of transform nmodes + Returns: + dict + """ matrix_data = {} for idx, member in enumerate(members): From 871f8d94f9c404ee685a4039d210679a27b300bd Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 13 Oct 2017 17:48:27 +0200 Subject: [PATCH 0311/1249] updated context renderlayer switching --- colorbleed/plugins/maya/publish/validate_look_sets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 23d03830c5..e73c81fea4 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -45,7 +45,8 @@ class ValidateLookSets(pyblish.api.InstancePlugin): relationships = instance.data["lookData"]["relationships"] invalid = [] - with context.renderlayer("defaultRenderLayer"): + renderlayer = instance.data.get("renderlayer", "defaultRenderLayer") + with context.renderlayer(renderlayer): for node in instance: # get the connected objectSets of the node sets = lib.get_related_sets(node) From da1c7f0756a0093dd43dc82c83ced9f5ddbaf57d Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 11:10:55 +0200 Subject: [PATCH 0312/1249] removed function and cosmetics --- .../plugins/maya/publish/collect_look.py | 57 +++++-------------- 1 file changed, 15 insertions(+), 42 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 02aa15c835..7962656124 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -73,9 +73,6 @@ class CollectLook(pyblish.api.InstancePlugin): def collect(self, instance): - # Whether to log information verbosely - verbose = instance.data.get("verbose", False) - self.log.info("Looking for look associations " "for %s" % instance.data['name']) @@ -89,20 +86,25 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Gathering set relations..") for objset in sets: self.log.debug("From %s.." % objset) - content = cmds.sets(objset, query=True) objset_members = sets[objset]["members"] - for member in cmds.ls(content, long=True): + + # Get all nodes of the current objectSet + for member in cmds.ls(cmds.sets(objset, query=True), long=True): member_data = self.collect_member_data(member, objset_members, - instance_lookup, - verbose) + instance_lookup) if not member_data: continue + # Add information of the node to the members list sets[objset]["members"].append(member_data) - # Remove sets that didn't have any members assigned in the end - sets = self.remove_sets_without_members(sets) + # Remove sets that didn't have any members assigned in the end + # Thus the data will be limited to only what we need. + if not sets[objset]["members"]: + self.log.info("Removing redundant set information: " + "%s" % objset) + sets.pop(objset) self.log.info("Gathering attribute changes to instance members..") @@ -118,6 +120,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect file nodes used by shading engines (if we have any) files = list() if looksets: + # Get the entire node chain of the look sets history = cmds.listHistory(looksets) files = cmds.ls(history, type="file", long=True) @@ -158,32 +161,12 @@ class CollectLook(pyblish.api.InstancePlugin): return sets - def remove_sets_without_members(self, sets): - """Remove any set which does not have any members - - Args: - sets (dict): collection if sets with data as value - - Returns: - dict - """ - - for objset, data in sets.items(): - if not data['members']: - self.log.info("Removing redundant set information: " - "%s" % objset) - sets.pop(objset) - - return sets - - def collect_member_data(self, member, objset_members, instance_members, - verbose=False): + def collect_member_data(self, member, objset_members, instance_members): """Get all information of the node Args: member (str): the name of the node to check objset_members (list): the objectSet members instance_members (set): the collected instance members - verbose (bool): get debug information Returns: dict @@ -194,8 +177,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Only include valid members of the instance if node not in instance_members: - if verbose: - self.log.info("Skipping member %s" % member) + # Skip nodes which are not in the instance members return if member in [m["name"] for m in objset_members]: @@ -205,13 +187,7 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.error("Node '{}' has no attribute 'cbId'".format(node)) return - member_data = {"name": node, "uuid": lib.get_id(node)} - - # Include components information when components are assigned - if components: - member_data["components"] = components - - return member_data + return {"name": node, "uuid": lib.get_id(node)} def collect_attributes_changed(self, instance): """Collect all userDefined attributes which have changed @@ -233,9 +209,6 @@ class CollectLook(pyblish.api.InstancePlugin): attributes = [] for node in instance: - # get history to ignore original shapes - cmds.listHistory(node) - # Collect changes to "custom" attributes node_attrs = get_look_attrs(node) From cadb9f5aa5fd9a060cd8d5b738c440f24170aea1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 11:20:38 +0200 Subject: [PATCH 0313/1249] improved docstrings validator --- colorbleed/plugins/maya/publish/validate_look_sets.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index e73c81fea4..0d16239215 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -19,6 +19,16 @@ class ValidateLookSets(pyblish.api.InstancePlugin): transform. In essence, ensure item the shader is assigned to has the Colorbleed ID! + Displacement shaders: + Ensure all geometry is added to the displacement objectSet. + It is best practice to add the transform group of the shape to the + displacement objectSet + Example content: + [asset_GRP|geometry_GRP|body_GES, + asset_GRP|geometry_GRP|L_eye_GES, + asset_GRP|geometry_GRP|R_eye_GES, + asset_GRP|geometry_GRP|wings_GEOShape] + """ order = colorbleed.api.ValidateContentsOrder From 5e05ae323a32f3ce6886e0d5ab46c843b7cc007a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 12:59:37 +0200 Subject: [PATCH 0314/1249] refactored based on feedback --- .../plugins/maya/publish/collect_look.py | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 7962656124..173a26dc32 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -78,13 +78,15 @@ class CollectLook(pyblish.api.InstancePlugin): # Discover related object sets self.log.info("Gathering sets..") - sets = self.gather_sets(instance) + sets = self.collect_sets(instance) # Lookup with absolute names (from root namespace) instance_lookup = set(cmds.ls(instance, long=True)) self.log.info("Gathering set relations..") - for objset in sets: + # Ensure iteration happen in a list so we can remove keys from the + # dict within the loop + for objset in list(sets): self.log.debug("From %s.." % objset) objset_members = sets[objset]["members"] @@ -104,14 +106,11 @@ class CollectLook(pyblish.api.InstancePlugin): if not sets[objset]["members"]: self.log.info("Removing redundant set information: " "%s" % objset) - sets.pop(objset) + sets.pop(objset, None) self.log.info("Gathering attribute changes to instance members..") attributes = self.collect_attributes_changed(instance) - looksets = cmds.ls(sets.keys(), long=True) - - self.log.info("Found the following sets:\n{}".format(looksets)) # Store data on the instance instance.data["lookData"] = {"attributes": attributes, @@ -119,13 +118,16 @@ class CollectLook(pyblish.api.InstancePlugin): # Collect file nodes used by shading engines (if we have any) files = list() + looksets = sets.keys() if looksets: + self.log.info("Found the following sets:\n{}".format(looksets)) # Get the entire node chain of the look sets history = cmds.listHistory(looksets) files = cmds.ls(history, type="file", long=True) - # Collect textures - instance.data["resources"] = [self.collect_resource(n) for n in files] + # Collect textures if any file nodes are found + instance.data["resources"] = [self.collect_resource(n) + for n in files] # Log a warning when no relevant sets were retrieved for the look. if not instance.data["lookData"]["relationships"]: @@ -134,8 +136,8 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Collected look for %s" % instance) - def gather_sets(self, instance): - """Gather all objectSets which are of importance for publishing + def collect_sets(self, instance): + """Collect all objectSets which are of importance for publishing It checks if all nodes in the instance are related to any objectSet which need to be @@ -187,7 +189,11 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.error("Node '{}' has no attribute 'cbId'".format(node)) return - return {"name": node, "uuid": lib.get_id(node)} + member_data = {"name": node, "uuid": lib.get_id(node)} + if components: + member_data["components"] = components + + return member def collect_attributes_changed(self, instance): """Collect all userDefined attributes which have changed From c18db31a91252fa8a9cc64c45015807190029ac6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 14:08:02 +0200 Subject: [PATCH 0315/1249] updated docstrings to match best practice --- colorbleed/plugins/maya/publish/validate_look_sets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 0d16239215..bf0567cc88 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -27,7 +27,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): [asset_GRP|geometry_GRP|body_GES, asset_GRP|geometry_GRP|L_eye_GES, asset_GRP|geometry_GRP|R_eye_GES, - asset_GRP|geometry_GRP|wings_GEOShape] + asset_GRP|geometry_GRP|wings_GEO] """ From fd78f969f7c4b9c53cf888a082d71e6dda05aa14 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 14:12:23 +0200 Subject: [PATCH 0316/1249] fixed return varaible --- colorbleed/plugins/maya/publish/collect_look.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 173a26dc32..334929b5e6 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -193,7 +193,7 @@ class CollectLook(pyblish.api.InstancePlugin): if components: member_data["components"] = components - return member + return member_data def collect_attributes_changed(self, instance): """Collect all userDefined attributes which have changed From be5ceee226a0580907b020cf3a4e6ad745dd549e Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 16 Oct 2017 14:27:29 +0200 Subject: [PATCH 0317/1249] clean up and optimization --- .../plugins/maya/publish/collect_look.py | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 334929b5e6..2df35499bc 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -30,14 +30,16 @@ def get_look_attrs(node): result = cmds.listAttr(node, userDefined=True, changedSinceFileOpen=True) or [] + # `cbId` is added when a scene is saved, ignore by default + if "cbId" in result: + result.remove("cbId") + # For shapes allow render stat changes if cmds.objectType(node, isAType="shape"): attrs = cmds.listAttr(node, changedSinceFileOpen=True) or [] - valid = [attr for attr in attrs if attr in SHAPE_ATTRS] - result.extend(valid) - - if "cbId" in result: - result.remove("cbId") + for attr in attrs: + if attr in SHAPE_ATTRS: + result.append(attr) return result @@ -80,7 +82,7 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.info("Gathering sets..") sets = self.collect_sets(instance) - # Lookup with absolute names (from root namespace) + # Lookup set (optimization) instance_lookup = set(cmds.ls(instance, long=True)) self.log.info("Gathering set relations..") @@ -88,12 +90,10 @@ class CollectLook(pyblish.api.InstancePlugin): # dict within the loop for objset in list(sets): self.log.debug("From %s.." % objset) - objset_members = sets[objset]["members"] # Get all nodes of the current objectSet for member in cmds.ls(cmds.sets(objset, query=True), long=True): member_data = self.collect_member_data(member, - objset_members, instance_lookup) if not member_data: continue @@ -109,7 +109,6 @@ class CollectLook(pyblish.api.InstancePlugin): sets.pop(objset, None) self.log.info("Gathering attribute changes to instance members..") - attributes = self.collect_attributes_changed(instance) # Store data on the instance @@ -163,11 +162,10 @@ class CollectLook(pyblish.api.InstancePlugin): return sets - def collect_member_data(self, member, objset_members, instance_members): + def collect_member_data(self, member, instance_members): """Get all information of the node Args: member (str): the name of the node to check - objset_members (list): the objectSet members instance_members (set): the collected instance members Returns: @@ -179,17 +177,14 @@ class CollectLook(pyblish.api.InstancePlugin): # Only include valid members of the instance if node not in instance_members: - # Skip nodes which are not in the instance members return - if member in [m["name"] for m in objset_members]: - return - - if not cmds.attributeQuery("cbId", node=node, exists=True): + node_id = lib.get_id(node) + if not node_id: self.log.error("Node '{}' has no attribute 'cbId'".format(node)) return - member_data = {"name": node, "uuid": lib.get_id(node)} + member_data = {"name": node, "uuid": node_id} if components: member_data["components"] = components From a4b0fc8f9aa247a66002d59d7a3b4cde490e6063 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 16 Oct 2017 16:48:41 +0200 Subject: [PATCH 0318/1249] Fix #ANM-5 --- colorbleed/plugins/maya/load/load_rig.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 9fb0ed9981..ee54de2ad6 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -52,16 +52,18 @@ class RigLoader(api.Loader): assert output, "No out_SET in rig, this is a bug." assert controls, "No controls_SET in rig, this is a bug." + # Find the roots amongst the loaded nodes + roots = cmds.ls(self[:], assemblies=True, long=True) + assert roots, "No root nodes in rig, this is a bug." + asset = os.environ["AVALON_ASSET"] - cmds.select([output, controls], noExpand=True) + dependency = str(context["representation"]["_id"]) + + # Create the animation instance with maya.maintained_selection(): - - # TODO(marcus): Hardcoding the family here, better separate this. - dependencies = [context["representation"]["_id"]] - dependencies = " ".join(str(d) for d in dependencies) - + cmds.select([output, controls] + roots, noExpand=True) maya.create(name=namespace, asset=asset, family="colorbleed.animation", options={"useSelection": True}, - data={"dependencies": dependencies}) + data={"dependencies": dependency}) From 95498f586eb56b3e312196ae0ddab5b850cc0e40 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 18 Oct 2017 16:16:53 +0200 Subject: [PATCH 0319/1249] remove filename from preview filename, added separator replace --- colorbleed/maya/lib.py | 104 ++++++++++++------ .../plugins/maya/publish/submit_deadline.py | 6 +- 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 968560b461..3c75e6c14d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -13,6 +13,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel from avalon import maya, io +from cb.utils.maya import core log = logging.getLogger(__name__) @@ -42,6 +43,42 @@ SHAPE_ATTRS = ["castsShadows", SHAPE_ATTRS = set(SHAPE_ATTRS) +DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0] + +# The maya alembic export types +_alembic_options = { + "startFrame": float, + "endFrame": float, + "frameRange": str, # "start end"; overrides startFrame & endFrame + "eulerFilter": bool, + "frameRelativeSample": float, + "noNormals": bool, + "renderableOnly": bool, + "step": float, + "stripNamespaces": bool, + "uvWrite": bool, + "wholeFrameGeo": bool, + "worldSpace": bool, + "writeVisibility": bool, + "writeColorSets": bool, + "writeFaceSets": bool, + "writeCreases": bool, # Maya 2015 Ext1+ + "dataFormat": str, + "root": (list, tuple), + "attr": (list, tuple), + "attrPrefix": (list, tuple), + "userAttr": (list, tuple), + "melPerFrameCallback": str, + "melPostJobCallback": str, + "pythonPerFrameCallback": str, + "pythonPostJobCallback": str, + "selection": bool +} + + def unique(name): assert isinstance(name, basestring), "`name` must be string" @@ -362,37 +399,6 @@ def is_visible(node, return True -# The maya alembic export types -_alembic_options = { - "startFrame": float, - "endFrame": float, - "frameRange": str, # "start end"; overrides startFrame & endFrame - "eulerFilter": bool, - "frameRelativeSample": float, - "noNormals": bool, - "renderableOnly": bool, - "step": float, - "stripNamespaces": bool, - "uvWrite": bool, - "wholeFrameGeo": bool, - "worldSpace": bool, - "writeVisibility": bool, - "writeColorSets": bool, - "writeFaceSets": bool, - "writeCreases": bool, # Maya 2015 Ext1+ - "dataFormat": str, - "root": (list, tuple), - "attr": (list, tuple), - "attrPrefix": (list, tuple), - "userAttr": (list, tuple), - "melPerFrameCallback": str, - "melPostJobCallback": str, - "pythonPerFrameCallback": str, - "pythonPostJobCallback": str, - "selection": bool -} - - def extract_alembic(file, startFrame=None, endFrame=None, @@ -761,7 +767,7 @@ def list_looks(asset_id): # # get all subsets with look leading in # the name associated with the asset - subset = io.find({"parent": asset_id, + subset = io.find({"parent": bson.ObjectId(asset_id), "type": "subset", "name": {"$regex": "look*"}}) @@ -799,8 +805,7 @@ def assign_look_by_version(nodes, version_id): log.info("Loading lookdev for the first time..") # Define namespace - assetname = shader_file['context']['asset'] - ns_assetname = "{}_".format(assetname) + ns_assetname = "{}_".format(shader_file['context']['asset']) namespace = maya.unique_namespace(ns_assetname, format="%03d", suffix="_look") @@ -816,8 +821,7 @@ def assign_look_by_version(nodes, version_id): # give along a fake "context" with only `representation` # because `maya.containerise` only used that key anyway context = {"representation": shader_file} - subset_name = shader_file["context"]["subset"] - maya.containerise(name=subset_name, + maya.containerise(name=shader_file["context"]["subset"], namespace=namespace, nodes=shader_nodes, context=context) @@ -1017,3 +1021,31 @@ def get_related_sets(node): sets = [s for s in sets if s not in defaults] return sets + + +def get_container_transforms(container, members=None, root=False): + """Retrieve the root node of the container content + + When a container is created through a Loader the content + of the file will be grouped under a transform. The name of the root + transform is stored in the container information + + Args: + container (dict): the container + members (list): optional and convenience argument + root (bool): return highest node in hierachy if True + + Returns: + root (list / str): + """ + + if not members: + members = cmds.sets(container["objectName"], query=True) + + results = cmds.ls(members, type="transform", long=True) + if root: + root = core.getHighestInHierarchy(results) + if root: + results = root[0] + + return results diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index ea96fb486c..2a79ef6280 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -131,14 +131,14 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Optional, enable double-click to preview rendered # frames from Deadline Monitor - "OutputFilename0": output_filename_0, + "OutputFilename0": output_filename_0.replace("\\", "/"), }, "PluginInfo": { # Input "SceneFile": fpath, # Output directory and filename - "OutputFilePath": dirname, + "OutputFilePath": dirname.replace("\\", "/"), "OutputFilePrefix": render_variables["filename_prefix"], # Mandatory for Deadline @@ -241,7 +241,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): padded_basename = "{}.{}.{}".format(layer, "#" * padding, ext) scene_layer_folder = "{}_{}".format(scene, layer) preview_fname = os.path.join(folder, scene, scene_layer_folder, - layer, padded_basename) + padded_basename) return preview_fname From 39c5bcac1066083b4620cfe1ea26231c7b03ee77 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 18 Oct 2017 17:02:36 +0200 Subject: [PATCH 0320/1249] quick dump --- colorbleed/maya/lib.py | 7 +++- .../plugins/maya/publish/collect_setdress.py | 41 +++++++++++-------- .../plugins/maya/publish/extract_setdress.py | 10 ++--- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index db89b7c403..3c75e6c14d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1033,9 +1033,10 @@ def get_container_transforms(container, members=None, root=False): Args: container (dict): the container members (list): optional and convenience argument + root (bool): return highest node in hierachy if True Returns: - root (list / str): highest node in hierarchy + root (list / str): """ if not members: @@ -1043,6 +1044,8 @@ def get_container_transforms(container, members=None, root=False): results = cmds.ls(members, type="transform", long=True) if root: - results = core.getHighestInHierarchy(results)[0] + root = core.getHighestInHierarchy(results) + if root: + results = root[0] return results diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 885cdcaf2a..5eaa5822ba 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -27,10 +27,12 @@ class CollectSetDress(pyblish.api.InstancePlugin): containers = amaya.ls() # Get all content from the instance + topnode = cmds.sets(instance.name, query=True)[0] instance_lookup = set(cmds.ls(instance, type="transform", long=True)) data = defaultdict(list) - for container in containers: + hierarchy_nodes = [] + for i, container in enumerate(containers): members = cmds.sets(container["objectName"], query=True) transforms = lib.get_container_transforms(container, members) root = lib.get_container_transforms(container, @@ -39,25 +41,35 @@ class CollectSetDress(pyblish.api.InstancePlugin): if root not in instance_lookup: continue - # retrieve all matrix data - matrix_data = self.get_matrix_data(sorted(transforms)) + # Retrieve all matrix data + hierarchy = cmds.listRelatives(root, parent=True, fullPath=True)[0] + relative_hierarchy = hierarchy.replace(topnode, "*") + hierarchy_nodes.append(relative_hierarchy) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] namespace = cmds.referenceQuery(reference_node, namespace=True) representation_id = container["representation"] - data[representation_id].append({ - "loader": container["loader"], - "matrix": matrix_data, - "namespace": namespace.strip(":") - }) + + instance_data = {"loader": container["loader"], + "hierarchy": hierarchy, + "namespace": namespace.strip(":")} + + # Check if matrix differs from default and store changes + matrix_data = self.get_matrix_data(root) + if matrix_data: + instance_data["matrix"] = matrix_data + + data[representation_id].append(instance_data) instance.data["scenedata"] = dict(data) + instance.data["hierarchy"] = list(set(hierarchy_nodes)) + def get_file_rule(self, rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) - def get_matrix_data(self, members): + def get_matrix_data(self, node): """Get the matrix of all members when they are not default Each matrix which differs from the default will be stored in a @@ -69,11 +81,8 @@ class CollectSetDress(pyblish.api.InstancePlugin): dict """ - matrix_data = {} - for idx, member in enumerate(members): - matrix = cmds.xform(member, query=True, matrix=True) - if matrix == lib.DEFAULT_MATRIX: - continue - matrix_data[idx] = matrix + matrix = cmds.xform(node, query=True, matrix=True) + if matrix == lib.DEFAULT_MATRIX: + return - return matrix_data + return matrix diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index 5d0e5cdb49..d902c2073d 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -23,8 +23,8 @@ class ExtractSetDress(colorbleed.api.Extractor): def process(self, instance): parent_dir = self.staging_dir(instance) - filename = "{}.abc".format(instance.name) - path = os.path.join(parent_dir, filename) + hierarchy_filename = "{}.abc".format(instance.name) + hierarchy_path = os.path.join(parent_dir, hierarchy_filename) json_filename = "{}.json".format(instance.name) json_path = os.path.join(parent_dir, json_filename) @@ -33,10 +33,10 @@ class ExtractSetDress(colorbleed.api.Extractor): json.dump(instance.data["scenedata"], filepath, ensure_ascii=False) self.log.info("Extracting point cache ..") - cmds.select(instance) + cmds.select(cmds.ls(instance.data["hierarchy"], long=True)) # Run basic alembic exporter - extract_alembic(file=path, + extract_alembic(file=hierarchy_path, startFrame=1.0, endFrame=1.0, **{"step": 1.0, @@ -46,7 +46,7 @@ class ExtractSetDress(colorbleed.api.Extractor): "uvWrite": True, "selection": True}) - instance.data["files"] = [json_path, path] + instance.data["files"] = [json_path, hierarchy_path] # Remove data instance.data.pop("scenedata", None) From 4ded1f1cb72e3ad28011eae7c679bf7087e0a3f2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 10:23:17 +0200 Subject: [PATCH 0321/1249] Force save plugin before submitting rendering --- .../increment_current_file_deadline.py | 75 +++++++++++++++++++ colorbleed/plugins/maya/publish/save_scene.py | 21 ++++++ .../publish/validate_deadline_connection.py | 26 +++++++ 3 files changed, 122 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/increment_current_file_deadline.py create mode 100644 colorbleed/plugins/maya/publish/save_scene.py create mode 100644 colorbleed/plugins/maya/publish/validate_deadline_connection.py diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py new file mode 100644 index 0000000000..25746f87fc --- /dev/null +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -0,0 +1,75 @@ +import pyblish.api + + +class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): + """Submit available render layers to Deadline + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable AVALON_DEADLINE + + """ + + label = "Increment current file.." + order = pyblish.api.IntegratorOrder + 99.0 + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, context): + + from maya import cmds + + from colorbleed.action import get_errored_plugins_from_data + + plugins = get_errored_plugins_from_data(context) + + if any(plugin.__name__ == "MindbenderSubmitDeadline" + for plugin in plugins): + raise RuntimeError("Skipping incrementing current file because" + "submission to deadline failed.") + + new_filename = self.version_up(context["currentFile"]) + + cmds.file(rename=new_filename) + cmds.file(save=True, force=True, type="mayaAscii") + + def version_up(self, filepath): + + import os + import re + + dirname = os.path.dirname(filepath) + basename, ext = os.path.splitext(os.path.basename(filepath)) + + regex = "[/_.]" + "v" + "\d+" + matches = re.findall(regex, str(basename), re.IGNORECASE) + if not len(matches): + self.log.info("Creating version ...") + version_str = "_v{number:03d}".format(number=1) + else: + version_label = matches[-1:][0] + basename = basename.strip(version_label) + + current_version = re.search("\d+", version_label).group() + padding = len(current_version) + prefix = version_label.split(current_version)[0] + + version_int = int(current_version) + 1 + version_str = '{prefix}{number:0{padding}d}'.format( + prefix=prefix, + padding=padding, + number=version_int) + # Create new basename + self.log.info("New version %s" % version_str) + new_basename = "{}{}{}".format(basename, version_str, ext) + + new_filename = os.path.join(dirname, new_basename) + new_filename = os.path.normpath(new_filename) + + if new_filename == filepath: + raise RuntimeError("Created path is the same as current file," + "please let someone no") + + if os.path.exists(new_filename): + new_filename = self.version_up(new_filename) + + return new_filename diff --git a/colorbleed/plugins/maya/publish/save_scene.py b/colorbleed/plugins/maya/publish/save_scene.py new file mode 100644 index 0000000000..850ccf2dca --- /dev/null +++ b/colorbleed/plugins/maya/publish/save_scene.py @@ -0,0 +1,21 @@ +import pyblish.api + + +class SaveCurrentScene(pyblish.api.ContextPlugin): + """Save current scene + + """ + + label = "Save current file..." + order = pyblish.api.IntegratorOrder - 0.49 + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, context): + import maya.cmds as cmds + + current = cmds.file(query=True, sceneName=True) + assert context['currentFile'] == current + + self.log.info("Saving current file..") + cmds.file(save=True, force=True) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py new file mode 100644 index 0000000000..7f57e5b96a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -0,0 +1,26 @@ +import pyblish.api + +import avalon.api as api +from avalon.vendor import requests + + +class ValidateDeadlineConnection(pyblish.api.ContextPlugin): + """Validate Deadline Web Service is running""" + + label = "Validate Deadline Web Service" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + + assert AVALON_DEADLINE is not None, "Requires AVALON_DEADLINE" + + # Check response + response = requests.get(AVALON_DEADLINE) + assert response.ok, "Response must be ok" + assert response.text.startswith("Deadline Web Service "), \ + "Web service did not respond with 'Deadline Web Service'" \ No newline at end of file From 7c3b3fcb7a9771398666c18bb926e3f276f34d33 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:06:06 +0200 Subject: [PATCH 0322/1249] using context.data instead of context to get currentFile --- .../plugins/maya/publish/increment_current_file_deadline.py | 2 +- colorbleed/plugins/maya/publish/save_scene.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index 25746f87fc..6db46fb72a 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -27,7 +27,7 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): raise RuntimeError("Skipping incrementing current file because" "submission to deadline failed.") - new_filename = self.version_up(context["currentFile"]) + new_filename = self.version_up(context.data["currentFile"]) cmds.file(rename=new_filename) cmds.file(save=True, force=True, type="mayaAscii") diff --git a/colorbleed/plugins/maya/publish/save_scene.py b/colorbleed/plugins/maya/publish/save_scene.py index 850ccf2dca..b1daf54236 100644 --- a/colorbleed/plugins/maya/publish/save_scene.py +++ b/colorbleed/plugins/maya/publish/save_scene.py @@ -15,7 +15,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): import maya.cmds as cmds current = cmds.file(query=True, sceneName=True) - assert context['currentFile'] == current + assert context.data['currentFile'] == current self.log.info("Saving current file..") cmds.file(save=True, force=True) From 11c41b4e9e264cafb569a358a735ee6b40e9aa8b Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:10:25 +0200 Subject: [PATCH 0323/1249] label change and order change --- colorbleed/plugins/publish/cleanup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/publish/cleanup.py index 58e86d9415..742316094f 100644 --- a/colorbleed/plugins/publish/cleanup.py +++ b/colorbleed/plugins/publish/cleanup.py @@ -9,6 +9,7 @@ class CleanUp(api.InstancePlugin): """ order = api.IntegratorOrder + 10 + label = "Clean Up" def process(self, instance): return From 9302cd93cf94fc385b47ecd517cb9e166a5c167c Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:10:40 +0200 Subject: [PATCH 0324/1249] label change --- .../plugins/maya/publish/increment_current_file_deadline.py | 4 ++-- colorbleed/plugins/maya/publish/save_scene.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index 6db46fb72a..d9913bba25 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -9,8 +9,8 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): """ - label = "Increment current file.." - order = pyblish.api.IntegratorOrder + 99.0 + label = "Increment current file" + order = pyblish.api.IntegratorOrder + 9.0 hosts = ["maya"] families = ["colorbleed.renderlayer"] diff --git a/colorbleed/plugins/maya/publish/save_scene.py b/colorbleed/plugins/maya/publish/save_scene.py index b1daf54236..bf80bc9699 100644 --- a/colorbleed/plugins/maya/publish/save_scene.py +++ b/colorbleed/plugins/maya/publish/save_scene.py @@ -6,7 +6,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): """ - label = "Save current file..." + label = "Save current file" order = pyblish.api.IntegratorOrder - 0.49 hosts = ["maya"] families = ["colorbleed.renderlayer"] From 5b006efcbec376c06a103121d5f5cd12d9eb83be Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:43:33 +0200 Subject: [PATCH 0325/1249] added render attribute constants --- colorbleed/maya/lib.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 3c75e6c14d..efb29ed35c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -40,6 +40,22 @@ SHAPE_ATTRS = ["castsShadows", "doubleSided", "opposite"] +RENDER_ATTRS = {"vray": + { + "node": "vraySettings", + "prefix": "fileNamePrefix", + "padding": "fileNamePadding", + "ext": "imageFormatStr" + }, + "default": + { + "node": "defaultRenderGlobals", + "prefix": "imageFilePrefix", + "padding": "extensionPadding" + } + } + + SHAPE_ATTRS = set(SHAPE_ATTRS) From 1cee730df76f17e952c57874470fce421ae0dcba Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:44:21 +0200 Subject: [PATCH 0326/1249] changed getAttr approach to use render attr constants --- .../maya/publish/validate_rendersettings.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index be1911d119..7313640bed 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -33,12 +33,14 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # Collect the filename prefix in the renderlayer with lib.renderlayer(layer_node): - if renderer == "vray": - prefix = cmds.getAttr("vraySettings.fileNamePrefix") - padding = cmds.getAttr("vraySettings.fileNamePadding") - else: - prefix = cmds.getAttr("defaultRenderGlobals.fileNamePrefix") - padding = cmds.getAttr("defaultRenderGlobals.fileNamePadding") + + render_attrs = lib.RENDER_ATTRS.get(renderer, "default") + node = render_attrs["node"] + padding_attr = render_attrs["node"] + prefix_attr = render_attrs["prefix"] + + prefix = cmds.getAttr("{}.{}".format(node, prefix_attr)) + padding = cmds.getAttr("{}.{}".format(node, padding_attr)) fname_prefix = self.RENDERER_PREFIX.get(renderer, self.DEFAULT_PREFIX) assert prefix == fname_prefix, ( From db855c1348f69eaa574d5856d4daa9d1c8bf61ba Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 11:45:34 +0200 Subject: [PATCH 0327/1249] using render constants from lib, add arg to get_renderer_variables --- .../plugins/maya/publish/submit_deadline.py | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 2a79ef6280..6ebcba9fa1 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -13,29 +13,23 @@ import pyblish.api import colorbleed.maya.lib as lib -RENDER_ATTRIBUTES = {"vray": - {"node": "vraySettings", - "prefix": "fileNamePrefix", - "padding": "fileNamePadding", - "ext": "imageFormatStr"}, - "arnold": - {"node": "defaultRenderGlobals", - "prefix": "imageFilePrefix", - "padding": "extensionPadding"} - } - - -def get_renderer_variables(): +def get_renderer_variables(renderlayer=None): """Retrieve the extension which has been set in the VRay settings Will return None if the current renderer is not VRay + For Maya 2016.5 and up the renderSetup creates renderSetupLayer node which + start with `rs`. Use the actual node name, do NOT use the `nice name` + + Args: + renderlayer (str): the node name of the renderlayer. Returns: dict """ - renderer = lib.get_renderer(lib.get_current_renderlayer()) - render_attrs = RENDER_ATTRIBUTES[renderer] + renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) + + render_attrs = lib.RENDER_ATTRS.get(renderer, "default") filename_padding = cmds.getAttr("{}.{}".format(render_attrs["node"], render_attrs["padding"])) @@ -94,7 +88,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): pass # Get the variables depending on the renderer - render_variables = get_renderer_variables() + render_variables = get_renderer_variables(renderlayer) # following hardcoded "renders//_/" output_filename_0 = self.preview_fname(scene, instance.name, From b348277115243273ab757791ea786eedfd4708c0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 12:02:17 +0200 Subject: [PATCH 0328/1249] cosmetics --- .../plugins/maya/publish/increment_current_file_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index d9913bba25..2e5bb694bc 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -24,7 +24,7 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): if any(plugin.__name__ == "MindbenderSubmitDeadline" for plugin in plugins): - raise RuntimeError("Skipping incrementing current file because" + raise RuntimeError("Skipping incrementing current file because " "submission to deadline failed.") new_filename = self.version_up(context.data["currentFile"]) From 3a90dc8561719ad7f7dd84d4d406226a54ff5a8d Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 19 Oct 2017 12:04:46 +0200 Subject: [PATCH 0329/1249] fixed fallback logic and comsmetics --- colorbleed/plugins/maya/publish/submit_deadline.py | 9 +++++---- .../plugins/maya/publish/validate_rendersettings.py | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 6ebcba9fa1..98634afc89 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -28,8 +28,7 @@ def get_renderer_variables(renderlayer=None): """ renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) - - render_attrs = lib.RENDER_ATTRS.get(renderer, "default") + render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) filename_padding = cmds.getAttr("{}.{}".format(render_attrs["node"], render_attrs["padding"])) @@ -81,6 +80,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): comment = context.data.get("comment", "") scene = os.path.splitext(fname)[0] dirname = os.path.join(workspace, "renders") + renderlayer = instance.data['setMembers'] # rs_beauty + renderlayer_name = instance.name # beauty try: os.makedirs(dirname) @@ -91,7 +92,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): render_variables = get_renderer_variables(renderlayer) # following hardcoded "renders//_/" output_filename_0 = self.preview_fname(scene, - instance.name, + renderlayer_name, dirname, render_variables["padding"], render_variables["ext"]) @@ -142,7 +143,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "UsingRenderLayers": True, # Render only this layer - "RenderLayer": instance.data["setMembers"], + "RenderLayer": renderlayer, # Determine which renderer to use from the file itself "Renderer": "file", diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 7313640bed..42579727ce 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -34,9 +34,10 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): # Collect the filename prefix in the renderlayer with lib.renderlayer(layer_node): - render_attrs = lib.RENDER_ATTRS.get(renderer, "default") + render_attrs = lib.RENDER_ATTRS.get(renderer, + lib.RENDER_ATTRS['default']) node = render_attrs["node"] - padding_attr = render_attrs["node"] + padding_attr = render_attrs["padding"] prefix_attr = render_attrs["prefix"] prefix = cmds.getAttr("{}.{}".format(node, prefix_attr)) From fa935a923fc4b31603c4b10aa67fa7a1351724d7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 14:31:21 +0200 Subject: [PATCH 0330/1249] added new set dress loader --- .../maya/load/load_setdress_rebuild.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index b616d42893..b8c84ef767 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -17,16 +17,18 @@ class SetDressRebuild(api.Loader): def process(self, name, namespace, context, data): - from maya import cmds - import avalon.maya as amaya + # Hack + import sys - context_ns = context["subset"]["name"] - with amaya.maintained_selection(): - file_nodes = cmds.file(self.fname, - namespace=context_ns, - reference=True, - returnNewNodes=True, - groupReference=True, - groupName="{}:{}".format(context_ns, name)) + p = r"C:\Users\User\Documents\development\research\setdress" + if p not in sys.path: + sys.path.insert(0, p) - self[:] = file_nodes + import loader + reload(loader) + + containers = loader.load_package(filepath=self.fname, + name=name, + namespace=namespace) + + self[:] = containers From 09f55ccf83f918496719dcf78c67c9d175bcc404 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 14:31:44 +0200 Subject: [PATCH 0331/1249] changed hierarchy key to parent --- colorbleed/plugins/maya/publish/collect_setdress.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 5eaa5822ba..52877ed8aa 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -5,6 +5,8 @@ from maya import cmds, mel from avalon import maya as amaya from colorbleed.maya import lib +# TODO : Publish of setdress: -unique namespace for all assets, VALIDATOR! + class CollectSetDress(pyblish.api.InstancePlugin): """Collect all relevant setdress items @@ -42,8 +44,8 @@ class CollectSetDress(pyblish.api.InstancePlugin): continue # Retrieve all matrix data - hierarchy = cmds.listRelatives(root, parent=True, fullPath=True)[0] - relative_hierarchy = hierarchy.replace(topnode, "*") + parent = cmds.listRelatives(root, parent=True, fullPath=True)[0] + relative_hierarchy = parent.replace(topnode, "*") hierarchy_nodes.append(relative_hierarchy) # Gather info for new data entry @@ -52,7 +54,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): representation_id = container["representation"] instance_data = {"loader": container["loader"], - "hierarchy": hierarchy, + "parent": parent, "namespace": namespace.strip(":")} # Check if matrix differs from default and store changes @@ -65,7 +67,6 @@ class CollectSetDress(pyblish.api.InstancePlugin): instance.data["scenedata"] = dict(data) instance.data["hierarchy"] = list(set(hierarchy_nodes)) - def get_file_rule(self, rule): return mel.eval('workspace -query -fileRuleEntry "{}"'.format(rule)) From a21ee37975c23d90d0117a07baed7552fa117d63 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 16:40:32 +0200 Subject: [PATCH 0332/1249] replaced os.environ lookups with session --- colorbleed/maya/lib.py | 9 ++++----- colorbleed/plugins/maya/load/load_rig.py | 6 ++---- .../plugins/maya/publish/collect_renderlayers.py | 5 ++--- .../plugins/publish/collect_assumed_destination.py | 8 ++++---- colorbleed/plugins/publish/integrate.py | 10 +++++----- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index efb29ed35c..c0f24fe6f2 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -12,14 +12,14 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel -from avalon import maya, io +from avalon import Session, maya, io from cb.utils.maya import core log = logging.getLogger(__name__) project = io.find_one({"type": "project", - "name": os.environ["AVALON_PROJECT"]}, + "name": Session["AVALON_PROJECT"]}, projection={"config.template.publish": True, "_id": False}) TEMPLATE = project["config"]["template"]["publish"] @@ -656,8 +656,7 @@ def generate_ids(nodes): """Assign a new id of the current active context to the nodes""" # Get the asset ID from the database for the asset of current context - asset = os.environ["AVALON_ASSET"] - asset_id = io.find_one({"type": "asset", "name": asset}, + asset_id = io.find_one({"type": "asset", "name": Session["AVALON_ASSET"]}, projection={"_id": True}) for node in nodes: @@ -700,7 +699,7 @@ def get_representation_file(representation, template=TEMPLATE): """ context = representation["context"].copy() - context["root"] = os.environ["AVALON_ROOT"] + context["root"] = Session["AVALON_ROOT"] return template.format(**context) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index ee54de2ad6..2057086b71 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -1,8 +1,6 @@ -import os - from maya import cmds -from avalon import api, maya +from avalon import api, Session, maya class RigLoader(api.Loader): @@ -56,7 +54,7 @@ class RigLoader(api.Loader): roots = cmds.ls(self[:], assemblies=True, long=True) assert roots, "No root nodes in rig, this is a bug." - asset = os.environ["AVALON_ASSET"] + asset = Session["AVALON_ASSET"] dependency = str(context["representation"]["_id"]) # Create the animation instance diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 971c9b582f..4cad5d7b3c 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,9 +1,8 @@ -import os from maya import cmds import pyblish.api -from avalon import maya, api +from avalon import Session, maya, api import colorbleed.maya.lib as lib @@ -18,7 +17,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): def process(self, context): registered_root = api.registered_root() - asset_name = os.environ["AVALON_ASSET"] + asset_name = Session["AVALON_ASSET"] current_file = context.data["currentFile"] relative_file = current_file.replace(registered_root, "{root}") diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py index d57039e8fb..201fef67aa 100644 --- a/colorbleed/plugins/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -1,7 +1,7 @@ import pyblish.api import os -import avalon.io as io +from avalon import Session, io class CollectAssumedDestination(pyblish.api.InstancePlugin): @@ -64,7 +64,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): # get all the stuff from the database subset_name = instance.data["subset"] asset_name = instance.data["asset"] - project_name = os.environ["AVALON_PROJECT"] + project_name = Session["AVALON_PROJECT"] project = io.find_one({"type": "project", "name": project_name}, @@ -95,9 +95,9 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): if version is not None: version_number += version["name"] - template_data = {"root": os.environ["AVALON_PROJECTS"], + template_data = {"root": Session["AVALON_PROJECTS"], "project": project_name, - "silo": os.environ["AVALON_SILO"], + "silo": Session["AVALON_SILO"], "asset": asset_name, "subset": subset_name, "version": version_number, diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 2c96d69556..c4b9325b6e 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -4,7 +4,7 @@ import shutil import errno import pyblish.api -from avalon import api, io +from avalon import Session, api, io log = logging.getLogger(__name__) @@ -47,10 +47,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def register(self, instance): # Required environment variables - PROJECT = os.environ["AVALON_PROJECT"] - ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - SILO = os.environ["AVALON_SILO"] - LOCATION = os.getenv("AVALON_LOCATION") + PROJECT = Session["AVALON_PROJECT"] + ASSET = instance.data.get("asset") or Session["AVALON_ASSET"] + SILO = Session["AVALON_SILO"] + LOCATION = Session("AVALON_LOCATION") context = instance.context # Atomicity From fcd0498f7cad0cca744f624f2d3262c90b40f0c3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 Oct 2017 17:35:49 +0200 Subject: [PATCH 0333/1249] Implement #195 --- colorbleed/plugins/maya/load/actions.py | 4 ++-- colorbleed/plugins/maya/load/load_animation.py | 7 ++----- colorbleed/plugins/maya/load/load_imagesequence.py | 2 +- colorbleed/plugins/maya/load/load_look.py | 4 ++-- colorbleed/plugins/maya/load/load_mayaascii.py | 4 ++-- colorbleed/plugins/maya/load/load_model.py | 4 ++-- colorbleed/plugins/maya/load/load_rig.py | 14 +++++++------- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py index 9e54195294..7b63c7d194 100644 --- a/colorbleed/plugins/maya/load/actions.py +++ b/colorbleed/plugins/maya/load/actions.py @@ -18,7 +18,7 @@ class SetFrameRangeLoader(api.Loader): icon = "clock-o" color = "white" - def process(self, name, namespace, context, data): + def load(self, context, name, namespace, data): import maya.cmds as cmds @@ -52,7 +52,7 @@ class SetFrameRangeWithHandlesLoader(api.Loader): icon = "clock-o" color = "white" - def process(self, name, namespace, context, data): + def load(self, context, name, namespace, data): import maya.cmds as cmds diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/load_animation.py index b9f187655d..cabb06d886 100644 --- a/colorbleed/plugins/maya/load/load_animation.py +++ b/colorbleed/plugins/maya/load/load_animation.py @@ -1,10 +1,7 @@ -import os +import avalon.maya.pipeline -from avalon import api - - -class AbcLoader(api.Loader): +class AbcLoader(avalon.maya.pipeline.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", diff --git a/colorbleed/plugins/maya/load/load_imagesequence.py b/colorbleed/plugins/maya/load/load_imagesequence.py index 27e24f4782..2386f0c6e7 100644 --- a/colorbleed/plugins/maya/load/load_imagesequence.py +++ b/colorbleed/plugins/maya/load/load_imagesequence.py @@ -26,7 +26,7 @@ class OpenImageSequence(api.Loader): icon = "play-circle" color = "orange" - def process(self, name, namespace, context, data): + def load(self, context, name, namespace, data): directory = self.fname from avalon.vendor import clique diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 6a265b64ce..f31d217282 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -1,10 +1,10 @@ import os import json -from avalon import api +import avalon.maya.pipeline -class LookLoader(api.Loader): +class LookLoader(avalon.maya.pipeline.ReferenceLoader): """Specific loader for lookdev""" families = ["colorbleed.look"] diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/colorbleed/plugins/maya/load/load_mayaascii.py index 7bc75fca45..247e832be0 100644 --- a/colorbleed/plugins/maya/load/load_mayaascii.py +++ b/colorbleed/plugins/maya/load/load_mayaascii.py @@ -1,7 +1,7 @@ -from avalon import api +import avalon.maya.pipeline -class MayaAsciiLoader(api.Loader): +class MayaAsciiLoader(avalon.maya.pipeline.ReferenceLoader): """Load the model""" families = ["colorbleed.mayaAscii"] diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 15637c9e81..ca54a2d41e 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,7 +1,7 @@ -from avalon import api +import avalon.maya.pipeline -class ModelLoader(api.Loader): +class ModelLoader(avalon.maya.pipeline.ReferenceLoader): """Load the model""" families = ["colorbleed.model"] diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index ee54de2ad6..ee7db5d7a1 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -2,10 +2,11 @@ import os from maya import cmds +import avalon.maya.pipeline from avalon import api, maya -class RigLoader(api.Loader): +class RigLoader(avalon.maya.pipeline.ReferenceLoader): """Specific loader for rigs This automatically creates an instance for animators upon load. @@ -37,7 +38,6 @@ class RigLoader(api.Loader): self._post_process(name, unique_namespace, context, data) def _post_process(self, name, namespace, context, data): - from avalon import maya # TODO(marcus): We are hardcoding the name "out_SET" here. # Better register this keyword, so that it can be used @@ -62,8 +62,8 @@ class RigLoader(api.Loader): # Create the animation instance with maya.maintained_selection(): cmds.select([output, controls] + roots, noExpand=True) - maya.create(name=namespace, - asset=asset, - family="colorbleed.animation", - options={"useSelection": True}, - data={"dependencies": dependency}) + api.create(name=namespace, + asset=asset, + family="colorbleed.animation", + options={"useSelection": True}, + data={"dependencies": dependency}) From e6de792519854d92a8759dc91c2f2ee9391e163c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 18:10:24 +0200 Subject: [PATCH 0334/1249] added validator and setdress api --- colorbleed/plugins/maya/load/setdress_api.py | 463 ++++++++++++++++++ .../publish/validate_setdress_namespaces.py | 0 2 files changed, 463 insertions(+) create mode 100644 colorbleed/plugins/maya/load/setdress_api.py create mode 100644 colorbleed/plugins/maya/publish/validate_setdress_namespaces.py diff --git a/colorbleed/plugins/maya/load/setdress_api.py b/colorbleed/plugins/maya/load/setdress_api.py new file mode 100644 index 0000000000..88ac85c1c3 --- /dev/null +++ b/colorbleed/plugins/maya/load/setdress_api.py @@ -0,0 +1,463 @@ +import logging +import json +import os + +import contextlib +import copy + +from maya import cmds + +import avalon.schema +import avalon.io as io +from avalon.maya.lib import unique_namespace + +log = logging.getLogger("PackageLoader") + + +def matrix_equals(a, b, tolerance=1e-10): + """Compares two matrices with an imperfection tolerance""" + if not all(abs(x - y) < tolerance for x, y in zip(a, b)): + return False + return True + + +def to_namespace(node, namespace): + """Return node name as if it's inside the namespace. + + Args: + node (str): Node name + namespace (str): Namespace + + Returns: + str: The node in the namespace. + + """ + namespace_prefix = "|{}:".format(namespace) + node = namespace_prefix.join(node.split("|")) + return node + + +@contextlib.contextmanager +def namespaced(namespace, new=True): + """Work inside namespace during context + + Args: + new (bool): When enabled this will rename the namespace to a unique + namespace if the input namespace already exists. + + Yields: + str: The namespace that is used during the context + + """ + original = cmds.namespaceInfo(cur=True) + if new: + namespace = unique_namespace(namespace) + cmds.namespace(add=namespace) + + try: + cmds.namespace(set=namespace) + yield namespace + finally: + cmds.namespace(set=original) + + +def load_package(filepath, name, namespace=None): + """Load a package that was gathered elsewhere. + + A package is a group of published instances, possibly with additional data + in a hierarchy. + + """ + + from avalon.tools.cbloader import lib + + if namespace is None: + # Define a unique namespace for the package + namespace = os.path.basename(filepath).split(".")[0] + unique_namespace(namespace) + assert isinstance(namespace, basestring) + + # Load the setdress package data + with open(filepath, "r") as fp: + data = json.load(fp) + + # Load the setdress alembic hierarchy + # We import this into the namespace in which we'll load the package's + # instances into afterwards. + alembic = filepath.replace(".json", ".abc") + hierarchy = cmds.file(alembic, + reference=True, + namespace=namespace, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name), + typ="Alembic") + + containers = [] + for representation_id, instances in data.items(): + + # Find the compatible loaders + loaders = list(lib.iter_loaders(representation_id)) + + for instance in instances: + container = _add(instance, representation_id, loaders, name, + namespace) + containers.append(container) + + # TODO: Do we want to cripple? Or do we want to add a 'parent' parameter? + # Cripple the original avalon containers so they don't show up in the + # manager + # for container in containers: + # cmds.setAttr("%s.id" % container, + # "colorbleed.setdress.container", + # type="string") + + # TODO: Lock all loaded nodes + # This is to ensure the hierarchy remains unaltered by the artists + # for node in nodes: + # cmds.lockNode(node, lock=True) + + return containers + hierarchy + + +def _add(instance, representation_id, loaders, name, namespace): + """Add an item from the package + + Args: + instance (dict): + representation_id (str): + loaders (list): + namespace (str): + + Returns: + str: The created Avalon container. + + """ + + from avalon.tools.cbloader import lib + from colorbleed.maya.lib import get_container_transforms + + # Process within the namespace + with namespaced(namespace, new=False) as namespace: + + # Get the used loader + Loader = next((x for x in loaders if + x.__name__ == instance['loader']), + None) + + if Loader is None: + log.warning("Loader is missing: %s. Skipping %s", + instance['loader'], instance) + raise RuntimeError("Loader is missing.") + + container = lib.run_loader(Loader, + representation_id, + namespace=instance['namespace']) + + # Get the root from the loaded container + root = get_container_transforms({"objectName": container}, + root=True) + + # Apply matrix to root node (if any matrix edits) + matrix = instance.get("matrix", None) + if matrix: + cmds.xform(root, objectSpace=True, matrix=matrix) + + # Parent into the setdress hierarchy + # Namespace is missing from parent node(s), add namespace + # manually + parent_grp = instance["parent"] + parent_grp = "{}:{}|".format(namespace, name) + to_namespace(parent_grp, namespace) + + cmds.parent(root, parent_grp, relative=True) + + return container + + +# Store root nodes based on representation and namespace +def _instances_by_namespace(data): + """Rebuild instance data so we can look it up by namespace. + + Note that the `representation` is added into the instance's + data with a `representation` key. + + Args: + data (dict): scene build data + + Returns: + dict + + """ + result = {} + # Add new assets + for representation_id, instances in data.items(): + + # Ensure we leave the source data unaltered + instances = copy.deepcopy(instances) + for instance in instances: + instance['representation'] = representation_id + result[instance['namespace']] = instance + + return result + + +def update_package(set_container, version): + """Update any matrix changes in the scene based on the new data + + Args: + set_container (dict): container data from `ls()` + version (int): version number of the subset + + """ + + from colorbleed.maya.lib import get_representation_file + from avalon.maya.pipeline import parse_container + + # Versioning (from `core.maya.pipeline`) + current_representation = io.find_one({ + "_id": io.ObjectId(set_container["representation"]) + }) + + assert current_representation is not None, "This is a bug" + + version_, subset, asset, project = io.parenthood(current_representation) + + if version == -1: + new_version = io.find_one({ + "type": "version", + "parent": subset["_id"] + }, sort=[("name", -1)]) + else: + new_version = io.find_one({ + "type": "version", + "parent": subset["_id"], + "name": version, + }) + + assert new_version is not None, "This is a bug" + + # Get the new representation (new file) + new_representation = io.find_one({ + "type": "representation", + "parent": new_version["_id"], + "name": current_representation["name"] + }) + + # Load the original package data + current_file = get_representation_file(current_representation) + assert current_file.endswith(".json") + with open(current_file, "r") as fp: + current_data = json.load(fp) + + # Load the new package data + new_file = get_representation_file(new_representation) + assert new_file.endswith(".json") + with open(new_file, "r") as fp: + new_data = json.load(fp) + + # Get avalon containers in the setdress + containers = [] + members = cmds.sets(set_container['objectName'], query=True) + for node in cmds.ls(members, type="objectSet"): + try: + container = parse_container(node) + containers.append(container) + except avalon.schema.ValidationError: + pass + + # Update scene content + update_scene(set_container, containers, current_data, new_data, new_file) + + # TODO: This should be handled by the pipeline itself + cmds.setAttr(set_container['objectName'] + ".representation", + str(new_representation['_id']), type="string") + + +def update_scene(set_container, containers, current_data, new_data, new_file): + """Updates the hierarchy, assets and their matrix + + Updates the following withing the scene: + * Setdress hierarchy alembic + * Matrix + * Parenting + * Representations + + It removes any assets which are not present in the new build data + + Args: + set_container (dict): the setdress container of the scene + containers (list): the list of containers under the setdress container + current_data (dict): the current build data of the setdress + new_data (dict): the new build data of the setdres + + Returns: + processed_containers (list): all new and updated containers + """ + + from colorbleed.maya.lib import DEFAULT_MATRIX, get_container_transforms + from avalon.tools.cbloader import lib + from avalon import api + + set_namespace = set_container['namespace'] + + # Update the setdress hierarchy alembic + set_root = get_container_transforms(set_container, root=True) + set_hierarchy_root = cmds.listRelatives(set_root, fullPath=True)[0] + set_hierarchy_reference = cmds.referenceQuery(set_hierarchy_root, + referenceNode=True) + new_alembic = new_file.replace(".json", ".abc") + assert os.path.exists(new_alembic), "%s does not exist." % new_alembic + cmds.file(new_alembic, + loadReference=set_hierarchy_reference, + type="Alembic") + + identity = DEFAULT_MATRIX[:] + + processed_namespaces = set() + processed_containers = list() + + new_lookup = _instances_by_namespace(new_data) + old_lookup = _instances_by_namespace(current_data) + for container in containers: + # container_repr = cmds.getAttr("{}.representation".format(container)) + container_ns = container['namespace'] + + if container_ns in new_lookup: + root = get_container_transforms(container, root=True) + if not root: + log.error("Can't find root for %s", container['objectName']) + continue + + old_instance = old_lookup.get(container_ns, {}) + new_instance = new_lookup[container_ns] + + # Update the matrix + # check matrix against old_data matrix to find local overrides + current_matrix = cmds.xform(root, + query=True, + matrix=True, + objectSpace=True) + + original_matrix = old_instance.get("matrix", identity) + has_matrix_override = not matrix_equals(current_matrix, + original_matrix) + + if has_matrix_override: + log.warning("Matrix override preserved on %s", container_ns) + + else: + new_matrix = new_instance.get("matrix", identity) + cmds.xform(root, matrix=new_matrix, objectSpace=True) + + # Update the parenting + if old_instance.get("parent", None) != new_instance["parent"]: + + parent = to_namespace(new_instance['parent'], set_namespace) + if not cmds.objExists(parent): + log.error("Can't find parent %s", parent) + continue + + # Set the new parent + cmds.lockNode(root, lock=False) + root = cmds.parent(root, parent, relative=True) + cmds.lockNode(root, lock=True) + + # TODO: Update the representation with the new loader + if new_instance['loader'] != old_instance['loader']: + log.error("Switching loader between updates is not supported.") + continue + + # TODO: Update the representation with the new loader + representation_new = new_instance['representation'] + representation_old = old_instance['representation'] + if representation_new != representation_old: + + print "namespace :", container_ns + + new = io.find_one({"_id": io.ObjectId(representation_new)}) + old = io.find_one({"_id": io.ObjectId(representation_old)}) + + is_valid = compare_representations(old=old, new=new) + if not is_valid: + continue + + new_version = new["context"]["version"] + + host = api.registered_host() + host.update(container, version=new_version) + + else: + # Remove this container because it's not in the new data + log.warning("Removing content: %s", container_ns) + host = api.registered_host() + host.remove(container) + + processed_namespaces.add(container_ns) + processed_containers.append(container['objectName']) + + # Add new assets + for representation_id, instances in new_data.items(): + + # Find the compatible loaders + loaders = list(lib.iter_loaders(representation_id)) + + for instance in instances: + + # Already processed in update functionality + if instance['namespace'] in processed_namespaces: + continue + + container = _add(instance, + representation_id, + loaders, + set_container['name'], + set_container['namespace']) + + # Add to the setdress container + cmds.sets(container, + addElement=set_container['objectName']) + + processed_containers.append(container) + + return processed_containers + + +def compare_representations(old, new): + """Check if the old representation given can be updated + + Due to limitations of the `host.update` function we cannot allow + differences in the following data: + + * Representation name (extension) + * Asset name + * Subset name (variation) + + If any of those data values differs, the function will raise an + RuntimeError + + Args: + old(dict): representation data from the database + new(dict): representation data from the database + + Returns: + bool: False if the representation is not invalid else True + """ + + if new["name"] != old["name"]: + log.error("Cannot switch extensions") + return False + + new_context = new["context"] + old_context = old["context"] + + if new_context["asset"] != old_context["asset"]: + log.error("Changing assets between updates is " + "not supported.") + return False + + if new_context["subset"] != old_context["subset"]: + log.error("Changing subsets between updates is " + "not supported.") + return False + + return True diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py new file mode 100644 index 0000000000..e69de29bb2 From f5eddfbfaf1504565ebf3f95c1df7dfa90e16c1c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 18:11:06 +0200 Subject: [PATCH 0335/1249] fixed wrong key --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index efb29ed35c..56191d880e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -700,7 +700,7 @@ def get_representation_file(representation, template=TEMPLATE): """ context = representation["context"].copy() - context["root"] = os.environ["AVALON_ROOT"] + context["root"] = os.environ["AVALON_PROJECTS"] return template.format(**context) From afae0fa893828503c904737d095043a347ae4517 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 18:11:35 +0200 Subject: [PATCH 0336/1249] removed site.addsitedir, use import instead --- .../maya/load/load_setdress_rebuild.py | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index b8c84ef767..77be943708 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -1,9 +1,5 @@ -import site - from avalon import api -site.addsitedir(r"C:\Users\User\Documents\development\research\setdress") - class SetDressRebuild(api.Loader): @@ -17,18 +13,10 @@ class SetDressRebuild(api.Loader): def process(self, name, namespace, context, data): - # Hack - import sys + import setdress_api - p = r"C:\Users\User\Documents\development\research\setdress" - if p not in sys.path: - sys.path.insert(0, p) - - import loader - reload(loader) - - containers = loader.load_package(filepath=self.fname, - name=name, - namespace=namespace) + containers = setdress_api.load_package(filepath=self.fname, + name=name, + namespace=namespace) self[:] = containers From 3b9f78ea5c16dffe0e3a8601e0c8adc396de079f Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 18:12:06 +0200 Subject: [PATCH 0337/1249] removed enumerate, unused --- colorbleed/plugins/maya/publish/collect_setdress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 52877ed8aa..e7ffa84a54 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -34,7 +34,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): data = defaultdict(list) hierarchy_nodes = [] - for i, container in enumerate(containers): + for container in containers: members = cmds.sets(container["objectName"], query=True) transforms = lib.get_container_transforms(container, members) root = lib.get_container_transforms(container, From e7f7efad8cbc11ce9b33429d95bd78c62a992e4c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 20 Oct 2017 18:12:38 +0200 Subject: [PATCH 0338/1249] initial commit for ns validator --- .../publish/validate_setdress_namespaces.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py index e69de29bb2..09015faddd 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py @@ -0,0 +1,35 @@ +import pyblish.api +from collection import defaultdict + + +class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): + """Ensure namespaces are not nested""" + + label = "Validate Setdress Namespaces" + order = pyblish.api.ValidatorOrder + families = ["colorbleed.setdress"] + + def process(self, instance): + + self.log.info("Checking namespace for %s", instance.name) + if self.get_invalid(instance): + self.log.error("Nested namespaces found") + + @classmethod + def get_invalid(cls, instance): + + from maya import cmds + + invalid = [] + + namspace_lookup = defaultdict(list) + for item in cmds.ls(instance): + namespace, node = item.rsplit(":", 1)[0] + namspace_lookup[namespace].append(node) + + for namespace, nodes in namspace_lookup.items(): + parts = [p for p in namespace.split(":") if p != ""] + if len(parts) > 1: + invalid.extend(nodes) + + return invalid \ No newline at end of file From 88ff47311efaa38b7b99033e5b1fae033524c421 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 Oct 2017 18:32:13 +0200 Subject: [PATCH 0339/1249] Implement gpuCache loader prototype --- colorbleed/plugins/maya/load/load_model.py | 86 +++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index ca54a2d41e..d2a05efd35 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,4 +1,5 @@ import avalon.maya.pipeline +import avalon.api class ModelLoader(avalon.maya.pipeline.ReferenceLoader): @@ -31,4 +32,87 @@ class ModelLoader(avalon.maya.pipeline.ReferenceLoader): groupReference=True, groupName="{}:{}".format(namespace, name)) - self[:] = nodes \ No newline at end of file + self[:] = nodes + + +class GpuCacheLoader(avalon.api.Loader): + """Load model Alembic as gpuCache""" + + families = ["colorbleed.model"] + representations = ["abc"] + + label = "Import Gpu Cache" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + + import maya.cmds as cmds + import avalon.maya.lib as lib + + asset = context['asset']['name'] + subset = context['subset']['name'] + + name = name or subset + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + cmds.loadPlugin("gpuCache", quiet=True) + + # Root group + label = "{}:{}".format(namespace, name) + root = cmds.group(name=label, empty=True) + + # Create transform with shape + transform_name = label + "_GPU" + transform = cmds.createNode("transform", name=transform_name, + parent=root) + cache = cmds.createNode("gpuCache", + parent=transform, + name="{0}Shape".format(transform_name)) + + # Set the cache filepath + cmds.setAttr(cache + '.cacheFileName', self.fname, type="string") + cmds.setAttr(cache + '.cacheGeomPath', "|", type="string") # root + + # Lock parenting of the transform and cache + cmds.lockNode([transform, cache], lock=True) + + nodes = [root, transform, cache] + self[:] = nodes + + return avalon.maya.pipeline.containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + import maya.cmds as cmds + + path = avalon.api.get_representation_path(representation) + + # Update the cache + members = cmds.sets(container['objectName'], query=True) + caches = cmds.ls(members, type="gpuCache", long=True) + + assert len(caches) == 1, "This is a bug" + + for cache in caches: + cmds.setAttr(cache + ".cacheFileName", path, type="string") + + cmds.setAttr(container["objectName"] + ".representation", + str(representation["_id"]), + type="string") + + def remove(self, container): + import maya.cmds as cmds + members = cmds.sets(container['objectName'], query=True) + cmds.lockNode(members, lock=False) + cmds.delete([container['objectName']] + members) From 758b4a116f8588d998eac3361bc0173c33b3dd49 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 09:26:20 +0200 Subject: [PATCH 0340/1249] Move setdress_api.py so it can be loaded automatically --- colorbleed/plugins/maya/load/load_setdress_rebuild.py | 2 +- colorbleed/{plugins/maya/load => }/setdress_api.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename colorbleed/{plugins/maya/load => }/setdress_api.py (100%) diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py index 77be943708..7fd5dd30e4 100644 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ b/colorbleed/plugins/maya/load/load_setdress_rebuild.py @@ -13,7 +13,7 @@ class SetDressRebuild(api.Loader): def process(self, name, namespace, context, data): - import setdress_api + from colorbleed import setdress_api containers = setdress_api.load_package(filepath=self.fname, name=name, diff --git a/colorbleed/plugins/maya/load/setdress_api.py b/colorbleed/setdress_api.py similarity index 100% rename from colorbleed/plugins/maya/load/setdress_api.py rename to colorbleed/setdress_api.py From 76a6ce4ce6dba54ac157f557e8807b319f492162 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 09:29:57 +0200 Subject: [PATCH 0341/1249] Remove unused import --- colorbleed/setdress_api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index 88ac85c1c3..db5c9641aa 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -7,7 +7,6 @@ import copy from maya import cmds -import avalon.schema import avalon.io as io from avalon.maya.lib import unique_namespace @@ -210,7 +209,7 @@ def update_package(set_container, version): """ - from colorbleed.maya.lib import get_representation_file + import avalon.api from avalon.maya.pipeline import parse_container # Versioning (from `core.maya.pipeline`) @@ -244,13 +243,13 @@ def update_package(set_container, version): }) # Load the original package data - current_file = get_representation_file(current_representation) + current_file = avalon.api.get_representation_path(new_representation) assert current_file.endswith(".json") with open(current_file, "r") as fp: current_data = json.load(fp) # Load the new package data - new_file = get_representation_file(new_representation) + new_file = avalon.api.get_representation_path(new_representation) assert new_file.endswith(".json") with open(new_file, "r") as fp: new_data = json.load(fp) From 8449e10244190f227d5f911e64be010b5c373bf6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 09:32:04 +0200 Subject: [PATCH 0342/1249] Refactor host.update and host.remove to api.update and api.remove --- colorbleed/setdress_api.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index db5c9641aa..117fe240cd 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -382,14 +382,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file): new_version = new["context"]["version"] - host = api.registered_host() - host.update(container, version=new_version) + api.update(container, version=new_version) else: # Remove this container because it's not in the new data log.warning("Removing content: %s", container_ns) - host = api.registered_host() - host.remove(container) + api.remove(container) processed_namespaces.add(container_ns) processed_containers.append(container['objectName']) @@ -424,7 +422,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): def compare_representations(old, new): """Check if the old representation given can be updated - Due to limitations of the `host.update` function we cannot allow + Due to limitations of the `api.update` function we cannot allow differences in the following data: * Representation name (extension) From 1ca84cb94ad799a62a9dc21f8369c2155234c577 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 12:05:16 +0200 Subject: [PATCH 0343/1249] Refactor loaders --- .../{load_animation.py => _load_animation.py} | 0 colorbleed/plugins/maya/load/load_alembic.py | 51 ++++++++++--------- 2 files changed, 26 insertions(+), 25 deletions(-) rename colorbleed/plugins/maya/load/{load_animation.py => _load_animation.py} (100%) diff --git a/colorbleed/plugins/maya/load/load_animation.py b/colorbleed/plugins/maya/load/_load_animation.py similarity index 100% rename from colorbleed/plugins/maya/load/load_animation.py rename to colorbleed/plugins/maya/load/_load_animation.py diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index 6cea82a832..b0b2b86101 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -1,7 +1,8 @@ from avalon import api +import avalon.maya.pipeline -class AnimationLoader(api.Loader): +class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", @@ -29,27 +30,27 @@ class AnimationLoader(api.Loader): self[:] = nodes -class SetDressAlembicLoader(api.Loader): - """Load the setdress as alembic""" - - families = ["colorbleed.setdress"] - label = "Reference Alembic" - representations = ["abc"] - order = -10 - icon = "code-fork" - color = "orange" - - def process(self, name, namespace, context, data): - - import maya.cmds as cmds - - cmds.loadPlugin("AbcImport.mll", quiet=True) - nodes = cmds.file(self.fname, - namespace=namespace, - sharedReferenceFile=False, - groupReference=True, - groupName="{}:{}".format(namespace, name), - reference=True, - returnNewNodes=True) - - self[:] = nodes +# class SetDressAlembicLoader(avalon.maya.pipeline.ReferenceLoader): +# """Load the setdress as alembic""" +# +# families = ["colorbleed.setdress"] +# label = "Reference Alembic" +# representations = ["abc"] +# order = -10 +# icon = "code-fork" +# color = "orange" +# +# def process(self, name, namespace, context, data): +# +# import maya.cmds as cmds +# +# cmds.loadPlugin("AbcImport.mll", quiet=True) +# nodes = cmds.file(self.fname, +# namespace=namespace, +# sharedReferenceFile=False, +# groupReference=True, +# groupName="{}:{}".format(namespace, name), +# reference=True, +# returnNewNodes=True) +# +# self[:] = nodes From 2b4d1a4ad70e4259d2caaa82d190e5aee77b6556 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 12:05:35 +0200 Subject: [PATCH 0344/1249] Simplify ModelLoader logic --- colorbleed/plugins/maya/load/load_model.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index d2a05efd35..e5d9dd960a 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -18,12 +18,6 @@ class ModelLoader(avalon.maya.pipeline.ReferenceLoader): import maya.cmds as cmds from avalon import maya - # Create a readable namespace - # Namespace should contain asset name and counter - # TEST_001{_descriptor} where `descriptor` can be `_abc` for example - assetname = "{}_".format(namespace.split("_")[0]) - namespace = maya.unique_namespace(assetname, format="%03d") - with maya.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, @@ -52,9 +46,6 @@ class GpuCacheLoader(avalon.api.Loader): import avalon.maya.lib as lib asset = context['asset']['name'] - subset = context['subset']['name'] - - name = name or subset namespace = namespace or lib.unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", From 2ab4d0bdc219c04b323017a6ab1d55447043039d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 12:06:27 +0200 Subject: [PATCH 0345/1249] Fix setdress api and implement `load`, `update`, `remove` correctly on SetDressLoader --- colorbleed/plugins/maya/load/load_setdress.py | 81 +++++++++++++++++++ .../maya/load/load_setdress_rebuild.py | 22 ----- colorbleed/setdress_api.py | 79 ++++++++++++------ 3 files changed, 134 insertions(+), 48 deletions(-) create mode 100644 colorbleed/plugins/maya/load/load_setdress.py delete mode 100644 colorbleed/plugins/maya/load/load_setdress_rebuild.py diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/colorbleed/plugins/maya/load/load_setdress.py new file mode 100644 index 0000000000..675e6b81a9 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_setdress.py @@ -0,0 +1,81 @@ +from avalon import api + + +class SetDressLoader(api.Loader): + + families = ["colorbleed.setdress"] + representations = ["json"] + + label = "Load Set Dress" + order = -9 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + + from avalon.maya.pipeline import containerise + from avalon.maya import lib + + asset = context['asset']['name'] + namespace = namespace or lib.unique_namespace( + asset + "_", + prefix="_" if asset[0].isdigit() else "", + suffix="_", + ) + + from colorbleed import setdress_api + + containers = setdress_api.load_package(filepath=self.fname, + name=name, + namespace=namespace) + + self[:] = containers + + # Only containerize if any nodes were loaded by the Loader + nodes = self[:] + if not nodes: + return + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + + from colorbleed import setdress_api + return setdress_api.update_package(container, + representation) + + def remove(self, container): + """Remove all sub containers""" + + from avalon import api + from colorbleed import setdress_api + import maya.cmds as cmds + + # Remove all members + member_containers = setdress_api.get_contained_containers(container) + for member_container in member_containers: + print 'member_container', member_container + self.log.info("Removing container %s", + member_container['objectName']) + api.remove(member_container) + + # Remove alembic hierarchy reference + # TODO: Check whether removing all contained references is safe enough + members = cmds.sets(container['objectName'], query=True) or [] + references = cmds.ls(members, type="reference") + for reference in references: + self.log.info("Removing %s", reference) + fname = cmds.referenceQuery(reference, filename=True) + cmds.file(fname, removeReference=True) + + # Delete container and its contents + if cmds.objExists(container['objectName']): + members = cmds.sets(container['objectName'], query=True) or [] + cmds.delete([container['objectName']] + members) + + # TODO: Ensure namespace is gone \ No newline at end of file diff --git a/colorbleed/plugins/maya/load/load_setdress_rebuild.py b/colorbleed/plugins/maya/load/load_setdress_rebuild.py deleted file mode 100644 index 7fd5dd30e4..0000000000 --- a/colorbleed/plugins/maya/load/load_setdress_rebuild.py +++ /dev/null @@ -1,22 +0,0 @@ -from avalon import api - - -class SetDressRebuild(api.Loader): - - families = ["colorbleed.setdress"] - representations = ["json"] - - label = "Rebuild Set Dress" - order = -9 - icon = "code-fork" - color = "orange" - - def process(self, name, namespace, context, data): - - from colorbleed import setdress_api - - containers = setdress_api.load_package(filepath=self.fname, - name=name, - namespace=namespace) - - self[:] = containers diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index 117fe240cd..cf775ee7ea 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -133,7 +133,7 @@ def _add(instance, representation_id, loaders, name, namespace): """ - from avalon.tools.cbloader import lib + import avalon.api as api from colorbleed.maya.lib import get_container_transforms # Process within the namespace @@ -149,9 +149,9 @@ def _add(instance, representation_id, loaders, name, namespace): instance['loader'], instance) raise RuntimeError("Loader is missing.") - container = lib.run_loader(Loader, - representation_id, - namespace=instance['namespace']) + container = api.load(Loader, + representation_id, + namespace=instance['namespace']) # Get the root from the loaded container root = get_container_transforms({"objectName": container}, @@ -200,21 +200,39 @@ def _instances_by_namespace(data): return result -def update_package(set_container, version): - """Update any matrix changes in the scene based on the new data - +def get_contained_containers(container): + """Get the Avalon containers in this container + Args: - set_container (dict): container data from `ls()` - version (int): version number of the subset - + container (dict): The container dict. + + Returns: + list: A list of member container dictionaries. + """ - import avalon.api + import avalon.schema from avalon.maya.pipeline import parse_container + # Get avalon containers in this package setdress container + containers = [] + members = cmds.sets(container['objectName'], query=True) + for node in cmds.ls(members, type="objectSet"): + try: + member_container = parse_container(node) + containers.append(member_container) + except avalon.schema.ValidationError: + pass + + return containers + + +def update_package_version(container, version): + """Update package by version number""" + # Versioning (from `core.maya.pipeline`) current_representation = io.find_one({ - "_id": io.ObjectId(set_container["representation"]) + "_id": io.ObjectId(container["representation"]) }) assert current_representation is not None, "This is a bug" @@ -242,34 +260,44 @@ def update_package(set_container, version): "name": current_representation["name"] }) + update_package(container, new_representation) + + +def update_package(set_container, representation): + """Update any matrix changes in the scene based on the new data + + Args: + set_container (dict): container data from `ls()` + version (int): version number of the subset + + """ + + import avalon.api + # Load the original package data - current_file = avalon.api.get_representation_path(new_representation) + current_representation = io.find_one({ + "_id": io.ObjectId(set_container['representation']), + "type": "representation" + }) + + current_file = avalon.api.get_representation_path(current_representation) assert current_file.endswith(".json") with open(current_file, "r") as fp: current_data = json.load(fp) # Load the new package data - new_file = avalon.api.get_representation_path(new_representation) + new_file = avalon.api.get_representation_path(representation) assert new_file.endswith(".json") with open(new_file, "r") as fp: new_data = json.load(fp) - # Get avalon containers in the setdress - containers = [] - members = cmds.sets(set_container['objectName'], query=True) - for node in cmds.ls(members, type="objectSet"): - try: - container = parse_container(node) - containers.append(container) - except avalon.schema.ValidationError: - pass - # Update scene content + containers = get_contained_containers(set_container) update_scene(set_container, containers, current_data, new_data, new_file) # TODO: This should be handled by the pipeline itself cmds.setAttr(set_container['objectName'] + ".representation", - str(new_representation['_id']), type="string") + str(representation['_id']), type="string") def update_scene(set_container, containers, current_data, new_data, new_file): @@ -318,7 +346,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): new_lookup = _instances_by_namespace(new_data) old_lookup = _instances_by_namespace(current_data) for container in containers: - # container_repr = cmds.getAttr("{}.representation".format(container)) container_ns = container['namespace'] if container_ns in new_lookup: From a0dced9981a332b944a4389e33d8459f52ca9691 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 12:25:36 +0200 Subject: [PATCH 0346/1249] Remove debugging print statement --- colorbleed/plugins/maya/load/load_setdress.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/colorbleed/plugins/maya/load/load_setdress.py index 675e6b81a9..8cc857fc3f 100644 --- a/colorbleed/plugins/maya/load/load_setdress.py +++ b/colorbleed/plugins/maya/load/load_setdress.py @@ -59,7 +59,6 @@ class SetDressLoader(api.Loader): # Remove all members member_containers = setdress_api.get_contained_containers(container) for member_container in member_containers: - print 'member_container', member_container self.log.info("Removing container %s", member_container['objectName']) api.remove(member_container) From 0057e5ed1a92c758d240628ba6d8bcaaf1367c9b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 16:23:46 +0200 Subject: [PATCH 0347/1249] Fix issue where overrides on instance would add a new one on update, support parenting into hierarchy more solidly, support parenting during update when root is parented to something. --- colorbleed/setdress_api.py | 81 ++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index cf775ee7ea..fc3a4319bf 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -92,6 +92,9 @@ def load_package(filepath, name, namespace=None): groupName="{}:{}".format(namespace, name), typ="Alembic") + # Get the top root node (the reference group) + root = "{}:{}".format(namespace, name) + containers = [] for representation_id, instances in data.items(): @@ -99,8 +102,11 @@ def load_package(filepath, name, namespace=None): loaders = list(lib.iter_loaders(representation_id)) for instance in instances: - container = _add(instance, representation_id, loaders, name, - namespace) + container = _add(instance=instance, + representation_id=representation_id, + loaders=loaders, + namespace=namespace, + root=root) containers.append(container) # TODO: Do we want to cripple? Or do we want to add a 'parent' parameter? @@ -119,7 +125,7 @@ def load_package(filepath, name, namespace=None): return containers + hierarchy -def _add(instance, representation_id, loaders, name, namespace): +def _add(instance, representation_id, loaders, namespace, root="|"): """Add an item from the package Args: @@ -154,21 +160,19 @@ def _add(instance, representation_id, loaders, name, namespace): namespace=instance['namespace']) # Get the root from the loaded container - root = get_container_transforms({"objectName": container}, - root=True) + loaded_root = get_container_transforms({"objectName": container}, + root=True) # Apply matrix to root node (if any matrix edits) matrix = instance.get("matrix", None) if matrix: - cmds.xform(root, objectSpace=True, matrix=matrix) + cmds.xform(loaded_root, objectSpace=True, matrix=matrix) # Parent into the setdress hierarchy # Namespace is missing from parent node(s), add namespace # manually - parent_grp = instance["parent"] - parent_grp = "{}:{}|".format(namespace, name) + to_namespace(parent_grp, namespace) - - cmds.parent(root, parent_grp, relative=True) + parent = root + to_namespace(instance["parent"], namespace) + cmds.parent(loaded_root, parent, relative=True) return container @@ -348,6 +352,11 @@ def update_scene(set_container, containers, current_data, new_data, new_file): for container in containers: container_ns = container['namespace'] + # Consider it processed here, even it it fails we want to store that + # the namespace was already available. + processed_namespaces.add(container_ns) + processed_containers.append(container['objectName']) + if container_ns in new_lookup: root = get_container_transforms(container, root=True) if not root: @@ -388,27 +397,42 @@ def update_scene(set_container, containers, current_data, new_data, new_file): root = cmds.parent(root, parent, relative=True) cmds.lockNode(root, lock=True) - # TODO: Update the representation with the new loader - if new_instance['loader'] != old_instance['loader']: - log.error("Switching loader between updates is not supported.") - continue - - # TODO: Update the representation with the new loader - representation_new = new_instance['representation'] + # Update the representation + representation_current = container['representation'] representation_old = old_instance['representation'] - if representation_new != representation_old: + representation_new = new_instance['representation'] + has_representation_override = (representation_current != + representation_old) - print "namespace :", container_ns + if representation_new != representation_current: + if has_representation_override: + log.warning("Your scene had local representation " + "overrides within the set. New " + "representations not loaded for %s.", + container_ns) + continue + + # We check it against the current 'loader' in the scene instead + # of the original data of the package that was loaded because + # an Artist might have made scene local overrides + if new_instance['loader'] != container['loader']: + log.error("Switching loader between updates is not " + "supported. Skipping: %s", container_ns) + continue + + # Check whether the conversion can be done by the Loader. + # They *must* use the same asset, subset and Loader for + # `api.update` to make sense. + old = io.find_one({"_id": io.ObjectId(representation_current)}) new = io.find_one({"_id": io.ObjectId(representation_new)}) - old = io.find_one({"_id": io.ObjectId(representation_old)}) - is_valid = compare_representations(old=old, new=new) if not is_valid: + log.error("Skipping: %s. See log for details.", + container_ns) continue new_version = new["context"]["version"] - api.update(container, version=new_version) else: @@ -416,9 +440,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): log.warning("Removing content: %s", container_ns) api.remove(container) - processed_namespaces.add(container_ns) - processed_containers.append(container['objectName']) - # Add new assets for representation_id, instances in new_data.items(): @@ -431,11 +452,11 @@ def update_scene(set_container, containers, current_data, new_data, new_file): if instance['namespace'] in processed_namespaces: continue - container = _add(instance, - representation_id, - loaders, - set_container['name'], - set_container['namespace']) + container = _add(instance=instance, + representation_id=representation_id, + loaders=loaders, + namespace=set_container['namespace'], + root=set_root) # Add to the setdress container cmds.sets(container, From 6905534738ebd78534dc09ceb226d989bbd6134d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 16:31:55 +0200 Subject: [PATCH 0348/1249] Improve logging order and log warning when instance is empty --- .../plugins/maya/publish/collect_instances.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index cfeb2d4098..c25856c12c 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -47,12 +47,6 @@ class CollectInstances(pyblish.api.ContextPlugin): objectset = cmds.ls("*.id", long=True, type="objectSet", recursive=True, objectsOnly=True) for objset in objectset: - self.log.info("Creating instance for {}".format(objset)) - - members = cmds.sets(objset, query=True) - if members is None: - self.log.info("Skipped empty Set: \"%s\" " % objset) - continue if not cmds.attributeQuery("id", node=objset, exists=True): continue @@ -68,6 +62,13 @@ class CollectInstances(pyblish.api.ContextPlugin): exists=True) assert has_family, "\"%s\" was missing a family" % objset + members = cmds.sets(objset, query=True) + if members is None: + self.log.warning("Skipped empty instance: \"%s\" " % objset) + continue + + self.log.info("Creating instance for {}".format(objset)) + data = dict() # Apply each user defined attribute as data From c77f305269b48949219eda51baba6a7d36800e13 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Oct 2017 23:52:46 +0200 Subject: [PATCH 0349/1249] Add icons and cleanup docstrings --- colorbleed/plugins/maya/create/colorbleed_animation.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_camera.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_look.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_mayaascii.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_model.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_pointcache.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_rig.py | 3 ++- colorbleed/plugins/maya/create/colorbleed_setdress.py | 3 ++- 8 files changed, 16 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 68f7ec2744..b559e15ec9 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -5,11 +5,12 @@ from colorbleed.maya import lib class CreateAnimation(avalon.maya.Creator): - """THe animated objects in the scene""" + """Animation output for character rigs""" name = "animationDefault" label = "Animation" family = "colorbleed.animation" + icon = "male" def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 0029380d34..94c1a82225 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -4,11 +4,12 @@ from colorbleed.maya import lib class CreateCamera(avalon.maya.Creator): - """Single baked camera extraction""" + """Single baked camera""" name = "cameraDefault" label = "Camera" family = "colorbleed.camera" + icon = "video-camera" def __init__(self, *args, **kwargs): super(CreateCamera, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index 9ac1cbe934..d5c0255360 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -4,11 +4,12 @@ from colorbleed.maya import lib class CreateLook(avalon.maya.Creator): - """Polygonal geometry for animation""" + """Shader connections defining shape look""" name = "look" label = "Look" family = "colorbleed.look" + icon = "paint-brush" def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py index 2c19e13711..d8cc9f9897 100644 --- a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py +++ b/colorbleed/plugins/maya/create/colorbleed_mayaascii.py @@ -2,8 +2,9 @@ import avalon.maya class CreateMayaAscii(avalon.maya.Creator): - """Raw Maya Ascii file of the item(s)""" + """Raw Maya Ascii file export""" name = "mayaAscii" label = "Maya Ascii" family = "colorbleed.mayaAscii" + icon = "file-archive-o" diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index 364d00dc8d..b55b3dc3dd 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -2,8 +2,9 @@ import avalon.maya class CreateModel(avalon.maya.Creator): - """Polygonal geometry for animation""" + """Polygonal static geometry""" name = "modelDefault" label = "Model" family = "colorbleed.model" + icon = "cube" diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 4dd5e11cde..c6a4edc5c1 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -5,11 +5,12 @@ from colorbleed.maya import lib class CreatePointCache(avalon.maya.Creator): - """Alembic extract""" + """Alembic pointcache for animated data""" name = "pointcache" label = "Point Cache" family = "colorbleed.pointcache" + icon = "gears" def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index 3fa718d756..6947aaac31 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -4,11 +4,12 @@ import avalon.maya class CreateRig(avalon.maya.Creator): - """Skeleton and controls for manipulation of the geometry""" + """Artist-friendly rig with controls to direct motion""" name = "rigDefault" label = "Rig" family = "colorbleed.rig" + icon = "wheelchair" def process(self): instance = super(CreateRig, self).process() diff --git a/colorbleed/plugins/maya/create/colorbleed_setdress.py b/colorbleed/plugins/maya/create/colorbleed_setdress.py index fe0c64dd40..47089bea21 100644 --- a/colorbleed/plugins/maya/create/colorbleed_setdress.py +++ b/colorbleed/plugins/maya/create/colorbleed_setdress.py @@ -2,8 +2,9 @@ import avalon.maya class CreateSetDress(avalon.maya.Creator): - """THe animated objects in the scene""" + """A grouped package of loaded content""" name = "setdress" label = "Set Dress" family = "colorbleed.setdress" + icon = "cubes" \ No newline at end of file From 4396ec06bad0638712b4ac121415241e5ee8314c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 22 Oct 2017 00:05:52 +0200 Subject: [PATCH 0350/1249] Implement PLN-22 --- colorbleed/plugins/publish/integrate.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index e26d45350b..07b416f3b0 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -50,7 +50,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Required environment variables PROJECT = os.environ["AVALON_PROJECT"] ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - SILO = os.environ["AVALON_SILO"] LOCATION = os.getenv("AVALON_LOCATION") context = instance.context @@ -140,7 +139,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): root = api.registered_root() template_data = {"root": root, "project": PROJECT, - "silo": SILO, + "silo": asset['silo'], "asset": ASSET, "subset": subset["name"], "version": version["name"]} @@ -214,7 +213,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "context": { "project": PROJECT, "asset": ASSET, - "silo": SILO, + "silo": asset['silo'], "subset": subset["name"], "version": version["name"], "representation": ext[1:] From 0f0479490e9c2ec4d782878ab35aa2321921239d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 22 Oct 2017 00:34:27 +0200 Subject: [PATCH 0351/1249] Fix generate uuid action so it uses pipeline methods and applies the ids as required by the instance.data['asset'] (instead of current context asset) --- colorbleed/action.py | 52 ++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 5566518588..4913fe8518 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -142,7 +142,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): receive new UUIDs are actually invalid. Requires: - - currentFile on context + - instance.data["asset"] """ @@ -166,44 +166,38 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): instances = pyblish.api.instances_by_plugin(errored_instances, plugin) # Get the nodes from the all instances that ran through this plug-in - invalid = [] + all_invalid = [] for instance in instances: - invalid_nodes = plugin.get_invalid(instance) - if invalid_nodes: - invalid.extend(invalid_nodes) + invalid = plugin.get_invalid(instance) + if invalid: - if not invalid: + self.log.info("Fixing instance {}".format(instance.name)) + self._update_id_attribute(instance, invalid) + + all_invalid.extend(invalid) + + if not all_invalid: self.log.info("No invalid nodes found.") return - # Ensure unique ( process each node only once ) - invalid = list(set(invalid)) + all_invalid = list(set(all_invalid)) + self.log.info("Generated ids on nodes: {0}".format(all_invalid)) - # Parse context from current file - self.log.info("Updating node IDs ...") - # Update the attributes - self._update_id_attribute(invalid) - - self.log.info("Generated ids on nodes: {0}".format(invalid)) - - def _update_id_attribute(self, nodes): + def _update_id_attribute(self, instance, nodes): """Delete the id attribute Args: - nodes (list): all nodes to remove the attribute from + instance: The instance we're fixing for + nodes (list): all nodes to regenerate ids on """ + import colorbleed.maya.lib as lib + import avalon.io as io + + asset = instance.data['asset'] + asset_id = io.find_one({"name": asset, "type": "asset"}, + projection={"_id": True})['_id'] for node in nodes: - - # get the database asset id - attr = "{}.cbId".format(node) - id_attr = cmds.getAttr(attr) - asset_id = id_attr.split(":")[0] - - # create a new unique id - _, uid = str(uuid.uuid4()).rsplit("-", 1) - cb_uid = "{}:{}".format(asset_id, uid) - - # set the new id - cmds.setAttr(attr, cb_uid, type="string") + lib.remove_id(node) + lib.set_id(asset_id, node) From 8a516e7bb9195e0e6cba9b6fc0f336e6bbdb8d1a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 10:09:27 +0200 Subject: [PATCH 0352/1249] cleaned code, removed line --- colorbleed/plugins/maya/publish/collect_setdress.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index e7ffa84a54..09282ef773 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -36,17 +36,13 @@ class CollectSetDress(pyblish.api.InstancePlugin): hierarchy_nodes = [] for container in containers: members = cmds.sets(container["objectName"], query=True) - transforms = lib.get_container_transforms(container, members) - root = lib.get_container_transforms(container, - transforms, - root=True) + root = lib.get_container_transforms(container, root=True) if root not in instance_lookup: continue # Retrieve all matrix data parent = cmds.listRelatives(root, parent=True, fullPath=True)[0] - relative_hierarchy = parent.replace(topnode, "*") - hierarchy_nodes.append(relative_hierarchy) + hierarchy_nodes.append(parent) # Gather info for new data entry reference_node = cmds.ls(members, type="reference")[0] From ec6c3f9dd4a826d82f14361b6d2a9eb195c935d4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 10:14:49 +0200 Subject: [PATCH 0353/1249] clean up --- colorbleed/plugins/maya/publish/collect_setdress.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 09282ef773..f1ec53a346 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -29,7 +29,6 @@ class CollectSetDress(pyblish.api.InstancePlugin): containers = amaya.ls() # Get all content from the instance - topnode = cmds.sets(instance.name, query=True)[0] instance_lookup = set(cmds.ls(instance, type="transform", long=True)) data = defaultdict(list) From cf24e3978f7f15cc4659201d989be281dfd6bdab Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 10:28:23 +0200 Subject: [PATCH 0354/1249] now selects only hierarchy nodes from collect_setdress --- colorbleed/plugins/maya/publish/extract_setdress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index d902c2073d..1d04358c83 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -33,7 +33,7 @@ class ExtractSetDress(colorbleed.api.Extractor): json.dump(instance.data["scenedata"], filepath, ensure_ascii=False) self.log.info("Extracting point cache ..") - cmds.select(cmds.ls(instance.data["hierarchy"], long=True)) + cmds.select(instance.data["hierarchy"]) # Run basic alembic exporter extract_alembic(file=hierarchy_path, From cb4e0323ef1b3b98e894eab33e7b69bf77bf51b5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 10:51:12 +0200 Subject: [PATCH 0355/1249] removed old logic for get representation --- colorbleed/maya/lib.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 56191d880e..f2c92fc670 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -12,7 +12,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel -from avalon import maya, io +from avalon import api, maya, io from cb.utils.maya import core @@ -688,22 +688,6 @@ def remove_id(node): cmds.deleteAttr("{}.cbId".format(node)) -def get_representation_file(representation, template=TEMPLATE): - """ - Rebuild the filepath of the representation's context - Args: - representation (dict): data of the registered in the database - template (str): the template to fill - - Returns: - str - - """ - context = representation["context"].copy() - context["root"] = os.environ["AVALON_PROJECTS"] - return template.format(**context) - - def get_reference_node(path): """ Get the reference node when the path is found being used in a reference @@ -813,8 +797,8 @@ def assign_look_by_version(nodes, version_id): "name": "json"}) # Load file - shader_filepath = get_representation_file(shader_file) - shader_relation = get_representation_file(shader_relations) + shader_filepath = api.get_representation_path(shader_file["_id"]) + shader_relation = api.get_representation_path(shader_relations["_id"]) reference_node = get_reference_node(shader_filepath) if reference_node is None: From 7197049643b790f5c202036da714841b4028edd7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 11:15:59 +0200 Subject: [PATCH 0356/1249] fixed bug in representation reqeust --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f2c92fc670..37d493f816 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -797,8 +797,8 @@ def assign_look_by_version(nodes, version_id): "name": "json"}) # Load file - shader_filepath = api.get_representation_path(shader_file["_id"]) - shader_relation = api.get_representation_path(shader_relations["_id"]) + shader_filepath = api.get_representation_path(shader_file) + shader_relation = api.get_representation_path(shader_relations) reference_node = get_reference_node(shader_filepath) if reference_node is None: From e30d14bc4d5cde8f21d84ebc192d7517c97503db Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 11:34:07 +0200 Subject: [PATCH 0357/1249] added support for ither model types --- colorbleed/plugins/maya/publish/collect_setdress.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index f1ec53a346..e019183041 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -2,7 +2,7 @@ from collections import defaultdict import pyblish.api from maya import cmds, mel -from avalon import maya as amaya +from avalon import maya as avalon from colorbleed.maya import lib # TODO : Publish of setdress: -unique namespace for all assets, VALIDATOR! @@ -26,7 +26,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): def process(self, instance): # Find containers - containers = amaya.ls() + containers = avalon.ls() # Get all content from the instance instance_lookup = set(cmds.ls(instance, type="transform", long=True)) @@ -34,7 +34,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): hierarchy_nodes = [] for container in containers: - members = cmds.sets(container["objectName"], query=True) + root = lib.get_container_transforms(container, root=True) if root not in instance_lookup: continue @@ -44,13 +44,10 @@ class CollectSetDress(pyblish.api.InstancePlugin): hierarchy_nodes.append(parent) # Gather info for new data entry - reference_node = cmds.ls(members, type="reference")[0] - namespace = cmds.referenceQuery(reference_node, namespace=True) representation_id = container["representation"] - instance_data = {"loader": container["loader"], "parent": parent, - "namespace": namespace.strip(":")} + "namespace": container["namespace"]} # Check if matrix differs from default and store changes matrix_data = self.get_matrix_data(root) From 133524880e601511fbcdc91ed68fc4bd2104a823 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 12:24:57 +0200 Subject: [PATCH 0358/1249] added warning for gpu caches, no full support yet --- .../plugins/maya/publish/collect_setdress.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index e019183041..0f405d5cf3 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -17,6 +17,10 @@ class CollectSetDress(pyblish.api.InstancePlugin): * Compatible loader * Matrix per instance * Namespace + + Note: GPU caches are currently not supported in the pipeline. There is no + logic yet which supports the swapping of GPU cache to renderable objects. + """ order = pyblish.api.CollectorOrder + 0.49 @@ -39,13 +43,19 @@ class CollectSetDress(pyblish.api.InstancePlugin): if root not in instance_lookup: continue - # Retrieve all matrix data + # Retrieve the hierarchy parent = cmds.listRelatives(root, parent=True, fullPath=True)[0] hierarchy_nodes.append(parent) + # Temporary warning for GPU cache which are not supported yet + loader = container["loader"] + if loader == "GpuCacheLoader": + self.log.warning("GPU Cache Loader is currently not supported" + "in the pipeline, we will export it tho") + # Gather info for new data entry representation_id = container["representation"] - instance_data = {"loader": container["loader"], + instance_data = {"loader": loader, "parent": parent, "namespace": container["namespace"]} From cf5a0cc8ce0b6da5939542b6f75815154987a839 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 12:25:21 +0200 Subject: [PATCH 0359/1249] moved import of avalon api to the top --- colorbleed/setdress_api.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index fc3a4319bf..b2619ff27a 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -7,6 +7,7 @@ import copy from maya import cmds +from avalon import api import avalon.io as io from avalon.maya.lib import unique_namespace @@ -139,7 +140,6 @@ def _add(instance, representation_id, loaders, namespace, root="|"): """ - import avalon.api as api from colorbleed.maya.lib import get_container_transforms # Process within the namespace @@ -276,21 +276,19 @@ def update_package(set_container, representation): """ - import avalon.api - # Load the original package data current_representation = io.find_one({ "_id": io.ObjectId(set_container['representation']), "type": "representation" }) - current_file = avalon.api.get_representation_path(current_representation) + current_file = api.get_representation_path(current_representation) assert current_file.endswith(".json") with open(current_file, "r") as fp: current_data = json.load(fp) # Load the new package data - new_file = avalon.api.get_representation_path(representation) + new_file = api.get_representation_path(representation) assert new_file.endswith(".json") with open(new_file, "r") as fp: new_data = json.load(fp) @@ -327,7 +325,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): from colorbleed.maya.lib import DEFAULT_MATRIX, get_container_transforms from avalon.tools.cbloader import lib - from avalon import api set_namespace = set_container['namespace'] From 26f4c22bc9b0495bc6bc51595db77a4c7b7c18a1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 12:32:34 +0200 Subject: [PATCH 0360/1249] updated docstrings --- colorbleed/setdress_api.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index b2619ff27a..a444fad57a 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -14,9 +14,21 @@ from avalon.maya.lib import unique_namespace log = logging.getLogger("PackageLoader") -def matrix_equals(a, b, tolerance=1e-10): - """Compares two matrices with an imperfection tolerance""" - if not all(abs(x - y) < tolerance for x, y in zip(a, b)): +def matrix_equals(current_matrix, original_matrix, tolerance=1e-10): + """ + Compares two matrices with an imperfection tolerance + + Args: + current_matrix (list, tuple): the matrix to check + original_matrix (list, tuple): the matrix to check against + tolerance (long): the precision of the differences + + Returns: + bool : True or False + + """ + zipped = zip(current_matrix, original_matrix) + if not all(abs(x - y) < tolerance for x, y in zipped): return False return True @@ -232,7 +244,17 @@ def get_contained_containers(container): def update_package_version(container, version): - """Update package by version number""" + """ + Update package by version number + + Args: + container (dict): container data of the container node + version (int): the new version number of the package + + Returns: + None + + """ # Versioning (from `core.maya.pipeline`) current_representation = io.find_one({ @@ -272,7 +294,10 @@ def update_package(set_container, representation): Args: set_container (dict): container data from `ls()` - version (int): version number of the subset + representation (dict): the representation document from the database + + Returns: + None """ @@ -321,6 +346,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): Returns: processed_containers (list): all new and updated containers + """ from colorbleed.maya.lib import DEFAULT_MATRIX, get_container_transforms From 96e3e04e6c075c04bb7697d5de6ed07b8ef22b08 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 12:51:58 +0200 Subject: [PATCH 0361/1249] added unlocked context manager for update --- colorbleed/setdress_api.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index a444fad57a..734f130df0 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -73,6 +73,28 @@ def namespaced(namespace, new=True): cmds.namespace(set=original) +@contextlib.contextmanager +def unlocked(nodes): + + # Get node state by Maya's uuid + nodes = cmds.ls(nodes, long=True) + uuids = cmds.ls(nodes, uuid=True) + states = cmds.lockNode(nodes, query=True, lock=True) + states = {uuid: state for uuid, state in zip(uuids, states)} + + try: + cmds.lockNode(nodes, lock=False) + yield + finally: + # Reapply original states + for uuid, state in states.iteritems(): + nodes_from_id = cmds.ls(uuid, long=True) + if not nodes_from_id: + log.warning("Node not found: %s", uuid) + continue + cmds.lockNode(nodes_from_id[0], lock=state) + + def load_package(filepath, name, namespace=None): """Load a package that was gathered elsewhere. @@ -361,9 +383,10 @@ def update_scene(set_container, containers, current_data, new_data, new_file): referenceNode=True) new_alembic = new_file.replace(".json", ".abc") assert os.path.exists(new_alembic), "%s does not exist." % new_alembic - cmds.file(new_alembic, - loadReference=set_hierarchy_reference, - type="Alembic") + with unlocked(cmds.listRelatives(set_root, ad=True, fullPath=True)): + cmds.file(new_alembic, + loadReference=set_hierarchy_reference, + type="Alembic") identity = DEFAULT_MATRIX[:] From 66be3bef50a3b017934150286da325041a7c669b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 16:14:19 +0200 Subject: [PATCH 0362/1249] Temp fix for Abc locader --- colorbleed/plugins/maya/load/load_alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index b0b2b86101..4998e704b0 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -2,7 +2,7 @@ from avalon import api import avalon.maya.pipeline -class AbcLoader(api.Loader): +class AbcLoader(avalon.maya.pipeline.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", From 571beb6c4075d988b61c3de3013feccf13d3be36 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 16:27:19 +0200 Subject: [PATCH 0363/1249] added matrix to maya.lib --- colorbleed/maya/lib.py | 22 +++++++++ .../publish/validate_setdress_transforms.py | 48 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_setdress_transforms.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index a465984138..40a177a51b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -95,6 +95,24 @@ _alembic_options = { } +def matrix_equals(a, b, tolerance=1e-10): + """ + Compares two matrices with an imperfection tolerance + + Args: + current_matrix (list, tuple): the matrix to check + original_matrix (list, tuple): the matrix to check against + tolerance (long): the precision of the differences + + Returns: + bool : True or False + + """ + if not all(abs(x - y) < tolerance for x, y in zip(a, b)): + return False + return True + + def unique(name): assert isinstance(name, basestring), "`name` must be string" @@ -577,6 +595,7 @@ def maya_temp_folder(): return tmp_dir +# region ID def get_id_required_nodes(referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly @@ -687,6 +706,7 @@ def remove_id(node): cmds.deleteAttr("{}.cbId".format(node)) +# endregion ID def get_reference_node(path): """ Get the reference node when the path is found being used in a reference @@ -758,6 +778,7 @@ def apply_attributes(attributes, nodes_by_id): set_attribute(attr, value, node) +# region LOOKDEV def list_looks(asset_id): """Return all look subsets for the given asset @@ -947,6 +968,7 @@ def apply_shaders(relationships, shadernodes, nodes): apply_attributes(attributes, ns_nodes_by_id) +# endregion LOOKDEV def get_isolate_view_sets(): """ diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py new file mode 100644 index 0000000000..4e2059a70d --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -0,0 +1,48 @@ +import pyblish.api + + +class CollectSetDress(pyblish.api.InstancePlugin): + """Collect all relevant setdress items + + Collected data: + + * File name + * Compatible loader + * Matrix per instance + * Namespace + + Note: GPU caches are currently not supported in the pipeline. There is no + logic yet which supports the swapping of GPU cache to renderable objects. + + """ + + order = pyblish.api.CollectorOrder + 0.49 + label = "Transformation of Items" + families = ["colorbleed.setdress"] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found invalid transforms of setdress items") + + @classmethod + def get_invalid(cls, instance): + + from colorbleed.maya import lib + from maya import cmds + + invalid = [] + + setdress_hierarchies = instance.data["hierarchy"] + items = cmds.listRelatives(instance, + allDescendents=True, + type="transform", + fullPath=True) + for item in items: + if item in setdress_hierarchies: + continue + + matri + + + return invalid From 8f80d7405dcfe8a4baf8c0fa807778ce497bbb29 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 16:27:47 +0200 Subject: [PATCH 0364/1249] add transform check for valid transformations --- colorbleed/plugins/maya/publish/validate_setdress_transforms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py index 4e2059a70d..9d94fd33fa 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -42,7 +42,6 @@ class CollectSetDress(pyblish.api.InstancePlugin): if item in setdress_hierarchies: continue - matri return invalid From 722a1b5015b11d28c5e92df712c96683cbb2df2e Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 17:57:00 +0200 Subject: [PATCH 0365/1249] added maya.lib import --- colorbleed/setdress_api.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index 734f130df0..698e520616 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -10,29 +10,11 @@ from maya import cmds from avalon import api import avalon.io as io from avalon.maya.lib import unique_namespace +from colorbleed.maya.lib import matrix_equals log = logging.getLogger("PackageLoader") -def matrix_equals(current_matrix, original_matrix, tolerance=1e-10): - """ - Compares two matrices with an imperfection tolerance - - Args: - current_matrix (list, tuple): the matrix to check - original_matrix (list, tuple): the matrix to check against - tolerance (long): the precision of the differences - - Returns: - bool : True or False - - """ - zipped = zip(current_matrix, original_matrix) - if not all(abs(x - y) < tolerance for x, y in zipped): - return False - return True - - def to_namespace(node, namespace): """Return node name as if it's inside the namespace. From 721ef1edc8173acf08ec841783a1396a68f7aeee Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 17:57:32 +0200 Subject: [PATCH 0366/1249] changed argument names back to mathematical names --- colorbleed/maya/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 40a177a51b..a20315efa8 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -100,9 +100,9 @@ def matrix_equals(a, b, tolerance=1e-10): Compares two matrices with an imperfection tolerance Args: - current_matrix (list, tuple): the matrix to check - original_matrix (list, tuple): the matrix to check against - tolerance (long): the precision of the differences + a (list, tuple): the matrix to check + b (list, tuple): the matrix to check against + tolerance (float): the precision of the differences Returns: bool : True or False From d63a75fdeb2e2916f84a5cbdff4c654f82cc4bdf Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 23 Oct 2017 17:58:16 +0200 Subject: [PATCH 0367/1249] improved method of getting illegal transform node --- .../publish/validate_setdress_transforms.py | 62 ++++++++++++------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py index 9d94fd33fa..cd2fc3e72c 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -1,47 +1,67 @@ import pyblish.api +import colorbleed.api -class CollectSetDress(pyblish.api.InstancePlugin): - """Collect all relevant setdress items +class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): + """Verify only root nodes of the loaded asset have transformations. - Collected data: + Note: This check is temporary and is subject to change. - * File name - * Compatible loader - * Matrix per instance - * Namespace + Example outliner: + <> means referenced + =================================================================== - Note: GPU caches are currently not supported in the pipeline. There is no - logic yet which supports the swapping of GPU cache to renderable objects. + setdress_GRP| + props_GRP| + barrel_01_:modelDefault| [can have transforms] + <> barrel_01_:barrel_GRP [CAN'T have transforms] + + fence_01_:modelDefault| [can have transforms] + <> fence_01_:fence_GRP [CAN'T have transforms] """ - order = pyblish.api.CollectorOrder + 0.49 - label = "Transformation of Items" + order = pyblish.api.ValidatorOrder + 0.49 + label = "Setdress Model Transforms" families = ["colorbleed.setdress"] + actions = [colorbleed.api.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found invalid transforms of setdress items") + raise RuntimeError("Found %s invalid transforms of setdress items") @classmethod def get_invalid(cls, instance): - from colorbleed.maya import lib + import colorbleed.maya.lib as lib from maya import cmds invalid = [] - setdress_hierarchies = instance.data["hierarchy"] - items = cmds.listRelatives(instance, - allDescendents=True, - type="transform", - fullPath=True) - for item in items: - if item in setdress_hierarchies: - continue + # Get all transforms in the loaded containers + container_roots = cmds.listRelatives(instance.data["hierarchy"], + children=True, + type="transform", + fullPath=True) + transforms_in_container = cmds.listRelatives(container_roots, + allDescendents=True, + type="transform", + fullPath=True) + # Extra check due to the container roots still being passed through + transforms_in_container = [i for i in transforms_in_container if i + not in container_roots] + + # Ensure all are identity matrix + for transform in transforms_in_container: + node_matrix = cmds.xform(transform, + query=True, + matrix=True, + objectSpace=True) + if not lib.matrix_equals(node_matrix, lib.DEFAULT_MATRIX): + print transform + invalid.append(transform) return invalid From e715602aa678178b5cbadcf11db08c67e6dad897 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 24 Oct 2017 09:44:08 +0200 Subject: [PATCH 0368/1249] improved error message, removed print, cosmetics --- .../plugins/maya/publish/validate_setdress_transforms.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py index cd2fc3e72c..84b2d0f112 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -29,7 +29,8 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found %s invalid transforms of setdress items") + raise RuntimeError("Found {} invalid transforms of setdress " + "items".format(len(invalid))) @classmethod def get_invalid(cls, instance): @@ -37,8 +38,6 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): import colorbleed.maya.lib as lib from maya import cmds - invalid = [] - # Get all transforms in the loaded containers container_roots = cmds.listRelatives(instance.data["hierarchy"], children=True, @@ -55,13 +54,13 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): not in container_roots] # Ensure all are identity matrix + invalid = [] for transform in transforms_in_container: node_matrix = cmds.xform(transform, query=True, matrix=True, objectSpace=True) if not lib.matrix_equals(node_matrix, lib.DEFAULT_MATRIX): - print transform invalid.append(transform) return invalid From ca9abc83123339846831a11bc79aa084415ebd15 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 10:03:20 +0200 Subject: [PATCH 0369/1249] Update Loaders to new-style plug-ins; inherit from `colorbleed.maya.plugin.ReferenceLoader`; fix updating referenced containers to also contain "new" nodes --- colorbleed/maya/plugin.py | 124 ++++++++++++++++++ .../plugins/maya/load/_load_animation.py | 10 +- colorbleed/plugins/maya/load/load_alembic.py | 33 +---- colorbleed/plugins/maya/load/load_look.py | 11 +- .../plugins/maya/load/load_mayaascii.py | 8 +- colorbleed/plugins/maya/load/load_model.py | 17 ++- colorbleed/plugins/maya/load/load_rig.py | 8 +- 7 files changed, 159 insertions(+), 52 deletions(-) create mode 100644 colorbleed/maya/plugin.py diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py new file mode 100644 index 0000000000..2a7c32d2ac --- /dev/null +++ b/colorbleed/maya/plugin.py @@ -0,0 +1,124 @@ +from avalon import api + + +class ReferenceLoader(api.Loader): + """A basic ReferenceLoader for Maya + + This will implement the basic behavior for a loader to inherit from that + will containerize the reference and will implement the `remove` and + `update` logic. + + """ + def load(self, + context, + name=None, + namespace=None, + data=None): + + from avalon.maya import lib + from avalon.maya.pipeline import containerise + + asset = context['asset'] + + namespace = namespace or lib.unique_namespace( + asset["name"] + "_", + prefix="_" if asset["name"][0].isdigit() else "", + suffix="_", + ) + + self.process_reference(context=context, + name=name, + namespace=namespace, + data=data) + + # Only containerize if any nodes were loaded by the Loader + nodes = self[:] + if not nodes: + return + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def process_reference(self, context, name, namespace, data): + """To be implemented by subclass""" + raise NotImplementedError("Must be implemented by subclass") + + def update(self, container, representation): + + import os + from maya import cmds + + node = container["objectName"] + + path = api.get_representation_path(representation) + + # Assume asset has been referenced + reference_node = next((node for node in cmds.sets(node, query=True) + if cmds.nodeType(node) == "reference"), None) + + file_type = { + "ma": "mayaAscii", + "mb": "mayaBinary", + "abc": "Alembic" + }.get(representation["name"]) + + assert file_type, "Unsupported representation: %s" % representation + + assert os.path.exists(path), "%s does not exist." % path + cmds.file(path, loadReference=reference_node, type=file_type) + + # TODO: Add all new nodes in the reference to the container + # Currently new nodes in an updated reference are not added to the + # container whereas actually they should be! + nodes = cmds.referenceQuery(reference_node, nodes=True, dagPath=True) + cmds.sets(nodes, forceElement=container['objectName']) + + # Update metadata + cmds.setAttr(container["objectName"] + ".representation", + str(representation["_id"]), + type="string") + + def remove(self, container): + """Remove an existing `container` from Maya scene + + Deprecated; this functionality is replaced by `api.remove()` + + Arguments: + container (avalon-core:container-1.0): Which container + to remove from scene. + + """ + + from maya import cmds + + node = container["objectName"] + + # Assume asset has been referenced + reference_node = next((node for node in cmds.sets(node, query=True) + if cmds.nodeType(node) == "reference"), None) + + assert reference_node, ("Imported container not supported; " + "container must be referenced.") + + self.log.info("Removing '%s' from Maya.." % container["name"]) + + namespace = cmds.referenceQuery(reference_node, namespace=True) + fname = cmds.referenceQuery(reference_node, filename=True) + cmds.file(fname, removeReference=True) + + try: + cmds.delete(node) + except ValueError: + # Already implicitly deleted by Maya upon removing reference + pass + + try: + # If container is not automatically cleaned up by May (issue #118) + cmds.namespace(removeNamespace=namespace, + deleteNamespaceContent=True) + except RuntimeError: + pass diff --git a/colorbleed/plugins/maya/load/_load_animation.py b/colorbleed/plugins/maya/load/_load_animation.py index cabb06d886..f4bcd6881d 100644 --- a/colorbleed/plugins/maya/load/_load_animation.py +++ b/colorbleed/plugins/maya/load/_load_animation.py @@ -1,7 +1,7 @@ -import avalon.maya.pipeline +import colorbleed.maya.plugin -class AbcLoader(avalon.maya.pipeline.ReferenceLoader): +class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", @@ -14,7 +14,7 @@ class AbcLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): import maya.cmds as cmds from avalon import maya @@ -43,4 +43,6 @@ class AbcLoader(avalon.maya.pipeline.ReferenceLoader): returnNewNodes=True) # load colorbleed ID attribute - self[:] = nodes \ No newline at end of file + self[:] = nodes + + return nodes diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index 4998e704b0..52ddde51c4 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -1,8 +1,7 @@ -from avalon import api -import avalon.maya.pipeline +import colorbleed.maya.plugin -class AbcLoader(avalon.maya.pipeline.ReferenceLoader): +class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", @@ -14,7 +13,7 @@ class AbcLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): import maya.cmds as cmds @@ -29,28 +28,4 @@ class AbcLoader(avalon.maya.pipeline.ReferenceLoader): self[:] = nodes - -# class SetDressAlembicLoader(avalon.maya.pipeline.ReferenceLoader): -# """Load the setdress as alembic""" -# -# families = ["colorbleed.setdress"] -# label = "Reference Alembic" -# representations = ["abc"] -# order = -10 -# icon = "code-fork" -# color = "orange" -# -# def process(self, name, namespace, context, data): -# -# import maya.cmds as cmds -# -# cmds.loadPlugin("AbcImport.mll", quiet=True) -# nodes = cmds.file(self.fname, -# namespace=namespace, -# sharedReferenceFile=False, -# groupReference=True, -# groupName="{}:{}".format(namespace, name), -# reference=True, -# returnNewNodes=True) -# -# self[:] = nodes + return nodes diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 157bf89740..3260737349 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -1,10 +1,7 @@ -import os -import json - -import avalon.maya.pipeline +import colorbleed.maya.plugin -class LookLoader(avalon.maya.pipeline.ReferenceLoader): +class LookLoader(colorbleed.maya.plugin.ReferenceLoader): """Specific loader for lookdev""" families = ["colorbleed.look"] @@ -15,7 +12,7 @@ class LookLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): """ Load and try to ssign Lookdev to nodes based on relationship data Args: @@ -28,6 +25,8 @@ class LookLoader(avalon.maya.pipeline.ReferenceLoader): """ + import json + import os import maya.cmds as cmds from avalon import maya import colorbleed.maya.lib as lib diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/colorbleed/plugins/maya/load/load_mayaascii.py index cdec04b773..07bf1ea836 100644 --- a/colorbleed/plugins/maya/load/load_mayaascii.py +++ b/colorbleed/plugins/maya/load/load_mayaascii.py @@ -1,7 +1,7 @@ -import avalon.maya.pipeline +import colorbleed.maya.plugin -class MayaAsciiLoader(avalon.maya.pipeline.ReferenceLoader): +class MayaAsciiLoader(colorbleed.maya.plugin.ReferenceLoader): """Load the model""" families = ["colorbleed.mayaAscii"] @@ -12,7 +12,7 @@ class MayaAsciiLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): import maya.cmds as cmds from avalon import maya @@ -26,3 +26,5 @@ class MayaAsciiLoader(avalon.maya.pipeline.ReferenceLoader): groupName="{}:{}".format(namespace, name)) self[:] = nodes + + return nodes diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index e5d9dd960a..0943eb4b24 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -1,8 +1,8 @@ -import avalon.maya.pipeline -import avalon.api +from avalon import api +import colorbleed.maya.plugin -class ModelLoader(avalon.maya.pipeline.ReferenceLoader): +class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): """Load the model""" families = ["colorbleed.model"] @@ -13,7 +13,7 @@ class ModelLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): import maya.cmds as cmds from avalon import maya @@ -28,8 +28,10 @@ class ModelLoader(avalon.maya.pipeline.ReferenceLoader): self[:] = nodes + return nodes -class GpuCacheLoader(avalon.api.Loader): + +class GpuCacheLoader(api.Loader): """Load model Alembic as gpuCache""" families = ["colorbleed.model"] @@ -44,6 +46,7 @@ class GpuCacheLoader(avalon.api.Loader): import maya.cmds as cmds import avalon.maya.lib as lib + from avalon.maya.pipeline import containerise asset = context['asset']['name'] namespace = namespace or lib.unique_namespace( @@ -76,7 +79,7 @@ class GpuCacheLoader(avalon.api.Loader): nodes = [root, transform, cache] self[:] = nodes - return avalon.maya.pipeline.containerise( + return containerise( name=name, namespace=namespace, nodes=nodes, @@ -87,7 +90,7 @@ class GpuCacheLoader(avalon.api.Loader): import maya.cmds as cmds - path = avalon.api.get_representation_path(representation) + path = api.get_representation_path(representation) # Update the cache members = cmds.sets(container['objectName'], query=True) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 20df124274..96ff40fc26 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -2,11 +2,11 @@ import os from maya import cmds -import avalon.maya.pipeline from avalon import api, maya +import colorbleed.maya.plugin -class RigLoader(avalon.maya.pipeline.ReferenceLoader): +class RigLoader(colorbleed.maya.plugin.ReferenceLoader): """Specific loader for rigs This automatically creates an instance for animators upon load. @@ -21,7 +21,7 @@ class RigLoader(avalon.maya.pipeline.ReferenceLoader): icon = "code-fork" color = "orange" - def process(self, name, namespace, context, data): + def process_reference(self, context, name, namespace, data): nodes = cmds.file(self.fname, namespace=namespace, @@ -35,6 +35,8 @@ class RigLoader(avalon.maya.pipeline.ReferenceLoader): if data.get("post_process", True): self._post_process(name, namespace, context, data) + return nodes + def _post_process(self, name, namespace, context, data): # TODO(marcus): We are hardcoding the name "out_SET" here. From b2af294bd9ed2785f615e08615344c110aef6eeb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 10:06:01 +0200 Subject: [PATCH 0370/1249] Fix PLN-26 --- colorbleed/plugins/maya/load/load_model.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 0943eb4b24..9b17a1ae47 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -110,3 +110,10 @@ class GpuCacheLoader(api.Loader): members = cmds.sets(container['objectName'], query=True) cmds.lockNode(members, lock=False) cmds.delete([container['objectName']] + members) + + # Clean up the namespace + try: + cmds.namespace(removeNamespace=container['namespace'], + deleteNamespaceContent=True) + except RuntimeError: + pass From 45731572889659d5bb664d98f20ea1ead73dece0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 24 Oct 2017 10:28:12 +0200 Subject: [PATCH 0371/1249] added repair function with prompt --- .../publish/validate_setdress_transforms.py | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py index 84b2d0f112..c3547f963e 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -1,6 +1,8 @@ import pyblish.api import colorbleed.api +from maya import cmds + class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): """Verify only root nodes of the loaded asset have transformations. @@ -24,7 +26,12 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder + 0.49 label = "Setdress Model Transforms" families = ["colorbleed.setdress"] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + prompt_message = ("You are about to reset the matrix to the default values." + " This can alter the look of your scene. " + "Are you sure you want to continue?") def process(self, instance): invalid = self.get_invalid(instance) @@ -36,7 +43,6 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): def get_invalid(cls, instance): import colorbleed.maya.lib as lib - from maya import cmds # Get all transforms in the loaded containers container_roots = cmds.listRelatives(instance.data["hierarchy"], @@ -64,3 +70,38 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): invalid.append(transform) return invalid + + @classmethod + def repair(cls, instance): + """Reset matrix for illegally transformed nodes + + We want to ensure the user knows the reset will alter the look of + the current scene because the transformations were done on asset + nodes instead of the asset top node. + + Args: + instance: + + Returns: + None + + """ + + import colorbleed.maya.lib as lib + from avalon.vendor.Qt import QtWidgets + + # Store namespace in variable, cosmetics thingy + messagebox = QtWidgets.QMessageBox + mode = messagebox.StandardButton.Ok | messagebox.StandardButton.Cancel + choice = messagebox.warning(None, + "Matrix reset", + cls.prompt_message, + mode) + + invalid = cls.get_invalid(instance) + if not invalid: + cls.log.info("No invalid nodes") + return + + if choice: + cmds.xform(invalid, matrix=lib.DEFAULT_MATRIX, objectSpace=True) From f5d6956060c12323ae7a1235af782e69fedf9be2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 11:07:02 +0200 Subject: [PATCH 0372/1249] Fix merge --- colorbleed/maya/lib.py | 26 +++++++++++++++++-- colorbleed/plugins/maya/load/load_rig.py | 7 +++-- .../maya/publish/collect_renderlayers.py | 3 +-- .../publish/collect_assumed_destination.py | 9 ++++--- colorbleed/plugins/publish/integrate.py | 4 +-- colorbleed/setdress_api.py | 20 +------------- 6 files changed, 36 insertions(+), 33 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 37d493f816..fe97252d5d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -95,6 +95,24 @@ _alembic_options = { } +def matrix_equals(a, b, tolerance=1e-10): + """ + Compares two matrices with an imperfection tolerance + + Args: + a (list, tuple): the matrix to check + b (list, tuple): the matrix to check against + tolerance (float): the precision of the differences + + Returns: + bool : True or False + + """ + if not all(abs(x - y) < tolerance for x, y in zip(a, b)): + return False + return True + + def unique(name): assert isinstance(name, basestring), "`name` must be string" @@ -577,6 +595,7 @@ def maya_temp_folder(): return tmp_dir +# region ID def get_id_required_nodes(referenced_nodes=False): """Filter out any node which are locked (reference) or readOnly @@ -656,8 +675,8 @@ def generate_ids(nodes): """Assign a new id of the current active context to the nodes""" # Get the asset ID from the database for the asset of current context - asset = os.environ["AVALON_ASSET"] - asset_id = io.find_one({"type": "asset", "name": asset}, + asset_id = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}, projection={"_id": True}) for node in nodes: @@ -688,6 +707,7 @@ def remove_id(node): cmds.deleteAttr("{}.cbId".format(node)) +# endregion ID def get_reference_node(path): """ Get the reference node when the path is found being used in a reference @@ -759,6 +779,7 @@ def apply_attributes(attributes, nodes_by_id): set_attribute(attr, value, node) +# region LOOKDEV def list_looks(asset_id): """Return all look subsets for the given asset @@ -948,6 +969,7 @@ def apply_shaders(relationships, shadernodes, nodes): apply_attributes(attributes, ns_nodes_by_id) +# endregion LOOKDEV def get_isolate_view_sets(): """ diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index 96ff40fc26..fdd926eaa6 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -1,12 +1,11 @@ -import os - from maya import cmds -from avalon import api, maya import colorbleed.maya.plugin +from avalon import api, maya class RigLoader(colorbleed.maya.plugin.ReferenceLoader): + """Specific loader for rigs This automatically creates an instance for animators upon load. @@ -56,7 +55,7 @@ class RigLoader(colorbleed.maya.plugin.ReferenceLoader): roots = cmds.ls(self[:], assemblies=True, long=True) assert roots, "No root nodes in rig, this is a bug." - asset = os.environ["AVALON_ASSET"] + asset = api.Session["AVALON_ASSET"] dependency = str(context["representation"]["_id"]) # Create the animation instance diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 971c9b582f..a8cdc5a40f 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,4 +1,3 @@ -import os from maya import cmds import pyblish.api @@ -18,7 +17,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): def process(self, context): registered_root = api.registered_root() - asset_name = os.environ["AVALON_ASSET"] + asset_name = api.Session["AVALON_ASSET"] current_file = context.data["currentFile"] relative_file = current_file.replace(registered_root, "{root}") diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/publish/collect_assumed_destination.py index d57039e8fb..00e56cd2bf 100644 --- a/colorbleed/plugins/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/publish/collect_assumed_destination.py @@ -1,7 +1,7 @@ import pyblish.api import os -import avalon.io as io +from avalon import io, api class CollectAssumedDestination(pyblish.api.InstancePlugin): @@ -64,7 +64,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): # get all the stuff from the database subset_name = instance.data["subset"] asset_name = instance.data["asset"] - project_name = os.environ["AVALON_PROJECT"] + project_name = api.Session["AVALON_PROJECT"] project = io.find_one({"type": "project", "name": project_name}, @@ -78,6 +78,7 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): assert asset, ("No asset found by the name '{}' " "in project '{}'".format(asset_name, project_name)) + silo = asset['silo'] subset = io.find_one({"type": "subset", "name": subset_name, @@ -95,9 +96,9 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): if version is not None: version_number += version["name"] - template_data = {"root": os.environ["AVALON_PROJECTS"], + template_data = {"root": api.Session["AVALON_PROJECTS"], "project": project_name, - "silo": os.environ["AVALON_SILO"], + "silo": silo, "asset": asset_name, "subset": subset_name, "version": version_number, diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 07b416f3b0..adbc28bcfe 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -49,8 +49,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Required environment variables PROJECT = os.environ["AVALON_PROJECT"] - ASSET = instance.data.get("asset") or os.environ["AVALON_ASSET"] - LOCATION = os.getenv("AVALON_LOCATION") + ASSET = instance.data.get("asset") or api.Session["AVALON_ASSET"] + LOCATION = os.environ("AVALON_LOCATION") context = instance.context # Atomicity diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index 734f130df0..698e520616 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -10,29 +10,11 @@ from maya import cmds from avalon import api import avalon.io as io from avalon.maya.lib import unique_namespace +from colorbleed.maya.lib import matrix_equals log = logging.getLogger("PackageLoader") -def matrix_equals(current_matrix, original_matrix, tolerance=1e-10): - """ - Compares two matrices with an imperfection tolerance - - Args: - current_matrix (list, tuple): the matrix to check - original_matrix (list, tuple): the matrix to check against - tolerance (long): the precision of the differences - - Returns: - bool : True or False - - """ - zipped = zip(current_matrix, original_matrix) - if not all(abs(x - y) < tolerance for x, y in zipped): - return False - return True - - def to_namespace(node, namespace): """Return node name as if it's inside the namespace. From d3ec60826876ab146f372392a99ccbc869345715 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 11:19:35 +0200 Subject: [PATCH 0373/1249] Remove Session import --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 3cdd304665..a8cdc5a40f 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -from avalon import Session, maya, api +from avalon import maya, api import colorbleed.maya.lib as lib From 6f9e5c0acf863f7847dce0dc191ad33106a7f49e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 11:27:34 +0200 Subject: [PATCH 0374/1249] Fix REN-8; active deadline user on local machine is submitted with Deadline job --- .../maya/publish/collect_deadline_user.py | 54 +++++++++++++++++++ .../plugins/maya/publish/submit_deadline.py | 4 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/publish/collect_deadline_user.py diff --git a/colorbleed/plugins/maya/publish/collect_deadline_user.py b/colorbleed/plugins/maya/publish/collect_deadline_user.py new file mode 100644 index 0000000000..34f6f4652c --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_deadline_user.py @@ -0,0 +1,54 @@ +import os +import subprocess + +import pyblish.api + +CREATE_NO_WINDOW = 0x08000000 + + +def deadline_command(cmd): + # Find Deadline + path = os.environ.get("DEADLINE_PATH", None) + assert path is not None, "Variable 'DEADLINE_PATH' must be set" + + executable = os.path.join(path, "deadlinecommand") + if os.name == "nt": + executable += ".exe" + assert os.path.exists( + executable), "Deadline executable not found at %s" % executable + assert cmd, "Must have a command" + + query = (executable, cmd) + + print executable + + process = subprocess.Popen(query, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + creationflags=CREATE_NO_WINDOW) + out, err = process.communicate() + + return out + + +class CollectDeadlineUser(pyblish.api.ContextPlugin): + """Retrieve the local active Deadline user""" + + order = pyblish.api.CollectorOrder + 0.499 + label = "Deadline User" + hosts = ['maya'] + families = ["colorbleed.renderlayer"] + + def process(self, context): + """Inject the current working file""" + + user = deadline_command("GetCurrentUserName").strip() + + if not user: + self.log.warning("No Deadline user found. " + "Do you have Deadline installed?") + return + + self.log.info("Found Deadline user: {}".format(user)) + context.data['deadlineUser'] = user + diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 98634afc89..39e84a484d 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -82,6 +82,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty + deadline_user = context.data.get("deadlineUser", + getpass.getuser()) try: os.makedirs(dirname) @@ -113,7 +115,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "Name": "%s - %s" % (fname, instance.name), # Arbitrary username, for visualisation in Monitor - "UserName": getpass.getuser(), + "UserName": deadline_user, "Plugin": "MayaBatch", "Frames": "{start}-{end}x{step}".format( From 078b06c892e50385fd2692b4289312dee6fcf2fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 11:30:32 +0200 Subject: [PATCH 0375/1249] Fix V-ray extension being wrong in Deadline when image format was never changed from .png --- colorbleed/plugins/maya/publish/submit_deadline.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 39e84a484d..8096eae923 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -39,6 +39,13 @@ def get_renderer_variables(renderlayer=None): # Maya's renderSettings function does not resolved V-Ray extension # Getting the extension for VRay settings node extension = cmds.getAttr("vraySettings.imageFormatStr") + + # When V-Ray image format has not been switched once from default .png + # the getAttr command above returns None. As such we explicitly set + # it to `.png` + if extension is None: + extension = "png" + filename_prefix = "/_/" else: # Get the extension, getAttr defaultRenderGlobals.imageFormat From f600e267ff127b1871c1c4e7419c5d21f0184120 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 12:00:57 +0200 Subject: [PATCH 0376/1249] Remove redundant globals that are make believe that template and project will remain static :) --- colorbleed/maya/lib.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index fe97252d5d..66f9d7bc03 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -18,12 +18,6 @@ from cb.utils.maya import core log = logging.getLogger(__name__) -project = io.find_one({"type": "project", - "name": os.environ["AVALON_PROJECT"]}, - projection={"config.template.publish": True, - "_id": False}) -TEMPLATE = project["config"]["template"]["publish"] - ATTRIBUTE_DICT = {"int": {"attributeType": "long"}, "str": {"dataType": "string"}, "unicode": {"dataType": "string"}, From db08b8420211fc223c48d51a972ff0da4d767ade Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 24 Oct 2017 12:01:52 +0200 Subject: [PATCH 0377/1249] Fix integrate to use api.Session instead of os.environ --- colorbleed/plugins/publish/integrate.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 4a5d687684..54680b1f93 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -4,7 +4,7 @@ import shutil import errno import pyblish.api -from avalon import Session, api, io +from avalon import api, io log = logging.getLogger(__name__) @@ -48,9 +48,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def register(self, instance): # Required environment variables - PROJECT = os.environ["AVALON_PROJECT"] + PROJECT = api.Session["AVALON_PROJECT"] ASSET = instance.data.get("asset") or api.Session["AVALON_ASSET"] - LOCATION = os.environ("AVALON_LOCATION") + LOCATION = api.Session["AVALON_LOCATION"] context = instance.context # Atomicity From 6f144b38ae6f4f4f55baba0a1579b248b8a1cd15 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 24 Oct 2017 14:08:19 +0200 Subject: [PATCH 0378/1249] created safe logic for metadata filename --- .../plugins/maya/publish/submit_deadline.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 8096eae923..95e992e2fa 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -86,6 +86,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): fname = os.path.basename(fpath) comment = context.data.get("comment", "") scene = os.path.splitext(fname)[0] + # Get image rule from workspace dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty @@ -106,6 +107,14 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): render_variables["padding"], render_variables["ext"]) + # Get parent folder of render output + render_folder = os.path.dirname(output_filename_0) + + # Get the folder name, this will be the name of the metadata file + json_fname = os.path.basename(render_folder) + json_fpath = os.path.join(os.path.dirname(render_folder), + "{}.json".format(json_fname)) + # E.g. http://192.168.0.1:8082/api/jobs url = "{}/api/jobs".format(AVALON_DEADLINE) @@ -193,20 +202,17 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload, indent=4, sort_keys=True)) response = requests.post(url, json=payload) - if response.ok: + # TODO: REN-11 Implement auto publish logic here as depending job # Write metadata for publish - fname = os.path.join(dirname, "{}.json".format(instance.name)) data = { "submission": payload, "session": api.Session, "instance": instance.data, - "jobs": [ - response.json() - ], + "jobs": [response.json()], } - with open(fname, "w") as f: + with open(json_fpath, "w") as f: json.dump(data, f, indent=4, sort_keys=True) else: From e60b14080177f4516d2168be51934ae76d5c93fe Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 24 Oct 2017 14:24:55 +0200 Subject: [PATCH 0379/1249] changed path which needs to be created --- .../plugins/maya/publish/submit_deadline.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 95e992e2fa..ce78eb03c9 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -90,17 +90,11 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty - deadline_user = context.data.get("deadlineUser", - getpass.getuser()) - - try: - os.makedirs(dirname) - except OSError: - pass + deadline_user = context.data.get("deadlineUser", getpass.getuser()) # Get the variables depending on the renderer + # Following hardcoded "renders//_/" render_variables = get_renderer_variables(renderlayer) - # following hardcoded "renders//_/" output_filename_0 = self.preview_fname(scene, renderlayer_name, dirname, @@ -110,6 +104,12 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Get parent folder of render output render_folder = os.path.dirname(output_filename_0) + try: + # Ensure folders exists + os.makedirs(render_folder) + except OSError: + pass + # Get the folder name, this will be the name of the metadata file json_fname = os.path.basename(render_folder) json_fpath = os.path.join(os.path.dirname(render_folder), From 363c35a7a0a5ea8ac2a9078f6cd27c27b758f7e4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 26 Oct 2017 14:30:09 +0200 Subject: [PATCH 0380/1249] added publish job function --- .../plugins/maya/publish/submit_deadline.py | 61 ++++++++++++- colorbleed/scripts/__init__.py | 0 colorbleed/scripts/publish_imagesequence.py | 90 +++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 colorbleed/scripts/__init__.py create mode 100644 colorbleed/scripts/publish_imagesequence.py diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index ce78eb03c9..bee0d7a369 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -91,6 +91,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty deadline_user = context.data.get("deadlineUser", getpass.getuser()) + jobname = "%s - %s" % (fname, instance.name) # Get the variables depending on the renderer # Following hardcoded "renders//_/" @@ -128,7 +129,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "BatchName": fname, # Job name, as seen in Monitor - "Name": "%s - %s" % (fname, instance.name), + "Name": jobname, # Arbitrary username, for visualisation in Monitor "UserName": deadline_user, @@ -205,16 +206,26 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): if response.ok: # TODO: REN-11 Implement auto publish logic here as depending job # Write metadata for publish + render_job = response.json() data = { "submission": payload, "session": api.Session, "instance": instance.data, - "jobs": [response.json()], + "jobs": [render_job], } with open(json_fpath, "w") as f: json.dump(data, f, indent=4, sort_keys=True) + publish_job = self.create_publish_job(fname, + deadline_user, + comment, + jobname, + render_job, + json_fpath) + if not publish_job: + self.log.error("Could not submit publish job!") + else: try: shutil.rmtree(dirname) @@ -268,3 +279,49 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "%f=%d was rounded off to nearest integer" % (value, int(value)) ) + + def create_publish_job(self, fname, user, comment, jobname, + job, json_fpath): + """ + Make sure all frames are published + Args: + job (dict): the render job data + json_fpath (str): file path to json file + + Returns: + + """ + + url = "{}/api/jobs".format(api.Session["AVALON_DEADLINE"]) + try: + from colorbleed.scripts import publish_imagesequence + except Exception as e: + raise RuntimeError("Expected module 'publish_imagesequence'" + "to be available") + + payload = { + "JobInfo": { + "Plugin": "Python", + "BatchName": fname, + "Name": "{} [publish]".format(jobname), + "JobType": "Normal", + "JobDependency0": job["_id"], + "UserName": user, + "Comment": comment, + }, + "PluginInfo": { + "Version": "3.6", + "ScriptFile": publish_imagesequence.__file__, + "Arguments": "--path {}".format(json_fpath), + "SingleFrameOnly": "True" + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + response = requests.post(url, json=payload) + if not response.ok: + return + + return payload \ No newline at end of file diff --git a/colorbleed/scripts/__init__.py b/colorbleed/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py new file mode 100644 index 0000000000..df0c566b76 --- /dev/null +++ b/colorbleed/scripts/publish_imagesequence.py @@ -0,0 +1,90 @@ +""" +This module is used for command line publishing of image sequences. +Due to its early intergration this module might change location within the +config. It is also subject to change + +Contributors: + Roy Nieterau + Wijnand Koreman + +Dependencies: + Avalon + Pyblish + +""" + +import os +import sys +import json +import logging + +handler = logging.basicConfig() +log = logging.getLogger() +log.setLevel(logging.DEBUG) + + +def publish_data(json_file): + """Publish rendered image sequences based on the job data + + Args: + json_file (str): the json file of the data dump of the submitted job + + Returns: + None + + """ + with open(json_file, "r") as fp: + json_data = json.load(fp) + + # Get the job's environment + job = json_data["jobs"][0] + job_env = job["Props"]["Env"] + job_env = {str(key): str(value) for key, value in job_env.items()} + + # Ensure the key exists + os.environ["PYTHONPATH"] = os.environ.get("PYTHONPATH", "") + + # Add the pythonpaths (also to sys.path for local session) + pythonpaths = job_env.pop("PYTHONPATH", "").split(";") + for path in pythonpaths: + sys.path.append(path) + + os.environ['PYTHONPATH'] += ";" + ";".join(pythonpaths) + + # Use the rest of the job's environment + os.environ.update(job_env) + + # Set the current pyblish host + os.environ["PYBLISH_HOSTS"] = "shell" + + # Set the current working directory + os.chdir(os.path.dirname(json_file)) + + # Install Avalon with shell as current host + from avalon import api, shell + api.install(shell) + + # Publish items, returns context instances + import pyblish.util + context = pyblish.util.publish() + + if not context: + log.warning("Nothing published.") + sys.exit(1) + + +def __main__(): + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("--path", help="The filepath of the JSON") + + kwargs, args = parser.parse_known_args() + + if kwargs.path: + filepath = os.path.normpath(kwargs.path) + print("JSON File {}".format(filepath)) + if not filepath.endswith(".json"): + raise RuntimeError("Wrong extesion! Expecting publish data to be " + "stored in a .JSON file") + + publish_data(filepath) From 3ba6243c35e08863a6ab8d87d63c59bbeee6df53 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 26 Oct 2017 14:40:34 +0200 Subject: [PATCH 0381/1249] removed to do lines --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index bee0d7a369..6ae6ffbce4 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -86,7 +86,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): fname = os.path.basename(fpath) comment = context.data.get("comment", "") scene = os.path.splitext(fname)[0] - # Get image rule from workspace dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty @@ -204,7 +203,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): response = requests.post(url, json=payload) if response.ok: - # TODO: REN-11 Implement auto publish logic here as depending job # Write metadata for publish render_job = response.json() data = { From 8f22636b04dbccdeea127c97afc2d0a3c4269c6c Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 26 Oct 2017 15:29:22 +0200 Subject: [PATCH 0382/1249] added if __name__ to ensure python consoles can run the script --- colorbleed/scripts/publish_imagesequence.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index df0c566b76..426f2e4c61 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -88,3 +88,8 @@ def __main__(): "stored in a .JSON file") publish_data(filepath) + + +# this is needed to ensure Deadline can run the script +if __name__ == '__main__': + __main__() From e415b55030b6eb7c97a91610483daaa18a2d4e0c Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 26 Oct 2017 16:32:00 +0200 Subject: [PATCH 0383/1249] inserted deepcopy of metadata to ensure unique data for all instances --- colorbleed/plugins/publish/collect_imagesequences.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index e1e3707ecc..244f054a67 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -1,4 +1,5 @@ import pyblish.api +import copy class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): @@ -44,11 +45,17 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): raise Exception("%s was not published correctly " "(missing metadata)" % renderlayer) + metadat_instance = metadata['instance'] + # For now ensure this data is ignored for collection in collections: instance = context.create_instance(str(collection)) self.log.info("Collection: %s" % list(collection)) - data = dict(metadata["instance"], **{ + # Ensure each instance gets its own unique reference to + # the source data + instance_metadata = copy.deepcopy(metadat_instance) + + data = dict(instance_metadata, **{ "name": instance.name, "family": "Image Sequences", "families": ["colorbleed.imagesequence"], From 1a2f1d0b0b590d1760130c0d6a9de0a31b8c4c87 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 26 Oct 2017 18:05:32 +0200 Subject: [PATCH 0384/1249] added USE_JSON logic to ensure single folder is scanned when collecting image sequences --- .../plugins/publish/collect_imagesequences.py | 13 +++++++++++-- colorbleed/scripts/publish_imagesequence.py | 4 ++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 244f054a67..ea13335bed 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -14,9 +14,18 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): import json from avalon.vendor import clique - workspace = context.data["workspaceDir"] + # Force towards a single json sequence (override searching + # the current working directory) + USE_JSON = os.environ.get("USE_JSON", "") + if USE_JSON: + workspace = os.path.dirname(USE_JSON) + base = workspace + dirs = [os.path.splitext(os.path.basename(USE_JSON))[0]] + # Else use the current working directory + else: + workspace = context.data["workspaceDir"] + base, dirs, _ = next(os.walk(workspace)) - base, dirs, _ = next(os.walk(workspace)) for renderlayer in dirs: abspath = os.path.join(base, renderlayer) files = os.listdir(abspath) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 426f2e4c61..f6997d1b06 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -64,6 +64,10 @@ def publish_data(json_file): from avalon import api, shell api.install(shell) + # Add environment variable to force collector to use a single folder + # based on the given JSON file + os.environ['USE_JSON'] = str(json_file) + # Publish items, returns context instances import pyblish.util context = pyblish.util.publish() From d87cfba2fb95b9b53d166c6fc562264e2ea8aa58 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 27 Oct 2017 10:21:14 +0200 Subject: [PATCH 0385/1249] cosmetics --- colorbleed/plugins/publish/collect_imagesequences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index ea13335bed..a20ecec781 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -66,7 +66,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): data = dict(instance_metadata, **{ "name": instance.name, - "family": "Image Sequences", + "family": "Image Sequence", "families": ["colorbleed.imagesequence"], "subset": collection.head[:-1], "stagingDir": os.path.join(workspace, renderlayer), From c8837d33e95df0ef7863141733d569ebc760f2dc Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 27 Oct 2017 16:55:34 +0200 Subject: [PATCH 0386/1249] allowed optiona; --- .../plugins/maya/publish/increment_current_file_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index 2e5bb694bc..23a0336bf9 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -13,6 +13,7 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder + 9.0 hosts = ["maya"] families = ["colorbleed.renderlayer"] + optional = True def process(self, context): From 8fca4f7f67ed2b0446015f31c974839f7d0c0ae9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 27 Oct 2017 17:48:46 +0200 Subject: [PATCH 0387/1249] added camera loader which locks the transforms for the loaded cameras --- colorbleed/plugins/maya/load/load_camera.py | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_camera.py diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py new file mode 100644 index 0000000000..fbe87cfe92 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -0,0 +1,35 @@ +import colorbleed.maya.plugin + + +class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.camera"] + label = "Reference camera" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, data): + + import maya.cmds as cmds + # import pprint + # Get family type from the context + + cmds.loadPlugin("AbcImport.mll", quiet=True) + nodes = cmds.file(self.fname, + namespace=namespace, + sharedReferenceFile=False, + groupReference=True, + groupName="{}:{}".format(namespace, name), + reference=True, + returnNewNodes=True) + + cameras = cmds.ls(nodes, type="camera") + for camera in cameras: + cmds.camera(camera, edit=True, lockTransform=True) + + self[:] = nodes + + return nodes From 5cfc7eea3d351ec00477764eb90267dbea675948 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 27 Oct 2017 17:49:24 +0200 Subject: [PATCH 0388/1249] removed the camera form the families --- colorbleed/plugins/maya/load/load_alembic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index 52ddde51c4..e9f4ce284b 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -5,7 +5,6 @@ class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", - "colorbleed.camera", "colorbleed.pointcache"] label = "Reference animation" representations = ["abc"] From 6a00d72962e97dd67a53aa4b4715d9c3b9675402 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 10:48:30 +0100 Subject: [PATCH 0389/1249] simplified loading logic, fixed issue with containers --- colorbleed/maya/lib.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 66f9d7bc03..a4f685ffb5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -12,7 +12,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel -from avalon import api, maya, io +from avalon import api, maya, io, pipeline from cb.utils.maya import core @@ -802,44 +802,34 @@ def assign_look_by_version(nodes, version_id): None """ + from avalon.tools.cbloader import lib as loaderlib + # get representations of shader file and relationships - shader_file = io.find_one({"type": "representation", - "parent": version_id, - "name": "ma"}) + representation = io.find_one({"type": "representation", + "parent": version_id, + "name": "ma"}) shader_relations = io.find_one({"type": "representation", "parent": version_id, "name": "json"}) # Load file - shader_filepath = api.get_representation_path(shader_file) + shader_filepath = api.get_representation_path(representation) shader_relation = api.get_representation_path(shader_relations) reference_node = get_reference_node(shader_filepath) if reference_node is None: log.info("Loading lookdev for the first time..") - # Define namespace - ns_assetname = "{}_".format(shader_file['context']['asset']) - namespace = maya.unique_namespace(ns_assetname, - format="%03d", - suffix="_look") + loaders = list(loaderlib.iter_loaders(representation["_id"])) + Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) + if Loader is None: + raise RuntimeError("Could not find LookLoader, this is a bug") # Reference the look file with maya.maintained_selection(): - shader_nodes = cmds.file(shader_filepath, - namespace=namespace, - reference=True, - returnNewNodes=True) - - # containerise like avalon (for manager) - # give along a fake "context" with only `representation` - # because `maya.containerise` only used that key anyway - context = {"representation": shader_file} - maya.containerise(name=shader_file["context"]["subset"], - namespace=namespace, - nodes=shader_nodes, - context=context) + shader_nodes = pipeline.load(Loader, representation) + else: log.info("Reusing existing lookdev '{}'".format(reference_node)) shader_nodes = cmds.referenceQuery(reference_node, nodes=True) From 13f837acf509c48356e993e4ef87c2295d3f4b20 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 11:09:22 +0100 Subject: [PATCH 0390/1249] cosmetics --- colorbleed/maya/lib.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index a4f685ffb5..95ca405feb 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -801,34 +801,34 @@ def assign_look_by_version(nodes, version_id): Returns: None """ - + # TODO: make `iter_loader` available in as a more global function from avalon.tools.cbloader import lib as loaderlib # get representations of shader file and relationships - representation = io.find_one({"type": "representation", - "parent": version_id, - "name": "ma"}) + look_representation = io.find_one({"type": "representation", + "parent": version_id, + "name": "ma"}) - shader_relations = io.find_one({"type": "representation", - "parent": version_id, - "name": "json"}) + connection_represenations = io.find_one({"type": "representation", + "parent": version_id, + "name": "json"}) # Load file - shader_filepath = api.get_representation_path(representation) - shader_relation = api.get_representation_path(shader_relations) + shader_filepath = api.get_representation_path(look_representation) + shader_relation = api.get_representation_path(connection_represenations) reference_node = get_reference_node(shader_filepath) if reference_node is None: log.info("Loading lookdev for the first time..") - loaders = list(loaderlib.iter_loaders(representation["_id"])) + loaders = list(loaderlib.iter_loaders(look_representation["_id"])) Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) if Loader is None: raise RuntimeError("Could not find LookLoader, this is a bug") # Reference the look file with maya.maintained_selection(): - shader_nodes = pipeline.load(Loader, representation) + shader_nodes = pipeline.load(Loader, look_representation) else: log.info("Reusing existing lookdev '{}'".format(reference_node)) From 11fbd15094bea4fde8f600c7c460fdcfb2b9a417 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2017 12:53:21 +0100 Subject: [PATCH 0391/1249] Add comment and fix typo in variable --- colorbleed/plugins/publish/collect_imagesequences.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index a20ecec781..3a1e95ce6b 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -16,6 +16,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): # Force towards a single json sequence (override searching # the current working directory) + # TODO: This logic should be simplified USE_JSON = os.environ.get("USE_JSON", "") if USE_JSON: workspace = os.path.dirname(USE_JSON) @@ -54,7 +55,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): raise Exception("%s was not published correctly " "(missing metadata)" % renderlayer) - metadat_instance = metadata['instance'] + metadata_instance = metadata['instance'] # For now ensure this data is ignored for collection in collections: instance = context.create_instance(str(collection)) @@ -62,7 +63,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): # Ensure each instance gets its own unique reference to # the source data - instance_metadata = copy.deepcopy(metadat_instance) + instance_metadata = copy.deepcopy(metadata_instance) data = dict(instance_metadata, **{ "name": instance.name, From 4b9e24d8dad517dadf8374016b5d780046b33224 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2017 12:53:46 +0100 Subject: [PATCH 0392/1249] Fix `family` issue --- colorbleed/plugins/publish/collect_imagesequences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 3a1e95ce6b..4da58840c0 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -67,7 +67,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): data = dict(instance_metadata, **{ "name": instance.name, - "family": "Image Sequence", + "family": "colorbleed.imagesequence", "families": ["colorbleed.imagesequence"], "subset": collection.head[:-1], "stagingDir": os.path.join(workspace, renderlayer), From b61b9848b2ec879d2e7e216c27caa7ba426e3290 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2017 14:21:33 +0100 Subject: [PATCH 0393/1249] Implement potential fix for PLN-31 --- colorbleed/maya/lib.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 95ca405feb..c671b7a561 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -14,6 +14,7 @@ from maya import cmds, mel from avalon import api, maya, io, pipeline from cb.utils.maya import core +import cb.utils.maya.context log = logging.getLogger(__name__) @@ -572,7 +573,11 @@ def extract_alembic(file, # Perform extraction print("Alembic Job Arguments : {}".format(job_str)) - cmds.AbcExport(j=job_str, verbose=verbose) + # Disable the parallel evaluation temporarily to ensure no buggy + # exports are made. (PLN-31) + # TODO: Make sure this actually fixes the issues + with cb.utils.maya.context.evaluation("off"): + cmds.AbcExport(j=job_str, verbose=verbose) if verbose: log.debug("Extracted Alembic to: %s", file) From 51f8713ff45a8a402c59e7a49979263a90ae9feb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 30 Oct 2017 14:22:22 +0100 Subject: [PATCH 0394/1249] Use set literal for SHAPE_ATTRS --- colorbleed/maya/lib.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index c671b7a561..8d417b79ed 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -25,7 +25,7 @@ ATTRIBUTE_DICT = {"int": {"attributeType": "long"}, "float": {"attributeType": "double"}, "bool": {"attributeType": "bool"}} -SHAPE_ATTRS = ["castsShadows", +SHAPE_ATTRS = {"castsShadows", "receiveShadows", "motionBlur", "primaryVisibility", @@ -33,7 +33,7 @@ SHAPE_ATTRS = ["castsShadows", "visibleInReflections", "visibleInRefractions", "doubleSided", - "opposite"] + "opposite"} RENDER_ATTRS = {"vray": { @@ -51,9 +51,6 @@ RENDER_ATTRS = {"vray": } -SHAPE_ATTRS = set(SHAPE_ATTRS) - - DEFAULT_MATRIX = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, From 36acca2a0e28a184c0d04f28cfefc5bf1bfa56d2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 14:26:39 +0100 Subject: [PATCH 0395/1249] fallback logic for when reference is found --- colorbleed/maya/lib.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 95ca405feb..a17d710296 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -814,25 +814,23 @@ def assign_look_by_version(nodes, version_id): "name": "json"}) # Load file - shader_filepath = api.get_representation_path(look_representation) shader_relation = api.get_representation_path(connection_represenations) - reference_node = get_reference_node(shader_filepath) - if reference_node is None: - log.info("Loading lookdev for the first time..") + loaders = list(loaderlib.iter_loaders(look_representation["_id"])) + Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) + if Loader is None: + raise RuntimeError("Could not find LookLoader, this is a bug") - loaders = list(loaderlib.iter_loaders(look_representation["_id"])) - Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) - if Loader is None: - raise RuntimeError("Could not find LookLoader, this is a bug") - - # Reference the look file - with maya.maintained_selection(): - shader_nodes = pipeline.load(Loader, look_representation) - - else: - log.info("Reusing existing lookdev '{}'".format(reference_node)) - shader_nodes = cmds.referenceQuery(reference_node, nodes=True) + # Reference the look file + with maya.maintained_selection(): + container = pipeline.load(Loader, look_representation) + if not container: + log.info("Reusing loaded Lookdev ..") + look_file = api.get_representation_path(look_representation) + shader_nodes = cmds.referenceQuery(look_file, nodes=True) + else: + print(">>", container) + shader_nodes = cmds.sets(container, query=True) # Assign relationships with open(shader_relation, "r") as f: From d66f4b41e7fef45ffeac43f8f87d443829b7c7f2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 14:27:06 +0100 Subject: [PATCH 0396/1249] set no nodes when reference is found --- colorbleed/plugins/maya/load/load_look.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 3260737349..da277691b2 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -25,8 +25,6 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader): """ - import json - import os import maya.cmds as cmds from avalon import maya import colorbleed.maya.lib as lib @@ -36,7 +34,8 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader): reference_node = None try: reference_node = lib.get_reference_node(self.fname) - except: + except Exception as e: + self.log.error(e) pass if reference_node is None: @@ -48,21 +47,6 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader): returnNewNodes=True) else: self.log.info("Reusing existing lookdev ...") - nodes = cmds.referenceQuery(reference_node, nodes=True) - - # Assign shaders - self.fname = self.fname.rsplit(".", 1)[0] + ".json" - if not os.path.isfile(self.fname): - self.log.warning("Look development asset " - "has no relationship data.") - return nodes - - with open(self.fname) as f: - relationships = json.load(f) - - # Get all nodes which belong to a matching name space - # Currently this is the safest way to get all the nodes - # Pass empty list as nodes to assign to in order to only load - lib.apply_shaders(relationships, nodes, []) + nodes = None self[:] = nodes From 5315a3e5eb7dada42fb58a1e85b47adc32b5240b Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 14:29:34 +0100 Subject: [PATCH 0397/1249] explicitly use .py file for python job --- colorbleed/plugins/maya/publish/submit_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 6ae6ffbce4..d6c9cd6911 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -297,6 +297,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") + module_fpath = publish_imagesequence.__file__.replace(".pyc", ".py") payload = { "JobInfo": { "Plugin": "Python", @@ -309,7 +310,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": "3.6", - "ScriptFile": publish_imagesequence.__file__, + "ScriptFile": module_fpath, "Arguments": "--path {}".format(json_fpath), "SingleFrameOnly": "True" }, From c96b45f4b369e509b402f18ad6f8ad60281e13f4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 14:36:44 +0100 Subject: [PATCH 0398/1249] explicit filetype check --- colorbleed/plugins/maya/publish/submit_deadline.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index d6c9cd6911..3cdf17a616 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -297,7 +297,10 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") - module_fpath = publish_imagesequence.__file__.replace(".pyc", ".py") + module_path = publish_imagesequence.__file__ + if module_path.endswith(".pyc"): + module_path = publish_imagesequence.__file__.replace(".pyc", ".py") + payload = { "JobInfo": { "Plugin": "Python", @@ -310,7 +313,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): }, "PluginInfo": { "Version": "3.6", - "ScriptFile": module_fpath, + "ScriptFile": module_path, "Arguments": "--path {}".format(json_fpath), "SingleFrameOnly": "True" }, From fd76882ee8f5f9d85ee19373fd03717cda56d05c Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 15:15:11 +0100 Subject: [PATCH 0399/1249] exra level of explicit replace --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 3cdf17a616..db0611839e 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -299,7 +299,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): module_path = publish_imagesequence.__file__ if module_path.endswith(".pyc"): - module_path = publish_imagesequence.__file__.replace(".pyc", ".py") + module_path = module_path[:-len(".pyc")] + ".py" payload = { "JobInfo": { From f9db6771f2f66e198975a16201e4ea3d49bdd325 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 16:14:32 +0100 Subject: [PATCH 0400/1249] reduced code to simple loading of file --- colorbleed/plugins/maya/load/load_look.py | 25 +++++------------------ 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index da277691b2..16428ef034 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -27,26 +27,11 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader): import maya.cmds as cmds from avalon import maya - import colorbleed.maya.lib as lib - # try / except here is to ensure that the get_reference_node - # does not fail when the file doesn't exist yet - reference_node = None - try: - reference_node = lib.get_reference_node(self.fname) - except Exception as e: - self.log.error(e) - pass - - if reference_node is None: - self.log.info("Loading lookdev for the first time ...") - with maya.maintained_selection(): - nodes = cmds.file(self.fname, - namespace=namespace, - reference=True, - returnNewNodes=True) - else: - self.log.info("Reusing existing lookdev ...") - nodes = None + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True) self[:] = nodes From 7c9ff40155c4f854bdf8d813e617072dc8abab46 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 16:14:59 +0100 Subject: [PATCH 0401/1249] check for existing look based on representation --- colorbleed/maya/lib.py | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index a17d710296..f9cc95eea0 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -809,33 +809,39 @@ def assign_look_by_version(nodes, version_id): "parent": version_id, "name": "ma"}) - connection_represenations = io.find_one({"type": "representation", + connection_representions = io.find_one({"type": "representation", "parent": version_id, "name": "json"}) - # Load file - shader_relation = api.get_representation_path(connection_represenations) + # See if representation is already loaded, if so reuse it. + host = api.registered_host() + representation_id = str(look_representation['_id']) + for container in host.ls(): + if (container['loader'] == "LookLoader" and + container['representation'] == representation_id): + log.info("Reusing loaded look ..") + container_node = container['objectName'] + break + else: + log.info("Using look for the first time ..") + # Load file - loaders = list(loaderlib.iter_loaders(look_representation["_id"])) - Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) - if Loader is None: - raise RuntimeError("Could not find LookLoader, this is a bug") + loaders = list(loaderlib.iter_loaders(look_representation["_id"])) + Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) + if Loader is None: + raise RuntimeError("Could not find LookLoader, this is a bug") - # Reference the look file - with maya.maintained_selection(): - container = pipeline.load(Loader, look_representation) - if not container: - log.info("Reusing loaded Lookdev ..") - look_file = api.get_representation_path(look_representation) - shader_nodes = cmds.referenceQuery(look_file, nodes=True) - else: - print(">>", container) - shader_nodes = cmds.sets(container, query=True) + # Reference the look file + with maya.maintained_selection(): + container_node = pipeline.load(Loader, look_representation) + shader_nodes = cmds.sets(container_node, query=True) - # Assign relationships + # Load relationships + shader_relation = api.get_representation_path(connection_representions) with open(shader_relation, "r") as f: relationships = json.load(f) + # Assign relationships apply_shaders(relationships, shader_nodes, nodes) From 41716efd77ad1bcf10304440768173599579bb06 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 16:21:04 +0100 Subject: [PATCH 0402/1249] cosmetics --- colorbleed/maya/lib.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index f9cc95eea0..02ac315087 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -796,7 +796,7 @@ def assign_look_by_version(nodes, version_id): Args: nodes(list): nodes to assign look to - version_id (bson.ObjectId) + version_id (bson.ObjectId): database id of the version Returns: None @@ -804,14 +804,14 @@ def assign_look_by_version(nodes, version_id): # TODO: make `iter_loader` available in as a more global function from avalon.tools.cbloader import lib as loaderlib - # get representations of shader file and relationships + # Get representations of shader file and relationships look_representation = io.find_one({"type": "representation", "parent": version_id, "name": "ma"}) connection_representions = io.find_one({"type": "representation", - "parent": version_id, - "name": "json"}) + "parent": version_id, + "name": "json"}) # See if representation is already loaded, if so reuse it. host = api.registered_host() @@ -824,8 +824,8 @@ def assign_look_by_version(nodes, version_id): break else: log.info("Using look for the first time ..") - # Load file + # Load file loaders = list(loaderlib.iter_loaders(look_representation["_id"])) Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) if Loader is None: @@ -834,6 +834,8 @@ def assign_look_by_version(nodes, version_id): # Reference the look file with maya.maintained_selection(): container_node = pipeline.load(Loader, look_representation) + + # Get container nodes shader_nodes = cmds.sets(container_node, query=True) # Load relationships From 59941cf844c0be3cfbac2757ae946b2bb9f66681 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 30 Oct 2017 17:16:26 +0100 Subject: [PATCH 0403/1249] cosmetics --- colorbleed/maya/lib.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 02ac315087..bf8e3e4d47 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -809,9 +809,9 @@ def assign_look_by_version(nodes, version_id): "parent": version_id, "name": "ma"}) - connection_representions = io.find_one({"type": "representation", - "parent": version_id, - "name": "json"}) + json_representation = io.find_one({"type": "representation", + "parent": version_id, + "name": "json"}) # See if representation is already loaded, if so reuse it. host = api.registered_host() @@ -835,11 +835,11 @@ def assign_look_by_version(nodes, version_id): with maya.maintained_selection(): container_node = pipeline.load(Loader, look_representation) - # Get container nodes + # Get container members shader_nodes = cmds.sets(container_node, query=True) # Load relationships - shader_relation = api.get_representation_path(connection_representions) + shader_relation = api.get_representation_path(json_representation) with open(shader_relation, "r") as f: relationships = json.load(f) From 09bc4a77562a0fe594b55577e44a7555b0bd482d Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 09:32:04 +0100 Subject: [PATCH 0404/1249] cosmentics and removed shadow name of variable --- .../validate_animation_out_set_related_node_ids.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 0d056b9b9d..93cde23062 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -44,15 +44,15 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): nodes = list() for s in pointcache_sets: - members = cmds.sets(s, query=True) - members = cmds.ls(members, long=True) # ensure long names - descendents = cmds.listRelatives(members, + # Ensure long names + members = cmds.ls(cmds.sets(s, query=True), long=True) + descendants = cmds.listRelatives(members, allDescendents=True, fullPath=True) or [] - descendents = cmds.ls(descendents, + descendants = cmds.ls(descendants, noIntermediate=True, long=True) - hierarchy = members + descendents + hierarchy = members + descendants nodes.extend(hierarchy) # ignore certain node types (e.g. constraints) @@ -84,6 +84,6 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): return invalid_items @staticmethod - def to_item(id): + def to_item(_id): """Split the item id part from a node id""" - return id.rsplit(":", 1)[0] + return _id.rsplit(":", 1)[0] From f2539fe6a2c63316d504aaa279cecb265026f9bd Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 16:40:11 +0100 Subject: [PATCH 0405/1249] improved docstring --- .../plugins/maya/publish/collect_animation.py | 0 .../maya/publish/validate_single_assembly.py | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/collect_animation.py diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/colorbleed/plugins/maya/publish/collect_animation.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index 179073f95d..bb79873f80 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -3,10 +3,19 @@ import colorbleed.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): - """Ensure all nodes are in a single assembly + """Ensure the instance is present in the assemblies - Published assets must be contained within a single transform - at the root of your outliner. + The instance must have a node which resides within the a group which + is visible in the outliner. + Example outliner: + root_GRP + -- asset_001_GRP + -- asset_01_:rigDefault + + animation_asset_01 + -- asset_01_:rigDefault + + The asset_01_:rigDefault is present within the root_GRP """ From 3430b6660bff05192c65bd12313424f7280f9051 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 16:41:48 +0100 Subject: [PATCH 0406/1249] added collect animation plugin to unify data point for animation and pointcache --- .../plugins/maya/publish/collect_animation.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/colorbleed/plugins/maya/publish/collect_animation.py index e69de29bb2..5eef0dee56 100644 --- a/colorbleed/plugins/maya/publish/collect_animation.py +++ b/colorbleed/plugins/maya/publish/collect_animation.py @@ -0,0 +1,63 @@ +import pyblish.api + +import maya.cmds as cmds + + +class CollectLook(pyblish.api.InstancePlugin): + """Collect look data for instance. + + For the shapes/transforms of the referenced object to collect look for + retrieve the user-defined attributes (like V-ray attributes) and their + values as they were created in the current scene. + + For the members of the instance collect the sets (shadingEngines and + other sets, e.g. VRayDisplacement) they are in along with the exact + membership relations. + + Collects: + lookAttribtutes (list): Nodes in instance with their altered attributes + lookSetRelations (list): Sets and their memberships + lookSets (list): List of set names included in the look + + """ + + order = pyblish.api.CollectorOrder + 0.4 + families = ["colorbleed.animation", "colorbleed.pointcache"] + label = "Collect Animation" + hosts = ["maya"] + + ignore_type = ["constraints"] + + def process(self, instance): + """Collect the Look in the instance with the correct layer settings""" + + family = instance.data["family"] + if family == "colorbleed.animation": + out_set = next((i for i in instance.data["setMembers"] if + i.endswith("out_SET")), None) + + assert out_set, ("Expecting out_SET for instance of family" + "'%s'" % family) + members = cmds.ls(cmds.sets(out_set, query=True), long=True) + else: + members = cmds.ls(instance, long=True) + + # Get all the relatives of the members + descendants = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + descendants = cmds.ls(descendants, noIntermediate=True, long=True) + + # Add members and descendants together for a complete overview + hierarchy = members + descendants + + # Ignore certain node types (e.g. constraints) + ignore = cmds.ls(hierarchy, type=self.ignore_type, long=True) + if ignore: + ignore = set(ignore) + hierarchy = [node for node in hierarchy if node not in ignore] + + return hierarchy + + # Store data in the instance for the validator + instance.data["pointcache_data"] = hierarchy From dee1d504bf77bca344234faccc94a2476ab645c5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 16:42:57 +0100 Subject: [PATCH 0407/1249] improved check to validate if IDs are related to original use shape --- ...date_animation_out_set_related_node_ids.py | 129 ++++++++++++------ 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 93cde23062..b588d5b375 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -3,11 +3,10 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api import colorbleed.maya.lib as lib -import avalon.io as io class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate rig out_SET nodes have related ids to current context + """Validate if nodes have related IDs to the source (original shapes) An ID is 'related' if its built in the current Item. @@ -18,10 +17,10 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.animation'] + families = ['colorbleed.animation', "colorbleed.pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] optional = True ignore_types = ("constraint",) @@ -29,61 +28,105 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): def process(self, instance): """Process all meshes""" - # Ensure all nodes have a cbId + # Ensure all nodes have a cbId and a related ID to the original shapes + # if a deformer has been created on the shape invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Nodes found with non-related " "asset IDs: {0}".format(invalid)) - @classmethod - def get_pointcache_nodes(cls, instance): - - # Get out_SET - sets = cmds.ls(instance, type='objectSet') - pointcache_sets = [x for x in sets if x == 'out_SET'] - - nodes = list() - for s in pointcache_sets: - # Ensure long names - members = cmds.ls(cmds.sets(s, query=True), long=True) - descendants = cmds.listRelatives(members, - allDescendents=True, - fullPath=True) or [] - descendants = cmds.ls(descendants, - noIntermediate=True, - long=True) - hierarchy = members + descendants - nodes.extend(hierarchy) - - # ignore certain node types (e.g. constraints) - ignore = cmds.ls(nodes, type=cls.ignore_types, long=True) - if ignore: - ignore = set(ignore) - nodes = [node for node in nodes if node not in ignore] - - return nodes - @classmethod def get_invalid(cls, instance): - invalid_items = [] + """Get all nodes which do not match the criteria""" + invalid = [] # get asset id - nodes = cls.get_pointcache_nodes(instance) + nodes = instance.data["pointcache_data"] for node in nodes: + node_type = cmds.nodeType(node) node_id = lib.get_id(node) + + if node_type == "mesh" and not node_id: + invalid.append(node) + continue + if not node_id: - invalid_items.append(node) + continue - # TODO: Should we check whether the ids are related to the rig's asset? + root_id = cls.get_history_root_id(node=node) + if root_id is not None: + asset_id = cls.to_item(node_id) + if root_id != asset_id: + invalid.append(node) - # Log invalid item ids - if invalid_items: - for item_id in sorted(invalid_items): - cls.log.warning("Found invalid item id: {0}".format(item_id)) + return invalid - return invalid_items + @classmethod + def get_history_root_id(cls, node): + """ + + Get the original node ID when a node has been deformed + Args: + node (str): node to retrieve the + + Returns: + str: the asset ID as found in the database + """ + + asset_id = None + node = cmds.ls(node, long=True)[0] + + # We only check when the node is *not* referenced + if cmds.referenceQuery(node, isNodeReferenced=True): + return + + # Find all similar nodes in history + history = cmds.listHistory(node) + node_type = cmds.nodeType(node) + similar_nodes = cmds.ls(history, exactType=node_type, long=True) + + # Exclude itself + similar_nodes = [x for x in similar_nodes if x != node] + + # The node *must be* under the same parent + parent = cls.get_parent(node) + similar_nodes = [i for i in similar_nodes if + cls.get_parent(i) == parent] + + # Check all of the remaining similar nodes and take the first one + # with an id and assume it's the original. + for similar_node in similar_nodes: + + _id = lib.get_id(similar_node) + if not _id: + continue + + asset_id = cls.to_item(_id) + break + + return asset_id + + @classmethod + def repair(cls, instance): + + for node in cls.get_invalid(instance): + # Get node ID and the asset ID part + node_id = lib.get_id(node) + asset_id = cls.to_item(node_id) + + # Get root asset ID + root_id = cls.get_history_root_id(node=node) + + # Replace errored ID with good ID + new_id = node_id.replace(asset_id, root_id) + + cmds.setAttr("%s.cbId" % node, new_id, type="string") @staticmethod def to_item(_id): """Split the item id part from a node id""" - return _id.rsplit(":", 1)[0] + return _id.split(":", 1)[0] + + @staticmethod + def get_parent(node): + return cmds.listRelatives(node, parent=True, fullPath=True) \ No newline at end of file From 19c68fff1b0267d9110f671fc4579ac7e9f00b58 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 31 Oct 2017 16:54:08 +0100 Subject: [PATCH 0408/1249] updated docstrings --- .../plugins/maya/publish/collect_animation.py | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/colorbleed/plugins/maya/publish/collect_animation.py index 5eef0dee56..18bab1958a 100644 --- a/colorbleed/plugins/maya/publish/collect_animation.py +++ b/colorbleed/plugins/maya/publish/collect_animation.py @@ -4,20 +4,14 @@ import maya.cmds as cmds class CollectLook(pyblish.api.InstancePlugin): - """Collect look data for instance. + """Collect out hierarchy data for instance. - For the shapes/transforms of the referenced object to collect look for - retrieve the user-defined attributes (like V-ray attributes) and their - values as they were created in the current scene. + Collect all hierarchy nodes which reside in the out_SET of the animation + instance or poitncache instance. This is to unify the logic of retrieving + that specifc data. This eliminates the need to write two separate pieces + of logic to fetch all hierarchy nodes. - For the members of the instance collect the sets (shadingEngines and - other sets, e.g. VRayDisplacement) they are in along with the exact - membership relations. - - Collects: - lookAttribtutes (list): Nodes in instance with their altered attributes - lookSetRelations (list): Sets and their memberships - lookSets (list): List of set names included in the look + Results in a list of nodes from the content of the instances """ @@ -29,7 +23,7 @@ class CollectLook(pyblish.api.InstancePlugin): ignore_type = ["constraints"] def process(self, instance): - """Collect the Look in the instance with the correct layer settings""" + """Collect the hierarchy nodes""" family = instance.data["family"] if family == "colorbleed.animation": From efc8cef45c217ff31c52ccd16dba90e02bccd82a Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 10:39:27 +0100 Subject: [PATCH 0409/1249] repair function now inserts full cbID, updated docstrings --- ...date_animation_out_set_related_node_ids.py | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index b588d5b375..5dd3363d35 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -5,14 +5,23 @@ import colorbleed.api import colorbleed.maya.lib as lib +def get_parent(node): + """Get the parent node of the given node + Args: + node (str): full path of the node + + Returns: + str, full path if parent node + """ + return cmds.listRelatives(node, parent=True, fullPath=True) + + class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): """Validate if nodes have related IDs to the source (original shapes) - An ID is 'related' if its built in the current Item. - - Note that this doesn't ensure it's from the current Task. An ID created - from `lookdev` has the same relation to the Item as one coming from others, - like `rigging` or `modeling`. + Any intermediate shapes which are created when creating deformers on + shapes will need to get the correct ID to ensure the look assignment still + works on the new shape. """ @@ -41,7 +50,7 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): invalid = [] # get asset id - nodes = instance.data["pointcache_data"] + nodes = instance.data.get("out_hierarchy", instance[:]) for node in nodes: node_type = cmds.nodeType(node) node_id = lib.get_id(node) @@ -64,16 +73,16 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): @classmethod def get_history_root_id(cls, node): """ - Get the original node ID when a node has been deformed + Args: node (str): node to retrieve the Returns: str: the asset ID as found in the database + """ - asset_id = None node = cmds.ls(node, long=True)[0] # We only check when the node is *not* referenced @@ -89,44 +98,32 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): similar_nodes = [x for x in similar_nodes if x != node] # The node *must be* under the same parent - parent = cls.get_parent(node) + parent = get_parent(node) similar_nodes = [i for i in similar_nodes if - cls.get_parent(i) == parent] + get_parent(i) == parent] # Check all of the remaining similar nodes and take the first one # with an id and assume it's the original. for similar_node in similar_nodes: - _id = lib.get_id(similar_node) if not _id: continue - asset_id = cls.to_item(_id) - break - - return asset_id + return _id @classmethod def repair(cls, instance): for node in cls.get_invalid(instance): - # Get node ID and the asset ID part - node_id = lib.get_id(node) - asset_id = cls.to_item(node_id) - # Get root asset ID root_id = cls.get_history_root_id(node=node) + if not root_id: + cls.log.error("Could not find root ID for '%s'", node) + continue - # Replace errored ID with good ID - new_id = node_id.replace(asset_id, root_id) - - cmds.setAttr("%s.cbId" % node, new_id, type="string") + cmds.setAttr("%s.cbId" % node, root_id, type="string") @staticmethod def to_item(_id): """Split the item id part from a node id""" return _id.split(":", 1)[0] - - @staticmethod - def get_parent(node): - return cmds.listRelatives(node, parent=True, fullPath=True) \ No newline at end of file From 2b91896daeebf65300dff73dd29e55d7053c3011 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 10:40:26 +0100 Subject: [PATCH 0410/1249] removed pointcach family from plugin, create new entry in instance.data for out hierarchy --- .../plugins/maya/publish/collect_animation.py | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/colorbleed/plugins/maya/publish/collect_animation.py index 18bab1958a..53251cadc3 100644 --- a/colorbleed/plugins/maya/publish/collect_animation.py +++ b/colorbleed/plugins/maya/publish/collect_animation.py @@ -3,12 +3,12 @@ import pyblish.api import maya.cmds as cmds -class CollectLook(pyblish.api.InstancePlugin): +class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): """Collect out hierarchy data for instance. Collect all hierarchy nodes which reside in the out_SET of the animation - instance or poitncache instance. This is to unify the logic of retrieving - that specifc data. This eliminates the need to write two separate pieces + instance or point cache instance. This is to unify the logic of retrieving + that specific data. This eliminates the need to write two separate pieces of logic to fetch all hierarchy nodes. Results in a list of nodes from the content of the instances @@ -16,8 +16,8 @@ class CollectLook(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.animation", "colorbleed.pointcache"] - label = "Collect Animation" + families = ["colorbleed.animation"] + label = "Collect Animation Output Geometry" hosts = ["maya"] ignore_type = ["constraints"] @@ -26,15 +26,12 @@ class CollectLook(pyblish.api.InstancePlugin): """Collect the hierarchy nodes""" family = instance.data["family"] - if family == "colorbleed.animation": - out_set = next((i for i in instance.data["setMembers"] if - i.endswith("out_SET")), None) + out_set = next((i for i in instance.data["setMembers"] if + i.endswith("out_SET")), None) - assert out_set, ("Expecting out_SET for instance of family" - "'%s'" % family) - members = cmds.ls(cmds.sets(out_set, query=True), long=True) - else: - members = cmds.ls(instance, long=True) + assert out_set, ("Expecting out_SET for instance of family" + " '%s'" % family) + members = cmds.ls(cmds.sets(out_set, query=True), long=True) # Get all the relatives of the members descendants = cmds.listRelatives(members, @@ -51,7 +48,6 @@ class CollectLook(pyblish.api.InstancePlugin): ignore = set(ignore) hierarchy = [node for node in hierarchy if node not in ignore] - return hierarchy - # Store data in the instance for the validator - instance.data["pointcache_data"] = hierarchy + instance.data["out_hierarchy"] = hierarchy + From 8d127c89e843784381ed11e01a8443667cbbb687 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 10:40:40 +0100 Subject: [PATCH 0411/1249] updated docstrings --- .../maya/publish/validate_single_assembly.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index bb79873f80..8e6e4ea0b6 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -3,19 +3,16 @@ import colorbleed.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): - """Ensure the instance is present in the assemblies + """Ensure the content of the instance is grouped in a single hierarchy The instance must have a node which resides within the a group which is visible in the outliner. Example outliner: root_GRP - -- asset_001_GRP - -- asset_01_:rigDefault - - animation_asset_01 - -- asset_01_:rigDefault - - The asset_01_:rigDefault is present within the root_GRP + -- asset_001_GRP + -- asset_01_:rigDefault + -- asset_001_GRP + -- asset_01_:rigDefault """ From ea01c662046bd0a855f2046a77e26ae6defd6fa1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 11:57:07 +0100 Subject: [PATCH 0412/1249] updated docstrings --- colorbleed/plugins/maya/publish/validate_single_assembly.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index 8e6e4ea0b6..147945ac33 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -5,7 +5,7 @@ import colorbleed.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): """Ensure the content of the instance is grouped in a single hierarchy - The instance must have a node which resides within the a group which + The instance must have a node which resides within the group which is visible in the outliner. Example outliner: root_GRP From bc5ddf556a16034f9c91b06fdc3d4ba1847873a8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:01:16 +0100 Subject: [PATCH 0413/1249] removed `to_item`, ensure node checked is of a type 'shape', update docstring --- ...date_animation_out_set_related_node_ids.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 5dd3363d35..bdda287e3c 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -16,7 +16,7 @@ def get_parent(node): return cmds.listRelatives(node, parent=True, fullPath=True) -class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): +class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """Validate if nodes have related IDs to the source (original shapes) Any intermediate shapes which are created when creating deformers on @@ -52,10 +52,11 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): # get asset id nodes = instance.data.get("out_hierarchy", instance[:]) for node in nodes: - node_type = cmds.nodeType(node) + # check if node is a shape as deformers only work on shapes + obj_type = cmds.objectType(node, isAType="shape") node_id = lib.get_id(node) - if node_type == "mesh" and not node_id: + if obj_type and not node_id: invalid.append(node) continue @@ -64,9 +65,7 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): root_id = cls.get_history_root_id(node=node) if root_id is not None: - asset_id = cls.to_item(node_id) - if root_id != asset_id: - invalid.append(node) + invalid.append(node) return invalid @@ -106,10 +105,8 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): # with an id and assume it's the original. for similar_node in similar_nodes: _id = lib.get_id(similar_node) - if not _id: - continue - - return _id + if _id: + return _id @classmethod def repair(cls, instance): @@ -122,8 +119,3 @@ class ValidateAnimationOutSetRelatedNodeIds(pyblish.api.InstancePlugin): continue cmds.setAttr("%s.cbId" % node, root_id, type="string") - - @staticmethod - def to_item(_id): - """Split the item id part from a node id""" - return _id.split(":", 1)[0] From 3a78fbbafbd6cc90c656ebbbeefe5f7605d321d0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:05:34 +0100 Subject: [PATCH 0414/1249] moved check for referenced node to `get_invalid` --- .../validate_animation_out_set_related_node_ids.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index bdda287e3c..23cb150b04 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -17,7 +17,7 @@ def get_parent(node): class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): - """Validate if nodes have related IDs to the source (original shapes) + """Validate if deformed shapes have related IDs to the original shapes Any intermediate shapes which are created when creating deformers on shapes will need to get the correct ID to ensure the look assignment still @@ -63,6 +63,10 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): if not node_id: continue + # We only check when the node is *not* referenced + if cmds.referenceQuery(node, isNodeReferenced=True): + return + root_id = cls.get_history_root_id(node=node) if root_id is not None: invalid.append(node) @@ -84,10 +88,6 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): node = cmds.ls(node, long=True)[0] - # We only check when the node is *not* referenced - if cmds.referenceQuery(node, isNodeReferenced=True): - return - # Find all similar nodes in history history = cmds.listHistory(node) node_type = cmds.nodeType(node) From 43013a6d79431b07b16e154f8a1a1572e968ef03 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:17:44 +0100 Subject: [PATCH 0415/1249] renamed module --- ...validate_unique_node_ids.py => validate_node_ids_unique.py} | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename colorbleed/plugins/maya/publish/{validate_unique_node_ids.py => validate_node_ids_unique.py} (96%) diff --git a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py similarity index 96% rename from colorbleed/plugins/maya/publish/validate_unique_node_ids.py rename to colorbleed/plugins/maya/publish/validate_node_ids_unique.py index 5fceecba71..e32eb297a3 100644 --- a/colorbleed/plugins/maya/publish/validate_unique_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py @@ -5,7 +5,7 @@ import colorbleed.api import colorbleed.maya.lib as lib -class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): +class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): """Validate the nodes in the instance have a unique Colorbleed Id Here we ensure that what has been added to the instance is unique @@ -30,7 +30,6 @@ class ValidateNonDuplicateInstanceMembers(pyblish.api.InstancePlugin): raise RuntimeError("Nodes found with non-unique " "asset IDs: {0}".format(invalid)) - @classmethod def get_invalid_dict(cls, instance): """Return a dictionary mapping of id key to list of member nodes""" From e5393790d880a43365311c272dcf75bb0d0cd984 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:18:11 +0100 Subject: [PATCH 0416/1249] updated comment for clearification --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8d417b79ed..3a9ce063fe 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -625,7 +625,7 @@ def get_id_required_nodes(referenced_nodes=False): if _node_type_exists("ilrBakeLayer"): ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) - # establish set of nodes to ignore + # Establish set of nodes to include types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] # We *always* ignore intermediate shapes, so we filter them out From f4d8654b445017355bd30e25c21af3a4d3868df9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:18:59 +0100 Subject: [PATCH 0417/1249] added extra families to plugin --- colorbleed/plugins/maya/publish/validate_node_ids.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index db445f0a11..e2abfb0c79 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -17,9 +17,11 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): label = 'Instance Nodes Have ID' hosts = ['maya'] families = ["colorbleed.model", - "colorbleed.pointcache", "colorbleed.look", - "colorbleed.rig"] + "colorbleed.rig", + "colorbleed.pointcache", + "colorbleed.animation", + "colorbleed.setdress"] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] From 5e1ce26bea9abb9685ce0094750fdf695f9070d8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:35:40 +0100 Subject: [PATCH 0418/1249] check if parent transform has ID as well --- .../publish/validate_animation_content.py | 0 ...date_animation_out_set_related_node_ids.py | 30 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_animation_content.py diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 23cb150b04..c3644aded5 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -47,25 +47,33 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): """Get all nodes which do not match the criteria""" + invalid = [] # get asset id nodes = instance.data.get("out_hierarchy", instance[:]) for node in nodes: - # check if node is a shape as deformers only work on shapes - obj_type = cmds.objectType(node, isAType="shape") - node_id = lib.get_id(node) - - if obj_type and not node_id: - invalid.append(node) - continue - - if not node_id: - continue # We only check when the node is *not* referenced if cmds.referenceQuery(node, isNodeReferenced=True): - return + continue + + # Check if node is a shape as deformers only work on shapes + obj_type = cmds.objectType(node, isAType="shape") + if not obj_type: + continue + + # Get the current id of the node + node_id = lib.get_id(node) + if node_id: + invalid.append(node) + continue + + # Check if parent transform node has an ID as well, pipeline rules + transform = get_parent(node) + if not lib.get_id(node): + invalid.append(transform) + continue root_id = cls.get_history_root_id(node=node) if root_id is not None: From 7f7b200ba74a528c346a323ffa4193b34a40699b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:36:11 +0100 Subject: [PATCH 0419/1249] validate content of out hierarchy --- .../publish/validate_animation_content.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index e69de29bb2..1ac522f69a 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -0,0 +1,36 @@ +import pyblish.api +import colorbleed.api + + +class ValidateAnimationContent(pyblish.api.InstancePlugin): + """Adheres to the content of 'animation' family + + - Must have collected `out_hierarchy` data. + - All nodes in `out_hierarchy` must be in the instance. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + families = ["colorbleed.animation"] + label = "Animation Content" + actions = [colorbleed.api.SelectInvalidAction] + + @classmethod + def get_invalid(cls, instance): + assert 'out_hierarchy' in instance.data, "Missing `out_hierarchy` data" + + # All nodes in the `out_hierarchy` must be among the nodes that are + # in the instance. The nodes in the instance are found from the top + # group, as such this tests whether all nodes are under that top group. + + lookup = set(instance[:]) + invalid = [node for node in instance.data['out_hierarchy'] if + node not in lookup] + + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file From 296693e209b5f4d543e097a88354e4da3f497d11 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:39:23 +0100 Subject: [PATCH 0420/1249] cosmetics --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 3a9ce063fe..0536b4c896 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -625,7 +625,7 @@ def get_id_required_nodes(referenced_nodes=False): if _node_type_exists("ilrBakeLayer"): ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) - # Establish set of nodes to include + # Establish set of nodes types to include types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] # We *always* ignore intermediate shapes, so we filter them out From 27fb168cf114f91c71f6b47ca3ac2925f058f977 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 12:42:47 +0100 Subject: [PATCH 0421/1249] updated docstrings of class --- .../publish/validate_animation_out_set_related_node_ids.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index c3644aded5..c42a6f1e11 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -19,9 +19,10 @@ def get_parent(node): class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """Validate if deformed shapes have related IDs to the original shapes - Any intermediate shapes which are created when creating deformers on - shapes will need to get the correct ID to ensure the look assignment still - works on the new shape. + When a deformer is applied in the scene on a referenced mesh that already + had deformers then Maya will create a new shape node for the mesh that + does not have the original id. This validator checks whether the ids are + valid on all the shape nodes in the instance. """ From 0ccaa4d534326490685ef6f96ad82a528d85a158 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 13:10:02 +0100 Subject: [PATCH 0422/1249] fixed bug, use lib function to create and set cbId attr --- .../validate_animation_out_set_related_node_ids.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index c42a6f1e11..7a510e7c96 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -66,18 +66,12 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): # Get the current id of the node node_id = lib.get_id(node) - if node_id: + if not node_id: invalid.append(node) continue - # Check if parent transform node has an ID as well, pipeline rules - transform = get_parent(node) - if not lib.get_id(node): - invalid.append(transform) - continue - root_id = cls.get_history_root_id(node=node) - if root_id is not None: + if root_id is not None and node_id != root_id: invalid.append(node) return invalid @@ -127,4 +121,5 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): cls.log.error("Could not find root ID for '%s'", node) continue - cmds.setAttr("%s.cbId" % node, root_id, type="string") + lib.remove_id(node) # remove the cbId attr if it exists + lib.set_id(root_id, node) # set root_id as cbId From 8a8be03d3de70af5344337e16f1638e8f42d6033 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 1 Nov 2017 17:49:09 +0100 Subject: [PATCH 0423/1249] added force flag to force set given id as id --- colorbleed/maya/lib.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 0536b4c896..bb35b986c1 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -671,20 +671,21 @@ def generate_ids(nodes): """Assign a new id of the current active context to the nodes""" # Get the asset ID from the database for the asset of current context - asset_id = io.find_one({"type": "asset", - "name": api.Session["AVALON_ASSET"]}, - projection={"_id": True}) - + asset_data = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}, + projection={"_id": True}) + asset_id = asset_data["_id"] for node in nodes: - set_id(asset_id["_id"], node) + set_id(asset_id, node) -def set_id(asset_id, node): +def set_id(unique_id, node, force=False): """Add cbId to `node` unless one already exists. Args: - asset_id (str): the unique asset code from the database + unique_id (str): the unique asset code from the database node (str): the node to add the "cbId" on + force (bool): if True sets the given unique_id as attribute value Returns: None @@ -692,10 +693,15 @@ def set_id(asset_id, node): attr = "{0}.cbId".format(node) if not cmds.attributeQuery("cbId", node=node, exists=True): + + if not force: + _, uid = str(uuid.uuid4()).rsplit("-", 1) + unique_id = "{}:{}".format(unique_id, uid) + else: + unique_id = unique_id + cmds.addAttr(node, longName="cbId", dataType="string") - _, uid = str(uuid.uuid4()).rsplit("-", 1) - cb_uid = "{}:{}".format(asset_id, uid) - cmds.setAttr(attr, cb_uid, type="string") + cmds.setAttr(attr, unique_id, type="string") def remove_id(node): From 18a24194728d523ccb2eed8986a4a517aef2e348 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 2 Nov 2017 11:12:22 +0100 Subject: [PATCH 0424/1249] removed rigging from family --- .../publish/validate_animation_out_set_related_node_ids.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 7a510e7c96..823d5841be 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -31,7 +31,6 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): hosts = ['maya'] label = 'Animation Out Set Related Node Ids' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] - optional = True ignore_types = ("constraint",) @@ -122,4 +121,4 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): continue lib.remove_id(node) # remove the cbId attr if it exists - lib.set_id(root_id, node) # set root_id as cbId + lib.set_id(root_id, node, force=True) # set root_id as cbId From 48376264e73016820b653f77708fb6cbef181894 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:11:49 +0100 Subject: [PATCH 0425/1249] Refactor lib.generate_ids, lib.set_id logic --- colorbleed/action.py | 3 +- colorbleed/maya/__init__.py | 3 +- colorbleed/maya/lib.py | 79 ++++++++++++++----- ...date_animation_out_set_related_node_ids.py | 3 +- 4 files changed, 65 insertions(+), 23 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 4913fe8518..9b66da0206 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -198,6 +198,5 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): asset_id = io.find_one({"name": asset, "type": "asset"}, projection={"_id": True})['_id'] for node in nodes: - lib.remove_id(node) - lib.set_id(asset_id, node) + lib.set_id(node, asset_id, overwrite=True) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 43fd9d7d8f..01929eb9e3 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -70,4 +70,5 @@ def on_save(_): # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) - lib.generate_ids(nodes) + for node, new_id in lib.generate_ids(nodes): + lib.set_id(node, new_id, overwrite=False) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index bb35b986c1..b18bd0243b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -667,46 +667,89 @@ def get_id(node): return cmds.getAttr("{}.cbId".format(node)) -def generate_ids(nodes): - """Assign a new id of the current active context to the nodes""" +def generate_ids(nodes, asset_id=None): + """Returns new unique ids for the given nodes. + + Note: This does not assign the new ids, it only generates the values. + + To assign new ids using this method: + >>> nodes = ["a", "b", "c"] + >>> for node, id in generate_ids(nodes): + >>> set_id(node, id) + + To also override any existing values (and assign regenerated ids): + >>> nodes = ["a", "b", "c"] + >>> for node, id in generate_ids(nodes): + >>> set_id(node, id, overwrite=True) + + Args: + nodes (list): List of nodes. + asset_id (str or bson.ObjectId): The database id for the *asset* to + generate for. When None provided the current asset in the + active session is used. + + Returns: + list: A list of (node, id) tuples. + + """ - # Get the asset ID from the database for the asset of current context - asset_data = io.find_one({"type": "asset", - "name": api.Session["AVALON_ASSET"]}, - projection={"_id": True}) - asset_id = asset_data["_id"] + if asset_id is None: + # Get the asset ID from the database for the asset of current context + asset_data = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}, + projection={"_id": True}) + assert asset_data, "No current asset found in Session" + asset_id = asset_data['_id'] + + node_ids = [] for node in nodes: - set_id(asset_id, node) + _, uid = str(uuid.uuid4()).rsplit("-", 1) + unique_id = "{}:{}".format(asset_id, uid) + node_ids.append((node, unique_id)) + + return node_ids -def set_id(unique_id, node, force=False): +def set_id(node, unique_id, overwrite=False): """Add cbId to `node` unless one already exists. Args: - unique_id (str): the unique asset code from the database node (str): the node to add the "cbId" on - force (bool): if True sets the given unique_id as attribute value + unique_id (str): the unique asset code from the database + overwrite (bool, optional): When True overrides the current value even + if `node` already has an id. Defaults to False. Returns: None + """ attr = "{0}.cbId".format(node) - if not cmds.attributeQuery("cbId", node=node, exists=True): - - if not force: - _, uid = str(uuid.uuid4()).rsplit("-", 1) - unique_id = "{}:{}".format(unique_id, uid) - else: - unique_id = unique_id + exists = cmds.attributeQuery("cbId", node=node, exists=True) + # Add the attribute if it does not exist yet + if not exists: cmds.addAttr(node, longName="cbId", dataType="string") + + # Set the value + if not exists or overwrite: cmds.setAttr(attr, unique_id, type="string") def remove_id(node): + """Remove the id attribute from the input node. + + Args: + node (str): The node name + + Returns: + bool: Whether an id attribute was deleted + + """ if cmds.attributeQuery("cbId", node=node, exists=True): cmds.deleteAttr("{}.cbId".format(node)) + return True + return False # endregion ID diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 823d5841be..4f2368a5a3 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -120,5 +120,4 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): cls.log.error("Could not find root ID for '%s'", node) continue - lib.remove_id(node) # remove the cbId attr if it exists - lib.set_id(root_id, node, force=True) # set root_id as cbId + lib.set_id(node, root_id, overwrite=True) # set root_id as cbId From 5e51ab8ecc482f72b61d6b57dcb217df47dd430d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:16:47 +0100 Subject: [PATCH 0426/1249] Improve docstring for ValidateSingleAssembly --- .../maya/publish/validate_single_assembly.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/colorbleed/plugins/maya/publish/validate_single_assembly.py index 147945ac33..886e88ada3 100644 --- a/colorbleed/plugins/maya/publish/validate_single_assembly.py +++ b/colorbleed/plugins/maya/publish/validate_single_assembly.py @@ -5,14 +5,15 @@ import colorbleed.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): """Ensure the content of the instance is grouped in a single hierarchy - The instance must have a node which resides within the group which - is visible in the outliner. + The instance must have a single root node containing all the content. + This root node *must* be a top group in the outliner. + Example outliner: root_GRP - -- asset_001_GRP - -- asset_01_:rigDefault - -- asset_001_GRP - -- asset_01_:rigDefault + -- geometry_GRP + -- mesh_GEO + -- controls_GRP + -- control_CTL """ From 2759d1238d457fa0a9b0b1936c48116f557087ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:28:18 +0100 Subject: [PATCH 0427/1249] Refactor ValidateOutRelatedNodeIds code to read as more generic (simplify) --- ...date_animation_out_set_related_node_ids.py | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 4f2368a5a3..97483041ee 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -5,6 +5,44 @@ import colorbleed.api import colorbleed.maya.lib as lib +def get_id_from_history(node): + """Return first node id in the history chain that matches this node. + + The nodes in history must be of the exact same node type and must be + parented under the same parent. + + Args: + node (str): node to retrieve the + + Returns: + str or None: The id from the node in history or None when no id found + on any valid nodes in the history. + + """ + + node = cmds.ls(node, long=True)[0] + + # Find all similar nodes in history + history = cmds.listHistory(node) + node_type = cmds.nodeType(node) + similar_nodes = cmds.ls(history, exactType=node_type, long=True) + + # Exclude itself + similar_nodes = [x for x in similar_nodes if x != node] + + # The node *must be* under the same parent + parent = get_parent(node) + similar_nodes = [i for i in similar_nodes if + get_parent(i) == parent] + + # Check all of the remaining similar nodes and take the first one + # with an id and assume it's the original. + for similar_node in similar_nodes: + _id = lib.get_id(similar_node) + if _id: + return _id + + def get_parent(node): """Get the parent node of the given node Args: @@ -69,55 +107,20 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): invalid.append(node) continue - root_id = cls.get_history_root_id(node=node) - if root_id is not None and node_id != root_id: + history_id = get_id_from_history(node) + if history_id is not None and node_id != history_id: invalid.append(node) return invalid - @classmethod - def get_history_root_id(cls, node): - """ - Get the original node ID when a node has been deformed - - Args: - node (str): node to retrieve the - - Returns: - str: the asset ID as found in the database - - """ - - node = cmds.ls(node, long=True)[0] - - # Find all similar nodes in history - history = cmds.listHistory(node) - node_type = cmds.nodeType(node) - similar_nodes = cmds.ls(history, exactType=node_type, long=True) - - # Exclude itself - similar_nodes = [x for x in similar_nodes if x != node] - - # The node *must be* under the same parent - parent = get_parent(node) - similar_nodes = [i for i in similar_nodes if - get_parent(i) == parent] - - # Check all of the remaining similar nodes and take the first one - # with an id and assume it's the original. - for similar_node in similar_nodes: - _id = lib.get_id(similar_node) - if _id: - return _id - @classmethod def repair(cls, instance): for node in cls.get_invalid(instance): - # Get root asset ID - root_id = cls.get_history_root_id(node=node) - if not root_id: - cls.log.error("Could not find root ID for '%s'", node) + # Get the original id from history + history_id = get_id_from_history(node) + if not history_id: + cls.log.error("Could not find ID in history for '%s'", node) continue - lib.set_id(node, root_id, overwrite=True) # set root_id as cbId + lib.set_id(node, history_id, overwrite=True) From 7ac8122fea9692978f94fadad2c8feca6523e2f7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:28:42 +0100 Subject: [PATCH 0428/1249] Remove unused ignore_types class attribute --- .../maya/publish/validate_animation_out_set_related_node_ids.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 97483041ee..ab024be49c 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -70,8 +70,6 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): label = 'Animation Out Set Related Node Ids' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] - ignore_types = ("constraint",) - def process(self, instance): """Process all meshes""" From 9617c6bb2e42224ae66805c0595c1328005d75f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:29:18 +0100 Subject: [PATCH 0429/1249] PEP08 new line at end of file --- colorbleed/plugins/maya/publish/validate_animation_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index 1ac522f69a..0725281705 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -33,4 +33,4 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file + raise RuntimeError("Animation content is invalid. See log.") From 990b50f7fbb406968d77d8b2cc53b3195078694a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 12:34:34 +0100 Subject: [PATCH 0430/1249] Improve lib.set_id docstring --- colorbleed/maya/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b18bd0243b..b70cce3543 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -715,7 +715,8 @@ def set_id(node, unique_id, overwrite=False): Args: node (str): the node to add the "cbId" on - unique_id (str): the unique asset code from the database + unique_id (str): The unique node id to assign. + This should be generated by `generate_ids`. overwrite (bool, optional): When True overrides the current value even if `node` already has an id. Defaults to False. From 2a42f9b27cb508a153325525e0175caa412b7110 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 2 Nov 2017 15:06:20 +0100 Subject: [PATCH 0431/1249] implemented ImportModelLoader to import assets (.ma) --- colorbleed/plugins/maya/load/load_model.py | 98 ++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 9b17a1ae47..22caac8c2c 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -31,6 +31,104 @@ class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): return nodes +class ImportModelLoader(api.Loader): + """An ImportModelLoader for Maya + + This will implement the basic behavior for a loader to inherit from that + will containerize content. The loader will not be able to use `update` + but only `remove`. When calling the `update` the loader will return a + "NotImplementedError". + + """ + + families = ["colorbleed.model"] + representations = ["ma"] + + label = "Import Model" + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import maya.cmds as cmds + + from avalon import maya + from avalon.maya import lib + from avalon.maya.pipeline import containerise + + asset = context['asset'] + + namespace = namespace or lib.unique_namespace( + asset["name"] + "_", + prefix="_" if asset["name"][0].isdigit() else "", + suffix="_", + ) + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + i=True, + namespace=namespace, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + # Only containerize if any nodes were loaded by the Loader + if not nodes: + return + + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) + + def update(self, container, representation): + from avalon.vendor.Qt import QtWidgets + + message = ("The content of this asset was imported. " + "We cannot update this because we will need" + " to keep insane amount of things in mind.\n" + "Please remove and reimport the asset." + "\n\nIf you really need to update a lot we " + "recommend referencing.") + + QtWidgets.QMessageBox.critical(None, + "Can't update", + message, + QtWidgets.QMessageBox.Ok) + + return + + def remove(self, container): + + from maya import cmds + + namespace = container["namespace"] + container_name = container["objectName"] + + self.log.info("Removing '%s' from Maya.." % container["name"]) + + container_content = cmds.sets(container_name, query=True) + nodes = cmds.ls(container_content, long=True) + relatives = cmds.listRelatives(nodes, + ad=True, + fullPath=True) or [] + + nodes += relatives + + try: + cmds.delete(nodes) + except ValueError: + # Already implicitly deleted by Maya upon removing reference + pass + + cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) + + class GpuCacheLoader(api.Loader): """Load model Alembic as gpuCache""" From 6647c19e92b9c44553d6edb079f719a0d545b508 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 2 Nov 2017 15:36:23 +0100 Subject: [PATCH 0432/1249] removed relatives query --- colorbleed/plugins/maya/load/load_model.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 22caac8c2c..ff512610a3 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -45,9 +45,9 @@ class ImportModelLoader(api.Loader): representations = ["ma"] label = "Import Model" - order = -10 - icon = "code-fork" - color = "orange" + order = 10 + icon = "arrow-circle-down" + color = "white" def load(self, context, name=None, namespace=None, data=None): @@ -114,11 +114,8 @@ class ImportModelLoader(api.Loader): container_content = cmds.sets(container_name, query=True) nodes = cmds.ls(container_content, long=True) - relatives = cmds.listRelatives(nodes, - ad=True, - fullPath=True) or [] - nodes += relatives + nodes.append(container_name) try: cmds.delete(nodes) From 2ee6bac4962abb10a0d010a04dc322860897fe0c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 2 Nov 2017 18:41:28 +0100 Subject: [PATCH 0433/1249] Implement ANM-9 --- colorbleed/maya/__init__.py | 19 +++++++++++ colorbleed/maya/customize.py | 66 ++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 colorbleed/maya/customize.py diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 01929eb9e3..d96fbfed08 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -55,10 +55,29 @@ def on_init(_): log.warning("Can't load plug-in: " "{0} - {1}".format(plugin, e)) + def safe_deferred(fn): + """Execute deferred the function in a try-except""" + + def _fn(): + """safely call in deferred callback""" + try: + fn() + except Exception as exc: + print(exc) + + try: + utils.executeDeferred(_fn) + except Exception as exc: + print(exc) + + cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) force_load_deferred("mtoa") + from .customize import override_component_mask_commands + safe_deferred(override_component_mask_commands) + def on_save(_): """Automatically add IDs to new nodes diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py new file mode 100644 index 0000000000..7363874fb1 --- /dev/null +++ b/colorbleed/maya/customize.py @@ -0,0 +1,66 @@ +"""A set of commands that install overrides to Maya's UI""" + +import maya.cmds as mc +import maya.mel as mel +from functools import partial +import logging + + +log = logging.getLogger(__name__) + +COMPONENT_MASK_ORIGINAL = {} + + +def override_component_mask_commands(): + """Override component mask ctrl+click behavior. + + This implements special behavior for Maya's component + mask menu items where a ctrl+click will instantly make + it a isolated behavior disabling all others. + + Tested in Maya 2016 and 2018.1 + + """ + log.info("Installing override_component_mask_commands..") + + BUTTONS = mc.formLayout("objectMaskIcons", + query=True, + childArray=True) + # Skip the triangle list item + BUTTONS = [btn for btn in BUTTONS if btn != "objPickMenuLayout"] + + def _on_changed_callback(original, state): + """New callback""" + + # If "control" is held force the toggled one to on and + # toggle the others based on whether any of the buttons + # was remaining active after the toggle, if not then + # enable all + if mc.getModifiers() == 4: # = CTRL + state = True + active = [mc.iconTextCheckBox(btn, query=True, value=True) for btn + in BUTTONS] + if any(active): + mc.selectType(allObjects=False) + else: + mc.selectType(allObjects=True) + + # Replace #1 with the current button state + cmd = original.replace(" #1", " {}".format(int(state))) + mel.eval(cmd) + + # Get all component mask buttons + for btn in BUTTONS: + + # Store a reference to the original command so that if + # we rerun this override command it doesn't recursively + # try to implement the fix. (This also allows us to + # "uninstall" the behavior later) + if btn not in COMPONENT_MASK_ORIGINAL: + original = mc.iconTextCheckBox(btn, query=True, cc=True) + COMPONENT_MASK_ORIGINAL[btn] = original + + # Assign the special callback + original = COMPONENT_MASK_ORIGINAL[btn] + new_fn = partial(_on_changed_callback, original) + mc.iconTextCheckBox(btn, edit=True, cc=new_fn) From 9681c427cfa07dd1eaf84dc9ea08b2c521af6397 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 Nov 2017 09:50:43 +0100 Subject: [PATCH 0434/1249] Cleanup some code --- colorbleed/maya/customize.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 7363874fb1..cb0e47dc27 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -16,20 +16,21 @@ def override_component_mask_commands(): This implements special behavior for Maya's component mask menu items where a ctrl+click will instantly make - it a isolated behavior disabling all others. + it an isolated behavior disabling all others. - Tested in Maya 2016 and 2018.1 + Tested in Maya 2016 and 2018 """ log.info("Installing override_component_mask_commands..") - BUTTONS = mc.formLayout("objectMaskIcons", + # Get all object mask buttons + buttons = mc.formLayout("objectMaskIcons", query=True, childArray=True) # Skip the triangle list item - BUTTONS = [btn for btn in BUTTONS if btn != "objPickMenuLayout"] + buttons = [btn for btn in buttons if btn != "objPickMenuLayout"] - def _on_changed_callback(original, state): + def _on_changed_callback(raw_command, state): """New callback""" # If "control" is held force the toggled one to on and @@ -39,18 +40,17 @@ def override_component_mask_commands(): if mc.getModifiers() == 4: # = CTRL state = True active = [mc.iconTextCheckBox(btn, query=True, value=True) for btn - in BUTTONS] + in buttons] if any(active): mc.selectType(allObjects=False) else: mc.selectType(allObjects=True) # Replace #1 with the current button state - cmd = original.replace(" #1", " {}".format(int(state))) + cmd = raw_command.replace(" #1", " {}".format(int(state))) mel.eval(cmd) - # Get all component mask buttons - for btn in BUTTONS: + for btn in buttons: # Store a reference to the original command so that if # we rerun this override command it doesn't recursively From 8c472a9594e7abe8d47946a37b52ea41e9b77f3e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 3 Nov 2017 14:14:32 +0100 Subject: [PATCH 0435/1249] Remove underscore from inner function, make it look less private. --- colorbleed/maya/customize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index cb0e47dc27..64f33d5aae 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -30,7 +30,7 @@ def override_component_mask_commands(): # Skip the triangle list item buttons = [btn for btn in buttons if btn != "objPickMenuLayout"] - def _on_changed_callback(raw_command, state): + def on_changed_callback(raw_command, state): """New callback""" # If "control" is held force the toggled one to on and @@ -62,5 +62,5 @@ def override_component_mask_commands(): # Assign the special callback original = COMPONENT_MASK_ORIGINAL[btn] - new_fn = partial(_on_changed_callback, original) + new_fn = partial(on_changed_callback, original) mc.iconTextCheckBox(btn, edit=True, cc=new_fn) From 0999b4b8fa870378d6e7642770d6d01a8f5b2e9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 6 Nov 2017 08:41:14 +0100 Subject: [PATCH 0436/1249] Fix PLN-40 --- colorbleed/maya/plugin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 2a7c32d2ac..2b7b9558cb 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -71,6 +71,13 @@ class ReferenceLoader(api.Loader): assert os.path.exists(path), "%s does not exist." % path cmds.file(path, loadReference=reference_node, type=file_type) + # Fix PLN-40 for older containers created with Avalon that had the + # `.verticesOnlySet` set to True. + if cmds.getAttr(container['objectName'] + ".verticesOnlySet"): + self.log.info("Setting %s.verticesOnlySet to False", + container['objectName']) + cmds.setAttr(container['objectName'] + ".verticesOnlySet", False) + # TODO: Add all new nodes in the reference to the container # Currently new nodes in an updated reference are not added to the # container whereas actually they should be! From e1ec04b2d2e388869546fd6cef63a3f8009817b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 6 Nov 2017 12:58:44 +0100 Subject: [PATCH 0437/1249] Implement draft version for PLN-10: notify outdated containers w/ pop-up --- colorbleed/lib.py | 47 +++++++++++++ colorbleed/maya/__init__.py | 34 ++++++++++ colorbleed/widgets/__init__.py | 0 colorbleed/widgets/popup.py | 119 +++++++++++++++++++++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 colorbleed/lib.py create mode 100644 colorbleed/widgets/__init__.py create mode 100644 colorbleed/widgets/popup.py diff --git a/colorbleed/lib.py b/colorbleed/lib.py new file mode 100644 index 0000000000..dc71f71fb8 --- /dev/null +++ b/colorbleed/lib.py @@ -0,0 +1,47 @@ +import avalon.io as io +import avalon.api + + +def is_latest(representation): + """Return whether the representation is from latest version + + Args: + representation (str or io.ObjectId): The representation id. + + Returns: + bool: Whether the representation is of latest version. + + """ + + rep = io.find_one({"_id": io.ObjectId(representation), + "type": "representation"}) + version = io.find_one({"_id": rep['parent']}) + + # Store the highest available version so the model can know + # whether current version is currently up-to-date. + highest_version = io.find_one({ + "type": "version", + "parent": version["parent"] + }, sort=[("name", -1)]) + + if version['name'] != highest_version['name']: + return True + else: + return False + + +def any_outdated(): + """Return whether the current scene has any outdated content""" + + checked = set() + host = avalon.api.registered_host() + for container in host.ls(): + representation = container['representation'] + if representation in checked: + continue + + if not is_latest(container['representation']): + return True + + checked.add(representation) + return False \ No newline at end of file diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index d96fbfed08..d482f6751a 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -32,6 +32,7 @@ def install(): log.info("Installing callbacks ... ") avalon.on("init", on_init) avalon.on("save", on_save) + avalon.on("open", on_open) def uninstall(): @@ -91,3 +92,36 @@ def on_save(_): nodes = lib.get_id_required_nodes(referenced_nodes=False) for node, new_id in lib.generate_ids(nodes): lib.set_id(node, new_id, overwrite=False) + + +def on_open(_): + """On scene open let's assume the containers have changed.""" + + from ..lib import any_outdated + from avalon.vendor.Qt import QtWidgets + from ..widgets import popup + + if any_outdated(): + log.warning("Scene has outdated content.") + + # Find maya main window + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + parent = top_level_widgets.get("MayaWindow", None) + + if parent is None: + log.info("Skipping outdated content pop-up " + "because Maya window can't be found.") + else: + + # Show outdated pop-up + def _on_show_inventory(): + import avalon.tools.cbsceneinventory as tool + tool.show(parent=parent) + + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene has outdated content") + dialog.setMessage("There are outdated containers in " + "your Maya scene.") + dialog.on_show.connect(_on_show_inventory) + dialog.show() diff --git a/colorbleed/widgets/__init__.py b/colorbleed/widgets/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/widgets/popup.py b/colorbleed/widgets/popup.py new file mode 100644 index 0000000000..0ad1e07490 --- /dev/null +++ b/colorbleed/widgets/popup.py @@ -0,0 +1,119 @@ +import sys +import logging +import contextlib + + +from avalon.vendor.Qt import QtCore, QtWidgets, QtGui + +log = logging.getLogger(__name__) + + +class Popup(QtWidgets.QDialog): + + on_show = QtCore.Signal() + + def __init__(self, parent=None, *args, **kwargs): + super(Popup, self).__init__(parent=parent, *args, **kwargs) + self.setContentsMargins(0, 0, 0, 0) + + # Layout + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(10, 5, 10, 10) + message = QtWidgets.QLabel("") + message.setStyleSheet(""" + QLabel { + font-size: 12px; + } + """) + show = QtWidgets.QPushButton("Show") + show.setSizePolicy(QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Maximum) + show.setStyleSheet("""QPushButton { background-color: #BB0000 }""") + + + layout.addWidget(message) + layout.addWidget(show) + + # Size + self.resize(400, 40) + geometry = self.calculate_window_geometry() + self.setGeometry(geometry) + + self.widgets = { + "message": message, + "show": show, + } + + # Signals + show.clicked.connect(self._on_show_clicked) + + # Set default title + self.setWindowTitle("Popup") + + def setMessage(self, message): + self.widgets['message'].setText(message) + + def _on_show_clicked(self): + """Callback for when the 'show' button is clicked. + + Raises the parent (if any) + + """ + + parent = self.parent() + self.close() + + # Trigger the signal + self.on_show.emit() + + if parent: + parent.raise_() + + def calculate_window_geometry(self): + """Respond to status changes + + On creation, align window with screen bottom right. + + """ + + window = self + + width = window.width() + width = max(width, window.minimumWidth()) + + height = window.height() + height = max(height, window.sizeHint().height()) + + desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() + screen_geometry = window.geometry() + + screen_width = screen_geometry.width() + screen_height = screen_geometry.height() + + # Calculate width and height of system tray + systray_width = screen_geometry.width() - desktop_geometry.width() + systray_height = screen_geometry.height() - desktop_geometry.height() + + padding = 10 + + x = screen_width - width + y = screen_height - height + + x -= systray_width + padding + y -= systray_height + padding + + return QtCore.QRect(x, y, width, height) + + +@contextlib.contextmanager +def application(): + app = QtWidgets.QApplication(sys.argv) + yield + app.exec_() + + +if __name__ == "__main__": + with application(): + dialog = Popup() + dialog.setMessage("There are outdated containers in your Maya scene.") + dialog.show() From 4a9e21fc05398fb22e6e425fd0677861a4787eae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 6 Nov 2017 13:06:48 +0100 Subject: [PATCH 0438/1249] Improve comment --- colorbleed/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index dc71f71fb8..e25bbfcafd 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -17,8 +17,7 @@ def is_latest(representation): "type": "representation"}) version = io.find_one({"_id": rep['parent']}) - # Store the highest available version so the model can know - # whether current version is currently up-to-date. + # Get highest version under the parent highest_version = io.find_one({ "type": "version", "parent": version["parent"] From b1c045ab72ffb46fb188ccd8027106fbd93c3e75 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 6 Nov 2017 17:55:31 +0100 Subject: [PATCH 0439/1249] Refactor container['objectName'] to use the `node` variable --- colorbleed/maya/plugin.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 2b7b9558cb..aecb55611b 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -73,19 +73,18 @@ class ReferenceLoader(api.Loader): # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. - if cmds.getAttr(container['objectName'] + ".verticesOnlySet"): - self.log.info("Setting %s.verticesOnlySet to False", - container['objectName']) - cmds.setAttr(container['objectName'] + ".verticesOnlySet", False) + if cmds.getAttr(node + ".verticesOnlySet"): + self.log.info("Setting %s.verticesOnlySet to False", node) + cmds.setAttr(node + ".verticesOnlySet", False) # TODO: Add all new nodes in the reference to the container # Currently new nodes in an updated reference are not added to the # container whereas actually they should be! nodes = cmds.referenceQuery(reference_node, nodes=True, dagPath=True) - cmds.sets(nodes, forceElement=container['objectName']) + cmds.sets(nodes, forceElement=node) # Update metadata - cmds.setAttr(container["objectName"] + ".representation", + cmds.setAttr(node + ".representation", str(representation["_id"]), type="string") From f27f986360e862537e64ded10cbcbd2c0f8f48b2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 10:39:02 +0100 Subject: [PATCH 0440/1249] renderlayer settings creator --- .../maya/create/colorbleed_rendersettings.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 colorbleed/plugins/maya/create/colorbleed_rendersettings.py diff --git a/colorbleed/plugins/maya/create/colorbleed_rendersettings.py b/colorbleed/plugins/maya/create/colorbleed_rendersettings.py new file mode 100644 index 0000000000..1d64871297 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_rendersettings.py @@ -0,0 +1,26 @@ +from collections import OrderedDict + +import avalon.maya +from colorbleed.maya import lib + + +class CreateRenderSettings(avalon.maya.Creator): + + label = "Render Settings" + family = "colorbleed.rendersettings" + icon = "gears" + + def __init__(self, *args, **kwargs): + super(CreateRenderSettings, self).__init__(*args, **kwargs) + + data = OrderedDict(**self.data) + + data["publish"] = True + data["includeDefaultRenderLayer"] = False + data["overrideFrameRange"] = False + + # Get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + data[key] = value + + self.data = data \ No newline at end of file From 4660a1d7745b23e7fadbeeac4c00e83b4ab4cf5f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 7 Nov 2017 11:16:34 +0100 Subject: [PATCH 0441/1249] Implement improved logging for `lib.apply_shaders` and cosmetics (PEP08) --- colorbleed/maya/lib.py | 65 +++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 4bd6bf6091..09032df09e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -447,7 +447,7 @@ def extract_alembic(file, endFrame (float): End frame of output. Ignored if `frameRange` provided. - frameRange (tuple or str): Two-tuple with start and end frame or a + frameRange (tuple or str): Two-tuple with start and end frame or a string formatted as: "startFrame endFrame". This argument overrides `startFrame` and `endFrame` arguments. @@ -481,7 +481,7 @@ def extract_alembic(file, an Euler filter. Euler filtering helps resolve irregularities in rotations especially if X, Y, and Z rotations exceed 360 degrees. Defaults to True. - + """ # Ensure alembic exporter is loaded @@ -669,28 +669,28 @@ def get_id(node): def generate_ids(nodes, asset_id=None): """Returns new unique ids for the given nodes. - + Note: This does not assign the new ids, it only generates the values. - + To assign new ids using this method: >>> nodes = ["a", "b", "c"] >>> for node, id in generate_ids(nodes): >>> set_id(node, id) - + To also override any existing values (and assign regenerated ids): >>> nodes = ["a", "b", "c"] >>> for node, id in generate_ids(nodes): >>> set_id(node, id, overwrite=True) - + Args: nodes (list): List of nodes. asset_id (str or bson.ObjectId): The database id for the *asset* to - generate for. When None provided the current asset in the + generate for. When None provided the current asset in the active session is used. - + Returns: list: A list of (node, id) tuples. - + """ if asset_id is None: @@ -715,14 +715,14 @@ def set_id(node, unique_id, overwrite=False): Args: node (str): the node to add the "cbId" on - unique_id (str): The unique node id to assign. + unique_id (str): The unique node id to assign. This should be generated by `generate_ids`. - overwrite (bool, optional): When True overrides the current value even + overwrite (bool, optional): When True overrides the current value even if `node` already has an id. Defaults to False. Returns: None - + """ attr = "{0}.cbId".format(node) @@ -739,9 +739,9 @@ def set_id(node, unique_id, overwrite=False): def remove_id(node): """Remove the id attribute from the input node. - + Args: - node (str): The node name + node (str): The node name Returns: bool: Whether an id attribute was deleted @@ -973,20 +973,19 @@ def apply_shaders(relationships, shadernodes, nodes): shader_data = relationships.get("relationships", {}) shading_engines = cmds.ls(shadernodes, type="objectSet", long=True) - assert len(shading_engines) > 0, ("Error in retrieving objectSets " - "from reference") + assert shading_engines, "Error in retrieving objectSets from reference" # region compute lookup - ns_nodes_by_id = defaultdict(list) + nodes_by_id = defaultdict(list) for node in nodes: - ns_nodes_by_id[get_id(node)].append(node) + nodes_by_id[get_id(node)].append(node) shading_engines_by_id = defaultdict(list) for shad in shading_engines: shading_engines_by_id[get_id(shad)].append(shad) # endregion - # region assign + # region assign shading engines and other sets for data in shader_data.values(): # collect all unique IDs of the set members shader_uuid = data["uuid"] @@ -994,21 +993,29 @@ def apply_shaders(relationships, shadernodes, nodes): filtered_nodes = list() for uuid in member_uuids: - filtered_nodes.extend(ns_nodes_by_id[uuid]) + filtered_nodes.extend(nodes_by_id[uuid]) - shading_engine = shading_engines_by_id[shader_uuid] - assert len(shading_engine) == 1, ("Could not find the correct " - "objectSet with cbId " - "'{}'".format(shader_uuid)) + id_shading_engines = shading_engines_by_id[shader_uuid] + if not id_shading_engines: + log.error("No shader found with cbId " + "'{}'".format(shader_uuid)) + continue + elif len(id_shading_engines) > 1: + log.error("Skipping shader assignment. " + "More than one shader found with cbId " + "'{}'. (found: {})".format(shader_uuid, + id_shading_engines)) + continue - if filtered_nodes: - cmds.sets(filtered_nodes, forceElement=shading_engine[0]) - else: + if not filtered_nodes: log.warning("No nodes found for shading engine " - "'{0}'".format(shading_engine[0])) + "'{0}'".format(id_shading_engines[0])) + continue + + cmds.sets(filtered_nodes, forceElement=id_shading_engines[0]) # endregion - apply_attributes(attributes, ns_nodes_by_id) + apply_attributes(attributes, nodes_by_id) # endregion LOOKDEV From 4d25408152c44f9828ecd39a1d7ad2b854ff23a2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 12:21:45 +0100 Subject: [PATCH 0442/1249] refactored regenerate IDs to match new logic --- colorbleed/action.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index 9b66da0206..a94dc6e921 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,8 +1,6 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import -import uuid - from maya import cmds import pyblish.api @@ -197,6 +195,5 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): asset = instance.data['asset'] asset_id = io.find_one({"name": asset, "type": "asset"}, projection={"_id": True})['_id'] - for node in nodes: - lib.set_id(node, asset_id, overwrite=True) - + for node, _id in lib.generate_ids(nodes, asset_id=asset_id): + lib.set_id(node, _id, overwrite=True) From 9cb1ea8430ace78c16961d50c953c7105b80b0bf Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 12:22:04 +0100 Subject: [PATCH 0443/1249] added shadingEngines to instance list --- colorbleed/plugins/maya/publish/collect_look.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 2df35499bc..12f0d77022 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -133,6 +133,11 @@ class CollectLook(pyblish.api.InstancePlugin): self.log.warning("No sets found for the nodes in the instance: " "%s" % instance[:]) + # Ensure unique shader sets + # Add shader sets to the instance for unify ID validation + instance.extend(shader for shader in looksets if shader + not in instance_lookup) + self.log.info("Collected look for %s" % instance) def collect_sets(self, instance): From dc11637c3a550a235135c3ff6ee0b28255274d2e Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 12:22:51 +0100 Subject: [PATCH 0444/1249] removed invalid_dict method, simplified get_invalid method --- .../maya/publish/validate_node_ids_unique.py | 29 +++++-------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py index e32eb297a3..f828cac0c3 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py @@ -25,14 +25,14 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): """Process all meshes""" # Ensure all nodes have a cbId - invalid = self.get_invalid_dict(instance) + invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Nodes found with non-unique " "asset IDs: {0}".format(invalid)) @classmethod - def get_invalid_dict(cls, instance): - """Return a dictionary mapping of id key to list of member nodes""" + def get_invalid(cls, instance): + """Return the member nodes that are invalid""" # Collect each id with their members ids = defaultdict(list) @@ -42,24 +42,11 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): continue ids[object_id].append(member) - # Skip those without IDs (if everything should have an ID that should - # be another validation) - ids.pop(None, None) - - # Take only the ids with more than one member - invalid = dict((_id, members) for _id, members in ids.iteritems() if - len(members) > 1) - return invalid - - @classmethod - def get_invalid(cls, instance): - """Return the member nodes that are invalid""" - - invalid_dict = cls.get_invalid_dict(instance) - # Take only the ids with more than one member invalid = list() - for members in invalid_dict.itervalues(): - invalid.extend(members) + for _ids, members in ids.iteritems(): + if len(members) > 1: + cls.log.error("ID found on multiple nodes: '%s'" % members) + invalid.extend(members) - return invalid \ No newline at end of file + return invalid From 24c75a6797b7d4ca66410ba2b1830c228a85b47f Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 12:36:21 +0100 Subject: [PATCH 0445/1249] comsetics --- .../plugins/maya/publish/validate_look_contents.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 6e14b3af24..c6ea36f9be 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -10,6 +10,10 @@ class ValidateLookContents(pyblish.api.InstancePlugin): * At least one relationship must be collection. * All relationship object sets at least have an ID value + Tip: + * When no node IDs are found on shadingEngines please save your scene + and try again. + """ order = colorbleed.api.ValidateContentsOrder @@ -57,12 +61,12 @@ class ValidateLookContents(pyblish.api.InstancePlugin): invalid = set() - attributes = ["relationships", "attributes"] + keys = ["relationships", "attributes"] lookdata = instance.data["lookData"] - for attr in attributes: - if attr not in lookdata: - cls.log.error("Look Data has no attribute " - "'{}'".format(attr)) + for key in keys: + if key not in lookdata: + cls.log.error("Look Data has no key " + "'{}'".format(key)) invalid.add(instance.name) # Validate at least one single relationship is collected From e62d3748ccc86c607a376cfe1a9d2fdbf1de548c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 7 Nov 2017 12:54:09 +0100 Subject: [PATCH 0446/1249] removed redundant check, cosmetics --- .../plugins/maya/publish/collect_look.py | 2 +- .../validate_look_no_default_shaders.py | 43 +++++-------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 12f0d77022..4dfd29cadd 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -186,7 +186,7 @@ class CollectLook(pyblish.api.InstancePlugin): node_id = lib.get_id(node) if not node_id: - self.log.error("Node '{}' has no attribute 'cbId'".format(node)) + self.log.error("Member '{}' has no attribute 'cbId'".format(node)) return member_data = {"name": node, "uuid": node_id} diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 39b4bd6cb8..9315359184 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -5,7 +5,7 @@ import colorbleed.api class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): - """Validate look contains no default shaders. + """Validate if any node has a connection to a default shader. This checks whether the look has any members of: - lambert1 @@ -28,6 +28,9 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): label = 'Look No Default Shaders' actions = [colorbleed.api.SelectInvalidAction] + DEFAULT_SHADERS = {"lambert1", "initialShadingGroup", + "initialParticleSE", "particleCloud1"} + def process(self, instance): """Process all the nodes in the instance""" @@ -38,44 +41,18 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - disallowed = ["lambert1", "initialShadingGroup", - "initialParticleSE", "particleCloud1"] - disallowed = set(disallowed) - - # Check if there are any skinClusters present - # If so ensure nodes which are skinned - intermediate = [] - skinclusters = cmds.ls(type="skinCluster") - cls.log.info("Found skinClusters, will skip original shapes") - if skinclusters: - intermediate += cmds.ls(intermediateObjects=True, - shapes=True, - long=True) invalid = set() for node in instance: + # Get shading engine connections + shaders = cmds.listConnections(node, type="shadingEngine") or [] - # get connection - # listConnections returns a list or None - object_sets = cmds.listConnections(node, type="objectSet") or [] - - # Ensure the shape in the instances have at least a single shader - # connected if it *can* have a shader, like a `surfaceShape` in - # Maya. - if (cmds.objectType(node, isAType="surfaceShape") and - not cmds.ls(object_sets, type="shadingEngine")): - if node in intermediate: - continue - cls.log.error("Detected shape without shading engine: " - "'{}'".format(node)) - invalid.add(node) - - # Check for any disallowed connections - if any(s in disallowed for s in object_sets): + # Check for any disallowed connections on *all* nodes + if any(s in cls.DEFAULT_SHADERS for s in shaders): # Explicitly log each individual "wrong" connection. - for s in object_sets: - if s in disallowed: + for s in shaders: + if s in cls.DEFAULT_SHADERS: cls.log.error("Node has unallowed connection to " "'{}': {}".format(s, node)) From 107c5d0129151ad52725a0691eeb42f14fcb1f43 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 7 Nov 2017 13:44:17 +0100 Subject: [PATCH 0447/1249] Fix collect_instances also including intermediate objects, now only non-intermediate nodes are included --- colorbleed/plugins/maya/publish/collect_instances.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index c25856c12c..08779fe378 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -87,10 +87,14 @@ class CollectInstances(pyblish.api.ContextPlugin): # Collect members members = cmds.ls(members, long=True) or [] + # `maya.cmds.listRelatives(noIntermediate=True)` only works when + # `shapes=True` argument is passed, since we also want to include + # transforms we filter afterwards. children = cmds.listRelatives(members, allDescendents=True, - fullPath=True, - noIntermediate=True) or [] + fullPath=True) or [] + children = cmds.ls(children, noIntermediate=True, long=True) + parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) From c3580630b0b8050e56a45f686e6eabe307d65835 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 14:36:57 +0100 Subject: [PATCH 0448/1249] updated submit to support initialstatus for job --- .../maya/create/colorbleed_renderglobals.py | 38 +++++++++++++++++++ .../maya/create/colorbleed_rendersettings.py | 26 ------------- .../plugins/maya/publish/submit_deadline.py | 24 +++++++++--- 3 files changed, 56 insertions(+), 32 deletions(-) create mode 100644 colorbleed/plugins/maya/create/colorbleed_renderglobals.py delete mode 100644 colorbleed/plugins/maya/create/colorbleed_rendersettings.py diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py new file mode 100644 index 0000000000..952e7a9476 --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -0,0 +1,38 @@ +from collections import OrderedDict + +import avalon.maya + + +class CreateRenderGlobals(avalon.maya.Creator): + + label = "Render Globals" + family = "colorbleed.renderglobals" + icon = "gears" + + def __init__(self, *args, **kwargs): + super(CreateRenderGlobals, self).__init__(*args, **kwargs) + + # We won't be publishing this one + self.data["id"] = "avalon.renderglobals" + data = OrderedDict(**self.data) + + data["suspendPublishJob"] = False + data["includeDefaultRenderLayer"] = False + data["priority"] = 50 + data["whitelist"] = False + data["machineList"] = "" + + self.data = data + self.options = {"useSelection": False} # Force no content + + def process(self): + from maya import cmds + + exists = cmds.ls("renderglobalsDefault") + assert len(exists) <= 1, ( + "More than one renderglobal exists, this is a bug") + + if exists: + return cmds.warning("%s already exists." % exists[0]) + + super(CreateRenderGlobals, self).process() \ No newline at end of file diff --git a/colorbleed/plugins/maya/create/colorbleed_rendersettings.py b/colorbleed/plugins/maya/create/colorbleed_rendersettings.py deleted file mode 100644 index 1d64871297..0000000000 --- a/colorbleed/plugins/maya/create/colorbleed_rendersettings.py +++ /dev/null @@ -1,26 +0,0 @@ -from collections import OrderedDict - -import avalon.maya -from colorbleed.maya import lib - - -class CreateRenderSettings(avalon.maya.Creator): - - label = "Render Settings" - family = "colorbleed.rendersettings" - icon = "gears" - - def __init__(self, *args, **kwargs): - super(CreateRenderSettings, self).__init__(*args, **kwargs) - - data = OrderedDict(**self.data) - - data["publish"] = True - data["includeDefaultRenderLayer"] = False - data["overrideFrameRange"] = False - - # Get basic animation data : start / end / handles / steps - for key, value in lib.collect_animation_data().items(): - data[key] = value - - self.data = data \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index db0611839e..8f046f1c39 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -2,6 +2,7 @@ import os import json import shutil import getpass +import pprint from maya import cmds @@ -194,7 +195,10 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): }) # Include optional render globals - payload["JobInfo"].update(instance.data.get("renderGlobals", {})) + render_globals = instance.data.get("renderGlobals", {}) + payload["JobInfo"].update(render_globals) + + pprint.pprint(payload["JobInfo"]) self.preflight_check(instance) @@ -215,14 +219,19 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): with open(json_fpath, "w") as f: json.dump(data, f, indent=4, sort_keys=True) + self.log.info("Creating publish job") + state = instance.data["suspendPublishJob"] publish_job = self.create_publish_job(fname, deadline_user, comment, jobname, render_job, - json_fpath) + json_fpath, + state) if not publish_job: self.log.error("Could not submit publish job!") + else: + self.log.info(publish_job) else: try: @@ -279,15 +288,17 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): ) def create_publish_job(self, fname, user, comment, jobname, - job, json_fpath): - """ - Make sure all frames are published + job, json_fpath, state): + """Make sure all frames are published + Args: job (dict): the render job data json_fpath (str): file path to json file + state (str): In which state the job needs to when submitted, e.g.: + "Suspended" Returns: - + dict """ url = "{}/api/jobs".format(api.Session["AVALON_DEADLINE"]) @@ -310,6 +321,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "JobDependency0": job["_id"], "UserName": user, "Comment": comment, + "InitialStatus": state }, "PluginInfo": { "Version": "3.6", From 6fc1ec413b9ca6c3f8ac6eca9d888eb77e55fe12 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 14:37:49 +0100 Subject: [PATCH 0449/1249] added render globals to control job submittions --- .../maya/publish/collect_renderlayers.py | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index a8cdc5a40f..36d6c0609f 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,3 +1,5 @@ +import pprint + from maya import cmds import pyblish.api @@ -23,12 +25,26 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): relative_file = current_file.replace(registered_root, "{root}") source_file = relative_file.replace("\\", "/") + # Get renderglobals + if cmds.objExists("renderglobalsDefault"): + attr = "renderglobalsDefault.includeDefaultRenderLayer" + use_defaultlayer = cmds.getAttr(attr) + else: + use_defaultlayer = False + + # Get render layers renderlayers = cmds.ls(type="renderLayer") + # Exclude renderlayers if attribute is False + if not use_defaultlayer: + renderlayers = [i for i in renderlayers if + not i.endswith("defaultRenderLayer")] + for layer in renderlayers: if layer.endswith("defaultRenderLayer"): - continue + layername = "masterLayer" + else: + layername = layer.split("rs_", 1)[-1] - layername = layer.split("rs_", 1)[-1] data = {"family": "Render Layers", "families": ["colorbleed.renderlayer"], "publish": cmds.getAttr("{}.renderable".format(layer)), @@ -63,20 +79,26 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): # Include (optional) global settings # TODO(marcus): Take into account layer overrides try: - avalon_globals = maya.lsattr("id", "avalon.renderglobals")[0] + render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: pass else: - _globals = maya.read(avalon_globals) - data["renderGlobals"] = self.get_global_overrides(_globals) + _globals = maya.read(render_globals) + # Ensure machine list is created correctly + overrides = self.translate_overrides(_globals) + + # Check if renders need to published + data.update(**overrides) instance = context.create_instance(layername) instance.data.update(data) + pprint.pprint(instance.data) + def get_render_attribute(self, attr): return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) - def get_global_overrides(self, globals): + def translate_overrides(self, globals): """ Get all overrides with a value, skip those without @@ -90,15 +112,19 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): Returns: dict: only overrides with values """ - keys = ["pool", "group", "frames", "priority"] - read_globals = {} - for key in keys: - value = globals[key] - if not value: - continue - read_globals[key.capitalize()] = value + machine_list = globals["machineList"] + translation = {"renderGlobals": + {"Priority": globals["priority"]}, + "suspendPublishJob": "Active" + } - if not read_globals: - self.log.info("Submitting without overrides") + if globals["whitelist"]: + translation["renderGlobals"]["Whitelist"] = machine_list + else: + translation["renderGlobals"]["Blacklist"] = machine_list + + if globals["suspendPublishJob"]: + translation["suspendPublishJob"] = "Suspended" + + return translation - return read_globals \ No newline at end of file From 8d3935b09c02409e1a01c09f99c368865fc427bd Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 14:38:45 +0100 Subject: [PATCH 0450/1249] added renderglobals controller --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 952e7a9476..a0e7d9195a 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -14,6 +14,11 @@ class CreateRenderGlobals(avalon.maya.Creator): # We won't be publishing this one self.data["id"] = "avalon.renderglobals" + + # We don't need subset or asset attributes + self.data.pop("subset", None) + self.data.pop("asset", None) + data = OrderedDict(**self.data) data["suspendPublishJob"] = False @@ -35,4 +40,4 @@ class CreateRenderGlobals(avalon.maya.Creator): if exists: return cmds.warning("%s already exists." % exists[0]) - super(CreateRenderGlobals, self).process() \ No newline at end of file + super(CreateRenderGlobals, self).process() From 8cdff651696c3dc66cfd2d75543672046d89f27f Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 15:40:40 +0100 Subject: [PATCH 0451/1249] added check, renderglobals must be present to collect renderlayers --- .../maya/publish/collect_renderlayers.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 36d6c0609f..ca7ccace09 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -25,20 +25,26 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): relative_file = current_file.replace(registered_root, "{root}") source_file = relative_file.replace("\\", "/") - # Get renderglobals - if cmds.objExists("renderglobalsDefault"): - attr = "renderglobalsDefault.includeDefaultRenderLayer" - use_defaultlayer = cmds.getAttr(attr) - else: - use_defaultlayer = False + # Get render globals node + try: + render_globals = cmds.ls("renderglobalsDefault")[0] + except IndexError: + raise RuntimeError("Can not collect renderlayers without " + "renderGlobals node") + + attr = "{}.includeDefaultRenderLayer".format(render_globals) + use_defaultlayer = cmds.getAttr(attr) # Get render layers renderlayers = cmds.ls(type="renderLayer") - # Exclude renderlayers if attribute is False if not use_defaultlayer: renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] + # Get global overrides and translate to Deadline values + _globals = maya.read(render_globals) + overrides = self.translate_overrides(_globals) + for layer in renderlayers: if layer.endswith("defaultRenderLayer"): layername = "masterLayer" @@ -78,17 +84,8 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): # Include (optional) global settings # TODO(marcus): Take into account layer overrides - try: - render_globals = cmds.ls("renderglobalsDefault")[0] - except IndexError: - pass - else: - _globals = maya.read(render_globals) - # Ensure machine list is created correctly - overrides = self.translate_overrides(_globals) - - # Check if renders need to published - data.update(**overrides) + # Add the global overrides + data.update(**overrides) instance = context.create_instance(layername) instance.data.update(data) From 0520b6a03f15f41cee1d10a1c5c56c87af9af9ef Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 16:41:31 +0100 Subject: [PATCH 0452/1249] check for render global not before collecting layers, simplified code --- .../maya/publish/collect_renderlayers.py | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index ca7ccace09..1ae584406a 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -29,7 +29,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): try: render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: - raise RuntimeError("Can not collect renderlayers without " + raise RuntimeError("Cannot collect renderlayers without " "renderGlobals node") attr = "{}.includeDefaultRenderLayer".format(render_globals) @@ -41,10 +41,6 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] - # Get global overrides and translate to Deadline values - _globals = maya.read(render_globals) - overrides = self.translate_overrides(_globals) - for layer in renderlayers: if layer.endswith("defaultRenderLayer"): layername = "masterLayer" @@ -84,7 +80,8 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): # Include (optional) global settings # TODO(marcus): Take into account layer overrides - # Add the global overrides + # Get global overrides and translate to Deadline values + overrides = self.translate_overrides(render_globals) data.update(**overrides) instance = context.create_instance(layername) @@ -95,33 +92,39 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): def get_render_attribute(self, attr): return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) - def translate_overrides(self, globals): - """ - Get all overrides with a value, skip those without + def translate_overrides(self, render_globals): + """Get all overrides with a value, skip those without Here's the kicker. These globals override defaults in the submission integrator, but an empty value means no overriding is made. Otherwise, Frames would override the default frames set under globals. Args: - globals (dict) collection of render globals + render_globals (str): collection of render globals Returns: dict: only overrides with values """ - machine_list = globals["machineList"] + + _globals = maya.read(render_globals) + + machine_list = _globals["machineList"] translation = {"renderGlobals": - {"Priority": globals["priority"]}, + {"Priority": _globals["priority"]}, "suspendPublishJob": "Active" } - if globals["whitelist"]: + if _globals["whitelist"]: translation["renderGlobals"]["Whitelist"] = machine_list else: translation["renderGlobals"]["Blacklist"] = machine_list - if globals["suspendPublishJob"]: + if _globals["suspendPublishJob"]: translation["suspendPublishJob"] = "Suspended" - return translation + if _globals["startFrame"] and _globals["endFrame"]: + frame_range = "{}-{}".format(_globals["startFrame"], + _globals["endFrame"]) + translation["renderGlobals"]["Frames"] = frame_range + return translation From a680686fadca13632917de9b78f7e6f72f7e6398 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 16:42:24 +0100 Subject: [PATCH 0453/1249] removed attributes, when processing check for existing node of the same name --- .../plugins/maya/create/colorbleed_renderglobals.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index a0e7d9195a..c2e96d0103 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,5 +1,7 @@ from collections import OrderedDict +import maya.cmds as cmds + import avalon.maya @@ -21,19 +23,23 @@ class CreateRenderGlobals(avalon.maya.Creator): data = OrderedDict(**self.data) + startframe = cmds.playbackOptions(query=True, animationStartTime=True) + endframe = cmds.playbackOptions(query=True, animationEndTime=True) + data["suspendPublishJob"] = False data["includeDefaultRenderLayer"] = False data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" + data["startFrame"] = int(startframe) + data["endFrame"] = int(endframe) self.data = data self.options = {"useSelection": False} # Force no content def process(self): - from maya import cmds - exists = cmds.ls("renderglobalsDefault") + exists = cmds.ls(self.name) assert len(exists) <= 1, ( "More than one renderglobal exists, this is a bug") From b8081925d0eb4cc051451f413770000dc02bbe50 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 16:51:24 +0100 Subject: [PATCH 0454/1249] removed if statement, not needed --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 1ae584406a..fbcdd59632 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -122,9 +122,8 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): if _globals["suspendPublishJob"]: translation["suspendPublishJob"] = "Suspended" - if _globals["startFrame"] and _globals["endFrame"]: - frame_range = "{}-{}".format(_globals["startFrame"], - _globals["endFrame"]) - translation["renderGlobals"]["Frames"] = frame_range + frame_range = "{}-{}".format(_globals["startFrame"], + _globals["endFrame"]) + translation["renderGlobals"]["Frames"] = frame_range return translation From 4f44fe25462c21f3410196e8aa83eaf39655d65b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 8 Nov 2017 18:00:42 +0100 Subject: [PATCH 0455/1249] added DL submission settigns UI --- colorbleed/maya/menu.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index e29c231c79..e426020061 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -925,6 +925,25 @@ } ] }, + { + "type": "menu", + "title": "Rendering", + "items": [ + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", + "sourcetype": "file", + "tags": [ + "settings", + "deadline", + "globals", + "render" + ], + "title": "DL Submission Settings UI", + "tooltip": "Open the Deadline Submission Settings UI" + } + ] + }, { "type": "menu", "title": "Animation", From ff40ebaf714f622327d7bd9dd7cc1538313423ff Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 9 Nov 2017 10:58:54 +0100 Subject: [PATCH 0456/1249] Fix `is_latest` check --- colorbleed/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index e25bbfcafd..ea49fa54a4 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -23,7 +23,7 @@ def is_latest(representation): "parent": version["parent"] }, sort=[("name", -1)]) - if version['name'] != highest_version['name']: + if version['name'] == highest_version['name']: return True else: return False From 4ffa72ae65c635fdfb64026ad0d2660857b19ad4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 11:20:48 +0100 Subject: [PATCH 0457/1249] removed frames from renderGlobals --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index fbcdd59632..4e5c69c509 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -122,8 +122,4 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): if _globals["suspendPublishJob"]: translation["suspendPublishJob"] = "Suspended" - frame_range = "{}-{}".format(_globals["startFrame"], - _globals["endFrame"]) - translation["renderGlobals"]["Frames"] = frame_range - return translation From 61c0fa4983933f01fff9ffec1cdaee661f7cad5d Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 11:24:09 +0100 Subject: [PATCH 0458/1249] removed debug pprint --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 4e5c69c509..954c075d4b 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -87,8 +87,6 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): instance = context.create_instance(layername) instance.data.update(data) - pprint.pprint(instance.data) - def get_render_attribute(self, attr): return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) From 143626d240bbb14ac1042e5f0f94ebfa276a726d Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 11:44:25 +0100 Subject: [PATCH 0459/1249] removed frame attribute from instance, add locking of machinelist attr --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index c2e96d0103..4831419047 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -23,16 +23,11 @@ class CreateRenderGlobals(avalon.maya.Creator): data = OrderedDict(**self.data) - startframe = cmds.playbackOptions(query=True, animationStartTime=True) - endframe = cmds.playbackOptions(query=True, animationEndTime=True) - data["suspendPublishJob"] = False data["includeDefaultRenderLayer"] = False data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" - data["startFrame"] = int(startframe) - data["endFrame"] = int(endframe) self.data = data self.options = {"useSelection": False} # Force no content @@ -47,3 +42,5 @@ class CreateRenderGlobals(avalon.maya.Creator): return cmds.warning("%s already exists." % exists[0]) super(CreateRenderGlobals, self).process() + + cmds.setAttr("{}.machineList".format(self.name), lock=True) From 8c15c38c9f381c6999aeb6078dafed93d0e18c56 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 11:45:00 +0100 Subject: [PATCH 0460/1249] removed unused import --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 954c075d4b..fda32f4b37 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,5 +1,3 @@ -import pprint - from maya import cmds import pyblish.api From 182522103ae4767938ad1eb6a0510393e6c4af77 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 9 Nov 2017 12:33:37 +0100 Subject: [PATCH 0461/1249] Add repair faulty containers script to menu.json --- colorbleed/maya/menu.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index e29c231c79..da3b59e9c7 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1480,6 +1480,16 @@ "title": "Cleanup", "items": [ { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\repair_faulty_containers.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "repair", "containers" + ], + "title": "Find and Repair Containers", + "tooltip": "" + },{ "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectByType.py", "sourcetype": "file", From 34ad135af70cc45d53d04383fa58db4546882a87 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 12:56:49 +0100 Subject: [PATCH 0462/1249] translating active to publish due to Pyblish logic --- colorbleed/plugins/maya/publish/collect_instances.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 08779fe378..c233341d48 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -81,9 +81,13 @@ class CollectInstances(pyblish.api.ContextPlugin): # are considered non-essential to this # particular publishing pipeline. value = None - data[attr] = value + # temporarily translation of `active` to `publish` till issue has + # been resolved, https://github.com/pyblish/pyblish-base/issues/307 + if "active" in data: + data["publish"] = data["active"] + # Collect members members = cmds.ls(members, long=True) or [] From 31f7242cd9fd6e301cb64560d49ea3c285c698fb Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 14:20:39 +0100 Subject: [PATCH 0463/1249] refactored translator methond to parse_options --- .../maya/publish/collect_renderlayers.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index fda32f4b37..71894d136b 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -79,7 +79,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): # Include (optional) global settings # TODO(marcus): Take into account layer overrides # Get global overrides and translate to Deadline values - overrides = self.translate_overrides(render_globals) + overrides = self.parse_options(render_globals) data.update(**overrides) instance = context.create_instance(layername) @@ -88,7 +88,7 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): def get_render_attribute(self, attr): return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) - def translate_overrides(self, render_globals): + def parse_options(self, render_globals): """Get all overrides with a value, skip those without Here's the kicker. These globals override defaults in the submission @@ -102,20 +102,20 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): dict: only overrides with values """ - _globals = maya.read(render_globals) + attributes = maya.read(render_globals) - machine_list = _globals["machineList"] - translation = {"renderGlobals": - {"Priority": _globals["priority"]}, - "suspendPublishJob": "Active" - } + options = {"renderGlobals": {}} - if _globals["whitelist"]: - translation["renderGlobals"]["Whitelist"] = machine_list - else: - translation["renderGlobals"]["Blacklist"] = machine_list + options['renderGlobals']['Priority'] = attributes['priority'] - if _globals["suspendPublishJob"]: - translation["suspendPublishJob"] = "Suspended" + # Machine list + machine_list = attributes["machineList"] + if machine_list: + key = "Whitelist" if attributes["whitelist"] else "Blacklist" + options['renderGlobals'][key] = machine_list - return translation + # Suspend publish job + state = "Suspended" if attributes["suspendPublishJob"] else "Active" + options["suspendPublishJob"] = state + + return options From 3e87af28505ff206c6fc7091112c3a057fd5b709 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 14:21:29 +0100 Subject: [PATCH 0464/1249] removed debug print --- colorbleed/plugins/maya/publish/submit_deadline.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 8f046f1c39..a0e6610744 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -2,7 +2,6 @@ import os import json import shutil import getpass -import pprint from maya import cmds @@ -198,8 +197,6 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): render_globals = instance.data.get("renderGlobals", {}) payload["JobInfo"].update(render_globals) - pprint.pprint(payload["JobInfo"]) - self.preflight_check(instance) self.log.info("Submitting..") From c513e1d6d5f261b47dd31e89f5bf9e3865a7ebee Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 15:03:25 +0100 Subject: [PATCH 0465/1249] remove active from data dict --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 4831419047..5ce04495cc 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -20,6 +20,7 @@ class CreateRenderGlobals(avalon.maya.Creator): # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) + self.data.pop("active", None) data = OrderedDict(**self.data) From 427c3da91a5388f6490028648c8eea287de6aa57 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 9 Nov 2017 16:19:11 +0100 Subject: [PATCH 0466/1249] replaced RuntimeError with info.log and return --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 71894d136b..56cbc6b148 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -27,8 +27,9 @@ class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): try: render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: - raise RuntimeError("Cannot collect renderlayers without " - "renderGlobals node") + self.log.info("Cannot collect renderlayers without " + "renderGlobals node") + return attr = "{}.includeDefaultRenderLayer".format(render_globals) use_defaultlayer = cmds.getAttr(attr) From b9206470a8ec4e11955ee02d0844a65e54bc9232 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 10 Nov 2017 14:03:34 +0100 Subject: [PATCH 0467/1249] added check for no root nodes --- colorbleed/plugins/maya/publish/collect_setdress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/colorbleed/plugins/maya/publish/collect_setdress.py index 0f405d5cf3..e5bddac887 100644 --- a/colorbleed/plugins/maya/publish/collect_setdress.py +++ b/colorbleed/plugins/maya/publish/collect_setdress.py @@ -40,7 +40,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): for container in containers: root = lib.get_container_transforms(container, root=True) - if root not in instance_lookup: + if not root or root not in instance_lookup: continue # Retrieve the hierarchy From c9a706fc5bdc3658cad2efc2bc753d4126ad05de Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 10 Nov 2017 16:26:43 +0100 Subject: [PATCH 0468/1249] Use `loaders_from_representation` from avalon.api --- colorbleed/maya/lib.py | 5 ++--- colorbleed/setdress_api.py | 18 +++++++++--------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 09032df09e..4c79e4bae5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -853,8 +853,6 @@ def assign_look_by_version(nodes, version_id): Returns: None """ - # TODO: make `iter_loader` available in as a more global function - from avalon.tools.cbloader import lib as loaderlib # Get representations of shader file and relationships look_representation = io.find_one({"type": "representation", @@ -878,7 +876,8 @@ def assign_look_by_version(nodes, version_id): log.info("Using look for the first time ..") # Load file - loaders = list(loaderlib.iter_loaders(look_representation["_id"])) + loaders = api.loaders_from_representation(api.discover(api.Loader), + representation_id) Loader = next((i for i in loaders if i.__name__ == "LookLoader"), None) if Loader is None: raise RuntimeError("Could not find LookLoader, this is a bug") diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index 698e520616..c4585037a6 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -85,8 +85,6 @@ def load_package(filepath, name, namespace=None): """ - from avalon.tools.cbloader import lib - if namespace is None: # Define a unique namespace for the package namespace = os.path.basename(filepath).split(".")[0] @@ -113,10 +111,12 @@ def load_package(filepath, name, namespace=None): root = "{}:{}".format(namespace, name) containers = [] + all_loaders = api.discover(api.Loader) for representation_id, instances in data.items(): # Find the compatible loaders - loaders = list(lib.iter_loaders(representation_id)) + loaders = api.loaders_from_representation(all_loaders, + representation_id) for instance in instances: container = _add(instance=instance, @@ -222,13 +222,13 @@ def _instances_by_namespace(data): def get_contained_containers(container): """Get the Avalon containers in this container - + Args: container (dict): The container dict. - + Returns: list: A list of member container dictionaries. - + """ import avalon.schema @@ -354,7 +354,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): """ from colorbleed.maya.lib import DEFAULT_MATRIX, get_container_transforms - from avalon.tools.cbloader import lib set_namespace = set_container['namespace'] @@ -469,11 +468,12 @@ def update_scene(set_container, containers, current_data, new_data, new_file): api.remove(container) # Add new assets + all_loaders = api.discover(api.Loader) for representation_id, instances in new_data.items(): # Find the compatible loaders - loaders = list(lib.iter_loaders(representation_id)) - + loaders = api.loaders_from_representation(all_loaders, + representation_id) for instance in instances: # Already processed in update functionality From 07590abc3c58c5b2d7953a662db127dbb001d7b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Nov 2017 21:57:51 +0100 Subject: [PATCH 0469/1249] Implement automatic context switch on Maya scene open and scene save This requires: https://github.com/getavalon/core/pull/300 --- colorbleed/lib.py | 40 ++++++- colorbleed/maya/__init__.py | 13 ++- colorbleed/vendor/__init__.py | 0 colorbleed/vendor/pather/__init__.py | 5 + colorbleed/vendor/pather/core.py | 168 +++++++++++++++++++++++++++ colorbleed/vendor/pather/error.py | 5 + colorbleed/vendor/pather/version.py | 10 ++ 7 files changed, 238 insertions(+), 3 deletions(-) create mode 100644 colorbleed/vendor/__init__.py create mode 100644 colorbleed/vendor/pather/__init__.py create mode 100644 colorbleed/vendor/pather/core.py create mode 100644 colorbleed/vendor/pather/error.py create mode 100644 colorbleed/vendor/pather/version.py diff --git a/colorbleed/lib.py b/colorbleed/lib.py index ea49fa54a4..04ea0cb092 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -1,6 +1,13 @@ +import logging + +from .vendor import pather +from .vendor.pather.error import ParseError + import avalon.io as io import avalon.api +log = logging.getLogger(__name__) + def is_latest(representation): """Return whether the representation is from latest version @@ -43,4 +50,35 @@ def any_outdated(): return True checked.add(representation) - return False \ No newline at end of file + return False + + +def update_context_from_path(path): + """Update the context using the current scene state. + + When no changes to the context it will not trigger an update. + When the context for a file could not be parsed an error is logged but not + raised. + + """ + if not path: + log.warning("Can't update the current context. Scene is not saved.") + return + + # Find the current context from the filename + project = io.find_one({"type": "project"}, + projection={"config.template.work": True}) + template = project['config']['template']['work'] + # Force to use the registered to root to avoid using wrong paths + template = pather.format(template, {"root": avalon.api.registered_root()}) + try: + context = pather.parse(template, path) + except ParseError: + log.error("Can't update the current context. Unable to parse the " + "context for: %s", path) + return + + if any([avalon.api.Session['AVALON_ASSET'] != context['asset'], + avalon.api.Session["AVALON_TASK"] != context['task']]): + log.info("Updating context to: %s", context) + avalon.api.update_current_context(context) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index d482f6751a..f3f1f66432 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -8,6 +8,10 @@ from maya import cmds from avalon import api as avalon from pyblish import api as pyblish +from ..lib import ( + update_context_from_path, + any_outdated +) from . import menu from . import lib @@ -88,6 +92,9 @@ def on_save(_): avalon.logger.info("Running callback on save..") + # Update context for the current scene + update_context_from_path(cmds.file(query=True, sceneName=True)) + # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) for node, new_id in lib.generate_ids(nodes): @@ -97,10 +104,12 @@ def on_save(_): def on_open(_): """On scene open let's assume the containers have changed.""" - from ..lib import any_outdated from avalon.vendor.Qt import QtWidgets from ..widgets import popup + # Update context for the current scene + update_context_from_path(cmds.file(query=True, sceneName=True)) + if any_outdated(): log.warning("Scene has outdated content.") @@ -124,4 +133,4 @@ def on_open(_): dialog.setMessage("There are outdated containers in " "your Maya scene.") dialog.on_show.connect(_on_show_inventory) - dialog.show() + dialog.show() \ No newline at end of file diff --git a/colorbleed/vendor/__init__.py b/colorbleed/vendor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/vendor/pather/__init__.py b/colorbleed/vendor/pather/__init__.py new file mode 100644 index 0000000000..91094e45a8 --- /dev/null +++ b/colorbleed/vendor/pather/__init__.py @@ -0,0 +1,5 @@ +__author__ = 'Roy Nieterau' + + +from .core import * +from .version import * diff --git a/colorbleed/vendor/pather/core.py b/colorbleed/vendor/pather/core.py new file mode 100644 index 0000000000..f2a469dc8b --- /dev/null +++ b/colorbleed/vendor/pather/core.py @@ -0,0 +1,168 @@ + +__all__ = ['parse', 'ls', 'ls_iter', 'format'] + +import os +import re +import string +import glob + +from .error import ParseError + +# Regex pattern that matches valid file +# TODO: Implement complete pattern if required +RE_FILENAME = '[-\w.,; \[\]]' + + +def format(pattern, data, allow_partial=True): + """Format a pattern with a set of data + + Examples: + + Full formatting + >>> format("{a}/{b}/{c}", {"a": "foo", "b": "bar", "c": "nugget"}) + 'foo/bar/nugget' + + Partial formatting + >>> format("{asset}/{character}", {"asset": "hero"}) + 'hero/{character}' + + Disallow partial formatting + >>> format("{asset}/{character}", {"asset": "hero"}, + ... allow_partial=False) + Traceback (most recent call last): + ... + KeyError: 'character' + + Args: + pattern (str): The pattern to format. + data (dict): The key, value pairs used for formatting. + allow_partial (bool): Whether to raise error on partial format. + + Returns: + str: The formatted result + """ + + assert isinstance(data, dict) + + if not all(isinstance(value, basestring) for value in data.values()): + raise TypeError("The values in the data " + "dictionary must be strings") + + if allow_partial: + return _partial_format(pattern, data) + else: + return pattern.format(**data) + + +def parse(pattern, path): + """Parse data from a path based on a pattern + + Example: + >>> pattern = "root/{task}/{version}/data/" + >>> path = "root/modeling/v001/data/" + >>> parse(pattern, path) + {'task': 'modeling', 'version': 'v001'} + + Returns: + dict: The data retrieved from path using pattern. + """ + + pattern = os.path.normpath(pattern) + path = os.path.normpath(path) + + # Force forward slashes + path = path.replace('\\', '/') + pattern = pattern.replace('\\', '/') + + # Escape characters in path that are regex patterns so they are + # excluded by the regex searches. Exclude '{' and '}' in escaping. + pattern = re.escape(pattern) + pattern = pattern.replace('\{', '{').replace('\}', '}') + + keys = re.findall(r'{(%s+)}' % RE_FILENAME, + pattern) + if not keys: + return [] + + # Find the corresponding values + value_pattern = re.sub(r'{(%s+)}' % RE_FILENAME, + r'(%s+)' % RE_FILENAME, + pattern) + match_values = re.match(value_pattern, path) + + if not match_values: + raise ParseError("Path doesn't match with pattern. No values parsed") + + values = match_values.groups() + + return dict(zip(keys, values)) + + +def ls_iter(pattern, include=None, with_matches=False): + """Yield all matches for the given pattern. + + If the pattern starts with a relative path (or a dynamic key) the search + will start from the current working directory, defined by os.path.realpath. + + Arguments: + pattern (str): The pattern to match and search against. + include (dict): A dictionary used to target the search with the pattern + to include only those key-value pairs within the pattern. With this + you can reduce the filesystem query to a specified subset. + + Example: + >>> data = {"root": "foobar", "content": "nugget"} + >>> for path in ls_iter("{root}/{project}/data/{content}/", + ... include=data): + ... print path + + Returns: + (str, tuple): The matched paths (and data if `with_matches` is True) + + The returned value changes whether `with_matches` parameter is True or + False. If True a 2-tuple is yielded for each match as (path, data) else + only the path is returned + """ + + # format rule by data already provided to reduce query + if include is not None: + pattern = format(pattern, include, allow_partial=True) + + pattern = os.path.expandvars(pattern) + pattern = os.path.realpath(pattern) + + glob_pattern = re.sub(r'([/\\]{\w+}[/\\])', '/*/', pattern) # folder + glob_pattern = re.sub(r'({\w+})', '*', glob_pattern) # filename + + for path in glob.iglob(glob_pattern): + path = os.path.realpath(path) + if with_matches: + data = parse(pattern, path) + yield path, data + else: + yield path + + +def ls(pattern, include=None, with_matches=False): + return list(ls_iter(pattern, include, with_matches=with_matches)) + + +def _partial_format(s, data): + """Return string `s` formatted by `data` allowing a partial format + + Arguments: + s (str): The string that will be formatted + data (dict): The dictionary used to format with. + + Example: + >>> _partial_format("{d} {a} {b} {c} {d}", {'b': "and", 'd': "left"}) + 'left {a} and {c} left' + """ + + class FormatDict(dict): + def __missing__(self, key): + return "{" + key + "}" + + formatter = string.Formatter() + mapping = FormatDict(**data) + return formatter.vformat(s, (), mapping) diff --git a/colorbleed/vendor/pather/error.py b/colorbleed/vendor/pather/error.py new file mode 100644 index 0000000000..92006534d4 --- /dev/null +++ b/colorbleed/vendor/pather/error.py @@ -0,0 +1,5 @@ + + +class ParseError(ValueError): + """Error raised when parsing a path with a pattern fails""" + pass diff --git a/colorbleed/vendor/pather/version.py b/colorbleed/vendor/pather/version.py new file mode 100644 index 0000000000..85f96b1e3f --- /dev/null +++ b/colorbleed/vendor/pather/version.py @@ -0,0 +1,10 @@ + +VERSION_MAJOR = 0 +VERSION_MINOR = 1 +VERSION_PATCH = 0 + +version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) +version = '%i.%i.%i' % version_info +__version__ = version + +__all__ = ['version', 'version_info', '__version__'] From b541694bf656fc809e63258f5e67ce68d0a54d9c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Nov 2017 15:50:44 +0100 Subject: [PATCH 0470/1249] Refactor update context to update task --- colorbleed/lib.py | 24 ++++++++++++++++-------- colorbleed/maya/__init__.py | 10 +++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 04ea0cb092..0586098489 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -53,7 +53,7 @@ def any_outdated(): return False -def update_context_from_path(path): +def update_task_from_path(path): """Update the context using the current scene state. When no changes to the context it will not trigger an update. @@ -62,7 +62,7 @@ def update_context_from_path(path): """ if not path: - log.warning("Can't update the current context. Scene is not saved.") + log.warning("Can't update the current task. Scene is not saved.") return # Find the current context from the filename @@ -74,11 +74,19 @@ def update_context_from_path(path): try: context = pather.parse(template, path) except ParseError: - log.error("Can't update the current context. Unable to parse the " - "context for: %s", path) + log.error("Can't update the current task. Unable to parse the " + "task for: %s", path) return - if any([avalon.api.Session['AVALON_ASSET'] != context['asset'], - avalon.api.Session["AVALON_TASK"] != context['task']]): - log.info("Updating context to: %s", context) - avalon.api.update_current_context(context) + # Find the changes between current Session and the path's context. + current = { + "asset": avalon.api.Session["AVALON_ASSET"], + "task": avalon.api.Session["AVALON_TASK"], + "app": avalon.api.Session["AVALON_APP"] + } + changes = {key: context[key] for key, current_value in current.items() + if context[key] != current_value} + + if changes: + log.info("Updating work task to: %s", context) + avalon.api.update_current_task(**changes) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index f3f1f66432..7426aac905 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -9,7 +9,7 @@ from avalon import api as avalon from pyblish import api as pyblish from ..lib import ( - update_context_from_path, + update_task_from_path, any_outdated ) from . import menu @@ -92,8 +92,8 @@ def on_save(_): avalon.logger.info("Running callback on save..") - # Update context for the current scene - update_context_from_path(cmds.file(query=True, sceneName=True)) + # Update current task for the current scene + update_task_from_path(cmds.file(query=True, sceneName=True)) # Generate ids of the current context on nodes in the scene nodes = lib.get_id_required_nodes(referenced_nodes=False) @@ -107,8 +107,8 @@ def on_open(_): from avalon.vendor.Qt import QtWidgets from ..widgets import popup - # Update context for the current scene - update_context_from_path(cmds.file(query=True, sceneName=True)) + # Update current task for the current scene + update_task_from_path(cmds.file(query=True, sceneName=True)) if any_outdated(): log.warning("Scene has outdated content.") From 9889ef39bab8c4b83ff5fd17e789cfc8cd8d2170 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2017 10:53:20 +0100 Subject: [PATCH 0471/1249] Include template in logged message --- colorbleed/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 0586098489..395a4fd5ca 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -75,7 +75,7 @@ def update_task_from_path(path): context = pather.parse(template, path) except ParseError: log.error("Can't update the current task. Unable to parse the " - "task for: %s", path) + "task for: %s (pattern: %s)", path, template) return # Find the changes between current Session and the path's context. From b18eb9a09bb4c8f40245726649415bc0d24d35ba Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Nov 2017 16:55:33 +0100 Subject: [PATCH 0472/1249] Implement SET-3: allow switching between loader for a namespaces (it will lose local edits during update) - Also fix warning being locked when `unlocked` context manager couldn't find a node by id anymore (those from the alembic hierarchy being replaced - now it will also try to search by full dag path. This should still be improved later. --- colorbleed/setdress_api.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/colorbleed/setdress_api.py b/colorbleed/setdress_api.py index c4585037a6..bc6c67aacc 100644 --- a/colorbleed/setdress_api.py +++ b/colorbleed/setdress_api.py @@ -63,6 +63,7 @@ def unlocked(nodes): uuids = cmds.ls(nodes, uuid=True) states = cmds.lockNode(nodes, query=True, lock=True) states = {uuid: state for uuid, state in zip(uuids, states)} + originals = {uuid: node for uuid, node in zip(uuids, nodes)} try: cmds.lockNode(nodes, lock=False) @@ -71,10 +72,15 @@ def unlocked(nodes): # Reapply original states for uuid, state in states.iteritems(): nodes_from_id = cmds.ls(uuid, long=True) - if not nodes_from_id: - log.warning("Node not found: %s", uuid) - continue - cmds.lockNode(nodes_from_id[0], lock=state) + if nodes_from_id: + node = nodes_from_id[0] + else: + log.debug("Falling back to node name: %s", node) + node = originals[uuid] + if not cmds.objExists(node): + log.warning("Unable to find: %s", node) + continue + cmds.lockNode(node, lock=state) def load_package(filepath, name, namespace=None): @@ -406,7 +412,6 @@ def update_scene(set_container, containers, current_data, new_data, new_file): if has_matrix_override: log.warning("Matrix override preserved on %s", container_ns) - else: new_matrix = new_instance.get("matrix", identity) cmds.xform(root, matrix=new_matrix, objectSpace=True) @@ -444,8 +449,15 @@ def update_scene(set_container, containers, current_data, new_data, new_file): # of the original data of the package that was loaded because # an Artist might have made scene local overrides if new_instance['loader'] != container['loader']: - log.error("Switching loader between updates is not " - "supported. Skipping: %s", container_ns) + log.warning("Loader is switched - local edits will be " + "lost. Removing: %s", + container_ns) + + # Remove this from the "has been processed" list so it's + # considered as new element and added afterwards. + processed_containers.pop() + processed_namespaces.remove(container_ns) + api.remove(container) continue # Check whether the conversion can be done by the Loader. From a8b8a513b14e6a2cf75dfd530680110c7ada5a25 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 11:46:43 +0100 Subject: [PATCH 0473/1249] Reorder cleanup scripts and add "remove unused looks" --- colorbleed/maya/menu.json | 140 +++++++++++++++++++++----------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 94aa716c13..7a7973dcdc 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1539,6 +1539,81 @@ "tooltip": "" }, {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeNamespaces.py", + "sourcetype": "file", + "tags": ["cleanup", "remove", "namespaces"], + "title": "Remove Namespaces", + "tooltip": "Remove all namespaces" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "remove_user_defined_attributes" + ], + "title": "Remove User Defined Attributes", + "tooltip": "Remove all user-defined attributs from all nodes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnknownNodes" + ], + "title": "Remove Unknown Nodes", + "tooltip": "Remove all unknown nodes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnloadedReferences" + ], + "title": "Remove Unloaded References", + "tooltip": "Remove all unloaded references" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeReferencesFailedEdits" + ], + "title": "Remove References Failed Edits", + "tooltip": "Remove failed edits for all references" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_unused_looks.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "removeUnusedLooks" + ], + "title": "Remove Unused Looks", + "tooltip": "Remove all loaded yet unused Avalon look containers" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", + "sourcetype": "file", + "tags": [ + "cleanup", + "deleteGhostIntermediateObjects" + ], + "title": "Delete Ghost Intermediate Objects", + "tooltip": "" + }, + {"type": "separator"}, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\resetViewportCache.py", @@ -1558,14 +1633,6 @@ "title": "Uniqify Node Names", "tooltip": "" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeNamespaces.py", - "sourcetype": "file", - "tags": ["cleanup", "remove", "namespaces"], - "title": "Remove Namespaces", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\autoRenameFileNodes.py", @@ -1577,17 +1644,6 @@ "title": "Auto Rename File Nodes", "tooltip": "" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "remove_user_defined_attributes" - ], - "title": "Remove User Defined Attributes", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\update_asset_id.py", @@ -1596,35 +1652,13 @@ "title": "Update Asset ID", "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnknownNodes" - ], - "title": "removeUnknownNodes", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\colorbleedRename.py", "sourcetype": "file", "tags": ["cleanup", "rename", "ui"], "title": "Colorbleed Renamer", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnloadedReferences" - ], - "title": "Remove Unloaded References", - "tooltip": "" + "tooltip": "Colorbleed Rename UI" }, { "type": "action", @@ -1648,17 +1682,6 @@ "title": "Rename Shapes To Transform", "tooltip": "" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeReferencesFailedEdits" - ], - "title": "Remove References Failed Edits", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\reorderUI.py", @@ -1680,17 +1703,6 @@ ], "title": "Pasted Cleaner", "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "deleteGhostIntermediateObjects" - ], - "title": "Delete Ghost Intermediate Objects", - "tooltip": "" } ] }, From b31d4a3e3f885e4f2714e261f72d88772f4b3798 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 12:05:39 +0100 Subject: [PATCH 0474/1249] Remove reference edits UI script because it's not implemented --- colorbleed/maya/menu.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 7a7973dcdc..e7a47ff2ce 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1660,17 +1660,6 @@ "title": "Colorbleed Renamer", "tooltip": "Colorbleed Rename UI" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\referenceEditsUI.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "referenceEditsUI" - ], - "title": "Reference Edits UI", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\renameShapesToTransform.py", From 64a07d2fc05024aaee7181dacaf18ae8c372db26 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 13:43:50 +0100 Subject: [PATCH 0475/1249] Add "Explore current scene.." script to menu --- colorbleed/maya/menu.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index e7a47ff2ce..bf42e454ee 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -6,6 +6,13 @@ "title": "Version Up", "tooltip": "Incremental save with a specific format" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\show_current_scene_in_explorer.py", + "sourcetype": "file", + "title": "Explore current scene..", + "tooltip": "Show current scene in Explorer" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\avalon\\launch_manager.py", From 6f5bd560a778f935e9f8134b94abc4344466cce7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 16:49:19 +0100 Subject: [PATCH 0476/1249] Fix collections import --- .../plugins/maya/publish/validate_setdress_namespaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py index 09015faddd..90246c216c 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py @@ -1,5 +1,5 @@ import pyblish.api -from collection import defaultdict +from collections import defaultdict class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): @@ -32,4 +32,4 @@ class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): if len(parts) > 1: invalid.extend(nodes) - return invalid \ No newline at end of file + return invalid From 12c183744bddaca66cc9885e0a4dd473d890de47 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 17:27:36 +0100 Subject: [PATCH 0477/1249] Simplify class name (remove old Mindbender prefix) --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 56cbc6b148..ffe84e2234 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -6,7 +6,7 @@ from avalon import maya, api import colorbleed.maya.lib as lib -class CollectMindbenderMayaRenderlayers(pyblish.api.ContextPlugin): +class CollectMayaRenderlayers(pyblish.api.ContextPlugin): """Gather instances by active render layers""" order = pyblish.api.CollectorOrder From 149fcf9914ea200844085e40039ebf9bcc1a9734 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 20:48:37 +0100 Subject: [PATCH 0478/1249] Fix `resetPolySelectConstraint` being available in mel in batch mode --- colorbleed/maya/lib.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 4c79e4bae5..3b2e8f430c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -357,6 +357,11 @@ def reset_polySelectConstraint(reset=True): try: if reset: + # Ensure command is available in mel + # This can happen when running standalone + if not mel.eval("exists resetPolySelectConstraint"): + mel.eval("source polygonConstraint") + # Reset all parameters mel.eval("resetPolySelectConstraint;") cmds.polySelectConstraint(disable=True) From eb82abcc72462714c522a63ec5edfe2cde2a681e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 21:45:51 +0100 Subject: [PATCH 0479/1249] Skip internal sets from maya.cmds.listSets (fix `no object matches name`) and change list constants to set constants --- colorbleed/maya/lib.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 3b2e8f430c..83a2e7bf1f 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1059,28 +1059,29 @@ def get_related_sets(node): ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] # Default nodes to ignore - defaults = ["initialShadingGroup", "defaultLightSet", "defaultObjectSet"] + defaults = {"initialShadingGroup", "defaultLightSet", "defaultObjectSet"} # Ids to ignore - ignored = ["pyblish.avalon.instance", "pyblish.avalon.container"] + ignored = {"pyblish.avalon.instance", "pyblish.avalon.container"} view_sets = get_isolate_view_sets() - related_sets = cmds.listSets(object=node, extendToShape=False) - if not related_sets: + sets = cmds.listSets(object=node, extendToShape=False) + if not sets: return [] + # Fix 'no object matches name' errors on nodes returned by listSets. + # In rare cases it can happen that a node is added to an internal maya + # set inaccessible by maya commands, for example check some nodes + # returned by `cmds.listSets(allSets=True)` + sets = cmds.ls(sets) + # Ignore `avalon.container` - sets = [s for s in related_sets if + sets = [s for s in sets if not cmds.attributeQuery("id", node=s, exists=True) or not cmds.getAttr("%s.id" % s) in ignored] - # Exclude deformer sets - # Autodesk documentation on listSets command: - # type(uint) : Returns all sets in the scene of the given - # >>> type: - # >>> 1 - all rendering sets - # >>> 2 - all deformer sets + # Exclude deformer sets (`type=2` for `maya.cmds.listSets`) deformer_sets = cmds.listSets(object=node, extendToShape=False, type=2) or [] From 49e309875a63f4d28d59684412e91cd732b83eda Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Nov 2017 22:39:34 +0100 Subject: [PATCH 0480/1249] Fix getPanel command returning None in batch mode --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 83a2e7bf1f..da66016ec9 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1030,7 +1030,7 @@ def get_isolate_view_sets(): """ view_sets = set() - for panel in cmds.getPanel(type="modelPanel"): + for panel in cmds.getPanel(type="modelPanel") or []: view_set = cmds.modelEditor(panel, query=True, viewObjects=True) if view_set: view_sets.add(view_set) From 6d396243a6255e6e58343bf4ed1bc858936c0931 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 22 Nov 2017 09:59:39 +0100 Subject: [PATCH 0481/1249] added force flag to cut all connections to objectset / vray sets --- colorbleed/plugins/maya/publish/extract_look.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 4e4ffd9938..43082b6669 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -41,7 +41,8 @@ class ExtractLook(colorbleed.api.Extractor): # exported file by accident self.log.info("Extract sets (Maya ASCII) ...") lookdata = instance.data["lookData"] - sets = lookdata["relationships"].keys() + relationships = lookdata["relationships"] + sets = relationships.keys() resources = instance.data["resources"] remap = {} @@ -55,7 +56,7 @@ class ExtractLook(colorbleed.api.Extractor): layer = instance.data.get("renderlayer", "defaultRenderLayer") with context.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides - with context.empty_sets(sets): + with context.empty_sets(sets, force=True): with context.attribute_values(remap): with avalon.maya.maintained_selection(): cmds.select(sets, noExpand=True) @@ -72,7 +73,7 @@ class ExtractLook(colorbleed.api.Extractor): # Write the JSON data self.log.info("Extract json..") data = {"attributes": lookdata["attributes"], - "relationships": lookdata["relationships"]} + "relationships": relationships} with open(json_path, "w") as f: json.dump(data, f) From d06b1b531aeb53fd2714ba06bf85c4db45c06f50 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 22 Nov 2017 15:34:13 +0100 Subject: [PATCH 0482/1249] updated menu entry --- colorbleed/maya/menu.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index bf42e454ee..91e2054542 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -817,17 +817,17 @@ }, { "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\assign_look_ui.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\look_manager_ui.py", "sourcetype": "file", "tags": [ "shading", - "lookdev", + "look", "assign", "shaders", "auto" ], - "title": "Assign Look UI", - "tooltip": "Open the Assign Look UI for custom look assignment" + "title": "Look Manager", + "tooltip": "Open the Look Manager UI for look assignment" }, { "type": "action", From 6305fa37bfd3637866f7d33adcfae116578d5de2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 22 Nov 2017 15:35:35 +0100 Subject: [PATCH 0483/1249] removed assign default action --- colorbleed/maya/menu.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 91e2054542..9748d257f3 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -829,20 +829,6 @@ "title": "Look Manager", "tooltip": "Open the Look Manager UI for look assignment" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\autoLookdevAssignment.py", - "sourcetype": "file", - "tags": [ - "shading", - "lookdev", - "assign", - "shaders", - "auto" - ], - "title": "Assign lookDefault Shader", - "tooltip": "Assign the latest `lookDefault` to assets without any lookdev in the scene" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", From b330653f9dea86532157ca3e5f41b8a578557f8f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 24 Nov 2017 14:34:42 +0100 Subject: [PATCH 0484/1249] Refactor validate rig controllers to check explicitly for non-default attribute values and incoming connections for unlocked attributes --- .../maya/publish/validate_rig_controllers.py | 214 ++++++++++++------ 1 file changed, 147 insertions(+), 67 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index f504a0f47e..9ecf6fefe5 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -1,17 +1,27 @@ -import logging - from maya import cmds import pyblish.api import colorbleed.api -import colorbleed.maya.lib as lib - -log = logging.getLogger("Rig Controllers") +from cb.utils.maya.context import undo_chunk class ValidateRigControllers(pyblish.api.InstancePlugin): - """Check if the controllers have the transformation attributes set to - default values, locked visibility attributes and are not keyed + """Validate rig controllers. + + Controls must have the transformation attributes on their default + values of translate zero, rotate zero and scale one when they are + unlocked attributes. + + Unlocked keyable attributes may not have any incoming connections. If + these connections are required for the rig then lock the attributes. + + The visibility attribute must be locked. + + Note that `repair` will: + - Lock all visibility attributes + - Reset all default values for translate, rotate, scale + - Break all incoming connections to keyable attributes + """ order = colorbleed.api.ValidateContentsOrder + 0.05 label = "Rig Controllers" @@ -26,96 +36,166 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): raise RuntimeError('{} failed, see log ' 'information'.format(self.label)) + # Default controller values + CONTROLLER_DEFAULTS = { + "translateX": 0, + "translateY": 0, + "translateZ": 0, + "rotateX": 0, + "rotateY": 0, + "rotateZ": 0, + "scaleX": 1, + "scaleY": 1, + "scaleZ": 1 + } + @classmethod def get_invalid(cls, instance): - error = False - invalid = [] - is_keyed = list() - not_locked = list() - is_offset = list() - controllers_sets = [i for i in instance if i == "controls_SET"] controls = cmds.sets(controllers_sets, query=True) assert controls, "Must have 'controls_SET' in rig instance" + # Ensure all controls are within the top group + lookup = set(instance[:]) + assert all(control in lookup for control in cmds.ls(controls, + long=True)), ( + "All controls must be inside the rig's group." + ) + + # Validate all controls + has_connections = list() + has_unlocked_visibility = list() + has_non_default_values = list() for control in controls: - valid_keyed = cls.validate_keyed_state(control) - if not valid_keyed: - is_keyed.append(control) + if cls.get_connected_attributes(control): + has_connections.append(control) # check if visibility is locked attribute = "{}.visibility".format(control) locked = cmds.getAttr(attribute, lock=True) if not locked: - not_locked.append(control) + has_unlocked_visibility.append(control) - valid_transforms = cls.validate_transforms(control) - if not valid_transforms: - is_offset.append(control) + if cls.get_non_default_attributes(control): + has_non_default_values.append(control) - if is_keyed: - cls.log.error("No controls can be keyes. Failed :\n" - "%s" % is_keyed) - error = True + if has_connections: + cls.log.error("Controls have keys: %s" % has_connections) - if is_offset: - cls.log.error("All controls default transformation values. " - "Failed :\n%s" % is_offset) - error = True + if has_non_default_values: + cls.log.error("Controls have invalid attribute " + "values: %s" % has_non_default_values) - if not_locked: - cls.log.error("All controls must have visibility " - "attribute locked. Failed :\n" - "%s" % not_locked) - error = True + if has_unlocked_visibility: + cls.log.error("Controls have unlocked visibility " + "attribute: %s" % has_unlocked_visibility) - if error: - invalid = is_keyed + not_locked + is_offset + invalid = [] + if (has_connections or + has_unlocked_visibility or + has_non_default_values): + invalid = set() + invalid.update(has_connections) + invalid.update(has_non_default_values) + invalid.update(has_unlocked_visibility) + invalid = list(invalid) cls.log.error("Invalid rig controllers. See log for details.") return invalid - @staticmethod - def validate_transforms(control): - tolerance = 1e-30 + @classmethod + def get_non_default_attributes(cls, control): + """Return attribute plugs with non-default values - matrix = cmds.xform(control, query=True, matrix=True, objectSpace=True) - if not all(abs(x - y) < tolerance for x, y in zip(lib.DEFAULT_MATRIX, - matrix)): - log.error("%s matrix : %s" % (control, matrix)) - return False - return True - - @staticmethod - def validate_keyed_state(control): - """Check if the control has an animation curve attached Args: - control: + control (str): Name of control node. Returns: + list: The invalid plugs """ - animation_curves = cmds.keyframe(control, query=True, name=True) - if animation_curves: - return False - return True + + invalid = [] + for attr, default in cls.CONTROLLER_DEFAULTS.items(): + if cmds.attributeQuery(attr, node=control, exists=True): + plug = "{}.{}".format(control, attr) + + # Ignore locked attributes + locked = cmds.getAttr(plug, lock=True) + if locked: + continue + + value = cmds.getAttr(plug) + if value != default: + cls.log.warning("Control non-default value: " + "%s = %s" % (plug, value)) + invalid.append(plug) + + return invalid + + @staticmethod + def get_connected_attributes(control): + """Return attribute plugs with incoming connections. + + This will also ensure no (driven) keys on unlocked keyable attributes. + + Args: + control (str): Name of control node. + + Returns: + list: The invalid plugs + + """ + import maya.cmds as mc + + attributes = mc.listAttr(control, keyable=True, scalar=True) + invalid = [] + for attr in attributes: + plug = "{}.{}".format(control, attr) + + # Ignore locked attributes + locked = cmds.getAttr(plug, lock=True) + if locked: + continue + + # Check for incoming connections + if cmds.listConnections(plug, source=True, destination=False): + invalid.append(plug) + + return invalid @classmethod def repair(cls, instance): - # lock all controllers in controls_SET - controls = cmds.sets("controls_SET", query=True) - for control in controls: - log.info("Repairing visibility") - attr = "{}.visibility".format(control) - locked = cmds.getAttr(attr, lock=True) - if not locked: - log.info("Locking visibility for %s" % control) - cmds.setAttr(attr, lock=True) + # Use a single undo chunk + with undo_chunk(): + controls = cmds.sets("controls_SET", query=True) + for control in controls: - log.info("Repairing matrix") - if not cls.validate_transforms(control): - cmds.xform(control, - matrix=lib.DEFAULT_MATRIX, - objectSpace=True) + # Lock visibility + attr = "{}.visibility".format(control) + locked = cmds.getAttr(attr, lock=True) + if not locked: + cls.log.info("Locking visibility for %s" % control) + cmds.setAttr(attr, lock=True) + + # Reset non-default values + invalid_plugs = cls.get_non_default_attributes(control) + if invalid_plugs: + for plug in invalid_plugs: + attr = plug.split(".")[-1] + default = cls.CONTROLLER_DEFAULTS[attr] + cls.log.info("Setting %s to %s" % (plug, default)) + cmds.setAttr(plug, default) + + # Remove incoming connections + invalid_plugs = cls.get_connected_attributes(control) + if invalid_plugs: + for plug in invalid_plugs: + cls.log.info("Breaking input connection to %s" % plug) + source = cmds.listConnections(plug, + source=True, + destination=False, + plugs=True)[0] + cmds.disconnectAttr(source, plug) From 279c4bb5e103b30e990b0e0dd20d0310203551fd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 24 Nov 2017 16:04:03 +0100 Subject: [PATCH 0485/1249] Improve log message --- .../plugins/maya/publish/validate_rig_controllers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 9ecf6fefe5..d4072707cf 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -81,11 +81,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): has_non_default_values.append(control) if has_connections: - cls.log.error("Controls have keys: %s" % has_connections) + cls.log.error("Controls have input connections: " + "%s" % has_connections) if has_non_default_values: - cls.log.error("Controls have invalid attribute " - "values: %s" % has_non_default_values) + cls.log.error("Controls have non-default values: " + "%s" % has_non_default_values) if has_unlocked_visibility: cls.log.error("Controls have unlocked visibility " From 9015820c36290f9d052c33d75b6ddd6b5aac91bb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 24 Nov 2017 16:49:10 +0100 Subject: [PATCH 0486/1249] On repair first remove incoming connections (if any) and then reset values afterwards so they can actually be set. --- .../maya/publish/validate_rig_controllers.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index d4072707cf..4e71699dab 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -181,15 +181,6 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): cls.log.info("Locking visibility for %s" % control) cmds.setAttr(attr, lock=True) - # Reset non-default values - invalid_plugs = cls.get_non_default_attributes(control) - if invalid_plugs: - for plug in invalid_plugs: - attr = plug.split(".")[-1] - default = cls.CONTROLLER_DEFAULTS[attr] - cls.log.info("Setting %s to %s" % (plug, default)) - cmds.setAttr(plug, default) - # Remove incoming connections invalid_plugs = cls.get_connected_attributes(control) if invalid_plugs: @@ -200,3 +191,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): destination=False, plugs=True)[0] cmds.disconnectAttr(source, plug) + + # Reset non-default values + invalid_plugs = cls.get_non_default_attributes(control) + if invalid_plugs: + for plug in invalid_plugs: + attr = plug.split(".")[-1] + default = cls.CONTROLLER_DEFAULTS[attr] + cls.log.info("Setting %s to %s" % (plug, default)) + cmds.setAttr(plug, default) \ No newline at end of file From b57b1ce02ae34b43ee34ab8929fde2cd7e9927db Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 24 Nov 2017 16:50:00 +0100 Subject: [PATCH 0487/1249] Cosmetics, move class constants actually to top --- .../maya/publish/validate_rig_controllers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 4e71699dab..2ae799fa71 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -30,12 +30,6 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): actions = [colorbleed.api.RepairAction, colorbleed.api.SelectInvalidAction] - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError('{} failed, see log ' - 'information'.format(self.label)) - # Default controller values CONTROLLER_DEFAULTS = { "translateX": 0, @@ -49,6 +43,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): "scaleZ": 1 } + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError('{} failed, see log ' + 'information'.format(self.label)) + @classmethod def get_invalid(cls, instance): @@ -199,4 +199,4 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): attr = plug.split(".")[-1] default = cls.CONTROLLER_DEFAULTS[attr] cls.log.info("Setting %s to %s" % (plug, default)) - cmds.setAttr(plug, default) \ No newline at end of file + cmds.setAttr(plug, default) From ba8b4140997a548b51d801866d0ed9dcaa9521fb Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 28 Nov 2017 10:59:56 +0100 Subject: [PATCH 0488/1249] added check for Yeti being loaded --- colorbleed/maya/lib.py | 5 + .../maya/create/colorbleed_yeti_cache.py | 0 .../maya/create/colorbleed_yeti_rig.py | 15 ++ .../plugins/maya/load/load_yeti_cache.py | 0 .../maya/publish/_validate_yeti_procedural.py | 23 +++ .../maya/publish/collect_yeti_cache.py | 159 ++++++++++++++++++ .../plugins/maya/publish/collect_yeti_rig.py | 131 +++++++++++++++ .../maya/publish/extract_yeti_cache.py | 92 ++++++++++ .../plugins/maya/publish/extract_yeti_rig.py | 62 +++++++ 9 files changed, 487 insertions(+) create mode 100644 colorbleed/plugins/maya/create/colorbleed_yeti_cache.py create mode 100644 colorbleed/plugins/maya/create/colorbleed_yeti_rig.py create mode 100644 colorbleed/plugins/maya/load/load_yeti_cache.py create mode 100644 colorbleed/plugins/maya/publish/_validate_yeti_procedural.py create mode 100644 colorbleed/plugins/maya/publish/collect_yeti_cache.py create mode 100644 colorbleed/plugins/maya/publish/collect_yeti_rig.py create mode 100644 colorbleed/plugins/maya/publish/extract_yeti_cache.py create mode 100644 colorbleed/plugins/maya/publish/extract_yeti_rig.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 4c79e4bae5..a73ea80c78 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -628,6 +628,11 @@ def get_id_required_nodes(referenced_nodes=False): # Establish set of nodes types to include types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] + # Check if plugin nodes are available for Maya by checking if the plugin + # is loaded + if cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): + types.append("pgYetiMaya") + # We *always* ignore intermediate shapes, so we filter them out # directly nodes = cmds.ls(type=types, long=True, noIntermediate=True) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py new file mode 100644 index 0000000000..5da695797a --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py @@ -0,0 +1,15 @@ +import avalon.maya + + +class CreateYetiRig(avalon.maya.Creator): + """Output for procedural plugin nodes ( Yeti / XGen / etc)""" + + name = "yetiDefault" + label = "Procedural" + family = "colorbleed.yetirig" + icon = "usb" + + def __init__(self, *args, **kwargs): + super(CreateYetiRig, self).__init__(*args, **kwargs) + + self.data["preroll"] = 0 diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py b/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py new file mode 100644 index 0000000000..4d4181256a --- /dev/null +++ b/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py @@ -0,0 +1,23 @@ +import pyblish.api +import colorbleed.api + + +class ValidateYetiProcedurals(pyblish.api.InstancePlugin): + """Validates mapped resources. + + These are external files to the current application, for example + these could be textures, image planes, cache files or other linked + media. + + This validates: + - The resources are existing files. + - The resources have correctly collected the data. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Yeti Procedurals" + + def process(self, instance): + return True + diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/colorbleed/plugins/maya/publish/collect_yeti_cache.py new file mode 100644 index 0000000000..e2eebb9377 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_yeti_cache.py @@ -0,0 +1,159 @@ +import os +import glob +import re + +from maya import cmds + +import pyblish.api +from avalon import api + +from colorbleed.maya import lib + + +SETTINGS = {"renderDensity", + "renderWidth", + "renderLength", + "increaseRenderBounds", + "cbId"} + + +class CollectYetiRig(pyblish.api.InstancePlugin): + """Collect all information of the Yeti Rig""" + + order = pyblish.api.CollectorOrder + 0.4 + label = "Collect Yeti Rig" + families = ["colorbleed.yetiRig"] + hosts = ["maya"] + + def process(self, instance): + + assert "input_SET" in cmds.sets(instance.name, query=True), ( + "Yeti Rig must have an input_SET") + + # Collect animation data + animation_data = lib.collect_animation_data() + instance.data.update(animation_data) + + # We only want one frame to export if it is not animation + if api.Session["AVALON_TASK"] != "animation": + instance.data["startFrame"] = 1 + instance.data["endFrame"] = 1 + + # Get the input meshes information + input_content = cmds.sets("input_SET", query=True) + input_nodes = cmds.listRelatives(input_content, + allDescendents=True, + fullPath=True) or [] + + # Get all the shapes + input_meshes = cmds.ls(input_nodes, type="shape", long=True) + + inputs = [] + for mesh in input_meshes: + connections = cmds.listConnections(mesh, + source=True, + destination=False, + connections=True, + plugs=True, + type="mesh") + source = connections[-1].split(".")[0] + plugs = [i.split(".")[-1] for i in connections] + inputs.append({"connections": plugs, + "inputID": lib.get_id(mesh), + "outputID": lib.get_id(source)}) + + # Collect any textures if used + node_attrs = {} + yeti_resources = [] + for node in cmds.ls(instance[:], type="pgYetiMaya"): + # Get Yeti resources (textures) + # TODO: referenced files in Yeti Graph + resources = self.get_yeti_resources(node) + yeti_resources.extend(resources) + + for attr in SETTINGS: + node_attr = "%s.%s" % (node, attr) + current = cmds.getAttr(node_attr) + node_attrs[node_attr] = current + + instance.data["inputs"] = inputs + instance.data["settings"] = node_attrs + instance.data["resources"] = yeti_resources + + def get_yeti_resources(self, node): + """Get all texture file paths + + If a texture is a sequence it gathers all sibling files to ensure + the texture sequence is complete. + + Args: + node (str): node name of the pgYetiMaya node + + Returns: + list + """ + resources = [] + image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) + texture_filenames = cmds.pgYetiCommand(node, listTextures=True) + + if texture_filenames and not image_search_path: + raise ValueError("pgYetiMaya node '%s' is missing the path to the " + "files in the 'imageSearchPath " + "atttribute'" % node) + + for texture in texture_filenames: + node_resources = {"files": [], "source": texture, "node": node} + texture_filepath = os.path.join(image_search_path, texture) + if len(texture.split(".")) > 2: + + # For UDIM based textures (tiles) + if "" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="") + node_resources["files"].extend(sequences) + + # Based textures (animated masks f.e) + elif "%04d" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="%04d") + node_resources["files"].extend(sequences) + # Assuming it is a fixed name + else: + node_resources["files"].append(texture_filepath) + else: + node_resources["files"].append(texture_filepath) + + resources.append(node_resources) + + return resources + + def get_sequence(self, filename, pattern="%04d"): + """Get sequence from filename + + Supports negative frame ranges like -001, 0000, 0001 and -0001, + 0000, 0001. + + Arguments: + filename (str): The full path to filename containing the given + pattern. + pattern (str): The pattern to swap with the variable frame number. + + Returns: + list: file sequence. + + """ + + from avalon.vendor import clique + + glob_pattern = filename.replace(pattern, "*") + + escaped = re.escape(filename) + re_pattern = escaped.replace(pattern, "-?[0-9]+") + + files = glob.glob(glob_pattern) + files = [str(f) for f in files if re.match(re_pattern, f)] + + pattern = [clique.PATTERNS["frames"]] + collection, remainer = clique.assemble(files, patterns=pattern) + + return collection diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py new file mode 100644 index 0000000000..af112c283e --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -0,0 +1,131 @@ +import os +import glob +import re + +from maya import cmds + +import pyblish.api +from avalon import api + +from colorbleed.maya import lib + + +SETTINGS = {"renderDensity": 10.0, + "renderWidth": 1.0, + "renderLength": 1.0, + "increaseRenderBounds": 0.1} + + +class CollectYetiProceduralData(pyblish.api.InstancePlugin): + """Collect procedural data""" + + order = pyblish.api.CollectorOrder + 0.4 + families = ["colorbleed.yetiprocedural"] + label = "Collect Yeti Procedural" + hosts = ["maya"] + + def process(self, instance): + + # Collect animation data + animation_data = lib.collect_animation_data() + instance.data.update(animation_data) + + # We only want one frame to export if it is not animation + if api.Session["AVALON_TASK"] != "animation": + instance.data["startFrame"] = 1 + instance.data["endFrame"] = 1 + + # Get all procedural nodes + yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya") + + # Collect any textures if used + node_attrs = {} + yeti_resources = [] + for node in yeti_nodes: + resources = self.get_yeti_resources(node) + yeti_resources.extend(resources) + + node_attrs[node] = {} + for attr, value in SETTINGS.iteritems(): + current = cmds.getAttr("%s.%s" % (node, attr)) + node_attrs[node][attr] = current + + instance.data["settings"] = node_attrs + instance.data["resources"] = yeti_resources + + def get_yeti_resources(self, node): + """Get all texture file paths + + If a texture is a sequence it gathers all sibling files to ensure + the texture sequence is complete. + + Args: + node (str): node name of the pgYetiMaya node + + Returns: + list + """ + resources = [] + image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) + texture_filenames = cmds.pgYetiCommand(node, listTextures=True) + + if texture_filenames and not image_search_path: + raise ValueError("pgYetiMaya node '%s' is missing the path to the " + "files in the 'imageSearchPath " + "atttribute'" % node) + + for texture in texture_filenames: + node_resources = {"files": [], "source": texture, "node": node} + texture_filepath = os.path.join(image_search_path, texture) + if len(texture.split(".")) > 2: + # For UDIM based textures (tiles) + if "" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="") + node_resources["node"].extend(sequences) + + # Based textures (animated masks f.e) + elif "%04d" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="%04d") + node_resources["node"].extend(sequences) + # Assuming it is a fixed name + else: + node_resources["node"].append(texture_filepath) + else: + node_resources["node"].append(texture_filepath) + + resources.append(node_resources) + + return resources + + def get_sequence(self, filename, pattern="%04d"): + """Get sequence from filename + + Supports negative frame ranges like -001, 0000, 0001 and -0001, + 0000, 0001. + + Arguments: + filename (str): The full path to filename containing the given + pattern. + pattern (str): The pattern to swap with the variable frame number. + + Returns: + list: file sequence. + + """ + + from avalon.vendor import clique + + glob_pattern = filename.replace(pattern, "*") + + escaped = re.escape(filename) + re_pattern = escaped.replace(pattern, "-?[0-9]+") + + files = glob.glob(glob_pattern) + files = [str(f) for f in files if re.match(re_pattern, f)] + + pattern = [clique.PATTERNS["frames"]] + collection, remainer = clique.assemble(files, patterns=pattern) + + return collection diff --git a/colorbleed/plugins/maya/publish/extract_yeti_cache.py b/colorbleed/plugins/maya/publish/extract_yeti_cache.py new file mode 100644 index 0000000000..59cb630a8c --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_yeti_cache.py @@ -0,0 +1,92 @@ +import os +import json +import pprint + +from maya import cmds + +import colorbleed.api +from cb.utils.maya import context + + +class ExtractYetiProcedural(colorbleed.api.Extractor): + """Produce an alembic of just point positions and normals. + + Positions and normals are preserved, but nothing more, + for plain and predictable point caches. + + """ + + label = "Extract Yeti" + hosts = ["maya"] + families = ["colorbleed.yetiRig"] + + def process(self, instance): + + yeti_nodes = cmds.ls(instance, type="pgYetiMaya") + if not yeti_nodes: + raise RuntimeError("No pgYetiMaya nodes found in the instance") + + # Define extract output file path + dirname = self.staging_dir(instance) + + # Yeti related staging dirs + data_file = os.path.join(dirname, "yeti_settings.json") + maya_path = os.path.join(dirname, "yeti_rig.ma") + + self.log.info("Writing out cache") + # Start writing the files for snap shot + # will be replace by the Yeti node name + path = os.path.join(dirname, "cache_.0001.fur") + cmds.pgYetiCommand(yeti_nodes, + writeCache=path, + range=(1, 1), + sampleTimes="0.0 1.0", + updateViewport=False, + generatePreview=False) + + cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] + + self.log.info("Writing metadata file") + image_search_path = "" + settings = instance.data.get("settings", None) + if settings is not None: + + # Create assumed destination folder for imageSearchPath + assumed_temp_data = instance.data["assumedTemplateData"] + template = instance.data["template"] + template_formatted = template.format(**assumed_temp_data) + + destination_folder = os.path.dirname(template_formatted) + image_search_path = os.path.join(destination_folder, "resources") + image_search_path = os.path.normpath(image_search_path) + + # Store assumed imageSearchPath + settings["imageSearchPath"] = image_search_path + + with open(data_file, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) + + attr_value = {"%s.imageSearchPath" % n: image_search_path for + n in yeti_nodes} + + with context.attribute_value(attr_value): + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.file(maya_path, + force=True, + exportSelected=True, + typ="mayaAscii", + preserveReferences=False, + constructionHistory=False, + shader=False) + + # Ensure files can be stored + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].extend([cache_files, + "yeti_rig.ma", + "yeti_settings.json"]) + + self.log.info("Extracted {} to {}".format(instance, dirname)) + + cmds.select(clear=True) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py new file mode 100644 index 0000000000..48f67b891e --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -0,0 +1,62 @@ +import os +import json + +from maya import cmds +import colorbleed.api + + +class ExtractYetiProcedural(colorbleed.api.Extractor): + """Produce an alembic of just point positions and normals. + + Positions and normals are preserved, but nothing more, + for plain and predictable point caches. + + """ + + label = "Extract Yeti" + hosts = ["maya"] + families = ["colorbleed.yetiprocedural"] + + def process(self, instance): + print instance + + yeti_nodes = cmds.ls(instance, type="pgYetiMaya") + if not yeti_nodes: + raise RuntimeError("No pgYetiMaya nodes found in the instance") + + # Define extract output file path + dirname = self.staging_dir(instance) + data_file = os.path.join(dirname, "{}.json".format(instance.name)) + + start = instance.data.get("startFrame") + end = instance.data.get("endFrame") + preroll = instance.data.get("preroll") + if preroll > 1: + start -= preroll # caching supports negative frames + + self.log.info("Writing out cache") + # Start writing the files + # will be replace by the yeti node name + filename = "{0}_.%04d.fur".format(instance.name) + path = os.path.join(dirname, filename) + cache_files = cmds.pgYetiCommand(yeti_nodes, + writeCache=path, + range=(start, end), + sampleTimes="0.0 1.0", + updateViewport=False, + generatePreivew=False) + + self.log.info("Writing metadata file") + settings = instance.data.get("settings", None) + if settings is not None: + with open(data_file, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) + + # Ensure files can be stored + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(cache_files) + instance.data["files"].append(data_file) + + self.log.info("Extracted {} to {}".format(instance, dirname)) From 4c4fba479b65728e5906a73789fbadaf718a0ead Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 28 Nov 2017 11:00:19 +0100 Subject: [PATCH 0489/1249] splitting and improving logic --- .../maya/create/colorbleed_yeti_cache.py | 15 +++ .../maya/create/colorbleed_yeti_rig.py | 17 ++- .../plugins/maya/load/load_yeti_cache.py | 17 +++ .../maya/publish/collect_yeti_cache.py | 124 +----------------- .../plugins/maya/publish/collect_yeti_rig.py | 69 ++++++---- .../maya/publish/extract_yeti_cache.py | 47 ++----- .../plugins/maya/publish/extract_yeti_rig.py | 62 +++++---- colorbleed/plugins/publish/integrate.py | 26 +++- 8 files changed, 161 insertions(+), 216 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index e69de29bb2..04c2254d69 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -0,0 +1,15 @@ +import avalon.maya + + +class CreateYetiRig(avalon.maya.Creator): + """Output for procedural plugin nodes ( Yeti / XGen / etc)""" + + name = "yetiDefault" + label = "Yeti Cache" + family = "colorbleed.yeticache" + icon = "pagelines" + + def __init__(self, *args, **kwargs): + super(CreateYetiRig, self).__init__(*args, **kwargs) + + self.data["peroll"] = 0 diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py index 5da695797a..3b21c586a7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py @@ -1,15 +1,20 @@ +from maya import cmds + import avalon.maya class CreateYetiRig(avalon.maya.Creator): """Output for procedural plugin nodes ( Yeti / XGen / etc)""" - name = "yetiDefault" - label = "Procedural" - family = "colorbleed.yetirig" + label = "Yeti Rig" + family = "colorbleed.yetiRig" icon = "usb" - def __init__(self, *args, **kwargs): - super(CreateYetiRig, self).__init__(*args, **kwargs) + def process(self): - self.data["preroll"] = 0 + instance = super(CreateYetiRig, self).process() + + self.log.info("Creating Rig instance set up ...") + + input_meshes = cmds.sets(name="input_SET", empty=True) + cmds.sets(input_meshes, forceElement=instance) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index e69de29bb2..2ca5020545 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -0,0 +1,17 @@ +from avalon import api + + +class YetiCacheLoader(api.Loader): + + families = ["colorbleed.yeticache"] + representations = ["fur"] + + label = "Load Yeti Cache" + order = -9 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + + pass diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/colorbleed/plugins/maya/publish/collect_yeti_cache.py index e2eebb9377..f072f5a22a 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_cache.py @@ -17,143 +17,33 @@ SETTINGS = {"renderDensity", "cbId"} -class CollectYetiRig(pyblish.api.InstancePlugin): - """Collect all information of the Yeti Rig""" +class CollectYetiCache(pyblish.api.InstancePlugin): + """Collect all information of the Yeti caches""" order = pyblish.api.CollectorOrder + 0.4 - label = "Collect Yeti Rig" - families = ["colorbleed.yetiRig"] + label = "Collect Yeti Cache" + families = ["colorbleed.yetiRig", "colorbleed.yeticache"] hosts = ["maya"] + tasks = ["animation", "fx"] def process(self, instance): - assert "input_SET" in cmds.sets(instance.name, query=True), ( - "Yeti Rig must have an input_SET") - # Collect animation data animation_data = lib.collect_animation_data() instance.data.update(animation_data) # We only want one frame to export if it is not animation - if api.Session["AVALON_TASK"] != "animation": + if api.Session["AVALON_TASK"] not in self.tasks: instance.data["startFrame"] = 1 instance.data["endFrame"] = 1 - # Get the input meshes information - input_content = cmds.sets("input_SET", query=True) - input_nodes = cmds.listRelatives(input_content, - allDescendents=True, - fullPath=True) or [] - - # Get all the shapes - input_meshes = cmds.ls(input_nodes, type="shape", long=True) - - inputs = [] - for mesh in input_meshes: - connections = cmds.listConnections(mesh, - source=True, - destination=False, - connections=True, - plugs=True, - type="mesh") - source = connections[-1].split(".")[0] - plugs = [i.split(".")[-1] for i in connections] - inputs.append({"connections": plugs, - "inputID": lib.get_id(mesh), - "outputID": lib.get_id(source)}) - # Collect any textures if used node_attrs = {} - yeti_resources = [] - for node in cmds.ls(instance[:], type="pgYetiMaya"): + for node in cmds.ls(instance.data["setMembers"], type="pgYetiMaya"): # Get Yeti resources (textures) - # TODO: referenced files in Yeti Graph - resources = self.get_yeti_resources(node) - yeti_resources.extend(resources) - for attr in SETTINGS: node_attr = "%s.%s" % (node, attr) current = cmds.getAttr(node_attr) node_attrs[node_attr] = current - instance.data["inputs"] = inputs instance.data["settings"] = node_attrs - instance.data["resources"] = yeti_resources - - def get_yeti_resources(self, node): - """Get all texture file paths - - If a texture is a sequence it gathers all sibling files to ensure - the texture sequence is complete. - - Args: - node (str): node name of the pgYetiMaya node - - Returns: - list - """ - resources = [] - image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) - texture_filenames = cmds.pgYetiCommand(node, listTextures=True) - - if texture_filenames and not image_search_path: - raise ValueError("pgYetiMaya node '%s' is missing the path to the " - "files in the 'imageSearchPath " - "atttribute'" % node) - - for texture in texture_filenames: - node_resources = {"files": [], "source": texture, "node": node} - texture_filepath = os.path.join(image_search_path, texture) - if len(texture.split(".")) > 2: - - # For UDIM based textures (tiles) - if "" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="") - node_resources["files"].extend(sequences) - - # Based textures (animated masks f.e) - elif "%04d" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="%04d") - node_resources["files"].extend(sequences) - # Assuming it is a fixed name - else: - node_resources["files"].append(texture_filepath) - else: - node_resources["files"].append(texture_filepath) - - resources.append(node_resources) - - return resources - - def get_sequence(self, filename, pattern="%04d"): - """Get sequence from filename - - Supports negative frame ranges like -001, 0000, 0001 and -0001, - 0000, 0001. - - Arguments: - filename (str): The full path to filename containing the given - pattern. - pattern (str): The pattern to swap with the variable frame number. - - Returns: - list: file sequence. - - """ - - from avalon.vendor import clique - - glob_pattern = filename.replace(pattern, "*") - - escaped = re.escape(filename) - re_pattern = escaped.replace(pattern, "-?[0-9]+") - - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] - - pattern = [clique.PATTERNS["frames"]] - collection, remainer = clique.assemble(files, patterns=pattern) - - return collection diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index af112c283e..fe57784b00 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -5,52 +5,66 @@ import re from maya import cmds import pyblish.api -from avalon import api from colorbleed.maya import lib -SETTINGS = {"renderDensity": 10.0, - "renderWidth": 1.0, - "renderLength": 1.0, - "increaseRenderBounds": 0.1} +SETTINGS = {"renderDensity", + "renderWidth", + "renderLength", + "increaseRenderBounds", + "cbId"} -class CollectYetiProceduralData(pyblish.api.InstancePlugin): - """Collect procedural data""" +class CollectYetiRig(pyblish.api.InstancePlugin): + """Collect all information of the Yeti Rig""" order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.yetiprocedural"] - label = "Collect Yeti Procedural" + label = "Collect Yeti Rig" + families = ["colorbleed.yetiRig"] hosts = ["maya"] def process(self, instance): + assert "input_SET" in cmds.sets(instance.name, query=True), ( + "Yeti Rig must have an input_SET") + # Collect animation data animation_data = lib.collect_animation_data() instance.data.update(animation_data) - # We only want one frame to export if it is not animation - if api.Session["AVALON_TASK"] != "animation": - instance.data["startFrame"] = 1 - instance.data["endFrame"] = 1 + # Get the input meshes information + input_content = cmds.sets("input_SET", query=True) + input_nodes = cmds.listRelatives(input_content, + allDescendents=True, + fullPath=True) or [] - # Get all procedural nodes - yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya") + # Get all the shapes + input_meshes = cmds.ls(input_nodes, type="shape", long=True) + + inputs = [] + for mesh in input_meshes: + connections = cmds.listConnections(mesh, + source=True, + destination=False, + connections=True, + plugs=True, + type="mesh") + source = connections[-1].split(".")[0] + plugs = [i.split(".")[-1] for i in connections] + inputs.append({"connections": plugs, + "inputID": lib.get_id(mesh), + "outputID": lib.get_id(source)}) # Collect any textures if used - node_attrs = {} yeti_resources = [] - for node in yeti_nodes: + for node in cmds.ls(instance[:], type="pgYetiMaya"): + # Get Yeti resources (textures) + # TODO: referenced files in Yeti Graph resources = self.get_yeti_resources(node) yeti_resources.extend(resources) - node_attrs[node] = {} - for attr, value in SETTINGS.iteritems(): - current = cmds.getAttr("%s.%s" % (node, attr)) - node_attrs[node][attr] = current - - instance.data["settings"] = node_attrs + instance.data["inputs"] = inputs instance.data["resources"] = yeti_resources def get_yeti_resources(self, node): @@ -78,22 +92,23 @@ class CollectYetiProceduralData(pyblish.api.InstancePlugin): node_resources = {"files": [], "source": texture, "node": node} texture_filepath = os.path.join(image_search_path, texture) if len(texture.split(".")) > 2: + # For UDIM based textures (tiles) if "" in texture: sequences = self.get_sequence(texture_filepath, pattern="") - node_resources["node"].extend(sequences) + node_resources["files"].extend(sequences) # Based textures (animated masks f.e) elif "%04d" in texture: sequences = self.get_sequence(texture_filepath, pattern="%04d") - node_resources["node"].extend(sequences) + node_resources["files"].extend(sequences) # Assuming it is a fixed name else: - node_resources["node"].append(texture_filepath) + node_resources["files"].append(texture_filepath) else: - node_resources["node"].append(texture_filepath) + node_resources["files"].append(texture_filepath) resources.append(node_resources) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_cache.py b/colorbleed/plugins/maya/publish/extract_yeti_cache.py index 59cb630a8c..34f4a205ef 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_cache.py @@ -1,14 +1,13 @@ import os import json -import pprint from maya import cmds import colorbleed.api -from cb.utils.maya import context +# from cb.utils.maya import context -class ExtractYetiProcedural(colorbleed.api.Extractor): +class ExtractYetiCache(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -16,9 +15,9 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): """ - label = "Extract Yeti" + label = "Extract Yeti Cache" hosts = ["maya"] - families = ["colorbleed.yetiRig"] + families = ["colorbleed.yetiRig", "colorbleed.yeticache"] def process(self, instance): @@ -31,7 +30,13 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): # Yeti related staging dirs data_file = os.path.join(dirname, "yeti_settings.json") - maya_path = os.path.join(dirname, "yeti_rig.ma") + + # Collect information for writing cache + start_frame = instance.data.get("startFrame") + end_frame = instance.data.get("endFrame") + preroll = instance.data.get("preroll") + if preroll > 0: + start_frame -= preroll self.log.info("Writing out cache") # Start writing the files for snap shot @@ -39,7 +44,7 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): path = os.path.join(dirname, "cache_.0001.fur") cmds.pgYetiCommand(yeti_nodes, writeCache=path, - range=(1, 1), + range=(start_frame, end_frame), sampleTimes="0.0 1.0", updateViewport=False, generatePreview=False) @@ -47,44 +52,16 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] self.log.info("Writing metadata file") - image_search_path = "" settings = instance.data.get("settings", None) if settings is not None: - - # Create assumed destination folder for imageSearchPath - assumed_temp_data = instance.data["assumedTemplateData"] - template = instance.data["template"] - template_formatted = template.format(**assumed_temp_data) - - destination_folder = os.path.dirname(template_formatted) - image_search_path = os.path.join(destination_folder, "resources") - image_search_path = os.path.normpath(image_search_path) - - # Store assumed imageSearchPath - settings["imageSearchPath"] = image_search_path - with open(data_file, "w") as fp: json.dump(settings, fp, ensure_ascii=False) - attr_value = {"%s.imageSearchPath" % n: image_search_path for - n in yeti_nodes} - - with context.attribute_value(attr_value): - cmds.select(instance.data["setMembers"], noExpand=True) - cmds.file(maya_path, - force=True, - exportSelected=True, - typ="mayaAscii", - preserveReferences=False, - constructionHistory=False, - shader=False) - # Ensure files can be stored if "files" not in instance.data: instance.data["files"] = list() instance.data["files"].extend([cache_files, - "yeti_rig.ma", "yeti_settings.json"]) self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 48f67b891e..feee6ab158 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -1,11 +1,13 @@ import os -import json from maya import cmds + import colorbleed.api +from cb.utils.maya import context +reload(context) -class ExtractYetiProcedural(colorbleed.api.Extractor): +class ExtractYetiRig(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -13,12 +15,11 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): """ - label = "Extract Yeti" + label = "Extract Yeti Rig" hosts = ["maya"] - families = ["colorbleed.yetiprocedural"] + families = ["colorbleed.yetiRig", "colorbleed.yeticache"] def process(self, instance): - print instance yeti_nodes = cmds.ls(instance, type="pgYetiMaya") if not yeti_nodes: @@ -26,37 +27,44 @@ class ExtractYetiProcedural(colorbleed.api.Extractor): # Define extract output file path dirname = self.staging_dir(instance) - data_file = os.path.join(dirname, "{}.json".format(instance.name)) - start = instance.data.get("startFrame") - end = instance.data.get("endFrame") - preroll = instance.data.get("preroll") - if preroll > 1: - start -= preroll # caching supports negative frames - - self.log.info("Writing out cache") - # Start writing the files - # will be replace by the yeti node name - filename = "{0}_.%04d.fur".format(instance.name) - path = os.path.join(dirname, filename) - cache_files = cmds.pgYetiCommand(yeti_nodes, - writeCache=path, - range=(start, end), - sampleTimes="0.0 1.0", - updateViewport=False, - generatePreivew=False) + # Yeti related staging dirs + maya_path = os.path.join(dirname, "yeti_rig.ma") self.log.info("Writing metadata file") + image_search_path = "" settings = instance.data.get("settings", None) if settings is not None: - with open(data_file, "w") as fp: - json.dump(settings, fp, ensure_ascii=False) + + # Create assumed destination folder for imageSearchPath + assumed_temp_data = instance.data["assumedTemplateData"] + template = instance.data["template"] + template_formatted = template.format(**assumed_temp_data) + + destination_folder = os.path.dirname(template_formatted) + image_search_path = os.path.join(destination_folder, "resources") + image_search_path = os.path.normpath(image_search_path) + + attr_value = {"%s.imageSearchPath" % n: image_search_path for + n in yeti_nodes} + + with context.attribute_values(attr_value): + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.file(maya_path, + force=True, + exportSelected=True, + typ="mayaAscii", + preserveReferences=False, + constructionHistory=False, + shader=False) # Ensure files can be stored if "files" not in instance.data: instance.data["files"] = list() - instance.data["files"].append(cache_files) - instance.data["files"].append(data_file) + instance.data["files"].extend(["yeti_rig.ma", + "yeti_settings.json"]) self.log.info("Extracted {} to {}".format(instance, dirname)) + + cmds.select(clear=True) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 54680b1f93..e0094a0297 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -1,5 +1,6 @@ import os import logging +import pprint import shutil import errno @@ -31,7 +32,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.model", "colorbleed.pointcache", "colorbleed.setdress", - "colorbleed.rig"] + "colorbleed.rig", + "colorbleed.yetiRig"] def process(self, instance): @@ -149,6 +151,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Find the representations to transfer amongst the files # Each should be a single representation (as such, a single extension) representations = [] + + print 'files', instance.data['files'] + for files in instance.data["files"]: # Collection @@ -162,7 +167,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # if isinstance(files, list): collection = files - # Assert that each member has identical suffix _, ext = os.path.splitext(collection[0]) assert all(ext == os.path.splitext(name)[1] @@ -170,10 +174,18 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "Files had varying suffixes, this is a bug" ) + assert not any(os.path.isabs(name) for name in collection) + template_data["representation"] = ext[1:] for fname in collection: - src = os.path.join(stagingdir, fname) + + if os.path.isabs(fname): + src = fname + fname = os.path.basename(src) + else: + src = os.path.join(stagingdir, fname) + dst = os.path.join( template_publish.format(**template_data), fname @@ -191,6 +203,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # |_______| # fname = files + assert not os.path.isabs(fname) _, ext = os.path.splitext(fname) template_data["representation"] = ext[1:] @@ -221,6 +234,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): } representations.append(representation) + pprint.pprint(instance.data["transfers"]) + self.log.info("Registering {} items".format(len(representations))) io.insert_many(representations) @@ -287,10 +302,13 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def create_version(self, subset, version_number, locations, data=None): """ Copy given source to destination - Arguments: + Args: subset (dict): the registered subset of the asset version_number (int): the version number locations (list): the currently registered locations + + Returns: + dict: collection of data to create a version """ # Imprint currently registered location version_locations = [location for location in locations if From 409e177606bd57f745e04fe1dca710a94698141e Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 28 Nov 2017 11:24:55 +0100 Subject: [PATCH 0490/1249] filtered out referenced renderlayers --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index ffe84e2234..e75a0b2dae 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -35,7 +35,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): use_defaultlayer = cmds.getAttr(attr) # Get render layers - renderlayers = cmds.ls(type="renderLayer") + renderlayers = [i for i in cmds.ls(type="renderLayer") if not + cmds.referenceQuery(i, isNodeReferenced=True)] if not use_defaultlayer: renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] From d3565e0433c72ec12533a5c113687365277ba81c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 28 Nov 2017 19:07:27 +0100 Subject: [PATCH 0491/1249] fixed publishers and loaders, added validator and load rig --- .../maya/create/colorbleed_yeti_cache.py | 12 +- .../plugins/maya/load/load_yeti_cache.py | 153 +++++++++++++++++- colorbleed/plugins/maya/load/load_yeti_rig.py | 77 +++++++++ .../maya/publish/collect_yeti_cache.py | 33 +--- .../plugins/maya/publish/collect_yeti_rig.py | 50 +++--- .../maya/publish/extract_yeti_cache.py | 12 +- .../plugins/maya/publish/extract_yeti_rig.py | 81 ++++++++-- .../maya/publish/validate_yeti_rig_inputs.py | 40 +++++ colorbleed/plugins/publish/integrate.py | 3 +- 9 files changed, 390 insertions(+), 71 deletions(-) create mode 100644 colorbleed/plugins/maya/load/load_yeti_rig.py create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 04c2254d69..df71d203c9 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -1,4 +1,7 @@ +from collections import OrderedDict + import avalon.maya +from colorbleed.maya import lib class CreateYetiRig(avalon.maya.Creator): @@ -12,4 +15,11 @@ class CreateYetiRig(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiRig, self).__init__(*args, **kwargs) - self.data["peroll"] = 0 + data = OrderedDict(self.data) + data["peroll"] = 0 + + anim_data = lib.collect_animation_data() + data.update({"startFrame": anim_data["startFrame"], + "endFrame": anim_data["endFrame"]}) + + self.data = data diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 2ca5020545..ebff67fdd4 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -1,9 +1,18 @@ +import os +import json +import re +import glob + +from maya import cmds + from avalon import api +from avalon.maya import lib as avalon_lib, pipeline +from colorbleed.maya import lib class YetiCacheLoader(api.Loader): - families = ["colorbleed.yeticache"] + families = ["colorbleed.yeticache", "colorbleed.yetiRig"] representations = ["fur"] label = "Load Yeti Cache" @@ -13,5 +22,145 @@ class YetiCacheLoader(api.Loader): def load(self, context, name=None, namespace=None, data=None): + # Build namespace + asset = context["asset"] + if namespace is None: + namespace = self.create_namespace(asset["name"]) + + # Ensure Yeti is loaded + if not cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): + cmds.loadPlugin("pgYetiMaya", quiet=True) + + # Get JSON + fname, ext = os.path.splitext(self.fname) + settings_fname = "{}.fursettings".format(fname) + with open(settings_fname, "r") as fp: + fursettings = json.load(fp) + + # Get node name from JSON + nodes = [] + for node, settings in fursettings.items(): + + # Create new pgYetiMaya node + node_name = "{}:{}".format(namespace, node) + yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + + # Apply colorbleed ID to node + lib.set_id(node=yeti_node, + unique_id=settings["cbId"], + overwrite=True) + settings.pop("cbId", None) + + # Apply settings + for attr, value in settings.items(): + attribute = "%s.%s" % (yeti_node, attr) + cmds.setAttr(attribute, value) + + # Ensure the node has no namespace identifiers + node = node.replace(":", "_") + + # Create full cache path + cache = "{}/{}.%04d.fur".format(self.fname, node) + cache_fname = self.validate_cache(cache) + cache_path = os.path.join(self.fname, cache_fname) + + # Add filename to `cacheFileName` attribute, set to cache mode + cmds.setAttr("%s.cacheFileName" % yeti_node, + cache_path, + type="string") + + cmds.setAttr("%s.fileMode" % yeti_node, 1) + + nodes.append(yeti_node) + + group_name = "{}:{}".format(namespace, asset["name"]) + group_node = cmds.group(nodes, name=group_name) + all_nodes = cmds.listRelatives(group_node, children=True) + all_nodes.append(group_node) + + self[:] = all_nodes + + return pipeline.containerise(name=name, + namespace=namespace, + nodes=all_nodes, + context=context, + loader=self.__class__.__name__) + + def remove(self, container): + + from maya import cmds + + namespace = container["namespace"] + container_name = container["objectName"] + + self.log.info("Removing '%s' from Maya.." % container["name"]) + + container_content = cmds.sets(container_name, query=True) + nodes = cmds.ls(container_content, long=True) + + nodes.append(container_name) + + try: + cmds.delete(nodes) + except ValueError: + # Already implicitly deleted by Maya upon removing reference + pass + + cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) + + def update(self, container, representation): + + path = api.get_representation_path(representation) + members = cmds.sets(container['objectName'], query=True) + yeti_node = cmds.ls(members, type="pgYetiMaya", long=True) + + for node in yeti_node: + node_name = node.split(":")[-1] + tmp_cache = os.path.join(path, "{}.%04d.fur".format(node_name)) + fpath = self.validate_cache(tmp_cache) + cmds.setAttr("{}.cacheFileName".format(node), fpath, type="string") + + # helper functions + + def create_namespace(self, asset): + """Create a unique namespace + Args: + asset (dict): asset information + + """ + + asset_name = "{}_".format(asset) + prefix = "_" if asset_name[0].isdigit()else "" + namespace = avalon_lib.unique_namespace(asset_name, + prefix=prefix, + suffix="_") + + return namespace + + def validate_cache(self, filename, pattern="%04d"): + """Check if the cache has more than 1 frame + + All caches with more than 1 frame need to be called with `%04d` + If the cache has only one frame we return that file name as we assume + it is a snapshot. + """ + + from avalon.vendor import clique + + glob_pattern = filename.replace(pattern, "*") + + escaped = re.escape(filename) + re_pattern = escaped.replace(pattern, "-?[0-9]+") + + files = glob.glob(glob_pattern) + files = [str(f) for f in files if re.match(re_pattern, f)] + + pattern = [clique.PATTERNS["frames"]] + collection, remainer = clique.assemble(files, patterns=pattern) + + if len(collection) == 1: + return collection[0] + + return filename - pass diff --git a/colorbleed/plugins/maya/load/load_yeti_rig.py b/colorbleed/plugins/maya/load/load_yeti_rig.py new file mode 100644 index 0000000000..248460b7f3 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_yeti_rig.py @@ -0,0 +1,77 @@ +import colorbleed.maya.plugin + + +class YetiRigLoader(colorbleed.maya.plugin.ReferenceLoader): + + families = ["colorbleed.yetiRig"] + representations = ["ma"] + + label = "Load Yeti Rig" + order = -9 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name=None, namespace=None, data=None): + + import maya.cmds as cmds + from avalon import maya + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + self[:] = nodes + + self._post_process(name, name, context, data) + + return nodes + + def _post_process(self, name, namespace, context, data): + + import os + import json + + import maya.cmds as cmds + + import avalon.maya.lib as lib + + # Get json data + fname, ext = os.path.splitext(self.fname) + data_file = "{}.rigsettings".format(fname) + + with open(data_file, "r") as fp: + rigsettings = json.load(fp) + + # Get content from input_SET + input_set = [i for i in self if "input_SET" in i] + assert len(input_set) == 1, "Could not find input_SET!" + members = cmds.ls(cmds.sets(input_set[0], query=True), long=True) + + for input in rigsettings["inputs"]: + + # Find input / output mesh + # Ensure the mesh is not it's self + plug_id_matches = lib.lsattr("cbId", input["plugID"]) or [] + plug_mesh = [i for i in plug_id_matches if i not in members] + + # Ensure connection goes to the correct mesh (might be duplicates) + socket_id_matches = lib.lsattr("cbId", input["socketID"]) or [] + socket_mesh = [i for i in socket_id_matches if i in members] + if len(plug_mesh) == 0: + return + + # Connect meshes, list of attributes to connect + socket_attr, plug_attr = input["connections"] + _plug_attr = "{}.{}".format(plug_mesh[0], plug_attr) + _socket_attr = "{}.{}".format(socket_mesh[0], socket_attr) + try: + cmds.connectAttr(_plug_attr, _socket_attr) + except Exception as e: + self.log.wanring(e) + continue + + return diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/colorbleed/plugins/maya/publish/collect_yeti_cache.py index f072f5a22a..0cc1e981f1 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_cache.py @@ -1,13 +1,6 @@ -import os -import glob -import re - from maya import cmds import pyblish.api -from avalon import api - -from colorbleed.maya import lib SETTINGS = {"renderDensity", @@ -20,7 +13,7 @@ SETTINGS = {"renderDensity", class CollectYetiCache(pyblish.api.InstancePlugin): """Collect all information of the Yeti caches""" - order = pyblish.api.CollectorOrder + 0.4 + order = pyblish.api.CollectorOrder + 0.45 label = "Collect Yeti Cache" families = ["colorbleed.yetiRig", "colorbleed.yeticache"] hosts = ["maya"] @@ -28,22 +21,12 @@ class CollectYetiCache(pyblish.api.InstancePlugin): def process(self, instance): - # Collect animation data - animation_data = lib.collect_animation_data() - instance.data.update(animation_data) - - # We only want one frame to export if it is not animation - if api.Session["AVALON_TASK"] not in self.tasks: - instance.data["startFrame"] = 1 - instance.data["endFrame"] = 1 - - # Collect any textures if used - node_attrs = {} - for node in cmds.ls(instance.data["setMembers"], type="pgYetiMaya"): - # Get Yeti resources (textures) + # Collect fur settings + settings = {} + for node in cmds.ls(instance, type="pgYetiMaya"): + settings[node] = {} for attr in SETTINGS: - node_attr = "%s.%s" % (node, attr) - current = cmds.getAttr(node_attr) - node_attrs[node_attr] = current + current = cmds.getAttr("%s.%s" % (node, attr)) + settings[node][attr] = current - instance.data["settings"] = node_attrs + instance.data["fursettings"] = settings diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index fe57784b00..09f0f311bb 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -13,6 +13,7 @@ SETTINGS = {"renderDensity", "renderWidth", "renderLength", "increaseRenderBounds", + "imageSearchPath", "cbId"} @@ -29,44 +30,53 @@ class CollectYetiRig(pyblish.api.InstancePlugin): assert "input_SET" in cmds.sets(instance.name, query=True), ( "Yeti Rig must have an input_SET") - # Collect animation data - animation_data = lib.collect_animation_data() - instance.data.update(animation_data) - # Get the input meshes information input_content = cmds.sets("input_SET", query=True) input_nodes = cmds.listRelatives(input_content, allDescendents=True, - fullPath=True) or [] + fullPath=True) or input_content # Get all the shapes - input_meshes = cmds.ls(input_nodes, type="shape", long=True) + input_shapes = cmds.ls(input_nodes, long=True) + + # Store all connections + connections = cmds.listConnections(input_shapes, + source=True, + destination=False, + connections=True, + plugs=True) or [] + + # Group per source, destination pair + grouped = [(item, connections[i+1]) for i, item in + enumerate(connections) if i % 2 == 0] inputs = [] - for mesh in input_meshes: - connections = cmds.listConnections(mesh, - source=True, - destination=False, - connections=True, - plugs=True, - type="mesh") - source = connections[-1].split(".")[0] - plugs = [i.split(".")[-1] for i in connections] - inputs.append({"connections": plugs, - "inputID": lib.get_id(mesh), - "outputID": lib.get_id(source)}) + for src, dest in grouped: + src_node, src_attr = src.split(".", 1) + dest_node, dest_attr = dest.split(".", 1) + + # The plug must go in the socket, remember this for the loader + inputs.append({"connections": [src_attr, dest_attr], + "plugID": lib.get_id(dest_node), + "socketID": lib.get_id(src_node)}) # Collect any textures if used yeti_resources = [] - for node in cmds.ls(instance[:], type="pgYetiMaya"): + yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya") + for node in yeti_nodes: # Get Yeti resources (textures) # TODO: referenced files in Yeti Graph resources = self.get_yeti_resources(node) yeti_resources.extend(resources) - instance.data["inputs"] = inputs + instance.data["rigsettings"] = {"inputs": inputs} + instance.data["resources"] = yeti_resources + # Force frame range for export + instance.data["startFrame"] = 1 + instance.data["endFrame"] = 1 + def get_yeti_resources(self, node): """Get all texture file paths diff --git a/colorbleed/plugins/maya/publish/extract_yeti_cache.py b/colorbleed/plugins/maya/publish/extract_yeti_cache.py index 34f4a205ef..479bf52813 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_cache.py @@ -4,7 +4,6 @@ import json from maya import cmds import colorbleed.api -# from cb.utils.maya import context class ExtractYetiCache(colorbleed.api.Extractor): @@ -29,7 +28,7 @@ class ExtractYetiCache(colorbleed.api.Extractor): dirname = self.staging_dir(instance) # Yeti related staging dirs - data_file = os.path.join(dirname, "yeti_settings.json") + data_file = os.path.join(dirname, "yeti.fursettings") # Collect information for writing cache start_frame = instance.data.get("startFrame") @@ -41,7 +40,7 @@ class ExtractYetiCache(colorbleed.api.Extractor): self.log.info("Writing out cache") # Start writing the files for snap shot # will be replace by the Yeti node name - path = os.path.join(dirname, "cache_.0001.fur") + path = os.path.join(dirname, ".%04d.fur") cmds.pgYetiCommand(yeti_nodes, writeCache=path, range=(start_frame, end_frame), @@ -52,7 +51,7 @@ class ExtractYetiCache(colorbleed.api.Extractor): cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] self.log.info("Writing metadata file") - settings = instance.data.get("settings", None) + settings = instance.data.get("fursettings", None) if settings is not None: with open(data_file, "w") as fp: json.dump(settings, fp, ensure_ascii=False) @@ -61,9 +60,6 @@ class ExtractYetiCache(colorbleed.api.Extractor): if "files" not in instance.data: instance.data["files"] = list() - instance.data["files"].extend([cache_files, - "yeti_settings.json"]) + instance.data["files"].extend([cache_files, "yeti.fursettings"]) self.log.info("Extracted {} to {}".format(instance, dirname)) - - cmds.select(clear=True) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index feee6ab158..a4e4d8e019 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -1,10 +1,52 @@ import os +import json +import contextlib from maya import cmds +import avalon.maya.lib as lib import colorbleed.api from cb.utils.maya import context -reload(context) + + +@contextlib.contextmanager +def disconnected_attributes(settings, members): + + members = cmds.ls(members, long=True) + original_connection = [] + try: + for input in settings["inputs"]: + + # get source + socket_id = input["socketID"] + sources = lib.lsattr("cbId", socket_id) + sources = [i for i in sources if + not cmds.referenceQuery(i, isNodeReferenced=True) + and i in members] + src = sources[0] + + # get destination + plug_id = input["plugID"] + plugs = lib.lsattr("cbId", plug_id) + destinations = [i for i in plugs if i not in members and + i not in sources] + dst = destinations[0] + + # break connection + connections = input["connections"] + src_attribute = "%s.%s" % (src, connections[0]) + dst_attribute = "%s.%s" % (dst, connections[1]) + + # store connection pair + original_connection.append([src_attribute, dst_attribute]) + cmds.disconnectAttr(dst_attribute, src_attribute) + yield + + finally: + # restore connections + for connection in original_connection: + src, dest = connection + cmds.connectAttr(dest, src) class ExtractYetiRig(colorbleed.api.Extractor): @@ -17,7 +59,7 @@ class ExtractYetiRig(colorbleed.api.Extractor): label = "Extract Yeti Rig" hosts = ["maya"] - families = ["colorbleed.yetiRig", "colorbleed.yeticache"] + families = ["colorbleed.yetiRig"] def process(self, instance): @@ -27,13 +69,15 @@ class ExtractYetiRig(colorbleed.api.Extractor): # Define extract output file path dirname = self.staging_dir(instance) + settings_path = os.path.join(dirname, "yeti.rigsettings") # Yeti related staging dirs maya_path = os.path.join(dirname, "yeti_rig.ma") self.log.info("Writing metadata file") + image_search_path = "" - settings = instance.data.get("settings", None) + settings = instance.data.get("rigsettings", None) if settings is not None: # Create assumed destination folder for imageSearchPath @@ -45,25 +89,34 @@ class ExtractYetiRig(colorbleed.api.Extractor): image_search_path = os.path.join(destination_folder, "resources") image_search_path = os.path.normpath(image_search_path) + settings["imageSearchPath"] = image_search_path + with open(settings_path, "w") as fp: + json.dump(settings, fp, ensure_ascii=False) + attr_value = {"%s.imageSearchPath" % n: image_search_path for n in yeti_nodes} - with context.attribute_values(attr_value): - cmds.select(instance.data["setMembers"], noExpand=True) - cmds.file(maya_path, - force=True, - exportSelected=True, - typ="mayaAscii", - preserveReferences=False, - constructionHistory=False, - shader=False) + # get input_SET members + input_set = [i for i in instance if i == "input_SET"] + members = cmds.sets(input_set[0], query=True) + + nodes = instance.data["setMembers"] + with disconnected_attributes(settings, members): + with context.attribute_values(attr_value): + cmds.select(nodes, noExpand=True) + cmds.file(maya_path, + force=True, + exportSelected=True, + typ="mayaAscii", + preserveReferences=False, + constructionHistory=True, + shader=False) # Ensure files can be stored if "files" not in instance.data: instance.data["files"] = list() - instance.data["files"].extend(["yeti_rig.ma", - "yeti_settings.json"]) + instance.data["files"].extend(["yeti_rig.ma", "yeti.rigsettings"]) self.log.info("Extracted {} to {}".format(instance, dirname)) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py new file mode 100644 index 0000000000..9d8072c78f --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py @@ -0,0 +1,40 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRigInputMeshes(pyblish.api.Validator): + """Validate if all pgYetiMaya nodes have at least one input shape""" + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + families = ["colorbleed.yetiRig"] + label = "Yeti Rig Input Shapes" + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Yeti Rig has invalid input meshes") + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + # Get yeti nodes + yeti_nodes = cmds.listRelatives(instance, + type="pgYetiMaya", + allDescendents=True, + fullPath=True) + # Get input meshes per node + for node in yeti_nodes: + attribute = "%s.inputGeometry" % node + input_meshes = cmds.listConnections(attribute, source=True) + if not input_meshes: + cls.log.error("'%s' has no input meshes" % node) + invalid.append(node) + + return [] diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index e0094a0297..2d8461134b 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -33,7 +33,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.pointcache", "colorbleed.setdress", "colorbleed.rig", - "colorbleed.yetiRig"] + "colorbleed.yetiRig", + "colorbleed.yeticache"] def process(self, instance): From 0b853ef111de0ff055072ba1fa466dff22ed70f9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Nov 2017 10:37:39 +0100 Subject: [PATCH 0492/1249] Implement temporary workaround for yeti in deadline submission --- colorbleed/plugins/maya/publish/submit_deadline.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index a0e6610744..ff6d973b47 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -175,16 +175,24 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): } # Include critical variables with submission - environment = dict({ + keys = [ # This will trigger `userSetup.py` on the slave # such that proper initialisation happens the same # way as it does on a local machine. # TODO(marcus): This won't work if the slaves don't # have accesss to these paths, such as if slaves are # running Linux and the submitter is on Windows. - "PYTHONPATH": os.getenv("PYTHONPATH", ""), + "PYTHONPATH", - }, **api.Session) + # todo: This is a temporary fix for yeti variables + "PEREGRINEL_LICENSE", + "VRAY_FOR_MAYA2018_PLUGINS_X64", + "VRAY_PLUGINS_X64", + "VRAY_USE_THREAD_AFFINITY", + "MAYA_MODULE_PATH" + ] + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **api.Session) payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( From b391f2dc6794a8f115433fc29d5307d01616cf47 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 29 Nov 2017 11:19:50 +0100 Subject: [PATCH 0493/1249] fixed and simplified validate_cache --- colorbleed/plugins/maya/load/load_yeti_cache.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index ebff67fdd4..57ac628cf9 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -146,8 +146,6 @@ class YetiCacheLoader(api.Loader): it is a snapshot. """ - from avalon.vendor import clique - glob_pattern = filename.replace(pattern, "*") escaped = re.escape(filename) @@ -156,11 +154,8 @@ class YetiCacheLoader(api.Loader): files = glob.glob(glob_pattern) files = [str(f) for f in files if re.match(re_pattern, f)] - pattern = [clique.PATTERNS["frames"]] - collection, remainer = clique.assemble(files, patterns=pattern) - - if len(collection) == 1: - return collection[0] + if len(files) == 1: + return files[0] return filename From 8eb54786e1581dbcd18d5884d5af1f432725d2b6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 29 Nov 2017 11:20:13 +0100 Subject: [PATCH 0494/1249] added temp fix for PATH ion environment for slaves --- colorbleed/plugins/maya/publish/submit_deadline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index ff6d973b47..edde6909c5 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -194,6 +194,10 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): environment = dict({key: os.environ[key] for key in keys if key in os.environ}, **api.Session) + PATHS = os.environ["PATH"].split(";") + environment["PATH"] = ";".join([p for p in PATHS + if p.startswith("P:")]) + payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( key=key, From 6af8a7782052ff1132077efafe56f6fa03d1431a Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 29 Nov 2017 11:30:45 +0100 Subject: [PATCH 0495/1249] updated class name --- colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py index 9d8072c78f..bc801b9849 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py @@ -4,7 +4,7 @@ import pyblish.api import colorbleed.api -class ValidateRigInputMeshes(pyblish.api.Validator): +class ValidateYetiRigInputMeshes(pyblish.api.Validator): """Validate if all pgYetiMaya nodes have at least one input shape""" order = colorbleed.api.ValidateContentsOrder From d6f8be341eb2fe58fb5b884d4d7d0f592c8a4f4a Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 29 Nov 2017 11:31:08 +0100 Subject: [PATCH 0496/1249] removed debug prints and redundant if statement --- colorbleed/plugins/publish/integrate.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 2d8461134b..a0ce130bd6 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -1,6 +1,5 @@ import os import logging -import pprint import shutil import errno @@ -153,8 +152,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Each should be a single representation (as such, a single extension) representations = [] - print 'files', instance.data['files'] - for files in instance.data["files"]: # Collection @@ -181,12 +178,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): for fname in collection: - if os.path.isabs(fname): - src = fname - fname = os.path.basename(src) - else: - src = os.path.join(stagingdir, fname) - + src = os.path.join(stagingdir, fname) dst = os.path.join( template_publish.format(**template_data), fname @@ -235,8 +227,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): } representations.append(representation) - pprint.pprint(instance.data["transfers"]) - self.log.info("Registering {} items".format(len(representations))) io.insert_many(representations) From 875b150cc61be33c2d9be47d347e2402fa36514b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 29 Nov 2017 11:43:53 +0100 Subject: [PATCH 0497/1249] fixed path creation for cache file, added check for resource folder --- colorbleed/plugins/maya/load/load_yeti_cache.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 57ac628cf9..23ee962616 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -37,6 +37,14 @@ class YetiCacheLoader(api.Loader): with open(settings_fname, "r") as fp: fursettings = json.load(fp) + # Check if resources map exists + # TODO: should be stored in fursettings + image_search_path = "" + version_folder = os.path.dirname(self.fname) + resource_folder = os.path.join(version_folder, "resources") + if os.path.exists(resource_folder): + image_search_path = os.path.normpath(resource_folder) + # Get node name from JSON nodes = [] for node, settings in fursettings.items(): @@ -61,7 +69,7 @@ class YetiCacheLoader(api.Loader): node = node.replace(":", "_") # Create full cache path - cache = "{}/{}.%04d.fur".format(self.fname, node) + cache = os.path.join(self.fname, "{}.%04d.fur".format(node)) cache_fname = self.validate_cache(cache) cache_path = os.path.join(self.fname, cache_fname) @@ -71,6 +79,9 @@ class YetiCacheLoader(api.Loader): type="string") cmds.setAttr("%s.fileMode" % yeti_node, 1) + cmds.setAttr("%s.imageSearchPath" % yeti_node, + image_search_path, + type="string") nodes.append(yeti_node) @@ -156,6 +167,8 @@ class YetiCacheLoader(api.Loader): if len(files) == 1: return files[0] + elif len(files) == 0: + self.log.error("Could not find cache files for '%s'" % filename) return filename From d66c2123b60a07cd8d5943b8852074b48785eca5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 30 Nov 2017 15:52:54 +0100 Subject: [PATCH 0498/1249] added attribute change uuid check --- .../plugins/maya/publish/validate_look_contents.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index c6ea36f9be..5f47ea39cc 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -71,10 +71,18 @@ class ValidateLookContents(pyblish.api.InstancePlugin): # Validate at least one single relationship is collected if not lookdata["relationships"]: - cls.log.error("Look '{}' has no " - "`relationships`".format(instance.name)) + cls.log.error("Look '%s' has no " + "`relationships`" % instance.name) invalid.add(instance.name) + # Check if attributes are on a node with an ID, crucial for rebuild! + for attr_changes in lookdata["attributes"]: + if not attr_changes["uuid"]: + cls.log.error("Node '%s' has no cbId, please set the " + "attributes to its children if it has any" + % attr_changes["name"]) + invalid.add(instance.name) + return list(invalid) @classmethod From 160925d275e02f901a3599dd1fba6278b3f7e7f2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 30 Nov 2017 17:02:23 +0100 Subject: [PATCH 0499/1249] fixed issue with updating container --- colorbleed/plugins/maya/load/load_yeti_cache.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 23ee962616..e3a6044f71 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -124,14 +124,19 @@ class YetiCacheLoader(api.Loader): path = api.get_representation_path(representation) members = cmds.sets(container['objectName'], query=True) - yeti_node = cmds.ls(members, type="pgYetiMaya", long=True) + all_members = cmds.listRelatives(members, ad=True) + yeti_node = cmds.ls(all_members, type="pgYetiMaya", long=True) for node in yeti_node: node_name = node.split(":")[-1] tmp_cache = os.path.join(path, "{}.%04d.fur".format(node_name)) - fpath = self.validate_cache(tmp_cache) + fpath = self.validate_cache(os.path.normpath(tmp_cache)) cmds.setAttr("{}.cacheFileName".format(node), fpath, type="string") + cmds.setAttr("{}.representation".format(container["objectName"]), + str(representation["_id"]), + type="string") + # helper functions def create_namespace(self, asset): From 33dd23a128210ca0ce0f8b97909eaecd42612749 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 30 Nov 2017 17:02:53 +0100 Subject: [PATCH 0500/1249] removed update logic from yeti rig loader --- colorbleed/plugins/maya/load/load_yeti_rig.py | 48 +------------------ 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_rig.py b/colorbleed/plugins/maya/load/load_yeti_rig.py index 248460b7f3..5043b1cbe0 100644 --- a/colorbleed/plugins/maya/load/load_yeti_rig.py +++ b/colorbleed/plugins/maya/load/load_yeti_rig.py @@ -26,52 +26,6 @@ class YetiRigLoader(colorbleed.maya.plugin.ReferenceLoader): self[:] = nodes - self._post_process(name, name, context, data) + self.log.info("Yeti Rig Connection Manager will be available soon") return nodes - - def _post_process(self, name, namespace, context, data): - - import os - import json - - import maya.cmds as cmds - - import avalon.maya.lib as lib - - # Get json data - fname, ext = os.path.splitext(self.fname) - data_file = "{}.rigsettings".format(fname) - - with open(data_file, "r") as fp: - rigsettings = json.load(fp) - - # Get content from input_SET - input_set = [i for i in self if "input_SET" in i] - assert len(input_set) == 1, "Could not find input_SET!" - members = cmds.ls(cmds.sets(input_set[0], query=True), long=True) - - for input in rigsettings["inputs"]: - - # Find input / output mesh - # Ensure the mesh is not it's self - plug_id_matches = lib.lsattr("cbId", input["plugID"]) or [] - plug_mesh = [i for i in plug_id_matches if i not in members] - - # Ensure connection goes to the correct mesh (might be duplicates) - socket_id_matches = lib.lsattr("cbId", input["socketID"]) or [] - socket_mesh = [i for i in socket_id_matches if i in members] - if len(plug_mesh) == 0: - return - - # Connect meshes, list of attributes to connect - socket_attr, plug_attr = input["connections"] - _plug_attr = "{}.{}".format(plug_mesh[0], plug_attr) - _socket_attr = "{}.{}".format(socket_mesh[0], socket_attr) - try: - cmds.connectAttr(_plug_attr, _socket_attr) - except Exception as e: - self.log.wanring(e) - continue - - return From f5f17d95414abaae0c83be16b4efa3a9be3f70fd Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 30 Nov 2017 17:26:47 +0100 Subject: [PATCH 0501/1249] ensure verbosity is at all for debug render message --- colorbleed/plugins/maya/load/load_yeti_cache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index e3a6044f71..0f58f1d735 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -83,6 +83,8 @@ class YetiCacheLoader(api.Loader): image_search_path, type="string") + cmds.setAttr("%s.verbosity" % yeti_node, 2) + nodes.append(yeti_node) group_name = "{}:{}".format(namespace, asset["name"]) From f77c82cd4ff4c1e8088885aaafa6343cf3ed243f Mon Sep 17 00:00:00 2001 From: Wijnand Koreman Date: Thu, 30 Nov 2017 22:39:47 +0100 Subject: [PATCH 0502/1249] Added creation of new cbId for transform node of Yeti node, explictly add transform node to list to add to container. --- .../plugins/maya/load/load_yeti_cache.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 0f58f1d735..31d0782f15 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -54,10 +54,24 @@ class YetiCacheLoader(api.Loader): yeti_node = cmds.createNode("pgYetiMaya", name=node_name) cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + # Note: the shape is created, we will need to add a new ID for the + # transform node of the new Yeti node + shapenode_id = settings["cbId"] + asset_id = shapenode_id.split(":", 1)[0] + # Apply colorbleed ID to node - lib.set_id(node=yeti_node, - unique_id=settings["cbId"], - overwrite=True) + lib.set_id(node=yeti_node, unique_id=shapenode_id, overwrite=True) + + # Though singular the generate_ids function needs list and as + # the result is returned as a list we can take advantage of that + # Todo: (Wijnand) double check this behavior! + transform_nodes = cmds.listRelatives(yeti_node, parent=True) or [] + if not transform_nodes: + raise RuntimeError("Something went wrong during creation of" + "the pgYetiMaya node") + + lib.generate_ids(nodes=transform_nodes, asset_id=asset_id) + settings.pop("cbId", None) # Apply settings @@ -86,6 +100,7 @@ class YetiCacheLoader(api.Loader): cmds.setAttr("%s.verbosity" % yeti_node, 2) nodes.append(yeti_node) + nodes.extend(transform_nodes) group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) From 7aa8a185d0f5d85b785f04de06adedef61a42f15 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 10:02:44 +0100 Subject: [PATCH 0503/1249] explicitly add transform node to container, add cbID to transform node for Look Manager --- .../plugins/maya/load/load_yeti_cache.py | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 0f58f1d735..35fdf4ea68 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -49,17 +49,32 @@ class YetiCacheLoader(api.Loader): nodes = [] for node, settings in fursettings.items(): + # Create transform + transform_name = "{}:{}".format(namespace, node.split("Shape")[0]) + transform_node = cmds.createNode("transform", name=transform_name) + # Create new pgYetiMaya node node_name = "{}:{}".format(namespace, node) - yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + yeti_node = cmds.createNode("pgYetiMaya", + name=node_name, + parent=transform_node) + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) - # Apply colorbleed ID to node + # Apply explicit colorbleed ID to node + shape_id = settings["cbId"] + asset_id = shape_id.split(":", 1)[0] + lib.set_id(node=yeti_node, - unique_id=settings["cbId"], + unique_id=shape_id, overwrite=True) settings.pop("cbId", None) + # Apply new colorbleed ID to transform node + _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) + for n, _id in _ids: + lib.set_id(n, unique_id=_id) + # Apply settings for attr, value in settings.items(): attribute = "%s.%s" % (yeti_node, attr) @@ -70,6 +85,7 @@ class YetiCacheLoader(api.Loader): # Create full cache path cache = os.path.join(self.fname, "{}.%04d.fur".format(node)) + cache = os.path.normpath(cache) cache_fname = self.validate_cache(cache) cache_path = os.path.join(self.fname, cache_fname) @@ -77,26 +93,28 @@ class YetiCacheLoader(api.Loader): cmds.setAttr("%s.cacheFileName" % yeti_node, cache_path, type="string") - + # Set file mode to cache cmds.setAttr("%s.fileMode" % yeti_node, 1) cmds.setAttr("%s.imageSearchPath" % yeti_node, image_search_path, type="string") + # Set verbosity for debug purposes cmds.setAttr("%s.verbosity" % yeti_node, 2) nodes.append(yeti_node) + nodes.append(transform_node) group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) - all_nodes = cmds.listRelatives(group_node, children=True) - all_nodes.append(group_node) - self[:] = all_nodes + nodes.append(group_node) + + self[:] = nodes return pipeline.containerise(name=name, namespace=namespace, - nodes=all_nodes, + nodes=nodes, context=context, loader=self.__class__.__name__) From c0772c505ff9e70ee4baeffc7abbc98852d50285 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 10:15:54 +0100 Subject: [PATCH 0504/1249] removed list all descendents from update, not needed anymore --- colorbleed/plugins/maya/load/load_yeti_cache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 35fdf4ea68..05a09ec1ad 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -144,8 +144,7 @@ class YetiCacheLoader(api.Loader): path = api.get_representation_path(representation) members = cmds.sets(container['objectName'], query=True) - all_members = cmds.listRelatives(members, ad=True) - yeti_node = cmds.ls(all_members, type="pgYetiMaya", long=True) + yeti_node = cmds.ls(members, type="pgYetiMaya", long=True) for node in yeti_node: node_name = node.split(":")[-1] From c52cddb466a73f915cfeb137e6ecadd6fd466435 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 10:21:11 +0100 Subject: [PATCH 0505/1249] preset viewport density to 0.1 --- colorbleed/plugins/maya/load/load_yeti_cache.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 05a09ec1ad..a575fa9341 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -89,12 +89,14 @@ class YetiCacheLoader(api.Loader): cache_fname = self.validate_cache(cache) cache_path = os.path.join(self.fname, cache_fname) - # Add filename to `cacheFileName` attribute, set to cache mode + # Preset the viewport density] + cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) + + # Add filename to `cacheFileName` attribute cmds.setAttr("%s.cacheFileName" % yeti_node, cache_path, type="string") - # Set file mode to cache - cmds.setAttr("%s.fileMode" % yeti_node, 1) + cmds.setAttr("%s.imageSearchPath" % yeti_node, image_search_path, type="string") @@ -102,6 +104,9 @@ class YetiCacheLoader(api.Loader): # Set verbosity for debug purposes cmds.setAttr("%s.verbosity" % yeti_node, 2) + # Enable the cache by setting the fil mode + cmds.setAttr("%s.fileMode" % yeti_node, 1) + nodes.append(yeti_node) nodes.append(transform_node) From d5dbc51d72439b5f37fd5ac2c279282e0e41e4ad Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 10:28:24 +0100 Subject: [PATCH 0506/1249] resolved conflict --- colorbleed/plugins/maya/load/load_yeti_cache.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 93ddc04d3e..a575fa9341 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -110,7 +110,6 @@ class YetiCacheLoader(api.Loader): nodes.append(yeti_node) nodes.append(transform_node) - group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) From d40775b3d091a86a7289a3093c8fa97996c2ba3f Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 11:40:47 +0100 Subject: [PATCH 0507/1249] cosmetics --- colorbleed/plugins/maya/load/load_yeti_cache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index a575fa9341..f8c2b05668 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -71,6 +71,7 @@ class YetiCacheLoader(api.Loader): settings.pop("cbId", None) # Apply new colorbleed ID to transform node + # TODO: get ID from transfrom in data to ensure consistency _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) for n, _id in _ids: lib.set_id(n, unique_id=_id) @@ -89,7 +90,7 @@ class YetiCacheLoader(api.Loader): cache_fname = self.validate_cache(cache) cache_path = os.path.join(self.fname, cache_fname) - # Preset the viewport density] + # Preset the viewport density cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) # Add filename to `cacheFileName` attribute @@ -104,7 +105,7 @@ class YetiCacheLoader(api.Loader): # Set verbosity for debug purposes cmds.setAttr("%s.verbosity" % yeti_node, 2) - # Enable the cache by setting the fil mode + # Enable the cache by setting the file mode cmds.setAttr("%s.fileMode" % yeti_node, 1) nodes.append(yeti_node) From e7e60da0b14ec836ce43e73d0905f443139ace2d Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 11:41:47 +0100 Subject: [PATCH 0508/1249] cosmetics --- colorbleed/plugins/maya/load/load_yeti_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index f8c2b05668..a4b2627b13 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -71,7 +71,7 @@ class YetiCacheLoader(api.Loader): settings.pop("cbId", None) # Apply new colorbleed ID to transform node - # TODO: get ID from transfrom in data to ensure consistency + # TODO: get ID from transform in data to ensure consistency _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) for n, _id in _ids: lib.set_id(n, unique_id=_id) From 155bb334859db6a338ebbeae811f4ab51849e1a9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 15:01:13 +0100 Subject: [PATCH 0509/1249] updated data for recreating nodes to include transform data --- .../maya/publish/collect_yeti_cache.py | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/colorbleed/plugins/maya/publish/collect_yeti_cache.py index 0cc1e981f1..96b9972540 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_cache.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api +from colorbleed.maya import lib SETTINGS = {"renderDensity", "renderWidth", @@ -22,11 +23,28 @@ class CollectYetiCache(pyblish.api.InstancePlugin): def process(self, instance): # Collect fur settings - settings = {} - for node in cmds.ls(instance, type="pgYetiMaya"): - settings[node] = {} + settings = {"nodes": []} + + # Get yeti nodes and their transforms + yeti_shapes = cmds.ls(instance, type="pgYetiMaya") + + for shape in yeti_shapes: + shape_data = {"transform": None, + "name": shape} + + # Get specific node attributes for attr in SETTINGS: - current = cmds.getAttr("%s.%s" % (node, attr)) - settings[node][attr] = current + current = cmds.getAttr("%s.%s" % (shape, attr)) + shape_data[attr] = current + + # Get transform data + parent = cmds.listRelatives(shape, parent=True)[0] + transform_data = {"name": parent, + "cbId": lib.get_id(parent)} + + # Store transform data + shape_data["transform"] = transform_data + + settings["nodes"].append(shape_data) instance.data["fursettings"] = settings From 1eaafbe545c892d9537c59b96a46493d0b8c117a Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 16:26:11 +0100 Subject: [PATCH 0510/1249] updated date structure for yeti cache nodes --- .../maya/publish/collect_yeti_cache.py | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/colorbleed/plugins/maya/publish/collect_yeti_cache.py index 96b9972540..550c780797 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_cache.py @@ -8,11 +8,23 @@ SETTINGS = {"renderDensity", "renderWidth", "renderLength", "increaseRenderBounds", + "imageSearchPath", "cbId"} class CollectYetiCache(pyblish.api.InstancePlugin): - """Collect all information of the Yeti caches""" + """Collect all information of the Yeti caches + + The information contains the following attributes per Yeti node + + - "renderDensity" + - "renderWidth" + - "renderLength" + - "increaseRenderBounds" + - "imageSearchPath" + + Other information is the name of the transform and it's Colorbleed ID + """ order = pyblish.api.CollectorOrder + 0.45 label = "Collect Yeti Cache" @@ -27,22 +39,24 @@ class CollectYetiCache(pyblish.api.InstancePlugin): # Get yeti nodes and their transforms yeti_shapes = cmds.ls(instance, type="pgYetiMaya") - for shape in yeti_shapes: shape_data = {"transform": None, - "name": shape} + "name": shape, + "cbId": lib.get_id(shape), + "attrs": None} # Get specific node attributes + attr_data = {} for attr in SETTINGS: current = cmds.getAttr("%s.%s" % (shape, attr)) - shape_data[attr] = current + attr_data[attr] = current # Get transform data parent = cmds.listRelatives(shape, parent=True)[0] - transform_data = {"name": parent, - "cbId": lib.get_id(parent)} + transform_data = {"name": parent, "cbId": lib.get_id(parent)} - # Store transform data + # Store collected data + shape_data["attrs"] = attr_data shape_data["transform"] = transform_data settings["nodes"].append(shape_data) From 34d91c75b67aa05f850b834baf64a60d5d4c3000 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 16:26:49 +0100 Subject: [PATCH 0511/1249] updated load logic to match collected data structure --- .../plugins/maya/load/load_yeti_cache.py | 49 +++++++++---------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index a4b2627b13..692380948a 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -47,45 +47,40 @@ class YetiCacheLoader(api.Loader): # Get node name from JSON nodes = [] - for node, settings in fursettings.items(): + node_data = fursettings["nodes"] + for node_settings in node_data: - # Create transform - transform_name = "{}:{}".format(namespace, node.split("Shape")[0]) - transform_node = cmds.createNode("transform", name=transform_name) + # Create transform node + transform = node_settings["transform"] + transform_node = cmds.createNode("transform", + name=transform["name"]) - # Create new pgYetiMaya node - node_name = "{}:{}".format(namespace, node) + lib.set_id(transform_node, transform["cbId"]) + + # Create pgYetiMaya node yeti_node = cmds.createNode("pgYetiMaya", - name=node_name, + name=node_settings["name"], parent=transform_node) - cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + lib.set_id(yeti_node, node_settings["cbId"]) - # Apply explicit colorbleed ID to node - shape_id = settings["cbId"] - asset_id = shape_id.split(":", 1)[0] + nodes.append(transform_node) + nodes.append(yeti_node) - lib.set_id(node=yeti_node, - unique_id=shape_id, - overwrite=True) - settings.pop("cbId", None) - - # Apply new colorbleed ID to transform node - # TODO: get ID from transform in data to ensure consistency - _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) - for n, _id in _ids: - lib.set_id(n, unique_id=_id) - - # Apply settings - for attr, value in settings.items(): + # Apply attributes to pgYetiMaya node + kwargs = {} + for attr, value in node_settings["attrs"].items(): attribute = "%s.%s" % (yeti_node, attr) - cmds.setAttr(attribute, value) + if isinstance(value, (str, unicode)): + cmds.setAttr(attribute, value, type="string") + continue + cmds.setAttr(attribute, value, **kwargs) # Ensure the node has no namespace identifiers - node = node.replace(":", "_") + node_name = yeti_node.replace(":", "_") # Create full cache path - cache = os.path.join(self.fname, "{}.%04d.fur".format(node)) + cache = os.path.join(self.fname, "{}.%04d.fur".format(node_name)) cache = os.path.normpath(cache) cache_fname = self.validate_cache(cache) cache_path = os.path.join(self.fname, cache_fname) From 427558db7968c7e25032d595c9bead0448c9f316 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 16:28:30 +0100 Subject: [PATCH 0512/1249] updated extract rig --- .../plugins/maya/publish/extract_yeti_rig.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index a4e4d8e019..2778856f57 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -38,15 +38,21 @@ def disconnected_attributes(settings, members): dst_attribute = "%s.%s" % (dst, connections[1]) # store connection pair - original_connection.append([src_attribute, dst_attribute]) - cmds.disconnectAttr(dst_attribute, src_attribute) - yield + if not cmds.isConnected(dst_attribute, src_attribute): + continue + cmds.disconnectAttr(dst_attribute, src_attribute) + original_connection.append([src_attribute, dst_attribute]) + yield finally: # restore connections for connection in original_connection: src, dest = connection - cmds.connectAttr(dest, src) + try: + cmds.connectAttr(dest, src) + except Exception as e: + print e, + continue class ExtractYetiRig(colorbleed.api.Extractor): @@ -96,9 +102,11 @@ class ExtractYetiRig(colorbleed.api.Extractor): attr_value = {"%s.imageSearchPath" % n: image_search_path for n in yeti_nodes} - # get input_SET members + # Get input_SET members input_set = [i for i in instance if i == "input_SET"] members = cmds.sets(input_set[0], query=True) + # Get all items + members = cmds.listRelatives(members, ad=True, fullPath=True) nodes = instance.data["setMembers"] with disconnected_attributes(settings, members): From dda1a582a99335918e54239e824914ba50b92bc0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 16:29:49 +0100 Subject: [PATCH 0513/1249] allowing all user defined attributes non-referenced nodes --- colorbleed/plugins/maya/publish/collect_look.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 4dfd29cadd..ead79a00b9 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -27,8 +27,12 @@ def get_look_attrs(node): """ + # When referenced get only attributes that are "changed since file open" + # which includes any reference edits, otherwise take *all* user defined + # attributes + is_referenced = cmds.referenceQuery(node, isNodeReferenced=True) result = cmds.listAttr(node, userDefined=True, - changedSinceFileOpen=True) or [] + changedSinceFileOpen=is_referenced) or [] # `cbId` is added when a scene is saved, ignore by default if "cbId" in result: @@ -91,7 +95,7 @@ class CollectLook(pyblish.api.InstancePlugin): for objset in list(sets): self.log.debug("From %s.." % objset) - # Get all nodes of the current objectSet + # Get all nodes of the current objectSet (shadingEngine) for member in cmds.ls(cmds.sets(objset, query=True), long=True): member_data = self.collect_member_data(member, instance_lookup) From c051842e192ba2999571a6361dec66e5dba26e2c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 1 Dec 2017 17:13:25 +0100 Subject: [PATCH 0514/1249] fixed issue with loading cache, added explicit namespace for yetinode --- colorbleed/plugins/maya/load/load_yeti_cache.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 692380948a..fbb8a86faf 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -58,8 +58,10 @@ class YetiCacheLoader(api.Loader): lib.set_id(transform_node, transform["cbId"]) # Create pgYetiMaya node + original_node = node_settings["name"] + node_name = "{}:{}".format(namespace, original_node) yeti_node = cmds.createNode("pgYetiMaya", - name=node_settings["name"], + name=node_name, parent=transform_node) lib.set_id(yeti_node, node_settings["cbId"]) @@ -77,7 +79,7 @@ class YetiCacheLoader(api.Loader): cmds.setAttr(attribute, value, **kwargs) # Ensure the node has no namespace identifiers - node_name = yeti_node.replace(":", "_") + node_name = original_node.replace(":", "_") # Create full cache path cache = os.path.join(self.fname, "{}.%04d.fur".format(node_name)) From 20c719abaeac8027f3e2b6a95e5731725d94fe85 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 10:58:20 +0100 Subject: [PATCH 0515/1249] added backwards compatibility support --- .../plugins/maya/load/load_yeti_cache.py | 212 +++++++++++++----- 1 file changed, 152 insertions(+), 60 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index fbb8a86faf..769b17878f 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -47,66 +47,12 @@ class YetiCacheLoader(api.Loader): # Get node name from JSON nodes = [] - node_data = fursettings["nodes"] - for node_settings in node_data: - - # Create transform node - transform = node_settings["transform"] - transform_node = cmds.createNode("transform", - name=transform["name"]) - - lib.set_id(transform_node, transform["cbId"]) - - # Create pgYetiMaya node - original_node = node_settings["name"] - node_name = "{}:{}".format(namespace, original_node) - yeti_node = cmds.createNode("pgYetiMaya", - name=node_name, - parent=transform_node) - - lib.set_id(yeti_node, node_settings["cbId"]) - - nodes.append(transform_node) - nodes.append(yeti_node) - - # Apply attributes to pgYetiMaya node - kwargs = {} - for attr, value in node_settings["attrs"].items(): - attribute = "%s.%s" % (yeti_node, attr) - if isinstance(value, (str, unicode)): - cmds.setAttr(attribute, value, type="string") - continue - cmds.setAttr(attribute, value, **kwargs) - - # Ensure the node has no namespace identifiers - node_name = original_node.replace(":", "_") - - # Create full cache path - cache = os.path.join(self.fname, "{}.%04d.fur".format(node_name)) - cache = os.path.normpath(cache) - cache_fname = self.validate_cache(cache) - cache_path = os.path.join(self.fname, cache_fname) - - # Preset the viewport density - cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) - - # Add filename to `cacheFileName` attribute - cmds.setAttr("%s.cacheFileName" % yeti_node, - cache_path, - type="string") - - cmds.setAttr("%s.imageSearchPath" % yeti_node, - image_search_path, - type="string") - - # Set verbosity for debug purposes - cmds.setAttr("%s.verbosity" % yeti_node, 2) - - # Enable the cache by setting the file mode - cmds.setAttr("%s.fileMode" % yeti_node, 1) - - nodes.append(yeti_node) - nodes.append(transform_node) + if "nodes" in fursettings: + node_data = fursettings["nodes"] + self.create_nodes(namespace, image_search_path, node_data) + else: + # Backwards compatibilty + self.create_nodes_old(namespace, image_search_path, fursettings) group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) @@ -199,3 +145,149 @@ class YetiCacheLoader(api.Loader): return filename + def create_nodes(self, namespace, image_search_path, settings): + + # Get node name from JSON + nodes = [] + for node_settings in settings: + + # Create transform node + transform = node_settings["transform"] + transform_node = cmds.createNode("transform", + name=transform["name"]) + + lib.set_id(transform_node, transform["cbId"]) + + # Create pgYetiMaya node + original_node = node_settings["name"] + node_name = "{}:{}".format(namespace, original_node) + yeti_node = cmds.createNode("pgYetiMaya", + name=node_name, + parent=transform_node) + + lib.set_id(yeti_node, node_settings["cbId"]) + + nodes.append(transform_node) + nodes.append(yeti_node) + + # Apply attributes to pgYetiMaya node + kwargs = {} + for attr, value in node_settings["attrs"].items(): + attribute = "%s.%s" % (yeti_node, attr) + if isinstance(value, (str, unicode)): + cmds.setAttr(attribute, value, type="string") + continue + cmds.setAttr(attribute, value, **kwargs) + + # Ensure the node has no namespace identifiers + node_name = original_node.replace(":", "_") + + # Create full cache path + cache = os.path.join(self.fname, "{}.%04d.fur".format(node_name)) + cache = os.path.normpath(cache) + cache_fname = self.validate_cache(cache) + cache_path = os.path.join(self.fname, cache_fname) + + # Preset the viewport density + cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) + + # Add filename to `cacheFileName` attribute + cmds.setAttr("%s.cacheFileName" % yeti_node, + cache_path, + type="string") + + cmds.setAttr("%s.imageSearchPath" % yeti_node, + image_search_path, + type="string") + + # Set verbosity for debug purposes + cmds.setAttr("%s.verbosity" % yeti_node, 2) + + # Enable the cache by setting the file mode + cmds.setAttr("%s.fileMode" % yeti_node, 1) + + nodes.append(yeti_node) + nodes.append(transform_node) + + return nodes + + def create_nodes_old(self, namespace, image_search_path, settings): + """Create nodes with the old logic + + Support previously published assets + + Args: + namespace(str): unique name to identify the nodes + image_search_path (str): directory path to textures + settings(dict): collection of node names with settings + Returns: + list: all created pgYetiMaya nodes and their transforms + + """ + + nodes = [] + for node, node_settings in settings.items(): + + # Create transform + transform_name = "{}:{}".format(namespace, node.split("Shape")[0]) + transform_node = cmds.createNode("transform", name=transform_name) + + # Create new pgYetiMaya node + node_name = "{}:{}".format(namespace, node) + yeti_node = cmds.createNode("pgYetiMaya", + name=node_name, + parent=transform_node) + + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) + + # Apply explicit colorbleed ID to node + shape_id = settings["cbId"] + asset_id = shape_id.split(":", 1)[0] + + lib.set_id(node=yeti_node, + unique_id=shape_id, + overwrite=True) + settings.pop("cbId", None) + + # Apply new colorbleed ID to transform node + # TODO: get ID from transform in data to ensure consistency + _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) + for n, _id in _ids: + lib.set_id(n, unique_id=_id) + + # Apply settings + for attr, value in settings.items(): + attribute = "%s.%s" % (yeti_node, attr) + cmds.setAttr(attribute, value) + + # Ensure the node has no namespace identifiers + node = node.replace(":", "_") + + # Create full cache path + cache = os.path.join(self.fname, "{}.%04d.fur".format(node)) + cache = os.path.normpath(cache) + cache_fname = self.validate_cache(cache) + cache_path = os.path.join(self.fname, cache_fname) + + # Preset the viewport density + cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) + + # Add filename to `cacheFileName` attribute + cmds.setAttr("%s.cacheFileName" % yeti_node, + cache_path, + type="string") + + cmds.setAttr("%s.imageSearchPath" % yeti_node, + image_search_path, + type="string") + + # Set verbosity for debug purposes + cmds.setAttr("%s.verbosity" % yeti_node, 2) + + # Enable the cache by setting the file mode + cmds.setAttr("%s.fileMode" % yeti_node, 1) + + nodes.append(yeti_node) + nodes.append(transform_node) + + return nodes \ No newline at end of file From 40b68f9839c570581f916e69200dd25cea6b3d91 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 12:42:26 +0100 Subject: [PATCH 0516/1249] fixed missing nodes for container, added message for backwards compatibility --- colorbleed/plugins/maya/load/load_yeti_cache.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 769b17878f..836fc90fb9 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -46,13 +46,16 @@ class YetiCacheLoader(api.Loader): image_search_path = os.path.normpath(resource_folder) # Get node name from JSON - nodes = [] if "nodes" in fursettings: node_data = fursettings["nodes"] - self.create_nodes(namespace, image_search_path, node_data) + nodes = self.create_nodes(namespace, image_search_path, node_data) else: # Backwards compatibilty - self.create_nodes_old(namespace, image_search_path, fursettings) + self.log.info("Encountered old data, " + "using backwards compatibility") + nodes = self.create_nodes_old(namespace, + image_search_path, + fursettings) group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) @@ -241,13 +244,13 @@ class YetiCacheLoader(api.Loader): cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) # Apply explicit colorbleed ID to node - shape_id = settings["cbId"] + shape_id = node_settings["cbId"] asset_id = shape_id.split(":", 1)[0] lib.set_id(node=yeti_node, unique_id=shape_id, overwrite=True) - settings.pop("cbId", None) + node_settings.pop("cbId", None) # Apply new colorbleed ID to transform node # TODO: get ID from transform in data to ensure consistency @@ -256,7 +259,7 @@ class YetiCacheLoader(api.Loader): lib.set_id(n, unique_id=_id) # Apply settings - for attr, value in settings.items(): + for attr, value in node_settings.items(): attribute = "%s.%s" % (yeti_node, attr) cmds.setAttr(attribute, value) @@ -290,4 +293,4 @@ class YetiCacheLoader(api.Loader): nodes.append(yeti_node) nodes.append(transform_node) - return nodes \ No newline at end of file + return nodes From f810d048616580db2cf11a249df4621a67beb7e1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 14:40:07 +0100 Subject: [PATCH 0517/1249] removed backwards compatibility, update source data files --- .../plugins/maya/load/load_yeti_cache.py | 114 ++---------------- 1 file changed, 9 insertions(+), 105 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 836fc90fb9..aa9275e258 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -38,24 +38,13 @@ class YetiCacheLoader(api.Loader): fursettings = json.load(fp) # Check if resources map exists - # TODO: should be stored in fursettings - image_search_path = "" - version_folder = os.path.dirname(self.fname) - resource_folder = os.path.join(version_folder, "resources") - if os.path.exists(resource_folder): - image_search_path = os.path.normpath(resource_folder) - # Get node name from JSON - if "nodes" in fursettings: - node_data = fursettings["nodes"] - nodes = self.create_nodes(namespace, image_search_path, node_data) - else: - # Backwards compatibilty - self.log.info("Encountered old data, " - "using backwards compatibility") - nodes = self.create_nodes_old(namespace, - image_search_path, - fursettings) + if "nodes" not in fursettings: + raise RuntimeError("Encountered invalid data, expect 'nodes' in " + "fursettings.") + + node_data = fursettings["nodes"] + nodes = self.create_nodes(namespace, node_data) group_name = "{}:{}".format(namespace, asset["name"]) group_node = cmds.group(nodes, name=group_name) @@ -148,7 +137,7 @@ class YetiCacheLoader(api.Loader): return filename - def create_nodes(self, namespace, image_search_path, settings): + def create_nodes(self, namespace, settings): # Get node name from JSON nodes = [] @@ -156,8 +145,8 @@ class YetiCacheLoader(api.Loader): # Create transform node transform = node_settings["transform"] - transform_node = cmds.createNode("transform", - name=transform["name"]) + transform_name = "{}:{}".format(namespace, transform["name"]) + transform_node = cmds.createNode("transform", name=transform_name) lib.set_id(transform_node, transform["cbId"]) @@ -199,91 +188,6 @@ class YetiCacheLoader(api.Loader): cache_path, type="string") - cmds.setAttr("%s.imageSearchPath" % yeti_node, - image_search_path, - type="string") - - # Set verbosity for debug purposes - cmds.setAttr("%s.verbosity" % yeti_node, 2) - - # Enable the cache by setting the file mode - cmds.setAttr("%s.fileMode" % yeti_node, 1) - - nodes.append(yeti_node) - nodes.append(transform_node) - - return nodes - - def create_nodes_old(self, namespace, image_search_path, settings): - """Create nodes with the old logic - - Support previously published assets - - Args: - namespace(str): unique name to identify the nodes - image_search_path (str): directory path to textures - settings(dict): collection of node names with settings - Returns: - list: all created pgYetiMaya nodes and their transforms - - """ - - nodes = [] - for node, node_settings in settings.items(): - - # Create transform - transform_name = "{}:{}".format(namespace, node.split("Shape")[0]) - transform_node = cmds.createNode("transform", name=transform_name) - - # Create new pgYetiMaya node - node_name = "{}:{}".format(namespace, node) - yeti_node = cmds.createNode("pgYetiMaya", - name=node_name, - parent=transform_node) - - cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) - - # Apply explicit colorbleed ID to node - shape_id = node_settings["cbId"] - asset_id = shape_id.split(":", 1)[0] - - lib.set_id(node=yeti_node, - unique_id=shape_id, - overwrite=True) - node_settings.pop("cbId", None) - - # Apply new colorbleed ID to transform node - # TODO: get ID from transform in data to ensure consistency - _ids = lib.generate_ids(nodes=[transform_node], asset_id=asset_id) - for n, _id in _ids: - lib.set_id(n, unique_id=_id) - - # Apply settings - for attr, value in node_settings.items(): - attribute = "%s.%s" % (yeti_node, attr) - cmds.setAttr(attribute, value) - - # Ensure the node has no namespace identifiers - node = node.replace(":", "_") - - # Create full cache path - cache = os.path.join(self.fname, "{}.%04d.fur".format(node)) - cache = os.path.normpath(cache) - cache_fname = self.validate_cache(cache) - cache_path = os.path.join(self.fname, cache_fname) - - # Preset the viewport density - cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) - - # Add filename to `cacheFileName` attribute - cmds.setAttr("%s.cacheFileName" % yeti_node, - cache_path, - type="string") - - cmds.setAttr("%s.imageSearchPath" % yeti_node, - image_search_path, - type="string") - # Set verbosity for debug purposes cmds.setAttr("%s.verbosity" % yeti_node, 2) From a6e1a1bf468a50fabb57856cf66865636585abc5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 14:40:45 +0100 Subject: [PATCH 0518/1249] updated keys for data dict, explicit naming relevant to package --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 09f0f311bb..b29ce1cb95 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -57,8 +57,8 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # The plug must go in the socket, remember this for the loader inputs.append({"connections": [src_attr, dest_attr], - "plugID": lib.get_id(dest_node), - "socketID": lib.get_id(src_node)}) + "destinationID": lib.get_id(dest_node), + "sourceID": lib.get_id(src_node)}) # Collect any textures if used yeti_resources = [] From c61b8ab6360e2de42c7d4eb033f6121cb45238d8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 14:42:03 +0100 Subject: [PATCH 0519/1249] udpated keys in decorator function to match collector keys --- colorbleed/plugins/maya/publish/extract_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 2778856f57..a78c736282 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -18,7 +18,7 @@ def disconnected_attributes(settings, members): for input in settings["inputs"]: # get source - socket_id = input["socketID"] + socket_id = input["sourceId"] sources = lib.lsattr("cbId", socket_id) sources = [i for i in sources if not cmds.referenceQuery(i, isNodeReferenced=True) @@ -26,7 +26,7 @@ def disconnected_attributes(settings, members): src = sources[0] # get destination - plug_id = input["plugID"] + plug_id = input["destinationID"] plugs = lib.lsattr("cbId", plug_id) destinations = [i for i in plugs if i not in members and i not in sources] From f1c623bd3bf2fbae04a81480b42000dda55cc36c Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 4 Dec 2017 16:27:44 +0100 Subject: [PATCH 0520/1249] updated keys of rigsettings to meet Maya logic --- .../plugins/maya/publish/collect_yeti_rig.py | 18 ++++++----- .../plugins/maya/publish/extract_yeti_rig.py | 30 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index b29ce1cb95..ed0d84907d 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -37,28 +37,30 @@ class CollectYetiRig(pyblish.api.InstancePlugin): fullPath=True) or input_content # Get all the shapes - input_shapes = cmds.ls(input_nodes, long=True) + input_shapes = cmds.ls(input_nodes, long=True, noIntermediate=True) # Store all connections + print "input shape:", input_shapes connections = cmds.listConnections(input_shapes, source=True, destination=False, connections=True, plugs=True) or [] - # Group per source, destination pair - grouped = [(item, connections[i+1]) for i, item in + # Group per source, destination pair. We need to reverse the connection + # list as it comes in with the shape used to query first while that + # shape is the destination of the connection + grouped = [(connections[i+1], item) for i, item in enumerate(connections) if i % 2 == 0] inputs = [] for src, dest in grouped: - src_node, src_attr = src.split(".", 1) + source_node, source_attr = src.split(".", 1) dest_node, dest_attr = dest.split(".", 1) - # The plug must go in the socket, remember this for the loader - inputs.append({"connections": [src_attr, dest_attr], - "destinationID": lib.get_id(dest_node), - "sourceID": lib.get_id(src_node)}) + inputs.append({"connections": [source_attr, dest_attr], + "sourceID": lib.get_id(source_node), + "destinationID": lib.get_id(dest_node)}) # Collect any textures if used yeti_resources = [] diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index a78c736282..6a13a0343b 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -17,31 +17,29 @@ def disconnected_attributes(settings, members): try: for input in settings["inputs"]: - # get source - socket_id = input["sourceId"] - sources = lib.lsattr("cbId", socket_id) - sources = [i for i in sources if + # Get source shapes + source_nodes = lib.lsattr("cbId", input["sourceID"]) + sources = [i for i in source_nodes if not cmds.referenceQuery(i, isNodeReferenced=True) and i in members] - src = sources[0] + source = sources[0] - # get destination - plug_id = input["destinationID"] - plugs = lib.lsattr("cbId", plug_id) - destinations = [i for i in plugs if i not in members and - i not in sources] - dst = destinations[0] + # Get destination shapes (the shapes used as hook up) + destination_nodes = lib.lsattr("cbId", input["destinationID"]) + destinations = [i for i in destination_nodes if i not in members + and i not in sources] + destination = destinations[0] - # break connection + # Break connection connections = input["connections"] - src_attribute = "%s.%s" % (src, connections[0]) - dst_attribute = "%s.%s" % (dst, connections[1]) + src_attribute = "%s.%s" % (source, connections[0]) + dst_attribute = "%s.%s" % (destination, connections[1]) # store connection pair - if not cmds.isConnected(dst_attribute, src_attribute): + if not cmds.isConnected(src_attribute, dst_attribute): continue - cmds.disconnectAttr(dst_attribute, src_attribute) + cmds.disconnectAttr(src_attribute, dst_attribute) original_connection.append([src_attribute, dst_attribute]) yield finally: From 6b9a1b2449cf4bb8363262a7a61f2cc2a8aefb61 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 16:46:47 +0100 Subject: [PATCH 0521/1249] force connections to time node --- colorbleed/plugins/maya/load/load_yeti_cache.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index aa9275e258..8f3f9aeb26 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -194,6 +194,9 @@ class YetiCacheLoader(api.Loader): # Enable the cache by setting the file mode cmds.setAttr("%s.fileMode" % yeti_node, 1) + # Connect to the time node + cmds.connectAttr("time1.timeOut", "%s.currentTime" % yeti_node) + nodes.append(yeti_node) nodes.append(transform_node) From 2652bfc7ec4163c861d8cbf066f8a11fdb753ef2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 16:47:45 +0100 Subject: [PATCH 0522/1249] collect all members, removed redundant line, renamed variable --- .../plugins/maya/publish/extract_yeti_rig.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 6a13a0343b..e3a273fdbf 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -13,7 +13,7 @@ from cb.utils.maya import context def disconnected_attributes(settings, members): members = cmds.ls(members, long=True) - original_connection = [] + original_connections = [] try: for input in settings["inputs"]: @@ -40,14 +40,13 @@ def disconnected_attributes(settings, members): continue cmds.disconnectAttr(src_attribute, dst_attribute) - original_connection.append([src_attribute, dst_attribute]) + original_connections.append([src_attribute, dst_attribute]) yield finally: # restore connections - for connection in original_connection: - src, dest = connection + for connection in original_connections: try: - cmds.connectAttr(dest, src) + cmds.connectAttr(connection[0], connection[1]) except Exception as e: print e, continue @@ -102,9 +101,10 @@ class ExtractYetiRig(colorbleed.api.Extractor): # Get input_SET members input_set = [i for i in instance if i == "input_SET"] - members = cmds.sets(input_set[0], query=True) # Get all items - members = cmds.listRelatives(members, ad=True, fullPath=True) + set_members = cmds.sets(input_set[0], query=True) + members = cmds.listRelatives(set_members, ad=True, fullPath=True) + members += cmds.ls(set_members, long=True) nodes = instance.data["setMembers"] with disconnected_attributes(settings, members): From 0f178ede9ae7e0f1b8a471cd3cd99513b03605c6 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 16:54:39 +0100 Subject: [PATCH 0523/1249] corrected attribute name --- colorbleed/plugins/maya/load/load_yeti_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 8f3f9aeb26..ce825a6edf 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -195,7 +195,7 @@ class YetiCacheLoader(api.Loader): cmds.setAttr("%s.fileMode" % yeti_node, 1) # Connect to the time node - cmds.connectAttr("time1.timeOut", "%s.currentTime" % yeti_node) + cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) nodes.append(yeti_node) nodes.append(transform_node) From 3ee2b2a38d815920a5ad01f13a94779232d5495b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 17:20:18 +0100 Subject: [PATCH 0524/1249] removed debug print --- colorbleed/plugins/maya/load/load_yeti_cache.py | 15 +++++++++++++-- .../plugins/maya/publish/collect_yeti_rig.py | 1 - 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index ce825a6edf..cfc9234df9 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -84,15 +84,26 @@ class YetiCacheLoader(api.Loader): def update(self, container, representation): path = api.get_representation_path(representation) + namespace = "{}:".format(container["namespace"]) members = cmds.sets(container['objectName'], query=True) - yeti_node = cmds.ls(members, type="pgYetiMaya", long=True) + yeti_node = cmds.ls(members, type="pgYetiMaya") + + # TODO: Count the amount of nodes cached + # To ensure new nodes get created or old nodes get destroyed for node in yeti_node: - node_name = node.split(":")[-1] + # Remove local given namespace + node_name = node.split(namespace, 1)[-1] + node_name = node_name.replace(":", "_") + + # Check if the node has a cache tmp_cache = os.path.join(path, "{}.%04d.fur".format(node_name)) fpath = self.validate_cache(os.path.normpath(tmp_cache)) + + # Update the attribute cmds.setAttr("{}.cacheFileName".format(node), fpath, type="string") + # Update the container cmds.setAttr("{}.representation".format(container["objectName"]), str(representation["_id"]), type="string") diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index ed0d84907d..9d26e0b4b5 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -40,7 +40,6 @@ class CollectYetiRig(pyblish.api.InstancePlugin): input_shapes = cmds.ls(input_nodes, long=True, noIntermediate=True) # Store all connections - print "input shape:", input_shapes connections = cmds.listConnections(input_shapes, source=True, destination=False, From 63c82f0639aa143882de7a300917ddcf00f54715 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 17:20:40 +0100 Subject: [PATCH 0525/1249] removed unused import --- colorbleed/plugins/maya/publish/validate_model_content.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 75959f66b0..06b29da68d 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -1,5 +1,3 @@ -import pprint - from maya import cmds import pyblish.api From 03b15a42abb6c190e2a7fffcc7708a970622b1ea Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 6 Dec 2017 18:15:56 +0100 Subject: [PATCH 0526/1249] implement legacy renderlayer support --- .../plugins/maya/create/colorbleed_renderglobals.py | 1 + colorbleed/plugins/maya/publish/collect_renderlayers.py | 9 +++++---- colorbleed/plugins/maya/publish/submit_deadline.py | 3 +++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 5ce04495cc..3ca857a11c 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -26,6 +26,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["suspendPublishJob"] = False data["includeDefaultRenderLayer"] = False + data["useLegacyRenderLayers"] = True data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index e75a0b2dae..cddfd54682 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -31,8 +31,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): "renderGlobals node") return - attr = "{}.includeDefaultRenderLayer".format(render_globals) - use_defaultlayer = cmds.getAttr(attr) + default_layer = "{}.includeDefaultRenderLayer".format(render_globals) + use_defaultlayer = cmds.getAttr(default_layer) # Get render layers renderlayers = [i for i in cmds.ls(type="renderLayer") if not @@ -107,8 +107,9 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): attributes = maya.read(render_globals) options = {"renderGlobals": {}} - - options['renderGlobals']['Priority'] = attributes['priority'] + options["renderGlobals"]["Priority"] = attributes["priority"] + legacy = attributes["useLegacyRenderLayers"] + options["renderGlobals"]["UseLegacyRenderLayers"] = legacy # Machine list machine_list = attributes["machineList"] diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index edde6909c5..6a9de663f4 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -160,6 +160,9 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): # Only render layers are considered renderable in this pipeline "UsingRenderLayers": True, + # Use legacy Render Layer system + "UseLegacyRenderLayers": True, + # Render only this layer "RenderLayer": renderlayer, From 0721436f2d48b2ef38287b986a0b6fbed67e9bf3 Mon Sep 17 00:00:00 2001 From: Wijnand Koreman Date: Wed, 6 Dec 2017 21:31:44 +0100 Subject: [PATCH 0527/1249] fixed bug in use of renderlayer system toggle --- colorbleed/plugins/maya/publish/submit_deadline.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 6a9de663f4..4117320522 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -89,6 +89,8 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty + renderlayer_globals = instance.data["renderGlobals"] + legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] deadline_user = context.data.get("deadlineUser", getpass.getuser()) jobname = "%s - %s" % (fname, instance.name) @@ -161,7 +163,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "UsingRenderLayers": True, # Use legacy Render Layer system - "UseLegacyRenderLayers": True, + "UseLegacyRenderLayers": legacy_layers, # Render only this layer "RenderLayer": renderlayer, From 039caaa9ae4b34952e93ee8e648a47f6ed536bf6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 7 Dec 2017 14:29:29 +0100 Subject: [PATCH 0528/1249] Pass on renderer with maya to deadline submissions (fix REN-7) --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 4117320522..8ba8e0296f 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -169,7 +169,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "RenderLayer": renderlayer, # Determine which renderer to use from the file itself - "Renderer": "file", + "Renderer": instance.data["renderer"], # Resolve relative references "ProjectPath": workspace, From 8e321aa1e8bcc10749d90c2c34cfc9a017c27ae3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 7 Dec 2017 16:26:19 +0100 Subject: [PATCH 0529/1249] catch None as list in case no relatives are there --- colorbleed/plugins/maya/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index e3a273fdbf..a086accfe8 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -103,7 +103,7 @@ class ExtractYetiRig(colorbleed.api.Extractor): input_set = [i for i in instance if i == "input_SET"] # Get all items set_members = cmds.sets(input_set[0], query=True) - members = cmds.listRelatives(set_members, ad=True, fullPath=True) + members = cmds.listRelatives(set_members, ad=True, fullPath=True) or [] members += cmds.ls(set_members, long=True) nodes = instance.data["setMembers"] From ff93354068361270087a871d880ed1c91d0fe27a Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 7 Dec 2017 16:40:05 +0100 Subject: [PATCH 0530/1249] cosmetics --- colorbleed/plugins/maya/load/load_yeti_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index cfc9234df9..0544868340 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -46,7 +46,7 @@ class YetiCacheLoader(api.Loader): node_data = fursettings["nodes"] nodes = self.create_nodes(namespace, node_data) - group_name = "{}:{}".format(namespace, asset["name"]) + group_name = "{}:{}".format(namespace, name) group_node = cmds.group(nodes, name=group_name) nodes.append(group_node) @@ -94,10 +94,10 @@ class YetiCacheLoader(api.Loader): for node in yeti_node: # Remove local given namespace node_name = node.split(namespace, 1)[-1] - node_name = node_name.replace(":", "_") + file_name = node_name.replace(":", "_") # Check if the node has a cache - tmp_cache = os.path.join(path, "{}.%04d.fur".format(node_name)) + tmp_cache = os.path.join(path, "{}.%04d.fur".format(file_name)) fpath = self.validate_cache(os.path.normpath(tmp_cache)) # Update the attribute From ccd40764a2ed956ed6d1c2e6c9d5b41f0e1b4ac2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 8 Dec 2017 10:34:17 +0100 Subject: [PATCH 0531/1249] cosmetics --- colorbleed/maya/__init__.py | 3 +-- .../maya/publish/_validate_yeti_procedural.py | 23 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_yeti_procedural.py diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 7426aac905..0fdad87655 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -75,7 +75,6 @@ def on_init(_): except Exception as exc: print(exc) - cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) force_load_deferred("mtoa") @@ -133,4 +132,4 @@ def on_open(_): dialog.setMessage("There are outdated containers in " "your Maya scene.") dialog.on_show.connect(_on_show_inventory) - dialog.show() \ No newline at end of file + dialog.show() diff --git a/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py b/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py deleted file mode 100644 index 4d4181256a..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_yeti_procedural.py +++ /dev/null @@ -1,23 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidateYetiProcedurals(pyblish.api.InstancePlugin): - """Validates mapped resources. - - These are external files to the current application, for example - these could be textures, image planes, cache files or other linked - media. - - This validates: - - The resources are existing files. - - The resources have correctly collected the data. - - """ - - order = colorbleed.api.ValidateContentsOrder - label = "Yeti Procedurals" - - def process(self, instance): - return True - From 01ec252978bc2b60dfa3b890e69fa026959b899d Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 8 Dec 2017 10:34:43 +0100 Subject: [PATCH 0532/1249] validate if input nodes have connections --- .../maya/publish/validate_yeti_rig_inputs.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py index bc801b9849..9cccd4dea7 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py @@ -4,13 +4,13 @@ import pyblish.api import colorbleed.api -class ValidateYetiRigInputMeshes(pyblish.api.Validator): - """Validate if all pgYetiMaya nodes have at least one input shape""" +class ValidateYetiRigInputShapesRequired(pyblish.api.Validator): + """Validate if all input nodes have at least one incoming connection""" order = colorbleed.api.ValidateContentsOrder hosts = ["maya"] families = ["colorbleed.yetiRig"] - label = "Yeti Rig Input Shapes" + label = "Yeti Rig Input Shapes Required" actions = [colorbleed.api.SelectInvalidAction] def process(self, instance): @@ -24,17 +24,15 @@ class ValidateYetiRigInputMeshes(pyblish.api.Validator): invalid = [] - # Get yeti nodes - yeti_nodes = cmds.listRelatives(instance, - type="pgYetiMaya", - allDescendents=True, - fullPath=True) - # Get input meshes per node - for node in yeti_nodes: - attribute = "%s.inputGeometry" % node - input_meshes = cmds.listConnections(attribute, source=True) - if not input_meshes: - cls.log.error("'%s' has no input meshes" % node) + input_set = [i for i in instance if i == "input_SET"][0] + input_nodes = cmds.sets(input_set, query=True) + for node in input_nodes: + incoming = cmds.listConnections(node, + source=True, + destination=False, + connections=True, + plugs=True) + if not incoming: invalid.append(node) - return [] + return invalid From c3570d70b2b8afa44d9f77330c9466c0f8e3eca8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 8 Dec 2017 11:36:39 +0100 Subject: [PATCH 0533/1249] Fix updating of referenced content when .placeHolderList attributes are present in the container; this update will also remove any .placeHolderList members when updating since they are redundant data for the container --- colorbleed/maya/plugin.py | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index aecb55611b..6b8653e95e 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -3,11 +3,11 @@ from avalon import api class ReferenceLoader(api.Loader): """A basic ReferenceLoader for Maya - + This will implement the basic behavior for a loader to inherit from that will containerize the reference and will implement the `remove` and `update` logic. - + """ def load(self, context, @@ -56,9 +56,14 @@ class ReferenceLoader(api.Loader): path = api.get_representation_path(representation) - # Assume asset has been referenced - reference_node = next((node for node in cmds.sets(node, query=True) - if cmds.nodeType(node) == "reference"), None) + # Get reference node from container members + members = cmds.sets(node, query=True, nodesOnly=True) + references = cmds.ls(members, exactType="reference", objectsOnly=True) + assert references, "No reference node found in container" + if len(set(references)) > 1: + self.log.warning("More than one reference node found in " + "container - using first one: %s", references) + reference_node = references[0] file_type = { "ma": "mayaAscii", @@ -69,7 +74,10 @@ class ReferenceLoader(api.Loader): assert file_type, "Unsupported representation: %s" % representation assert os.path.exists(path), "%s does not exist." % path - cmds.file(path, loadReference=reference_node, type=file_type) + content = cmds.file(path, + loadReference=reference_node, + type=file_type, + returnNewNodes=True) # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. @@ -77,11 +85,15 @@ class ReferenceLoader(api.Loader): self.log.info("Setting %s.verticesOnlySet to False", node) cmds.setAttr(node + ".verticesOnlySet", False) - # TODO: Add all new nodes in the reference to the container - # Currently new nodes in an updated reference are not added to the - # container whereas actually they should be! - nodes = cmds.referenceQuery(reference_node, nodes=True, dagPath=True) - cmds.sets(nodes, forceElement=node) + # Add new nodes of the reference to the container + cmds.sets(content, forceElement=node) + + # Remove any placeHolderList attribute entries from the set that + # are remaining from nodes being removed from the referenced file. + members = cmds.sets(node, query=True) + invalid = [x for x in members if ".placeHolderList" in x] + if invalid: + cmds.sets(invalid, remove=node) # Update metadata cmds.setAttr(node + ".representation", From 0f2f326e6efc2eca106bbc5001d1e48986d59a82 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 8 Dec 2017 16:56:19 +0100 Subject: [PATCH 0534/1249] fixed issue with clashing plugins --- colorbleed/plugins/maya/create/colorbleed_yeti_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index df71d203c9..1c390db4ba 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -4,8 +4,8 @@ import avalon.maya from colorbleed.maya import lib -class CreateYetiRig(avalon.maya.Creator): - """Output for procedural plugin nodes ( Yeti / XGen / etc)""" +class CreateYetiCache(avalon.maya.Creator): + """Output for procedural plugin nodes of Yeti """ name = "yetiDefault" label = "Yeti Cache" @@ -13,7 +13,7 @@ class CreateYetiRig(avalon.maya.Creator): icon = "pagelines" def __init__(self, *args, **kwargs): - super(CreateYetiRig, self).__init__(*args, **kwargs) + super(CreateYetiCache, self).__init__(*args, **kwargs) data = OrderedDict(self.data) data["peroll"] = 0 From 4bfc1aec424b30005002a0ed4b45ce1e06838d59 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 11 Dec 2017 16:27:22 +0100 Subject: [PATCH 0535/1249] Fix model content validation so it checks all descendants - this also fixes the "No visible shapes in the model instance" always being displayed --- .../maya/publish/validate_model_content.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 06b29da68d..d953be56bb 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -27,6 +27,15 @@ class ValidateModelContent(pyblish.api.InstancePlugin): cls.log.error("Instance has no nodes!") return True + # All children will be included in the extracted export so we also + # validate *all* descendents of the set members and we skip any + # intermediate shapes + descendants = cmds.listRelatives(content_instance, + allDescendents=True, + fullPath=True) or [] + descendants = cmds.ls(descendants, noIntermediate=True, long=True) + content_instance = list(set(content_instance + descendants)) + # Ensure only valid node types allowed = ('mesh', 'transform', 'nurbsCurve') nodes = cmds.ls(content_instance, long=True) @@ -37,6 +46,16 @@ class ValidateModelContent(pyblish.api.InstancePlugin): cls.log.error("These nodes are not allowed: %s" % invalid) return list(invalid) + if not valid: + cls.log.error("No valid nodes in the instance") + return True + + # Ensure it has shapes + shapes = cmds.ls(valid, long=True, shapes=True) + if not shapes: + cls.log.error("No shapes in the model instance") + return True + # Top group assemblies = cmds.ls(content_instance, assemblies=True, long=True) if len(assemblies) != 1: @@ -46,10 +65,6 @@ class ValidateModelContent(pyblish.api.InstancePlugin): "(Are there objects in the instance?)") return assemblies or True - if not valid: - cls.log.error("No valid nodes in the instance") - return True - def _is_visible(node): """Return whether node is visible""" return lib.is_visible(node, @@ -66,7 +81,6 @@ class ValidateModelContent(pyblish.api.InstancePlugin): invalid.add(assembly) # Ensure at least one shape is visible - shapes = cmds.ls(valid, long=True, shapes=True) if not any(_is_visible(shape) for shape in shapes): cls.log.error("No visible shapes in the model instance") invalid.update(shapes) From ac4626dfd6551c9915654fd44aabb289ed70c815 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 12 Dec 2017 16:44:28 +0100 Subject: [PATCH 0536/1249] Speed up polyConstraint context by not re-evaluating at the end of the context --- colorbleed/maya/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 28cc393da7..82ab58e5f4 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -338,9 +338,10 @@ def polyConstraint(components, *args, **kwargs): # it applies to the selection made before it; because just # a `maya.cmds.select()` call will not trigger the constraint. with reset_polySelectConstraint(): - cmds.select(components, r=1) + cmds.select(components, r=1, noExpand=True) cmds.polySelectConstraint(*args, mode=2, **kwargs) result = cmds.ls(selection=True) + cmds.select(clear=True) return result From 23e56fee4cad8ecb28da43d63c1b5cee07c3a3f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 12 Dec 2017 18:14:13 +0100 Subject: [PATCH 0537/1249] Fix "sharedReferenceNode" being used from avalon container --- colorbleed/maya/plugin.py | 45 ++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 6b8653e95e..89d6306813 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -47,6 +47,40 @@ class ReferenceLoader(api.Loader): """To be implemented by subclass""" raise NotImplementedError("Must be implemented by subclass") + def _get_reference_node(self, members): + """Get the reference node from the container members + + Returns: + str: Reference node name. + + """ + + from maya import cmds + + # Collect the references without .placeHolderList[] attributes as + # unique entries (objects only) and skipping the sharedReferenceNode. + matched = set() + references = list() + for ref in cmds.ls(members, exactType="reference", objectsOnly=True): + + # Ensure unique entries + if ref in matched: + continue + + # Ignore `sharedReferenceNode` + if ref == "sharedReferenceNode": + continue + + references.append(ref) + matched.add(ref) + + assert references, "No reference node found in container" + if len(set(references)) > 1: + self.log.warning("More than one reference node found in " + "container - using first one: %s", references) + reference_node = references[0] + return reference_node + def update(self, container, representation): import os @@ -58,12 +92,7 @@ class ReferenceLoader(api.Loader): # Get reference node from container members members = cmds.sets(node, query=True, nodesOnly=True) - references = cmds.ls(members, exactType="reference", objectsOnly=True) - assert references, "No reference node found in container" - if len(set(references)) > 1: - self.log.warning("More than one reference node found in " - "container - using first one: %s", references) - reference_node = references[0] + reference_node = self._get_reference_node(members) file_type = { "ma": "mayaAscii", @@ -116,8 +145,8 @@ class ReferenceLoader(api.Loader): node = container["objectName"] # Assume asset has been referenced - reference_node = next((node for node in cmds.sets(node, query=True) - if cmds.nodeType(node) == "reference"), None) + members = cmds.sets(node, query=True) + reference_node = self._get_reference_node(members) assert reference_node, ("Imported container not supported; " "container must be referenced.") From aa5a8660c1710443221327a4ce0a53912cf46217 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 15 Dec 2017 13:36:49 +0100 Subject: [PATCH 0538/1249] quick fix for publihsing shapes with the same cbId as te original shapes --- .../maya/publish/validate_node_ids_unique.py | 8 +- .../publish/validate_rig_out_set_node_ids.py | 125 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py index f828cac0c3..b8cbbd552d 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py @@ -34,9 +34,15 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): def get_invalid(cls, instance): """Return the member nodes that are invalid""" + # Check only non intermediate shapes + # todo: must the instance itself ensure to have no intermediates? + # todo: how come there are intermediates? + from maya import cmds + instance_members = cmds.ls(instance, noIntermediate=True, long=True) + # Collect each id with their members ids = defaultdict(list) - for member in instance: + for member in instance_members: object_id = lib.get_id(member) if not object_id: continue diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py new file mode 100644 index 0000000000..cd197a9831 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -0,0 +1,125 @@ +import maya.cmds as cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +def get_id_from_history(node): + """Return first node id in the history chain that matches this node. + + The nodes in history must be of the exact same node type and must be + parented under the same parent. + + Args: + node (str): node to retrieve the + + Returns: + str or None: The id from the node in history or None when no id found + on any valid nodes in the history. + + """ + + node = cmds.ls(node, long=True)[0] + + # Find all similar nodes in history + history = cmds.listHistory(node) + node_type = cmds.nodeType(node) + similar_nodes = cmds.ls(history, exactType=node_type, long=True) + + # Exclude itself + similar_nodes = [x for x in similar_nodes if x != node] + + # The node *must be* under the same parent + parent = get_parent(node) + similar_nodes = [i for i in similar_nodes if + get_parent(i) == parent] + + print similar_nodes + + # Check all of the remaining similar nodes and take the first one + # with an id and assume it's the original. + for similar_node in similar_nodes: + _id = lib.get_id(similar_node) + if _id: + return _id + + +def get_parent(node): + """Get the parent node of the given node + Args: + node (str): full path of the node + + Returns: + str, full path if parent node + """ + return cmds.listRelatives(node, parent=True, fullPath=True) + + +for node in cmds.ls(sl=1, dag=True, leaf=True, noIntermediate=True, + shapes=True): + print lib.get_id(node) + print get_id_from_history(node) + + +class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): + """Validate if deformed shapes have related IDs to the original shapes. + + When a deformer is applied in the scene on a referenced mesh that already + had deformers then Maya will create a new shape node for the mesh that + does not have the original id. This validator checks whether the ids are + valid on all the shape nodes in the instance. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ["colorbleed.rig"] + hosts = ['maya'] + label = 'Rig Out Set Node Ids' + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] + + def process(self, instance): + """Process all meshes""" + + # Ensure all nodes have a cbId and a related ID to the original shapes + # if a deformer has been created on the shape + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes found with non-related " + "asset IDs: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + """Get all nodes which do not match the criteria""" + + invalid = [] + + out_set = next(x for x in instance if x.endswith("out_SET")) + members = cmds.sets(out_set, query=True) + shapes = cmds.ls(members, + dag=True, + leaf=True, + shapes=True, + long=True, + noIntermediate=True) + + for shape in shapes: + history_id = get_id_from_history(shape) + if history_id: + current_id = lib.get_id(shape) + if current_id != history_id: + invalid.append(shape) + + return invalid + + @classmethod + def repair(cls, instance): + + for node in cls.get_invalid(instance): + # Get the original id from history + history_id = get_id_from_history(node) + if not history_id: + cls.log.error("Could not find ID in history for '%s'", node) + continue + + lib.set_id(node, history_id, overwrite=True) From 73260855e043cfc77f60a4ccd53fd5b82ef05ca5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 18 Dec 2017 09:34:40 +0100 Subject: [PATCH 0539/1249] fix for rig if no input found --- colorbleed/plugins/maya/publish/extract_yeti_rig.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index a086accfe8..5a5a101747 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -22,7 +22,11 @@ def disconnected_attributes(settings, members): sources = [i for i in source_nodes if not cmds.referenceQuery(i, isNodeReferenced=True) and i in members] - source = sources[0] + try: + source = sources[0] + except IndexError: + print "source_id:", input["sourceID"] + continue # Get destination shapes (the shapes used as hook up) destination_nodes = lib.lsattr("cbId", input["destinationID"]) From a7215f5fc49bfccdf4fe4b0d6b5d353b5764c253 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 18 Dec 2017 11:10:28 +0100 Subject: [PATCH 0540/1249] simplified function, raises propper error, added selected action --- .../publish/validate_setdress_namespaces.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py index 90246c216c..afec765ac3 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py @@ -1,5 +1,5 @@ import pyblish.api -from collections import defaultdict +import colorbleed.api class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): @@ -8,12 +8,13 @@ class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): label = "Validate Setdress Namespaces" order = pyblish.api.ValidatorOrder families = ["colorbleed.setdress"] + actions = [colorbleed.api.SelectInvalidAction] def process(self, instance): - self.log.info("Checking namespace for %s", instance.name) + self.log.info("Checking namespace for %s" % instance.name) if self.get_invalid(instance): - self.log.error("Nested namespaces found") + raise RuntimeError("Nested namespaces found") @classmethod def get_invalid(cls, instance): @@ -21,15 +22,9 @@ class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): from maya import cmds invalid = [] - - namspace_lookup = defaultdict(list) for item in cmds.ls(instance): - namespace, node = item.rsplit(":", 1)[0] - namspace_lookup[namespace].append(node) - - for namespace, nodes in namspace_lookup.items(): - parts = [p for p in namespace.split(":") if p != ""] - if len(parts) > 1: - invalid.extend(nodes) + item_parts = item.split("|", 1)[0].rsplit(":") + if len(item_parts[:-1]) > 1: + invalid.append(item) return invalid From a741a1150244673778fc7d5fffb75b3a12bdeb21 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 18 Dec 2017 11:16:09 +0100 Subject: [PATCH 0541/1249] updated docstrings --- .../maya/publish/validate_setdress_namespaces.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py index afec765ac3..1eda02cf74 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py @@ -3,7 +3,16 @@ import colorbleed.api class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): - """Ensure namespaces are not nested""" + """Ensure namespaces are not nested + + In the outliner an item in a normal namespace looks as following: + props_desk_01_:modelDefault + + Any namespace which diverts from that is illegal, example of an illegal + namespace: + room_study_01_:props_desk_01_:modelDefault + + """ label = "Validate Setdress Namespaces" order = pyblish.api.ValidatorOrder From c0713622d2616e0454a7ba3546a546dad30a1bd0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 18 Dec 2017 14:20:59 +0100 Subject: [PATCH 0542/1249] removed absolute path from files list, using only file names --- colorbleed/plugins/maya/publish/extract_setdress.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/colorbleed/plugins/maya/publish/extract_setdress.py index 1d04358c83..444e8f3d02 100644 --- a/colorbleed/plugins/maya/publish/extract_setdress.py +++ b/colorbleed/plugins/maya/publish/extract_setdress.py @@ -46,7 +46,7 @@ class ExtractSetDress(colorbleed.api.Extractor): "uvWrite": True, "selection": True}) - instance.data["files"] = [json_path, hierarchy_path] + instance.data["files"] = [json_filename, hierarchy_filename] # Remove data instance.data.pop("scenedata", None) From 71587db6cd403351a3b710a6b40f4a055b01c9d7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 19 Dec 2017 11:48:53 +0100 Subject: [PATCH 0543/1249] Improve versioning logic and move the versioning logic to lib.py --- colorbleed/lib.py | 54 +++++++++++++++ .../increment_current_file_deadline.py | 65 ++++--------------- 2 files changed, 68 insertions(+), 51 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 395a4fd5ca..b57c9c4a83 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -1,3 +1,5 @@ +import os +import re import logging from .vendor import pather @@ -90,3 +92,55 @@ def update_task_from_path(path): if changes: log.info("Updating work task to: %s", context) avalon.api.update_current_task(**changes) + + +def _rreplace(s, a, b, n=1): + """Replace a with b in string s from right side n times""" + return b.join(s.rsplit(a, n)) + + +def version_up(filepath): + """Version up filepath to a new non-existing version. + + Parses for a version identifier like `_v001` or `.v001` + When no version present _v001 is appended as suffix. + + Returns: + str: filepath with increased version number + + """ + + dirname = os.path.dirname(filepath) + basename, ext = os.path.splitext(os.path.basename(filepath)) + + regex = "[._]v\d+" + matches = re.findall(regex, str(basename), re.IGNORECASE) + if not matches: + log.info("Creating version...") + new_label = "_v{version:03d}".format(version=1) + new_basename = "{}{}".format(basename, new_label) + else: + label = matches[-1] + version = re.search("\d+", label).group() + padding = len(version) + + new_version = int(version) + 1 + new_version = '{version:0{padding}d}'.format(version=new_version, + padding=padding) + new_label = label.replace(version, new_version, 1) + new_basename = _rreplace(basename, label, new_label) + + new_filename = "{}{}".format(new_basename, ext) + new_filename = os.path.join(dirname, new_filename) + new_filename = os.path.normpath(new_filename) + + if new_filename == filepath: + raise RuntimeError("Created path is the same as current file," + "this is a bug") + + if os.path.exists(new_filename): + log.info("Skipping existing version %s" % new_label) + return version_up(new_filename) + + log.info("New version %s" % new_label) + return new_filename diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index 23a0336bf9..3ad7d04bea 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -2,10 +2,9 @@ import pyblish.api class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): - """Submit available render layers to Deadline + """Increment the current file. - Renders are submitted to a Deadline Web Service as - supplied via the environment variable AVALON_DEADLINE + Saves the current maya scene with an increased version number. """ @@ -17,60 +16,24 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): def process(self, context): + import os from maya import cmds - + from colorbleed.lib import version_up from colorbleed.action import get_errored_plugins_from_data - plugins = get_errored_plugins_from_data(context) - + errored_plugins = get_errored_plugins_from_data(context) if any(plugin.__name__ == "MindbenderSubmitDeadline" - for plugin in plugins): + for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " "submission to deadline failed.") - new_filename = self.version_up(context.data["currentFile"]) + current_filepath = context.data["currentFile"] + new_filepath = version_up(current_filepath) - cmds.file(rename=new_filename) + # Ensure the suffix is .ma because we're saving to `mayaAscii` type + if not new_filepath.endswith(".ma"): + self.log.warning("Refactoring scene to .ma extension") + new_filepath = os.path.splitext(new_filepath)[0] + ".ma" + + cmds.file(rename=new_filepath) cmds.file(save=True, force=True, type="mayaAscii") - - def version_up(self, filepath): - - import os - import re - - dirname = os.path.dirname(filepath) - basename, ext = os.path.splitext(os.path.basename(filepath)) - - regex = "[/_.]" + "v" + "\d+" - matches = re.findall(regex, str(basename), re.IGNORECASE) - if not len(matches): - self.log.info("Creating version ...") - version_str = "_v{number:03d}".format(number=1) - else: - version_label = matches[-1:][0] - basename = basename.strip(version_label) - - current_version = re.search("\d+", version_label).group() - padding = len(current_version) - prefix = version_label.split(current_version)[0] - - version_int = int(current_version) + 1 - version_str = '{prefix}{number:0{padding}d}'.format( - prefix=prefix, - padding=padding, - number=version_int) - # Create new basename - self.log.info("New version %s" % version_str) - new_basename = "{}{}{}".format(basename, version_str, ext) - - new_filename = os.path.join(dirname, new_basename) - new_filename = os.path.normpath(new_filename) - - if new_filename == filepath: - raise RuntimeError("Created path is the same as current file," - "please let someone no") - - if os.path.exists(new_filename): - new_filename = self.version_up(new_filename) - - return new_filename From 36bec68e4414198c6c22dd63dd89ed0f70291f7c Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Dec 2017 15:56:58 +0100 Subject: [PATCH 0544/1249] fix REN-27, collect settings per layer --- .../maya/publish/collect_renderlayers.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index cddfd54682..070ddc6556 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,3 +1,5 @@ +import pprint + from maya import cmds import pyblish.api @@ -47,20 +49,21 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): else: layername = layer.split("rs_", 1)[-1] - data = {"family": "Render Layers", - "families": ["colorbleed.renderlayer"], + # Get layer specific settings, might be overrides + with lib.renderlayer(layer): + data = { + "subset": layername, + "setMembers": layer, "publish": cmds.getAttr("{}.renderable".format(layer)), - "startFrame": self.get_render_attribute("startFrame"), "endFrame": self.get_render_attribute("endFrame"), "byFrameStep": self.get_render_attribute("byFrameStep"), - "renderer": lib.get_renderer(layer), + "renderer": self.get_render_attribute("currentRenderer"), # instance subset + "family": "Render Layers", + "families": ["colorbleed.renderlayer"], "asset": asset_name, - "subset": layername, - "setMembers": layer, - "time": api.time(), "author": context.data["user"], "source": source_file} From e7c25f3e0c7026e316623481a2c3a20205f0315b Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 19 Dec 2017 16:08:37 +0100 Subject: [PATCH 0545/1249] removed unused module import --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 070ddc6556..e6336596df 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -1,5 +1,3 @@ -import pprint - from maya import cmds import pyblish.api From 3b6f7a3d4764517a3409ef6bb3fd5b01c4fcc0bd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Dec 2017 14:47:29 +0100 Subject: [PATCH 0546/1249] Remove debugging lines of code --- .../plugins/maya/publish/validate_rig_out_set_node_ids.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py index cd197a9831..32909e6e3e 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -56,12 +56,6 @@ def get_parent(node): return cmds.listRelatives(node, parent=True, fullPath=True) -for node in cmds.ls(sl=1, dag=True, leaf=True, noIntermediate=True, - shapes=True): - print lib.get_id(node) - print get_id_from_history(node) - - class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): """Validate if deformed shapes have related IDs to the original shapes. From 2154108cec49fa1efcc0bb8c3242717327ef1d96 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 20 Dec 2017 14:51:00 +0100 Subject: [PATCH 0547/1249] Fix typo in docstring, add repair method and PEP08 cosmetics --- .../validate_mesh_vertices_have_edges.py | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py index a948e2aedf..90eca6e6f4 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py @@ -13,7 +13,7 @@ def len_flattened(components): when requesting with `maya.cmds.ls` without the `flatten` flag. Though enabling `flatten` on a large list (e.g. millions) will result in a slow result. This command will return the amount - of entries in a non-flattened list by parsing the result with + of entries in a non-flattened list by parsing the result with regex. Args: @@ -38,20 +38,20 @@ def len_flattened(components): class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): - """Validate meshes have only vertices that are connected by to edges. + """Validate meshes have only vertices that are connected to edges. Maya can have invalid geometry with vertices that have no edges or faces connected to them. In Maya 2016 EXT 2 and later there's a command to fix this: - `maya.cmds.polyClean(mesh, cleanVertices=True) + `maya.cmds.polyClean(mesh, cleanVertices=True)` In older versions of Maya it works to select the invalid vertices - and merge the components. + and merge the components. - To find these invalid vertices select all vertices of the mesh + To find these invalid vertices select all vertices of the mesh that are visible in the viewport (drag to select), afterwards - invert your selection (Ctrl + Shift + I). The remaining selection + invert your selection (Ctrl + Shift + I). The remaining selection contains the invalid vertices. """ @@ -61,7 +61,20 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): families = ['colorbleed.model'] category = 'geometry' label = 'Mesh Vertices Have Edges' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + @classmethod + def repair(cls, instance): + + # This fix only works in Maya 2016 EXT2 and newer + if float(cmds.about(version=True)) <= 2016.0: + raise RuntimeError("Repair not supported in Maya version below " + "2016 EXT 2") + + invalid = cls.get_invalid(instance) + for node in invalid: + cmds.polyClean(node, cleanVertices=True) @classmethod def get_invalid(cls, instance): From 137891afd5d26ccd4e107f1c59365838743d812c Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Dec 2017 12:21:28 +0100 Subject: [PATCH 0548/1249] ensure path is string for cmd --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 8ba8e0296f..1bf413666d 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -340,7 +340,7 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "PluginInfo": { "Version": "3.6", "ScriptFile": module_path, - "Arguments": "--path {}".format(json_fpath), + "Arguments": '--path "{}"'.format(json_fpath), "SingleFrameOnly": "True" }, From 06fdaa02e8ba14ace1c93faac8ae66f1f91709dd Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 22 Dec 2017 15:42:19 +0100 Subject: [PATCH 0549/1249] take only renderable layers --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index e6336596df..045ae35bff 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -35,8 +35,10 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): use_defaultlayer = cmds.getAttr(default_layer) # Get render layers - renderlayers = [i for i in cmds.ls(type="renderLayer") if not + renderlayers = [i for i in cmds.ls(type="renderLayer") if + cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] + if not use_defaultlayer: renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] @@ -52,7 +54,7 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): data = { "subset": layername, "setMembers": layer, - "publish": cmds.getAttr("{}.renderable".format(layer)), + "publish": True, "startFrame": self.get_render_attribute("startFrame"), "endFrame": self.get_render_attribute("endFrame"), "byFrameStep": self.get_render_attribute("byFrameStep"), From 321df3b4adc3ff8dfdc90506503fa7de82defc12 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 29 Dec 2017 14:54:42 +0100 Subject: [PATCH 0550/1249] get writeColorSets from instance and pass to export command --- colorbleed/plugins/maya/publish/extract_pointcache.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 727649ccf5..878bb14d6c 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -32,6 +32,9 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): start -= handles end += handles + # Get extra export arguments + writeColorSets = instance.data.get("writeColorSets", False) + self.log.info("Extracting animation..") dirname = self.staging_dir(instance) @@ -51,6 +54,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): "attr": ["cbId"], "writeVisibility": True, "writeCreases": True, + "writeColorSets": writeColorSets, "uvWrite": True, "selection": True}) From c81b18aeb1a563896fde7bb84f5f8e293b889bac Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 8 Jan 2018 13:58:38 +0100 Subject: [PATCH 0551/1249] added select action for validator --- colorbleed/plugins/maya/publish/validate_look_single_shader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py index af3aa26bbe..f56f8eb64e 100644 --- a/colorbleed/plugins/maya/publish/validate_look_single_shader.py +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -15,6 +15,7 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' + actions = [colorbleed.api.SelectInvalidAction] # The default connections to check def process(self, instance): From 4f3fc41b74871eef3b42845f82de39ee2ab91b43 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 8 Jan 2018 14:00:36 +0100 Subject: [PATCH 0552/1249] sorting instances based on fammily, sorting renderlayers based on display order --- .../plugins/maya/publish/collect_instances.py | 13 ++++++++++++- .../plugins/maya/publish/collect_renderlayers.py | 6 +++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index c233341d48..4e565fc679 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -113,7 +113,11 @@ class CollectInstances(pyblish.api.ContextPlugin): # user interface interested in visualising it. self.log.info("Found: \"%s\" " % instance.data["name"]) - context[:] = sorted(context) + # Sort/grouped by family (preserving local index) + grouped = sorted(enumerate(context), key=self.sorter) + context[:] = [x[1] for x in grouped] + # context[:] = sorted(context, key= lambda x: (x.data['families'], + # x.data['name'])) return context @@ -134,3 +138,10 @@ class CollectInstances(pyblish.api.ContextPlugin): parents.extend(items) return list(set(parents)) + + def sorter(self, x): + """Sort a tuple of index and instance""" + index, instance = x + family = instance.data.get("families", instance.data.get("family")) + + return family, index diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 045ae35bff..f556b99067 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -12,7 +12,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder hosts = ["maya"] label = "Render Layers" - optional = True def process(self, context): @@ -39,6 +38,11 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] + # Sort by displayOrder + renderlayers = sorted(renderlayers, + key=lambda x: -cmds.getAttr("%s.displayOrder" % x)) + + renderlayers = reversed(renderlayers) if not use_defaultlayer: renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] From dbba99b75a063631e4db725e5217fe0ae4791592 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 8 Jan 2018 14:25:25 +0100 Subject: [PATCH 0553/1249] simplified code, PEP8 conform in regards to lambdas --- .../plugins/maya/publish/collect_instances.py | 16 +++++----------- .../plugins/maya/publish/collect_renderlayers.py | 7 ++++--- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 4e565fc679..e40893c71c 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -113,11 +113,12 @@ class CollectInstances(pyblish.api.ContextPlugin): # user interface interested in visualising it. self.log.info("Found: \"%s\" " % instance.data["name"]) + def sort_by_family(instance): + """Sort by family""" + return instance.data.get("families", instance.data.get("family")) + # Sort/grouped by family (preserving local index) - grouped = sorted(enumerate(context), key=self.sorter) - context[:] = [x[1] for x in grouped] - # context[:] = sorted(context, key= lambda x: (x.data['families'], - # x.data['name'])) + context[:] = sorted(context, key=sort_by_family) return context @@ -138,10 +139,3 @@ class CollectInstances(pyblish.api.ContextPlugin): parents.extend(items) return list(set(parents)) - - def sorter(self, x): - """Sort a tuple of index and instance""" - index, instance = x - family = instance.data.get("families", instance.data.get("family")) - - return family, index diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index f556b99067..2c090df7e7 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -39,10 +39,11 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): cmds.referenceQuery(i, isNodeReferenced=True)] # Sort by displayOrder - renderlayers = sorted(renderlayers, - key=lambda x: -cmds.getAttr("%s.displayOrder" % x)) + def sort_by_display_order(layer): + return cmds.getAttr("%s.displayOrder" % layer) + + renderlayers = sorted(renderlayers, key=sort_by_display_order) - renderlayers = reversed(renderlayers) if not use_defaultlayer: renderlayers = [i for i in renderlayers if not i.endswith("defaultRenderLayer")] From 21329b113fdc782dbc7379e7636ae78401a24219 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Jan 2018 10:13:24 +0100 Subject: [PATCH 0554/1249] Add pyblish prefix to temp dir --- colorbleed/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 84e64f6392..0ba1fe5ded 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -28,7 +28,7 @@ class Extractor(pyblish.api.InstancePlugin): staging_dir = instance.data.get('stagingDir', None) if not staging_dir: - staging_dir = tempfile.mkdtemp() + staging_dir = tempfile.mkdtemp(prefix="pyblish_tmp_") instance.data['stagingDir'] = staging_dir return staging_dir From 4fe087bff333132ce28e827f2fa503d6b92d2cbd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Jan 2018 10:28:10 +0100 Subject: [PATCH 0555/1249] Move integration logged message only when "integrate" starts --- colorbleed/plugins/publish/integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index a0ce130bd6..1221ddc217 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -37,9 +37,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): def process(self, instance): - self.log.info("Integrating Asset in to the database ...") - self.register(instance) + + self.log.info("Integrating Asset in to the database ...") self.integrate(instance) # TODO: Decide how to clean up? And when? From 581a5a0d3f19f9174e60fc31e740509a538b69d4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Jan 2018 10:29:37 +0100 Subject: [PATCH 0556/1249] Use Pyblish's new `targets` workflow for "imagesequence" --- colorbleed/plugins/publish/collect_imagesequences.py | 1 + colorbleed/scripts/publish_imagesequence.py | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 4da58840c0..4186bd684d 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -6,6 +6,7 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): """Gather image sequnences from working directory""" order = pyblish.api.CollectorOrder + targets = ["imagesequence"] hosts = ["shell"] label = "Image Sequences" diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index f6997d1b06..314daacf09 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -64,13 +64,9 @@ def publish_data(json_file): from avalon import api, shell api.install(shell) - # Add environment variable to force collector to use a single folder - # based on the given JSON file - os.environ['USE_JSON'] = str(json_file) - # Publish items, returns context instances import pyblish.util - context = pyblish.util.publish() + context = pyblish.util.publish(targets=["imagesequence"]) if not context: log.warning("Nothing published.") From 649acbad0adf2f577aa152838d13dc6b28f14a93 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Jan 2018 10:31:16 +0100 Subject: [PATCH 0557/1249] Enable and implement clean up plug-in --- colorbleed/plugins/publish/cleanup.py | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/publish/cleanup.py index 742316094f..c17842d357 100644 --- a/colorbleed/plugins/publish/cleanup.py +++ b/colorbleed/plugins/publish/cleanup.py @@ -1,21 +1,32 @@ import os - -from pyblish import api +import pyblish.api -class CleanUp(api.InstancePlugin): - """Cleans up the staging directory after a successful publish +class CleanUp(pyblish.api.InstancePlugin): + """Cleans up the staging directory after a successful publish. + + The removal will only happen for staging directories which are inside the + temporary folder, otherwise the folder is ignored. """ - order = api.IntegratorOrder + 10 + order = pyblish.api.IntegratorOrder + 10 label = "Clean Up" def process(self, instance): - return - def clean_up(self, instance): - staging_dir = instance.get("stagingDir", None) - if staging_dir and os.path.exists(staging_dir): - self.log.info("Removing temporary folder ...") - os.rmdir(staging_dir) + import tempfile + + staging_dir = instance.data.get("stagingDir", None) + if not staging_dir or not os.path.exists(staging_dir): + self.log.info("No staging directory found: %s" % staging_dir) + return + + temp_root = tempfile.gettempdir() + if not os.path.normpath(staging_dir).startswith(temp_root): + self.log.info("Skipping cleanup. Staging directory is not in the " + "temp folder: %s" % staging_dir) + return + + self.log.info("Removing temporary folder ...") + os.rmdir(staging_dir) From 7427d5a2861df0e0460d7225ff7fbb98b43b959b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Jan 2018 10:44:46 +0100 Subject: [PATCH 0558/1249] Remove commented out clean up code in favor of clean up plug-in --- colorbleed/plugins/publish/integrate.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/publish/integrate.py index 1221ddc217..2418971546 100644 --- a/colorbleed/plugins/publish/integrate.py +++ b/colorbleed/plugins/publish/integrate.py @@ -42,11 +42,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): self.log.info("Integrating Asset in to the database ...") self.integrate(instance) - # TODO: Decide how to clean up? And when? - # self.log.info("Removing temporary files and folders ...") - # stagingdir = instance.data["stagingDir"] - # shutil.rmtree(stagingdir) - def register(self, instance): # Required environment variables From e44cb87b584d55a1c6960c6e627546082b1e76a1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 15 Jan 2018 11:07:29 +0100 Subject: [PATCH 0559/1249] Allow nurbsSurface to be be published with a model --- colorbleed/plugins/maya/publish/validate_model_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index d953be56bb..1e43754c6d 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -37,7 +37,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): content_instance = list(set(content_instance + descendants)) # Ensure only valid node types - allowed = ('mesh', 'transform', 'nurbsCurve') + allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface') nodes = cmds.ls(content_instance, long=True) valid = cmds.ls(content_instance, long=True, type=allowed) invalid = set(nodes) - set(valid) From 81371c9427504b9a92e5256ed056b0832c9be95e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 16 Jan 2018 00:02:49 +0100 Subject: [PATCH 0560/1249] Fix removal of non-empty temporary folder --- colorbleed/plugins/publish/cleanup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/publish/cleanup.py index c17842d357..43f8385592 100644 --- a/colorbleed/plugins/publish/cleanup.py +++ b/colorbleed/plugins/publish/cleanup.py @@ -1,4 +1,5 @@ import os +import shutil import pyblish.api @@ -29,4 +30,4 @@ class CleanUp(pyblish.api.InstancePlugin): return self.log.info("Removing temporary folder ...") - os.rmdir(staging_dir) + shutil.rmtree(staging_dir) From 6c9ad4e238c35179b6dc23eb386d9962f64b0c7e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Jan 2018 11:46:20 +0100 Subject: [PATCH 0561/1249] Remove debugging print statement --- .../plugins/maya/publish/validate_rig_out_set_node_ids.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py index 32909e6e3e..a109c39171 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -35,8 +35,6 @@ def get_id_from_history(node): similar_nodes = [i for i in similar_nodes if get_parent(i) == parent] - print similar_nodes - # Check all of the remaining similar nodes and take the first one # with an id and assume it's the original. for similar_node in similar_nodes: From ccb97fe1234c7464748eb2a8e9fc6c6233cef57d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Jan 2018 11:47:57 +0100 Subject: [PATCH 0562/1249] Remove redundant info logging in plug-in --- colorbleed/plugins/maya/publish/validate_rig_contents.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index d07fafe362..7111d679cb 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -38,8 +38,6 @@ class ValidateRigContents(pyblish.api.InstancePlugin): # in the rig instance set_members = self.check_set_members(instance) - self.log.info("Evaluating contents of object sets..") - # Ensure contents in sets and retrieve long path for all objects output_content = cmds.sets("out_SET", query=True) or [] assert output_content, "Must have members in rig out_SET" From c143f164d0ad06fd7b115a3e8cc381208d5b0795 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Jan 2018 12:06:14 +0100 Subject: [PATCH 0563/1249] Simplify validate rig contents --- .../maya/publish/validate_rig_contents.py | 153 +++++++----------- 1 file changed, 57 insertions(+), 96 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 7111d679cb..824e298646 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -20,87 +20,68 @@ class ValidateRigContents(pyblish.api.InstancePlugin): accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] - ignore_nodes = [] - - invalid_hierarchy = [] - invalid_controls = [] - invalid_geometry = [] def process(self, instance): - error = False - objectsets = ("controls_SET", "out_SET") missing = [obj for obj in objectsets if obj not in instance] assert not missing, ("%s is missing %s" % (instance, missing)) # Ensure there are at least some transforms or dag nodes # in the rig instance - set_members = self.check_set_members(instance) - - # Ensure contents in sets and retrieve long path for all objects - output_content = cmds.sets("out_SET", query=True) or [] - assert output_content, "Must have members in rig out_SET" - - controls_content = cmds.sets("controls_SET", query=True) or [] - assert controls_content, "Must have members in rig controls_SET" - - root_node = cmds.ls(set_members, assemblies=True) - hierarchy = cmds.listRelatives(root_node, allDescendents=True, - fullPath=True) - - self.invalid_geometry = self.validate_geometry(output_content, - hierarchy) - self.invalid_controls = self.validate_controls(controls_content, - hierarchy) - - if self.invalid_hierarchy: - self.log.error("Found nodes which reside outside of root group " - "while they are set up for publishing." - "\n%s" % self.invalid_hierarchy) - error = True - - if self.invalid_controls: - self.log.error("Only transforms can be part of the controls_SET." - "\n%s" % self.invalid_controls) - error = True - - if self.invalid_geometry: - self.log.error("Only meshes can be part of the out_SET\n%s" - % self.invalid_geometry) - error = True - - if error: - raise RuntimeError("Invalid rig content. See log for details.") - - def check_set_members(self, instance): - """Check if the instance has any dagNodes - Args: - instance: the instance which needs to be published - Returns: - set_members (list): all dagNodes from instance - """ - set_members = instance.data['setMembers'] if not cmds.ls(set_members, type="dagNode", long=True): raise RuntimeError("No dag nodes in the pointcache instance. " "(Empty instance?)") - return set_members - def validate_hierarchy(self, hierarchy, nodes): - """Collect all nodes which are NOT within the hierarchy - Args: - hierarchy (list): nodes within the root node - nodes (list): nodes to check + # Ensure contents in sets and retrieve long path for all objects + output_content = cmds.sets("out_SET", query=True) or [] + assert output_content, "Must have members in rig out_SET" + output_content = cmds.ls(output_content, long=True) - Returns: - errors (list): list of nodes - """ - errors = [] - for node in nodes: + controls_content = cmds.sets("controls_SET", query=True) or [] + assert controls_content, "Must have members in rig controls_SET" + controls_content = cmds.ls(controls_content, long=True) + + # Validate members are inside the hierarchy from root node + root_node = cmds.ls(set_members, assemblies=True) + hierarchy = cmds.listRelatives(root_node, allDescendents=True, + fullPath=True) + hierarchy = set(hierarchy) + + invalid_hierarchy = [] + for node in output_content: if node not in hierarchy: - errors.append(node) - return errors + invalid_hierarchy.append(node) + for node in controls_content: + if node not in hierarchy: + invalid_hierarchy.append(node) + + # Additional validations + invalid_geometry = self.validate_geometry(output_content, + hierarchy) + invalid_controls = self.validate_controls(controls_content, + hierarchy) + + error = False + if invalid_hierarchy: + self.log.error("Found nodes which reside outside of root group " + "while they are set up for publishing." + "\n%s" % invalid_hierarchy) + error = True + + if invalid_controls: + self.log.error("Only transforms can be part of the controls_SET." + "\n%s" % invalid_controls) + error = True + + if invalid_geometry: + self.log.error("Only meshes can be part of the out_SET\n%s" + % invalid_geometry) + error = True + + if error: + raise RuntimeError("Invalid rig content. See log for details.") def validate_geometry(self, set_members, hierarchy): """Check if the out set passes the validations @@ -116,31 +97,18 @@ class ValidateRigContents(pyblish.api.InstancePlugin): errors (list) """ - errors = [] - # Validate the contents further + # Validate all shape types + invalid = [] shapes = cmds.listRelatives(set_members, allDescendents=True, shapes=True, fullPath=True) or [] - - # The user can add the shape node to the out_set, this will result - # in none when querying allDescendents - all_shapes = set_members + shapes - all_long_names = [cmds.ls(i, long=True)[0] for i in all_shapes] - - # geometry - invalid_shapes = self.validate_hierarchy(hierarchy, - all_long_names) - self.invalid_hierarchy.extend(invalid_shapes) + all_shapes = cmds.ls(set_members + shapes, long=True, shapes=True) for shape in all_shapes: - nodetype = cmds.nodeType(shape) - if nodetype in self.ignore_nodes: - continue + if cmds.nodeType(shape) not in self.accepted_output: + invalid.append(shape) - if nodetype not in self.accepted_output: - errors.append(shape) - - return errors + return invalid def validate_controls(self, set_members, hierarchy): """Check if the controller set passes the validations @@ -156,17 +124,10 @@ class ValidateRigContents(pyblish.api.InstancePlugin): errors (list) """ - errors = [] - all_long_names = [cmds.ls(i, long=True)[0] for i in set_members] - invalid_controllers = self.validate_hierarchy(hierarchy, - all_long_names) - self.invalid_hierarchy.extend(invalid_controllers) + # Validate control types + invalid = [] for node in set_members: - nodetype = cmds.nodeType(node) - if nodetype in self.ignore_nodes: - continue + if cmds.nodeType(node) not in self.accepted_controllers: + invalid.append(node) - if nodetype not in self.accepted_controllers: - errors.append(node) - - return errors + return invalid From 9a702cbd25429469421d5eef07920cf55de6ec97 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Jan 2018 12:28:34 +0100 Subject: [PATCH 0564/1249] Remove redundant hierarchy argument for methods --- .../plugins/maya/publish/validate_rig_contents.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/colorbleed/plugins/maya/publish/validate_rig_contents.py index 824e298646..7530936a66 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_contents.py +++ b/colorbleed/plugins/maya/publish/validate_rig_contents.py @@ -58,10 +58,8 @@ class ValidateRigContents(pyblish.api.InstancePlugin): invalid_hierarchy.append(node) # Additional validations - invalid_geometry = self.validate_geometry(output_content, - hierarchy) - invalid_controls = self.validate_controls(controls_content, - hierarchy) + invalid_geometry = self.validate_geometry(output_content) + invalid_controls = self.validate_controls(controls_content) error = False if invalid_hierarchy: @@ -83,7 +81,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): if error: raise RuntimeError("Invalid rig content. See log for details.") - def validate_geometry(self, set_members, hierarchy): + def validate_geometry(self, set_members): """Check if the out set passes the validations Checks if all its set members are within the hierarchy of the root @@ -110,7 +108,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin): return invalid - def validate_controls(self, set_members, hierarchy): + def validate_controls(self, set_members): """Check if the controller set passes the validations Checks if all its set members are within the hierarchy of the root From ea43cdd868643a26c4e00677d3b67e8f8659b775 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Jan 2018 08:27:56 +0100 Subject: [PATCH 0565/1249] Remove cb dependency for getting highest in hierarchy - embed in lib.py --- colorbleed/maya/lib.py | 50 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 82ab58e5f4..8eb4d5799a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1126,8 +1126,56 @@ def get_container_transforms(container, members=None, root=False): results = cmds.ls(members, type="transform", long=True) if root: - root = core.getHighestInHierarchy(results) + root = get_highest_in_hierarchy(results) if root: results = root[0] return results + + +def get_highest_in_hierarchy(nodes): + """Return highest nodes in the hierarchy that are in the `nodes` list. + + The "highest in hierarchy" are the nodes closest to world: top-most level. + + Args: + nodes (list): The nodes in which find the highest in hierarchies. + + Returns: + list: The highest nodes from the input nodes. + + """ + + # Ensure we use long names + nodes = cmds.ls(nodes, long=True) + lookup = set(nodes) + + highest = [] + for node in nodes: + # If no parents are within the nodes input list + # then this is a highest node + if not any(n in lookup for n in iter_parents(node)): + highest.append(node) + + return highest + + +def iter_parents(node): + """Iter parents of node from its long name. + + Note: The `node` *must* be the long node name. + + Args: + node (str): Node long name. + + Yields: + str: All parent node names (long names) + + """ + while True: + split = node.rsplit("|", 1) + if len(split) == 1: + return + + node = split[0] + yield node From f26c78b7820c511bd771f97af6bb2c600d1da14e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Jan 2018 11:56:23 +0100 Subject: [PATCH 0566/1249] Add validate rig controllers for Arnold attributes --- ...idate_rig_controllers_arnold_attributes.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py new file mode 100644 index 0000000000..f7e0b43681 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py @@ -0,0 +1,91 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +from cb.utils.maya.context import undo_chunk + + +class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): + """Validate rig control curves have no keyable arnold attributes. + + The Arnold plug-in will create curve attributes like: + - aiRenderCurve + - aiCurveWidth + - aiSampleRate + - aiCurveShaderR + - aiCurveShaderG + - aiCurveShaderB + + Unfortunately these attributes visible in the channelBox are *keyable* + by default and visible in the channelBox. As such pressing a regular "S" + set key shortcut will set keys on these attributes too, thus cluttering + the animator's scene. + + This validator will ensure they are hidden or unkeyable attributes. + + """ + order = colorbleed.api.ValidateContentsOrder + 0.05 + label = "Rig Controllers (Arnold Attributes)" + hosts = ["maya"] + families = ["colorbleed.rig"] + actions = [colorbleed.api.RepairAction, + colorbleed.api.SelectInvalidAction] + + attributes = [ + "rcurve", + "cwdth", + "srate", + "ai_curve_shaderr", + "ai_curve_shaderg", + "ai_curve_shaderb" + ] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError('{} failed, see log ' + 'information'.format(self.label)) + + @classmethod + def get_invalid(cls, instance): + + controllers_sets = [i for i in instance if i == "controls_SET"] + if not controllers_sets: + return [] + + controls = cmds.sets(controllers_sets, query=True) or [] + if not controls: + return [] + + shapes = cmds.ls(controls, + dag=True, + leaf=True, + long=True, + shapes=True, + noIntermediate=True) + curves = cmds.ls(shapes, type="nurbsCurve", long=True) + + print curves + + invalid = list() + for node in curves: + + for attribute in cls.attributes: + if cmds.attributeQuery(attribute, node=node, exists=True): + plug = "{}.{}".format(node, attribute) + if cmds.getAttr(plug, keyable=True): + invalid.append(node) + break + + return invalid + + @classmethod + def repair(cls, instance): + + invalid = cls.get_invalid(instance) + with undo_chunk(): + for node in invalid: + for attribute in cls.attributes: + if cmds.attributeQuery(attribute, node=node, exists=True): + plug = "{}.{}".format(node, attribute) + cmds.setAttr(plug, channelBox=False, keyable=False) From 7b34e1b28bf805ad179c845aea6d7dcce6c3aced Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Jan 2018 12:55:27 +0100 Subject: [PATCH 0567/1249] Remove debugging print statement --- .../maya/publish/validate_rig_controllers_arnold_attributes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py index f7e0b43681..b6a1191006 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py @@ -65,8 +65,6 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): noIntermediate=True) curves = cmds.ls(shapes, type="nurbsCurve", long=True) - print curves - invalid = list() for node in curves: From 78a7cab98d5f5964a5c52c09ff843c4b55538cb3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 25 Jan 2018 16:31:31 +0100 Subject: [PATCH 0568/1249] Fixed issue with unsupported kwarg in older Maya version --- colorbleed/plugins/maya/load/load_camera.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index fbe87cfe92..d375f1a519 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -27,8 +27,15 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): returnNewNodes=True) cameras = cmds.ls(nodes, type="camera") - for camera in cameras: - cmds.camera(camera, edit=True, lockTransform=True) + + # Check the Maya version, lockTransform has been introduced since + # Maya 2017 + if cmds.about(version=True) >= "2018": + for camera in cameras: + cmds.camera(camera, edit=True, lockTransform=True) + else: + self.log.warning("This version of Maya does not support locking of" + " transforms of cameras.") self[:] = nodes From 1255ffca73ee0daa66eb89e84edab6c5a150effa Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 25 Jan 2018 16:43:01 +0100 Subject: [PATCH 0569/1249] added vray to attrPrefix list --- colorbleed/plugins/maya/publish/extract_pointcache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 878bb14d6c..742425c4eb 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -52,6 +52,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): endFrame=end, **{"step": instance.data.get("step", 1.0), "attr": ["cbId"], + "attrPrefix": ["vray"], "writeVisibility": True, "writeCreases": True, "writeColorSets": writeColorSets, From 742031d100a9754710e8158d4789d8cafbe6f5e7 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 25 Jan 2018 17:49:13 +0100 Subject: [PATCH 0570/1249] switched to int comparison, lowered version number to 2017 --- colorbleed/plugins/maya/load/load_camera.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index d375f1a519..7edd073dbd 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -30,7 +30,8 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): # Check the Maya version, lockTransform has been introduced since # Maya 2017 - if cmds.about(version=True) >= "2018": + version = int(cmds.about(version=True)) + if version >= 2017: for camera in cameras: cmds.camera(camera, edit=True, lockTransform=True) else: From e97fa88a121ccc13d1dfe091e9bdd5b6048af903 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Jan 2018 12:41:33 +0100 Subject: [PATCH 0571/1249] Support multiple UV sets in Alembic (Maya 2017+) --- colorbleed/maya/lib.py | 55 +++++++++++ .../plugins/maya/publish/extract_animation.py | 22 +++-- .../maya/publish/extract_pointcache.py | 24 +++-- .../publish/validate_mesh_single_uv_set.py | 98 ++++--------------- 4 files changed, 107 insertions(+), 92 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8eb4d5799a..2be9174864 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1179,3 +1179,58 @@ def iter_parents(node): node = split[0] yield node + + +def remove_other_uv_sets(mesh): + """Remove all other UV sets than the current UV set. + + Keep only current UV set and ensure it's the renamed to default 'map1'. + + """ + + uvSets = cmds.polyUVSet(mesh, query=True, allUVSets=True) + current = cmds.polyUVSet(mesh, query=True, currentUVSet=True)[0] + + # Copy over to map1 + if current != 'map1': + cmds.polyUVSet(mesh, uvSet=current, newUVSet='map1', copy=True) + cmds.polyUVSet(mesh, currentUVSet=True, uvSet='map1') + current = 'map1' + + # Delete all non-current UV sets + deleteUVSets = [uvSet for uvSet in uvSets if uvSet != current] + uvSet = None + + # Maya Bug (tested in 2015/2016): + # In some cases the API's MFnMesh will report less UV sets than + # maya.cmds.polyUVSet. This seems to happen when the deletion of UV sets + # has not triggered a cleanup of the UVSet array attribute on the mesh + # node. It will still have extra entries in the attribute, though it will + # not show up in API or UI. Nevertheless it does show up in + # maya.cmds.polyUVSet. To ensure we clean up the array we'll force delete + # the extra remaining 'indices' that we don't want. + + # TODO: Implement a better fix + # The best way to fix would be to get the UVSet indices from api with + # MFnMesh (to ensure we keep correct ones) and then only force delete the + # other entries in the array attribute on the node. But for now we're + # deleting all entries except first one. Note that the first entry could + # never be removed (the default 'map1' always exists and is supposed to + # be undeletable.) + try: + for uvSet in deleteUVSets: + cmds.polyUVSet(mesh, delete=True, uvSet=uvSet) + except RuntimeError as exc: + log.warning('Error uvSet: %s - %s', uvSet, exc) + indices = cmds.getAttr('{0}.uvSet'.format(mesh), + multiIndices=True) + if not indices: + log.warning("No uv set found indices for: %s", mesh) + return + + # Delete from end to avoid shifting indices + # and remove the indices in the attribute + indices = reversed(indices[1:]) + for i in indices: + attr = '{0}.uvSet[{1}]'.format(mesh, i) + cmds.removeMultiInstance(attr, b=True) diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index abc9be046f..d62c34e915 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -10,7 +10,7 @@ from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAnimation(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. - Positions and normals are preserved, but nothing more, + Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. """ @@ -49,18 +49,26 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) + options = { + "step": instance.data.get("step", 1.0), + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "uvWrite": True, + "selection": True + } + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, - **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "uvWrite": True, - "selection": True}) + **options) if "files" not in instance.data: instance.data["files"] = list() diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 878bb14d6c..405d1e6549 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -10,7 +10,7 @@ from colorbleed.maya.lib import extract_alembic class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. - Positions and normals are preserved, but nothing more, + Positions and normals, uvs, creases are preserved, but nothing more, for plain and predictable point caches. """ @@ -44,19 +44,27 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) + options = { + "step": instance.data.get("step", 1.0), + "attr": ["cbId"], + "writeVisibility": True, + "writeCreases": True, + "writeColorSets": writeColorSets, + "uvWrite": True, + "selection": True + } + + if int(cmds.about(version=True)) >= 2017: + # Since Maya 2017 alembic supports multiple uv sets - write them. + options["writeUVSets"] = True + with avalon.maya.suspended_refresh(): with avalon.maya.maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, endFrame=end, - **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"], - "writeVisibility": True, - "writeCreases": True, - "writeColorSets": writeColorSets, - "uvWrite": True, - "selection": True}) + **options) if "files" not in instance.data: instance.data["files"] = list() diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py index 85567631d5..77ec1a0661 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -2,10 +2,17 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.lib as lib class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): - """Ensure no multiple UV sets exist for each polygon mesh""" + """Warn on multiple UV sets existing for each polygon mesh. + + On versions prior to Maya 2017 this will force no multiple uv sets because + the Alembic exports in Maya prior to 2017 don't support writing multiple + UV sets. + + """ order = colorbleed.api.ValidateMeshOrder hosts = ['maya'] @@ -42,83 +49,20 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Nodes found with multiple " - "UV sets: {0}".format(invalid)) + + message = "Nodes found with multiple UV sets: {0}".format(invalid) + + # Maya 2017 and up allows multiple UV sets in Alembic exports + # so we allow it, yet just warn the user to ensure they know about + # the other UV sets. + allowed = int(cmds.about(version=True)) >= 2017 + + if allowed: + self.log.warning(message) + else: + raise ValueError(message) @classmethod def repair(cls, instance): for mesh in cls.get_invalid(instance): - cls._repair_mesh(mesh) - - @classmethod - def _repair_mesh(cls, mesh): - """Process a single mesh, deleting other UV sets than the active one. - - Keep only current UV set and ensure it's the default 'map1' - - """ - from maya import cmds - - uvSets = cmds.polyUVSet(mesh, - query=True, - allUVSets=True) - current = cmds.polyUVSet(mesh, - query=True, - currentUVSet=True)[0] - - # Copy over to map1 - if current != 'map1': - cmds.polyUVSet(mesh, - uvSet=current, - newUVSet='map1', - copy=True) - cmds.polyUVSet(mesh, - currentUVSet=True, - uvSet='map1') - current = 'map1' - - # Delete all non-current UV sets - deleteUVSets = [uvSet for uvSet in uvSets if uvSet != current] - uvSet = None - - # Maya Bug (tested in 2015/2016): - # In some cases the API's MFnMesh will report less UV sets - # than maya.cmds.polyUVSet. - # This seems to happen when the deletion of UV sets has not - # triggered a cleanup of the UVSet array - # attribute on the mesh node. It will still have extra - # entries in the attribute, though it will not - # show up in API or UI. Nevertheless it does show up in - # maya.cmds.polyUVSet. - # To ensure we clean up the array we'll force delete the - # extra remaining 'indices' that we don't want. - - # TODO: Implement a better fix - # The best way to fix would be to get the UVSet - # indices from api with MFnMesh (to ensure we keep - # correct ones) and then only force delete the other - # entries in the array attribute on the node. - # But for now we're deleting all entries except first - # one. Note that the first entry could never - # be removed (the default 'map1' always exists and is - # supposed to be undeletable.) - try: - for uvSet in deleteUVSets: - cmds.polyUVSet(mesh, delete=True, uvSet=uvSet) - except RuntimeError, e: - cls.log.warning('uvSet: {0} - ' - 'Error: {1}'.format(uvSet, e)) - - indices = cmds.getAttr('{0}.uvSet'.format(mesh), - multiIndices=True) - if not indices: - cls.log.warning( - "No uv set found indices for: {0}".format(mesh)) - return - - # Delete from end to avoid shifting indices - # and remove the indices in the attribute - indices = reversed(indices[1:]) - for i in indices: - attr = '{0}.uvSet[{1}]'.format(mesh, i) - cmds.removeMultiInstance(attr, b=True) + lib.remove_other_uv_sets(mesh) From 71a6e4bf6fe943fe317c1f3492346e4251abd790 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 29 Jan 2018 14:01:55 +0100 Subject: [PATCH 0572/1249] Initial draft for Fusion integration (not fully working) --- colorbleed/fusion/__init__.py | 26 ++++++++++++ .../plugins/fusion/load/load_sequence.py | 41 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 colorbleed/fusion/__init__.py create mode 100644 colorbleed/plugins/fusion/load/load_sequence.py diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py new file mode 100644 index 0000000000..36270980bf --- /dev/null +++ b/colorbleed/fusion/__init__.py @@ -0,0 +1,26 @@ +import os + +from avalon import api as avalon +from pyblish import api as pyblish + +PARENT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.path.dirname(PARENT_DIR) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") + +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "fusion", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") + + +def install(): + print("Registering Fusion plug-ins..") + pyblish.register_plugin_path(PUBLISH_PATH) + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + + +def uninstall(): + print("Deregistering Fusion plug-ins..") + pyblish.deregister_plugin_path(PUBLISH_PATH) + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py new file mode 100644 index 0000000000..2a675eee4c --- /dev/null +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -0,0 +1,41 @@ +from avalon import api +import fusionless +import fusionless.context as fuCtx +import os + + +class FusionLoadSequence(api.Loader): + """Load image sequence into Fusion""" + + families = ["colorbleed.imagesequence"] + representations = ["*"] + + label = "Load sequence" + order = -10 + icon = "play-circle" + color = "orange" + + def load(self, context, name, namespace, data): + + from avalon.fusion.pipeline import imprint_container + + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + # Use the first file for now + root = self.fname + files = os.listdir(root) + path = os.path.join(root, files[0]) + + # Create the Loader with the filename path set + comp = fusionless.Comp() + with fuCtx.lock_and_undo_chunk(comp, "Create Loader"): + tool = comp.create_tool("Loader") + tool.input("Clip").set_value(path) + + imprint_container(tool, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__) From 82748d33071d726a76841d733a7c957a51ab36ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Feb 2018 16:18:30 +0100 Subject: [PATCH 0573/1249] Implement FusionLoadSequence, including update and remove - also remove fusionless dependency --- .../plugins/fusion/load/load_sequence.py | 72 ++++++++++++++++--- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 2a675eee4c..52b869e540 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -1,6 +1,4 @@ from avalon import api -import fusionless -import fusionless.context as fuCtx import os @@ -17,25 +15,79 @@ class FusionLoadSequence(api.Loader): def load(self, context, name, namespace, data): - from avalon.fusion.pipeline import imprint_container + from avalon.fusion import ( + imprint_container, + get_current_comp, + comp_lock_and_undo_chunk + ) # Fallback to asset name when namespace is None if namespace is None: namespace = context['asset']['name'] # Use the first file for now - root = self.fname - files = os.listdir(root) - path = os.path.join(root, files[0]) + path = self._get_first_image(self.fname) # Create the Loader with the filename path set - comp = fusionless.Comp() - with fuCtx.lock_and_undo_chunk(comp, "Create Loader"): - tool = comp.create_tool("Loader") - tool.input("Clip").set_value(path) + comp = get_current_comp() + with comp_lock_and_undo_chunk(comp, "Create Loader"): + + args = (-32768, -32768) + tool = comp.AddTool("Loader", *args) + tool["Clip"] = path imprint_container(tool, name=name, namespace=namespace, context=context, loader=self.__class__.__name__) + + def _get_first_image(self, root): + """Get first file in representation root""" + files = sorted(os.listdir(root)) + return os.path.join(root, files[0]) + + def update(self, container, representation): + """Update the Loader's path + + Fusion automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + - ClipTimeStart (if duration changes) + - ClipTimeEnd (if duration changes) + - GlobalIn (if duration changes) + - GlobalEnd (if duration changes) + - Reverse (sometimes?) + - Loop (sometimes?) + - Depth (always resets to "Format") + - KeyCode (always resets to "") + - TimeCodeOffset (always resets to 0) + + """ + + from avalon.fusion import comp_lock_and_undo_chunk + + root = api.get_representation_path(representation) + path = self._get_first_image(root) + print representation + print path + + tool = container["_tool"] + assert tool.ID == "Loader", "Must be Loader" + comp = tool.Comp() + + with comp_lock_and_undo_chunk(comp, "Update Loader"): + tool["Clip"] = path + + # Update the imprinted representation + tool.SetData("avalon.representation", str(representation["_id"])) + + def remove(self, container): + + from avalon.fusion import comp_lock_and_undo_chunk + + tool = container["_tool"] + assert tool.ID == "Loader", "Must be Loader" + comp = tool.Comp() + with comp_lock_and_undo_chunk(comp, "Remove Loader"): + tool.Delete() From a37820607a2c03ad5b2bc541ef3ce3be563ec698 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Feb 2018 16:18:53 +0100 Subject: [PATCH 0574/1249] Add set frame range actions for Fusion --- colorbleed/plugins/fusion/load/actions.py | 103 ++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 colorbleed/plugins/fusion/load/actions.py diff --git a/colorbleed/plugins/fusion/load/actions.py b/colorbleed/plugins/fusion/load/actions.py new file mode 100644 index 0000000000..68f66775a8 --- /dev/null +++ b/colorbleed/plugins/fusion/load/actions.py @@ -0,0 +1,103 @@ +"""A module containing generic loader actions that will display in the Loader. + +""" + +from avalon import api + + +def _set_frame_range(start, end, set_render_range=True): + """Set Fusion comp's start and end frame range + + Attrs: + set_render_range (bool, Optional): When True this will also set the + composition's render start and end frame. + + Returns: + None + + """ + + from avalon.fusion import get_current_comp, comp_lock_and_undo_chunk + + comp = get_current_comp() + + attrs = { + "COMPN_GlobalStart": start, + "COMPN_GlobalEnd": end + } + + if set_render_range: + attrs.update({ + "COMPN_RenderStart": start, + "COMPN_RenderEnd": end + }) + + with comp_lock_and_undo_chunk(comp): + comp.SetAttrs(attrs) + + +class FusionSetFrameRangeLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", + "colorbleed.camera", + "colorbleed.imagesequence", + "colorbleed.yeticache", + "colorbleed.pointcache"] + representations = ["*"] + + label = "Set frame range" + order = 11 + icon = "clock-o" + color = "white" + + def load(self, context, name, namespace, data): + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("startFrame", None) + end = version_data.get("endFrame", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + _set_frame_range(start, end) + + +class FusionSetFrameRangeWithHandlesLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", + "colorbleed.camera", + "colorbleed.imagesequence", + "colorbleed.yeticache", + "colorbleed.pointcache"] + representations = ["*"] + + label = "Set frame range (with handles)" + order = 12 + icon = "clock-o" + color = "white" + + def load(self, context, name, namespace, data): + + version = context['version'] + version_data = version.get("data", {}) + + start = version_data.get("startFrame", None) + end = version_data.get("endFrame", None) + + if start is None or end is None: + print("Skipping setting frame range because start or " + "end frame data is missing..") + return + + # Include handles + handles = version_data.get("handles", 0) + start -= handles + end += handles + + _set_frame_range(start, end) From 7c9581e26b22300cf12c9f1c315c80d666fff8d9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 8 Feb 2018 16:18:23 +0100 Subject: [PATCH 0575/1249] work in progress development --- .../plugins/fusion/create/create_saver.py | 21 +++++ .../fusion/load/load_alembic_camera.py | 27 ++++++ .../fusion/publish/_validate_filenames.py | 52 ++++++++++++ .../fusion/publish/_validate_frame_range.py | 19 +++++ .../plugins/fusion/publish/collect_comp.py | 22 +++++ .../fusion/publish/collect_instances.py | 84 +++++++++++++++++++ .../fusion/publish/extract_image_sequence.py | 80 ++++++++++++++++++ .../fusion/publish/validate_unique_name.py | 29 +++++++ 8 files changed, 334 insertions(+) create mode 100644 colorbleed/plugins/fusion/create/create_saver.py create mode 100644 colorbleed/plugins/fusion/load/load_alembic_camera.py create mode 100644 colorbleed/plugins/fusion/publish/_validate_filenames.py create mode 100644 colorbleed/plugins/fusion/publish/_validate_frame_range.py create mode 100644 colorbleed/plugins/fusion/publish/collect_comp.py create mode 100644 colorbleed/plugins/fusion/publish/collect_instances.py create mode 100644 colorbleed/plugins/fusion/publish/extract_image_sequence.py create mode 100644 colorbleed/plugins/fusion/publish/validate_unique_name.py diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_saver.py new file mode 100644 index 0000000000..75581da9cc --- /dev/null +++ b/colorbleed/plugins/fusion/create/create_saver.py @@ -0,0 +1,21 @@ +import avalon.api +from avalon import fusion + + +class CreateTiffSaver(avalon.api.Creator): + + name = "tiffDefault" + label = "Create Tiff Saver" + hosts = "fusion" + family = "colorbleed.imagesequence" + + def process(self): + + comp = fusion.get_current_comp() + with fusion.comp_lock_and_undo_chunk(comp): + args = (-32768, -32768) # Magical position numbers + saver = comp.AddTool("Saver", *args) + saver.SetAttrs({ + "TOOLS_Name": self.data.get("name", self.name), + 'TOOLST_Clip_FormatID': {1.0: 'TiffFormat'}, + }) diff --git a/colorbleed/plugins/fusion/load/load_alembic_camera.py b/colorbleed/plugins/fusion/load/load_alembic_camera.py new file mode 100644 index 0000000000..f2ed66fc9c --- /dev/null +++ b/colorbleed/plugins/fusion/load/load_alembic_camera.py @@ -0,0 +1,27 @@ +from avalon import api + + +class FusionLoadAlembicCamera(api.Loader): + """Load image sequence into Fusion""" + + families = ["colorbleed.camera"] + representations = ["ma"] + + label = "Load sequence" + order = -10 + icon = "play-circle" + color = "orange" + + def load(self, context, name, namespace, data): + """""" + + from avalon.fusion import (imprint_container, + get_current_comp, + comp_lock_and_undo_chunk) + + current_comp = get_current_comp() + with comp_lock_and_undo_chunk(current_comp): + tool = current_comp.SurfaceAlembicMesh() + tool.SetData("TOOLS_NameSet", ) + + pass diff --git a/colorbleed/plugins/fusion/publish/_validate_filenames.py b/colorbleed/plugins/fusion/publish/_validate_filenames.py new file mode 100644 index 0000000000..7f7764fb17 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/_validate_filenames.py @@ -0,0 +1,52 @@ +import os + +import pyblish.api + + +class ValidateFileNames(pyblish.api.Validator): + """Ensure all file names follow the same structure + + Filename should have identifiable parts: + - name ( example: Avengers_shot010_preview ) + - frame ( example: #### ) + - extension ( example: tiff ) + + The result when rendering frame 1250 would be as follows: + Avengers_shot010_preview.1250.tiff + + When certain parts need to be rendered out separately for some reason it + is advisable to something all the lines of: + Avengers_shot010_character_beauty.1250.tiff + """ + + order = pyblish.api.ValidatorOrder + label = "Validate File Names (Saver)" + families = ["colorbleed.imagesequence"] + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + path = instance.data["path"] + basename = os.path.basename(path) + + parts = basename.split(".") + if len(parts) != 3: + invalid.append(instance) + cls.log.error("%s has %i parts, should be 3" + % (instance, len(parts))) + else: + is_numbers = all(i.isdigit() for i in parts[1]) + if len(parts[1]) != 4 or not is_numbers: + cls.log.error("Number padding is not four digits") + invalid.append(instance) + + return invalid + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found %i instances with a wrong file name " + "structure" % len(invalid)) diff --git a/colorbleed/plugins/fusion/publish/_validate_frame_range.py b/colorbleed/plugins/fusion/publish/_validate_frame_range.py new file mode 100644 index 0000000000..134180d675 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/_validate_frame_range.py @@ -0,0 +1,19 @@ +import pyblish.api + + +class ValidateFrameRange(pyblish.api.InstancePlugin): + """Validate the frame range of the current Saver""" + + order = pyblish.api.ValidatorOrder + label = "Validate Frame Range" + families = ["colorbleed.imagesequence"] + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + return [] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file diff --git a/colorbleed/plugins/fusion/publish/collect_comp.py b/colorbleed/plugins/fusion/publish/collect_comp.py new file mode 100644 index 0000000000..7dae0b908b --- /dev/null +++ b/colorbleed/plugins/fusion/publish/collect_comp.py @@ -0,0 +1,22 @@ +import pyblish.api + +from avalon import fusion + + +class CollectCurrentCompFusion(pyblish.api.ContextPlugin): + """Collect current comp""" + + order = pyblish.api.CollectorOrder - 0.4 + label = "Collect Current Comp" + hosts = ["fusion"] + + def process(self, context): + """Collect all image sequence tools""" + + current_comp = fusion.get_current_comp() + assert current_comp, "Must have active Fusion composition" + context.data["currentComp"] = current_comp + + # Store path to current file + attrs = current_comp.GetAttrs() + context.data['currentFile'] = attrs.get("COMPS_FileName", "") diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py new file mode 100644 index 0000000000..e04fb1d432 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -0,0 +1,84 @@ +import os +import re + +import pyblish.api + + +def get_comp_render_range(comp): + """Return comp's start and end render range.""" + comp_attrs = comp.GetAttrs() + start = comp_attrs["COMPN_RenderStart"] + end = comp_attrs["COMPN_RenderEnd"] + + # Whenever render ranges are undefined fall back + # to the comp's global start and end + if start == -1000000000: + start = comp_attrs["COMPN_GlobalEnd"] + if end == -1000000000: + end = comp_attrs["COMPN_GlobalStart"] + + return start, end + + +class CollectInstances(pyblish.api.ContextPlugin): + """Collect Fusion saver instances""" + + order = pyblish.api.CollectorOrder + label = "Collect Instances" + hosts = ["fusion"] + + def process(self, context): + """Collect all image sequence tools""" + + comp = context.data["currentComp"] + + # Get all savers in the comp + tools = comp.GetToolList(False).values() + savers = [tool for tool in tools if tool.ID == "Saver"] + + start, end = get_comp_render_range(comp) + for tool in savers: + path = tool["Clip"][comp.TIME_UNDEFINED] + + if not path: + self.log.warning("Skipping saver because it " + "has no path set: {}".format(tool.Name)) + continue + + fname = os.path.basename(path) + _subset, ext = os.path.splitext(fname) + + # match all digits and character but no points + match = re.match("([\d\w]+)", _subset) + if not match: + self.log.warning("Skipping save because the file name is not" + "compatible") + subset = match.group(0) + + # Include start and end render frame in label + label = "{subset} ({start}-{end})".format(subset=subset, + start=int(start), + end=int(end)) + + instance = context.create_instance(subset) + instance.data.update({ + "asset": os.environ["AVALON_ASSET"], # todo: not a constant + "subset": subset, + "path": path, + "ext": ext, # todo: should be redundant + "label": label, + "families": ["colorbleed.imagesequence"], + "family": "colorbleed.imagesequence", + "tool": tool # keep link to the tool + }) + + self.log.info("Found: \"%s\" " % path) + + # Sort/grouped by family (preserving local index) + context[:] = sorted(context, key=self.sort_by_family) + + return context + + def sort_by_family(self, instance): + """Sort by family""" + return instance.data.get("families", instance.data.get("family")) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py new file mode 100644 index 0000000000..26264bfcad --- /dev/null +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -0,0 +1,80 @@ +import os +import glob +import re + +import pyblish.api + +_frame_regex = re.compile("[0-9]") + + +class ExtractImageSequence(pyblish.api.Extractor): + """Extract result of saver by starting a comp render + + This will run the local render of Fusion, + """ + + order = pyblish.api.ExtractorOrder + label = "Extract Image Sequence" + families = ["colorbleed.imagesequence"] + hosts = ["fusion"] + + def process(self, context): + + current_comp = context.data["currentComp"] + start_frame = current_comp.GetAttrs("COMPN_RenderStart") + end_frame = current_comp.GetAttrs("COMPN_RenderEnd") + + # todo: read more about Render table form, page 84 + # todo: Think out strategy, create renderSettings instance? + # Build Fusion Render Job + + self.log.info("Starting render") + self.log.info("Start frame: {}".format(start_frame)) + self.log.info("End frame: {}".format(end_frame)) + + result = current_comp.Render() + if result: + + # Get all output paths after render was successful + # Note the .ID check, this is to ensure we only have savers + instances = [i for i in context[:] if i.data["tool"].ID == "Saver"] + for instance in instances: + # Ensure each instance has its files for the integrator + output_path = instance.data["path"] + query = self._create_qeury(output_path) + files = glob.glob(query) + + if "files" not in instance.data: + instance.data["files"] = list() + + print("{} files : {}".format(instance.data["subset"], + len(files))) + instance.data["files"].append(files) + + # Ensure the integrator has stagingDir + instance.data["stagingDir"] = os.path.dirname(output_path) + + def _create_qeury(self, instance): + """Create a queriable string for glob + + Args: + instance: instance of current context (comp) + + Returns: + str + """ + + clipname = instance.data["path"] + clip_dir = os.path.dirname(clipname) + basename = os.path.basename(clipname) + _, ext = os.path.splitext(basename) + + match = re.match("([0-9]{4})", basename) + if not match: + query_name = "{}.*.{}".format(instance.data["subset"], ext[1:]) + else: + query_name = basename.replace(match.group(0), ".*.") + + query = os.path.join(clip_dir, query_name) + + return query diff --git a/colorbleed/plugins/fusion/publish/validate_unique_name.py b/colorbleed/plugins/fusion/publish/validate_unique_name.py new file mode 100644 index 0000000000..6933729eb2 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_unique_name.py @@ -0,0 +1,29 @@ +import pyblish.api + + +class ValidateUniqueSubsetName(pyblish.api.InstancePlugin): + """Ensure all instances have a unique subset name""" + + order = pyblish.api.ValidatorOrder + label = "Validate Unique Subset Names" + families = ["colorbleed.imagesequence"] + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + + context = instance.context + subset = instance.data["subset"] + for other_instance in context[:]: + if other_instance == instance: + continue + + if other_instance.data["subset"] == subset: + return [instance] # current instance is invalid + + return [] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file From 4ab6b530e366fdb9d9399be53a84fae19e842b67 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 09:33:25 +0100 Subject: [PATCH 0576/1249] Also support locking of camera in CameraLoader in Maya 2017 --- colorbleed/plugins/maya/load/load_camera.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index 7edd073dbd..7bb095df67 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -29,9 +29,9 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): cameras = cmds.ls(nodes, type="camera") # Check the Maya version, lockTransform has been introduced since - # Maya 2017 + # Maya 2016.5 Ext 2 version = int(cmds.about(version=True)) - if version >= 2017: + if version >= 2016: for camera in cameras: cmds.camera(camera, edit=True, lockTransform=True) else: From f014954b5c36002336a2e22228ff11930924ed5e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:13:14 +0100 Subject: [PATCH 0577/1249] Remove unsupported hosts attribute on Creator --- colorbleed/plugins/fusion/create/create_saver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_saver.py index 75581da9cc..1e2cf721ad 100644 --- a/colorbleed/plugins/fusion/create/create_saver.py +++ b/colorbleed/plugins/fusion/create/create_saver.py @@ -6,7 +6,6 @@ class CreateTiffSaver(avalon.api.Creator): name = "tiffDefault" label = "Create Tiff Saver" - hosts = "fusion" family = "colorbleed.imagesequence" def process(self): From 28cb2258a8a5afce9004915798140bff1be1c69d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:26:53 +0100 Subject: [PATCH 0578/1249] Add validate fusion comp saved plug-in --- .../fusion/publish/validate_comp_saved.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/validate_comp_saved.py diff --git a/colorbleed/plugins/fusion/publish/validate_comp_saved.py b/colorbleed/plugins/fusion/publish/validate_comp_saved.py new file mode 100644 index 0000000000..18621ceb4f --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_comp_saved.py @@ -0,0 +1,29 @@ +import os + +import pyblish.api + + +class ValidateFusionCompSaved(pyblish.api.ContextPlugin): + """Ensure current comp is saved""" + + order = pyblish.api.ValidatorOrder + label = "Validate Comp Saved" + families = ["colorbleed.saver"] + hosts = ["fusion"] + + def process(self, context): + + comp = context.data.get("currentComp") + assert comp, "Must have Comp object" + attrs = comp.GetAttrs() + + filename = attrs["COMPS_FileName"] + if not filename: + raise RuntimeError("Comp is not saved.") + + if not os.path.exists(filename): + raise RuntimeError("Comp file does not exist: %s" % filename) + + if attrs["COMPB_Modified"]: + self.log.warning("Comp is modified. Save your comp to ensure your " + "changes propagate correctly.") From 9e07e963b05235994e2df57c212b2f91c6b1e849 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:29:17 +0100 Subject: [PATCH 0579/1249] Change Fusion saver instances to `colorbleed.saver` family --- colorbleed/plugins/fusion/publish/collect_instances.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index e04fb1d432..507d2ad301 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -67,8 +67,8 @@ class CollectInstances(pyblish.api.ContextPlugin): "path": path, "ext": ext, # todo: should be redundant "label": label, - "families": ["colorbleed.imagesequence"], - "family": "colorbleed.imagesequence", + "families": ["colorbleed.saver"], + "family": "colorbleed.saver", "tool": tool # keep link to the tool }) From a851e4c87f56eb4219896fc070a6d26b159170db Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:30:41 +0100 Subject: [PATCH 0580/1249] Collect tool in the instance's list instead of as data --- colorbleed/plugins/fusion/publish/collect_instances.py | 3 ++- colorbleed/plugins/fusion/publish/extract_image_sequence.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 507d2ad301..0772c93d3d 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -69,9 +69,10 @@ class CollectInstances(pyblish.api.ContextPlugin): "label": label, "families": ["colorbleed.saver"], "family": "colorbleed.saver", - "tool": tool # keep link to the tool }) + instance.append(tool) + self.log.info("Found: \"%s\" " % path) # Sort/grouped by family (preserving local index) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 26264bfcad..271c69ec1d 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -37,7 +37,7 @@ class ExtractImageSequence(pyblish.api.Extractor): # Get all output paths after render was successful # Note the .ID check, this is to ensure we only have savers - instances = [i for i in context[:] if i.data["tool"].ID == "Saver"] + instances = [i for i in context[:] if i[0].ID == "Saver"] for instance in instances: # Ensure each instance has its files for the integrator output_path = instance.data["path"] From 0c6a514f5464279d139e9a62c05603abc85676b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:31:01 +0100 Subject: [PATCH 0581/1249] Add Collect Fusion version plug-in --- .../fusion/publish/collect_fusion_version.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/collect_fusion_version.py diff --git a/colorbleed/plugins/fusion/publish/collect_fusion_version.py b/colorbleed/plugins/fusion/publish/collect_fusion_version.py new file mode 100644 index 0000000000..3c26423661 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/collect_fusion_version.py @@ -0,0 +1,24 @@ +import pyblish.api + +from avalon import fusion + + +class CollectFusionVersion(pyblish.api.ContextPlugin): + """Collect current comp""" + + order = pyblish.api.CollectorOrder + label = "Collect Fusion Version" + hosts = ["fusion"] + + def process(self, context): + """Collect all image sequence tools""" + + comp = context.data.get("currentComp") + if not comp: + raise RuntimeError("No comp previously collected, unable to " + "retrieve Fusion version.") + + version = comp.GetApp().Version + context.data["fusionVersion"] = version + + self.log.info("Fusion version: %s" % version) From 53325e9465866a834682ba8458917f30d6948cd3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 10:31:38 +0100 Subject: [PATCH 0582/1249] Rename Validate Unique Subset Names to Validate Unique Subsets, also relink to "colorbleed.saver" family --- ...validate_unique_name.py => validate_unique_subsets.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename colorbleed/plugins/fusion/publish/{validate_unique_name.py => validate_unique_subsets.py} (81%) diff --git a/colorbleed/plugins/fusion/publish/validate_unique_name.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py similarity index 81% rename from colorbleed/plugins/fusion/publish/validate_unique_name.py rename to colorbleed/plugins/fusion/publish/validate_unique_subsets.py index 6933729eb2..527b4acc69 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_name.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -1,12 +1,12 @@ import pyblish.api -class ValidateUniqueSubsetName(pyblish.api.InstancePlugin): +class ValidateUniqueSubsets(pyblish.api.InstancePlugin): """Ensure all instances have a unique subset name""" order = pyblish.api.ValidatorOrder - label = "Validate Unique Subset Names" - families = ["colorbleed.imagesequence"] + label = "Validate Unique Subsets" + families = ["colorbleed.saver"] hosts = ["fusion"] @classmethod @@ -26,4 +26,4 @@ class ValidateUniqueSubsetName(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file + raise RuntimeError("Animation content is invalid. See log.") From de1b8f0c77ecb2a9bf71f5c45498f4e9fa652d04 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 11:41:33 +0100 Subject: [PATCH 0583/1249] Add "startFrame" and "endFrame" in context for Fusion saver, add "outputDir" in saver instances. --- .../plugins/fusion/publish/collect_instances.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 0772c93d3d..c26c23680b 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -21,7 +21,12 @@ def get_comp_render_range(comp): class CollectInstances(pyblish.api.ContextPlugin): - """Collect Fusion saver instances""" + """Collect Fusion saver instances + + This additionally stores the Comp start and end render range in the + current context's data as "startFrame" and "endFrame". + + """ order = pyblish.api.CollectorOrder label = "Collect Instances" @@ -37,6 +42,9 @@ class CollectInstances(pyblish.api.ContextPlugin): savers = [tool for tool in tools if tool.ID == "Saver"] start, end = get_comp_render_range(comp) + context.data["startFrame"] = start + context.data["endFrame"] = end + for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] @@ -65,10 +73,11 @@ class CollectInstances(pyblish.api.ContextPlugin): "asset": os.environ["AVALON_ASSET"], # todo: not a constant "subset": subset, "path": path, + "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, "families": ["colorbleed.saver"], - "family": "colorbleed.saver", + "family": "colorbleed.saver" }) instance.append(tool) From 45d18636380f73e6de54914369108558283bcc12 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 9 Feb 2018 16:04:35 +0100 Subject: [PATCH 0584/1249] improved extractor and saver --- .../plugins/fusion/create/create_saver.py | 37 +++++++-- .../fusion/publish/_validate_filenames.py | 52 ------------ .../fusion/publish/_validate_frame_range.py | 19 ----- .../fusion/publish/collect_instances.py | 18 ++-- .../fusion/publish/extract_image_sequence.py | 82 ++++++++++--------- .../fusion/publish/validate_unique_name.py | 2 +- 6 files changed, 85 insertions(+), 125 deletions(-) delete mode 100644 colorbleed/plugins/fusion/publish/_validate_filenames.py delete mode 100644 colorbleed/plugins/fusion/publish/_validate_frame_range.py diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_saver.py index 75581da9cc..57420015f8 100644 --- a/colorbleed/plugins/fusion/create/create_saver.py +++ b/colorbleed/plugins/fusion/create/create_saver.py @@ -1,3 +1,5 @@ +import os + import avalon.api from avalon import fusion @@ -6,16 +8,39 @@ class CreateTiffSaver(avalon.api.Creator): name = "tiffDefault" label = "Create Tiff Saver" - hosts = "fusion" - family = "colorbleed.imagesequence" + hosts = ["fusion"] + family = "fusion.saver" def process(self): + file_format = "TiffFormat" + comp = fusion.get_current_comp() + + # todo: improve method of getting current environment + # todo: pref avalon.Session over os.environ + + workdir = os.path.normpath(os.environ["AVALON_WORKDIR"]) + + filename = "{}..tiff".format(self.name) + filepath = os.path.join(workdir, "render", "preview", filename) + with fusion.comp_lock_and_undo_chunk(comp): args = (-32768, -32768) # Magical position numbers saver = comp.AddTool("Saver", *args) - saver.SetAttrs({ - "TOOLS_Name": self.data.get("name", self.name), - 'TOOLST_Clip_FormatID': {1.0: 'TiffFormat'}, - }) + saver.SetAttrs({"TOOLS_Name": self.name}) + + # Setting input attributes is different from basic attributes + # Not confused with "MainInputAttributes" which + saver["Clip"] = filepath + saver["OutputFormat"] = file_format + + # # # Set standard TIFF settings + if saver[file_format] is None: + raise RuntimeError("File format is not set to TiffFormat, " + "this is a bug") + + # Set file format attributes + saver[file_format]["Depth"] = 1 # int8 | int16 | float32 | other + saver[file_format]["SaveAlpha"] = 0 + diff --git a/colorbleed/plugins/fusion/publish/_validate_filenames.py b/colorbleed/plugins/fusion/publish/_validate_filenames.py deleted file mode 100644 index 7f7764fb17..0000000000 --- a/colorbleed/plugins/fusion/publish/_validate_filenames.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -import pyblish.api - - -class ValidateFileNames(pyblish.api.Validator): - """Ensure all file names follow the same structure - - Filename should have identifiable parts: - - name ( example: Avengers_shot010_preview ) - - frame ( example: #### ) - - extension ( example: tiff ) - - The result when rendering frame 1250 would be as follows: - Avengers_shot010_preview.1250.tiff - - When certain parts need to be rendered out separately for some reason it - is advisable to something all the lines of: - Avengers_shot010_character_beauty.1250.tiff - """ - - order = pyblish.api.ValidatorOrder - label = "Validate File Names (Saver)" - families = ["colorbleed.imagesequence"] - hosts = ["fusion"] - - @classmethod - def get_invalid(cls, instance): - - invalid = [] - - path = instance.data["path"] - basename = os.path.basename(path) - - parts = basename.split(".") - if len(parts) != 3: - invalid.append(instance) - cls.log.error("%s has %i parts, should be 3" - % (instance, len(parts))) - else: - is_numbers = all(i.isdigit() for i in parts[1]) - if len(parts[1]) != 4 or not is_numbers: - cls.log.error("Number padding is not four digits") - invalid.append(instance) - - return invalid - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Found %i instances with a wrong file name " - "structure" % len(invalid)) diff --git a/colorbleed/plugins/fusion/publish/_validate_frame_range.py b/colorbleed/plugins/fusion/publish/_validate_frame_range.py deleted file mode 100644 index 134180d675..0000000000 --- a/colorbleed/plugins/fusion/publish/_validate_frame_range.py +++ /dev/null @@ -1,19 +0,0 @@ -import pyblish.api - - -class ValidateFrameRange(pyblish.api.InstancePlugin): - """Validate the frame range of the current Saver""" - - order = pyblish.api.ValidatorOrder - label = "Validate Frame Range" - families = ["colorbleed.imagesequence"] - hosts = ["fusion"] - - @classmethod - def get_invalid(cls, instance): - return [] - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Animation content is invalid. See log.") \ No newline at end of file diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index e04fb1d432..ae8e72c85a 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -3,6 +3,8 @@ import re import pyblish.api +from avalon.vendor import clique + def get_comp_render_range(comp): """Return comp's start and end render range.""" @@ -39,21 +41,18 @@ class CollectInstances(pyblish.api.ContextPlugin): start, end = get_comp_render_range(comp) for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] - if not path: self.log.warning("Skipping saver because it " "has no path set: {}".format(tool.Name)) continue fname = os.path.basename(path) - _subset, ext = os.path.splitext(fname) + # we don't use the padding + basename, ext = os.path.splitext(fname) + chars = [char for char in basename if + not char.isdigit() and char != "."] - # match all digits and character but no points - match = re.match("([\d\w]+)", _subset) - if not match: - self.log.warning("Skipping save because the file name is not" - "compatible") - subset = match.group(0) + subset = "".join(chars) # Include start and end render frame in label label = "{subset} ({start}-{end})".format(subset=subset, @@ -69,9 +68,10 @@ class CollectInstances(pyblish.api.ContextPlugin): "label": label, "families": ["colorbleed.imagesequence"], "family": "colorbleed.imagesequence", - "tool": tool # keep link to the tool }) + instance.append(tool) # For future use, store the tool + self.log.info("Found: \"%s\" " % path) # Sort/grouped by family (preserving local index) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 26264bfcad..e3f406ba39 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,10 +1,27 @@ import os -import glob -import re import pyblish.api -_frame_regex = re.compile("[0-9]") +from avalon.vendor import clique + + +def get_collection_for_instance(subset, collections): + """Get the collection which matches the subset name + + Args: + subset (str): name of the subset + collections (clique.Collection): + + Returns: + list + """ + for collection in collections: + name = collection.head + if name[-1] == ".": + name = name[:-1] + + if name == subset: + return collection class ExtractImageSequence(pyblish.api.Extractor): @@ -14,7 +31,7 @@ class ExtractImageSequence(pyblish.api.Extractor): """ order = pyblish.api.ExtractorOrder - label = "Extract Image Sequence" + label = "Extract Image Sequence (Local)" families = ["colorbleed.imagesequence"] hosts = ["fusion"] @@ -37,44 +54,33 @@ class ExtractImageSequence(pyblish.api.Extractor): # Get all output paths after render was successful # Note the .ID check, this is to ensure we only have savers - instances = [i for i in context[:] if i.data["tool"].ID == "Saver"] + # Use instance[0] to get the tool + instances = [i for i in context[:] if i[0].ID == "Saver"] for instance in instances: - # Ensure each instance has its files for the integrator - output_path = instance.data["path"] - query = self._create_qeury(output_path) - files = glob.glob(query) + # Ensure each instance has its files for the integrator + output_path = os.path.dirname(instance.data["path"]) + files = os.listdir(output_path) + pattern = clique.PATTERNS["frames"] + collections, remainder = clique.assemble(files, + patterns=[pattern], + minimum_items=1) + + assert not remainder, ("There shouldn't have been a remainder " + "for '%s': %s" % + (instance.data["subset"], + remainder)) + + # Filter collections to ensure specific files are part of + # the instance, store instance's collection if "files" not in instance.data: instance.data["files"] = list() - print("{} files : {}".format(instance.data["subset"], - len(files))) - instance.data["files"].append(files) + subset = instance.data["subset"] + collection = get_collection_for_instance(subset, collections) + + # Add found collection to the instance + instance.data["files"].append(list(collection)) # Ensure the integrator has stagingDir - instance.data["stagingDir"] = os.path.dirname(output_path) - - def _create_qeury(self, instance): - """Create a queriable string for glob - - Args: - instance: instance of current context (comp) - - Returns: - str - """ - - clipname = instance.data["path"] - clip_dir = os.path.dirname(clipname) - basename = os.path.basename(clipname) - _, ext = os.path.splitext(basename) - - match = re.match("([0-9]{4})", basename) - if not match: - query_name = "{}.*.{}".format(instance.data["subset"], ext[1:]) - else: - query_name = basename.replace(match.group(0), ".*.") - - query = os.path.join(clip_dir, query_name) - - return query + instance.data["stagingDir"] = output_path diff --git a/colorbleed/plugins/fusion/publish/validate_unique_name.py b/colorbleed/plugins/fusion/publish/validate_unique_name.py index 6933729eb2..3388263422 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_name.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_name.py @@ -4,7 +4,7 @@ import pyblish.api class ValidateUniqueSubsetName(pyblish.api.InstancePlugin): """Ensure all instances have a unique subset name""" - order = pyblish.api.ValidatorOrder + order = pyblish.api.ValidatorOrder + 0.1 label = "Validate Unique Subset Names" families = ["colorbleed.imagesequence"] hosts = ["fusion"] From 815f761a6d8df80d8d7b9f12ca0a5cc4b73e8957 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 16:12:25 +0100 Subject: [PATCH 0585/1249] Collect comp name as context label --- colorbleed/plugins/fusion/publish/collect_comp.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/collect_comp.py b/colorbleed/plugins/fusion/publish/collect_comp.py index 7dae0b908b..f14598cf42 100644 --- a/colorbleed/plugins/fusion/publish/collect_comp.py +++ b/colorbleed/plugins/fusion/publish/collect_comp.py @@ -1,3 +1,5 @@ +import os + import pyblish.api from avalon import fusion @@ -19,4 +21,8 @@ class CollectCurrentCompFusion(pyblish.api.ContextPlugin): # Store path to current file attrs = current_comp.GetAttrs() - context.data['currentFile'] = attrs.get("COMPS_FileName", "") + filepath = attrs.get("COMPS_FileName", "") + context.data['currentFile'] = filepath + + # Labelize the context + context.data["label"] = os.path.basename(filepath) From ba14e7eaa519c3e09c36ac8715ee2381aeaf9023 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 16:30:24 +0100 Subject: [PATCH 0586/1249] Add support for passthrough state for savers, improve subset naming --- .../fusion/publish/collect_instances.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index c26c23680b..23c64365a1 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -48,20 +48,20 @@ class CollectInstances(pyblish.api.ContextPlugin): for tool in savers: path = tool["Clip"][comp.TIME_UNDEFINED] + tool_attrs = tool.GetAttrs() + active = not tool_attrs["TOOLB_PassThrough"] + if not path: self.log.warning("Skipping saver because it " "has no path set: {}".format(tool.Name)) continue - fname = os.path.basename(path) - _subset, ext = os.path.splitext(fname) + from avalon.fusion.lib import get_frame_path + filename = os.path.basename(path) + head, padding, tail = get_frame_path(filename) - # match all digits and character but no points - match = re.match("([\d\w]+)", _subset) - if not match: - self.log.warning("Skipping save because the file name is not" - "compatible") - subset = match.group(0) + assert tail == os.path.splitext(path)[1], "tail == extension" + subset = head # subset is head of the filename # Include start and end render frame in label label = "{subset} ({start}-{end})".format(subset=subset, @@ -74,10 +74,12 @@ class CollectInstances(pyblish.api.ContextPlugin): "subset": subset, "path": path, "outputDir": os.path.dirname(path), - "ext": ext, # todo: should be redundant + "ext": tail, # todo: should be redundant "label": label, "families": ["colorbleed.saver"], - "family": "colorbleed.saver" + "family": "colorbleed.saver", + "active": active, + "publish": active # backwards compatibility }) instance.append(tool) From 82ccd43d95bfccb7311227123e5304e50a38ce38 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 16:30:50 +0100 Subject: [PATCH 0587/1249] Add validate saver passthrough state - ensure the pyblish state is similar to the comp's current state --- .../publish/validate_saver_passthrough.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/validate_saver_passthrough.py diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py new file mode 100644 index 0000000000..4380f4e380 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py @@ -0,0 +1,29 @@ +import pyblish.api + + +class ValidateSaverPassthrough(pyblish.api.InstancePlugin): + """Validate saver passthrough is similar to Pyblish publish state""" + + order = pyblish.api.ValidatorOrder + label = "Validate Saver Active" + families = ["colorbleed.saver"] + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + + saver = instance[0] + attr = saver.GetAttrs() + active = not attr["TOOLB_PassThrough"] + + if active != instance.data["publish"]: + return [saver] + + return [] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Saver has different passthrough state than " + "Pyblish: {} ({})".format(instance, + invalid[0].Name)) From 1e4234f45691f328f3d347c326c017e00979ec57 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Feb 2018 16:31:09 +0100 Subject: [PATCH 0588/1249] Draft setup for submission to deadline --- .../plugins/fusion/publish/submit_deadline.py | 136 ++++++++++++++++++ .../fusion/publish/submit_publish_job.py | 110 ++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/submit_deadline.py create mode 100644 colorbleed/plugins/fusion/publish/submit_publish_job.py diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py new file mode 100644 index 0000000000..7c4613d9ef --- /dev/null +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -0,0 +1,136 @@ +import os +import re +import json +import getpass + +from avalon import api +from avalon.vendor import requests + +import pyblish.api + + +class FusionSubmitCompDeadline(pyblish.api.ContextPlugin): + """Submit current Comp to Deadline + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable AVALON_DEADLINE + + """ + + label = "Submit to Deadline" + order = pyblish.api.IntegratorOrder + hosts = ["fusion"] + families = ["colorbleed.saver"] + + def process(self, context): + + from avalon.fusion.lib import get_frame_path + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + + # Collect all saver instances in context that are to be rendered + saver_instances = [] + for instance in context[:]: + if instance.data.get("families")[0] != "colorbleed.saver": + # Allow only saver family instances + continue + + if not instance.data.get("publish", True): + # Skip inactive instances + continue + + saver_instances.append(instance) + + fusion_version = int(context.data["fusionVersion"]) + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) + comment = context.data.get("comment", "") + deadline_user = context.data.get("deadlineUser", getpass.getuser()) + + # Documentation for keys available at: + # https://docs.thinkboxsoftware.com + # /products/deadline/8.0/1_User%20Manual/manual + # /manual-submission.html#job-info-file-options + payload = { + "JobInfo": { + # Top-level group name + "BatchName": filename, + + # Job name, as seen in Monitor + "Name": filename, + + # User, as seen in Monitor + "UserName": deadline_user, + + "Plugin": "Fusion", + "Frames": "{start}-{end}".format( + start=int(context.data["startFrame"]), + end=int(context.data["endFrame"]) + ), + + "Comment": comment, + }, + "PluginInfo": { + # Input + "FlowFile": filepath, + + # Mandatory for Deadline + "Version": str(fusion_version), + + # Render in high quality + "HighQuality": True, + + # Whether saver output should be checked after rendering + # is complete + "CheckOutput": True, + + # Proxy: higher numbers smaller images for faster test renders + # 1 = no proxy quality + "Proxy": 1, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + # Enable going to rendered frames from Deadline Monitor + for index, instance in enumerate(saver_instances): + head, padding, tail = get_frame_path(instance.data["path"]) + path = "{}{}{}".format(head, "#" * padding, tail) + folder, filename = os.path.split(path) + payload["JobInfo"]["OutputDirectory%d" % index] = folder + payload["JobInfo"]["OutputFilename%d" % index] = filename + + # Include critical variables with submission + keys = [ + # TODO: This won't work if the slaves don't have accesss to + # these paths, such as if slaves are running Linux and the + # submitter is on Windows. + "PYTHONPATH", + "OFX_PLUGIN_PATH", + "FUSION9_MasterPrefs" + ] + environment = dict({key: os.environ[key] for key in keys + if key in os.environ}, **api.Session) + + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + + self.log.info("Submitting..") + self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(AVALON_DEADLINE) + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) + + # Store the response for dependent job submission plug-ins + for instance in saver_instances: + instance.data["deadlineSubmissionJob"] = response.json() diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/fusion/publish/submit_publish_job.py new file mode 100644 index 0000000000..e8c725c94e --- /dev/null +++ b/colorbleed/plugins/fusion/publish/submit_publish_job.py @@ -0,0 +1,110 @@ +import os +import json + +from avalon import api +from avalon.vendor import requests + +import pyblish.api + + +def _get_script(): + """Get path to the image sequence script""" + try: + from colorbleed.scripts import publish_imagesequence + except Exception as e: + raise RuntimeError("Expected module 'publish_imagesequence'" + "to be available") + + module_path = publish_imagesequence.__file__ + if module_path.endswith(".pyc"): + module_path = module_path[:-len(".pyc")] + ".py" + + return module_path + + +class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): + """Submit image sequence publish jobs to Deadline. + + These jobs are dependent on a deadline job submission prior to this + plug-in. + + Renders are submitted to a Deadline Web Service as + supplied via the environment variable AVALON_DEADLINE + + Requires: + - instance.data["deadlineSubmission"] + - instance.data["outputDir"] + + Optional: + - instance.data["publishJobState"] (str): "Active" or "Suspended" + defaults to "Suspended" + + """ + + label = "Submit image sequence jobs to Deadline" + order = pyblish.api.IntegratorOrder + 0.1 + hosts = ["fusion"] + families = ["colorbleed.saver"] + + def process(self, instance): + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + + # Get a submission job + job = instance.data.get("deadlineSubmissionJob") + if not job: + raise RuntimeError("Can't continue without valid deadline " + "submission prior to this plug-in.") + + state = instance.data.get("publishJobState", "Suspended") + job_name = "{batch} - {subset} [publish image sequence]".format( + batch=job["Props"]["Name"], + subset=instance.data["subset"] + ) + output_dir = instance.data["outputDir"] + + # Write metadata for publish job + data = instance.data.copy() + data.pop("deadlineSubmissionJob") + metadata = { + "instance": data, + "jobs": [job], + "session": api.Session.copy() + } + metadata_filename = "{}_metadata.json".format(instance.data["subset"]) + metadata_path = os.path.join(output_dir, metadata_filename) + with open(metadata_path, "w") as f: + json.dump(metadata, f, indent=4, sort_keys=True) + + # Generate the payload for Deadline submission + payload = { + "JobInfo": { + "Plugin": "Python", + "BatchName": job["Props"]["Batch"], + "Name": job_name, + "JobType": "Normal", + "JobDependency0": job["_id"], + "UserName": job["Props"]["User"], + "Comment": instance.context.data.get("comment", ""), + "InitialStatus": state + }, + "PluginInfo": { + "Version": "3.6", + "ScriptFile": _get_script(), + "Arguments": '--path "{}"'.format(metadata_path), + "SingleFrameOnly": "True" + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + self.log.info("Submitting..") + self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + + url = "{}/api/jobs".format(AVALON_DEADLINE) + response = requests.post(url, json=payload) + if not response.ok: + raise Exception(response.text) From bdf85919c07ad19feac87b92b0f93bdd265ce47a Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 12:03:05 +0100 Subject: [PATCH 0589/1249] extended and improved validators and image sequence extractor --- .../fusion/publish/extract_image_sequence.py | 1 + .../publish/validate_background_depth.py | 41 ++++++++++++++++++ .../publish/validate_create_folder_checked.py | 42 +++++++++++++++++++ .../fusion/publish/validate_unique_subsets.py | 6 --- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index e3f406ba39..ad465a853e 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -78,6 +78,7 @@ class ExtractImageSequence(pyblish.api.Extractor): subset = instance.data["subset"] collection = get_collection_for_instance(subset, collections) + assert collection, "No collection found, this is a bug" # Add found collection to the instance instance.data["files"].append(list(collection)) diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index e69de29bb2..0664ab4002 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -0,0 +1,41 @@ +import pyblish.api + +from colorbleed import action + + +class ValidateBackgroundDepth(pyblish.api.ContextPlugin): + """Validate if all Background tool are set to float32 bit""" + + order = pyblish.api.ValidatorOrder + label = "Validate Background Depth 32 bit" + actions = [action.RepairContextAction] + hosts = ["fusion"] + families = ["*"] + optional = True + + comp = None + + @classmethod + def get_invalid(cls, context): + cls.comp = context.data.get("currentComp") + assert cls.comp, "Must have Comp object" + + backgrounds = cls.comp.GetToolList(False, "Background").values() + if not backgrounds: + return [] + + return [i for i in backgrounds if i.GetInput("Depth") != 4.0] + + def process(self, context): + invalid = self.get_invalid(context) + if invalid: + raise RuntimeError("Found %i nodes which are not set to float32" + % len(invalid)) + + @classmethod + def repair(cls): + # todo: improve this method, context should be available(?) + backgrounds = cls.comp.GetToolList(False, "Background").values() + invalid = [i for i in backgrounds if i.GetInput("Depth") != 4.0] + for i in invalid: + i.SetInput("Depth", 4.0, cls.comp.TIME_UNDEFINED) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index e69de29bb2..634296b2da 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -0,0 +1,42 @@ +import pyblish.api + +from colorbleed import action + + +class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): + """Valid if all savers have the input attribute CreateDir checked on + + This attribute ensures that the folders to which the saver will write + will be created. + """ + + order = pyblish.api.ValidatorOrder + actions = [action.RepairAction] + label = "Validate Create Folder Checked" + family = "colorbleed.saver" + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + active = instance.data.get("active", instance.data.get("publish")) + if not active: + return [] + + tool = instance[0] + create_dir = tool.GetInput("CreateDir") + if create_dir == 0.0: + cls.log.error("%s has Create Folder turned off" % instance[0].Name) + return [instance] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found %i Savers with Create Folder During " + "Render checked off") + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + for i in invalid: + tool = i[0] + tool.SetInput("CreateDir", 1.0) diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py index f0c629849a..527b4acc69 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -4,15 +4,9 @@ import pyblish.api class ValidateUniqueSubsets(pyblish.api.InstancePlugin): """Ensure all instances have a unique subset name""" -<<<<<<< HEAD:colorbleed/plugins/fusion/publish/validate_unique_name.py - order = pyblish.api.ValidatorOrder + 0.1 - label = "Validate Unique Subset Names" - families = ["colorbleed.imagesequence"] -======= order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" families = ["colorbleed.saver"] ->>>>>>> 1e4234f45691f328f3d347c326c017e00979ec57:colorbleed/plugins/fusion/publish/validate_unique_subsets.py hosts = ["fusion"] @classmethod From fe6b61476ef013911a22102c76aeb7b21ea9da30 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 12:28:55 +0100 Subject: [PATCH 0590/1249] Remove debugging print statements --- colorbleed/plugins/fusion/load/load_sequence.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 52b869e540..a6cf71d719 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -69,8 +69,6 @@ class FusionLoadSequence(api.Loader): root = api.get_representation_path(representation) path = self._get_first_image(root) - print representation - print path tool = container["_tool"] assert tool.ID == "Loader", "Must be Loader" From bc62284f8f53d48075b5c7e2efa80ae9191b6202 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 13:17:39 +0100 Subject: [PATCH 0591/1249] Strip trailing dots, spaces or underscores from subset name --- colorbleed/plugins/fusion/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 23c64365a1..912995d3e6 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -61,7 +61,7 @@ class CollectInstances(pyblish.api.ContextPlugin): head, padding, tail = get_frame_path(filename) assert tail == os.path.splitext(path)[1], "tail == extension" - subset = head # subset is head of the filename + subset = head.rstrip("_. ") # subset is head of the filename # Include start and end render frame in label label = "{subset} ({start}-{end})".format(subset=subset, From 9dfc3a3c71f4784a4fc02d055c09aae37043da2d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 13:21:30 +0100 Subject: [PATCH 0592/1249] Simplify publish imagesequence script + implement it for Fusion renders --- .../fusion/publish/submit_publish_job.py | 49 ++++- .../plugins/publish/collect_imagesequences.py | 195 +++++++++++++----- colorbleed/scripts/publish_imagesequence.py | 91 ++++---- 3 files changed, 220 insertions(+), 115 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/fusion/publish/submit_publish_job.py index e8c725c94e..1c4e71c449 100644 --- a/colorbleed/plugins/fusion/publish/submit_publish_job.py +++ b/colorbleed/plugins/fusion/publish/submit_publish_job.py @@ -1,5 +1,6 @@ import os import json +import re from avalon import api from avalon.vendor import requests @@ -31,13 +32,23 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): Renders are submitted to a Deadline Web Service as supplied via the environment variable AVALON_DEADLINE - Requires: - - instance.data["deadlineSubmission"] - - instance.data["outputDir"] + Options in instance.data: + - deadlineSubmission (dict, Required): The returned .json + data from the job submission to deadline. - Optional: - - instance.data["publishJobState"] (str): "Active" or "Suspended" - defaults to "Suspended" + - outputDir (str, Required): The output directory where the metadata + file should be generated. It's assumed that this will also be + final folder containing the output files. + + - ext (str, Optional): The extension (including `.`) that is required + in the output filename to be picked up for image sequence + publishing. + + - publishJobState (str, Optional): "Active" or "Suspended" + This defaults to "Suspended" + + This requires a "startFrame" and "endFrame" to be present in instance.data + or in context.data. """ @@ -65,13 +76,33 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ) output_dir = instance.data["outputDir"] + # Add in start/end frame + context = instance.context + start = instance.data.get("startFrame", context.data["startFrame"]) + end = instance.data.get("endFrame", context.data["endFrame"]) + + # Add in regex for sequence filename + # This assumes the output files start with subset name and ends with + # a file extension. + regex = "^{subset}.*\d+{ext}$".format( + subset=re.escape(instance.data["subset"]), + ext=re.escape(instance.data.get("ext", "\D")) + ) + # Write metadata for publish job data = instance.data.copy() data.pop("deadlineSubmissionJob") metadata = { - "instance": data, - "jobs": [job], - "session": api.Session.copy() + "regex": regex, + "startFrame": start, + "endFrame": end, + + # Optional metadata (for debugging) + "metadata": { + "instance": data, + "job": job, + "session": api.Session.copy() + } } metadata_filename = "{}_metadata.json".format(instance.data["subset"]) metadata_path = os.path.join(output_dir, metadata_filename) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 4186bd684d..40364bf44d 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -1,9 +1,86 @@ -import pyblish.api +import os +import re import copy +import json +import pprint + +import pyblish.api +from avalon import api -class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): - """Gather image sequnences from working directory""" +def collect(root, + regex=None, + exclude_regex=None, + startFrame=None, + endFrame=None): + """Collect sequence collections in root""" + + from avalon.vendor import clique + + files = list() + for filename in os.listdir(root): + + # Must have extension + ext = os.path.splitext(filename)[1] + if not ext: + continue + + # Only files + if not os.path.isfile(os.path.join(root, filename)): + continue + + # Include and exclude regex + if regex and not re.search(regex, filename): + continue + if exclude_regex and re.search(exclude_regex, filename): + continue + + files.append(filename) + + # Match collections + collections, remainder = clique.assemble(files, + minimum_items=1) + + # Ignore any remainders + if remainder: + print("Skipping remainder {}".format(remainder)) + + # Exclude any frames outside start and end frame. + for collection in collections: + for index in list(collection.indexes): + if startFrame is not None and index < startFrame: + collection.indexes.discard(index) + continue + if endFrame is not None and index > endFrame: + collection.indexes.discard(index) + continue + + # Keep only collections that have at least a single frame + collections = [c for c in collections if c.indexes] + + return collections + + +class CollectImageSequences(pyblish.api.ContextPlugin): + """Gather image sequences from working directory + + When "IMAGESEQUENCES" environment variable is set these paths (folders or + .json files) are parsed for image sequences. Otherwise the current + working directory is searched for file sequences. + + The json configuration may have the optional keys: + asset (str): The asset to publish to. If not provided fall back to + api.Session["AVALON_ASSET"] + subset (str): The subset to publish to. If not provided the sequence's + head (up to frame number) will be used. + startFrame (int): The start frame for the sequence + endFrame (int): The end frame for the sequence + root (str): The path to collect from (can be relative to the .json) + regex (str): A regex for the sequence filename + exclude_regex (str): A regex for filename to exclude from collection + metadata (dict): Custom metadata for instance.data["metadata"] + + """ order = pyblish.api.CollectorOrder targets = ["imagesequence"] @@ -11,69 +88,87 @@ class CollectMindbenderImageSequences(pyblish.api.ContextPlugin): label = "Image Sequences" def process(self, context): - import os - import json - from avalon.vendor import clique - # Force towards a single json sequence (override searching - # the current working directory) - # TODO: This logic should be simplified - USE_JSON = os.environ.get("USE_JSON", "") - if USE_JSON: - workspace = os.path.dirname(USE_JSON) - base = workspace - dirs = [os.path.splitext(os.path.basename(USE_JSON))[0]] - # Else use the current working directory + if os.environ.get("IMAGESEQUENCES"): + paths = os.environ["IMAGESEQUENCES"].split(os.pathsep) else: - workspace = context.data["workspaceDir"] - base, dirs, _ = next(os.walk(workspace)) + cwd = context.get("workspaceDir", os.getcwd()) + paths = [cwd] - for renderlayer in dirs: - abspath = os.path.join(base, renderlayer) - files = os.listdir(abspath) - pattern = clique.PATTERNS["frames"] - collections, remainder = clique.assemble(files, - patterns=[pattern], - minimum_items=1) - assert not remainder, ( - "There shouldn't have been a remainder for '%s': " - "%s" % (renderlayer, remainder)) + for path in paths: - # Maya 2017 compatibility, it inexplicably prefixes layers - # with "rs_" without warning. - compatpath = os.path.join(base, renderlayer.split("rs_", 1)[-1]) + self.log.info("Loading: {}".format(path)) - for fname in (abspath, compatpath): - try: - with open("{}.json".format(fname)) as f: - metadata = json.load(f) - break + if path.endswith(".json"): + # Search using .json configuration + with open(path, "r") as f: + try: + data = json.load(f) + except Exception as exc: + self.log.error("Error loading json: " + "{} - Exception: {}".format(path, exc)) + raise - except OSError: - continue + cwd = os.path.dirname(path) + root_override = data.get("root") + if root_override: + if os.path.isabs(root_override): + root = root_override + else: + root = os.path.join(cwd, root_override) + else: + root = cwd else: - raise Exception("%s was not published correctly " - "(missing metadata)" % renderlayer) + # Search in directory + data = dict() + root = path + + self.log.info("Collecting: {}".format(root)) + + collections = collect(root=root, + regex=data.get("regex"), + exclude_regex=data.get("exclude_regex"), + startFrame=data.get("startFrame"), + endFrame=data.get("endFrame")) + + self.log.info("Found collections: {}".format(collections)) + + if data.get("subset"): + # If subset is provided for this json then it must be a single + # collection. + if len(collections) > 1: + self.log.error("Forced subset can only work with a single " + "found sequence") + raise RuntimeError("Invalid sequence") - metadata_instance = metadata['instance'] - # For now ensure this data is ignored for collection in collections: instance = context.create_instance(str(collection)) self.log.info("Collection: %s" % list(collection)) - # Ensure each instance gets its own unique reference to - # the source data - instance_metadata = copy.deepcopy(metadata_instance) + # Ensure each instance gets a unique reference to the data + data = copy.deepcopy(data) - data = dict(instance_metadata, **{ - "name": instance.name, + # If no subset provided, get it from collection's head + subset = data.get("subset", collection.head.rstrip("_. ")) + + # If no start or end frame provided, get it from collection + indices = list(collection.indexes) + start = data.get("startFrame", indices[0]) + end = data.get("endFrame", indices[-1]) + + instance.data.update({ + "name": str(collection), "family": "colorbleed.imagesequence", "families": ["colorbleed.imagesequence"], - "subset": collection.head[:-1], - "stagingDir": os.path.join(workspace, renderlayer), + "subset": subset, + "asset": data.get("asset", api.Session["AVALON_ASSET"]), + "stagingDir": root, "files": [list(collection)], - "metadata": metadata + "startFrame": start, + "endFrame": end, + "metadata": data.get("metadata") }) - instance.data.update(data) + self.log.debug("Collected instance:\n" + "{}".format(pprint.pformat(instance.data))) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 314daacf09..2799c8c62e 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -1,21 +1,7 @@ -""" -This module is used for command line publishing of image sequences. -Due to its early intergration this module might change location within the -config. It is also subject to change - -Contributors: - Roy Nieterau - Wijnand Koreman - -Dependencies: - Avalon - Pyblish - -""" +"""This module is used for command line publishing of image sequences.""" import os import sys -import json import logging handler = logging.basicConfig() @@ -23,7 +9,7 @@ log = logging.getLogger() log.setLevel(logging.DEBUG) -def publish_data(json_file): +def publish(paths, gui=False): """Publish rendered image sequences based on the job data Args: @@ -33,63 +19,56 @@ def publish_data(json_file): None """ - with open(json_file, "r") as fp: - json_data = json.load(fp) - # Get the job's environment - job = json_data["jobs"][0] - job_env = job["Props"]["Env"] - job_env = {str(key): str(value) for key, value in job_env.items()} + assert isinstance(paths, (list, tuple)), "Must be list of paths" - # Ensure the key exists - os.environ["PYTHONPATH"] = os.environ.get("PYTHONPATH", "") - - # Add the pythonpaths (also to sys.path for local session) - pythonpaths = job_env.pop("PYTHONPATH", "").split(";") - for path in pythonpaths: - sys.path.append(path) - - os.environ['PYTHONPATH'] += ";" + ";".join(pythonpaths) - - # Use the rest of the job's environment - os.environ.update(job_env) - - # Set the current pyblish host - os.environ["PYBLISH_HOSTS"] = "shell" - - # Set the current working directory - os.chdir(os.path.dirname(json_file)) + # Set the paths to publish for the collector if any provided + if paths: + os.environ["IMAGESEQUENCES"] = os.pathsep.join(paths) # Install Avalon with shell as current host from avalon import api, shell api.install(shell) - # Publish items, returns context instances - import pyblish.util - context = pyblish.util.publish(targets=["imagesequence"]) + # Register target and host + import pyblish.api + pyblish.api.register_target("imagesequence") + pyblish.api.register_host("shell") - if not context: - log.warning("Nothing published.") - sys.exit(1) + # Publish items + if gui: + import pyblish_qml + pyblish_qml.show(modal=True) + else: + + import pyblish.util + context = pyblish.util.publish() + + if not context: + log.warning("Nothing published.") + sys.exit(1) def __main__(): import argparse parser = argparse.ArgumentParser() - parser.add_argument("--path", help="The filepath of the JSON") + parser.add_argument("--paths", + nargs="*", + default=[], + help="The filepaths to publish. This can be a " + "directory or a path to a .json publish " + "configuration.") + parser.add_argument("--gui", + default=False, + action="store_true", + help="Whether to run Pyblish in GUI mode.") kwargs, args = parser.parse_known_args() - if kwargs.path: - filepath = os.path.normpath(kwargs.path) - print("JSON File {}".format(filepath)) - if not filepath.endswith(".json"): - raise RuntimeError("Wrong extesion! Expecting publish data to be " - "stored in a .JSON file") - - publish_data(filepath) + print("Running publish imagesequence...") + print("Paths: {}".format(kwargs.paths or [os.getcwd()])) + publish(kwargs.paths, gui=kwargs.gui) -# this is needed to ensure Deadline can run the script if __name__ == '__main__': __main__() From ffbf25f4c0666a47576c6629a011d0d581e94e31 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 13:54:52 +0100 Subject: [PATCH 0593/1249] Rename MindbenderSubmitDeadline to MayaSubmitDeadline --- .../increment_current_file_deadline.py | 2 +- .../plugins/maya/publish/submit_deadline.py | 55 +------------------ 2 files changed, 2 insertions(+), 55 deletions(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index 3ad7d04bea..cb8374a7e1 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -22,7 +22,7 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): from colorbleed.action import get_errored_plugins_from_data errored_plugins = get_errored_plugins_from_data(context) - if any(plugin.__name__ == "MindbenderSubmitDeadline" + if any(plugin.__name__ == "MayaSubmitDeadline" for plugin in errored_plugins): raise RuntimeError("Skipping incrementing current file because " "submission to deadline failed.") diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 1bf413666d..79e06483b8 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -60,7 +60,7 @@ def get_renderer_variables(renderlayer=None): "filename_0": filename_0} -class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): +class MayaSubmitDeadline(pyblish.api.InstancePlugin): """Submit available render layers to Deadline Renders are submitted to a Deadline Web Service as @@ -300,56 +300,3 @@ class MindbenderSubmitDeadline(pyblish.api.InstancePlugin): "%f=%d was rounded off to nearest integer" % (value, int(value)) ) - - def create_publish_job(self, fname, user, comment, jobname, - job, json_fpath, state): - """Make sure all frames are published - - Args: - job (dict): the render job data - json_fpath (str): file path to json file - state (str): In which state the job needs to when submitted, e.g.: - "Suspended" - - Returns: - dict - """ - - url = "{}/api/jobs".format(api.Session["AVALON_DEADLINE"]) - try: - from colorbleed.scripts import publish_imagesequence - except Exception as e: - raise RuntimeError("Expected module 'publish_imagesequence'" - "to be available") - - module_path = publish_imagesequence.__file__ - if module_path.endswith(".pyc"): - module_path = module_path[:-len(".pyc")] + ".py" - - payload = { - "JobInfo": { - "Plugin": "Python", - "BatchName": fname, - "Name": "{} [publish]".format(jobname), - "JobType": "Normal", - "JobDependency0": job["_id"], - "UserName": user, - "Comment": comment, - "InitialStatus": state - }, - "PluginInfo": { - "Version": "3.6", - "ScriptFile": module_path, - "Arguments": '--path "{}"'.format(json_fpath), - "SingleFrameOnly": "True" - }, - - # Mandatory for Deadline, may be empty - "AuxFiles": [] - } - - response = requests.post(url, json=payload) - if not response.ok: - return - - return payload \ No newline at end of file From 66dba81efdb6a05027656372bc8ca1578ba9e867 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 13:55:31 +0100 Subject: [PATCH 0594/1249] Simplify assertion for AVALON_DEADLINE value, it must at least have a value --- colorbleed/plugins/maya/publish/submit_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 79e06483b8..084a50a39f 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -77,8 +77,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") - - assert AVALON_DEADLINE is not None, "Requires AVALON_DEADLINE" + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" context = instance.context workspace = context.data["workspaceDir"] From f63760a35accb930dd290bb2cdd3ffafbc540ced Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 14:22:40 +0100 Subject: [PATCH 0595/1249] Simplify class name --- colorbleed/plugins/fusion/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index 7c4613d9ef..24df593ea9 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -9,7 +9,7 @@ from avalon.vendor import requests import pyblish.api -class FusionSubmitCompDeadline(pyblish.api.ContextPlugin): +class FusionSubmitDeadline(pyblish.api.ContextPlugin): """Submit current Comp to Deadline Renders are submitted to a Deadline Web Service as From c272a54502489e0064fc4174b7c53888d341be22 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 14:33:30 +0100 Subject: [PATCH 0596/1249] improved get_sequence method, removed glob --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 9d26e0b4b5..3198120374 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -1,5 +1,4 @@ import os -import glob import re from maya import cmds @@ -143,15 +142,15 @@ class CollectYetiRig(pyblish.api.InstancePlugin): from avalon.vendor import clique - glob_pattern = filename.replace(pattern, "*") - escaped = re.escape(filename) re_pattern = escaped.replace(pattern, "-?[0-9]+") - files = glob.glob(glob_pattern) - files = [str(f) for f in files if re.match(re_pattern, f)] + source_dir = os.path.dirname(filename) + files = [f for f in os.listdir(source_dir) + if re.match(re_pattern, f)] pattern = [clique.PATTERNS["frames"]] - collection, remainer = clique.assemble(files, patterns=pattern) + collection, remainder = clique.assemble(files, + patterns=pattern) return collection From 0175e9f814396a4eab10c954188e975fdb2693c3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 14:34:25 +0100 Subject: [PATCH 0597/1249] resolved issue with input meshes being validated --- .../maya/publish/validate_yeti_rig_inputs.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py index 9cccd4dea7..1b2f8148bb 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py @@ -5,7 +5,11 @@ import colorbleed.api class ValidateYetiRigInputShapesRequired(pyblish.api.Validator): - """Validate if all input nodes have at least one incoming connection""" + """Validate if all input nodes have at least one outgoing connection. + + The input nodes are the approach to ensure the rig can be hooked up to + other meshes, for example: Alembic Mesh. + """ order = colorbleed.api.ValidateContentsOrder hosts = ["maya"] @@ -24,15 +28,26 @@ class ValidateYetiRigInputShapesRequired(pyblish.api.Validator): invalid = [] - input_set = [i for i in instance if i == "input_SET"][0] - input_nodes = cmds.sets(input_set, query=True) - for node in input_nodes: - incoming = cmds.listConnections(node, - source=True, - destination=False, - connections=True, - plugs=True) + input_set = [i for i in instance if i == "input_SET"] + assert input_set, "Current %s instance has no `input_SET`" % instance + + # Get all children + input_nodes = cmds.ls(cmds.sets(input_set, query=True), long=True) + children = [i for i in cmds.listRelatives(input_nodes, + allDescendents=True, + fullPath=True)] + children = cmds.ls(children, + type="shape", + long=True, + noIntermediate=True) + + for shape in children: + incoming = cmds.listConnections(shape, + source=False, + destination=True, + connections=True) if not incoming: - invalid.append(node) + cls.log.error("%s has not incoming connections" % shape) + invalid.append(shape) return invalid From adc429edf30d117c39e6ef0f70ade1cace2f6861 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 15:16:10 +0100 Subject: [PATCH 0598/1249] refactored based on feedback --- .../publish/validate_background_depth.py | 29 +++++++++---------- .../publish/validate_create_folder_checked.py | 8 ++--- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index 0664ab4002..d66f3de451 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -3,39 +3,38 @@ import pyblish.api from colorbleed import action -class ValidateBackgroundDepth(pyblish.api.ContextPlugin): +class ValidateBackgroundDepth(pyblish.api.InstancePlugin): """Validate if all Background tool are set to float32 bit""" order = pyblish.api.ValidatorOrder label = "Validate Background Depth 32 bit" - actions = [action.RepairContextAction] + actions = [action.RepairAction] hosts = ["fusion"] families = ["*"] optional = True - comp = None - @classmethod - def get_invalid(cls, context): - cls.comp = context.data.get("currentComp") - assert cls.comp, "Must have Comp object" + def get_invalid(cls, instance): - backgrounds = cls.comp.GetToolList(False, "Background").values() + context = instance.context + comp = context.data.get("currentComp") + assert comp, "Must have Comp object" + + backgrounds = comp.GetToolList(False, "Background").values() if not backgrounds: return [] return [i for i in backgrounds if i.GetInput("Depth") != 4.0] - def process(self, context): - invalid = self.get_invalid(context) + def process(self, instance): + invalid = self.get_invalid(instance) if invalid: raise RuntimeError("Found %i nodes which are not set to float32" % len(invalid)) @classmethod - def repair(cls): - # todo: improve this method, context should be available(?) - backgrounds = cls.comp.GetToolList(False, "Background").values() - invalid = [i for i in backgrounds if i.GetInput("Depth") != 4.0] + def repair(cls, instance): + comp = instance.context.data.get("currentComp") + invalid = cls.get_invalid(instance) for i in invalid: - i.SetInput("Depth", 4.0, cls.comp.TIME_UNDEFINED) + i.SetInput("Depth", 4.0, comp.TIME_UNDEFINED) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index 634296b2da..c1a52ee671 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -26,17 +26,17 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): create_dir = tool.GetInput("CreateDir") if create_dir == 0.0: cls.log.error("%s has Create Folder turned off" % instance[0].Name) - return [instance] + return [tool] def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found %i Savers with Create Folder During " + print(">>>", invalid) + raise RuntimeError("Found Saver with Create Folder During " "Render checked off") @classmethod def repair(cls, instance): invalid = cls.get_invalid(instance) - for i in invalid: - tool = i[0] + for tool in invalid: tool.SetInput("CreateDir", 1.0) From 666d81c236ef74246189276162a1a9abc6e5c8d1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 15:28:45 +0100 Subject: [PATCH 0599/1249] removed debug print --- .../plugins/fusion/publish/validate_create_folder_checked.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index c1a52ee671..444c808cdd 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -31,7 +31,6 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - print(">>>", invalid) raise RuntimeError("Found Saver with Create Folder During " "Render checked off") From 99cf220c49e8c12d4d7d8ec6b96ac98190d73562 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 16:20:55 +0100 Subject: [PATCH 0600/1249] changed validator due to previous logic being redudand --- .../validate_yeti_rig_input_in_instance.py | 42 +++++++++++++++ .../maya/publish/validate_yeti_rig_inputs.py | 53 ------------------- 2 files changed, 42 insertions(+), 53 deletions(-) create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py delete mode 100644 colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py new file mode 100644 index 0000000000..4829f5bd67 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py @@ -0,0 +1,42 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): + """Validate if all input nodes are part of the instance's hierarchy""" + + order = colorbleed.api.ValidateContentsOrder + hosts = ["maya"] + families = ["colorbleed.yetiRig"] + label = "Yeti Rig Input Shapes In Instance" + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Yeti Rig has invalid input meshes") + + @classmethod + def get_invalid(cls, instance): + + input_set = next((i for i in instance if i == "input_SET"), None) + assert input_set, "Current %s instance has no `input_SET`" % instance + + # Get all children, we do not care about intermediates + input_nodes = cmds.ls(cmds.sets(input_set, query=True), long=True) + dag = cmds.ls(input_nodes, dag=True, long=True) + shapes = cmds.ls(dag, long=True, shapes=True, noIntermediate=True) + + # Allow publish without input meshes. + if not shapes: + cls.log.info("Found no input meshes for %s, skipping ..." + % instance) + return [] + + # check if input node is part of groomRig instance + invalid = [s for s in shapes if s not in instance[:]] + + return invalid diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py deleted file mode 100644 index 1b2f8148bb..0000000000 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_inputs.py +++ /dev/null @@ -1,53 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class ValidateYetiRigInputShapesRequired(pyblish.api.Validator): - """Validate if all input nodes have at least one outgoing connection. - - The input nodes are the approach to ensure the rig can be hooked up to - other meshes, for example: Alembic Mesh. - """ - - order = colorbleed.api.ValidateContentsOrder - hosts = ["maya"] - families = ["colorbleed.yetiRig"] - label = "Yeti Rig Input Shapes Required" - actions = [colorbleed.api.SelectInvalidAction] - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Yeti Rig has invalid input meshes") - - @classmethod - def get_invalid(cls, instance): - - invalid = [] - - input_set = [i for i in instance if i == "input_SET"] - assert input_set, "Current %s instance has no `input_SET`" % instance - - # Get all children - input_nodes = cmds.ls(cmds.sets(input_set, query=True), long=True) - children = [i for i in cmds.listRelatives(input_nodes, - allDescendents=True, - fullPath=True)] - children = cmds.ls(children, - type="shape", - long=True, - noIntermediate=True) - - for shape in children: - incoming = cmds.listConnections(shape, - source=False, - destination=True, - connections=True) - if not incoming: - cls.log.error("%s has not incoming connections" % shape) - invalid.append(shape) - - return invalid From 13fd88b65b826175894eae6a71e7a1cd10715291 Mon Sep 17 00:00:00 2001 From: aardschok Date: Mon, 12 Feb 2018 16:36:07 +0100 Subject: [PATCH 0601/1249] improved look up loop --- .../maya/publish/validate_yeti_rig_input_in_instance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py index 4829f5bd67..58d9834617 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py @@ -37,6 +37,7 @@ class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): return [] # check if input node is part of groomRig instance - invalid = [s for s in shapes if s not in instance[:]] + instance_lookup = set(instance[:]) + invalid = [s for s in shapes if s not in instance_lookup] return invalid From a929ca5b1f5e399579c4c922475e1e4fdb784c6f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 16:40:35 +0100 Subject: [PATCH 0602/1249] Include environment from the dependent job (so avalon and config is set up correctly) --- .../plugins/fusion/publish/submit_publish_job.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/fusion/publish/submit_publish_job.py index 1c4e71c449..38677ff796 100644 --- a/colorbleed/plugins/fusion/publish/submit_publish_job.py +++ b/colorbleed/plugins/fusion/publish/submit_publish_job.py @@ -132,6 +132,16 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } + # Transfer the environment from the original job to this dependent + # job so they use the same environment + environment = job["Props"].get("Env", {}) + payload["JobInfo"].update({ + "EnvironmentKeyValue%d" % index: "{key}={value}".format( + key=key, + value=environment[key] + ) for index, key in enumerate(environment) + }) + self.log.info("Submitting..") self.log.info(json.dumps(payload, indent=4, sort_keys=True)) From 69455e0c514370d11c030e5ddd40f7067b567fd0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Feb 2018 16:58:09 +0100 Subject: [PATCH 0603/1249] Add Fusion "Version Up" comp script --- setup/fusion/scripts/Comp/Version Up.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 setup/fusion/scripts/Comp/Version Up.py diff --git a/setup/fusion/scripts/Comp/Version Up.py b/setup/fusion/scripts/Comp/Version Up.py new file mode 100644 index 0000000000..89498a9b20 --- /dev/null +++ b/setup/fusion/scripts/Comp/Version Up.py @@ -0,0 +1,13 @@ +import colorbleed.lib +import avalon.fusion + +attrs = comp.GetAttrs() +filename = comp.MapPath(attrs["COMPS_FileName"]) +if not filename: + raise RuntimeError("File not saved yet. Can't increment version.") + +new = colorbleed.lib.version_up(filename) +print("Incrementing comp to: {}".format(new)) + +with avalon.fusion.comp_lock_and_undo_chunk(comp, "Save incrementally.."): + comp.Save(new) \ No newline at end of file From 17852422bd16a9476d2f1f0a09894fde359e508d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 10:16:28 +0100 Subject: [PATCH 0604/1249] Fix actually using the options dict declared earlier in the script - note: this removes "vray" from attrPrefix in Alembic which was temporarily hardcoded --- colorbleed/plugins/maya/publish/extract_pointcache.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 060aeaec8a..405d1e6549 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -64,14 +64,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): extract_alembic(file=path, startFrame=start, endFrame=end, - **{"step": instance.data.get("step", 1.0), - "attr": ["cbId"], - "attrPrefix": ["vray"], - "writeVisibility": True, - "writeCreases": True, - "writeColorSets": writeColorSets, - "uvWrite": True, - "selection": True}) + **options) if "files" not in instance.data: instance.data["files"] = list() From 685f6ae2bdcca93de2975ae5bf50fd0310e54072 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 10:17:45 +0100 Subject: [PATCH 0605/1249] Make ValidateNodeIDsRelated optional - this allows having multiple assets to be embedded in a single rig. --- colorbleed/plugins/maya/publish/validate_node_ids_related.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index 1aced342c7..985bd54dda 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -17,6 +17,7 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): families = ["colorbleed.model", "colorbleed.look", "colorbleed.rig"] + optional = True actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] From 10f83d8421292bc529629bd90d5c6ec9fffc32d1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 12:15:46 +0100 Subject: [PATCH 0606/1249] Simplify MayaSubmitDeadline plug-in (remove dependent job publish logic) --- .../plugins/maya/publish/submit_deadline.py | 148 ++++++------------ 1 file changed, 51 insertions(+), 97 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 084a50a39f..b441260431 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -1,6 +1,5 @@ import os import json -import shutil import getpass from maya import cmds @@ -30,14 +29,14 @@ def get_renderer_variables(renderlayer=None): renderer = lib.get_renderer(renderlayer or lib.get_current_renderlayer()) render_attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS["default"]) - filename_padding = cmds.getAttr("{}.{}".format(render_attrs["node"], - render_attrs["padding"])) + padding = cmds.getAttr("{}.{}".format(render_attrs["node"], + render_attrs["padding"])) filename_0 = cmds.renderSettings(fullPath=True, firstImageName=True)[0] if renderer == "vray": - # Maya's renderSettings function does not resolved V-Ray extension - # Getting the extension for VRay settings node + # Maya's renderSettings function does not return V-Ray file extension + # so we get the extension from vraySettings extension = cmds.getAttr("vraySettings.imageFormatStr") # When V-Ray image format has not been switched once from default .png @@ -56,10 +55,39 @@ def get_renderer_variables(renderlayer=None): return {"ext": extension, "filename_prefix": filename_prefix, - "padding": filename_padding, + "padding": padding, "filename_0": filename_0} +def preview_fname(folder, scene, layer, padding, ext): + """Return output file path with #### for padding. + + Deadline requires the path to be formatted with # in place of numbers. + For example `/path/to/render.####.png` + + Args: + folder (str): The root output folder (image path) + scene (str): The scene name + layer (str): The layer name to be rendered + padding (int): The padding length + ext(str): The output file extension + + Returns: + str + + """ + + # Following hardcoded "/_/" + output = "{scene}/{scene}_{layer}/{layer}.{number}.{ext}".format( + scene=scene, + layer=layer, + number="#" * padding, + ext=ext + ) + + return os.path.join(folder, output) + + class MayaSubmitDeadline(pyblish.api.InstancePlugin): """Submit available render layers to Deadline @@ -81,44 +109,32 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): context = instance.context workspace = context.data["workspaceDir"] - fpath = context.data["currentFile"] - fname = os.path.basename(fpath) + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) comment = context.data.get("comment", "") - scene = os.path.splitext(fname)[0] + scene = os.path.splitext(filename)[0] dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty renderlayer_name = instance.name # beauty renderlayer_globals = instance.data["renderGlobals"] legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] deadline_user = context.data.get("deadlineUser", getpass.getuser()) - jobname = "%s - %s" % (fname, instance.name) + jobname = "%s - %s" % (filename, instance.name) # Get the variables depending on the renderer - # Following hardcoded "renders//_/" render_variables = get_renderer_variables(renderlayer) - output_filename_0 = self.preview_fname(scene, - renderlayer_name, - dirname, - render_variables["padding"], - render_variables["ext"]) - - # Get parent folder of render output - render_folder = os.path.dirname(output_filename_0) + output_filename_0 = preview_fname(folder=dirname, + scene=scene, + layer=renderlayer_name, + padding=render_variables["padding"], + ext=render_variables["ext"]) try: - # Ensure folders exists - os.makedirs(render_folder) + # Ensure render folder exists + os.makedirs(dirname) except OSError: pass - # Get the folder name, this will be the name of the metadata file - json_fname = os.path.basename(render_folder) - json_fpath = os.path.join(os.path.dirname(render_folder), - "{}.json".format(json_fname)) - - # E.g. http://192.168.0.1:8082/api/jobs - url = "{}/api/jobs".format(AVALON_DEADLINE) - # Documentation for keys available at: # https://docs.thinkboxsoftware.com # /products/deadline/8.0/1_User%20Manual/manual @@ -126,7 +142,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload = { "JobInfo": { # Top-level group name - "BatchName": fname, + "BatchName": filename, # Job name, as seen in Monitor "Name": jobname, @@ -149,7 +165,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): }, "PluginInfo": { # Input - "SceneFile": fpath, + "SceneFile": filepath, # Output directory and filename "OutputFilePath": dirname.replace("\\", "/"), @@ -178,7 +194,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } - # Include critical variables with submission + # Include critical environment variables with submission keys = [ # This will trigger `userSetup.py` on the slave # such that proper initialisation happens the same @@ -218,74 +234,12 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.log.info("Submitting..") self.log.info(json.dumps(payload, indent=4, sort_keys=True)) + # E.g. http://192.168.0.1:8082/api/jobs + url = "{}/api/jobs".format(AVALON_DEADLINE) response = requests.post(url, json=payload) - if response.ok: - # Write metadata for publish - render_job = response.json() - data = { - "submission": payload, - "session": api.Session, - "instance": instance.data, - "jobs": [render_job], - } - - with open(json_fpath, "w") as f: - json.dump(data, f, indent=4, sort_keys=True) - - self.log.info("Creating publish job") - state = instance.data["suspendPublishJob"] - publish_job = self.create_publish_job(fname, - deadline_user, - comment, - jobname, - render_job, - json_fpath, - state) - if not publish_job: - self.log.error("Could not submit publish job!") - else: - self.log.info(publish_job) - - else: - try: - shutil.rmtree(dirname) - except OSError: - # This is nice-to-have, but not critical to the operation - pass - + if not response.ok: raise Exception(response.text) - def preview_fname(self, scene, layer, folder, padding, ext): - """Return outputted filename with #### for padding - - Passing the absolute path to Deadline enables Deadline Monitor - to provide the user with a Job Output menu option. - - Deadline requires the path to be formatted with # in place of numbers. - - From - /path/to/render.0000.png - To - /path/to/render.####.png - - Args: - layer: name of the current layer to be rendered - folder (str): folder to which will be written - padding (int): padding length - ext(str): file extension - - Returns: - str - - """ - - padded_basename = "{}.{}.{}".format(layer, "#" * padding, ext) - scene_layer_folder = "{}_{}".format(scene, layer) - preview_fname = os.path.join(folder, scene, scene_layer_folder, - padded_basename) - - return preview_fname - def preflight_check(self, instance): """Ensure the startFrame, endFrame and byFrameStep are integers""" From c521d6c8b73e403494d3f0a0fb89d3b65eb08c29 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 12:35:55 +0100 Subject: [PATCH 0607/1249] Move context labeling into own plug-in so also maya benefits --- .../plugins/fusion/publish/collect_comp.py | 6 +---- .../plugins/publish/collect_context_label.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 colorbleed/plugins/publish/collect_context_label.py diff --git a/colorbleed/plugins/fusion/publish/collect_comp.py b/colorbleed/plugins/fusion/publish/collect_comp.py index f14598cf42..1cf182c8ca 100644 --- a/colorbleed/plugins/fusion/publish/collect_comp.py +++ b/colorbleed/plugins/fusion/publish/collect_comp.py @@ -20,9 +20,5 @@ class CollectCurrentCompFusion(pyblish.api.ContextPlugin): context.data["currentComp"] = current_comp # Store path to current file - attrs = current_comp.GetAttrs() - filepath = attrs.get("COMPS_FileName", "") + filepath = current_comp.GetAttrs().get("COMPS_FileName", "") context.data['currentFile'] = filepath - - # Labelize the context - context.data["label"] = os.path.basename(filepath) diff --git a/colorbleed/plugins/publish/collect_context_label.py b/colorbleed/plugins/publish/collect_context_label.py new file mode 100644 index 0000000000..0bee510861 --- /dev/null +++ b/colorbleed/plugins/publish/collect_context_label.py @@ -0,0 +1,22 @@ +import os +import pyblish.api + + +class CollectCurrentShellFile(pyblish.api.ContextPlugin): + """Labelize context using the registered host and current file""" + + order = pyblish.api.CollectorOrder + 0.25 + label = "Context Label" + + def process(self, context): + + # Get last registered host + host = pyblish.api.registered_hosts()[-1] + + # Get scene name from "currentFile" + path = context.data.get("currentFile") or "" + base = os.path.basename(path) + + # Set label + label = "{host} - {scene}".format(host=host.title(), scene=base) + context.data["label"] = label From a1b99cf3066617e6037f62f8d68a050d7e1269ed Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 12:36:38 +0100 Subject: [PATCH 0608/1249] Move and change submit publish job so both Maya and Fusion can use it --- colorbleed/plugins/{fusion => }/publish/submit_publish_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename colorbleed/plugins/{fusion => }/publish/submit_publish_job.py (98%) diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py similarity index 98% rename from colorbleed/plugins/fusion/publish/submit_publish_job.py rename to colorbleed/plugins/publish/submit_publish_job.py index 38677ff796..fff6059d44 100644 --- a/colorbleed/plugins/fusion/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -54,8 +54,8 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 - hosts = ["fusion"] - families = ["colorbleed.saver"] + hosts = ["fusion", "maya"] + families = ["colorbleed.saver", "colorbleed.renderlayer"] def process(self, instance): From 90c01285186d7f97f5cc2c5299996de1ec749662 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 13 Feb 2018 12:37:24 +0100 Subject: [PATCH 0609/1249] Solved issue with separate plugins for local and deadline --- .../fusion/publish/collect_instances.py | 13 +++++- .../fusion/publish/extract_image_sequence.py | 44 +++++++------------ .../plugins/fusion/publish/submit_deadline.py | 4 +- .../fusion/publish/submit_publish_job.py | 3 +- .../publish/validate_background_depth.py | 2 +- .../fusion/publish/validate_comp_saved.py | 2 +- .../publish/validate_create_folder_checked.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../fusion/publish/validate_unique_subsets.py | 2 +- 9 files changed, 36 insertions(+), 38 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index e9a7bda799..4b2bbca61b 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -36,6 +36,15 @@ class CollectInstances(pyblish.api.ContextPlugin): from avalon.fusion.lib import get_frame_path + # Change family when submitting to Deadline + targets = pyblish.api.registered_targets() + if "deadline" in targets: + # Submit to Deadline + family = "fusion.deadline" + else: + # Render local + family = "colorbleed.imagesequence" + comp = context.data["currentComp"] # Get all savers in the comp @@ -76,8 +85,8 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": tail, # todo: should be redundant "label": label, - "families": ["colorbleed.saver"], - "family": "colorbleed.saver", + "families": [family], + "family": family, "active": active, "publish": active # backwards compatibility }) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index ad465a853e..a23f9da098 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -5,25 +5,6 @@ import pyblish.api from avalon.vendor import clique -def get_collection_for_instance(subset, collections): - """Get the collection which matches the subset name - - Args: - subset (str): name of the subset - collections (clique.Collection): - - Returns: - list - """ - for collection in collections: - name = collection.head - if name[-1] == ".": - name = name[:-1] - - if name == subset: - return collection - - class ExtractImageSequence(pyblish.api.Extractor): """Extract result of saver by starting a comp render @@ -31,9 +12,10 @@ class ExtractImageSequence(pyblish.api.Extractor): """ order = pyblish.api.ExtractorOrder - label = "Extract Image Sequence (Local)" + label = "Render Local" families = ["colorbleed.imagesequence"] hosts = ["fusion"] + targets = ["renderlocal"] def process(self, context): @@ -41,10 +23,7 @@ class ExtractImageSequence(pyblish.api.Extractor): start_frame = current_comp.GetAttrs("COMPN_RenderStart") end_frame = current_comp.GetAttrs("COMPN_RenderEnd") - # todo: read more about Render table form, page 84 - # todo: Think out strategy, create renderSettings instance? # Build Fusion Render Job - self.log.info("Starting render") self.log.info("Start frame: {}".format(start_frame)) self.log.info("End frame: {}".format(end_frame)) @@ -66,10 +45,9 @@ class ExtractImageSequence(pyblish.api.Extractor): patterns=[pattern], minimum_items=1) - assert not remainder, ("There shouldn't have been a remainder " - "for '%s': %s" % - (instance.data["subset"], - remainder)) + assert not remainder, ("There should be no remainder for '%s': " + "%s" % (instance.data["subset"], + remainder)) # Filter collections to ensure specific files are part of # the instance, store instance's collection @@ -77,7 +55,17 @@ class ExtractImageSequence(pyblish.api.Extractor): instance.data["files"] = list() subset = instance.data["subset"] - collection = get_collection_for_instance(subset, collections) + collection = next((c for c in collections if + c.head[:-1] == subset), None) + # for c in collections: + # name = c.head + # if name[-1] == ".": + # name = name[:-1] + # + # if name == subset: + # collection = c + # break + assert collection, "No collection found, this is a bug" # Add found collection to the instance diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index 24df593ea9..529bd29812 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -1,5 +1,4 @@ import os -import re import json import getpass @@ -20,7 +19,8 @@ class FusionSubmitDeadline(pyblish.api.ContextPlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["fusion.deadline"] + targets = ["deadline"] def process(self, context): diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/fusion/publish/submit_publish_job.py index 38677ff796..13028b0391 100644 --- a/colorbleed/plugins/fusion/publish/submit_publish_job.py +++ b/colorbleed/plugins/fusion/publish/submit_publish_job.py @@ -55,7 +55,8 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["fusion.deadline"] + targets = ["deadline"] def process(self, instance): diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index d66f3de451..a3ce8966ae 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["*"] + families = ["fusion.deadline", "colorbleed.imagesequence"] optional = True @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_comp_saved.py b/colorbleed/plugins/fusion/publish/validate_comp_saved.py index 18621ceb4f..bc8dd85b1c 100644 --- a/colorbleed/plugins/fusion/publish/validate_comp_saved.py +++ b/colorbleed/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["colorbleed.saver"] + families = ["fusion.deadline", "colorbleed.imagesequence"] hosts = ["fusion"] def process(self, context): diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index 444c808cdd..8b34950c58 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - family = "colorbleed.saver" + families = ["fusion.deadline", "colorbleed.imagesequence"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py index 4380f4e380..a7c8d5efb2 100644 --- a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py +++ b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Active" - families = ["colorbleed.saver"] + families = ["fusion.deadline", "colorbleed.imagesequence"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py index 527b4acc69..aec8b5c226 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["colorbleed.saver"] + families = ["fusion.deadline", "colorbleed.imagesequence"] hosts = ["fusion"] @classmethod From 9e6a302719e2f9c71615f3af4033164e7c31c843 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Feb 2018 12:51:16 +0100 Subject: [PATCH 0610/1249] Remove relative filepath conversion (since it wasn't working in our case anyway because of backslash vs. forward slash mismatches in path) --- .../plugins/maya/publish/collect_renderlayers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 2c090df7e7..b3157551e0 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -15,12 +15,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): def process(self, context): - registered_root = api.registered_root() - asset_name = api.Session["AVALON_ASSET"] - - current_file = context.data["currentFile"] - relative_file = current_file.replace(registered_root, "{root}") - source_file = relative_file.replace("\\", "/") + asset = api.Session["AVALON_ASSET"] + filepath = context.data["currentFile"].replace("\\", "/") # Get render globals node try: @@ -68,10 +64,14 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # instance subset "family": "Render Layers", "families": ["colorbleed.renderlayer"], - "asset": asset_name, + "asset": asset, "time": api.time(), "author": context.data["user"], - "source": source_file} + + # Add source to allow tracing back to the scene from + # which was submitted originally + "source": filepath + } # Apply each user defined attribute as data for attr in cmds.listAttr(layer, userDefined=True) or list(): From 354f5a0f4c7246319ca55daf389bccc6d01e851e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Feb 2018 10:36:22 +0100 Subject: [PATCH 0611/1249] Move default layer logic into one block (improve readability) --- .../plugins/maya/publish/collect_renderlayers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index b3157551e0..0060aa8df9 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -26,24 +26,24 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): "renderGlobals node") return - default_layer = "{}.includeDefaultRenderLayer".format(render_globals) - use_defaultlayer = cmds.getAttr(default_layer) - # Get render layers renderlayers = [i for i in cmds.ls(type="renderLayer") if cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] + # Include/exclude default render layer + default_layer = "{}.includeDefaultRenderLayer".format(render_globals) + use_defaultlayer = cmds.getAttr(default_layer) + if not use_defaultlayer: + renderlayers = [i for i in renderlayers if + not i.endswith("defaultRenderLayer")] + # Sort by displayOrder def sort_by_display_order(layer): return cmds.getAttr("%s.displayOrder" % layer) renderlayers = sorted(renderlayers, key=sort_by_display_order) - if not use_defaultlayer: - renderlayers = [i for i in renderlayers if - not i.endswith("defaultRenderLayer")] - for layer in renderlayers: if layer.endswith("defaultRenderLayer"): layername = "masterLayer" From a33db2fd0010b6839ea70f2b65848737a80252d9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:29:45 +0100 Subject: [PATCH 0612/1249] changed logged and returned item in get_invalid --- .../fusion/publish/publish_image_sequences.py | 0 .../validate_filename_has_extension.py | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/publish_image_sequences.py create mode 100644 colorbleed/plugins/fusion/publish/validate_filename_has_extension.py diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py new file mode 100644 index 0000000000..18f1649756 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py @@ -0,0 +1,37 @@ +import os + +import pyblish.api + + +class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): + """Ensure the Saver has an extension in the filename path + + This is to counter any possible file being written as `filename` instead + of `filename.frame.ext`. + Fusion does not set an extension for your filename + when changing the file format of the saver. + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Filename Has Extension" + families = ["fusion.deadline", "colorbleed.imagesequence"] + hosts = ["fusion"] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found Saver(s) without a extesions") + + @classmethod + def get_invalid(cls, instance): + + path = instance.data["path"] + fname, ext = os.path.splitext(path) + + if not ext: + cls.log.error("%s has no extension specified" % + instance[0].Name) + # Return the tool + return [instance[0]] + + return [] From 1e9471a160c31208cf678b3bc7785b54349cc460 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:30:29 +0100 Subject: [PATCH 0613/1249] removed redundant import --- colorbleed/plugins/fusion/publish/collect_fusion_version.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_fusion_version.py b/colorbleed/plugins/fusion/publish/collect_fusion_version.py index 3c26423661..65d8386f33 100644 --- a/colorbleed/plugins/fusion/publish/collect_fusion_version.py +++ b/colorbleed/plugins/fusion/publish/collect_fusion_version.py @@ -1,7 +1,5 @@ import pyblish.api -from avalon import fusion - class CollectFusionVersion(pyblish.api.ContextPlugin): """Collect current comp""" From 764408d4aceb07423e1519ebf7747e48a7be2456 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:31:53 +0100 Subject: [PATCH 0614/1249] cosmetics --- .../fusion/publish/publish_image_sequences.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index e69de29bb2..6c8a72c8d9 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -0,0 +1,67 @@ +import re +import os +import json +import subprocess + +import pyblish.api + + +class PublishImageSequence(pyblish.api.Extractor): + """Extract result of saver by starting a comp render + + This will run the local render of Fusion, + """ + + order = pyblish.api.ExtractorOrder + label = "Publish Rendered Image Sequence(s)" + hosts = ["fusion"] + targets = ["renderlocal"] + + def process(self, instance): + + context = instance.context + subset = instance.data["subset"] + output_directory = instance.data["outputDir"] + ext = instance.data["ext"] + + # Regex to match resulting renders + regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset), + ext=re.escape(ext)) + + metadata = { + "regex": regex, + "startFrame": context.data["startFrame"], + "endFrame": context.data["endFrame"], + "asset": instance.data["asset"], + "subset": subset + } + + # Write metadata + # todo: create temp file or more unique name for json + path = os.path.join(output_directory, + "{}_metadata.json".format(subset)) + + # Create subprocess command string + from colorbleed.scripts import publish_imagesequence + + module_path = publish_imagesequence.__file__ + if module_path.endswith(".pyc"): + module_path = module_path[:-len(".pyc")] + ".py" + + cmd = '{0} --paths"{1}"'.format(module_path, path) + with open(path, "w") as f: + json.dump(metadata, f) + + env = os.environ.copy() + env["IMAGESEQUENCE"] = path + + subprocess.Popen(cmd, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + # todo: ensure publish went without any issues + valid = bool(context) + if not valid: + raise RuntimeError("Unable to publish image sequences") + From 60e157e3288de584f3baa8f215198398f3c64f38 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:32:51 +0100 Subject: [PATCH 0615/1249] implemented subprocess publish image sequences --- colorbleed/scripts/publish_imagesequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 2799c8c62e..aeb43dc8b7 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -13,7 +13,8 @@ def publish(paths, gui=False): """Publish rendered image sequences based on the job data Args: - json_file (str): the json file of the data dump of the submitted job + paths (list): a list of paths where to publish from + gui (bool, Optional): Choose to show Pyblish GUI, default is False Returns: None @@ -21,7 +22,7 @@ def publish(paths, gui=False): """ assert isinstance(paths, (list, tuple)), "Must be list of paths" - + assert any(paths) # Set the paths to publish for the collector if any provided if paths: os.environ["IMAGESEQUENCES"] = os.pathsep.join(paths) From 701d64f57316de51119fff6f9d6d80e7b020587f Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:34:31 +0100 Subject: [PATCH 0616/1249] removed subprocess publish image sequences --- .../fusion/publish/extract_image_sequence.py | 51 +------------------ 1 file changed, 2 insertions(+), 49 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index a23f9da098..8996b7e1c2 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,9 +1,5 @@ -import os - import pyblish.api -from avalon.vendor import clique - class ExtractImageSequence(pyblish.api.Extractor): """Extract result of saver by starting a comp render @@ -13,7 +9,6 @@ class ExtractImageSequence(pyblish.api.Extractor): order = pyblish.api.ExtractorOrder label = "Render Local" - families = ["colorbleed.imagesequence"] hosts = ["fusion"] targets = ["renderlocal"] @@ -29,47 +24,5 @@ class ExtractImageSequence(pyblish.api.Extractor): self.log.info("End frame: {}".format(end_frame)) result = current_comp.Render() - if result: - - # Get all output paths after render was successful - # Note the .ID check, this is to ensure we only have savers - # Use instance[0] to get the tool - instances = [i for i in context[:] if i[0].ID == "Saver"] - for instance in instances: - - # Ensure each instance has its files for the integrator - output_path = os.path.dirname(instance.data["path"]) - files = os.listdir(output_path) - pattern = clique.PATTERNS["frames"] - collections, remainder = clique.assemble(files, - patterns=[pattern], - minimum_items=1) - - assert not remainder, ("There should be no remainder for '%s': " - "%s" % (instance.data["subset"], - remainder)) - - # Filter collections to ensure specific files are part of - # the instance, store instance's collection - if "files" not in instance.data: - instance.data["files"] = list() - - subset = instance.data["subset"] - collection = next((c for c in collections if - c.head[:-1] == subset), None) - # for c in collections: - # name = c.head - # if name[-1] == ".": - # name = name[:-1] - # - # if name == subset: - # collection = c - # break - - assert collection, "No collection found, this is a bug" - - # Add found collection to the instance - instance.data["files"].append(list(collection)) - - # Ensure the integrator has stagingDir - instance.data["stagingDir"] = output_path + if not result: + raise RuntimeError("Comp render failed") From e467434eb75f5dc00e2acd4513dbd297c0a50cf3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:35:16 +0100 Subject: [PATCH 0617/1249] removed hosts --- colorbleed/plugins/publish/collect_imagesequences.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 40364bf44d..1873f4d5af 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -84,7 +84,6 @@ class CollectImageSequences(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder targets = ["imagesequence"] - hosts = ["shell"] label = "Image Sequences" def process(self, context): From db16bcf26da6bf0533137dd791f871a59cad09d4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 12:50:11 +0100 Subject: [PATCH 0618/1249] changed family name to counter integrator --- colorbleed/plugins/fusion/publish/collect_instances.py | 2 +- colorbleed/plugins/fusion/publish/validate_background_depth.py | 2 +- colorbleed/plugins/fusion/publish/validate_comp_saved.py | 2 +- .../plugins/fusion/publish/validate_create_folder_checked.py | 2 +- .../plugins/fusion/publish/validate_filename_has_extension.py | 2 +- colorbleed/plugins/fusion/publish/validate_saver_passthrough.py | 2 +- colorbleed/plugins/fusion/publish/validate_unique_subsets.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 4b2bbca61b..b103141307 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -43,7 +43,7 @@ class CollectInstances(pyblish.api.ContextPlugin): family = "fusion.deadline" else: # Render local - family = "colorbleed.imagesequence" + family = "fusion.local" comp = context.data["currentComp"] diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index a3ce8966ae..6173fd836a 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] optional = True @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_comp_saved.py b/colorbleed/plugins/fusion/publish/validate_comp_saved.py index bc8dd85b1c..741171ba10 100644 --- a/colorbleed/plugins/fusion/publish/validate_comp_saved.py +++ b/colorbleed/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] hosts = ["fusion"] def process(self, context): diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index 8b34950c58..3aa2552651 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py index 18f1649756..a3d6b8c972 100644 --- a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py +++ b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py @@ -14,7 +14,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] hosts = ["fusion"] def process(self, instance): diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py index a7c8d5efb2..c2f910f642 100644 --- a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py +++ b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Active" - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py index aec8b5c226..a60304efcf 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["fusion.deadline", "colorbleed.imagesequence"] + families = ["fusion.deadline", "fusion.local"] hosts = ["fusion"] @classmethod From 3e960d307e60e52472797965499a18537fe3385f Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 14:52:28 +0100 Subject: [PATCH 0619/1249] added logger for subprocess --- .../fusion/publish/publish_image_sequences.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 6c8a72c8d9..69df8ed0ee 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -37,7 +37,6 @@ class PublishImageSequence(pyblish.api.Extractor): } # Write metadata - # todo: create temp file or more unique name for json path = os.path.join(output_directory, "{}_metadata.json".format(subset)) @@ -48,20 +47,25 @@ class PublishImageSequence(pyblish.api.Extractor): if module_path.endswith(".pyc"): module_path = module_path[:-len(".pyc")] + ".py" - cmd = '{0} --paths"{1}"'.format(module_path, path) + cmd = 'python {0} --paths "{1}"'.format(module_path, path) with open(path, "w") as f: json.dump(metadata, f) - env = os.environ.copy() - env["IMAGESEQUENCE"] = path + process = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) - subprocess.Popen(cmd, - env=env, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + output, error = process.communicate() - # todo: ensure publish went without any issues - valid = bool(context) - if not valid: - raise RuntimeError("Unable to publish image sequences") + if output: + self.log.info(output) + + if error: + self.log.error(error) + raise RuntimeError(error) + + # todo: ensure publish went without any issues + valid = bool(context) + if not valid: + raise RuntimeError("Unable to publish image sequences") From db70d1950bcca182119e74871329dc0b1f3d2210 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 14:53:44 +0100 Subject: [PATCH 0620/1249] added custom regex to support more filename structures --- colorbleed/plugins/publish/collect_imagesequences.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index 1873f4d5af..f8ed9946c6 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -38,7 +38,10 @@ def collect(root, files.append(filename) # Match collections + # Support filenames like: projectX_shot01_0010.tiff with this regex + pattern = r"(?P(?P0*)\d+)\.\D+\d?$" collections, remainder = clique.assemble(files, + patterns=[pattern], minimum_items=1) # Ignore any remainders From 510140897bd498971116d3dad14c5f575695f7ae Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 14:56:19 +0100 Subject: [PATCH 0621/1249] named logger and assert there are actual paths are incoming --- colorbleed/scripts/publish_imagesequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index aeb43dc8b7..3f00b207b1 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -5,7 +5,7 @@ import sys import logging handler = logging.basicConfig() -log = logging.getLogger() +log = logging.getLogger("Publish Image Sequences") log.setLevel(logging.DEBUG) @@ -22,7 +22,8 @@ def publish(paths, gui=False): """ assert isinstance(paths, (list, tuple)), "Must be list of paths" - assert any(paths) + log.info(paths) + assert any(paths), "No paths found in the list" # Set the paths to publish for the collector if any provided if paths: os.environ["IMAGESEQUENCES"] = os.pathsep.join(paths) From 0136671bb410401c9c87c03502517f4267b8f97b Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 17:13:20 +0100 Subject: [PATCH 0622/1249] put collection in instance --- colorbleed/plugins/publish/collect_imagesequences.py | 1 + colorbleed/plugins/publish/validate_sequence_frames.py | 0 2 files changed, 1 insertion(+) create mode 100644 colorbleed/plugins/publish/validate_sequence_frames.py diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_imagesequences.py index f8ed9946c6..ad08a03a54 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_imagesequences.py @@ -171,6 +171,7 @@ class CollectImageSequences(pyblish.api.ContextPlugin): "endFrame": end, "metadata": data.get("metadata") }) + instance.append(collection) self.log.debug("Collected instance:\n" "{}".format(pprint.pformat(instance.data))) diff --git a/colorbleed/plugins/publish/validate_sequence_frames.py b/colorbleed/plugins/publish/validate_sequence_frames.py new file mode 100644 index 0000000000..e69de29bb2 From 4e4c69d660384fb58ae0169c073ff9e36b1657cb Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 17:14:08 +0100 Subject: [PATCH 0623/1249] added custom logger for subprocess --- .../fusion/publish/publish_image_sequences.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 69df8ed0ee..64d4648475 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -55,14 +55,21 @@ class PublishImageSequence(pyblish.api.Extractor): stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - output, error = process.communicate() + while True: + output = process.stdout.readline() + # Break when there is no output or a return code has been given + if output == '' and process.poll() is not None: + break + if output: + line = output.strip() + if line.startswith("ERROR"): + self.log.error(line) + else: + self.log.info(line) - if output: - self.log.info(output) - - if error: - self.log.error(error) - raise RuntimeError(error) + if process.returncode != 0: + raise RuntimeError("Process quit with non-zero " + "return code: {}".format(process.returncode)) # todo: ensure publish went without any issues valid = bool(context) From 6fa048a541ddb88d8e3315721d993bf315da6a7f Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 17:14:36 +0100 Subject: [PATCH 0624/1249] added validate frames for sequence --- .../publish/validate_sequence_frames.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/colorbleed/plugins/publish/validate_sequence_frames.py b/colorbleed/plugins/publish/validate_sequence_frames.py index e69de29bb2..9d1764f719 100644 --- a/colorbleed/plugins/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/publish/validate_sequence_frames.py @@ -0,0 +1,19 @@ +import pyblish.api + + +class ValidateSequenceFrames(pyblish.api.InstancePlugin): + + order = pyblish.api.ValidatorOrder + label = "Validate Sequence Frames" + families = ["colorbleed.imagesequence", "colorbleed.yeticache"] + + def process(self, instance): + + collection = instance[0] + frames = collection.indexes + + assert frames[0] == instance.data["startFrame"] + assert frames[-1] == instance.data["endFrame"] + + missing = collection.holes + assert not missing, "Missing frames: %s" % (missing,) From 43e3e15975a365fa029c7d5ba5e6472eed21f0c8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 17:15:50 +0100 Subject: [PATCH 0625/1249] log errors from context --- colorbleed/scripts/publish_imagesequence.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 3f00b207b1..915f728c22 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -45,11 +45,22 @@ def publish(paths, gui=False): import pyblish.util context = pyblish.util.publish() + # todo: ensure publish went flawless if not context: - log.warning("Nothing published.") + log.warning("Nothing collected.") sys.exit(1) + # Collect errors, {plugin name: error} + errors = {str(r["plugin"].__name__): r["error"] for r in + context.data["results"] if r["error"]} + + if errors: + log.error(" Errors occurred ...") + for plugin, error in errors.items(): + log.error(" {}".format(error)) + sys.exit(2) + def __main__(): import argparse From 66e3b555823db53915f1bf78e2305be1b56b76ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 17:20:37 +0100 Subject: [PATCH 0626/1249] removed todo --- colorbleed/scripts/publish_imagesequence.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 915f728c22..1e1478368c 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -45,7 +45,6 @@ def publish(paths, gui=False): import pyblish.util context = pyblish.util.publish() - # todo: ensure publish went flawless if not context: log.warning("Nothing collected.") From 83bf264f4a03d0adb7226d3b80a92c5d2912565a Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 14 Feb 2018 18:05:13 +0100 Subject: [PATCH 0627/1249] improved subprocess logging in pyblish for fusion --- .../plugins/fusion/publish/publish_image_sequences.py | 4 ++++ .../plugins/publish/validate_sequence_frames.py | 7 +++++-- colorbleed/scripts/publish_imagesequence.py | 11 ++++++----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 64d4648475..10a2f67ce0 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -52,6 +52,7 @@ class PublishImageSequence(pyblish.api.Extractor): json.dump(metadata, f) process = subprocess.Popen(cmd, + bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -59,6 +60,7 @@ class PublishImageSequence(pyblish.api.Extractor): output = process.stdout.readline() # Break when there is no output or a return code has been given if output == '' and process.poll() is not None: + process.stdout.close() break if output: line = output.strip() @@ -68,6 +70,8 @@ class PublishImageSequence(pyblish.api.Extractor): self.log.info(line) if process.returncode != 0: + # self.log.error("Return code: {}".format(process.returncode)) + # self.log.error("Process quit with non-zero return code") raise RuntimeError("Process quit with non-zero " "return code: {}".format(process.returncode)) diff --git a/colorbleed/plugins/publish/validate_sequence_frames.py b/colorbleed/plugins/publish/validate_sequence_frames.py index 9d1764f719..a4704a1f4b 100644 --- a/colorbleed/plugins/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/publish/validate_sequence_frames.py @@ -10,10 +10,13 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): collection = instance[0] - frames = collection.indexes + + frames = list(collection.indexes) assert frames[0] == instance.data["startFrame"] assert frames[-1] == instance.data["endFrame"] - missing = collection.holes + missing = collection.holes().indexes assert not missing, "Missing frames: %s" % (missing,) + + raise RuntimeError("STOP") \ No newline at end of file diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_imagesequence.py index 1e1478368c..a3e2b00a42 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_imagesequence.py @@ -8,6 +8,8 @@ handler = logging.basicConfig() log = logging.getLogger("Publish Image Sequences") log.setLevel(logging.DEBUG) +error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}" + def publish(paths, gui=False): """Publish rendered image sequences based on the job data @@ -51,13 +53,12 @@ def publish(paths, gui=False): sys.exit(1) # Collect errors, {plugin name: error} - errors = {str(r["plugin"].__name__): r["error"] for r in - context.data["results"] if r["error"]} + error_results = [r for r in context.data["results"] if r["error"]] - if errors: + if error_results: log.error(" Errors occurred ...") - for plugin, error in errors.items(): - log.error(" {}".format(error)) + for result in error_results: + log.error(error_format.format(**result)) sys.exit(2) From a45878501981c498ef91d9fe43de090d8a06a771 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Feb 2018 18:34:15 +0100 Subject: [PATCH 0628/1249] Implement FUS-20: Update Fusion sequence loader consistently --- .../plugins/fusion/load/load_sequence.py | 187 ++++++++++++++++-- 1 file changed, 168 insertions(+), 19 deletions(-) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index a6cf71d719..1f834532a0 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -1,5 +1,101 @@ -from avalon import api import os +import contextlib + +from avalon import api +import avalon.io as io + + +@contextlib.contextmanager +def preserve_inputs(tool, inputs): + """Preserve the tool's inputs after context""" + + comp = tool.Comp() + + values = {} + for name in inputs: + tool_input = getattr(tool, name) + value = tool_input[comp.TIME_UNDEFINED] + values[name] = value + + try: + yield + finally: + for name, value in values.items(): + tool_input = getattr(tool, name) + tool_input[comp.TIME_UNDEFINED] = value + + +@contextlib.contextmanager +def preserve_trim(loader, log=None): + """Preserve the relative trim of the Loader tool. + + This tries to preserve the loader's trim (trim in and trim out) after + the context by reapplying the "amount" it trims on the clip's length at + start and end. + + """ + + # Get original trim as amount of "trimming" from length + time = loader.Comp().TIME_UNDEFINED + length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1 + trim_from_start = loader["ClipTimeStart"][time] + trim_from_end = length - loader["ClipTimeEnd"][time] + + try: + yield + finally: + + length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1 + if trim_from_start > length: + trim_from_start = length + if log: + log.warning("Reducing trim in to %d " + "(because of less frames)" % trim_from_start) + + remainder = length - trim_from_start + if trim_from_end > remainder: + trim_from_end = remainder + if log: + log.warning("Reducing trim in to %d " + "(because of less frames)" % trim_from_end) + + loader["ClipTimeStart"][time] = trim_from_start + loader["ClipTimeEnd"][time] = length - trim_from_end + + +def loader_shift(loader, frame, relative=True): + """Shift global in time by i preserving duration + + This moves the loader by i frames preserving global duration. When relative + is False it will shift the global in to the start frame. + + Args: + loader (tool): The fusion loader tool. + frame (int): The amount of frames to move. + relative (bool): When True the shift is relative, else the shift will + change the global in to frame. + + Returns: + int: The resulting relative frame change (how much it moved) + + """ + comp = loader.Comp() + time = comp.TIME_UNDEFINED + + if not relative: + start = loader["GlobalIn"][time] + frame -= start + + # Shifting global in will try to automatically compensate for the change + # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those + # input values to "just shift" the clip + with preserve_inputs(loader, inputs=["ClipTimeStart", + "ClipTimeEnd", + "HoldFirstFrame", + "HoldLastFrame"]): + loader["GlobalIn"][time] = loader["GlobalIn"][time] + frame + + return int(frame) class FusionLoadSequence(api.Loader): @@ -36,46 +132,93 @@ class FusionLoadSequence(api.Loader): tool = comp.AddTool("Loader", *args) tool["Clip"] = path + # Set global in point to start frame (if in version.data) + start = context["version"]["data"].get("startFrame", None) + if start is not None: + loader_shift(tool, start, relative=False) + imprint_container(tool, name=name, namespace=namespace, context=context, loader=self.__class__.__name__) - def _get_first_image(self, root): - """Get first file in representation root""" - files = sorted(os.listdir(root)) - return os.path.join(root, files[0]) - def update(self, container, representation): """Update the Loader's path Fusion automatically tries to reset some variables when changing the loader's path to a new file. These automatic changes are to its inputs: - - ClipTimeStart (if duration changes) - - ClipTimeEnd (if duration changes) - - GlobalIn (if duration changes) - - GlobalEnd (if duration changes) - - Reverse (sometimes?) - - Loop (sometimes?) - - Depth (always resets to "Format") - - KeyCode (always resets to "") - - TimeCodeOffset (always resets to 0) + - ClipTimeStart: Fusion reset to 0 if duration changes + - We keep the trim in as close as possible to the previous value. + When there are less frames then the amount of trim we reduce + it accordingly. + + - ClipTimeEnd: Fusion reset to 0 if duration changes + - We keep the trim out as close as possible to the previous value + within new amount of frames after trim in (ClipTimeStart) has + been set. + + - GlobalIn: Fusion reset to comp's global in if duration changes + - We change it to the "startFrame" + + - GlobalEnd: Fusion resets to globalIn + length if duration changes + - We do the same like Fusion - allow fusion to take control. + + - HoldFirstFrame: Fusion resets this to 0 + - We preverse the value. + + - HoldLastFrame: Fusion resets this to 0 + - We preverse the value. + + - Depth: Fusion resets to "Format" + - We preverse the value. + + - KeyCode: Fusion resets to "" + - We preverse the value. + + - TimeCodeOffset: Fusion resets to 0 + - We preverse the value. """ from avalon.fusion import comp_lock_and_undo_chunk - root = api.get_representation_path(representation) - path = self._get_first_image(root) - tool = container["_tool"] assert tool.ID == "Loader", "Must be Loader" comp = tool.Comp() + root = api.get_representation_path(representation) + path = self._get_first_image(root) + + # Get start frame from version data + version = io.find_one({"type": "version", + "_id": representation["parent"]}) + start = version["data"].get("startFrame") + if start is None: + self.log.warning("Missing start frame for updated version" + "assuming starts at frame 0 for: " + "{} ({})".format(tool.Name, representation)) + start = 0 + with comp_lock_and_undo_chunk(comp, "Update Loader"): - tool["Clip"] = path + + # Update the loader's path whilst preserving some values + with preserve_trim(tool, log=self.log): + with preserve_inputs(tool, + inputs=("HoldFirstFrame", + "HoldLastFrame", + "Depth", + "KeyCode", + "TimeCodeOffset")): + tool["Clip"] = path + + # Set the global in to the start frame of the sequence + global_in_changed = loader_shift(tool, start, relative=False) + if global_in_changed: + # Log this change to the user + self.log.debug("Changed '%s' global in: %d" % (tool.Name, + start)) # Update the imprinted representation tool.SetData("avalon.representation", str(representation["_id"])) @@ -87,5 +230,11 @@ class FusionLoadSequence(api.Loader): tool = container["_tool"] assert tool.ID == "Loader", "Must be Loader" comp = tool.Comp() + with comp_lock_and_undo_chunk(comp, "Remove Loader"): tool.Delete() + + def _get_first_image(self, root): + """Get first file in representation root""" + files = sorted(os.listdir(root)) + return os.path.join(root, files[0]) From 85e75465b1228b13e3cb1ac5df9ea3dc2ff45d38 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 10:25:24 +0100 Subject: [PATCH 0629/1249] added check for locked nodes --- colorbleed/maya/lib.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 82ab58e5f4..63f0b21fa7 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -598,15 +598,20 @@ def maya_temp_folder(): # region ID -def get_id_required_nodes(referenced_nodes=False): +def get_id_required_nodes(referenced_nodes=False, nodes=None): """Filter out any node which are locked (reference) or readOnly Args: referenced_nodes (bool): set True to filter out reference nodes + nodes (list, Optional): nodes to consider Returns: nodes (set): list of filtered nodes """ + if nodes is None: + # Consider all nodes + nodes = cmds.ls() + def _node_type_exists(node_type): try: cmds.nodeType(node_type, isTypeName=True) @@ -641,7 +646,7 @@ def get_id_required_nodes(referenced_nodes=False): # We *always* ignore intermediate shapes, so we filter them out # directly - nodes = cmds.ls(type=types, long=True, noIntermediate=True) + nodes = cmds.ls(nodes, type=types, long=True, noIntermediate=True) # The items which need to pass the id to their parent # Add the collected transform to the nodes @@ -655,6 +660,14 @@ def get_id_required_nodes(referenced_nodes=False): nodes -= ignore # Remove the ignored nodes + # Avoid locked nodes + nodes_list = list(nodes) + locked = cmds.lockNode(nodes_list, query=True, lock=True) + for node, lock in zip(nodes_list, locked): + if lock: + log.warning("Skipping locked node: %s" % node) + nodes.remove(node) + return nodes From 1e003e95f11e40808518d12e61465bb32603c505 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 10:31:50 +0100 Subject: [PATCH 0630/1249] removed reference bool argument --- colorbleed/plugins/maya/publish/validate_node_ids.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index e2abfb0c79..08a187669e 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -41,8 +41,7 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): # We do want to check the referenced nodes as it might be # part of the end product - id_nodes = lib.get_id_required_nodes(referenced_nodes=True) - invalid = [n for n in instance[:] if n in id_nodes - and not lib.get_id(n)] + id_nodes = lib.get_id_required_nodes(nodes=instance[:]) + invalid = [n for n in id_nodes if not lib.get_id(n)] return invalid From 848ac357841069a67729633c5ea0629b8caf748e Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 10:50:21 +0100 Subject: [PATCH 0631/1249] reverted back to bool True --- colorbleed/plugins/maya/publish/validate_node_ids.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 08a187669e..3bd4471788 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -40,8 +40,9 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): """Return the member nodes that are invalid""" # We do want to check the referenced nodes as it might be - # part of the end product - id_nodes = lib.get_id_required_nodes(nodes=instance[:]) + # part of the end product. + id_nodes = lib.get_id_required_nodes(referenced_nodes=True, + nodes=instance[:]) invalid = [n for n in id_nodes if not lib.get_id(n)] return invalid From e475037f728c5f659b1663d0d2650d483a0ce4e5 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 15:54:24 +0100 Subject: [PATCH 0632/1249] renamed env key to FILESEQUENCE, added assertion of family in data, added convertion from json string to string, removed metadata from instance.data --- ...esequences.py => collect_filesequences.py} | 32 ++++++++++++------- ...agesequence.py => publish_filesequence.py} | 2 +- 2 files changed, 22 insertions(+), 12 deletions(-) rename colorbleed/plugins/publish/{collect_imagesequences.py => collect_filesequences.py} (84%) rename colorbleed/scripts/{publish_imagesequence.py => publish_filesequence.py} (97%) diff --git a/colorbleed/plugins/publish/collect_imagesequences.py b/colorbleed/plugins/publish/collect_filesequences.py similarity index 84% rename from colorbleed/plugins/publish/collect_imagesequences.py rename to colorbleed/plugins/publish/collect_filesequences.py index ad08a03a54..9430dbdc92 100644 --- a/colorbleed/plugins/publish/collect_imagesequences.py +++ b/colorbleed/plugins/publish/collect_filesequences.py @@ -64,10 +64,10 @@ def collect(root, return collections -class CollectImageSequences(pyblish.api.ContextPlugin): - """Gather image sequences from working directory +class CollectFileSequences(pyblish.api.ContextPlugin): + """Gather file sequences from working directory - When "IMAGESEQUENCES" environment variable is set these paths (folders or + When "FILESEQUENCE" environment variable is set these paths (folders or .json files) are parsed for image sequences. Otherwise the current working directory is searched for file sequences. @@ -87,12 +87,12 @@ class CollectImageSequences(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder targets = ["imagesequence"] - label = "Image Sequences" + label = "File Sequences" def process(self, context): - if os.environ.get("IMAGESEQUENCES"): - paths = os.environ["IMAGESEQUENCES"].split(os.pathsep) + if os.environ.get("FILESEQUENCE"): + paths = os.environ["FILESEQUENCE"].split(os.pathsep) else: cwd = context.get("workspaceDir", os.getcwd()) paths = [cwd] @@ -128,8 +128,14 @@ class CollectImageSequences(pyblish.api.ContextPlugin): self.log.info("Collecting: {}".format(root)) + regex = data.get("regex") + if regex: + # ensure regex string is proper, json adds double slashes + regex = regex.replace("\\_", "_") + self.log.info("Decoded regex to: {}".format(regex)) + collections = collect(root=root, - regex=data.get("regex"), + regex=regex, exclude_regex=data.get("exclude_regex"), startFrame=data.get("startFrame"), endFrame=data.get("endFrame")) @@ -144,6 +150,11 @@ class CollectImageSequences(pyblish.api.ContextPlugin): "found sequence") raise RuntimeError("Invalid sequence") + # Get family from the data + families = data.get("families", ["colorbleed.imagesequence"]) + assert isinstance(families, (list, tuple)), "Must be iterable" + assert families, "Must have at least a single family" + for collection in collections: instance = context.create_instance(str(collection)) self.log.info("Collection: %s" % list(collection)) @@ -161,15 +172,14 @@ class CollectImageSequences(pyblish.api.ContextPlugin): instance.data.update({ "name": str(collection), - "family": "colorbleed.imagesequence", - "families": ["colorbleed.imagesequence"], + "family": families[0], # backwards compatibility / pyblish + "families": list(families), "subset": subset, "asset": data.get("asset", api.Session["AVALON_ASSET"]), "stagingDir": root, "files": [list(collection)], "startFrame": start, - "endFrame": end, - "metadata": data.get("metadata") + "endFrame": end }) instance.append(collection) diff --git a/colorbleed/scripts/publish_imagesequence.py b/colorbleed/scripts/publish_filesequence.py similarity index 97% rename from colorbleed/scripts/publish_imagesequence.py rename to colorbleed/scripts/publish_filesequence.py index a3e2b00a42..b793f3a4c8 100644 --- a/colorbleed/scripts/publish_imagesequence.py +++ b/colorbleed/scripts/publish_filesequence.py @@ -28,7 +28,7 @@ def publish(paths, gui=False): assert any(paths), "No paths found in the list" # Set the paths to publish for the collector if any provided if paths: - os.environ["IMAGESEQUENCES"] = os.pathsep.join(paths) + os.environ["FILESEQUENCE"] = os.pathsep.join(paths) # Install Avalon with shell as current host from avalon import api, shell From c550d41f607bf8f1ce96d64e558c5bdc82cea080 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 15:55:25 +0100 Subject: [PATCH 0633/1249] write json file and store path per instance --- .../fusion/publish/extract_image_sequence.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 8996b7e1c2..1739261818 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,3 +1,7 @@ +import json +import os +import re + import pyblish.api @@ -26,3 +30,32 @@ class ExtractImageSequence(pyblish.api.Extractor): result = current_comp.Render() if not result: raise RuntimeError("Comp render failed") + + # Write metadata json file per Saver instance + instances = [i for i in context[:] if i[0].ID == "Saver"] + for instance in instances: + subset = instance.data["subset"] + ext = instance.data["ext"] + + # Regex to match resulting renders + regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset) + , ext=re.escape(ext)) + + # The instance has most of the information already stored + metadata = { + "regex": regex, + "startFrame": context.data["startFrame"], + "endFrame": context.data["endFrame"], + "families": ["colorbleed.imagesequence"], + } + + # Write metadata and store the path in the instance + output_directory = instance.data["outputDir"] + path = os.path.join(output_directory, + "{}_metadata.json".format(subset)) + + with open(path, "w") as f: + json.dump(metadata, f) + + # Store json path in instance + instance.data["jsonpath"] = path From 55567d7bc3d9577eed360c8533cb07808abfcc71 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 15:57:01 +0100 Subject: [PATCH 0634/1249] created function to fetch script, assert json file is in instance, suppress subprocess console --- .../fusion/publish/publish_image_sequences.py | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 10a2f67ce0..94f5187ffc 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -1,11 +1,27 @@ -import re import os -import json import subprocess import pyblish.api +def _get_script(): + """Get path to the image sequence script""" + + # todo: use a more elegant way to get the python script + + try: + from colorbleed.scripts import publish_filesequence + except Exception as e: + raise RuntimeError("Expected module 'publish_imagesequence'" + "to be available") + + module_path = publish_filesequence.__file__ + if module_path.endswith(".pyc"): + module_path = module_path[:-len(".pyc")] + ".py" + + return module_path + + class PublishImageSequence(pyblish.api.Extractor): """Extract result of saver by starting a comp render @@ -20,41 +36,26 @@ class PublishImageSequence(pyblish.api.Extractor): def process(self, instance): context = instance.context - subset = instance.data["subset"] - output_directory = instance.data["outputDir"] - ext = instance.data["ext"] - # Regex to match resulting renders - regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset), - ext=re.escape(ext)) + # Get metadata file, check if path is to an existing file + path = instance.data.get("jsonpath", "") + assert os.path.isfile(path), ("Stored path is not a file for %s" + % instance.data["name"]) - metadata = { - "regex": regex, - "startFrame": context.data["startFrame"], - "endFrame": context.data["endFrame"], - "asset": instance.data["asset"], - "subset": subset - } + # Get the script to execute + script = _get_script() + cmd = 'python {0} --paths "{1}"'.format(script, path) - # Write metadata - path = os.path.join(output_directory, - "{}_metadata.json".format(subset)) - - # Create subprocess command string - from colorbleed.scripts import publish_imagesequence - - module_path = publish_imagesequence.__file__ - if module_path.endswith(".pyc"): - module_path = module_path[:-len(".pyc")] + ".py" - - cmd = 'python {0} --paths "{1}"'.format(module_path, path) - with open(path, "w") as f: - json.dump(metadata, f) + # Suppress any subprocess console + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = subprocess.SW_HIDE process = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + stderr=subprocess.STDOUT, + startupinfo=startupinfo) while True: output = process.stdout.readline() @@ -70,8 +71,6 @@ class PublishImageSequence(pyblish.api.Extractor): self.log.info(line) if process.returncode != 0: - # self.log.error("Return code: {}".format(process.returncode)) - # self.log.error("Process quit with non-zero return code") raise RuntimeError("Process quit with non-zero " "return code: {}".format(process.returncode)) From 38dafe093c5f811f67aa3248ec3b11baeba91080 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 15:58:09 +0100 Subject: [PATCH 0635/1249] improved assert error message --- colorbleed/plugins/fusion/publish/collect_instances.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index b103141307..2c00f411bb 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -68,8 +68,8 @@ class CollectInstances(pyblish.api.ContextPlugin): filename = os.path.basename(path) head, padding, tail = get_frame_path(filename) - - assert tail == os.path.splitext(path)[1], "tail == extension" + ext = os.path.splitext(path)[1] + assert tail == ext, ("Tail does not match %s" % ext) subset = head.rstrip("_. ") # subset is head of the filename # Include start and end render frame in label From 2e383cce7324de834c1bb36888f78f39e3b91d01 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 15:59:37 +0100 Subject: [PATCH 0636/1249] fixed family type --- colorbleed/plugins/fusion/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index 529bd29812..b1f3fdb4ad 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -33,7 +33,7 @@ class FusionSubmitDeadline(pyblish.api.ContextPlugin): # Collect all saver instances in context that are to be rendered saver_instances = [] for instance in context[:]: - if instance.data.get("families")[0] != "colorbleed.saver": + if instance.data.get("families")[0] != "fusion.deadline": # Allow only saver family instances continue From 39a65ecc1c473e44af6503486cf058f5f8b7f244 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 16:03:33 +0100 Subject: [PATCH 0637/1249] store family in metadata and cosmetics --- .../plugins/fusion/publish/submit_publish_job.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/submit_publish_job.py b/colorbleed/plugins/fusion/publish/submit_publish_job.py index 13028b0391..2c6b5b7a45 100644 --- a/colorbleed/plugins/fusion/publish/submit_publish_job.py +++ b/colorbleed/plugins/fusion/publish/submit_publish_job.py @@ -11,12 +11,12 @@ import pyblish.api def _get_script(): """Get path to the image sequence script""" try: - from colorbleed.scripts import publish_imagesequence + from colorbleed.scripts import publish_filesequence except Exception as e: raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") - module_path = publish_imagesequence.__file__ + module_path = publish_filesequence.__file__ if module_path.endswith(".pyc"): module_path = module_path[:-len(".pyc")] + ".py" @@ -70,12 +70,12 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): raise RuntimeError("Can't continue without valid deadline " "submission prior to this plug-in.") + subset = instance.data.get("subset") state = instance.data.get("publishJobState", "Suspended") job_name = "{batch} - {subset} [publish image sequence]".format( batch=job["Props"]["Name"], - subset=instance.data["subset"] + subset=subset ) - output_dir = instance.data["outputDir"] # Add in start/end frame context = instance.context @@ -86,7 +86,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # This assumes the output files start with subset name and ends with # a file extension. regex = "^{subset}.*\d+{ext}$".format( - subset=re.escape(instance.data["subset"]), + subset=re.escape(subset), ext=re.escape(instance.data.get("ext", "\D")) ) @@ -97,6 +97,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): "regex": regex, "startFrame": start, "endFrame": end, + "families": ["colorbleed.imagesequence"], # Optional metadata (for debugging) "metadata": { @@ -105,7 +106,9 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): "session": api.Session.copy() } } - metadata_filename = "{}_metadata.json".format(instance.data["subset"]) + + output_dir = instance.data["outputDir"] + metadata_filename = "{}_metadata.json".format(subset) metadata_path = os.path.join(output_dir, metadata_filename) with open(metadata_path, "w") as f: json.dump(metadata, f, indent=4, sort_keys=True) From 048357c3702d9f8ec30444ec0ae5fe85c6e89347 Mon Sep 17 00:00:00 2001 From: aardschok Date: Thu, 15 Feb 2018 16:04:16 +0100 Subject: [PATCH 0638/1249] removed test raise --- colorbleed/plugins/publish/validate_sequence_frames.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/publish/validate_sequence_frames.py b/colorbleed/plugins/publish/validate_sequence_frames.py index a4704a1f4b..0e13064e12 100644 --- a/colorbleed/plugins/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/publish/validate_sequence_frames.py @@ -18,5 +18,3 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): missing = collection.holes().indexes assert not missing, "Missing frames: %s" % (missing,) - - raise RuntimeError("STOP") \ No newline at end of file From e34644e36dc7e2fd0875c991a206b092861436f8 Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 16 Feb 2018 17:46:16 +0100 Subject: [PATCH 0639/1249] added validator for yeti node's i/o setting --- .../publish/validate_yetirig_cache_state.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py new file mode 100644 index 0000000000..561b1bd25b --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py @@ -0,0 +1,64 @@ +import pyblish.api + +import colorbleed.action + +import maya.cmds as cmds + + +class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): + """Validate the I/O attributes of the node + + Every pgYetiMaya cache node per instance should have: + 1. Input Mode is set to `None` + 2. Input Cache File Name is empty + + """ + + order = pyblish.api.ValidatorOrder + label = "Yeti Rig Cache State" + hosts = ["maya"] + families = ["colorbleed.yetiRig"] + actions = [colorbleed.action.RepairAction, + colorbleed.action.SelectInvalidAction] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Nodes have incorrect I/O settings") + + @classmethod + def get_invalid(cls, instance): + + # As we check 2 attributes of per node it might be that both attrs + # are wrong, therefor we use a set to ensure the user only gets unique + # nodes in the reports. It also ensures we only have go over each node + # once when repairing ;) + invalid = set() + + yeti_nodes = cmds.ls(instance, type="pgYetiMaya") + for node in yeti_nodes: + # check reading state + state = cmds.getAttr("%s.fileMode" % node) + if state == 1: + cls.log.error("Node `%s` is set to mode `cache`" % node) + invalid.add(node) + + has_cache = cmds.getAttr("%s.cacheFileName" % node) + if has_cache: + cls.log.error("Node `%s` has a ") + invalid.add(node) + + invalid = list(invalid) + + return invalid + + @classmethod + def repair(self, instance): + """Repair all errors""" + + # Create set to ensure all nodes only pass once + invalid = self.get_invalid(instance) + for node in invalid: + cmds.setAttr("%s.fileMode" % node, 0) + cmds.setAttr("%s.cacheFileName" % node, "", type="string") + From b1797f59216bc24832ce7f19b63b8a48dcac223f Mon Sep 17 00:00:00 2001 From: aardschok Date: Fri, 16 Feb 2018 17:56:25 +0100 Subject: [PATCH 0640/1249] improved get_invalid function --- .../publish/validate_yetirig_cache_state.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py index 561b1bd25b..7ff80676bf 100644 --- a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py +++ b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py @@ -29,26 +29,23 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - # As we check 2 attributes of per node it might be that both attrs - # are wrong, therefor we use a set to ensure the user only gets unique - # nodes in the reports. It also ensures we only have go over each node - # once when repairing ;) - invalid = set() + invalid = [] yeti_nodes = cmds.ls(instance, type="pgYetiMaya") for node in yeti_nodes: - # check reading state + # Check reading state state = cmds.getAttr("%s.fileMode" % node) if state == 1: cls.log.error("Node `%s` is set to mode `cache`" % node) - invalid.add(node) + invalid.append(node) + continue + # Check reading state has_cache = cmds.getAttr("%s.cacheFileName" % node) if has_cache: cls.log.error("Node `%s` has a ") - invalid.add(node) - - invalid = list(invalid) + invalid.append(node) + continue return invalid From 787e736281c44bceac21b754ddef55339eb76012 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 13:01:30 +0100 Subject: [PATCH 0641/1249] changed imagesequence to filesequence for target, more generic --- colorbleed/plugins/publish/collect_filesequences.py | 2 +- colorbleed/scripts/publish_filesequence.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_filesequences.py b/colorbleed/plugins/publish/collect_filesequences.py index 9430dbdc92..a73d053bdf 100644 --- a/colorbleed/plugins/publish/collect_filesequences.py +++ b/colorbleed/plugins/publish/collect_filesequences.py @@ -86,7 +86,7 @@ class CollectFileSequences(pyblish.api.ContextPlugin): """ order = pyblish.api.CollectorOrder - targets = ["imagesequence"] + targets = ["filesequence"] label = "File Sequences" def process(self, context): diff --git a/colorbleed/scripts/publish_filesequence.py b/colorbleed/scripts/publish_filesequence.py index b793f3a4c8..c37ceee07c 100644 --- a/colorbleed/scripts/publish_filesequence.py +++ b/colorbleed/scripts/publish_filesequence.py @@ -36,7 +36,7 @@ def publish(paths, gui=False): # Register target and host import pyblish.api - pyblish.api.register_target("imagesequence") + pyblish.api.register_target("filesequence") pyblish.api.register_host("shell") # Publish items From cf1c25f087d3891343dc181ec57221accfe00bfd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 13:06:58 +0100 Subject: [PATCH 0642/1249] Preserve Loader.Reverse correctly when updating --- colorbleed/plugins/fusion/load/load_sequence.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 1f834532a0..b647ac0e0f 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -171,6 +171,9 @@ class FusionLoadSequence(api.Loader): - HoldLastFrame: Fusion resets this to 0 - We preverse the value. + - Reverse: Fusion resets to disabled if "Loop" is not enabled. + - We preserve the value. + - Depth: Fusion resets to "Format" - We preverse the value. @@ -208,6 +211,7 @@ class FusionLoadSequence(api.Loader): with preserve_inputs(tool, inputs=("HoldFirstFrame", "HoldLastFrame", + "Reverse", "Depth", "KeyCode", "TimeCodeOffset")): From 75b7d63af959a33e3890de58890a9af4620abe33 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 13:45:30 +0100 Subject: [PATCH 0643/1249] Implement Save scene and increment current file plugins for Fusion --- .../increment_current_file_deadline.py | 35 +++++++++++++++++++ .../plugins/fusion/publish/save_scene.py | 21 +++++++++++ 2 files changed, 56 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/increment_current_file_deadline.py create mode 100644 colorbleed/plugins/fusion/publish/save_scene.py diff --git a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py new file mode 100644 index 0000000000..0dba66b51e --- /dev/null +++ b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py @@ -0,0 +1,35 @@ +import pyblish.api + + +class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): + """Increment the current file. + + Saves the current file with an increased version number. + + """ + + label = "Increment current file" + order = pyblish.api.IntegratorOrder + 9.0 + hosts = ["fusion"] + families = ["colorbleed.saver"] + optional = True + + def process(self, context): + + import os + from colorbleed.lib import version_up + from colorbleed.action import get_errored_plugins_from_data + + errored_plugins = get_errored_plugins_from_data(context) + if any(plugin.__name__ == "FusionSubmitDeadline" + for plugin in errored_plugins): + raise RuntimeError("Skipping incrementing current file because " + "submission to deadline failed.") + + comp = context.data.get("currentComp") + assert comp, "Must have comp" + + current_filepath = context.data["currentFile"] + new_filepath = version_up(current_filepath) + + comp.Save(new_filepath) diff --git a/colorbleed/plugins/fusion/publish/save_scene.py b/colorbleed/plugins/fusion/publish/save_scene.py new file mode 100644 index 0000000000..cc302d684e --- /dev/null +++ b/colorbleed/plugins/fusion/publish/save_scene.py @@ -0,0 +1,21 @@ +import pyblish.api + + +class FusionSaveComp(pyblish.api.ContextPlugin): + """Save current comp""" + + label = "Save current file" + order = pyblish.api.IntegratorOrder - 0.49 + hosts = ["fusion"] + families = ["colorbleed.saver"] + + def process(self, context): + + comp = context.data.get("currentComp") + assert comp, "Must have comp" + + current = comp.GetAttrs().get("COMPS_FileName", "") + assert context.data['currentFile'] == current + + self.log.info("Saving current file..") + comp.Save() From bb13f0da185692818cd301349b6d6f222a7ffbf3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 14:59:29 +0100 Subject: [PATCH 0644/1249] Remove non-working FusionLoadAlembicCamera plug-in --- .../fusion/load/load_alembic_camera.py | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 colorbleed/plugins/fusion/load/load_alembic_camera.py diff --git a/colorbleed/plugins/fusion/load/load_alembic_camera.py b/colorbleed/plugins/fusion/load/load_alembic_camera.py deleted file mode 100644 index f2ed66fc9c..0000000000 --- a/colorbleed/plugins/fusion/load/load_alembic_camera.py +++ /dev/null @@ -1,27 +0,0 @@ -from avalon import api - - -class FusionLoadAlembicCamera(api.Loader): - """Load image sequence into Fusion""" - - families = ["colorbleed.camera"] - representations = ["ma"] - - label = "Load sequence" - order = -10 - icon = "play-circle" - color = "orange" - - def load(self, context, name, namespace, data): - """""" - - from avalon.fusion import (imprint_container, - get_current_comp, - comp_lock_and_undo_chunk) - - current_comp = get_current_comp() - with comp_lock_and_undo_chunk(current_comp): - tool = current_comp.SurfaceAlembicMesh() - tool.SetData("TOOLS_NameSet", ) - - pass From 31f8c85d1f19344dc7e1b8034e43e212849d4c2a Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 16:24:52 +0100 Subject: [PATCH 0645/1249] Fixed class name of plugin, was clashing with another plugin --- colorbleed/plugins/publish/collect_context_label.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_context_label.py b/colorbleed/plugins/publish/collect_context_label.py index 0bee510861..ec8e0f7cdc 100644 --- a/colorbleed/plugins/publish/collect_context_label.py +++ b/colorbleed/plugins/publish/collect_context_label.py @@ -2,7 +2,7 @@ import os import pyblish.api -class CollectCurrentShellFile(pyblish.api.ContextPlugin): +class CollectContextLabel(pyblish.api.ContextPlugin): """Labelize context using the registered host and current file""" order = pyblish.api.CollectorOrder + 0.25 From 1933a529ac5804066424423446bfb41a175b4608 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 16:25:21 +0100 Subject: [PATCH 0646/1249] Fix logged error message being incomplete --- colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py index 7ff80676bf..55afcf5c6f 100644 --- a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py +++ b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py @@ -43,7 +43,7 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): # Check reading state has_cache = cmds.getAttr("%s.cacheFileName" % node) if has_cache: - cls.log.error("Node `%s` has a ") + cls.log.error("Node `%s` has a cache file set" % node) invalid.append(node) continue From d8693ec4333e71376373e60c796d9037a7f974ca Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 16:25:36 +0100 Subject: [PATCH 0647/1249] Added os.makedirs, needed for metadata file --- colorbleed/plugins/publish/submit_publish_job.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py index d76867ff4f..1fc1ac0482 100644 --- a/colorbleed/plugins/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -106,7 +106,11 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): } } + # Ensure output dir exists output_dir = instance.data["outputDir"] + if not os.path.isdir(output_dir): + os.makedirs(output_dir) + metadata_filename = "{}_metadata.json".format(subset) metadata_path = os.path.join(output_dir, metadata_filename) with open(metadata_path, "w") as f: From fedf9b4e3303ec7a49c3ce90bb61d2b93a827876 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 16:25:53 +0100 Subject: [PATCH 0648/1249] Fix classmethod referring to self instead of cls --- .../plugins/maya/publish/validate_yetirig_cache_state.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py index 55afcf5c6f..2c4d4dbc72 100644 --- a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py +++ b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py @@ -50,11 +50,11 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): return invalid @classmethod - def repair(self, instance): + def repair(cls, instance): """Repair all errors""" # Create set to ensure all nodes only pass once - invalid = self.get_invalid(instance) + invalid = cls.get_invalid(instance) for node in invalid: cmds.setAttr("%s.fileMode" % node, 0) cmds.setAttr("%s.cacheFileName" % node, "", type="string") From 7f06d8d867c4d20e1f1443d47a93d4fada93a9f2 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 16:28:39 +0100 Subject: [PATCH 0649/1249] Adding start and endframe to context --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 0060aa8df9..c196415a0c 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -26,6 +26,12 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): "renderGlobals node") return + # Get start and end frame + start_frame = self.get_render_attribute("startFrame") + end_frame = self.get_render_attribute("endFrame") + context.data["startFrame"] = start_frame + context.data["endFrame"] = end_frame + # Get render layers renderlayers = [i for i in cmds.ls(type="renderLayer") if cmds.getAttr("{}.renderable".format(i)) and not From 647edea7a8be8709aa0c1a35002d8381ccbd1bc3 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 16:29:29 +0100 Subject: [PATCH 0650/1249] Added output dir and submitted job to instance.data --- colorbleed/plugins/maya/publish/submit_deadline.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index b441260431..7202aaea62 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -240,6 +240,10 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): if not response.ok: raise Exception(response.text) + # Store output dir for unified publisher (filesequence) + instance.data["outputDir"] = os.path.dirname(output_filename_0) + instance.data["deadlineSubmissionJob"] = response.json() + def preflight_check(self, instance): """Ensure the startFrame, endFrame and byFrameStep are integers""" From be8fa781958ee1cb3709eaa5511d2cac6c331325 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 17:07:03 +0100 Subject: [PATCH 0651/1249] Fixed issue with escape characters, fixes regex for clique --- colorbleed/plugins/publish/submit_publish_job.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py index 1fc1ac0482..f4576f668f 100644 --- a/colorbleed/plugins/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -84,10 +84,13 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Add in regex for sequence filename # This assumes the output files start with subset name and ends with # a file extension. - regex = "^{subset}.*\d+{ext}$".format( - subset=re.escape(subset), - ext=re.escape(instance.data.get("ext", "\D")) - ) + if "ext" in instance.data: + ext = re.escape(instance.data["ext"]) + else: + ext = "\.\D+" + + regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), + ext=ext) # Write metadata for publish job data = instance.data.copy() From abb032cbd515e3a69bb1050e93b0a7fbf8d32b01 Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 17:07:36 +0100 Subject: [PATCH 0652/1249] Removed replace function as regex formating has been fixed properly --- colorbleed/plugins/publish/collect_filesequences.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/publish/collect_filesequences.py b/colorbleed/plugins/publish/collect_filesequences.py index a73d053bdf..c572704415 100644 --- a/colorbleed/plugins/publish/collect_filesequences.py +++ b/colorbleed/plugins/publish/collect_filesequences.py @@ -127,11 +127,8 @@ class CollectFileSequences(pyblish.api.ContextPlugin): root = path self.log.info("Collecting: {}".format(root)) - regex = data.get("regex") if regex: - # ensure regex string is proper, json adds double slashes - regex = regex.replace("\\_", "_") self.log.info("Decoded regex to: {}".format(regex)) collections = collect(root=root, From afb10abe67b51ac43e780287dd142ce12ec033ea Mon Sep 17 00:00:00 2001 From: aardschok Date: Tue, 20 Feb 2018 17:29:00 +0100 Subject: [PATCH 0653/1249] Fixed family for fusion publising --- colorbleed/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py index f4576f668f..7b8c6f1852 100644 --- a/colorbleed/plugins/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -55,7 +55,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion", "maya"] - families = ["colorbleed.saver", "colorbleed.renderlayer"] + families = ["fusion.deadline", "colorbleed.renderlayer"] def process(self, instance): From 2474d2fe787656cda508495ab1f01c01caf9bf7f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 17:43:49 +0100 Subject: [PATCH 0654/1249] With now the `ext` variable defined this makes much more sense to assign `ext` to `ext` --- colorbleed/plugins/fusion/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 2c00f411bb..98425f6672 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -83,7 +83,7 @@ class CollectInstances(pyblish.api.ContextPlugin): "subset": subset, "path": path, "outputDir": os.path.dirname(path), - "ext": tail, # todo: should be redundant + "ext": ext, # todo: should be redundant "label": label, "families": [family], "family": family, From d7ff9156fb67a98a7159bcfe37343d2acb55dffb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 17:51:52 +0100 Subject: [PATCH 0655/1249] Refactor how arguments are passed to subprocess.Popen --- .../plugins/fusion/publish/publish_image_sequences.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 94f5187ffc..fe3413d9a7 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -42,16 +42,13 @@ class PublishImageSequence(pyblish.api.Extractor): assert os.path.isfile(path), ("Stored path is not a file for %s" % instance.data["name"]) - # Get the script to execute - script = _get_script() - cmd = 'python {0} --paths "{1}"'.format(script, path) - # Suppress any subprocess console startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE - process = subprocess.Popen(cmd, + process = subprocess.Popen(["python", _get_script(), + "--paths", path], bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -78,4 +75,3 @@ class PublishImageSequence(pyblish.api.Extractor): valid = bool(context) if not valid: raise RuntimeError("Unable to publish image sequences") - From 05523899f4454371cc6cf881666d3687cab0a37d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 17:52:16 +0100 Subject: [PATCH 0656/1249] Remove invalid check on context - doesn't check what is intended --- colorbleed/plugins/fusion/publish/publish_image_sequences.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index fe3413d9a7..0346146c9c 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -70,8 +70,3 @@ class PublishImageSequence(pyblish.api.Extractor): if process.returncode != 0: raise RuntimeError("Process quit with non-zero " "return code: {}".format(process.returncode)) - - # todo: ensure publish went without any issues - valid = bool(context) - if not valid: - raise RuntimeError("Unable to publish image sequences") From 06f775458f5ad4d7954d1968baa69ef8331a59fa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 17:52:46 +0100 Subject: [PATCH 0657/1249] Remove redundant import os --- .../plugins/fusion/publish/increment_current_file_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py index 0dba66b51e..91589699c5 100644 --- a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py @@ -16,7 +16,6 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): def process(self, context): - import os from colorbleed.lib import version_up from colorbleed.action import get_errored_plugins_from_data From 062c44b32f25d097be61a7970b13f1478e80dddf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 18:08:28 +0100 Subject: [PATCH 0658/1249] Move logic of publish image sequence for local render to the plug-in itself so it runs per instance and requires less logic --- .../fusion/publish/extract_image_sequence.py | 37 +------------------ .../fusion/publish/publish_image_sequences.py | 36 +++++++++++++----- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 1739261818..272daa4371 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,14 +1,11 @@ -import json -import os -import re - import pyblish.api class ExtractImageSequence(pyblish.api.Extractor): """Extract result of saver by starting a comp render - This will run the local render of Fusion, + This will run the local render of Fusion. + """ order = pyblish.api.ExtractorOrder @@ -22,7 +19,6 @@ class ExtractImageSequence(pyblish.api.Extractor): start_frame = current_comp.GetAttrs("COMPN_RenderStart") end_frame = current_comp.GetAttrs("COMPN_RenderEnd") - # Build Fusion Render Job self.log.info("Starting render") self.log.info("Start frame: {}".format(start_frame)) self.log.info("End frame: {}".format(end_frame)) @@ -30,32 +26,3 @@ class ExtractImageSequence(pyblish.api.Extractor): result = current_comp.Render() if not result: raise RuntimeError("Comp render failed") - - # Write metadata json file per Saver instance - instances = [i for i in context[:] if i[0].ID == "Saver"] - for instance in instances: - subset = instance.data["subset"] - ext = instance.data["ext"] - - # Regex to match resulting renders - regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset) - , ext=re.escape(ext)) - - # The instance has most of the information already stored - metadata = { - "regex": regex, - "startFrame": context.data["startFrame"], - "endFrame": context.data["endFrame"], - "families": ["colorbleed.imagesequence"], - } - - # Write metadata and store the path in the instance - output_directory = instance.data["outputDir"] - path = os.path.join(output_directory, - "{}_metadata.json".format(subset)) - - with open(path, "w") as f: - json.dump(metadata, f) - - # Store json path in instance - instance.data["jsonpath"] = path diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 0346146c9c..066f734f46 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -1,4 +1,6 @@ +import re import os +import json import subprocess import pyblish.api @@ -11,7 +13,7 @@ def _get_script(): try: from colorbleed.scripts import publish_filesequence - except Exception as e: + except Exception: raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") @@ -23,22 +25,38 @@ def _get_script(): class PublishImageSequence(pyblish.api.Extractor): - """Extract result of saver by starting a comp render + """Publish the generated local image sequences.""" - This will run the local render of Fusion, - """ - - order = pyblish.api.ExtractorOrder + order = pyblish.api.ExtractorOrder + 0.1 label = "Publish Rendered Image Sequence(s)" hosts = ["fusion"] targets = ["renderlocal"] + families = ["fusion.local"] def process(self, instance): - context = instance.context + subset = instance.data["subset"] + ext = instance.data["ext"] + + # Regex to match resulting renders + regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset), + ext=re.escape(ext)) + + # The instance has most of the information already stored + metadata = { + "regex": regex, + "startFrame": instance.context.data["startFrame"], + "endFrame": instance.context.data["endFrame"], + "families": ["colorbleed.imagesequence"], + } + + # Write metadata and store the path in the instance + output_directory = instance.data["outputDir"] + path = os.path.join(output_directory, + "{}_metadata.json".format(subset)) + with open(path, "w") as f: + json.dump(metadata, f) - # Get metadata file, check if path is to an existing file - path = instance.data.get("jsonpath", "") assert os.path.isfile(path), ("Stored path is not a file for %s" % instance.data["name"]) From 806c37c74216157a5a121fa6f58be3e5a694cd72 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 18:15:42 +0100 Subject: [PATCH 0659/1249] Remove wrong dot in regex The dot character in regex matches *any character*. Here it matches one of the digits because the "ext" data already includes the an explicit dot. This passes because the padding is 4 numbers, so [0-9]+ would match the first three and the `.` would match the last number. --- colorbleed/plugins/fusion/publish/publish_image_sequences.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 066f734f46..23495517f3 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -39,8 +39,8 @@ class PublishImageSequence(pyblish.api.Extractor): ext = instance.data["ext"] # Regex to match resulting renders - regex = "^{subset}.*[0-9]+.{ext}+$".format(subset=re.escape(subset), - ext=re.escape(ext)) + regex = "^{subset}.*[0-9]+{ext}+$".format(subset=re.escape(subset), + ext=re.escape(ext)) # The instance has most of the information already stored metadata = { From 01ec54651dc90c1a0db588f490ee159d84a3140e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 18:24:51 +0100 Subject: [PATCH 0660/1249] Cleanup, simplify, fix typos --- .../publish/validate_filename_has_extension.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py index a3d6b8c972..6e6c1423d2 100644 --- a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py +++ b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py @@ -6,10 +6,10 @@ import pyblish.api class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): """Ensure the Saver has an extension in the filename path - This is to counter any possible file being written as `filename` instead - of `filename.frame.ext`. - Fusion does not set an extension for your filename - when changing the file format of the saver. + This disallows files written as `filename` instead of `filename.frame.ext`. + Fusion does not always set an extension for your filename when + changing the file format of the saver. + """ order = pyblish.api.ValidatorOrder @@ -20,7 +20,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found Saver(s) without a extesions") + raise RuntimeError("Found Saver without an extension") @classmethod def get_invalid(cls, instance): @@ -29,9 +29,8 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): fname, ext = os.path.splitext(path) if not ext: - cls.log.error("%s has no extension specified" % - instance[0].Name) - # Return the tool - return [instance[0]] + tool = instance[0] + cls.log.error("%s has no extension specified" % tool.Name) + return [tool] return [] From 6c4887a64d6c50ab9d785e6a5be94f3cb83a7ff2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 18:27:05 +0100 Subject: [PATCH 0661/1249] Simplify regex logging statement --- colorbleed/plugins/publish/collect_filesequences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/collect_filesequences.py b/colorbleed/plugins/publish/collect_filesequences.py index c572704415..9c2390d3dc 100644 --- a/colorbleed/plugins/publish/collect_filesequences.py +++ b/colorbleed/plugins/publish/collect_filesequences.py @@ -129,7 +129,7 @@ class CollectFileSequences(pyblish.api.ContextPlugin): self.log.info("Collecting: {}".format(root)) regex = data.get("regex") if regex: - self.log.info("Decoded regex to: {}".format(regex)) + self.log.info("Using regex: {}".format(regex)) collections = collect(root=root, regex=regex, From 43bb754f42ef58eb0f875611037b5045d753ade3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Feb 2018 18:30:32 +0100 Subject: [PATCH 0662/1249] Error out on KeyError if subset is not in data, because it is required --- colorbleed/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py index 7b8c6f1852..dda7600f58 100644 --- a/colorbleed/plugins/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -69,7 +69,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): raise RuntimeError("Can't continue without valid deadline " "submission prior to this plug-in.") - subset = instance.data.get("subset") + subset = instance.data["subset"] state = instance.data.get("publishJobState", "Suspended") job_name = "{batch} - {subset} [publish image sequence]".format( batch=job["Props"]["Name"], From c7bee639ba032944bfdcbbba5269c656cce611d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 21 Feb 2018 10:01:41 +0100 Subject: [PATCH 0663/1249] Change icon so it's more similar to Maya implementation for loaded content --- colorbleed/plugins/fusion/load/load_sequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index b647ac0e0f..06304c685c 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -106,7 +106,7 @@ class FusionLoadSequence(api.Loader): label = "Load sequence" order = -10 - icon = "play-circle" + icon = "code-fork" color = "orange" def load(self, context, name, namespace, data): From 895d4ab5edf04e976c9676164c15b5f0ad4bfb09 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:33:12 +0100 Subject: [PATCH 0664/1249] removed setting family based on target --- .../plugins/fusion/publish/collect_instances.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/colorbleed/plugins/fusion/publish/collect_instances.py index 98425f6672..322197fc87 100644 --- a/colorbleed/plugins/fusion/publish/collect_instances.py +++ b/colorbleed/plugins/fusion/publish/collect_instances.py @@ -36,15 +36,6 @@ class CollectInstances(pyblish.api.ContextPlugin): from avalon.fusion.lib import get_frame_path - # Change family when submitting to Deadline - targets = pyblish.api.registered_targets() - if "deadline" in targets: - # Submit to Deadline - family = "fusion.deadline" - else: - # Render local - family = "fusion.local" - comp = context.data["currentComp"] # Get all savers in the comp @@ -85,8 +76,8 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, - "families": [family], - "family": family, + "families": ["colorbleed.saver"], + "family": "colorbleed.saver", "active": active, "publish": active # backwards compatibility }) From 5e4ed0a9e4b140fe5e9bb852a560439bfc74f1b1 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:34:07 +0100 Subject: [PATCH 0665/1249] added collector for rendermode --- .../fusion/publish/collect_render_target.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/collect_render_target.py diff --git a/colorbleed/plugins/fusion/publish/collect_render_target.py b/colorbleed/plugins/fusion/publish/collect_render_target.py new file mode 100644 index 0000000000..f80fb3ae75 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/collect_render_target.py @@ -0,0 +1,47 @@ +import pyblish.api + + +class CollectFusionRenderMode(pyblish.api.InstancePlugin): + """Collect current comp's render Mode + + Options: + renderlocal + deadline + + Note that this value is set for each comp separately. When you save the + comp this information will be stored in that file. If for some reason the + available tool does not visualize which render mode is set for the + current comp, please run the following line in the console (Py2) + + comp.GetData("colorbleed.rendermode") + + This will return the name of the current render mode as seen above under + Options. + + """ + + order = pyblish.api.CollectorOrder + 0.4 + label = "Collect Render Mode" + hosts = ["fusion"] + families = ["colorbleed.saver"] + + def process(self, instance): + """Collect all image sequence tools""" + options = ["renderlocal", "deadline"] + + comp = instance.context.data.get("currentComp") + if not comp: + raise RuntimeError("No comp previously collected, unable to " + "retrieve Fusion version.") + + rendermode = comp.GetData("colorbleed.rendermode") or "renderlocal" + assert rendermode in options, "Must be supported render mode" + + self.log.info("Render mode: {0}".format(rendermode)) + + # Append family + family = "colorbleed.saver.{0}".format(rendermode) + instance.data["families"].append(family) + + self.log.info("Instance families: {0}".format(instance.data["families"])) + From dcf1de53dc8a5a0fc71e1a7adcdf8324d3d27277 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:36:04 +0100 Subject: [PATCH 0666/1249] Changed targets for family, changed to instance plugin, added safety to run only onces --- .../fusion/publish/extract_image_sequence.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 272daa4371..55d20cf356 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,7 +1,7 @@ import pyblish.api -class ExtractImageSequence(pyblish.api.Extractor): +class ExtractImageSequence(pyblish.api.InstancePlugin): """Extract result of saver by starting a comp render This will run the local render of Fusion. @@ -11,9 +11,18 @@ class ExtractImageSequence(pyblish.api.Extractor): order = pyblish.api.ExtractorOrder label = "Render Local" hosts = ["fusion"] - targets = ["renderlocal"] + families = ["colorbleed.saver.renderlocal"] - def process(self, context): + 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 current_comp = context.data["currentComp"] start_frame = current_comp.GetAttrs("COMPN_RenderStart") From b3db146a2c260410f5992fee23f2aa59768f28a4 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:36:41 +0100 Subject: [PATCH 0667/1249] Changed family, plugin only for deadline --- .../plugins/fusion/publish/increment_current_file_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py index 91589699c5..9d766c426c 100644 --- a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py @@ -11,7 +11,7 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["colorbleed.saver.deadline"] optional = True def process(self, context): From 2faeb6328fe8190f038172ddcd8f021f511e547c Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:38:05 +0100 Subject: [PATCH 0668/1249] Changed plugin to Integrator, removed targets, updated families --- .../plugins/fusion/publish/publish_image_sequences.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 23495517f3..cd0835b3ce 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -24,14 +24,13 @@ def _get_script(): return module_path -class PublishImageSequence(pyblish.api.Extractor): +class PublishImageSequence(pyblish.api.Integrator): """Publish the generated local image sequences.""" - order = pyblish.api.ExtractorOrder + 0.1 + order = pyblish.api.IntegratorOrder label = "Publish Rendered Image Sequence(s)" hosts = ["fusion"] - targets = ["renderlocal"] - families = ["fusion.local"] + families = ["colorbleed.saver.renderlocal"] def process(self, instance): From c3f7caba82bd1562cfe8be5dcec6261bf411369c Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:39:56 +0100 Subject: [PATCH 0669/1249] Changed to Instance plugin, removed targets, changed families, added safety to only run once --- .../plugins/fusion/publish/submit_deadline.py | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index b1f3fdb4ad..5ac65870c4 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -8,7 +8,7 @@ from avalon.vendor import requests import pyblish.api -class FusionSubmitDeadline(pyblish.api.ContextPlugin): +class FusionSubmitDeadline(pyblish.api.InstancePlugin): """Submit current Comp to Deadline Renders are submitted to a Deadline Web Service as @@ -19,10 +19,17 @@ class FusionSubmitDeadline(pyblish.api.ContextPlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["fusion.deadline"] - targets = ["deadline"] + families = ["colorbleed.saver.deadline"] - def process(self, context): + def process(self, instance): + + context = instance.context + + key = "__hasRun{}".format(self.__class__.__name__) + if context.data.get(key, False): + return + else: + context.data[key] = True from avalon.fusion.lib import get_frame_path @@ -33,16 +40,20 @@ class FusionSubmitDeadline(pyblish.api.ContextPlugin): # Collect all saver instances in context that are to be rendered saver_instances = [] for instance in context[:]: - if instance.data.get("families")[0] != "fusion.deadline": + if not self.families[0] in instance.data.get("families"): # Allow only saver family instances continue if not instance.data.get("publish", True): # Skip inactive instances continue - + self.log.debug(instance.data["name"]) saver_instances.append(instance) + if not saver_instances: + self.log.warning(saver_instances) + raise RuntimeError("No instances found for Deadline submittion") + fusion_version = int(context.data["fusionVersion"]) filepath = context.data["currentFile"] filename = os.path.basename(filepath) From d84b1795c7a036f966ac9de9d327fd23eb8bbebc Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:41:30 +0100 Subject: [PATCH 0670/1249] Updated order to ExtractorOrder --- colorbleed/plugins/fusion/publish/save_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/save_scene.py b/colorbleed/plugins/fusion/publish/save_scene.py index cc302d684e..e88b8ba769 100644 --- a/colorbleed/plugins/fusion/publish/save_scene.py +++ b/colorbleed/plugins/fusion/publish/save_scene.py @@ -5,7 +5,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): """Save current comp""" label = "Save current file" - order = pyblish.api.IntegratorOrder - 0.49 + order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] families = ["colorbleed.saver"] From f6bb148afba8adb74704a3d9993bf60e38b27926 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:41:54 +0100 Subject: [PATCH 0671/1249] Updated families --- colorbleed/plugins/fusion/publish/validate_background_depth.py | 2 +- colorbleed/plugins/fusion/publish/validate_comp_saved.py | 2 +- .../plugins/fusion/publish/validate_create_folder_checked.py | 2 +- .../plugins/fusion/publish/validate_filename_has_extension.py | 2 +- colorbleed/plugins/fusion/publish/validate_saver_passthrough.py | 2 +- colorbleed/plugins/fusion/publish/validate_unique_subsets.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/colorbleed/plugins/fusion/publish/validate_background_depth.py index 6173fd836a..abf5dd248e 100644 --- a/colorbleed/plugins/fusion/publish/validate_background_depth.py +++ b/colorbleed/plugins/fusion/publish/validate_background_depth.py @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] optional = True @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_comp_saved.py b/colorbleed/plugins/fusion/publish/validate_comp_saved.py index 741171ba10..18621ceb4f 100644 --- a/colorbleed/plugins/fusion/publish/validate_comp_saved.py +++ b/colorbleed/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] hosts = ["fusion"] def process(self, context): diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py index 3aa2552651..7a10b1e538 100644 --- a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py +++ b/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py index 6e6c1423d2..b7fe1f4c31 100644 --- a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py +++ b/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py @@ -14,7 +14,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] hosts = ["fusion"] def process(self, instance): diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py index c2f910f642..4380f4e380 100644 --- a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py +++ b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Active" - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] hosts = ["fusion"] @classmethod diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py index a60304efcf..527b4acc69 100644 --- a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py +++ b/colorbleed/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["fusion.deadline", "fusion.local"] + families = ["colorbleed.saver"] hosts = ["fusion"] @classmethod From 5b2126f340d35fc7a4971a3b7376a453b70189ac Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:43:44 +0100 Subject: [PATCH 0672/1249] Updated families --- colorbleed/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/publish/submit_publish_job.py index dda7600f58..c13f7e3dce 100644 --- a/colorbleed/plugins/publish/submit_publish_job.py +++ b/colorbleed/plugins/publish/submit_publish_job.py @@ -55,7 +55,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion", "maya"] - families = ["fusion.deadline", "colorbleed.renderlayer"] + families = ["colorbleed.saver.deadline", "colorbleed.renderlayer"] def process(self, instance): From e8941f059cdda92a6a4f0121adefd65e13b93fd9 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:51:33 +0100 Subject: [PATCH 0673/1249] Removed unneeded log message --- colorbleed/plugins/fusion/publish/collect_render_target.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/collect_render_target.py b/colorbleed/plugins/fusion/publish/collect_render_target.py index f80fb3ae75..a9193eaee5 100644 --- a/colorbleed/plugins/fusion/publish/collect_render_target.py +++ b/colorbleed/plugins/fusion/publish/collect_render_target.py @@ -42,6 +42,3 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): # Append family family = "colorbleed.saver.{0}".format(rendermode) instance.data["families"].append(family) - - self.log.info("Instance families: {0}".format(instance.data["families"])) - From b2c18cc424adfe074ec3300b17b425f8ff8bc650 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 14:52:18 +0100 Subject: [PATCH 0674/1249] Locking comp when calling render to counter pop up window --- colorbleed/plugins/fusion/publish/extract_image_sequence.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/extract_image_sequence.py index 55d20cf356..bdb2d8fbf2 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/extract_image_sequence.py @@ -1,5 +1,7 @@ import pyblish.api +import avalon.fusion as fusion + class ExtractImageSequence(pyblish.api.InstancePlugin): """Extract result of saver by starting a comp render @@ -32,6 +34,8 @@ class ExtractImageSequence(pyblish.api.InstancePlugin): self.log.info("Start frame: {}".format(start_frame)) self.log.info("End frame: {}".format(end_frame)) - result = current_comp.Render() + with fusion.comp_lock_and_undo_chunk(current_comp): + result = current_comp.Render() + if not result: raise RuntimeError("Comp render failed") From e3ed841103ddd102cff8bf72f6a255363f4fc02d Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 17:32:45 +0100 Subject: [PATCH 0675/1249] Introduce rendermode switch tool --- .../scripts/Comp/colorbleed/set_rendermode.py | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 setup/fusion/scripts/Comp/colorbleed/set_rendermode.py diff --git a/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py new file mode 100644 index 0000000000..1ab523ce3e --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py @@ -0,0 +1,138 @@ +import os + +from avalon.vendor.Qt import QtCore, QtWidgets +from avalon.vendor import qtawesome +import avalon.fusion as avalon + + +_help = {"renderlocal": "Render the comp on your own machine and publish " + "it from that the destination folder", + "deadline": "Submit a Fusion render job to Deadline to use all other " + "computers and add a publish job"} + + +class SetRenderMode(QtWidgets.QWidget): + + def __init__(self, parent=None): + QtWidgets.QWidget.__init__(self, parent) + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + + self.setWindowTitle("Set Render Mode - {}".format(self._comp_name)) + self.resize(300, 150) + self.setFixedSize(300, 150) + + layout = QtWidgets.QVBoxLayout() + + # region comp info + comp_info_layout = QtWidgets.QHBoxLayout() + + update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh"), "") + update_btn.setFixedWidth(25) + update_btn.setFixedHeight(25) + + comp_information = QtWidgets.QLineEdit() + comp_information.setEnabled(False) + + comp_info_layout.addWidget(comp_information) + comp_info_layout.addWidget(update_btn) + # endregion comp info + + # region modes + mode_options = QtWidgets.QComboBox() + mode_options.addItems(_help.keys()) + + mode_information = QtWidgets.QTextEdit() + mode_information.setEnabled(False) + # endregion modes + + accept_layout = QtWidgets.QHBoxLayout() + accept_btn = QtWidgets.QPushButton("Accept") + validation_state = QtWidgets.QPushButton() + validation_state.setFixedHeight(15) + validation_state.setFixedWidth(15) + validation_state.setEnabled(False) + validation_state.setStyleSheet("background-color: green") + + accept_layout.addWidget(accept_btn) + accept_layout.addWidget(validation_state) + + layout.addLayout(comp_info_layout) + layout.addWidget(mode_options) + layout.addWidget(mode_information) + layout.addLayout(accept_layout) + + self.setLayout(layout) + + self.comp_information = comp_information + self.update_btn = update_btn + + self.mode_options = mode_options + self.mode_information = mode_information + + self.accept_btn = accept_btn + self.validation = validation_state + + self.connections() + self.update() + + def connections(self): + """Build connections between code and buttons""" + + self.update_btn.clicked.connect(self.update) + self.accept_btn.clicked.connect(self._set_comp_rendermode) + self.mode_options.currentIndexChanged.connect( + self._update_rendermode_info) + + def update(self): + """Update all information in the UI""" + + self._comp = avalon.get_current_comp() + self._comp_name = self._get_comp_name() + + self.setWindowTitle("Set Render Mode") + self.comp_information.setText(self._comp_name) + + self._update_rendermode_info() + + def _update_rendermode_info(self): + + rendermode = self._get_comp_rendermode() + if rendermode is None: + rendermode = "renderlocal" + + self.mode_information.setText(_help[rendermode]) + + def _get_comp_name(self): + return os.path.basename(self._comp.GetAttrs("COMPS_FileName")) + + def _get_comp_rendermode(self): + return self._comp.GetData("colorbleed.rendermode") + + def _set_comp_rendermode(self): + rendermode = self.mode_options.currentText() + self._comp.SetData("colorbleed.rendermode", rendermode) + + # Validate the rendermode has been updated correctly + if not self._validation(): + self.validation.setStyleSheet("background-color: red") + raise AssertionError("Rendermode in UI is not render mode in comp: " + "%s" % self._comp_name) + + print("Updated render mode for %s to %s" % (self._comp_name, rendermode)) + + def _validation(self): + ui_mode = self.mode_options.currentText() + comp_mode = self._get_comp_rendermode() + + return comp_mode == ui_mode + + +if __name__ == '__main__': + + import sys + app = QtWidgets.QApplication(sys.argv) + w = SetRenderMode() + w.show() + sys.exit(app.exec_()) From a6430bf5bd5121d4f49f06b2055153071f2f92b0 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 17:33:35 +0100 Subject: [PATCH 0676/1249] Moving main comp scripts to colorbleed config --- .../32bit/backgrounds_selected_to32bit.py | 28 +++++ .../colorbleed/32bit/backgrounds_to32bit.py | 28 +++++ .../32bit/loaders_selected_to32bit.py | 28 +++++ .../Comp/colorbleed/32bit/loaders_to32bit.py | 28 +++++ .../Comp/colorbleed/SE_LoaderTweaker.lua | 107 ++++++++++++++++++ .../duplicate_with_input_connections.py | 52 +++++++++ .../predivide_postmultiply_BC_and_CC.py | 25 ++++ .../Comp/colorbleed/search_replace_paths.py | 107 ++++++++++++++++++ .../Comp/colorbleed/set_relative_paths.py | 96 ++++++++++++++++ .../update_selected_loader_ranges.py | 37 ++++++ 10 files changed, 536 insertions(+) create mode 100644 setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua create mode 100644 setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py create mode 100644 setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py new file mode 100644 index 0000000000..16fc2a61af --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py @@ -0,0 +1,28 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + + +def f1(): + with FusionLockComp('Selected Backgrounds to 32bit'): + toolsDict = comp.GetToolList(True) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID == "Background": + tool.Depth = 5 + +f1() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py new file mode 100644 index 0000000000..60d558a8cb --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py @@ -0,0 +1,28 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + + +def f1(): + with FusionLockComp('Backgrounds to 32bit'): + toolsDict = comp.GetToolList(False) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID == "Background": + tool.Depth = 5 + +f1() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py new file mode 100644 index 0000000000..74c5ca00f7 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py @@ -0,0 +1,28 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + + +def f1(): + with FusionLockComp('Selected Loaders to 32bit'): + toolsDict = comp.GetToolList(True) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID == "Loader": + tool.Depth = 5 + +f1() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py new file mode 100644 index 0000000000..edfeffaa75 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py @@ -0,0 +1,28 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + + +def f1(): + with FusionLockComp('Loaders to 32bit'): + toolsDict = comp.GetToolList(False) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID == "Loader": + tool.Depth = 5 + +f1() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua b/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua new file mode 100644 index 0000000000..0d54536d7d --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua @@ -0,0 +1,107 @@ +------------------------------------------------------------ +-- Change various options in Loadertools, Revision: 2.1 +-- +-- +-- place in Fusion:\Scripts\Comp +-- +-- written by Isaac Guenard (izyk@eyeonline.com) / Sean Konrad +-- created : January 24rd, 2005 +-- modified by Eric Westphal (Eric@SirEdric.de), February 2007 +------------------------------------------------------------ + + +MissingFramesOpt = {"Do Not Change", "Fail", "Hold Previous", "Output Black", "Wait"} +DepthOpt={"Do Not Change", "Format", "Default", "int8", "int16", "float16", "float32"} +GeneralOpt={"Do Not Change", "Off", "On"} +HoldOpt={"Do Not Change", "Set to..."} +PixelOpt={"Do Not Change", "From File", "Default", "Custom (set below)"} + +ret = comp.AskUser("SirEdric's Tweak-All-Loaders", { + {"AlphaSolid", "Dropdown", Options = GeneralOpt}, + {"PostMultiply", "Dropdown", Options = GeneralOpt}, + {"InvertAlpha", "Dropdown", Options = GeneralOpt}, + {"MissingFrames", "Dropdown", Options = MissingFramesOpt}, + {"Depth", "Dropdown", Options = DepthOpt}, + {"PixelAspect", "Dropdown", Options = PixelOpt}, + {"CustomPixelAspect", "Position", Default={1,1} }, + {"HoldFirstOpt", Name="Hold First Frame", "Dropdown", Options = HoldOpt}, + {"HoldFirst", Name = "Hold first Frame for", "Screw", Default = 0, Min = 0, Max=500, Integer = true}, + {"HoldLastOpt", Name="Hold Last Frame", "Dropdown", Options = HoldOpt}, + {"HoldLast", Name = "Hold first Frame for", "Screw", Default = 0, Min = 0, Max=500, Integer = true}, + {"Selected", Name = "Affect Selected Tools Only", "Checkbox", Default = 0} + }) + +if ret then + composition:StartUndo("SE_LoaderTweaker") + MyPixX=ret.CustomPixelAspect[1] + MyPixY=ret.CustomPixelAspect[2] + print(MyPixX.."bb".. MyPixY) + print() + print("SE_LoaderTweaker is about to change...") + print("...AlphaSolid to ["..GeneralOpt[ret.AlphaSolid+1].."]") + print("...PostMultiply to ["..GeneralOpt[ret.PostMultiply+1].."]") + print("...InvertAlpha to ["..GeneralOpt[ret.InvertAlpha+1].."]") + print("...Missing Frames to [".. MissingFramesOpt[ret.MissingFrames + 1].."]") + print("...Depth to ["..DepthOpt[ret.Depth+1].."]") + print("...PixelAspect to ["..PixelOpt[ret.PixelAspect+1].."]") + print("...CustomPixelAspect(if selected): X="..MyPixX.." Y="..MyPixY) + print("...Hold First Frame to ["..HoldOpt[ret.HoldFirstOpt+1]..": " .. ret.HoldFirst.."]") + print("...Hold Last Frame to ["..HoldOpt[ret.HoldLastOpt+1]..": " .. ret.HoldLast.."]") + if ret.Selected then + print("...... *** on selected tools only! ***") + end + print("---------------------------------------------------") + print() + + -- ((ret.Selected ==1)) will return true if the + -- selected checkbox is enabled..... + + for i, v in composition:GetToolList((ret.Selected == 1)) do + id = v:GetAttrs().TOOLS_RegID + MyName = v:GetAttrs().TOOLS_Name + if id == "Loader" then + print("Changing "..MyName.." Options:") + if ret.AlphaSolid > 0 then -- check for 'DoNothing' + print("MakeAlphaSolid set to: "..(ret.AlphaSolid-1)) + v.MakeAlphaSolid = (ret.AlphaSolid-1) + end + if ret.PostMultiply > 0 then -- check for 'DoNothing' + print("PostMultiplyByAlpha set to: "..(ret.PostMultiply-1)) + v.PostMultiplyByAlpha = (ret.PostMultiply-1) + end + if ret.InvertAlpha > 0 then -- check for 'DoNothing' + print("InvertAlpha set to: "..(ret.InvertAlpha-1)) + v.InvertAlpha = (ret.InvertAlpha-1) + end + if ret.MissingFrames >0 then -- check for 'DoNothing' + print("MissingFrames set to: "..(ret.MissingFrames-1)) + v.MissingFrames = (ret.MissingFrames-1) + end + if ret.Depth >0 then -- check for 'DoNothing' + print("Depth set to: "..(ret.Depth-1)) + v.Depth = (ret.Depth-1) + end + if ret.PixelAspect >0 then -- check for 'DoNothing' + print("PixelAspect set to: "..(ret.PixelAspect-1)) + v.PixelAspect = (ret.PixelAspect-1) + if ret.PixelAspect == 3 then + v.CustomPixelAspect={MyPixX, MyPixY} + end + end + if ret.HoldFirstOpt >0 then -- check for 'DoNothing' + print("HoldFirstFrame set to: "..(ret.HoldFirst)) + v.HoldFirstFrame = (ret.HoldFirst) + end + + if ret.HoldLastOpt >0 then -- check for 'DoNothing' + print("HoldLastFrame set to: "..(ret.HoldLast)) + v.HoldLastFrame = (ret.HoldLast) + end + + print(v:GetAttrs().TOOLS_Name) + end + end +composition:EndUndo(true) +end + +print() diff --git a/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py b/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py new file mode 100644 index 0000000000..1e53e213c1 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py @@ -0,0 +1,52 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + +def pairs(n): + it = iter(n) + return zip(it, it) + + +def duplicateWithInputConnections(): + + with FusionLockComp("Duplicate With Input Connections"): + originalTools = comp.GetToolList(True) + if not originalTools: + return # nothing selected + + comp.Copy() + comp.SetActiveTool() + comp.Paste() + + duplicateTools = comp.GetToolList(True) + + for i, tool in originalTools.iteritems(): + dupToolInputs = duplicateTools[i].GetInputList() + + for j, input in tool.GetInputList().iteritems(): + if input.GetAttrs()['INPB_Connected']: + if j in dupToolInputs: + if dupToolInputs[j].GetAttrs()['INPB_Connected']: + print (" Both connected. ") + else: + dupToolInputs[j].ConnectTo(input.GetConnectedOutput()) + if dupToolInputs[j].GetAttrs()['INPB_Connected']: + print (" Connection Successful ") + + + +duplicateWithInputConnections() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py b/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py new file mode 100644 index 0000000000..9834625109 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py @@ -0,0 +1,25 @@ + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + +idList = set(["BrightnessContrast", "ColorCorrector"]) +attrName = "PreDividePostMultiply" + +with FusionLockComp("BC & CC set PreMultiplyPostDivide to 1"): + toolsDict = comp.GetToolList(False) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID in idList: + for input in tool.GetInputList().values(): + setattr(tool, attrName, 1.0) \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py b/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py new file mode 100644 index 0000000000..7fdfb6e388 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py @@ -0,0 +1,107 @@ +import os +import re + + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + +def replaceStr(inputString, srchFor, srchTo, caseSensitive=True): + if caseSensitive: + return inputString.replace(srchFor, srchTo) + else: + regex = re.compile(re.escape(srchFor), re.IGNORECASE) + return regex.sub(srchTo, inputString) + + +def searchReplaceLoaderSavers(): + userResponse = comp.AskUser("Repath All Loaders", + {1:{1:"Loaders", 2:"Checkbox", "Name":"Loaders", "NumAcross":3, "Default":1}, + 2:{1:"Savers", 2:"Checkbox", "Name":"Savers", "NumAcross":3, "Default":1}, + 3:{1:"Proxy", 2:"Checkbox", "Name":"Proxy", "NumAcross":3, "Default":1}, + 4:{1:"Source", 2:"Text", "Name":"Enter pattern to search for"}, + 5:{1:"Replacement", 2:"Text", "Name":"Enter the replacement path"}, + 6:{1:"Valid", 2:"Checkbox", "Name":"Check If New Path is Valid", "Default":1}, + 7:{1:"CaseSensitive", 2:"Checkbox", "Name":"Case Sensitive", "Default":1}, + 8:{1:"SelectedOnly", 2:"Checkbox", "Name":"Selected Only", "Default":0}, + 9:{1:"PreserveGlobalIn", 2:"Checkbox", "Name":"Preserve Global In Point", "Default":1} + } + ) + + if userResponse: + srchFor = userResponse['Source'] + if not srchFor: + raise RuntimeError("No source string specified.") + + srchTo = userResponse['Replacement'] + if not srchTo: + raise RuntimeError("No replacement string specified.") + + doLoaders = userResponse['Loaders'] + doSavers = userResponse['Savers'] + doProxy = userResponse['Proxy'] + doValidate = userResponse['Valid'] + doCaseSensitive = userResponse['CaseSensitive'] + doSelectedOnly = bool(userResponse['SelectedOnly']) + doPreserveGlobalIn = bool(userResponse['PreserveGlobalIn']) + + + with FusionLockComp('Path Remap - "{0}" to "{1}"'.format(srchFor, srchTo)): + toolsDict = comp.GetToolList(doSelectedOnly) + for i, tool in toolsDict.items(): + toolId = tool.ID + if toolId == "Loader" or toolId == "Saver": + tool_a = tool.GetAttrs() + if (doLoaders or doProxy) and toolId == "Loader": + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + + if doPreserveGlobalIn: + oldGlobalIn = tool.GlobalIn[comp.CurrentTime] + + if doLoaders: + for n, name in clipTable.items(): + #for i in table.getn(clipTable): + if name: + newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) + print (name, newPath) + if not doValidate or os.path.exists(comp.MapPath(newPath)): + tool.Clip[startTime[n]] = newPath + else: + print( "FAILED : New clip does not exist; skipping sequence.\n {0} .. {1}".format(name, newPath)) + + if doProxy: + for n, name in altclipTable.items(): + if name: + newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) + if not doValidate or os.path.exists(comp.MapPath(newPath)): + tool.ProxyFilename[startTime[n]] = newPath + else: + print( "FAILED : New proxy clip does not exist; skipping sequence.\n {0} .. {1}".format(name, newPath)) + + if doPreserveGlobalIn: + tool.GlobalIn[comp.CurrentTime] = oldGlobalIn + + if doSavers and toolId == "Saver": + for i, name in tool_a['TOOLST_Clip_Name'].items(): + newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) + if not doValidate or os.path.exists(os.path.dirname(comp.MapPath(newPath))): + tool.Clip[comp. ] = newPath + else: + print( "FAILED : Output directory does not exist; skipping saver.\n {0} .. {1}".format(name, newPath)) + +searchReplaceLoaderSavers() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py b/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py new file mode 100644 index 0000000000..d3a011162b --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py @@ -0,0 +1,96 @@ +import os +import re + + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + +def makeRelativePath(root, path): + try: + return "Comp:\{0}".format(os.path.relpath(os.path.abspath(fusion.MapPath(path)), root)) + except ValueError: + print("Warning -- Can't define relative path for: {0}".format(path)) + return path + +def pathToCurrentComp(): + return comp.GetAttrs()["COMPS_FileName"] + + +def cbRelativePaths(): + userResponse = comp.AskUser("Make paths relative", + { + 1:{1:"Loaders", 2:"Checkbox", "Name":"Loaders", "NumAcross":3, "Default":1}, + 2:{1:"Savers", 2:"Checkbox", "Name":"Savers", "NumAcross":3, "Default":1}, + 3:{1:"Proxy", 2:"Checkbox", "Name":"Proxy", "NumAcross":3, "Default":1}, + 4:{1:"SelectedOnly", 2:"Checkbox", "Name":"Selected Only", "Default":0} + } + ) + + if userResponse: + + root = pathToCurrentComp() + if not root: + raise RuntimeError("Fusion file has not been saved. Can't make paths relative") + if root: + root = os.path.dirname(root) + + # set root + os.chdir(root) + + doLoaders = userResponse['Loaders'] + doSavers = userResponse['Savers'] + doProxy = userResponse['Proxy'] + doSelectedOnly = bool(userResponse['SelectedOnly']) + + + + with FusionLockComp('Make paths relative'): + toolsDict = comp.GetToolList(doSelectedOnly) + for i, tool in toolsDict.items(): + toolId = tool.ID + if toolId == "Loader" or toolId == "Saver": + tool_a = tool.GetAttrs() + if (doLoaders or doProxy) and toolId == "Loader": + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + + # Preserve global in + oldGlobalIn = tool.GlobalIn[comp.CurrentTime] + + if doLoaders: + for n, name in clipTable.items(): + if name: + newPath = makeRelativePath(root, name) + tool.Clip[startTime[n]] = newPath + + if doProxy: + for n, name in altclipTable.items(): + if name: + newPath = makeRelativePath(root, name) + tool.ProxyFilename[startTime[n]] = newPath + + # Set global in (to what we preserved) + tool.GlobalIn[comp.CurrentTime] = oldGlobalIn + + if doSavers and toolId == "Saver": + for i, name in tool_a['TOOLST_Clip_Name'].items(): + if name: + newPath = makeRelativePath(root, name) + tool.Clip[comp.TIME_UNDEFINED] = newPath + +cbRelativePaths() + + \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py b/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py new file mode 100644 index 0000000000..7d0a4b25a8 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py @@ -0,0 +1,37 @@ + + +class FusionLockComp(object): + def __init__(self, undoQueueName="Script CMD"): + # Lock flow + comp.Lock() + # Start undo event + comp.StartUndo(undoQueueName) + + def __enter__(self): + return None + + def __exit__(self, type, value, traceback): + comp.EndUndo(True) + comp.Unlock() + + +with FusionLockComp("Reload clip time ranges"): + toolsDict = comp.GetToolList(True) + if toolsDict: + for i, tool in toolsDict.items(): + if tool.ID != "Loader": + continue + + tool_a = tool.GetAttrs() + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + oldGlobalIn = tool.GlobalIn[comp.CurrentTime] + + for n, c in clipTable.items(): + tool.Clip[startTime[n]] = tool.Clip[startTime[n]] + + for n, c in altclipTable.items(): + tool.ProxyFilename[startTime[n]] = tool.ProxyFilename[startTime[n]] + + tool.GlobalIn[comp.CurrentTime] = oldGlobalIn From 6b3e040e65400097f3f0b2ac0ed1fa4d52420237 Mon Sep 17 00:00:00 2001 From: aardschok Date: Wed, 21 Feb 2018 17:53:08 +0100 Subject: [PATCH 0677/1249] Removed redundant log message --- colorbleed/plugins/fusion/publish/submit_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index 5ac65870c4..5afb484e50 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -51,7 +51,6 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): saver_instances.append(instance) if not saver_instances: - self.log.warning(saver_instances) raise RuntimeError("No instances found for Deadline submittion") fusion_version = int(context.data["fusionVersion"]) From cb89ef29cfe05d31c3b2a6e75c1fbc049de6ab6f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 10:49:13 +0100 Subject: [PATCH 0678/1249] Simplify and cleanuo code in 32 bit utility functions in Fusion Comp scripts --- .../32bit/backgrounds_selected_to32bit.py | 32 +++++-------------- .../colorbleed/32bit/backgrounds_to32bit.py | 32 +++++-------------- .../32bit/loaders_selected_to32bit.py | 32 +++++-------------- .../Comp/colorbleed/32bit/loaders_to32bit.py | 32 +++++-------------- 4 files changed, 32 insertions(+), 96 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py index 16fc2a61af..c0dcef5410 100644 --- a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_selected_to32bit.py @@ -1,28 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - def __enter__(self): - return None +def main(): + """Set all selected backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'): + tools = comp.GetToolList(True, "Background").values() + for tool in tools: + tool.Depth = 5 - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def f1(): - with FusionLockComp('Selected Backgrounds to 32bit'): - toolsDict = comp.GetToolList(True) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID == "Background": - tool.Depth = 5 - -f1() - - \ No newline at end of file +main() diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py index 60d558a8cb..92ca18a82d 100644 --- a/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/backgrounds_to32bit.py @@ -1,28 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - def __enter__(self): - return None +def main(): + """Set all backgrounds to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'): + tools = comp.GetToolList(False, "Background").values() + for tool in tools: + tool.Depth = 5 - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def f1(): - with FusionLockComp('Backgrounds to 32bit'): - toolsDict = comp.GetToolList(False) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID == "Background": - tool.Depth = 5 - -f1() - - \ No newline at end of file +main() diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py index 74c5ca00f7..6e3802d9ff 100644 --- a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_selected_to32bit.py @@ -1,28 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - def __enter__(self): - return None +def main(): + """Set all selected loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: + tool.Depth = 5 - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def f1(): - with FusionLockComp('Selected Loaders to 32bit'): - toolsDict = comp.GetToolList(True) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID == "Loader": - tool.Depth = 5 - -f1() - - \ No newline at end of file +main() diff --git a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py index edfeffaa75..d86bef35f3 100644 --- a/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py +++ b/setup/fusion/scripts/Comp/colorbleed/32bit/loaders_to32bit.py @@ -1,28 +1,12 @@ +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - def __enter__(self): - return None +def main(): + """Set all loaders to 32 bit""" + with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'): + tools = comp.GetToolList(False, "Loader").values() + for tool in tools: + tool.Depth = 5 - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def f1(): - with FusionLockComp('Loaders to 32bit'): - toolsDict = comp.GetToolList(False) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID == "Loader": - tool.Depth = 5 - -f1() - - \ No newline at end of file +main() From 89720ae3a9001adbe5dd63557de3b3efb66fd632 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 11:00:54 +0100 Subject: [PATCH 0679/1249] Cleanup code and improve readability --- .../duplicate_with_input_connections.py | 77 ++++++++----------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py b/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py index 1e53e213c1..9f4f4a8f0a 100644 --- a/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py +++ b/setup/fusion/scripts/Comp/colorbleed/duplicate_with_input_connections.py @@ -1,52 +1,43 @@ +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - def __enter__(self): - return None +def is_connected(input): + """Return whether an input has incoming connection""" + return input.GetAttrs()["INPB_Connected"] - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def pairs(n): - it = iter(n) - return zip(it, it) - -def duplicateWithInputConnections(): +def duplicate_with_input_connections(): + """Duplicate selected tools with incoming connections.""" - with FusionLockComp("Duplicate With Input Connections"): - originalTools = comp.GetToolList(True) - if not originalTools: - return # nothing selected - + original_tools = comp.GetToolList(True).values() + if not original_tools: + return # nothing selected + + with comp_lock_and_undo_chunk(comp, "Duplicate With Input Connections"): + + # Generate duplicates comp.Copy() comp.SetActiveTool() comp.Paste() - - duplicateTools = comp.GetToolList(True) - - for i, tool in originalTools.iteritems(): - dupToolInputs = duplicateTools[i].GetInputList() - - for j, input in tool.GetInputList().iteritems(): - if input.GetAttrs()['INPB_Connected']: - if j in dupToolInputs: - if dupToolInputs[j].GetAttrs()['INPB_Connected']: - print (" Both connected. ") - else: - dupToolInputs[j].ConnectTo(input.GetConnectedOutput()) - if dupToolInputs[j].GetAttrs()['INPB_Connected']: - print (" Connection Successful ") - - + duplicate_tools = comp.GetToolList(True).values() -duplicateWithInputConnections() - - \ No newline at end of file + # Copy connections + for original, new in zip(original_tools, duplicate_tools): + + original_inputs = original.GetInputList().values() + new_inputs = new.GetInputList().values() + assert len(original_inputs) == len(new_inputs) + + for original_input, new_input in zip(original_inputs, new_inputs): + + if is_connected(original_input): + + if is_connected(new_input): + # Already connected if it is between the copied tools + continue + + new_input.ConnectTo(original_input.GetConnectedOutput()) + assert is_connected(new_input), "Must be connected now" + + +duplicate_with_input_connections() From 16fac347b22964a87403da8da869a436961aeef4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 11:46:30 +0100 Subject: [PATCH 0680/1249] Cleanup code and improve readability --- .../update_selected_loader_ranges.py | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py b/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py index 7d0a4b25a8..f42a032e84 100644 --- a/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py +++ b/setup/fusion/scripts/Comp/colorbleed/update_selected_loader_ranges.py @@ -1,37 +1,32 @@ +"""Forces Fusion to 'retrigger' the Loader to update. + +Warning: + This might change settings like 'Reverse', 'Loop', trims and other + settings of the Loader. So use this at your own risk. + +""" + +from avalon.fusion import comp_lock_and_undo_chunk -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) +with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"): + tools = comp.GetToolList(True, "Loader").values() + for tool in tools: - def __enter__(self): - return None + # Get tool attributes + tool_a = tool.GetAttrs() + clipTable = tool_a['TOOLST_Clip_Name'] + altclipTable = tool_a['TOOLST_AltClip_Name'] + startTime = tool_a['TOOLNT_Clip_Start'] + old_global_in = tool.GlobalIn[comp.CurrentTime] - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() + # Reapply + for index, _ in clipTable.items(): + time = startTime[index] + tool.Clip[time] = tool.Clip[time] + for index, _ in altclipTable.items(): + time = startTime[index] + tool.ProxyFilename[time] = tool.ProxyFilename[time] -with FusionLockComp("Reload clip time ranges"): - toolsDict = comp.GetToolList(True) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID != "Loader": - continue - - tool_a = tool.GetAttrs() - clipTable = tool_a['TOOLST_Clip_Name'] - altclipTable = tool_a['TOOLST_AltClip_Name'] - startTime = tool_a['TOOLNT_Clip_Start'] - oldGlobalIn = tool.GlobalIn[comp.CurrentTime] - - for n, c in clipTable.items(): - tool.Clip[startTime[n]] = tool.Clip[startTime[n]] - - for n, c in altclipTable.items(): - tool.ProxyFilename[startTime[n]] = tool.ProxyFilename[startTime[n]] - - tool.GlobalIn[comp.CurrentTime] = oldGlobalIn + tool.GlobalIn[comp.CurrentTime] = old_global_in From 59818ea30d9d0d49004c605857ff4bc14bbcb8ea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 11:47:06 +0100 Subject: [PATCH 0681/1249] Remove unused Fusion Comp scripts (discussed with team) --- .../Comp/colorbleed/SE_LoaderTweaker.lua | 107 ------------------ .../predivide_postmultiply_BC_and_CC.py | 25 ---- .../Comp/colorbleed/search_replace_paths.py | 107 ------------------ .../Comp/colorbleed/set_relative_paths.py | 96 ---------------- 4 files changed, 335 deletions(-) delete mode 100644 setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua delete mode 100644 setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py delete mode 100644 setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py delete mode 100644 setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py diff --git a/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua b/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua deleted file mode 100644 index 0d54536d7d..0000000000 --- a/setup/fusion/scripts/Comp/colorbleed/SE_LoaderTweaker.lua +++ /dev/null @@ -1,107 +0,0 @@ ------------------------------------------------------------- --- Change various options in Loadertools, Revision: 2.1 --- --- --- place in Fusion:\Scripts\Comp --- --- written by Isaac Guenard (izyk@eyeonline.com) / Sean Konrad --- created : January 24rd, 2005 --- modified by Eric Westphal (Eric@SirEdric.de), February 2007 ------------------------------------------------------------- - - -MissingFramesOpt = {"Do Not Change", "Fail", "Hold Previous", "Output Black", "Wait"} -DepthOpt={"Do Not Change", "Format", "Default", "int8", "int16", "float16", "float32"} -GeneralOpt={"Do Not Change", "Off", "On"} -HoldOpt={"Do Not Change", "Set to..."} -PixelOpt={"Do Not Change", "From File", "Default", "Custom (set below)"} - -ret = comp.AskUser("SirEdric's Tweak-All-Loaders", { - {"AlphaSolid", "Dropdown", Options = GeneralOpt}, - {"PostMultiply", "Dropdown", Options = GeneralOpt}, - {"InvertAlpha", "Dropdown", Options = GeneralOpt}, - {"MissingFrames", "Dropdown", Options = MissingFramesOpt}, - {"Depth", "Dropdown", Options = DepthOpt}, - {"PixelAspect", "Dropdown", Options = PixelOpt}, - {"CustomPixelAspect", "Position", Default={1,1} }, - {"HoldFirstOpt", Name="Hold First Frame", "Dropdown", Options = HoldOpt}, - {"HoldFirst", Name = "Hold first Frame for", "Screw", Default = 0, Min = 0, Max=500, Integer = true}, - {"HoldLastOpt", Name="Hold Last Frame", "Dropdown", Options = HoldOpt}, - {"HoldLast", Name = "Hold first Frame for", "Screw", Default = 0, Min = 0, Max=500, Integer = true}, - {"Selected", Name = "Affect Selected Tools Only", "Checkbox", Default = 0} - }) - -if ret then - composition:StartUndo("SE_LoaderTweaker") - MyPixX=ret.CustomPixelAspect[1] - MyPixY=ret.CustomPixelAspect[2] - print(MyPixX.."bb".. MyPixY) - print() - print("SE_LoaderTweaker is about to change...") - print("...AlphaSolid to ["..GeneralOpt[ret.AlphaSolid+1].."]") - print("...PostMultiply to ["..GeneralOpt[ret.PostMultiply+1].."]") - print("...InvertAlpha to ["..GeneralOpt[ret.InvertAlpha+1].."]") - print("...Missing Frames to [".. MissingFramesOpt[ret.MissingFrames + 1].."]") - print("...Depth to ["..DepthOpt[ret.Depth+1].."]") - print("...PixelAspect to ["..PixelOpt[ret.PixelAspect+1].."]") - print("...CustomPixelAspect(if selected): X="..MyPixX.." Y="..MyPixY) - print("...Hold First Frame to ["..HoldOpt[ret.HoldFirstOpt+1]..": " .. ret.HoldFirst.."]") - print("...Hold Last Frame to ["..HoldOpt[ret.HoldLastOpt+1]..": " .. ret.HoldLast.."]") - if ret.Selected then - print("...... *** on selected tools only! ***") - end - print("---------------------------------------------------") - print() - - -- ((ret.Selected ==1)) will return true if the - -- selected checkbox is enabled..... - - for i, v in composition:GetToolList((ret.Selected == 1)) do - id = v:GetAttrs().TOOLS_RegID - MyName = v:GetAttrs().TOOLS_Name - if id == "Loader" then - print("Changing "..MyName.." Options:") - if ret.AlphaSolid > 0 then -- check for 'DoNothing' - print("MakeAlphaSolid set to: "..(ret.AlphaSolid-1)) - v.MakeAlphaSolid = (ret.AlphaSolid-1) - end - if ret.PostMultiply > 0 then -- check for 'DoNothing' - print("PostMultiplyByAlpha set to: "..(ret.PostMultiply-1)) - v.PostMultiplyByAlpha = (ret.PostMultiply-1) - end - if ret.InvertAlpha > 0 then -- check for 'DoNothing' - print("InvertAlpha set to: "..(ret.InvertAlpha-1)) - v.InvertAlpha = (ret.InvertAlpha-1) - end - if ret.MissingFrames >0 then -- check for 'DoNothing' - print("MissingFrames set to: "..(ret.MissingFrames-1)) - v.MissingFrames = (ret.MissingFrames-1) - end - if ret.Depth >0 then -- check for 'DoNothing' - print("Depth set to: "..(ret.Depth-1)) - v.Depth = (ret.Depth-1) - end - if ret.PixelAspect >0 then -- check for 'DoNothing' - print("PixelAspect set to: "..(ret.PixelAspect-1)) - v.PixelAspect = (ret.PixelAspect-1) - if ret.PixelAspect == 3 then - v.CustomPixelAspect={MyPixX, MyPixY} - end - end - if ret.HoldFirstOpt >0 then -- check for 'DoNothing' - print("HoldFirstFrame set to: "..(ret.HoldFirst)) - v.HoldFirstFrame = (ret.HoldFirst) - end - - if ret.HoldLastOpt >0 then -- check for 'DoNothing' - print("HoldLastFrame set to: "..(ret.HoldLast)) - v.HoldLastFrame = (ret.HoldLast) - end - - print(v:GetAttrs().TOOLS_Name) - end - end -composition:EndUndo(true) -end - -print() diff --git a/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py b/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py deleted file mode 100644 index 9834625109..0000000000 --- a/setup/fusion/scripts/Comp/colorbleed/predivide_postmultiply_BC_and_CC.py +++ /dev/null @@ -1,25 +0,0 @@ - -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - - def __enter__(self): - return None - - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - -idList = set(["BrightnessContrast", "ColorCorrector"]) -attrName = "PreDividePostMultiply" - -with FusionLockComp("BC & CC set PreMultiplyPostDivide to 1"): - toolsDict = comp.GetToolList(False) - if toolsDict: - for i, tool in toolsDict.items(): - if tool.ID in idList: - for input in tool.GetInputList().values(): - setattr(tool, attrName, 1.0) \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py b/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py deleted file mode 100644 index 7fdfb6e388..0000000000 --- a/setup/fusion/scripts/Comp/colorbleed/search_replace_paths.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import re - - -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - - def __enter__(self): - return None - - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def replaceStr(inputString, srchFor, srchTo, caseSensitive=True): - if caseSensitive: - return inputString.replace(srchFor, srchTo) - else: - regex = re.compile(re.escape(srchFor), re.IGNORECASE) - return regex.sub(srchTo, inputString) - - -def searchReplaceLoaderSavers(): - userResponse = comp.AskUser("Repath All Loaders", - {1:{1:"Loaders", 2:"Checkbox", "Name":"Loaders", "NumAcross":3, "Default":1}, - 2:{1:"Savers", 2:"Checkbox", "Name":"Savers", "NumAcross":3, "Default":1}, - 3:{1:"Proxy", 2:"Checkbox", "Name":"Proxy", "NumAcross":3, "Default":1}, - 4:{1:"Source", 2:"Text", "Name":"Enter pattern to search for"}, - 5:{1:"Replacement", 2:"Text", "Name":"Enter the replacement path"}, - 6:{1:"Valid", 2:"Checkbox", "Name":"Check If New Path is Valid", "Default":1}, - 7:{1:"CaseSensitive", 2:"Checkbox", "Name":"Case Sensitive", "Default":1}, - 8:{1:"SelectedOnly", 2:"Checkbox", "Name":"Selected Only", "Default":0}, - 9:{1:"PreserveGlobalIn", 2:"Checkbox", "Name":"Preserve Global In Point", "Default":1} - } - ) - - if userResponse: - srchFor = userResponse['Source'] - if not srchFor: - raise RuntimeError("No source string specified.") - - srchTo = userResponse['Replacement'] - if not srchTo: - raise RuntimeError("No replacement string specified.") - - doLoaders = userResponse['Loaders'] - doSavers = userResponse['Savers'] - doProxy = userResponse['Proxy'] - doValidate = userResponse['Valid'] - doCaseSensitive = userResponse['CaseSensitive'] - doSelectedOnly = bool(userResponse['SelectedOnly']) - doPreserveGlobalIn = bool(userResponse['PreserveGlobalIn']) - - - with FusionLockComp('Path Remap - "{0}" to "{1}"'.format(srchFor, srchTo)): - toolsDict = comp.GetToolList(doSelectedOnly) - for i, tool in toolsDict.items(): - toolId = tool.ID - if toolId == "Loader" or toolId == "Saver": - tool_a = tool.GetAttrs() - if (doLoaders or doProxy) and toolId == "Loader": - clipTable = tool_a['TOOLST_Clip_Name'] - altclipTable = tool_a['TOOLST_AltClip_Name'] - startTime = tool_a['TOOLNT_Clip_Start'] - - if doPreserveGlobalIn: - oldGlobalIn = tool.GlobalIn[comp.CurrentTime] - - if doLoaders: - for n, name in clipTable.items(): - #for i in table.getn(clipTable): - if name: - newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) - print (name, newPath) - if not doValidate or os.path.exists(comp.MapPath(newPath)): - tool.Clip[startTime[n]] = newPath - else: - print( "FAILED : New clip does not exist; skipping sequence.\n {0} .. {1}".format(name, newPath)) - - if doProxy: - for n, name in altclipTable.items(): - if name: - newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) - if not doValidate or os.path.exists(comp.MapPath(newPath)): - tool.ProxyFilename[startTime[n]] = newPath - else: - print( "FAILED : New proxy clip does not exist; skipping sequence.\n {0} .. {1}".format(name, newPath)) - - if doPreserveGlobalIn: - tool.GlobalIn[comp.CurrentTime] = oldGlobalIn - - if doSavers and toolId == "Saver": - for i, name in tool_a['TOOLST_Clip_Name'].items(): - newPath = replaceStr(name, srchFor, srchTo, doCaseSensitive) - if not doValidate or os.path.exists(os.path.dirname(comp.MapPath(newPath))): - tool.Clip[comp. ] = newPath - else: - print( "FAILED : Output directory does not exist; skipping saver.\n {0} .. {1}".format(name, newPath)) - -searchReplaceLoaderSavers() - - \ No newline at end of file diff --git a/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py b/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py deleted file mode 100644 index d3a011162b..0000000000 --- a/setup/fusion/scripts/Comp/colorbleed/set_relative_paths.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import re - - -class FusionLockComp(object): - def __init__(self, undoQueueName="Script CMD"): - # Lock flow - comp.Lock() - # Start undo event - comp.StartUndo(undoQueueName) - - def __enter__(self): - return None - - def __exit__(self, type, value, traceback): - comp.EndUndo(True) - comp.Unlock() - - -def makeRelativePath(root, path): - try: - return "Comp:\{0}".format(os.path.relpath(os.path.abspath(fusion.MapPath(path)), root)) - except ValueError: - print("Warning -- Can't define relative path for: {0}".format(path)) - return path - -def pathToCurrentComp(): - return comp.GetAttrs()["COMPS_FileName"] - - -def cbRelativePaths(): - userResponse = comp.AskUser("Make paths relative", - { - 1:{1:"Loaders", 2:"Checkbox", "Name":"Loaders", "NumAcross":3, "Default":1}, - 2:{1:"Savers", 2:"Checkbox", "Name":"Savers", "NumAcross":3, "Default":1}, - 3:{1:"Proxy", 2:"Checkbox", "Name":"Proxy", "NumAcross":3, "Default":1}, - 4:{1:"SelectedOnly", 2:"Checkbox", "Name":"Selected Only", "Default":0} - } - ) - - if userResponse: - - root = pathToCurrentComp() - if not root: - raise RuntimeError("Fusion file has not been saved. Can't make paths relative") - if root: - root = os.path.dirname(root) - - # set root - os.chdir(root) - - doLoaders = userResponse['Loaders'] - doSavers = userResponse['Savers'] - doProxy = userResponse['Proxy'] - doSelectedOnly = bool(userResponse['SelectedOnly']) - - - - with FusionLockComp('Make paths relative'): - toolsDict = comp.GetToolList(doSelectedOnly) - for i, tool in toolsDict.items(): - toolId = tool.ID - if toolId == "Loader" or toolId == "Saver": - tool_a = tool.GetAttrs() - if (doLoaders or doProxy) and toolId == "Loader": - clipTable = tool_a['TOOLST_Clip_Name'] - altclipTable = tool_a['TOOLST_AltClip_Name'] - startTime = tool_a['TOOLNT_Clip_Start'] - - # Preserve global in - oldGlobalIn = tool.GlobalIn[comp.CurrentTime] - - if doLoaders: - for n, name in clipTable.items(): - if name: - newPath = makeRelativePath(root, name) - tool.Clip[startTime[n]] = newPath - - if doProxy: - for n, name in altclipTable.items(): - if name: - newPath = makeRelativePath(root, name) - tool.ProxyFilename[startTime[n]] = newPath - - # Set global in (to what we preserved) - tool.GlobalIn[comp.CurrentTime] = oldGlobalIn - - if doSavers and toolId == "Saver": - for i, name in tool_a['TOOLST_Clip_Name'].items(): - if name: - newPath = makeRelativePath(root, name) - tool.Clip[comp.TIME_UNDEFINED] = newPath - -cbRelativePaths() - - \ No newline at end of file From 303b3bdb287c3eca99c4334f40ddb96373692f84 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 12:38:08 +0100 Subject: [PATCH 0682/1249] Fix correct updating of information, fix updating of comp information, apply Avalon stylesheet to tool, print into the Comp that's set in the tool. --- .../scripts/Comp/colorbleed/set_rendermode.py | 54 +++++++------------ 1 file changed, 20 insertions(+), 34 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py index 1ab523ce3e..97b19d9ef6 100644 --- a/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py +++ b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py @@ -1,8 +1,7 @@ -import os - from avalon.vendor.Qt import QtCore, QtWidgets from avalon.vendor import qtawesome import avalon.fusion as avalon +from avalon import style _help = {"renderlocal": "Render the comp on your own machine and publish " @@ -20,15 +19,15 @@ class SetRenderMode(QtWidgets.QWidget): self._comp_name = self._get_comp_name() self.setWindowTitle("Set Render Mode - {}".format(self._comp_name)) - self.resize(300, 150) - self.setFixedSize(300, 150) + self.setFixedSize(300, 175) layout = QtWidgets.QVBoxLayout() # region comp info comp_info_layout = QtWidgets.QHBoxLayout() - update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh"), "") + update_btn = QtWidgets.QPushButton(qtawesome.icon("fa.refresh", + color="white"), "") update_btn.setFixedWidth(25) update_btn.setFixedHeight(25) @@ -44,24 +43,15 @@ class SetRenderMode(QtWidgets.QWidget): mode_options.addItems(_help.keys()) mode_information = QtWidgets.QTextEdit() - mode_information.setEnabled(False) + mode_information.setReadOnly(True) # endregion modes - accept_layout = QtWidgets.QHBoxLayout() accept_btn = QtWidgets.QPushButton("Accept") - validation_state = QtWidgets.QPushButton() - validation_state.setFixedHeight(15) - validation_state.setFixedWidth(15) - validation_state.setEnabled(False) - validation_state.setStyleSheet("background-color: green") - - accept_layout.addWidget(accept_btn) - accept_layout.addWidget(validation_state) layout.addLayout(comp_info_layout) layout.addWidget(mode_options) layout.addWidget(mode_information) - layout.addLayout(accept_layout) + layout.addWidget(accept_btn) self.setLayout(layout) @@ -72,11 +62,13 @@ class SetRenderMode(QtWidgets.QWidget): self.mode_information = mode_information self.accept_btn = accept_btn - self.validation = validation_state self.connections() self.update() + # Force updated render mode help text + self._update_rendermode_info() + def connections(self): """Build connections between code and buttons""" @@ -94,33 +86,26 @@ class SetRenderMode(QtWidgets.QWidget): self.setWindowTitle("Set Render Mode") self.comp_information.setText(self._comp_name) - self._update_rendermode_info() + # Update current comp settings + mode = self._get_comp_rendermode() + index = self.mode_options.findText(mode) + self.mode_options.setCurrentIndex(index) def _update_rendermode_info(self): - - rendermode = self._get_comp_rendermode() - if rendermode is None: - rendermode = "renderlocal" - + rendermode = self.mode_options.currentText() self.mode_information.setText(_help[rendermode]) def _get_comp_name(self): - return os.path.basename(self._comp.GetAttrs("COMPS_FileName")) + return self._comp.GetAttrs("COMPS_Name") def _get_comp_rendermode(self): - return self._comp.GetData("colorbleed.rendermode") + return self._comp.GetData("colorbleed.rendermode") or "renderlocal" def _set_comp_rendermode(self): rendermode = self.mode_options.currentText() self._comp.SetData("colorbleed.rendermode", rendermode) - # Validate the rendermode has been updated correctly - if not self._validation(): - self.validation.setStyleSheet("background-color: red") - raise AssertionError("Rendermode in UI is not render mode in comp: " - "%s" % self._comp_name) - - print("Updated render mode for %s to %s" % (self._comp_name, rendermode)) + self._comp.Print("Updated render mode to '%s'\n" % rendermode) def _validation(self): ui_mode = self.mode_options.currentText() @@ -133,6 +118,7 @@ if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) - w = SetRenderMode() - w.show() + window = SetRenderMode() + window.setStyleSheet(style.load_stylesheet()) + window.show() sys.exit(app.exec_()) From ea43b451769027d851a050dada027ec1c2feec0a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 12:40:54 +0100 Subject: [PATCH 0683/1249] Remove comp name being in title, because it wasn't actually shown anyway (because comp update was removing it, hehe) --- setup/fusion/scripts/Comp/colorbleed/set_rendermode.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py index 97b19d9ef6..0fbcf1bf86 100644 --- a/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py +++ b/setup/fusion/scripts/Comp/colorbleed/set_rendermode.py @@ -18,7 +18,7 @@ class SetRenderMode(QtWidgets.QWidget): self._comp = avalon.get_current_comp() self._comp_name = self._get_comp_name() - self.setWindowTitle("Set Render Mode - {}".format(self._comp_name)) + self.setWindowTitle("Set Render Mode") self.setFixedSize(300, 175) layout = QtWidgets.QVBoxLayout() @@ -82,8 +82,6 @@ class SetRenderMode(QtWidgets.QWidget): self._comp = avalon.get_current_comp() self._comp_name = self._get_comp_name() - - self.setWindowTitle("Set Render Mode") self.comp_information.setText(self._comp_name) # Update current comp settings From 0868ebbfe9d05f9499245198a5b16c56ad37e643 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 15:36:12 +0100 Subject: [PATCH 0684/1249] Add ValidateSaverHasInput for Fusion savers --- .../publish/validate_saver_has_input.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 colorbleed/plugins/fusion/publish/validate_saver_has_input.py diff --git a/colorbleed/plugins/fusion/publish/validate_saver_has_input.py b/colorbleed/plugins/fusion/publish/validate_saver_has_input.py new file mode 100644 index 0000000000..27ed77d9d9 --- /dev/null +++ b/colorbleed/plugins/fusion/publish/validate_saver_has_input.py @@ -0,0 +1,29 @@ +import pyblish.api + + +class ValidateSaverHasInput(pyblish.api.InstancePlugin): + """Validate saver has incoming connection + + This ensures a Saver has at least an input connection. + + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Saver Has Input" + families = ["colorbleed.saver"] + hosts = ["fusion"] + + @classmethod + def get_invalid(cls, instance): + + saver = instance[0] + if not saver.Input.GetConnectedOutput(): + return [saver] + + return [] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Saver has no incoming connection: " + "{} ({})".format(instance, invalid[0].Name)) From 9f9dd8c3e1b4af052fd85ffcf0953e6076cb9ac6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 15:54:07 +0100 Subject: [PATCH 0685/1249] Remove Fusion Version Up comp script - fusion already has this (Ctrl + Shift + S) --- setup/fusion/scripts/Comp/Version Up.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 setup/fusion/scripts/Comp/Version Up.py diff --git a/setup/fusion/scripts/Comp/Version Up.py b/setup/fusion/scripts/Comp/Version Up.py deleted file mode 100644 index 89498a9b20..0000000000 --- a/setup/fusion/scripts/Comp/Version Up.py +++ /dev/null @@ -1,13 +0,0 @@ -import colorbleed.lib -import avalon.fusion - -attrs = comp.GetAttrs() -filename = comp.MapPath(attrs["COMPS_FileName"]) -if not filename: - raise RuntimeError("File not saved yet. Can't increment version.") - -new = colorbleed.lib.version_up(filename) -print("Incrementing comp to: {}".format(new)) - -with avalon.fusion.comp_lock_and_undo_chunk(comp, "Save incrementally.."): - comp.Save(new) \ No newline at end of file From 9365eb42f14c1f501269bd2fc941d87dba63b2f9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 15:59:40 +0100 Subject: [PATCH 0686/1249] Refactor create saver to more explicit create tiff saver.py --- .../fusion/create/{create_saver.py => create_tiff_saver.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/fusion/create/{create_saver.py => create_tiff_saver.py} (100%) diff --git a/colorbleed/plugins/fusion/create/create_saver.py b/colorbleed/plugins/fusion/create/create_tiff_saver.py similarity index 100% rename from colorbleed/plugins/fusion/create/create_saver.py rename to colorbleed/plugins/fusion/create/create_tiff_saver.py From 2f568e4b5f49610c8458090cb4edfb89bfa218ab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 16:07:59 +0100 Subject: [PATCH 0687/1249] Refactor extractor to simpler name, Render Local --- .../publish/{extract_image_sequence.py => render_local.py} | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename colorbleed/plugins/fusion/publish/{extract_image_sequence.py => render_local.py} (87%) diff --git a/colorbleed/plugins/fusion/publish/extract_image_sequence.py b/colorbleed/plugins/fusion/publish/render_local.py similarity index 87% rename from colorbleed/plugins/fusion/publish/extract_image_sequence.py rename to colorbleed/plugins/fusion/publish/render_local.py index bdb2d8fbf2..39ce0d45dc 100644 --- a/colorbleed/plugins/fusion/publish/extract_image_sequence.py +++ b/colorbleed/plugins/fusion/publish/render_local.py @@ -3,9 +3,10 @@ import pyblish.api import avalon.fusion as fusion -class ExtractImageSequence(pyblish.api.InstancePlugin): - """Extract result of saver by starting a comp render +class FusionRenderLocal(pyblish.api.InstancePlugin): + """Render the current Fusion composition locally. + Extract the result of savers by starting a comp render This will run the local render of Fusion. """ From 1ca08e26414cf6c8cbeed1af25d6cd144e250b47 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 16:09:36 +0100 Subject: [PATCH 0688/1249] Don't publish image sequence if Render Local failed --- .../plugins/fusion/publish/publish_image_sequences.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index cd0835b3ce..9ee240a45e 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -5,6 +5,8 @@ import subprocess import pyblish.api +from colorbleed.action import get_errored_plugins_from_data + def _get_script(): """Get path to the image sequence script""" @@ -34,6 +36,13 @@ class PublishImageSequence(pyblish.api.Integrator): def process(self, instance): + # Skip this plug-in if the ExtractImageSequence failed + errored_plugins = get_errored_plugins_from_data(instance.context) + if any(plugin.__name__ == "FusionRenderLocal" for plugin in + errored_plugins): + raise RuntimeError("Fusion local render failed, " + "publishing images skipped.") + subset = instance.data["subset"] ext = instance.data["ext"] From a20fdcffc736cbcaaec9c1b7898c7071da6665a3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 16:09:59 +0100 Subject: [PATCH 0689/1249] Refactor to InstancePlugin over deprecated Integrator class. --- colorbleed/plugins/fusion/publish/publish_image_sequences.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/colorbleed/plugins/fusion/publish/publish_image_sequences.py index 9ee240a45e..33d6d2773d 100644 --- a/colorbleed/plugins/fusion/publish/publish_image_sequences.py +++ b/colorbleed/plugins/fusion/publish/publish_image_sequences.py @@ -26,7 +26,7 @@ def _get_script(): return module_path -class PublishImageSequence(pyblish.api.Integrator): +class PublishImageSequence(pyblish.api.InstancePlugin): """Publish the generated local image sequences.""" order = pyblish.api.IntegratorOrder From b519f275bd9a11dba4d572eb8814964cb78bbac0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Feb 2018 16:23:58 +0100 Subject: [PATCH 0690/1249] Fix error on disconnected_attribute context manager --- colorbleed/plugins/maya/publish/extract_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 5a5a101747..923d9a6ba9 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -100,7 +100,7 @@ class ExtractYetiRig(colorbleed.api.Extractor): with open(settings_path, "w") as fp: json.dump(settings, fp, ensure_ascii=False) - attr_value = {"%s.imageSearchPath" % n: image_search_path for + attr_value = {"%s.imageSearchPath" % n: str(image_search_path) for n in yeti_nodes} # Get input_SET members From 44c5f8ae1a9c603d2c6329651e28c630c3441257 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Feb 2018 10:24:34 +0100 Subject: [PATCH 0691/1249] Fix REN-34: Support Yeti fur in Deadline submitted Redshift renders --- colorbleed/plugins/maya/publish/submit_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 7202aaea62..6be1640281 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -206,6 +206,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # todo: This is a temporary fix for yeti variables "PEREGRINEL_LICENSE", + "REDSHIFT_MAYAEXTENSIONSPATH", "VRAY_FOR_MAYA2018_PLUGINS_X64", "VRAY_PLUGINS_X64", "VRAY_USE_THREAD_AFFINITY", From 44f935b24b714a211c0c792c2c80609c540fead3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Feb 2018 12:49:50 +0100 Subject: [PATCH 0692/1249] Fix PLN-89: UDIM textures nog working after publish because set with computed file pattern instead of source pattern. --- .../plugins/maya/publish/collect_look.py | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index ead79a00b9..93080b5312 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -141,7 +141,7 @@ class CollectLook(pyblish.api.InstancePlugin): # Add shader sets to the instance for unify ID validation instance.extend(shader for shader in looksets if shader not in instance_lookup) - + self.log.info("Collected look for %s" % instance) def collect_sets(self, instance): @@ -239,11 +239,10 @@ class CollectLook(pyblish.api.InstancePlugin): return attributes - def collect_resource(self, node, verbose=False): + def collect_resource(self, node): """Collect the link to the file(s) used (resource) Args: node (str): name of the node - verbose (bool): enable debug information Returns: dict @@ -252,23 +251,22 @@ class CollectLook(pyblish.api.InstancePlugin): attribute = "{}.fileTextureName".format(node) source = cmds.getAttr(attribute) - # Get the computed file path (e.g. the one with the pattern - # in it) So we can reassign it this computed file path whenever - # we need to. + # Compare with the computed file path, e.g. the one with the + # pattern in it, to generate some logging information about this + # difference computed_attribute = "{}.computedFileTextureNamePattern".format(node) computed_source = cmds.getAttr(computed_attribute) if source != computed_source: - if verbose: - self.log.debug("File node computed pattern differs from " - "original pattern: {0} " - "({1} -> {2})".format(node, - source, - computed_source)) + self.log.debug("Detected computed file pattern difference " + "from original pattern: {0} " + "({1} -> {2})".format(node, + source, + computed_source)) - # We replace backslashes with forward slashes because V-Ray - # can't handle the UDIM files with the backslashes in the - # paths as the computed patterns - source = computed_source.replace("\\", "/") + # We replace backslashes with forward slashes because V-Ray + # can't handle the UDIM files with the backslashes in the + # paths as the computed patterns + source = source.replace("\\", "/") files = shaders.get_file_node_files(node) if len(files) == 0: From c4daf66e55596c1cc7963673e9620665aca0c5ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Feb 2018 14:58:05 +0100 Subject: [PATCH 0693/1249] Fix LKD-15: Ensure deformed shapes in look have ids from history (reuse logic from validate_rig_out_set_node_ids.py) --- colorbleed/maya/lib.py | 41 ++++++ .../publish/validate_look_deformed_shapes.py | 130 ------------------ .../validate_node_ids_deformed_shapes.py | 66 +++++++++ .../publish/validate_rig_out_set_node_ids.py | 53 +------ 4 files changed, 109 insertions(+), 181 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py create mode 100644 colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 729cc1f60c..d6df4fae52 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1247,3 +1247,44 @@ def remove_other_uv_sets(mesh): for i in indices: attr = '{0}.uvSet[{1}]'.format(mesh, i) cmds.removeMultiInstance(attr, b=True) + + +def get_id_from_history(node): + """Return first node id in the history chain that matches this node. + + The nodes in history must be of the exact same node type and must be + parented under the same parent. + + Args: + node (str): node to retrieve the + + Returns: + str or None: The id from the node in history or None when no id found + on any valid nodes in the history. + + """ + + def _get_parent(node): + """Return full path name for parent of node""" + return cmds.listRelatives(node, parent=True, fullPath=True) + + node = cmds.ls(node, long=True)[0] + + # Find all similar nodes in history + history = cmds.listHistory(node) + node_type = cmds.nodeType(node) + similar_nodes = cmds.ls(history, exactType=node_type, long=True) + + # Exclude itself + similar_nodes = [x for x in similar_nodes if x != node] + + # The node *must be* under the same parent + parent = _get_parent(node) + similar_nodes = [i for i in similar_nodes if _get_parent(i) == parent] + + # Check all of the remaining similar nodes and take the first one + # with an id and assume it's the original. + for similar_node in similar_nodes: + _id = get_id(similar_node) + if _id: + return _id diff --git a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py deleted file mode 100644 index 1e458b557e..0000000000 --- a/colorbleed/plugins/maya/publish/validate_look_deformed_shapes.py +++ /dev/null @@ -1,130 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib - -# from cbra.utils.maya.node_uuid import add_ids - - -def get_deformed_history_id_mapping(shapes): - """Return the id from history for nodes that are "Deformed". - - When shapes are referenced and get deformed by a deformer - the shape is duplicated *without its attributes* as such - the new shape misses object ids. This method will try to - trace back in the history to find the first shape with - ids to identify the possible best match. - - Args: - shapes (list): The shapes that are deformed. - - Returns: - dict: Mapping of deformed shape to history shape. - - """ - - shapes = cmds.ls(shapes, shapes=True, long=True) - - # Possibly deformed shapes - deformed_shapes = [x for x in shapes if "Deformed" in x.rsplit("|", 1)[-1]] - - # The deformed shape should not be referenced - is_referenced = lambda n: cmds.referenceQuery(n, isNodeReferenced=True) - deformed_shapes = [x for x in deformed_shapes if not is_referenced(x)] - - # Shapes without id - deformed_shapes_without_id = [x for x in deformed_shapes - if not lib.get_id(x)] - - mapping = {} - for shape in deformed_shapes_without_id: - - node_type = cmds.objectType(shape) - history = cmds.listHistory(shape)[1:] # history, skipping itself - history_shapes = cmds.ls(history, exactType=node_type, long=True) - if not history_shapes: - continue - - for history_shape in history_shapes: - id = lib.get_id(history_shape) - if not id: - continue - - mapping[shape] = history_shape - break - - return mapping - - -class CopyUUIDsFromHistory(pyblish.api.Action): - """Repairs the action - - To retrieve the invalid nodes this assumes a static `get_invalid(instance)` - method is available on the plugin. - - """ - label = "Copy UUIDs from History" - on = "failed" # This action is only available on a failed plug-in - icon = "wrench" # Icon from Awesome Icon - - def process(self, context, plugin): - - # Get the errored instances - self.log.info("Finding failed instances..") - errored = colorbleed.api.get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored, plugin) - - ids_map = dict() - for instance in instances: - invalid = plugin.get_invalid(instance) - mapping = get_deformed_history_id_mapping(invalid) - - for destination, source in mapping.items(): - ids_map[destination] = lib.get_id(source) - - if not ids_map: - return - self.log.info(ids_map) - - -class ValidateLookDeformedShapes(pyblish.api.InstancePlugin): - """Validate look textures are set to ignore color space when set to RAW - - Whenever the format is NOT set to sRGB for a file texture it must have - its ignore color space file rules checkbox enabled to avoid unwanted - reverting to sRGB settings upon file relinking. - - To fix this use the select invalid action to find the invalid file nodes - and then check the "Ignore Color Space File Rules" checkbox under the - Color Space settings. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] - hosts = ['maya'] - label = 'Look deformed shapes' - actions = [colorbleed.api.SelectInvalidAction, CopyUUIDsFromHistory] - - @classmethod - def get_invalid(cls, instance): - - context = instance.context - nodes = context.data.get("instancePerItemNodesWithoutId", None) - if not nodes: - return list() - - mapping = get_deformed_history_id_mapping(nodes) - return mapping.keys() - - def process(self, instance): - """Process all the nodes in the instance""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Shapes found that are considered 'Deformed'" - "without object ids: {0}".format(invalid)) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py new file mode 100644 index 0000000000..4513acb6e1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py @@ -0,0 +1,66 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): + """Validate if deformed shapes have related IDs to the original shapes. + + When a deformer is applied in the scene on a referenced mesh that already + had deformers then Maya will create a new shape node for the mesh that + does not have the original id. This validator checks whether the ids are + valid on all the shape nodes in the instance. + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Deformed shape ids' + actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] + + def process(self, instance): + """Process all the nodes in the instance""" + + # Ensure all nodes have a cbId and a related ID to the original shapes + # if a deformer has been created on the shape + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Shapes found that are considered 'Deformed'" + "without object ids: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + """Get all nodes which do not match the criteria""" + + shapes = cmds.ls(instance[:], + dag=True, + leaf=True, + shapes=True, + long=True, + noIntermediate=True) + + invalid = [] + for shape in shapes: + history_id = lib.get_id_from_history(shape) + if history_id: + current_id = lib.get_id(shape) + if current_id != history_id: + invalid.append(shape) + + return invalid + + @classmethod + def repair(cls, instance): + + for node in cls.get_invalid(instance): + # Get the original id from history + history_id = lib.get_id_from_history(node) + if not history_id: + cls.log.error("Could not find ID in history for '%s'", node) + continue + + lib.set_id(node, history_id, overwrite=True) + diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py index a109c39171..6b8fc9d28c 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -5,55 +5,6 @@ import colorbleed.api import colorbleed.maya.lib as lib -def get_id_from_history(node): - """Return first node id in the history chain that matches this node. - - The nodes in history must be of the exact same node type and must be - parented under the same parent. - - Args: - node (str): node to retrieve the - - Returns: - str or None: The id from the node in history or None when no id found - on any valid nodes in the history. - - """ - - node = cmds.ls(node, long=True)[0] - - # Find all similar nodes in history - history = cmds.listHistory(node) - node_type = cmds.nodeType(node) - similar_nodes = cmds.ls(history, exactType=node_type, long=True) - - # Exclude itself - similar_nodes = [x for x in similar_nodes if x != node] - - # The node *must be* under the same parent - parent = get_parent(node) - similar_nodes = [i for i in similar_nodes if - get_parent(i) == parent] - - # Check all of the remaining similar nodes and take the first one - # with an id and assume it's the original. - for similar_node in similar_nodes: - _id = lib.get_id(similar_node) - if _id: - return _id - - -def get_parent(node): - """Get the parent node of the given node - Args: - node (str): full path of the node - - Returns: - str, full path if parent node - """ - return cmds.listRelatives(node, parent=True, fullPath=True) - - class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): """Validate if deformed shapes have related IDs to the original shapes. @@ -96,7 +47,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): noIntermediate=True) for shape in shapes: - history_id = get_id_from_history(shape) + history_id = lib.get_id_from_history(shape) if history_id: current_id = lib.get_id(shape) if current_id != history_id: @@ -109,7 +60,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): for node in cls.get_invalid(instance): # Get the original id from history - history_id = get_id_from_history(node) + history_id = lib.get_id_from_history(node) if not history_id: cls.log.error("Could not find ID in history for '%s'", node) continue From d6e67b6b39933367bddd9b10e15ee2bc4bf48776 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Feb 2018 15:06:37 +0100 Subject: [PATCH 0694/1249] Use get_id_from_history from colorbleed.maya.lib --- ...date_animation_out_set_related_node_ids.py | 53 +------------------ 1 file changed, 2 insertions(+), 51 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index ab024be49c..6aa39efe07 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -5,55 +5,6 @@ import colorbleed.api import colorbleed.maya.lib as lib -def get_id_from_history(node): - """Return first node id in the history chain that matches this node. - - The nodes in history must be of the exact same node type and must be - parented under the same parent. - - Args: - node (str): node to retrieve the - - Returns: - str or None: The id from the node in history or None when no id found - on any valid nodes in the history. - - """ - - node = cmds.ls(node, long=True)[0] - - # Find all similar nodes in history - history = cmds.listHistory(node) - node_type = cmds.nodeType(node) - similar_nodes = cmds.ls(history, exactType=node_type, long=True) - - # Exclude itself - similar_nodes = [x for x in similar_nodes if x != node] - - # The node *must be* under the same parent - parent = get_parent(node) - similar_nodes = [i for i in similar_nodes if - get_parent(i) == parent] - - # Check all of the remaining similar nodes and take the first one - # with an id and assume it's the original. - for similar_node in similar_nodes: - _id = lib.get_id(similar_node) - if _id: - return _id - - -def get_parent(node): - """Get the parent node of the given node - Args: - node (str): full path of the node - - Returns: - str, full path if parent node - """ - return cmds.listRelatives(node, parent=True, fullPath=True) - - class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """Validate if deformed shapes have related IDs to the original shapes @@ -105,7 +56,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): invalid.append(node) continue - history_id = get_id_from_history(node) + history_id = lib.get_id_from_history(node) if history_id is not None and node_id != history_id: invalid.append(node) @@ -116,7 +67,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): for node in cls.get_invalid(instance): # Get the original id from history - history_id = get_id_from_history(node) + history_id = lib.get_id_from_history(node) if not history_id: cls.log.error("Could not find ID in history for '%s'", node) continue From 4d4996b9babcb1b27bf76ce1f1aa63504b9f7df7 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 1 Mar 2018 18:04:52 +0100 Subject: [PATCH 0695/1249] Added switch to loader --- colorbleed/plugins/fusion/load/load_sequence.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 06304c685c..6d3d743c8a 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -143,6 +143,9 @@ class FusionLoadSequence(api.Loader): context=context, loader=self.__class__.__name__) + def switch(self, container, representation): + self.update(container, representation) + def update(self, container, representation): """Update the Loader's path From 05a442aec20978d8467fe0ef5657ac0492bc9c09 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 2 Mar 2018 12:47:11 +0100 Subject: [PATCH 0696/1249] Implemented switch function for loader to support switching subset --- colorbleed/plugins/fusion/load/load_sequence.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 06304c685c..6d3d743c8a 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -143,6 +143,9 @@ class FusionLoadSequence(api.Loader): context=context, loader=self.__class__.__name__) + def switch(self, container, representation): + self.update(container, representation) + def update(self, container, representation): """Update the Loader's path From 9520f563b8a6b4bc17753a4fab421ef2a0dbee05 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 2 Mar 2018 12:52:23 +0100 Subject: [PATCH 0697/1249] Added switch function --- colorbleed/plugins/maya/load/load_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index ff512610a3..9428ad40bd 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -30,6 +30,9 @@ class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): return nodes + def switch(self, container, representation): + self.update(container, representation) + class ImportModelLoader(api.Loader): """An ImportModelLoader for Maya From 5bd79a1b2ea75ef5a5f8112b693a846fa8ac343b Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 2 Mar 2018 16:37:33 +0100 Subject: [PATCH 0698/1249] slapcomp switch batch script --- colorbleed/scripts/slapcomp_switch.py | 80 +++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 colorbleed/scripts/slapcomp_switch.py diff --git a/colorbleed/scripts/slapcomp_switch.py b/colorbleed/scripts/slapcomp_switch.py new file mode 100644 index 0000000000..70553dd903 --- /dev/null +++ b/colorbleed/scripts/slapcomp_switch.py @@ -0,0 +1,80 @@ +import logging +import argparse + +import avalon.io as io +import avalon.api as api +import avalon.fusion + + +log = logging.getLogger("UpdateSlapComp") + + +def switch(data, targets=None): + """Update comp loaders through the containers + + Args: + data (dict): collection if {asset: [subset, subset]} + targets (list): to be implemented + """ + + representations = [] + + for asset_name, subsets in data.iteritems(): + + asset = io.find_one({"type": "asset", "name": asset_name}) + assert asset, ("Could not find asset in the database with the name " + "'%s'" % asset_name) + + subsets = io.find({"type": "subset", + "name": {"$in": subsets}, + "parent": asset["_id"]}) + + for subset in subsets: + version = io.find_one({"type": "version", + "parent": subset["_id"]}, + sort=[('name', -1)]) + + if not version: + log.error("Could not find a verison for {}.{}".format( + asset["name"], subset["name"] + )) + continue + + representation = io.find_one({"type": "representation", + "parent": version["_id"]}) + + representations.append(representation) + + host = api.registered_host() + containers = list(host.ls()) + count = 0 + for i, container in enumerate(containers): + representation = representations[i] + api.switch(container, representation) + count += 1 + + log.info("Updated %i items" % count) + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + + parser.add_argument("--data", + help="Dictionary with asset name as key and related " + "subsets (list) as value") + + parser.add_argument("--targets", + optional=True, + help="A list of target to update with given data") + + args = parser.parse_known_args() + if not args.data: + raise RuntimeError("No data given, cannot update slap comp") + + if not isinstance(args.data, dict): + raise ValueError("Expecting data in the form of a dict, got " + "%s" % type(args.data)) + + api.install(avalon.fusion) + switch(data=args.data) From a5be74475e5f0b5c6aa4c0acaf96269d504485e2 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 2 Mar 2018 18:10:21 +0100 Subject: [PATCH 0699/1249] Improved logic, arg only needs shot name --- colorbleed/scripts/slapcomp_switch.py | 83 +++++++++++++++------------ 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/colorbleed/scripts/slapcomp_switch.py b/colorbleed/scripts/slapcomp_switch.py index 70553dd903..95270259aa 100644 --- a/colorbleed/scripts/slapcomp_switch.py +++ b/colorbleed/scripts/slapcomp_switch.py @@ -9,47 +9,55 @@ import avalon.fusion log = logging.getLogger("UpdateSlapComp") -def switch(data, targets=None): +def switch(shot): """Update comp loaders through the containers Args: data (dict): collection if {asset: [subset, subset]} - targets (list): to be implemented """ - representations = [] - - for asset_name, subsets in data.iteritems(): - - asset = io.find_one({"type": "asset", "name": asset_name}) - assert asset, ("Could not find asset in the database with the name " - "'%s'" % asset_name) - - subsets = io.find({"type": "subset", - "name": {"$in": subsets}, - "parent": asset["_id"]}) - - for subset in subsets: - version = io.find_one({"type": "version", - "parent": subset["_id"]}, - sort=[('name', -1)]) - - if not version: - log.error("Could not find a verison for {}.{}".format( - asset["name"], subset["name"] - )) - continue - - representation = io.find_one({"type": "representation", - "parent": version["_id"]}) - - representations.append(representation) - host = api.registered_host() containers = list(host.ls()) + + targets = [c["name"] for c in containers] + + representations = {} + + asset = io.find_one({"type": "asset", "name": shot}) + assert asset, ("Could not find asset in the database with the name " + "'%s'" % shot) + + subsets = io.find({"type": "subset", + "name": {"$in": targets}, + "parent": asset["_id"]}) + + if len(subsets) != len(targets): + log.warning("Could not find the same amount of subsets in '%s'" % shot) + + for subset in subsets: + version = io.find_one({"type": "version", + "parent": subset["_id"]}, + sort=[('name', -1)]) + + if not version: + log.error("Could not find a verison for {}.{}".format( + asset["name"], subset["name"] + )) + continue + + representation = io.find_one({"type": "representation", + "parent": version["_id"]}) + + representations[subset["name"]] = representation + count = 0 for i, container in enumerate(containers): - representation = representations[i] + subsetname = container["name"] + if subsetname not in representations: + log.error("Subset '%s' not found in comp, skipping" % subsetname) + continue + + representation = representations[container["name"]] api.switch(container, representation) count += 1 @@ -60,21 +68,20 @@ if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument("--data", - help="Dictionary with asset name as key and related " - "subsets (list) as value") + parser.add_argument("--shot", + help="Shotname to update the current comp to") parser.add_argument("--targets", optional=True, help="A list of target to update with given data") args = parser.parse_known_args() - if not args.data: + if not args.shot: raise RuntimeError("No data given, cannot update slap comp") - if not isinstance(args.data, dict): + if not isinstance(args.shot, dict): raise ValueError("Expecting data in the form of a dict, got " - "%s" % type(args.data)) + "%s" % type(args.shot)) api.install(avalon.fusion) - switch(data=args.data) + switch(shot=args.shot) From 0b7161dfac1a863940c84dc35c0439571771188e Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 5 Mar 2018 13:49:25 +0100 Subject: [PATCH 0700/1249] Added switch method to support switching --- colorbleed/plugins/maya/load/load_alembic.py | 3 +++ colorbleed/plugins/maya/load/load_camera.py | 4 +++- colorbleed/plugins/maya/load/load_look.py | 3 +++ colorbleed/plugins/maya/load/load_mayaascii.py | 3 +++ colorbleed/plugins/maya/load/load_model.py | 3 +++ colorbleed/plugins/maya/load/load_rig.py | 3 +++ colorbleed/plugins/maya/load/load_yeti_cache.py | 3 +++ 7 files changed, 21 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index e9f4ce284b..afea761ab8 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -28,3 +28,6 @@ class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): self[:] = nodes return nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index 7bb095df67..c1e1b4bf25 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -14,7 +14,6 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, data): import maya.cmds as cmds - # import pprint # Get family type from the context cmds.loadPlugin("AbcImport.mll", quiet=True) @@ -41,3 +40,6 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): self[:] = nodes return nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/load/load_look.py b/colorbleed/plugins/maya/load/load_look.py index 16428ef034..cc13918ba1 100644 --- a/colorbleed/plugins/maya/load/load_look.py +++ b/colorbleed/plugins/maya/load/load_look.py @@ -35,3 +35,6 @@ class LookLoader(colorbleed.maya.plugin.ReferenceLoader): returnNewNodes=True) self[:] = nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/colorbleed/plugins/maya/load/load_mayaascii.py index 07bf1ea836..dd86cc97b5 100644 --- a/colorbleed/plugins/maya/load/load_mayaascii.py +++ b/colorbleed/plugins/maya/load/load_mayaascii.py @@ -28,3 +28,6 @@ class MayaAsciiLoader(colorbleed.maya.plugin.ReferenceLoader): self[:] = nodes return nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index 9428ad40bd..eb76bd9ada 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -203,6 +203,9 @@ class GpuCacheLoader(api.Loader): str(representation["_id"]), type="string") + def switch(self, container, representation): + self.update(container, representation) + def remove(self, container): import maya.cmds as cmds members = cmds.sets(container['objectName'], query=True) diff --git a/colorbleed/plugins/maya/load/load_rig.py b/colorbleed/plugins/maya/load/load_rig.py index cb4b5f2f7c..1f3e83f60d 100644 --- a/colorbleed/plugins/maya/load/load_rig.py +++ b/colorbleed/plugins/maya/load/load_rig.py @@ -65,3 +65,6 @@ class RigLoader(colorbleed.maya.plugin.ReferenceLoader): family="colorbleed.animation", options={"useSelection": True}, data={"dependencies": dependency}) + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 0544868340..ec7ab11474 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -108,6 +108,9 @@ class YetiCacheLoader(api.Loader): str(representation["_id"]), type="string") + def switch(self, container, representation): + self.update(container, representation) + # helper functions def create_namespace(self, asset): From 2bfdaff5c4e7463779325e5380d051b15141037b Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 5 Mar 2018 13:50:18 +0100 Subject: [PATCH 0701/1249] Added switch_item function to support switching --- colorbleed/lib.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index b57c9c4a83..686ff992a9 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -144,3 +144,70 @@ def version_up(filepath): log.info("New version %s" % new_label) return new_filename + + +def switch_item(container, + asset_name=None, + subset_name=None, + representation_name=None): + """Switch container asset, subset or representation of a container by name. + + It'll always switch to the latest version - of course a different + approach could be implemented. + + Args: + container (dict): data of the item to switch with + asset_name (str): name of the asset + subset_name (str): name of the subset + representation_name (str): name of the representation + + Returns: + None + + """ + + if all(not x for x in [asset_name, subset_name, representation_name]): + raise ValueError("Must have at least one change provided to switch.") + + # Collect any of current asset, subset and representation if not provided + # so we can use the original name from those. + if any(not x for x in [asset_name, subset_name, representation_name]): + _id = io.ObjectId(container["representation"]) + representation = io.find_one({"type": "representation", "_id": _id}) + version, subset, asset, project = io.parenthood(representation) + + if asset_name is None: + asset_name = asset["name"] + + if subset_name is None: + subset_name = subset["name"] + + if representation_name is None: + representation_name = representation["name"] + + # Find the new one + asset = io.find_one({"name": asset_name, "type": "asset"}) + assert asset, ("Could not find asset in the database with the name " + "'%s'" % asset_name) + + subset = io.find_one({"name": subset_name, + "type": "subset", + "parent": asset["_id"]}) + assert subset, ("Could not find subset in the database with the name " + "'%s'" % subset_name) + + version = io.find_one({"type": "version", + "parent": subset["_id"]}, + sort=[('name', -1)]) + + assert version, "Could not find a version for {}.{}".format( + asset_name, subset_name + ) + + representation = io.find_one({"name": representation_name, + "type": "representation", + "parent": version["_id"]}) + assert representation, ("Could not find subset in the database with the " + "name '%s'" % representation_name) + + avalon.api.switch(container, representation) From 85214e67daab338f9bc34c31695a8e0f1d1237a5 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 7 Mar 2018 09:39:23 +0100 Subject: [PATCH 0702/1249] Fixed assertion error message, return representation document --- colorbleed/lib.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 686ff992a9..9084410d18 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -207,7 +207,10 @@ def switch_item(container, representation = io.find_one({"name": representation_name, "type": "representation", "parent": version["_id"]}) - assert representation, ("Could not find subset in the database with the " - "name '%s'" % representation_name) + + assert representation, ("Could not find representation in the database with " + "the name '%s'" % representation_name) avalon.api.switch(container, representation) + + return representation From 7977b0f05d197c3eddbbebfc57510f18dff7076b Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 8 Mar 2018 15:01:15 +0100 Subject: [PATCH 0703/1249] Create lib module, added update_frame_range function --- colorbleed/fusion/lib.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 colorbleed/fusion/lib.py diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py new file mode 100644 index 0000000000..7186cd2cac --- /dev/null +++ b/colorbleed/fusion/lib.py @@ -0,0 +1,40 @@ +import sys + +import avalon.fusion + + +self = sys.modules[__name__] +self._project = None + + +def update_frame_range(start, end, comp=None, set_render_range=True): + """Set Fusion comp's start and end frame range + + Args: + start (float, int): start frame + end (float, int): end frame + comp (object, Optional): comp object from fusion + set_render_range (bool, Optional): When True this will also set the + composition's render start and end frame. + + Returns: + None + + """ + + if not comp: + comp = avalon.fusion.get_current_comp() + + attrs = { + "COMPN_GlobalStart": start, + "COMPN_GlobalEnd": end + } + + if set_render_range: + attrs.update({ + "COMPN_RenderStart": start, + "COMPN_RenderEnd": end + }) + + with avalon.fusion.comp_lock_and_undo_chunk(comp): + comp.SetAttrs(attrs) From 62696a2678144931ebf78f155527eb1c19b66f9f Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 8 Mar 2018 15:02:53 +0100 Subject: [PATCH 0704/1249] Removed _set_frame_range, switched with lib.update_frame_range --- colorbleed/plugins/fusion/load/actions.py | 39 ++++------------------- 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/colorbleed/plugins/fusion/load/actions.py b/colorbleed/plugins/fusion/load/actions.py index 68f66775a8..c986229748 100644 --- a/colorbleed/plugins/fusion/load/actions.py +++ b/colorbleed/plugins/fusion/load/actions.py @@ -5,37 +5,6 @@ from avalon import api -def _set_frame_range(start, end, set_render_range=True): - """Set Fusion comp's start and end frame range - - Attrs: - set_render_range (bool, Optional): When True this will also set the - composition's render start and end frame. - - Returns: - None - - """ - - from avalon.fusion import get_current_comp, comp_lock_and_undo_chunk - - comp = get_current_comp() - - attrs = { - "COMPN_GlobalStart": start, - "COMPN_GlobalEnd": end - } - - if set_render_range: - attrs.update({ - "COMPN_RenderStart": start, - "COMPN_RenderEnd": end - }) - - with comp_lock_and_undo_chunk(comp): - comp.SetAttrs(attrs) - - class FusionSetFrameRangeLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" @@ -53,6 +22,8 @@ class FusionSetFrameRangeLoader(api.Loader): def load(self, context, name, namespace, data): + from colorbleed.fusion import lib + version = context['version'] version_data = version.get("data", {}) @@ -64,7 +35,7 @@ class FusionSetFrameRangeLoader(api.Loader): "end frame data is missing..") return - _set_frame_range(start, end) + lib.update_frame_range(start, end) class FusionSetFrameRangeWithHandlesLoader(api.Loader): @@ -84,6 +55,8 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): def load(self, context, name, namespace, data): + from colorbleed.fusion import lib + version = context['version'] version_data = version.get("data", {}) @@ -100,4 +73,4 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): start -= handles end += handles - _set_frame_range(start, end) + lib.update_frame_range(start, end) From 81b2768bd29452a2fd436a988248f1aae89bc17e Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 8 Mar 2018 15:03:41 +0100 Subject: [PATCH 0705/1249] Corrected return type in docstrings --- colorbleed/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 9084410d18..14ad6884b5 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -162,7 +162,7 @@ def switch_item(container, representation_name (str): name of the representation Returns: - None + dict """ From 02b24d3ce8a3e8741b945f0073e2427985d65792 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 8 Mar 2018 18:09:08 +0100 Subject: [PATCH 0706/1249] Scrip to automate shot switching --- colorbleed/scripts/fusion_switch_shot.py | 215 +++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 colorbleed/scripts/fusion_switch_shot.py diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py new file mode 100644 index 0000000000..6119862b42 --- /dev/null +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -0,0 +1,215 @@ +import os +import sys +import logging + +# Pipeline imports +from avalon import api, io, pipeline +import avalon.fusion + +# Config imports +import colorbleed.lib as colorbleed +import colorbleed.fusion.lib as fusion_lib + +log = logging.getLogger("Update Slap Comp") + +self = sys.modules[__name__] +self._project = None +fusion = None + + +def _get_work_folder(session): + """Convenience function to get the work folder path of the current asset""" + + # Get new filename, create path based on asset and work template + template_work = self._project["config"]["template"]["work"] + work_path = pipeline._format_work_template(template_work, session) + + return os.path.normpath(work_path) + + +def _get_fusion_instance(): + fusion = getattr(sys.modules["__main__"], "fusion", None) + if fusion is None: + try: + # Support for FuScript.exe, BlackmagicFusion module for py2 only + import BlackmagicFusion as bmf + fusion = bmf.scriptapp("Fusion") + except ImportError: + raise RuntimeError("Could not find a Fusion instance") + return fusion + + +def _format_filepath(session): + + project = session["AVALON_PROJECT"] + asset = session["AVALON_ASSET"] + + # Save updated slap comp + work_path = _get_work_folder(session) + walk_to_dir = os.path.join(work_path, "scenes", "slapcomp") + slapcomp_dir = os.path.abspath(walk_to_dir) + + # Ensure destination exists + if not os.path.isdir(slapcomp_dir): + log.warning("Folder did not exist, creating folder structure") + os.makedirs(slapcomp_dir) + + # Compute output path + new_filename = "{}_{}_slapcomp_v001.comp".format(project, asset) + new_filepath = os.path.join(slapcomp_dir, new_filename) + + # Create new unqiue filepath + if os.path.exists(new_filepath): + new_filepath = colorbleed.version_up(new_filepath) + + return new_filepath + + +def _update_savers(comp, session): + """Update all savers of the current comp to ensure the output is correct + + Args: + comp (object): current comp instance + asset (dict): asset document of the asset to update TO + + Returns: + None + """ + + new_work = _get_work_folder(session) + # TODO + renders = os.path.join(new_work, "renders", "?") + + comp.Print("New renders to: %s\n" % renders) + + with avalon.fusion.comp_lock_and_undo_chunk(comp): + savers = comp.GetToolList(False, "Saver").values() + for saver in savers: + filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] + filename = os.path.basename(filepath) + new_path = os.path.join(renders, filename) + saver["Clip"] = new_path + + +def update_frame_range(comp, representations): + """Update the frame range of the comp and render length + + The start and end frame are based on the lowest start frame and the highest + end frame + + Args: + comp (object): current focused comp + representations (list) collection of dicts + + Returns: + None + + """ + + version_ids = [r["parent"] for r in representations] + versions = io.find({"type": "version", "_id": {"$in": version_ids}}) + versions = list(versions) + + start = min(v["data"]["startFrame"] for v in versions) + end = max(v["data"]["endFrame"] for v in versions) + + fusion_lib.update_frame_range(start, end, comp=comp) + + +def switch(filepath, asset_name, new=True, fusion=None): + """Switch the current containers of the file to the other asset (shot) + + Args: + filepath (str): file path of the comp file + asset_name (str): name of the asset (shot) + new (bool): Save updated comp under a different name + fusion (object, Optional): pass on the fusion instance + + Returns: + comp path (str): new filepath of the updated comp + + """ + + # Ensure filename is absolute + if not os.path.abspath(filepath): + filepath = os.path.abspath(filepath) + + # Get current project + self._project = io.find_one({"type": "project", + "name": api.Session["AVALON_PROJECT"]}) + + # Assert asset name exists + # It is better to do this here then to wait till switch_shot does it + asset = io.find_one({"type": "asset", "name": asset_name}) + assert asset, "Could not find '%s' in the database" % asset_name + + # Go to comp + if fusion is None: + fusion = _get_fusion_instance() + + current_comp = fusion.LoadComp(filepath) + assert current_comp is not None, "Fusion could not load '%s'" % filepath + + host = api.registered_host() + assert host is not None, "No host found! This is a bug" + containers = list(host.ls()) + assert containers, "Nothing to update" + + representations = [] + for container in containers: + try: + representation = colorbleed.switch_item(container, + asset_name=asset_name) + representations.append(representation) + current_comp.Print(str(representation["_id"]) + "\n") + except Exception as e: + current_comp.Print("Error in switching! %s\n" % e.message) + + message = "Switched %i Loaders of the %i\n" % (len(representations), + len(containers)) + current_comp.Print(message) + + # Build the session to switch to + switch_to_session = api.Session.copy() + switch_to_session["AVALON_ASSET"] = asset['name'] + + if new: + comp_path = _format_filepath(switch_to_session) + + # Update savers output based on new session + _update_savers(current_comp, switch_to_session) + else: + comp_path = colorbleed.version_up(filepath) + + current_comp.Print(comp_path) + + current_comp.Print("\nUpdating frame range") + update_frame_range(current_comp, representations) + + current_comp.Save(comp_path) + + return comp_path + + +if __name__ == '__main__': + + import argparse + + parser = argparse.ArgumentParser(description="Switch to a shot within an" + "existing comp file") + + parser.add_argument("--file_path", + type=str, + default=True, + help="File path of the comp to use") + parser.add_argument("--asset_name", + type=str, + default=True, + help="Name of the asset (shot) to switch") + + args, unknown = parser.parse_args() + + api.install(avalon.fusion) + switch(args.file_path, args.asset_name) + + sys.exit(0) From 73f2e404d0f556877400061443315e2675abaec6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Mar 2018 16:02:58 +0100 Subject: [PATCH 0707/1249] Fix MOD-6: Invalid shader connections brings along shader with model publish --- .../validate_mesh_shader_connections.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py new file mode 100644 index 0000000000..5f44bf1f9f --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py @@ -0,0 +1,111 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +def pairs(iterable): + """Iterate over iterable per group of two""" + a = iter(iterable) + for i, y in zip(a, a): + yield i, y + + +def get_invalid_sets(shape): + """Get sets that are considered related but do not contain the shape. + + In some scenarios Maya keeps connections to multiple shaders + even if just a single one is assigned on the full object. + + These are related sets returned by `maya.cmds.listSets` that don't + actually have the shape as member. + + """ + + invalid = [] + sets = cmds.listSets(object=shape, t=1, extendToShape=False) + for s in sets: + members = cmds.sets(s, query=True, nodesOnly=True) + if not members: + invalid.append(s) + continue + + members = set(cmds.ls(members, long=True)) + if shape not in members: + invalid.append(s) + + return invalid + + +def disconnect(node_a, node_b): + """Remove all connections between node a and b.""" + + # Disconnect outputs + outputs = cmds.listConnections(node_a, + plugs=True, + connections=True, + source=False, + destination=True) + for output, destination in pairs(outputs): + if destination.split(".", 1)[0] == node_b: + cmds.disconnectAttr(output, destination) + + # Disconnect inputs + inputs = cmds.listConnections(node_a, + plugs=True, + connections=True, + source=True, + destination=False) + for input, source in pairs(inputs): + if source.split(".", 1)[0] == node_b: + cmds.disconnectAttr(source, input) + + +class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): + """Ensure mesh shading engine connections are valid. + + In some scenarios Maya keeps connections to multiple shaders even if just + a single one is assigned on the shape. + + These are related sets returned by `maya.cmds.listSets` that don't + actually have the shape as member. + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + label = "Mesh Shader Connections" + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Shapes found with invalid shader " + "connections: {0}".format(invalid)) + + @staticmethod + def get_invalid(instance): + + shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True) + shapes = cmds.ls(shapes, shapes=True, noIntermediate=True, long=True) + + invalid = [] + for shape in shapes: + if get_invalid_sets(shape): + invalid.append(shape) + + return invalid + + @classmethod + def repair(cls, instance): + + shapes = cls.get_invalid(instance) + for shape in shapes: + invalid_sets = get_invalid_sets(shape) + for set_node in invalid_sets: + disconnect(shape, set_node) From 4d975aab49c2ecc73d447186129de7f9cc553e6e Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 9 Mar 2018 16:55:18 +0100 Subject: [PATCH 0708/1249] Adds simple switch UI --- .../scripts/Comp/colorbleed/switch_ui.py | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 setup/fusion/scripts/Comp/colorbleed/switch_ui.py diff --git a/setup/fusion/scripts/Comp/colorbleed/switch_ui.py b/setup/fusion/scripts/Comp/colorbleed/switch_ui.py new file mode 100644 index 0000000000..1b0d4a1b82 --- /dev/null +++ b/setup/fusion/scripts/Comp/colorbleed/switch_ui.py @@ -0,0 +1,213 @@ +import os +import glob +import logging + +import avalon.io as io +import avalon.api as api +import avalon.pipeline as pipeline +import avalon.fusion +import avalon.style as style +from avalon.vendor.Qt import QtWidgets, QtCore +from avalon.vendor import qtawesome as qta + + +log = logging.getLogger("Fusion Switch Shot") + + +class App(QtWidgets.QWidget): + + def __init__(self, parent=None): + + ################################################ + # |---------------------| |------------------| # + # |Comp | |Asset | # + # |[..][ v]| |[ v]| # + # |---------------------| |------------------| # + # | Update existing comp [ ] | # + # |------------------------------------------| # + # | Switch | # + # |------------------------------------------| # + ################################################ + + QtWidgets.QWidget.__init__(self, parent) + + layout = QtWidgets.QVBoxLayout() + + # Comp related input + comp_hlayout = QtWidgets.QHBoxLayout() + comp_label = QtWidgets.QLabel("Comp file") + comp_label.setFixedWidth(50) + comp_box = QtWidgets.QComboBox() + + button_icon = qta.icon("fa.folder", color="white") + open_from_dir = QtWidgets.QPushButton() + open_from_dir.setIcon(button_icon) + + comp_box.setFixedHeight(25) + open_from_dir.setFixedWidth(25) + open_from_dir.setFixedHeight(25) + + comp_hlayout.addWidget(comp_label) + comp_hlayout.addWidget(comp_box) + comp_hlayout.addWidget(open_from_dir) + + # Asset related input + asset_hlayout = QtWidgets.QHBoxLayout() + asset_label = QtWidgets.QLabel("Shot") + asset_label.setFixedWidth(50) + + asset_box = QtWidgets.QComboBox() + asset_box.setLineEdit(QtWidgets.QLineEdit()) + asset_box.setFixedHeight(25) + + refresh_icon = qta.icon("fa.refresh", color="white") + refresh_btn = QtWidgets.QPushButton() + refresh_btn.setIcon(refresh_icon) + + asset_box.setFixedHeight(25) + refresh_btn.setFixedWidth(25) + refresh_btn.setFixedHeight(25) + + asset_hlayout.addWidget(asset_label) + asset_hlayout.addWidget(asset_box) + asset_hlayout.addWidget(refresh_btn) + + # Options + options = QtWidgets.QHBoxLayout() + options.setAlignment(QtCore.Qt.AlignLeft) + + update_label = QtWidgets.QLabel("Update version") + update_check = QtWidgets.QCheckBox() + update_check.setChecked(False) + update_check.setToolTip("If checked it versions up the selected comp " + "file with else it will create a new slapcomp " + "file based on the selected comp file") + + current_comp_check = QtWidgets.QCheckBox() + current_comp_check.setChecked(True) + current_comp_label = QtWidgets.QLabel("Use current comp") + + options.addWidget(update_label) + options.addWidget(update_check) + + options.addWidget(current_comp_label) + options.addWidget(current_comp_check) + + accept_btn = QtWidgets.QPushButton("Switch") + + layout.addLayout(options) + layout.addLayout(comp_hlayout) + layout.addLayout(asset_hlayout) + layout.addWidget(accept_btn) + + self._open_from_dir = open_from_dir + self._comps = comp_box + self._assets = asset_box + self._update = update_check + self._use_current = current_comp_check + self._accept_btn = accept_btn + self._refresh_btn = refresh_btn + + self.setWindowTitle("Fusion Switch Shot") + self.setLayout(layout) + + self.resize(260, 140) + self.setMinimumWidth(260) + self.setFixedHeight(140) + + self.connections() + + # Update ui to correct state + self._on_use_current_comp() + self._refresh() + + def connections(self): + self._use_current.clicked.connect(self._on_use_current_comp) + self._open_from_dir.clicked.connect(self._on_open_from_dir) + self._refresh_btn.clicked.connect(self._refresh) + self._accept_btn.clicked.connect(self._on_switch) + + def _on_use_current_comp(self): + state = self._use_current.isChecked() + self._open_from_dir.setEnabled(not state) + self._comps.setEnabled(not state) + + def _on_open_from_dir(self): + + start_dir = self._get_context_directory() + comp_file, _ = QtWidgets.QFileDialog.getOpenFileName( + self, "Choose comp", start_dir) + + if not comp_file: + return + + # Create completer + self.populate_comp_box([comp_file]) + self._refresh() + + def _refresh(self): + # Clear any existing items + self._assets.clear() + + asset_names = [a["name"] for a in self.collect_assets()] + completer = QtWidgets.QCompleter(asset_names) + + self._assets.setCompleter(completer) + self._assets.addItems(asset_names) + + def _on_switch(self): + + if not self._use_current.isChecked(): + file_name = self._comps.itemData(self._comps.currentIndex()) + else: + comp = avalon.fusion.get_current_comp() + file_name = comp.GetAttrs("COMPS_FileName") + + asset = self._assets.currentText() + new = not self._update.isChecked() + + import colorbleed.scripts.fusion_switch_shot as switch_shot + switch_shot.switch(file_name, asset, new) + + def _get_context_directory(self): + + project = io.find_one({"type": "project", + "name": api.Session["AVALON_PROJECT"]}, + projection={"config": True}) + + template = project["config"]["template"]["work"] + dir = pipeline._format_work_template(template, api.Session) + + return dir + + def collect_slap_comps(self, directory): + items = glob.glob("{}/*.comp".format(directory)) + return items + + def collect_assets(self): + return list(io.find({"type": "asset", "silo": "film"})) + + def populate_comp_box(self, files): + """Ensure we display the filename only but the path is stored as well + + Args: + files (list): list of full file path [path/to/item/item.ext,] + + Returns: + None + """ + + for f in files: + filename = os.path.basename(f) + self._comps.addItem(filename, userData=f) + + +if __name__ == '__main__': + import sys + api.install(avalon.fusion) + + app = QtWidgets.QApplication(sys.argv) + window = App() + window.setStyleSheet(style.load_stylesheet()) + window.show() + sys.exit(app.exec_()) From 16bb134a7f4fc88827190125a1250727de65481d Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 9 Mar 2018 16:56:55 +0100 Subject: [PATCH 0709/1249] renamed module, removed unused kwarg --- colorbleed/scripts/fusion_switch_shot.py | 6 +- colorbleed/scripts/slapcomp_switch.py | 87 ------------------------ 2 files changed, 2 insertions(+), 91 deletions(-) delete mode 100644 colorbleed/scripts/slapcomp_switch.py diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 6119862b42..43ad149ca5 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -116,7 +116,7 @@ def update_frame_range(comp, representations): fusion_lib.update_frame_range(start, end, comp=comp) -def switch(filepath, asset_name, new=True, fusion=None): +def switch(filepath, asset_name, new=True): """Switch the current containers of the file to the other asset (shot) Args: @@ -144,14 +144,12 @@ def switch(filepath, asset_name, new=True, fusion=None): assert asset, "Could not find '%s' in the database" % asset_name # Go to comp - if fusion is None: - fusion = _get_fusion_instance() + fusion = _get_fusion_instance() current_comp = fusion.LoadComp(filepath) assert current_comp is not None, "Fusion could not load '%s'" % filepath host = api.registered_host() - assert host is not None, "No host found! This is a bug" containers = list(host.ls()) assert containers, "Nothing to update" diff --git a/colorbleed/scripts/slapcomp_switch.py b/colorbleed/scripts/slapcomp_switch.py deleted file mode 100644 index 95270259aa..0000000000 --- a/colorbleed/scripts/slapcomp_switch.py +++ /dev/null @@ -1,87 +0,0 @@ -import logging -import argparse - -import avalon.io as io -import avalon.api as api -import avalon.fusion - - -log = logging.getLogger("UpdateSlapComp") - - -def switch(shot): - """Update comp loaders through the containers - - Args: - data (dict): collection if {asset: [subset, subset]} - """ - - host = api.registered_host() - containers = list(host.ls()) - - targets = [c["name"] for c in containers] - - representations = {} - - asset = io.find_one({"type": "asset", "name": shot}) - assert asset, ("Could not find asset in the database with the name " - "'%s'" % shot) - - subsets = io.find({"type": "subset", - "name": {"$in": targets}, - "parent": asset["_id"]}) - - if len(subsets) != len(targets): - log.warning("Could not find the same amount of subsets in '%s'" % shot) - - for subset in subsets: - version = io.find_one({"type": "version", - "parent": subset["_id"]}, - sort=[('name', -1)]) - - if not version: - log.error("Could not find a verison for {}.{}".format( - asset["name"], subset["name"] - )) - continue - - representation = io.find_one({"type": "representation", - "parent": version["_id"]}) - - representations[subset["name"]] = representation - - count = 0 - for i, container in enumerate(containers): - subsetname = container["name"] - if subsetname not in representations: - log.error("Subset '%s' not found in comp, skipping" % subsetname) - continue - - representation = representations[container["name"]] - api.switch(container, representation) - count += 1 - - log.info("Updated %i items" % count) - - -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - - parser.add_argument("--shot", - help="Shotname to update the current comp to") - - parser.add_argument("--targets", - optional=True, - help="A list of target to update with given data") - - args = parser.parse_known_args() - if not args.shot: - raise RuntimeError("No data given, cannot update slap comp") - - if not isinstance(args.shot, dict): - raise ValueError("Expecting data in the form of a dict, got " - "%s" % type(args.shot)) - - api.install(avalon.fusion) - switch(shot=args.shot) From ade5b04091b20d27a5f0f35a8a9ccee7f5d1096f Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 11:57:10 +0100 Subject: [PATCH 0710/1249] Logic to format version folder --- colorbleed/scripts/fusion_switch_shot.py | 39 ++++++++++++++++++++---- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 43ad149ca5..4c215c65c0 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -1,4 +1,5 @@ import os +import re import sys import logging @@ -14,7 +15,33 @@ log = logging.getLogger("Update Slap Comp") self = sys.modules[__name__] self._project = None -fusion = None + + +def _format_version_folder(folder): + """Format a version folder based on the filepath + + Assumption here is made that, if the path does not exists the folder + will be "v001" + + Args: + folder: file path to a folder + + Returns: + str: new version folder name + """ + + if not os.path.isdir(folder): + return "v001" + + re_version = re.compile("v\d+") + + content = sorted(os.listdir(folder)) + versions = [i for i in content if re_version.match(i)] + + new_version = int(max(versions)[1:]) + 1 # ensure the "v" is not included + version_folder = "v{:03d}".format(new_version) + + return version_folder def _get_work_folder(session): @@ -70,15 +97,16 @@ def _update_savers(comp, session): Args: comp (object): current comp instance - asset (dict): asset document of the asset to update TO + session (dict): the current Avalon session Returns: None """ new_work = _get_work_folder(session) - # TODO - renders = os.path.join(new_work, "renders", "?") + renders = os.path.join(new_work, "renders") + version_folder = _format_version_folder(renders) + renders_version = os.path.join(renders, version_folder) comp.Print("New renders to: %s\n" % renders) @@ -87,7 +115,7 @@ def _update_savers(comp, session): for saver in savers: filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0] filename = os.path.basename(filepath) - new_path = os.path.join(renders, filename) + new_path = os.path.join(renders_version, filename) saver["Clip"] = new_path @@ -123,7 +151,6 @@ def switch(filepath, asset_name, new=True): filepath (str): file path of the comp file asset_name (str): name of the asset (shot) new (bool): Save updated comp under a different name - fusion (object, Optional): pass on the fusion instance Returns: comp path (str): new filepath of the updated comp From 8c359655c1d5ed878826ddf20e432123a038bfcf Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 12:06:11 +0100 Subject: [PATCH 0711/1249] Ensure only dirs are filtered --- colorbleed/scripts/fusion_switch_shot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 4c215c65c0..928be6309a 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -36,7 +36,7 @@ def _format_version_folder(folder): re_version = re.compile("v\d+") content = sorted(os.listdir(folder)) - versions = [i for i in content if re_version.match(i)] + versions = [i for i in content if os.path.isdir(i) and re_version.match(i)] new_version = int(max(versions)[1:]) + 1 # ensure the "v" is not included version_folder = "v{:03d}".format(new_version) From bf2df9a4500e62ea5cab2ac9215c1af00b9a613c Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 15:18:27 +0100 Subject: [PATCH 0712/1249] Explicitly get "v###", exclude any mutations "v###_a" --- colorbleed/scripts/fusion_switch_shot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 928be6309a..ca5b859f73 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -33,7 +33,7 @@ def _format_version_folder(folder): if not os.path.isdir(folder): return "v001" - re_version = re.compile("v\d+") + re_version = re.compile("v\d+$") content = sorted(os.listdir(folder)) versions = [i for i in content if os.path.isdir(i) and re_version.match(i)] From 4c90aeda3a27f609485b5a362c1d11266bef3640 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 15:29:15 +0100 Subject: [PATCH 0713/1249] improved _format_version_folder --- colorbleed/scripts/fusion_switch_shot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index ca5b859f73..1a7e6bfed6 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -34,9 +34,8 @@ def _format_version_folder(folder): return "v001" re_version = re.compile("v\d+$") - - content = sorted(os.listdir(folder)) - versions = [i for i in content if os.path.isdir(i) and re_version.match(i)] + versions = [i for i in os.listdir(folder) if os.path.isdir(i) + and re_version.match(i)] new_version = int(max(versions)[1:]) + 1 # ensure the "v" is not included version_folder = "v{:03d}".format(new_version) From 6fbdf46e2474d115839f6cf001875176a5288e4d Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 17:11:16 +0100 Subject: [PATCH 0714/1249] improved _format_version_folder, use current (active) comp by default --- colorbleed/scripts/fusion_switch_shot.py | 31 ++++++++++++++---------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 1a7e6bfed6..07bbafa057 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -30,14 +30,15 @@ def _format_version_folder(folder): str: new version folder name """ - if not os.path.isdir(folder): - return "v001" + new_version = 1 + if os.path.isdir(folder): + re_version = re.compile("v\d+$") + versions = [i for i in os.listdir(folder) if os.path.isdir(i) + and re_version.match(i)] + if versions: + # ensure the "v" is not included + new_version = int(max(versions)[1:]) + 1 - re_version = re.compile("v\d+$") - versions = [i for i in os.listdir(folder) if os.path.isdir(i) - and re_version.match(i)] - - new_version = int(max(versions)[1:]) + 1 # ensure the "v" is not included version_folder = "v{:03d}".format(new_version) return version_folder @@ -143,7 +144,7 @@ def update_frame_range(comp, representations): fusion_lib.update_frame_range(start, end, comp=comp) -def switch(filepath, asset_name, new=True): +def switch(asset_name, filepath=None, new=True): """Switch the current containers of the file to the other asset (shot) Args: @@ -170,10 +171,13 @@ def switch(filepath, asset_name, new=True): assert asset, "Could not find '%s' in the database" % asset_name # Go to comp - fusion = _get_fusion_instance() - - current_comp = fusion.LoadComp(filepath) - assert current_comp is not None, "Fusion could not load '%s'" % filepath + if not filepath: + current_comp = avalon.fusion.get_current_comp() + assert current_comp is not None, "Could not find current comp" + else: + fusion = _get_fusion_instance() + current_comp = fusion.LoadComp(filepath, quiet=True) + assert current_comp is not None, "Fusion could not load '%s'" % filepath host = api.registered_host() containers = list(host.ls()) @@ -226,6 +230,7 @@ if __name__ == '__main__': type=str, default=True, help="File path of the comp to use") + parser.add_argument("--asset_name", type=str, default=True, @@ -234,6 +239,6 @@ if __name__ == '__main__': args, unknown = parser.parse_args() api.install(avalon.fusion) - switch(args.file_path, args.asset_name) + switch(args.asset_name, args.file_path) sys.exit(0) From ce57aa9949433f378beebca448ca3a24374a3ff9 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 12 Mar 2018 17:15:00 +0100 Subject: [PATCH 0715/1249] Removed update version check, updated switch call --- setup/fusion/scripts/Comp/colorbleed/switch_ui.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/setup/fusion/scripts/Comp/colorbleed/switch_ui.py b/setup/fusion/scripts/Comp/colorbleed/switch_ui.py index 1b0d4a1b82..8f1466abe0 100644 --- a/setup/fusion/scripts/Comp/colorbleed/switch_ui.py +++ b/setup/fusion/scripts/Comp/colorbleed/switch_ui.py @@ -76,20 +76,10 @@ class App(QtWidgets.QWidget): options = QtWidgets.QHBoxLayout() options.setAlignment(QtCore.Qt.AlignLeft) - update_label = QtWidgets.QLabel("Update version") - update_check = QtWidgets.QCheckBox() - update_check.setChecked(False) - update_check.setToolTip("If checked it versions up the selected comp " - "file with else it will create a new slapcomp " - "file based on the selected comp file") - current_comp_check = QtWidgets.QCheckBox() current_comp_check.setChecked(True) current_comp_label = QtWidgets.QLabel("Use current comp") - options.addWidget(update_label) - options.addWidget(update_check) - options.addWidget(current_comp_label) options.addWidget(current_comp_check) @@ -103,7 +93,6 @@ class App(QtWidgets.QWidget): self._open_from_dir = open_from_dir self._comps = comp_box self._assets = asset_box - self._update = update_check self._use_current = current_comp_check self._accept_btn = accept_btn self._refresh_btn = refresh_btn @@ -164,10 +153,9 @@ class App(QtWidgets.QWidget): file_name = comp.GetAttrs("COMPS_FileName") asset = self._assets.currentText() - new = not self._update.isChecked() import colorbleed.scripts.fusion_switch_shot as switch_shot - switch_shot.switch(file_name, asset, new) + switch_shot.switch(asset_name=asset, filepath=file_name, new=True) def _get_context_directory(self): From 77706d635a00f4eac99896736dda34c73959ad4c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Mar 2018 13:25:52 +0100 Subject: [PATCH 0716/1249] Fix publish job state being collected correctly for dependent job publish --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index c196415a0c..f1495a0b89 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -133,6 +133,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Suspend publish job state = "Suspended" if attributes["suspendPublishJob"] else "Active" - options["suspendPublishJob"] = state + options["publishJobState"] = state return options From 48cee11492602296c505ba05aa5e916424be51c3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 16 Mar 2018 13:02:13 +0100 Subject: [PATCH 0717/1249] Implement on instanceToggled callback in Fusion install to toggle saver passthrough for Pyblish interface --- colorbleed/fusion/__init__.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index 36270980bf..86ee04a71a 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -18,9 +18,39 @@ def install(): avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + def uninstall(): print("Deregistering Fusion plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) + + pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + from avalon.fusion import comp_lock_and_undo_chunk + + comp = instance.context.data.get("currentComp") + if not comp: + return + + savers = [tool for tool in instance if + getattr(tool, "ID", None) == "Saver"] + if not savers: + return + + # Whether instances should be passthrough based on new value + passthrough = not new_value + with comp_lock_and_undo_chunk(comp, + undo_queue_name="Change instance " + "active state"): + for tool in savers: + attrs = tool.GetAttrs() + current = attrs["TOOLB_PassThrough"] + if current != passthrough: + tool.SetAttrs({"TOOLB_PassThrough": passthrough}) From 69d61d6f3c4edff8e8bb8ba8ebd4218a38de7991 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 20 Mar 2018 18:10:27 +0100 Subject: [PATCH 0718/1249] Implement MOD-7: Allow _OSD suffix in model --- .../maya/publish/validate_transform_naming_suffix.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py index 2ec2cfd9da..51d63ad505 100644 --- a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py +++ b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py @@ -4,7 +4,7 @@ import pyblish.api import colorbleed.api -SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP"], +SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP", "_OSD"], 'nurbsCurve': ["_CRV"], 'nurbsSurface': ["_NRB"], None: ['_GRP']} @@ -12,11 +12,15 @@ SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP"], ALLOW_IF_NOT_IN_SUFFIX_TABLE = True -class ValidateTransformNamingSuffix(pyblish.api.Validator): +class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): """Validates transform suffix based on the type of its children shapes. Suffices must be: - - mesh: _GEO or _GES or _GEP + - mesh: + _GEO (regular geometry) + _GES (geometry to be smoothed at render) + _GEP (proxy geometry; usually not to be rendered) + _OSD (open subdiv smooth at rendertime) - nurbsCurve: _CRV - nurbsSurface: _NRB - null/group: _GRP From d2f95c80d085bebc5b0c3830e6baac1ce32ac816 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Mar 2018 10:11:25 +0100 Subject: [PATCH 0719/1249] Fix separateMeshPerShader command - Also remove redundant empty command string for Others/Hair menu --- colorbleed/maya/menu.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9748d257f3..9141b82ac6 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -104,7 +104,7 @@ }, { "type": "action", - "command": "", + "command": "$COLORBLEED_SCRIPTS\\modeling\\separateMeshPerShader.py", "sourcetype": "file", "tags": [ "modeling", @@ -1709,7 +1709,6 @@ }, { "type": "menu", - "command": "", "title": "Hair", "tooltip": "", "items": [ From b7b10a7cb0357821d521593c904b84a2e1232fe8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Mar 2018 13:56:20 +0100 Subject: [PATCH 0720/1249] Fix FUS-35: Retrieve Deadline user with both Maya and Fusion jobs --- .../plugins/{maya => }/publish/collect_deadline_user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename colorbleed/plugins/{maya => }/publish/collect_deadline_user.py (93%) diff --git a/colorbleed/plugins/maya/publish/collect_deadline_user.py b/colorbleed/plugins/publish/collect_deadline_user.py similarity index 93% rename from colorbleed/plugins/maya/publish/collect_deadline_user.py rename to colorbleed/plugins/publish/collect_deadline_user.py index 34f6f4652c..6197d5aa64 100644 --- a/colorbleed/plugins/maya/publish/collect_deadline_user.py +++ b/colorbleed/plugins/publish/collect_deadline_user.py @@ -36,8 +36,8 @@ class CollectDeadlineUser(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.499 label = "Deadline User" - hosts = ['maya'] - families = ["colorbleed.renderlayer"] + hosts = ['maya', 'fusion'] + families = ["colorbleed.renderlayer", "colorbleed.saver.deadline"] def process(self, context): """Inject the current working file""" From 42fec43fb7cc785cbd99ae715976f8ba65e708b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 Mar 2018 14:48:19 +0100 Subject: [PATCH 0721/1249] Remove debugging print statement --- colorbleed/plugins/publish/collect_deadline_user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/publish/collect_deadline_user.py b/colorbleed/plugins/publish/collect_deadline_user.py index 6197d5aa64..4f7af94419 100644 --- a/colorbleed/plugins/publish/collect_deadline_user.py +++ b/colorbleed/plugins/publish/collect_deadline_user.py @@ -20,8 +20,6 @@ def deadline_command(cmd): query = (executable, cmd) - print executable - process = subprocess.Popen(query, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, From 8fc5bfb7990c8b49dff6a256c03b94e12441e3fe Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 23 Mar 2018 15:12:44 +0100 Subject: [PATCH 0722/1249] Introduce ValidateNodeIdsInDatabase --- .../publish/validate_node_ids_in_database.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_node_ids_in_database.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py new file mode 100644 index 0000000000..855e6aa4a1 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -0,0 +1,55 @@ +import pyblish.api + +import avalon.io as io + +import colorbleed.api +from colorbleed.maya import lib + + +class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): + """Validate if the CB Id is related to an asset in the database + + All nodes with the `cbId` attribute will be validated to ensure that + the loaded asset in the scene is related to the current project. + + Tip: If there is an asset which is being reused from a different project + please ensure the asset is republished in the new project + + """ + + order = colorbleed.api.ValidatePipelineOrder + label = 'Node Ids in Database' + hosts = ['maya'] + families = ["*"] + + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found asset IDs which are not related to" + "current project in instance: " + "`%s`" % instance.name) + + @classmethod + def get_invalid(cls, instance): + + invalid = [] + + # Get all id required nodes + id_required_nodes = lib.get_id_required_nodes(referenced_nodes=True, + nodes=instance[:]) + + # check ids against database ids + db_asset_ids = io.find({"type": "asset"}).distinct("_id") + db_asset_ids = set(str(i) for i in db_asset_ids) + + # Get all asset IDs + for node in id_required_nodes: + cb_id = lib.get_id(node) + asset_id = cb_id.split(":", 1)[0] + if asset_id not in db_asset_ids: + cls.log.error("`%s` has unassociated assetfd ID" % node) + invalid.append(node) + + return invalid From f7600139cfb531bc59e39c916bd816fec8b66e5c Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 23 Mar 2018 15:41:15 +0100 Subject: [PATCH 0723/1249] Removed typo --- .../plugins/maya/publish/validate_node_ids_in_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 855e6aa4a1..73cba004bb 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -49,7 +49,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): cb_id = lib.get_id(node) asset_id = cb_id.split(":", 1)[0] if asset_id not in db_asset_ids: - cls.log.error("`%s` has unassociated assetfd ID" % node) + cls.log.error("`%s` has unassociated asset ID" % node) invalid.append(node) return invalid From 3bba14ea37130f6cb140379335923d28efba9cf7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Mar 2018 20:44:49 +0100 Subject: [PATCH 0724/1249] Fix typo (missing space) --- .../plugins/maya/publish/validate_node_ids_in_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 73cba004bb..7d13cf021f 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -27,7 +27,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found asset IDs which are not related to" + raise RuntimeError("Found asset IDs which are not related to " "current project in instance: " "`%s`" % instance.name) From f24a4d19078ccb1fdb8d275060fe8ec98745d460 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Mar 2018 20:47:24 +0100 Subject: [PATCH 0725/1249] Fix `cmds.lockNode` raising error on empty nodes list --- colorbleed/maya/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index d6df4fae52..86db5e6b7e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -659,6 +659,8 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): nodes |= set(transforms) nodes -= ignore # Remove the ignored nodes + if not nodes: + return [] # Avoid locked nodes nodes_list = list(nodes) From f1b57f077cc5ec21f1f0248cd27c6890c8fd5d24 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Mar 2018 20:54:33 +0100 Subject: [PATCH 0726/1249] Return the empty nodes set instead of list since return type is set --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 86db5e6b7e..93ede7fe58 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -660,7 +660,7 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): nodes -= ignore # Remove the ignored nodes if not nodes: - return [] + return nodes # Avoid locked nodes nodes_list = list(nodes) From 5b5e6179614cf5218d4f1dccda7df8a1ef793284 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 27 Mar 2018 15:03:29 +0200 Subject: [PATCH 0727/1249] Ignore nodes without id --- .../plugins/maya/publish/validate_node_ids_in_database.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 7d13cf021f..0493779da2 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -47,6 +47,11 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): # Get all asset IDs for node in id_required_nodes: cb_id = lib.get_id(node) + + # Ignore nodes without id, those are validated elsewhere + if not cb_id: + continue + asset_id = cb_id.split(":", 1)[0] if asset_id not in db_asset_ids: cls.log.error("`%s` has unassociated asset ID" % node) From 2fdd3c8c64b2c294ad1c28adb1b1318435cfae19 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 27 Mar 2018 16:23:24 +0200 Subject: [PATCH 0728/1249] Added register_launcher_action --- colorbleed/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index f29597a297..6a1699a030 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,5 +1,6 @@ import os from pyblish import api as pyblish +from avalon import api, pipeline PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -16,3 +17,11 @@ def install(): def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) + +def register_launcher_actions(): + """Register specific actions which should be accessible in the launcher""" + + # Register fusion actions + from .fusion import rendernode + pipeline.register_plugin(api.Action, rendernode.FusionRenderNode) + From cc7124a1ba2328d1071fe0be7746874b12bdcd86 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 27 Mar 2018 16:24:33 +0200 Subject: [PATCH 0729/1249] FusionRenderNode action for launcher --- colorbleed/fusion/rendernode.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 colorbleed/fusion/rendernode.py diff --git a/colorbleed/fusion/rendernode.py b/colorbleed/fusion/rendernode.py new file mode 100644 index 0000000000..21ed091ad7 --- /dev/null +++ b/colorbleed/fusion/rendernode.py @@ -0,0 +1,23 @@ +import os +from avalon import api, lib + +# TODO: get fusion render node exe from somewhere else +FRN = "C:/Program Files/Blackmagic Design/Fusion Render Node 9/FusionRenderNode.exe" + + +class FusionRenderNode(api.Action): + + name = "fusionrendernode" + label = "F9 Render Node" + icon = "object-group" + order = 997 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered""" + return lib.launch(executable=FRN, + args=[], + environment=os.environ.update(session)) From 7eb58a1525488cc903c86fe91b0dead05ede145e Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 27 Mar 2018 16:23:24 +0200 Subject: [PATCH 0730/1249] Added register_launcher_action (cherry picked from commit 2fdd3c8) --- colorbleed/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index f29597a297..6a1699a030 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,5 +1,6 @@ import os from pyblish import api as pyblish +from avalon import api, pipeline PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -16,3 +17,11 @@ def install(): def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) + +def register_launcher_actions(): + """Register specific actions which should be accessible in the launcher""" + + # Register fusion actions + from .fusion import rendernode + pipeline.register_plugin(api.Action, rendernode.FusionRenderNode) + From b46b5e4cba707c7b764dbb7bb0d29f988d7d291f Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 27 Mar 2018 16:24:33 +0200 Subject: [PATCH 0731/1249] FusionRenderNode action for launcher (cherry picked from commit cc7124a) --- colorbleed/fusion/rendernode.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 colorbleed/fusion/rendernode.py diff --git a/colorbleed/fusion/rendernode.py b/colorbleed/fusion/rendernode.py new file mode 100644 index 0000000000..21ed091ad7 --- /dev/null +++ b/colorbleed/fusion/rendernode.py @@ -0,0 +1,23 @@ +import os +from avalon import api, lib + +# TODO: get fusion render node exe from somewhere else +FRN = "C:/Program Files/Blackmagic Design/Fusion Render Node 9/FusionRenderNode.exe" + + +class FusionRenderNode(api.Action): + + name = "fusionrendernode" + label = "F9 Render Node" + icon = "object-group" + order = 997 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered""" + return lib.launch(executable=FRN, + args=[], + environment=os.environ.update(session)) From 4f3c02c07ca0b9e652a979f3a7dc3e5ebbf3cbae Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 28 Mar 2018 12:26:15 +0200 Subject: [PATCH 0732/1249] only show rusion render node at root of launcher --- colorbleed/fusion/rendernode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/fusion/rendernode.py b/colorbleed/fusion/rendernode.py index 21ed091ad7..ae823427a5 100644 --- a/colorbleed/fusion/rendernode.py +++ b/colorbleed/fusion/rendernode.py @@ -14,6 +14,8 @@ class FusionRenderNode(api.Action): def is_compatible(self, session): """Return whether the action is compatible with the session""" + if "AVALON_PROJECT" in session: + return False return True def process(self, session, **kwargs): From f8869e4f68a04958186634e3cf239a982a0abff6 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 30 Mar 2018 11:42:25 +0200 Subject: [PATCH 0733/1249] register actions for launcher --- colorbleed/config_actions.py | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 colorbleed/config_actions.py diff --git a/colorbleed/config_actions.py b/colorbleed/config_actions.py new file mode 100644 index 0000000000..c5ca3f8573 --- /dev/null +++ b/colorbleed/config_actions.py @@ -0,0 +1,44 @@ +import os +from avalon import api, lib, pipeline + + +class FusionRenderNode(api.Action): + + name = "fusionrendernode9" + label = "F9 Render Node" + icon = "object-group" + order = 997 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + if "AVALON_PROJECT" in session: + return False + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered + + Args: + session (dict): environment dictionary + + Returns: + Popen instance of newly spawned process + + """ + + # Update environment with session + env = os.environ.copy() + env.update(session) + + # Get executable by na.e + app = lib.get_application(self.name) + executable = lib.which(app["executable"]) + + return lib.launch(executable=executable, args=[], environment=env) + + +def register_actions(): + """Register specific actions which should be accessible in the launcher""" + + # Register fusion actions + pipeline.register_plugin(api.Action, FusionRenderNode) From c283f732c93edd8e91cb3f72409ab82beea868d2 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 30 Mar 2018 11:44:11 +0200 Subject: [PATCH 0734/1249] Register launcher action --- colorbleed/__init__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 6a1699a030..1e47cf9995 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,6 +1,7 @@ import os from pyblish import api as pyblish -from avalon import api, pipeline + +from .config_actions import register_actions PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -19,9 +20,5 @@ def uninstall(): def register_launcher_actions(): - """Register specific actions which should be accessible in the launcher""" - - # Register fusion actions - from .fusion import rendernode - pipeline.register_plugin(api.Action, rendernode.FusionRenderNode) - + """Convenience function to register the actions""" + register_actions() From 115299e6b5ac85dd320949f3b9676f98b1f8bdb7 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 30 Mar 2018 11:44:41 +0200 Subject: [PATCH 0735/1249] Removed module, content moved to config_actions --- colorbleed/fusion/rendernode.py | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 colorbleed/fusion/rendernode.py diff --git a/colorbleed/fusion/rendernode.py b/colorbleed/fusion/rendernode.py deleted file mode 100644 index ae823427a5..0000000000 --- a/colorbleed/fusion/rendernode.py +++ /dev/null @@ -1,25 +0,0 @@ -import os -from avalon import api, lib - -# TODO: get fusion render node exe from somewhere else -FRN = "C:/Program Files/Blackmagic Design/Fusion Render Node 9/FusionRenderNode.exe" - - -class FusionRenderNode(api.Action): - - name = "fusionrendernode" - label = "F9 Render Node" - icon = "object-group" - order = 997 - - def is_compatible(self, session): - """Return whether the action is compatible with the session""" - if "AVALON_PROJECT" in session: - return False - return True - - def process(self, session, **kwargs): - """Implement the behavior for when the action is triggered""" - return lib.launch(executable=FRN, - args=[], - environment=os.environ.update(session)) From 1ec79ac2fb71f199ae2a4ae4c5b03b3f60f9d3a4 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 30 Mar 2018 12:19:42 +0200 Subject: [PATCH 0736/1249] Renamed module, refactored register function --- colorbleed/{config_actions.py => launcher_actions.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename colorbleed/{config_actions.py => launcher_actions.py} (97%) diff --git a/colorbleed/config_actions.py b/colorbleed/launcher_actions.py similarity index 97% rename from colorbleed/config_actions.py rename to colorbleed/launcher_actions.py index c5ca3f8573..dd5cf2374e 100644 --- a/colorbleed/config_actions.py +++ b/colorbleed/launcher_actions.py @@ -37,7 +37,7 @@ class FusionRenderNode(api.Action): return lib.launch(executable=executable, args=[], environment=env) -def register_actions(): +def register_launcher_actions(): """Register specific actions which should be accessible in the launcher""" # Register fusion actions From ddf9d3d6aebe2bf0c994ba07652f6983e1bca99a Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 30 Mar 2018 12:20:47 +0200 Subject: [PATCH 0737/1249] Removed register function as import adds it to module attributes --- colorbleed/__init__.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 1e47cf9995..13aa87c153 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,7 +1,7 @@ import os from pyblish import api as pyblish -from .config_actions import register_actions +from .launcher_actions import register_launcher_actions PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -17,8 +17,3 @@ def install(): def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) - - -def register_launcher_actions(): - """Convenience function to register the actions""" - register_actions() From eca945dee4872ef41a5867a20971c00748832f08 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 30 Mar 2018 13:38:46 +0200 Subject: [PATCH 0738/1249] Remove force_load_deferred of Arnold at startup since it just introduced more .ai attributes in scenes and didn't fix the render issue --- colorbleed/maya/__init__.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 0fdad87655..3a890bb135 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -50,16 +50,6 @@ def uninstall(): def on_init(_): avalon.logger.info("Running callback on init..") - def force_load_deferred(plugin): - """Load a plug-in deferred so it runs after UI has initialized""" - try: - utils.executeDeferred(partial(cmds.loadPlugin, - plugin, - quiet=True)) - except RuntimeError, e: - log.warning("Can't load plug-in: " - "{0} - {1}".format(plugin, e)) - def safe_deferred(fn): """Execute deferred the function in a try-except""" @@ -77,7 +67,6 @@ def on_init(_): cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) - force_load_deferred("mtoa") from .customize import override_component_mask_commands safe_deferred(override_component_mask_commands) From c8e0ca500e5ea11d63ced33af7445aed3af70c44 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 30 Mar 2018 18:09:01 +0200 Subject: [PATCH 0739/1249] Fix FUS-25: Also check unchecked instances that their Saver state is correct in Comp --- .../publish/validate_saver_passthrough.py | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py index 4380f4e380..ca58eba5bd 100644 --- a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py +++ b/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py @@ -1,29 +1,44 @@ import pyblish.api -class ValidateSaverPassthrough(pyblish.api.InstancePlugin): +class ValidateSaverPassthrough(pyblish.api.ContextPlugin): """Validate saver passthrough is similar to Pyblish publish state""" order = pyblish.api.ValidatorOrder - label = "Validate Saver Active" + label = "Validate Saver Passthrough" families = ["colorbleed.saver"] hosts = ["fusion"] - @classmethod - def get_invalid(cls, instance): + def process(self, context): + + # Workaround for ContextPlugin always running, even if no instance + # is present with the family + instances = pyblish.api.instances_by_plugin(instances=list(context), + plugin=self) + if not instances: + self.log.debug("Ignoring plugin.. (bugfix)") + + invalid_instances = [] + for instance in instances: + invalid = self.is_invalid(instance) + if invalid: + invalid_instances.append(instance) + + if invalid_instances: + self.log.info("Reset pyblish to collect your current scene state, " + "that should fix error.") + raise RuntimeError("Invalid instances: " + "{0}".format(invalid_instances)) + + def is_invalid(self, instance): saver = instance[0] attr = saver.GetAttrs() active = not attr["TOOLB_PassThrough"] if active != instance.data["publish"]: + self.log.info("Saver has different passthrough state than " + "Pyblish: {} ({})".format(instance, saver.Name)) return [saver] return [] - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Saver has different passthrough state than " - "Pyblish: {} ({})".format(instance, - invalid[0].Name)) From 2ac6235bbb83982c90e9802b1a37a8e31da14d8c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Apr 2018 17:18:04 +0200 Subject: [PATCH 0740/1249] Remove ImportModelLoader --- colorbleed/plugins/maya/load/load_model.py | 95 ---------------------- 1 file changed, 95 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_model.py b/colorbleed/plugins/maya/load/load_model.py index eb76bd9ada..0d08c6ecbd 100644 --- a/colorbleed/plugins/maya/load/load_model.py +++ b/colorbleed/plugins/maya/load/load_model.py @@ -34,101 +34,6 @@ class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): self.update(container, representation) -class ImportModelLoader(api.Loader): - """An ImportModelLoader for Maya - - This will implement the basic behavior for a loader to inherit from that - will containerize content. The loader will not be able to use `update` - but only `remove`. When calling the `update` the loader will return a - "NotImplementedError". - - """ - - families = ["colorbleed.model"] - representations = ["ma"] - - label = "Import Model" - order = 10 - icon = "arrow-circle-down" - color = "white" - - def load(self, context, name=None, namespace=None, data=None): - - import maya.cmds as cmds - - from avalon import maya - from avalon.maya import lib - from avalon.maya.pipeline import containerise - - asset = context['asset'] - - namespace = namespace or lib.unique_namespace( - asset["name"] + "_", - prefix="_" if asset["name"][0].isdigit() else "", - suffix="_", - ) - - with maya.maintained_selection(): - nodes = cmds.file(self.fname, - i=True, - namespace=namespace, - returnNewNodes=True, - groupReference=True, - groupName="{}:{}".format(namespace, name)) - - # Only containerize if any nodes were loaded by the Loader - if not nodes: - return - - self[:] = nodes - - return containerise( - name=name, - namespace=namespace, - nodes=nodes, - context=context, - loader=self.__class__.__name__) - - def update(self, container, representation): - from avalon.vendor.Qt import QtWidgets - - message = ("The content of this asset was imported. " - "We cannot update this because we will need" - " to keep insane amount of things in mind.\n" - "Please remove and reimport the asset." - "\n\nIf you really need to update a lot we " - "recommend referencing.") - - QtWidgets.QMessageBox.critical(None, - "Can't update", - message, - QtWidgets.QMessageBox.Ok) - - return - - def remove(self, container): - - from maya import cmds - - namespace = container["namespace"] - container_name = container["objectName"] - - self.log.info("Removing '%s' from Maya.." % container["name"]) - - container_content = cmds.sets(container_name, query=True) - nodes = cmds.ls(container_content, long=True) - - nodes.append(container_name) - - try: - cmds.delete(nodes) - except ValueError: - # Already implicitly deleted by Maya upon removing reference - pass - - cmds.namespace(removeNamespace=namespace, deleteNamespaceContent=True) - - class GpuCacheLoader(api.Loader): """Load model Alembic as gpuCache""" From 475837a38bd816e3e79d88f9718f178fd76875ef Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 4 Apr 2018 22:03:20 +0200 Subject: [PATCH 0741/1249] Implement generic ImportMayaLoader action that does not containerize --- colorbleed/plugins/maya/load/actions.py | 43 +++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py index 7b63c7d194..12a3dfe248 100644 --- a/colorbleed/plugins/maya/load/actions.py +++ b/colorbleed/plugins/maya/load/actions.py @@ -76,3 +76,46 @@ class SetFrameRangeWithHandlesLoader(api.Loader): maxTime=end, animationStartTime=start, animationEndTime=end) + + +class ImportMayaLoader(api.Loader): + """Import action for Maya (unmanaged) + + Warning: + The loaded content will be unmanaged and is *not* visible in the + scene inventory. It's purely intended to merge content into your scene + so you could also use it as a new base. + + """ + representations = ["ma"] + families = ["*"] + + label = "Import" + order = 10 + icon = "arrow-circle-down" + color = "#775555" + + def load(self, context, name=None, namespace=None, data=None): + import maya.cmds as cmds + + from avalon import maya + from avalon.maya import lib + + asset = context['asset'] + + namespace = namespace or lib.unique_namespace( + asset["name"] + "_", + prefix="_" if asset["name"][0].isdigit() else "", + suffix="_", + ) + + with maya.maintained_selection(): + cmds.file(self.fname, + i=True, + namespace=namespace, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + # We do not containerize imported content, it remains unmanaged + return From c4a289692f6ef9187a1e8c5b0f5202e657f3c375 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Apr 2018 15:40:04 +0200 Subject: [PATCH 0742/1249] Implement PLN-85: Refactor global plug-ins to its own folder - Also move OpenImageSequence to global loaders --- colorbleed/__init__.py | 14 ++++++++++---- .../load/open_imagesequence.py} | 0 colorbleed/plugins/{ => global}/publish/cleanup.py | 0 .../publish/collect_assumed_destination.py | 0 .../{ => global}/publish/collect_comment.py | 0 .../{ => global}/publish/collect_context_label.py | 0 .../publish/collect_current_shell_file.py | 0 .../{ => global}/publish/collect_deadline_user.py | 0 .../{ => global}/publish/collect_filesequences.py | 0 .../publish/collect_shell_workspace.py | 0 .../plugins/{ => global}/publish/collect_time.py | 0 .../plugins/{ => global}/publish/integrate.py | 0 .../{ => global}/publish/submit_publish_job.py | 0 .../{ => global}/publish/validate_file_saved.py | 0 .../publish/validate_sequence_frames.py | 0 15 files changed, 10 insertions(+), 4 deletions(-) rename colorbleed/plugins/{maya/load/load_imagesequence.py => global/load/open_imagesequence.py} (100%) rename colorbleed/plugins/{ => global}/publish/cleanup.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_assumed_destination.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_comment.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_context_label.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_current_shell_file.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_deadline_user.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_filesequences.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_shell_workspace.py (100%) rename colorbleed/plugins/{ => global}/publish/collect_time.py (100%) rename colorbleed/plugins/{ => global}/publish/integrate.py (100%) rename colorbleed/plugins/{ => global}/publish/submit_publish_job.py (100%) rename colorbleed/plugins/{ => global}/publish/validate_file_saved.py (100%) rename colorbleed/plugins/{ => global}/publish/validate_sequence_frames.py (100%) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 13aa87c153..07b65815cb 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,19 +1,25 @@ import os from pyblish import api as pyblish +from avalon import api as avalon from .launcher_actions import register_launcher_actions PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") + +# Global plugin paths +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "global", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "global", "load") def install(): - publish_path = os.path.join(PLUGINS_DIR, "publish") print("Registering global plug-ins..") - - pyblish.register_plugin_path(publish_path) + pyblish.register_plugin_path(PUBLISH_PATH) + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) def uninstall(): + print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) + avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + diff --git a/colorbleed/plugins/maya/load/load_imagesequence.py b/colorbleed/plugins/global/load/open_imagesequence.py similarity index 100% rename from colorbleed/plugins/maya/load/load_imagesequence.py rename to colorbleed/plugins/global/load/open_imagesequence.py diff --git a/colorbleed/plugins/publish/cleanup.py b/colorbleed/plugins/global/publish/cleanup.py similarity index 100% rename from colorbleed/plugins/publish/cleanup.py rename to colorbleed/plugins/global/publish/cleanup.py diff --git a/colorbleed/plugins/publish/collect_assumed_destination.py b/colorbleed/plugins/global/publish/collect_assumed_destination.py similarity index 100% rename from colorbleed/plugins/publish/collect_assumed_destination.py rename to colorbleed/plugins/global/publish/collect_assumed_destination.py diff --git a/colorbleed/plugins/publish/collect_comment.py b/colorbleed/plugins/global/publish/collect_comment.py similarity index 100% rename from colorbleed/plugins/publish/collect_comment.py rename to colorbleed/plugins/global/publish/collect_comment.py diff --git a/colorbleed/plugins/publish/collect_context_label.py b/colorbleed/plugins/global/publish/collect_context_label.py similarity index 100% rename from colorbleed/plugins/publish/collect_context_label.py rename to colorbleed/plugins/global/publish/collect_context_label.py diff --git a/colorbleed/plugins/publish/collect_current_shell_file.py b/colorbleed/plugins/global/publish/collect_current_shell_file.py similarity index 100% rename from colorbleed/plugins/publish/collect_current_shell_file.py rename to colorbleed/plugins/global/publish/collect_current_shell_file.py diff --git a/colorbleed/plugins/publish/collect_deadline_user.py b/colorbleed/plugins/global/publish/collect_deadline_user.py similarity index 100% rename from colorbleed/plugins/publish/collect_deadline_user.py rename to colorbleed/plugins/global/publish/collect_deadline_user.py diff --git a/colorbleed/plugins/publish/collect_filesequences.py b/colorbleed/plugins/global/publish/collect_filesequences.py similarity index 100% rename from colorbleed/plugins/publish/collect_filesequences.py rename to colorbleed/plugins/global/publish/collect_filesequences.py diff --git a/colorbleed/plugins/publish/collect_shell_workspace.py b/colorbleed/plugins/global/publish/collect_shell_workspace.py similarity index 100% rename from colorbleed/plugins/publish/collect_shell_workspace.py rename to colorbleed/plugins/global/publish/collect_shell_workspace.py diff --git a/colorbleed/plugins/publish/collect_time.py b/colorbleed/plugins/global/publish/collect_time.py similarity index 100% rename from colorbleed/plugins/publish/collect_time.py rename to colorbleed/plugins/global/publish/collect_time.py diff --git a/colorbleed/plugins/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py similarity index 100% rename from colorbleed/plugins/publish/integrate.py rename to colorbleed/plugins/global/publish/integrate.py diff --git a/colorbleed/plugins/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py similarity index 100% rename from colorbleed/plugins/publish/submit_publish_job.py rename to colorbleed/plugins/global/publish/submit_publish_job.py diff --git a/colorbleed/plugins/publish/validate_file_saved.py b/colorbleed/plugins/global/publish/validate_file_saved.py similarity index 100% rename from colorbleed/plugins/publish/validate_file_saved.py rename to colorbleed/plugins/global/publish/validate_file_saved.py diff --git a/colorbleed/plugins/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py similarity index 100% rename from colorbleed/plugins/publish/validate_sequence_frames.py rename to colorbleed/plugins/global/publish/validate_sequence_frames.py From e06d2fe8f5317964bd4208c73be6b747fefaa297 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Apr 2018 16:32:13 +0200 Subject: [PATCH 0743/1249] Refactor OpenImageSequence to PlayImageSequence to describe the intent of playing it and not opening it to alter the files. --- colorbleed/plugins/global/load/open_imagesequence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/global/load/open_imagesequence.py b/colorbleed/plugins/global/load/open_imagesequence.py index 2386f0c6e7..12fb9a0226 100644 --- a/colorbleed/plugins/global/load/open_imagesequence.py +++ b/colorbleed/plugins/global/load/open_imagesequence.py @@ -15,13 +15,13 @@ def open(filepath): subprocess.call(('xdg-open', filepath)) -class OpenImageSequence(api.Loader): +class PlayImageSequence(api.Loader): """Open Image Sequence with system default""" families = ["colorbleed.imagesequence"] representations = ["*"] - label = "Open sequence" + label = "Play sequence" order = -10 icon = "play-circle" color = "orange" From cdfe8401dc6c3a31f8181dff2ddd73cc241d2560 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Apr 2018 16:32:33 +0200 Subject: [PATCH 0744/1249] Add CopyToClipboard global action --- .../plugins/global/load/copy_to_clipboard.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 colorbleed/plugins/global/load/copy_to_clipboard.py diff --git a/colorbleed/plugins/global/load/copy_to_clipboard.py b/colorbleed/plugins/global/load/copy_to_clipboard.py new file mode 100644 index 0000000000..c773ebfeb3 --- /dev/null +++ b/colorbleed/plugins/global/load/copy_to_clipboard.py @@ -0,0 +1,32 @@ +from avalon import api + + +class CopyToClipboardLoader(api.Loader): + """Copy published file to clipboard to allow to paste the content""" + representations = ["*"] + families = ["*"] + + label = "Copy to Clipboard" + order = 20 + icon = "clipboard" + color = "#999999" + + def load(self, context, name=None, namespace=None, data=None): + self.log.info("Added to clipboard: {0}".format(self.fname)) + self.copy_file_to_clipboard(self.fname) + + @staticmethod + def copy_file_to_clipboard(path): + from avalon.vendor.Qt import QtCore, QtWidgets + + app = QtWidgets.QApplication.instance() + assert app, "Must have running QApplication instance" + + # Build mime data for clipboard + mime = QtCore.QMimeData() + mime.setText(path) + mime.setUrls([QtCore.QUrl.fromLocalFile(path)]) + + # Set to Clipboard + clipboard = app.clipboard() + clipboard.setMimeData(mime) From 5f6ce9060d9de54079670afcb7ffa7694a98b752 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Apr 2018 13:56:18 +0200 Subject: [PATCH 0745/1249] Implement FUS-37: Use default "fusion" pool for Deadline submissions --- colorbleed/plugins/fusion/publish/submit_deadline.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/colorbleed/plugins/fusion/publish/submit_deadline.py index 5afb484e50..9fbf9b4003 100644 --- a/colorbleed/plugins/fusion/publish/submit_deadline.py +++ b/colorbleed/plugins/fusion/publish/submit_deadline.py @@ -74,6 +74,9 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): # User, as seen in Monitor "UserName": deadline_user, + # Use a default submission pool for Fusion + "Pool": "fusion", + "Plugin": "Fusion", "Frames": "{start}-{end}".format( start=int(context.data["startFrame"]), From 483f8adbfe8aab0185084d5326c5bb4eda225e98 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Apr 2018 17:17:55 +0200 Subject: [PATCH 0746/1249] Fix typo --- colorbleed/launcher_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/launcher_actions.py b/colorbleed/launcher_actions.py index dd5cf2374e..8fe1297eb0 100644 --- a/colorbleed/launcher_actions.py +++ b/colorbleed/launcher_actions.py @@ -30,7 +30,7 @@ class FusionRenderNode(api.Action): env = os.environ.copy() env.update(session) - # Get executable by na.e + # Get executable by name app = lib.get_application(self.name) executable = lib.which(app["executable"]) From fa01128de5144176aedb05ecad8af3838695d18b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Apr 2018 17:19:38 +0200 Subject: [PATCH 0747/1249] Merge in the Application's environment correctly --- colorbleed/launcher_actions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/launcher_actions.py b/colorbleed/launcher_actions.py index 8fe1297eb0..edd8404bcf 100644 --- a/colorbleed/launcher_actions.py +++ b/colorbleed/launcher_actions.py @@ -32,6 +32,7 @@ class FusionRenderNode(api.Action): # Get executable by name app = lib.get_application(self.name) + env.update(app["environment"]) executable = lib.which(app["executable"]) return lib.launch(executable=executable, args=[], environment=env) From 978b0dff2e852eae2e0009322adfb611e956bc20 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 9 Apr 2018 17:21:04 +0200 Subject: [PATCH 0748/1249] Add VrayRenderSlave to Launcher actions --- colorbleed/launcher_actions.py | 43 +++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/colorbleed/launcher_actions.py b/colorbleed/launcher_actions.py index edd8404bcf..7d72cb2b38 100644 --- a/colorbleed/launcher_actions.py +++ b/colorbleed/launcher_actions.py @@ -38,8 +38,49 @@ class FusionRenderNode(api.Action): return lib.launch(executable=executable, args=[], environment=env) +class VrayRenderSlave(api.Action): + + name = "vrayrenderslave" + label = "V-Ray Slave" + icon = "object-group" + order = 996 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + if "AVALON_PROJECT" in session: + return False + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered + + Args: + session (dict): environment dictionary + + Returns: + Popen instance of newly spawned process + + """ + + # Update environment with session + env = os.environ.copy() + env.update(session) + + # Get executable by name + app = lib.get_application(self.name) + env.update(app["environment"]) + executable = lib.which(app["executable"]) + + # Run as server + arguments = ["-server", "-portNumber=20207"] + + return lib.launch(executable=executable, + args=arguments, + environment=env) + + def register_launcher_actions(): """Register specific actions which should be accessible in the launcher""" - # Register fusion actions pipeline.register_plugin(api.Action, FusionRenderNode) + pipeline.register_plugin(api.Action, VrayRenderSlave) From fec1e4a4f1c7c362ac9073d6550532b696ac6eeb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Apr 2018 15:34:17 +0200 Subject: [PATCH 0749/1249] Fix REN-39: Preserve color space values when altered from default value for image --- colorbleed/plugins/maya/publish/extract_look.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 43082b6669..d6767bea49 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -1,5 +1,6 @@ import os import json +from collections import OrderedDict from maya import cmds @@ -45,11 +46,18 @@ class ExtractLook(colorbleed.api.Extractor): sets = relationships.keys() resources = instance.data["resources"] - remap = {} + + remap = OrderedDict() # needs to be ordered, see color space values for resource in resources: attr = resource['attribute'] remap[attr] = resource['destination'] + # Preserve color space values (force value after filepath change) + # This will also trigger in the same order at end of context to + # ensure after context it's still the original value. + color_space_attr = resource['node'] + ".colorSpace" + remap[color_space_attr] = cmds.getAttr(color_space_attr) + self.log.info("Finished remapping destinations ...") # Extract in correct render layer From 2ec53af9fd4c41943660dce2ffc3a91900ce91c9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 10 Apr 2018 15:36:16 +0200 Subject: [PATCH 0750/1249] Remove look ignore color space validator - it's fixed in a different way (previous commit) and validator wasn't working anyway because of missings "tags" on resources --- .../validate_look_ignore_color_space.py | 58 ------------------- 1 file changed, 58 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py diff --git a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py b/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py deleted file mode 100644 index 58f0bb2f23..0000000000 --- a/colorbleed/plugins/maya/publish/validate_look_ignore_color_space.py +++ /dev/null @@ -1,58 +0,0 @@ -from maya import cmds - -import pyblish.api -import colorbleed.api - - -class ValidateLookIgnoreColorSpace(pyblish.api.InstancePlugin): - """Validate look textures are set to ignore color space when set to RAW - - Whenever the format is NOT set to sRGB for a file texture it must have - its ignore color space file rules checkbox enabled to avoid unwanted - reverting to sRGB settings upon file relinking. - - To fix this use the select invalid action to find the invalid file nodes - and then check the "Ignore Color Space File Rules" checkbox under the - Color Space settings. - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] - hosts = ['maya'] - label = 'Look RAW Ignore color space' - actions = [colorbleed.api.SelectInvalidAction] - - @classmethod - def get_invalid(cls, instance): - - # Get texture nodes from the collected resources - required = ["maya", "look", "attribute"] - nodes = list() - for resource in instance.data.get("resources", []): - if all(tag in resource.get("tags", []) for tag in required): - node = resource['node'] - nodes.append(node) - - nodes = list(sorted(set(nodes))) - cls.log.info("Checking nodes: {0}".format(nodes)) - - # Validate - invalid = [] - for node in nodes: - color_space = cmds.getAttr(node + ".colorSpace") - ignore_rules = cmds.getAttr(node + ".ignoreColorSpaceFileRules") - if color_space != "sRGB" and not ignore_rules: - invalid.append(node) - - return invalid - - def process(self, instance): - """Process all the nodes in the instance""" - - invalid = self.get_invalid(instance) - - if invalid: - raise RuntimeError("Non-sRGB file textures nodes with ignore " - "color space file rules disabled: " - "{0}".format(invalid)) From 4fe9ac3d520d7b8106e449d4bb01b8da0da19c55 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Apr 2018 15:43:49 +0200 Subject: [PATCH 0751/1249] Fix PLN-108: Always operate on highest reference (top) reference node. --- colorbleed/maya/plugin.py | 49 +++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 89d6306813..247325593f 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -1,6 +1,30 @@ from avalon import api +def get_reference_node_parents(ref): + """Return all parent reference nodes of reference node + + Args: + ref (str): reference node. + + Returns: + list: The upstream parent reference nodes. + + """ + from maya import cmds + + parent = cmds.referenceQuery(ref, + referenceNode=True, + parent=True) + parents = [] + while parent: + parents.append(parent) + parent = cmds.referenceQuery(parent, + referenceNode=True, + parent=True) + return parents + + class ReferenceLoader(api.Loader): """A basic ReferenceLoader for Maya @@ -59,27 +83,28 @@ class ReferenceLoader(api.Loader): # Collect the references without .placeHolderList[] attributes as # unique entries (objects only) and skipping the sharedReferenceNode. - matched = set() - references = list() + references = set() for ref in cmds.ls(members, exactType="reference", objectsOnly=True): - # Ensure unique entries - if ref in matched: - continue - # Ignore `sharedReferenceNode` if ref == "sharedReferenceNode": continue - references.append(ref) - matched.add(ref) + references.add(ref) assert references, "No reference node found in container" - if len(set(references)) > 1: + + # Get highest reference node (least parents) + highest = min(references, + key=lambda x: len(get_reference_node_parents(x))) + + # Warn the user when we're taking the highest reference node + if len(references) > 1: self.log.warning("More than one reference node found in " - "container - using first one: %s", references) - reference_node = references[0] - return reference_node + "container, using highest reference node: " + "%s (in: %s)", highest, list(references)) + + return highest def update(self, container, representation): From c077d946f6aa5f175e6fa2f075ae24b247a3d81b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Apr 2018 17:29:04 +0200 Subject: [PATCH 0752/1249] Fix PLN-109: Files with read errors (e.g. on missing arnold plug-in) fail to update container correctly --- colorbleed/maya/plugin.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 247325593f..5e5dcd018a 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -128,10 +128,25 @@ class ReferenceLoader(api.Loader): assert file_type, "Unsupported representation: %s" % representation assert os.path.exists(path), "%s does not exist." % path - content = cmds.file(path, - loadReference=reference_node, - type=file_type, - returnNewNodes=True) + + try: + content = cmds.file(path, + loadReference=reference_node, + type=file_type, + returnNewNodes=True) + except RuntimeError as exc: + # When changing a reference to a file that has load errors the + # command will raise an error even if the file is still loaded + # correctly (e.g. when raising errors on Arnold attributes) + # When the file is loaded and has content, we consider it's fine. + if not cmds.referenceQuery(reference_node, isLoaded=True): + raise + + content = cmds.referenceQuery(reference_node, nodes=True) + if not content: + raise + + self.log.info("Ignoring file read error:\n%s", exc) # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. From 11a8b952d79cfbfefb8bbf503b890cdfca92f73e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Apr 2018 17:30:32 +0200 Subject: [PATCH 0753/1249] Assert file exists on load in ReferenceLoader to avoid empty reference --- colorbleed/maya/plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 5e5dcd018a..4ca3c67ac0 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -39,9 +39,12 @@ class ReferenceLoader(api.Loader): namespace=None, data=None): + import os from avalon.maya import lib from avalon.maya.pipeline import containerise + assert os.path.exists(self.fname), "%s does not exist." % self.fname + asset = context['asset'] namespace = namespace or lib.unique_namespace( From 555b64085cfa691aa9a3b254ec52a0543b152d4e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Apr 2018 17:42:51 +0200 Subject: [PATCH 0754/1249] Raise logging level for ignore read errors to warning --- colorbleed/maya/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 4ca3c67ac0..4b3b29d326 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -149,7 +149,7 @@ class ReferenceLoader(api.Loader): if not content: raise - self.log.info("Ignoring file read error:\n%s", exc) + self.log.warning("Ignoring file read error:\n%s", exc) # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. From 5a04ab770a4d08a898220e6212280ac12463dcc6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 13 Apr 2018 18:38:34 +0200 Subject: [PATCH 0755/1249] Fix error on conflicting node names - force dagPath for returned nodes --- colorbleed/maya/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 4b3b29d326..ff212a9e5a 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -145,7 +145,9 @@ class ReferenceLoader(api.Loader): if not cmds.referenceQuery(reference_node, isLoaded=True): raise - content = cmds.referenceQuery(reference_node, nodes=True) + content = cmds.referenceQuery(reference_node, + nodes=True, + dagPath=True) if not content: raise From f0794b19bb3b977027ad3a613818cc3c9c3e865a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Apr 2018 17:02:15 +0200 Subject: [PATCH 0756/1249] Don't ignore default set "initialShadingGroup" in `get_related_sets` This allows to collect the default shader too - it's up to the validator to decide whether it's valid to have it, which is a validator we have in place. --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 93ede7fe58..ba435a8102 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1080,7 +1080,7 @@ def get_related_sets(node): ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"] # Default nodes to ignore - defaults = {"initialShadingGroup", "defaultLightSet", "defaultObjectSet"} + defaults = {"defaultLightSet", "defaultObjectSet"} # Ids to ignore ignored = {"pyblish.avalon.instance", "pyblish.avalon.container"} From 46f94ade8f3446b98695e8a76494ff0e8d3b7131 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 15 Apr 2018 17:10:40 +0200 Subject: [PATCH 0757/1249] Improve docstring --- colorbleed/maya/lib.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index ba435a8102..1493f7385e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1045,8 +1045,10 @@ def apply_shaders(relationships, shadernodes, nodes): # endregion LOOKDEV def get_isolate_view_sets(): - """ + """Return isolate view sets of all modelPanels. + Returns: + list: all sets related to isolate view """ From 342a0aab8e7b10a45e32642484167032ca006073 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 16 Apr 2018 10:36:52 +0200 Subject: [PATCH 0758/1249] Fix name of colorbleed.renderlayer family --- colorbleed/plugins/maya/publish/validate_frame_range.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index c5a88d5f22..010422df38 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -20,7 +20,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): families = ["colorbleed.animation", "colorbleed.pointcache", "colorbleed.camera", - "colorbleed.render"] + "colorbleed.renderlayer"] def process(self, instance): From 6ead34aa6f8261f8992ae729454257d2b0c7efb1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 16 Apr 2018 20:41:47 +0200 Subject: [PATCH 0759/1249] Allow loading the .ma export of the camera - to support Orthographic cameras --- colorbleed/plugins/maya/load/load_camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index c1e1b4bf25..1d66433a6f 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -6,7 +6,7 @@ class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): families = ["colorbleed.camera"] label = "Reference camera" - representations = ["abc"] + representations = ["abc", "ma"] order = -10 icon = "code-fork" color = "orange" From afaf4a6194271ef3995dbff6dd9a90ad4118e790 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 24 Apr 2018 12:11:31 +0200 Subject: [PATCH 0760/1249] Remove unneeded module --- colorbleed/fusion/rendernode.py | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 colorbleed/fusion/rendernode.py diff --git a/colorbleed/fusion/rendernode.py b/colorbleed/fusion/rendernode.py deleted file mode 100644 index 21ed091ad7..0000000000 --- a/colorbleed/fusion/rendernode.py +++ /dev/null @@ -1,23 +0,0 @@ -import os -from avalon import api, lib - -# TODO: get fusion render node exe from somewhere else -FRN = "C:/Program Files/Blackmagic Design/Fusion Render Node 9/FusionRenderNode.exe" - - -class FusionRenderNode(api.Action): - - name = "fusionrendernode" - label = "F9 Render Node" - icon = "object-group" - order = 997 - - def is_compatible(self, session): - """Return whether the action is compatible with the session""" - return True - - def process(self, session, **kwargs): - """Implement the behavior for when the action is triggered""" - return lib.launch(executable=FRN, - args=[], - environment=os.environ.update(session)) From 3eb853dec70f132c3664a31e3039146ed12afb18 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 24 Apr 2018 12:22:04 +0200 Subject: [PATCH 0761/1249] Removed unused import, added override_event and on_task_changed --- colorbleed/maya/__init__.py | 48 +++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 3a890bb135..2060834495 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -1,11 +1,11 @@ import os import logging -from functools import partial +import weakref from maya import utils from maya import cmds -from avalon import api as avalon +from avalon import api as avalon, pipeline, maya from pyblish import api as pyblish from ..lib import ( @@ -38,6 +38,9 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) + log.info("Overriding existing event 'taskChanged'") + override_event("taskChanged", on_task_changed) + def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) @@ -47,6 +50,24 @@ def uninstall(): menu.uninstall() +def override_event(event, callback): + """ + Override existing event callback + Args: + event (str): name of the event + callback (function): callback to be triggered + + Returns: + None + + """ + + ref = weakref.WeakSet() + ref.add(callback) + + pipeline._registered_event_handlers[event] = callback + + def on_init(_): avalon.logger.info("Running callback on init..") @@ -122,3 +143,26 @@ def on_open(_): "your Maya scene.") dialog.on_show.connect(_on_show_inventory) dialog.show() + + +def on_task_changed(): + """Wrapped function of app initialize and maya's on task changed""" + + # Inputs (from the switched session and running app) + session = avalon.Session.copy() + app_name = os.environ["AVALON_APP_NAME"] + + # Find the application definition + app_definition = pipeline.lib.get_application(app_name) + + App = type("app_%s" % app_name, + (avalon.Application,), + {"config": app_definition.copy()}) + + # Initialize within the new session's environment + app = App() + env = app.environ(session) + app.initialize(env) + + # Run + maya.pipeline._on_task_changed() From 438b15715b7b4ee43d242a16a14e20d1566e5c1b Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 11:59:34 +0200 Subject: [PATCH 0762/1249] Added VRay Displacement Viewer --- colorbleed/maya/menu.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9141b82ac6..65c458a71c 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -829,6 +829,21 @@ "title": "Look Manager", "tooltip": "Open the Look Manager UI for look assignment" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", + "sourcetype": "file", + "tags": [ + "shading", + "look", + "vray", + "displacement", + "shaders", + "auto" + ], + "title": "VRay Displ Viewer", + "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", From a74601a5aee5859d29d91de62b83abacc7527b81 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 12:02:59 +0200 Subject: [PATCH 0763/1249] Revert VRay Displacement Viewer --- colorbleed/maya/menu.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 65c458a71c..9141b82ac6 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -829,21 +829,6 @@ "title": "Look Manager", "tooltip": "Open the Look Manager UI for look assignment" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", - "sourcetype": "file", - "tags": [ - "shading", - "look", - "vray", - "displacement", - "shaders", - "auto" - ], - "title": "VRay Displ Viewer", - "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", From a29f5f177ab48951b04616c282bf3bd9689ff823 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 12:03:48 +0200 Subject: [PATCH 0764/1249] Addedd VRay Displacement Viewer --- colorbleed/maya/menu.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 9141b82ac6..a1d2b426d4 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -855,6 +855,21 @@ "title": "Set Texture Preview To CLRImage", "tooltip": "" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", + "sourcetype": "file", + "tags": [ + "shading", + "look", + "vray", + "displacement", + "shaders", + "auto" + ], + "title": "VRay Displ Viewer", + "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", From 77072482abadc38a72f902d1e24ced6bc5295175 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 12:18:42 +0200 Subject: [PATCH 0765/1249] fixed order issue --- colorbleed/maya/menu.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index a1d2b426d4..55e4553fbe 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -842,19 +842,6 @@ "title": "Light Link UI", "tooltip": "" }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", - "sourcetype": "file", - "tags": [ - "shading", - "CLRImage", - "textures", - "preview" - ], - "title": "Set Texture Preview To CLRImage", - "tooltip": "" - }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", @@ -870,6 +857,19 @@ "title": "VRay Displ Viewer", "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "sourcetype": "file", + "tags": [ + "shading", + "CLRImage", + "textures", + "preview" + ], + "title": "Set Texture Preview To CLRImage", + "tooltip": "" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", From cd10166f1c25451764cc631497ab5d0b4687e3ef Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 16:52:50 +0200 Subject: [PATCH 0766/1249] Register manager actions on init --- colorbleed/__init__.py | 1 + colorbleed/fusion/__init__.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 07b65815cb..55258c8e80 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -3,6 +3,7 @@ from pyblish import api as pyblish from avalon import api as avalon from .launcher_actions import register_launcher_actions +from .manager_actions import register_manager_actions PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index 86ee04a71a..021a872d6c 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -3,6 +3,8 @@ import os from avalon import api as avalon from pyblish import api as pyblish +import colorbleed + PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") @@ -20,6 +22,8 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + colorbleed.register_manager_actions() + def uninstall(): print("Deregistering Fusion plug-ins..") From b80eb38199b99643345fcb4b1bac697e14ce0e77 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 16:53:21 +0200 Subject: [PATCH 0767/1249] Definitions of tool action plugins and the register_manager_action function --- colorbleed/manager_actions.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 colorbleed/manager_actions.py diff --git a/colorbleed/manager_actions.py b/colorbleed/manager_actions.py new file mode 100644 index 0000000000..02ff3ce610 --- /dev/null +++ b/colorbleed/manager_actions.py @@ -0,0 +1,29 @@ +from avalon import api, pipeline + + +class FusionSelectContainer(api.ToolAction): + + label = "Select Container" + icon = "object-group" + hosts = ["fusion"] + tools = ["manager"] + + def process(self, items): + + import avalon.fusion + + tools = [i["_tool"] for i in items] + + comp = avalon.fusion.get_current_comp() + flow = comp.CurrentFrame.FlowView + + # Clear selection + flow.Select() + + # Select tool + for tool in tools: + flow.Select(tool) + + +def register_manager_actions(): + pipeline.register_plugin(api.ToolAction, FusionSelectContainer) From a7b5a8d755593d15c947dbb8670e47264285326f Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 17:20:12 +0200 Subject: [PATCH 0768/1249] Changed icon and added color --- colorbleed/manager_actions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/manager_actions.py b/colorbleed/manager_actions.py index 02ff3ce610..b0a1fe18bd 100644 --- a/colorbleed/manager_actions.py +++ b/colorbleed/manager_actions.py @@ -4,7 +4,8 @@ from avalon import api, pipeline class FusionSelectContainer(api.ToolAction): label = "Select Container" - icon = "object-group" + icon = "mouse-pointer" + color = "#d8d8d8" hosts = ["fusion"] tools = ["manager"] From b1c53ba21e915338a40d0531468e0a0ad37b1e21 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 25 Apr 2018 17:44:57 +0200 Subject: [PATCH 0769/1249] Updated function, lock comp during selection, updated arg name --- colorbleed/manager_actions.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/colorbleed/manager_actions.py b/colorbleed/manager_actions.py index b0a1fe18bd..50a3e35056 100644 --- a/colorbleed/manager_actions.py +++ b/colorbleed/manager_actions.py @@ -9,21 +9,22 @@ class FusionSelectContainer(api.ToolAction): hosts = ["fusion"] tools = ["manager"] - def process(self, items): + def process(self, containers): import avalon.fusion - tools = [i["_tool"] for i in items] + tools = [i["_tool"] for i in containers] comp = avalon.fusion.get_current_comp() flow = comp.CurrentFrame.FlowView - # Clear selection - flow.Select() + with avalon.fusion.comp_lock_and_undo_chunk(comp, self.label): + # Clear selection + flow.Select() - # Select tool - for tool in tools: - flow.Select(tool) + # Select tool + for tool in tools: + flow.Select(tool) def register_manager_actions(): From 85dd7e247ce184d2c544d6bc25776e213275a4a7 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 26 Apr 2018 16:06:18 +0200 Subject: [PATCH 0770/1249] Refactored ToolAction to InventoryAction --- colorbleed/manager_actions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/manager_actions.py b/colorbleed/manager_actions.py index 50a3e35056..17d4c80605 100644 --- a/colorbleed/manager_actions.py +++ b/colorbleed/manager_actions.py @@ -1,7 +1,7 @@ from avalon import api, pipeline -class FusionSelectContainer(api.ToolAction): +class FusionSelectContainer(api.InventoryAction): label = "Select Container" icon = "mouse-pointer" @@ -28,4 +28,4 @@ class FusionSelectContainer(api.ToolAction): def register_manager_actions(): - pipeline.register_plugin(api.ToolAction, FusionSelectContainer) + pipeline.register_plugin(api.InventoryAction, FusionSelectContainer) From bd16d489bc07f65ac7617f57b276ed82808eac00 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 30 Apr 2018 11:12:33 +0200 Subject: [PATCH 0771/1249] removed tools attribute from class --- colorbleed/manager_actions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/manager_actions.py b/colorbleed/manager_actions.py index 17d4c80605..adb76d42b4 100644 --- a/colorbleed/manager_actions.py +++ b/colorbleed/manager_actions.py @@ -7,7 +7,6 @@ class FusionSelectContainer(api.InventoryAction): icon = "mouse-pointer" color = "#d8d8d8" hosts = ["fusion"] - tools = ["manager"] def process(self, containers): From a8aba5d4e08cf0ac0c01272b08995615a9a37832 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 30 Apr 2018 13:21:02 +0200 Subject: [PATCH 0772/1249] Moved plugin to inventory folder of host, removes need for register_manager_actions --- colorbleed/__init__.py | 1 - colorbleed/fusion/__init__.py | 5 ++--- .../fusion/inventory/select_containers.py} | 8 ++------ 3 files changed, 4 insertions(+), 10 deletions(-) rename colorbleed/{manager_actions.py => plugins/fusion/inventory/select_containers.py} (73%) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 55258c8e80..07b65815cb 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -3,7 +3,6 @@ from pyblish import api as pyblish from avalon import api as avalon from .launcher_actions import register_launcher_actions -from .manager_actions import register_manager_actions PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index 021a872d6c..2bb4fec5ea 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -3,7 +3,6 @@ import os from avalon import api as avalon from pyblish import api as pyblish -import colorbleed PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) @@ -12,6 +11,7 @@ PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") PUBLISH_PATH = os.path.join(PLUGINS_DIR, "fusion", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") def install(): @@ -19,11 +19,10 @@ def install(): pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - colorbleed.register_manager_actions() - def uninstall(): print("Deregistering Fusion plug-ins..") diff --git a/colorbleed/manager_actions.py b/colorbleed/plugins/fusion/inventory/select_containers.py similarity index 73% rename from colorbleed/manager_actions.py rename to colorbleed/plugins/fusion/inventory/select_containers.py index adb76d42b4..a1b54ebb74 100644 --- a/colorbleed/manager_actions.py +++ b/colorbleed/plugins/fusion/inventory/select_containers.py @@ -1,9 +1,9 @@ from avalon import api, pipeline -class FusionSelectContainer(api.InventoryAction): +class FusionSelectContainers(api.InventoryAction): - label = "Select Container" + label = "Select Containers" icon = "mouse-pointer" color = "#d8d8d8" hosts = ["fusion"] @@ -24,7 +24,3 @@ class FusionSelectContainer(api.InventoryAction): # Select tool for tool in tools: flow.Select(tool) - - -def register_manager_actions(): - pipeline.register_plugin(api.InventoryAction, FusionSelectContainer) From 84eea57b996608ed24d4ca4a4ffe487adc685272 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 30 Apr 2018 13:21:31 +0200 Subject: [PATCH 0773/1249] Removed unused import --- colorbleed/plugins/fusion/inventory/select_containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/fusion/inventory/select_containers.py b/colorbleed/plugins/fusion/inventory/select_containers.py index a1b54ebb74..654384b847 100644 --- a/colorbleed/plugins/fusion/inventory/select_containers.py +++ b/colorbleed/plugins/fusion/inventory/select_containers.py @@ -1,4 +1,4 @@ -from avalon import api, pipeline +from avalon import api class FusionSelectContainers(api.InventoryAction): From 127895117378351339b4e94fed5f04c3ee48fe0a Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 30 Apr 2018 13:23:52 +0200 Subject: [PATCH 0774/1249] removed redundant empty line --- colorbleed/widgets/popup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/widgets/popup.py b/colorbleed/widgets/popup.py index 0ad1e07490..805eae8c23 100644 --- a/colorbleed/widgets/popup.py +++ b/colorbleed/widgets/popup.py @@ -30,7 +30,6 @@ class Popup(QtWidgets.QDialog): QtWidgets.QSizePolicy.Maximum) show.setStyleSheet("""QPushButton { background-color: #BB0000 }""") - layout.addWidget(message) layout.addWidget(show) From 3098b68d91ace746b0bb2ef36095a9477865f354 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 30 Apr 2018 16:51:37 +0200 Subject: [PATCH 0775/1249] removed hosts attribute --- colorbleed/plugins/fusion/inventory/select_containers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/fusion/inventory/select_containers.py b/colorbleed/plugins/fusion/inventory/select_containers.py index 654384b847..2f7b3e5809 100644 --- a/colorbleed/plugins/fusion/inventory/select_containers.py +++ b/colorbleed/plugins/fusion/inventory/select_containers.py @@ -6,7 +6,6 @@ class FusionSelectContainers(api.InventoryAction): label = "Select Containers" icon = "mouse-pointer" color = "#d8d8d8" - hosts = ["fusion"] def process(self, containers): From b507e8d7b84a322d9f1e9578b54c7a3503e128fb Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 2 May 2018 11:23:40 +0200 Subject: [PATCH 0776/1249] fixed minor bug --- colorbleed/maya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 2060834495..d64d93f3ae 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -65,7 +65,7 @@ def override_event(event, callback): ref = weakref.WeakSet() ref.add(callback) - pipeline._registered_event_handlers[event] = callback + pipeline._registered_event_handlers[event] = ref def on_init(_): From 9079016b4ce97ded392a9c983d536c9743c7f9a3 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 2 May 2018 11:33:59 +0200 Subject: [PATCH 0777/1249] Fixes issue with arguments --- colorbleed/maya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index d64d93f3ae..237389c8c0 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -145,7 +145,7 @@ def on_open(_): dialog.show() -def on_task_changed(): +def on_task_changed(_): """Wrapped function of app initialize and maya's on task changed""" # Inputs (from the switched session and running app) From 10568c8661c194ade8d20d447124093b8ce1b39d Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 2 May 2018 11:40:02 +0200 Subject: [PATCH 0778/1249] Ensured passed arguments would not clash with callback --- colorbleed/maya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 237389c8c0..464a0eb039 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -145,7 +145,7 @@ def on_open(_): dialog.show() -def on_task_changed(_): +def on_task_changed(*args): """Wrapped function of app initialize and maya's on task changed""" # Inputs (from the switched session and running app) From 9cb06bc23f02c2a327448d1c5825e4d03e0d96c8 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 10:55:56 +0200 Subject: [PATCH 0779/1249] retrieve name of host --- colorbleed/__init__.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 07b65815cb..0a3f182864 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,4 +1,6 @@ import os +import importlib + from pyblish import api as pyblish from avalon import api as avalon @@ -23,3 +25,36 @@ def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + +def _get_host_name(): + + _host = avalon.registered_host() + # This covers nested module name like avalon.maya + return _host.__name__.rsplit(".", 1)[-1] + + +def collect_container_metadata(container): + """Add additional data based on the current host + + If the host application's lib module does not have a function to inject + additional data it will return the input container + + Args: + container (dict): collection if representation data in host + + Returns: + generator + """ + + host_name = _get_host_name() + + # This will cover nested module names like avalon.maya + package_name = "{}.{}.lib".format(__name__, host_name) + hostlib = importlib.import_module(package_name) + + if not hasattr(hostlib, "get_additional_data"): + print("{} has no function called " + "get_additional_data".format(package_name)) + return container + + return hostlib.get_additional_data(container) From af734f9c56b0276276d43eaf5bd2abb10325c71e Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 10:56:28 +0200 Subject: [PATCH 0780/1249] added get_additional_data for inventory --- colorbleed/fusion/lib.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py index 7186cd2cac..7c6b6dbdda 100644 --- a/colorbleed/fusion/lib.py +++ b/colorbleed/fusion/lib.py @@ -38,3 +38,27 @@ def update_frame_range(start, end, comp=None, set_render_range=True): with avalon.fusion.comp_lock_and_undo_chunk(comp): comp.SetAttrs(attrs) + + +def get_additional_data(container): + """Get Fusion related data for the container + + Args: + container(dict): the container found by the ls() function + + Returns: + dict + """ + + def clamp(value): + return int(value * 255) + + tool = container["_tool"] + tile_color = tool.TileColor + if tile_color is None: + return container + + tile_color.pop("__flags", None) + rgb = {key: clamp(value) for key, value in tile_color.items()} + + return {"color": "#{R:02x}{G:02x}{B:02x}".format(**rgb)} From a43d0f06b5e775163ea0aa00abb8bf981d5af12f Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 12:41:59 +0200 Subject: [PATCH 0781/1249] Added select_loader_color action --- .../fusion/inventory/select_loader_color.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 colorbleed/plugins/fusion/inventory/select_loader_color.py diff --git a/colorbleed/plugins/fusion/inventory/select_loader_color.py b/colorbleed/plugins/fusion/inventory/select_loader_color.py new file mode 100644 index 0000000000..a3c9cd15f8 --- /dev/null +++ b/colorbleed/plugins/fusion/inventory/select_loader_color.py @@ -0,0 +1,35 @@ +from avalon import api +from avalon.vendor.Qt import QtGui, QtWidgets + +import avalon.fusion + + +class FusionSelectLoaderColor(api.InventoryAction): + + label = "Select Loader Color" + icon = "plus" + color = "#d8d8d8" + + def process(self, containers): + + comp = avalon.fusion.get_current_comp() + + # Get color of selected container + _tool = containers[0]["_tool"] + table = _tool.TileColor + if table: + color = QtGui.QColor.fromRgbF(table["R"], table["G"], table["B"]) + else: + color = QtGui.QColor.fromRgbF(0.0, 0.0, 0.0) + + # Launch pick color + picked_color = QtWidgets.QColorDialog().getColor(color) + with avalon.fusion.comp_lock_and_undo_chunk(comp): + for container in containers: + # Convert color to 0-1 floats + rgb_f = picked_color.getRgbF() + rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]} + + # Update tool + tool = container["_tool"] + tool.TileColor = rgb_f_table From 481ffb6e1390bb5d1c2dea7209d105bebd5a7ad0 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 13:48:23 +0200 Subject: [PATCH 0782/1249] Return QColor instead of hex string --- colorbleed/fusion/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py index 7c6b6dbdda..504a9b0271 100644 --- a/colorbleed/fusion/lib.py +++ b/colorbleed/fusion/lib.py @@ -1,5 +1,6 @@ import sys +from avalon.vendor.Qt import QtGui import avalon.fusion @@ -58,7 +59,6 @@ def get_additional_data(container): if tile_color is None: return container - tile_color.pop("__flags", None) - rgb = {key: clamp(value) for key, value in tile_color.items()} - - return {"color": "#{R:02x}{G:02x}{B:02x}".format(**rgb)} + return {"color": QtGui.QColor(clamp(tile_color["R"]), + clamp(tile_color["G"]), + clamp(tile_color["B"]))} From 74efe81d6ef8f7eff0c7c2079544b751f6ec4b1b Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 14:33:09 +0200 Subject: [PATCH 0783/1249] Removed query for color, added refresh of inventory after action --- .../fusion/inventory/select_loader_color.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/select_loader_color.py b/colorbleed/plugins/fusion/inventory/select_loader_color.py index a3c9cd15f8..dc112c5011 100644 --- a/colorbleed/plugins/fusion/inventory/select_loader_color.py +++ b/colorbleed/plugins/fusion/inventory/select_loader_color.py @@ -5,31 +5,36 @@ import avalon.fusion class FusionSelectLoaderColor(api.InventoryAction): + """Update the color of the selected tools""" label = "Select Loader Color" icon = "plus" color = "#d8d8d8" def process(self, containers): + """Color all selected tools the selected colors""" comp = avalon.fusion.get_current_comp() - # Get color of selected container - _tool = containers[0]["_tool"] - table = _tool.TileColor - if table: - color = QtGui.QColor.fromRgbF(table["R"], table["G"], table["B"]) - else: - color = QtGui.QColor.fromRgbF(0.0, 0.0, 0.0) - # Launch pick color + color = QtGui.QColor(1.0, 1.0, 1.0) picked_color = QtWidgets.QColorDialog().getColor(color) with avalon.fusion.comp_lock_and_undo_chunk(comp): for container in containers: - # Convert color to 0-1 floats + # Convert color to RGB 0-1 floats rgb_f = picked_color.getRgbF() rgb_f_table = {"R": rgb_f[0], "G": rgb_f[1], "B": rgb_f[2]} # Update tool tool = container["_tool"] tool.TileColor = rgb_f_table + + self.refresh() + + def refresh(self): + """Refresh Scene Inventory window""" + + app = QtWidgets.QApplication.instance() + widgets = dict((w.objectName(), w) for w in app.allWidgets()) + widget = widgets.get("SceneInventory") + widget.refresh() From 133ba71963bddbbfbd7575c3c35baa87009333a4 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 15:36:09 +0200 Subject: [PATCH 0784/1249] fixes bug with empty scriptmenu --- colorbleed/maya/menu.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 9cc5b5911d..1e411e1c78 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -36,10 +36,6 @@ def deferred(): cb_menu = launchformaya.main(title=self._menu.title(), objectName=self._menu) - # register modifiers - modifiers = QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier - cb_menu.register_callback(modifiers, launchformaya.to_shelf) - # apply configuration cb_menu.build_from_configuration(cb_menu, config) From 88fe63fe57ca76bcddb14f050084bc73402fa8a0 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 16:57:09 +0200 Subject: [PATCH 0785/1249] Renamed action, add fallback color for start color of picker --- .../plugins/fusion/inventory/select_loader_color.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/select_loader_color.py b/colorbleed/plugins/fusion/inventory/select_loader_color.py index dc112c5011..bef99e53b9 100644 --- a/colorbleed/plugins/fusion/inventory/select_loader_color.py +++ b/colorbleed/plugins/fusion/inventory/select_loader_color.py @@ -4,12 +4,13 @@ from avalon.vendor.Qt import QtGui, QtWidgets import avalon.fusion -class FusionSelectLoaderColor(api.InventoryAction): +class FusionSetToolColor(api.InventoryAction): """Update the color of the selected tools""" - label = "Select Loader Color" + label = "Set Tool Color" icon = "plus" color = "#d8d8d8" + _fallback_color = QtGui.QColor(1.0, 1.0, 1.0) def process(self, containers): """Color all selected tools the selected colors""" @@ -17,7 +18,8 @@ class FusionSelectLoaderColor(api.InventoryAction): comp = avalon.fusion.get_current_comp() # Launch pick color - color = QtGui.QColor(1.0, 1.0, 1.0) + first = containers[0] + color = QtGui.QColor(first.get("color", self._fallback_color)) picked_color = QtWidgets.QColorDialog().getColor(color) with avalon.fusion.comp_lock_and_undo_chunk(comp): for container in containers: From b7910446ff3bd79c2ccee248a23a6b786d90efa4 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 16:57:25 +0200 Subject: [PATCH 0786/1249] return empty dict instead of container --- colorbleed/fusion/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py index 504a9b0271..97c9fb9e86 100644 --- a/colorbleed/fusion/lib.py +++ b/colorbleed/fusion/lib.py @@ -57,7 +57,7 @@ def get_additional_data(container): tool = container["_tool"] tile_color = tool.TileColor if tile_color is None: - return container + return {} return {"color": QtGui.QColor(clamp(tile_color["R"]), clamp(tile_color["G"]), From e4c9dd6ab19b97bc5bd67957a3e76eab354f4aca Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 16:58:30 +0200 Subject: [PATCH 0787/1249] moved collect_contianer_metadata to lib, return empty dict if not compatible --- colorbleed/__init__.py | 35 ----------------------------------- colorbleed/lib.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 0a3f182864..8b85412f06 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -1,5 +1,4 @@ import os -import importlib from pyblish import api as pyblish from avalon import api as avalon @@ -24,37 +23,3 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - - -def _get_host_name(): - - _host = avalon.registered_host() - # This covers nested module name like avalon.maya - return _host.__name__.rsplit(".", 1)[-1] - - -def collect_container_metadata(container): - """Add additional data based on the current host - - If the host application's lib module does not have a function to inject - additional data it will return the input container - - Args: - container (dict): collection if representation data in host - - Returns: - generator - """ - - host_name = _get_host_name() - - # This will cover nested module names like avalon.maya - package_name = "{}.{}.lib".format(__name__, host_name) - hostlib = importlib.import_module(package_name) - - if not hasattr(hostlib, "get_additional_data"): - print("{} has no function called " - "get_additional_data".format(package_name)) - return container - - return hostlib.get_additional_data(container) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 14ad6884b5..8eb3cf4844 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -1,6 +1,7 @@ import os import re import logging +import importlib from .vendor import pather from .vendor.pather.error import ParseError @@ -214,3 +215,37 @@ def switch_item(container, avalon.api.switch(container, representation) return representation + + +def _get_host_name(): + + _host = avalon.api.registered_host() + # This covers nested module name like avalon.maya + return _host.__name__.rsplit(".", 1)[-1] + + +def collect_container_metadata(container): + """Add additional data based on the current host + + If the host application's lib module does not have a function to inject + additional data it will return the input container + + Args: + container (dict): collection if representation data in host + + Returns: + generator + """ + + host_name = _get_host_name() + + # This will cover nested module names like avalon.maya + package_name = "{}.{}.lib".format(__name__, host_name) + hostlib = importlib.import_module(package_name) + + if not hasattr(hostlib, "get_additional_data"): + print("{} has no function called " + "get_additional_data".format(package_name)) + return {} + + return hostlib.get_additional_data(container) From 5afb50090895f27511321acdf38075a239497b9d Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 3 May 2018 17:21:58 +0200 Subject: [PATCH 0788/1249] return result of process --- .../plugins/fusion/inventory/select_loader_color.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/select_loader_color.py b/colorbleed/plugins/fusion/inventory/select_loader_color.py index bef99e53b9..e0cb46e4e5 100644 --- a/colorbleed/plugins/fusion/inventory/select_loader_color.py +++ b/colorbleed/plugins/fusion/inventory/select_loader_color.py @@ -15,6 +15,7 @@ class FusionSetToolColor(api.InventoryAction): def process(self, containers): """Color all selected tools the selected colors""" + result = [] comp = avalon.fusion.get_current_comp() # Launch pick color @@ -31,12 +32,6 @@ class FusionSetToolColor(api.InventoryAction): tool = container["_tool"] tool.TileColor = rgb_f_table - self.refresh() + result.append(container) - def refresh(self): - """Refresh Scene Inventory window""" - - app = QtWidgets.QApplication.instance() - widgets = dict((w.objectName(), w) for w in app.allWidgets()) - widget = widgets.get("SceneInventory") - widget.refresh() + return result From 0aeeec4e352a359750038e1386eefb7a9e5aad60 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 11:07:29 +0200 Subject: [PATCH 0789/1249] Updated module name, added check for valid color --- .../inventory/{select_loader_color.py => set_tool_color.py} | 3 +++ 1 file changed, 3 insertions(+) rename colorbleed/plugins/fusion/inventory/{select_loader_color.py => set_tool_color.py} (94%) diff --git a/colorbleed/plugins/fusion/inventory/select_loader_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py similarity index 94% rename from colorbleed/plugins/fusion/inventory/select_loader_color.py rename to colorbleed/plugins/fusion/inventory/set_tool_color.py index e0cb46e4e5..3ebc07aebc 100644 --- a/colorbleed/plugins/fusion/inventory/select_loader_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -22,6 +22,9 @@ class FusionSetToolColor(api.InventoryAction): first = containers[0] color = QtGui.QColor(first.get("color", self._fallback_color)) picked_color = QtWidgets.QColorDialog().getColor(color) + if not picked_color.isValid(): + return result + with avalon.fusion.comp_lock_and_undo_chunk(comp): for container in containers: # Convert color to RGB 0-1 floats From f2beb2de93e60d28af7c47ed45e206bb72d55a32 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 11:48:11 +0200 Subject: [PATCH 0790/1249] Added stylesheet to color picker --- .../fusion/inventory/set_tool_color.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py index 3ebc07aebc..7f57a1420b 100644 --- a/colorbleed/plugins/fusion/inventory/set_tool_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -21,9 +21,9 @@ class FusionSetToolColor(api.InventoryAction): # Launch pick color first = containers[0] color = QtGui.QColor(first.get("color", self._fallback_color)) - picked_color = QtWidgets.QColorDialog().getColor(color) - if not picked_color.isValid(): - return result + picked_color = self.get_color_picker(color) + if not picked_color: + return with avalon.fusion.comp_lock_and_undo_chunk(comp): for container in containers: @@ -38,3 +38,22 @@ class FusionSetToolColor(api.InventoryAction): result.append(container) return result + + def get_color_picker(self, color): + """Launch color picker and return chosen color + + Args: + color(QtGui.QColor): Start color to display + """ + + app = QtWidgets.QApplication.instance() + widgets = dict((w.objectName(), w) for w in app.allWidgets()) + widget = widgets.get("SceneInventory") + + color_dialog = QtWidgets.QColorDialog(color) + color_dialog.setStyleSheet(widget.styleSheet()) + + accepted = color_dialog.exec_() + picked_color = color_dialog.selectedColor() if accepted else False + + return picked_color From 2cc29c73c6c1065c0f11805e3f10a924dce9266d Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 11:49:59 +0200 Subject: [PATCH 0791/1249] Extended docstrings --- colorbleed/plugins/fusion/inventory/set_tool_color.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py index 7f57a1420b..b54e772e96 100644 --- a/colorbleed/plugins/fusion/inventory/set_tool_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -44,6 +44,10 @@ class FusionSetToolColor(api.InventoryAction): Args: color(QtGui.QColor): Start color to display + + Returns: + QtGui.QColor + """ app = QtWidgets.QApplication.instance() From b3e80ce417b82a5dc628d5a60bd494eb27a676bd Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 12:26:23 +0200 Subject: [PATCH 0792/1249] Removed hack, apply stylesheet from style module --- colorbleed/plugins/fusion/inventory/set_tool_color.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py index b54e772e96..fca87b0b71 100644 --- a/colorbleed/plugins/fusion/inventory/set_tool_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -1,4 +1,4 @@ -from avalon import api +from avalon import api, style from avalon.vendor.Qt import QtGui, QtWidgets import avalon.fusion @@ -50,12 +50,8 @@ class FusionSetToolColor(api.InventoryAction): """ - app = QtWidgets.QApplication.instance() - widgets = dict((w.objectName(), w) for w in app.allWidgets()) - widget = widgets.get("SceneInventory") - color_dialog = QtWidgets.QColorDialog(color) - color_dialog.setStyleSheet(widget.styleSheet()) + color_dialog.setStyleSheet(style.load_stylesheet()) accepted = color_dialog.exec_() picked_color = color_dialog.selectedColor() if accepted else False From 95df838d6d89311da4bc07149d6e203a4a30c2d1 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 17:38:34 +0200 Subject: [PATCH 0793/1249] Get start color from tool --- .../plugins/fusion/inventory/set_tool_color.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py index fca87b0b71..d33d216bf2 100644 --- a/colorbleed/plugins/fusion/inventory/set_tool_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -18,10 +18,18 @@ class FusionSetToolColor(api.InventoryAction): result = [] comp = avalon.fusion.get_current_comp() - # Launch pick color + # Get tool color first = containers[0] - color = QtGui.QColor(first.get("color", self._fallback_color)) - picked_color = self.get_color_picker(color) + tool = first["_tool"] + color = tool.TileColor + + if color is not None: + qcolor = QtGui.QColor().fromRgbF(color["R"], color["G"], color["B"]) + else: + qcolor = self._fallback_color + + # Launch pick color + picked_color = self.get_color_picker(qcolor) if not picked_color: return From 7ececaace05ed718ec093851d7850e3d5f2719af Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 4 May 2018 17:39:30 +0200 Subject: [PATCH 0794/1249] return result of process, cosmetics --- colorbleed/plugins/fusion/inventory/set_tool_color.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/colorbleed/plugins/fusion/inventory/set_tool_color.py index d33d216bf2..940a0e9941 100644 --- a/colorbleed/plugins/fusion/inventory/set_tool_color.py +++ b/colorbleed/plugins/fusion/inventory/set_tool_color.py @@ -62,6 +62,7 @@ class FusionSetToolColor(api.InventoryAction): color_dialog.setStyleSheet(style.load_stylesheet()) accepted = color_dialog.exec_() - picked_color = color_dialog.selectedColor() if accepted else False + if not accepted: + return - return picked_color + return color_dialog.selectedColor() From 7e4debe83f7c81d53700221b84046652ed9850c1 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 7 May 2018 12:57:09 +0200 Subject: [PATCH 0795/1249] Added set_family_state for cbloader --- colorbleed/fusion/lib.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py index 7186cd2cac..c712bc4027 100644 --- a/colorbleed/fusion/lib.py +++ b/colorbleed/fusion/lib.py @@ -38,3 +38,16 @@ def update_frame_range(start, end, comp=None, set_render_range=True): with avalon.fusion.comp_lock_and_undo_chunk(comp): comp.SetAttrs(attrs) + + +def set_family_filter(family_name): + """Get state based on what is most related to the host + + Args: + family_name (str): name of the family, e.g: "colorbleed.imagesequence" + + Returns: + bool + """ + + return family_name in ["colorbleed.imagesequence"] From 2a54a22ecc37e12d277b76d2eb43a5c830e093d6 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 7 May 2018 16:42:39 +0200 Subject: [PATCH 0796/1249] Skipping collection if it is a string type, added docstrings --- .../global/publish/validate_sequence_frames.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py index 0e13064e12..7375edfaf5 100644 --- a/colorbleed/plugins/global/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/global/publish/validate_sequence_frames.py @@ -1,7 +1,14 @@ import pyblish.api +from avalon.vendor import six class ValidateSequenceFrames(pyblish.api.InstancePlugin): + """Ensure the sequence of frames is complete + + The files found in the instance are checked against the startFrame and + endFrame of the instance. If the first or last file is not + corresponding with the first or last frame it is flagged as invalid. + """ order = pyblish.api.ValidatorOrder label = "Validate Sequence Frames" @@ -10,7 +17,13 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): collection = instance[0] + # Hack: Skip the check for `colorbleed.yeticache` from within Maya + # When publishing a Yeti cache from Maya the "collection" is a node, + # which is a string and it will fail when calling `indexes` + if isinstance(collection, six.string_types): + return + self.log.warning(collection) frames = list(collection.indexes) assert frames[0] == instance.data["startFrame"] From 1159b534d92d53345500881ce845ce178ce0e4ae Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 7 May 2018 16:46:36 +0200 Subject: [PATCH 0797/1249] Improved type check --- colorbleed/plugins/global/publish/validate_sequence_frames.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py index 7375edfaf5..dcd2fab3d7 100644 --- a/colorbleed/plugins/global/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/global/publish/validate_sequence_frames.py @@ -1,5 +1,5 @@ import pyblish.api -from avalon.vendor import six +from avalon.vendor import clique class ValidateSequenceFrames(pyblish.api.InstancePlugin): @@ -20,7 +20,7 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): # Hack: Skip the check for `colorbleed.yeticache` from within Maya # When publishing a Yeti cache from Maya the "collection" is a node, # which is a string and it will fail when calling `indexes` - if isinstance(collection, six.string_types): + if not isinstance(collection, clique.Collection): return self.log.warning(collection) From b9150f745571ee18a67bd790e280e7ebacd015cc Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 7 May 2018 16:51:07 +0200 Subject: [PATCH 0798/1249] added hosts as currently the check is for on disk files --- .../plugins/global/publish/validate_sequence_frames.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py index dcd2fab3d7..e015648da8 100644 --- a/colorbleed/plugins/global/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/global/publish/validate_sequence_frames.py @@ -1,11 +1,10 @@ import pyblish.api -from avalon.vendor import clique class ValidateSequenceFrames(pyblish.api.InstancePlugin): """Ensure the sequence of frames is complete - The files found in the instance are checked against the startFrame and + The files found in the folder are checked against the startFrame and endFrame of the instance. If the first or last file is not corresponding with the first or last frame it is flagged as invalid. """ @@ -13,15 +12,11 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Sequence Frames" families = ["colorbleed.imagesequence", "colorbleed.yeticache"] + hosts = ["shell"] def process(self, instance): collection = instance[0] - # Hack: Skip the check for `colorbleed.yeticache` from within Maya - # When publishing a Yeti cache from Maya the "collection" is a node, - # which is a string and it will fail when calling `indexes` - if not isinstance(collection, clique.Collection): - return self.log.warning(collection) frames = list(collection.indexes) From 79f17b6cc6c5f6580f7919dbf933c6b7eee80d7f Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 10:23:05 +0200 Subject: [PATCH 0799/1249] Improved getting QtColor from tool, removed need from clamp --- colorbleed/fusion/lib.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/colorbleed/fusion/lib.py b/colorbleed/fusion/lib.py index 97c9fb9e86..f2846c966a 100644 --- a/colorbleed/fusion/lib.py +++ b/colorbleed/fusion/lib.py @@ -51,14 +51,11 @@ def get_additional_data(container): dict """ - def clamp(value): - return int(value * 255) - tool = container["_tool"] tile_color = tool.TileColor if tile_color is None: return {} - return {"color": QtGui.QColor(clamp(tile_color["R"]), - clamp(tile_color["G"]), - clamp(tile_color["B"]))} + return {"color": QtGui.QColor.fromRgbF(tile_color["R"], + tile_color["G"], + tile_color["B"])} From 653f35c54744d7507a07ec3dec1f7fd916892926 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 10:36:39 +0200 Subject: [PATCH 0800/1249] Added todo, fixed line too long --- colorbleed/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 8eb3cf4844..82be01987e 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -209,8 +209,8 @@ def switch_item(container, "type": "representation", "parent": version["_id"]}) - assert representation, ("Could not find representation in the database with " - "the name '%s'" % representation_name) + assert representation, ("Could not find representation in the database with" + " the name '%s'" % representation_name) avalon.api.switch(container, representation) @@ -236,7 +236,7 @@ def collect_container_metadata(container): Returns: generator """ - + # TODO: Improve method of getting the host lib module host_name = _get_host_name() # This will cover nested module names like avalon.maya From b3fa7a36d4e66aa4cc105f4d2b850fa661d79634 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 11:09:53 +0200 Subject: [PATCH 0801/1249] Fixed issue with config name --- colorbleed/lib.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 82be01987e..8e1d0becc6 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -238,9 +238,7 @@ def collect_container_metadata(container): """ # TODO: Improve method of getting the host lib module host_name = _get_host_name() - - # This will cover nested module names like avalon.maya - package_name = "{}.{}.lib".format(__name__, host_name) + package_name = "colorbleed.{}.lib".format(host_name) hostlib = importlib.import_module(package_name) if not hasattr(hostlib, "get_additional_data"): From 6a60e1056f29b3df281bbe9fba64bed3cab5d8eb Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 11:30:34 +0200 Subject: [PATCH 0802/1249] Fixed issue with collect_container_metadata not being accessible from colorbleed.__init__ --- colorbleed/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 8b85412f06..0efbb1eb34 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -4,6 +4,7 @@ from pyblish import api as pyblish from avalon import api as avalon from .launcher_actions import register_launcher_actions +from lib import collect_container_metadata PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") From 14da2e277e93c0a865e04691bd7e6af823e21d95 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 11:39:49 +0200 Subject: [PATCH 0803/1249] fixed non-package import --- colorbleed/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 0efbb1eb34..e5d1aee374 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -4,7 +4,7 @@ from pyblish import api as pyblish from avalon import api as avalon from .launcher_actions import register_launcher_actions -from lib import collect_container_metadata +from .lib import collect_container_metadata PACKAGE_DIR = os.path.dirname(__file__) PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") From 553e78763ac4aceb60cea33cd76524bc3645d46b Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 8 May 2018 15:56:28 +0200 Subject: [PATCH 0804/1249] set custom family state data at install --- colorbleed/fusion/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index 2bb4fec5ea..366ad83288 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -13,6 +13,8 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") +FAMILY_STATES = {"colorbleed.imagesequenbce": True} + def install(): print("Registering Fusion plug-ins..") @@ -23,6 +25,8 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + avalon.set_data("family_states", FAMILY_STATES) + def uninstall(): print("Deregistering Fusion plug-ins..") From 7c10db98dec4acc6f8b6b61ff7496223091659d9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 9 May 2018 16:57:22 +0200 Subject: [PATCH 0805/1249] Set default family states using api.data --- colorbleed/fusion/__init__.py | 10 +++++++--- colorbleed/maya/__init__.py | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index 366ad83288..cd87a95465 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -1,5 +1,6 @@ import os +import avalon.io as io from avalon import api as avalon from pyblish import api as pyblish @@ -13,8 +14,6 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "fusion", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "fusion", "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "fusion", "inventory") -FAMILY_STATES = {"colorbleed.imagesequenbce": True} - def install(): print("Registering Fusion plug-ins..") @@ -25,7 +24,12 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - avalon.set_data("family_states", FAMILY_STATES) + # Disable all families except for the ones we explicitly want to see + enabled = {"colorbleed.imagesequence", + "colorbleed.camera", + "colorbleed.pointcache"} + families = set(io.distinct("data.families") + io.distinct("data.family")) + avalon.data["familyStates"] = {key: key in enabled for key in families} def uninstall(): diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 464a0eb039..d95b56c52d 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -41,6 +41,11 @@ def install(): log.info("Overriding existing event 'taskChanged'") override_event("taskChanged", on_task_changed) + log.info("Setting default family states for loader..") + avalon.data["familyStates"] = { + "colorbleed.imagesequence": False + } + def uninstall(): pyblish.deregister_plugin_path(PUBLISH_PATH) From 1555045884f9b1ee8676bfd2bd5e722e3316d6c2 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 17 May 2018 15:54:37 +0200 Subject: [PATCH 0806/1249] Removed unused import, update override families toggled state --- colorbleed/fusion/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/colorbleed/fusion/__init__.py b/colorbleed/fusion/__init__.py index cd87a95465..8ae042c934 100644 --- a/colorbleed/fusion/__init__.py +++ b/colorbleed/fusion/__init__.py @@ -1,6 +1,5 @@ import os -import avalon.io as io from avalon import api as avalon from pyblish import api as pyblish @@ -25,11 +24,12 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) # Disable all families except for the ones we explicitly want to see - enabled = {"colorbleed.imagesequence", - "colorbleed.camera", - "colorbleed.pointcache"} - families = set(io.distinct("data.families") + io.distinct("data.family")) - avalon.data["familyStates"] = {key: key in enabled for key in families} + family_states = ["colorbleed.imagesequence", + "colorbleed.camera", + "colorbleed.pointcache"] + + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states def uninstall(): From 26b164ad03fb98574a1e9bb08c878d5d9904dceb Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 17 May 2018 16:11:47 +0200 Subject: [PATCH 0807/1249] Updated familiesStateToggled for Maya --- colorbleed/maya/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index d95b56c52d..0e21125666 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -42,9 +42,7 @@ def install(): override_event("taskChanged", on_task_changed) log.info("Setting default family states for loader..") - avalon.data["familyStates"] = { - "colorbleed.imagesequence": False - } + avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] def uninstall(): From 7e90103ce4ac5a19e413d5fda31be6dbdbf80ed0 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 8 Jun 2018 15:19:29 +0200 Subject: [PATCH 0808/1249] added 'locator' to types list to include --- colorbleed/maya/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1493f7385e..b2db9bff7c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -637,7 +637,8 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) # Establish set of nodes types to include - types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] + types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface", + "locator"] # Check if plugin nodes are available for Maya by checking if the plugin # is loaded From 015b8b7e2d1238ea982ff50afb9ddabc857fce1d Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 11 Jun 2018 10:05:39 +0200 Subject: [PATCH 0809/1249] removed locator from tupes --- colorbleed/maya/lib.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b2db9bff7c..1493f7385e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -637,8 +637,7 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): ignore |= set(cmds.ls(type="ilrBakeLayer", long=True)) # Establish set of nodes types to include - types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface", - "locator"] + types = ["objectSet", "file", "mesh", "nurbsCurve", "nurbsSurface"] # Check if plugin nodes are available for Maya by checking if the plugin # is loaded From 1b99fdf88f2ccb9126ca8cbcf415a4618ebbca30 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 11 Jun 2018 10:08:13 +0200 Subject: [PATCH 0810/1249] skipping locators in validator --- .../publish/validate_animation_out_set_related_node_ids.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 6aa39efe07..63705dd3ba 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -36,6 +36,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """Get all nodes which do not match the criteria""" invalid = [] + types_to_skip = ["locator"] # get asset id nodes = instance.data.get("out_hierarchy", instance[:]) @@ -50,6 +51,10 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): if not obj_type: continue + # Skip specific types + if cmds.objectType(node) in types_to_skip: + continue + # Get the current id of the node node_id = lib.get_id(node) if not node_id: From 9e1dcd50083c28fbc8c69f4c27e6aa1a9175d045 Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 13 Jun 2018 10:47:41 +0200 Subject: [PATCH 0811/1249] removed print from function --- colorbleed/lib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 8e1d0becc6..73bd80f9ff 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -242,8 +242,6 @@ def collect_container_metadata(container): hostlib = importlib.import_module(package_name) if not hasattr(hostlib, "get_additional_data"): - print("{} has no function called " - "get_additional_data".format(package_name)) return {} return hostlib.get_additional_data(container) From 9ce470faa8b24a95ad9241a9841f927a785cdbe3 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 11:17:26 +0200 Subject: [PATCH 0812/1249] added houdini folder --- colorbleed/houdini/__init__.py | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 colorbleed/houdini/__init__.py diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py new file mode 100644 index 0000000000..91a6df3edd --- /dev/null +++ b/colorbleed/houdini/__init__.py @@ -0,0 +1,69 @@ +import os + +import logging + +from avalon import api as avalon, pipeline, houdini +from pyblish import api as pyblish + + +PARENT_DIR = os.path.dirname(__file__) +PACKAGE_DIR = os.path.dirname(PARENT_DIR) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins") + +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "houdini", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "houdini", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "houdini", "create") + +log = logging.getLogger("colorbleed.houdini") + + +def install(): + pyblish.register_plugin_path(PUBLISH_PATH) + avalon.register_plugin_path(avalon.Loader, LOAD_PATH) + avalon.register_plugin_path(avalon.Creator, CREATE_PATH) + + log.info("Installing callbacks ... ") + avalon.on("init", on_init) + avalon.on("save", on_save) + avalon.on("open", on_open) + + log.info("Overriding existing event 'taskChanged'") + + log.info("Setting default family states for loader..") + avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] + + + +def on_init(): + pass + + +def on_save(): + pass + + +def on_open(): + pass + + +def on_task_changed(*args): + """Wrapped function of app initialize and maya's on task changed""" + + # Inputs (from the switched session and running app) + session = avalon.Session.copy() + app_name = os.environ["AVALON_APP_NAME"] + + # Find the application definition + app_definition = pipeline.lib.get_application(app_name) + + App = type("app_%s" % app_name, + (avalon.Application,), + {"config": app_definition.copy()}) + + # Initialize within the new session's environment + app = App() + env = app.environ(session) + app.initialize(env) + + # Run + houdini.pipeline._on_task_changed() \ No newline at end of file From d43a80945d18b8aac8afc38e73c0629150911627 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 17:52:39 +0200 Subject: [PATCH 0813/1249] implemented setting project fps --- colorbleed/lib.py | 20 ++++++++++++++++++++ colorbleed/maya/__init__.py | 7 +++++-- colorbleed/maya/lib.py | 20 +++++++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 8e1d0becc6..e93296fbc0 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -247,3 +247,23 @@ def collect_container_metadata(container): return {} return hostlib.get_additional_data(container) + + +def get_project_fps(): + """Returns project's FPS, if not found will return 25 by default + + Returns: + int, float + """ + + project_name = io.active_project() + project = io.find_one({"name": project_name, + "type": "project"}, + projection={"config.fps": True}) + + config = project.get("config", None) + assert config, "This is a bug" + + fps = config.get("fps", 25) + + return fps diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 0e21125666..008d5229ce 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,8 +2,7 @@ import os import logging import weakref -from maya import utils -from maya import cmds +from maya import utils, cmds from avalon import api as avalon, pipeline, maya from pyblish import api as pyblish @@ -94,6 +93,7 @@ def on_init(_): from .customize import override_component_mask_commands safe_deferred(override_component_mask_commands) + safe_deferred(lib.set_project_fps) def on_save(_): @@ -119,6 +119,9 @@ def on_open(_): from avalon.vendor.Qt import QtWidgets from ..widgets import popup + # Ensure scene's FPS is set to project config + lib.set_project_fps() + # Update current task for the current scene update_task_from_path(cmds.file(query=True, sceneName=True)) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1493f7385e..1faad51d99 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -13,7 +13,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel from avalon import api, maya, io, pipeline -from cb.utils.maya import core +from colorbleed import lib import cb.utils.maya.context @@ -1292,3 +1292,21 @@ def get_id_from_history(node): _id = get_id(similar_node) if _id: return _id + + +def set_project_fps(): + """Set FPS from project configuration""" + + fps = lib.get_project_fps() + if not isinstance(fps, (int, float)): + raise ValueError("Set value for project's FPS is not a number. " + "Only accepts floats and integers") + + if int(fps) == 24: + cmds.currentUnit(time="film") + log.info("Updated FPS to 24 (film)") + elif int(fps) == 25: + cmds.currentUnit(time="pal") + log.info("Updated FPS to 25 (pal)") + else: + raise RuntimeError("Cannot translate FPS: `%s`" % fps) From 522e5e857b105ea24fc3f1269ff81b009d6ffb92 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 18:35:52 +0200 Subject: [PATCH 0814/1249] ensure float is returned --- colorbleed/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index e93296fbc0..e1fe8b7e69 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -253,7 +253,7 @@ def get_project_fps(): """Returns project's FPS, if not found will return 25 by default Returns: - int, float + float """ project_name = io.active_project() @@ -264,6 +264,6 @@ def get_project_fps(): config = project.get("config", None) assert config, "This is a bug" - fps = config.get("fps", 25) + fps = config.get("fps", 25.0) - return fps + return float(fps) From 44aede0cc45adb7ba9c1cd577d3d11739ef5c483 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 18:36:52 +0200 Subject: [PATCH 0815/1249] added fps check to on_save --- colorbleed/maya/__init__.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 008d5229ce..60fb897c46 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,14 +2,15 @@ import os import logging import weakref -from maya import utils, cmds +from maya import utils, cmds, mel from avalon import api as avalon, pipeline, maya from pyblish import api as pyblish from ..lib import ( update_task_from_path, - any_outdated + any_outdated, + get_project_fps ) from . import menu from . import lib @@ -112,6 +113,29 @@ def on_save(_): for node, new_id in lib.generate_ids(nodes): lib.set_id(node, new_id, overwrite=False) + # Valid FPS + current_fps = mel.eval('currentTimeUnitToFPS()') # returns float + fps = get_project_fps() + if fps != current_fps: + + from avalon.vendor.Qt import QtWidgets + from ..widgets import popup + + # Find maya main window + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + + parent = top_level_widgets.get("MayaWindow", None) + + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix") + # Set new text for button (could be an optional argument for the popup) + dialog.widgets["show"].setText("Fix") + dialog.on_show.connect(lib.set_project_fps) + + dialog.show() + def on_open(_): """On scene open let's assume the containers have changed.""" From 3299ee92e3295d04139398f1ab8fab68cdea9d5d Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 18:37:42 +0200 Subject: [PATCH 0816/1249] updated unit validation --- .../plugins/maya/publish/validate_maya_units.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 81dfceb19f..69f22bb74f 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -2,6 +2,8 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +from colorbleed import lib +import colorbleed.maya.lib as mayalib class ValidateMayaUnits(pyblish.api.ContextPlugin): @@ -16,19 +18,21 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): linearunits = context.data('linearUnits') angularunits = context.data('angularUnits') + fps = context.data['fps'] + project_fps = lib.get_project_fps() self.log.info('Units (linear): {0}'.format(linearunits)) self.log.info('Units (angular): {0}'.format(angularunits)) self.log.info('Units (time): {0} FPS'.format(fps)) - # check if units are correct + # Check if units are correct assert linearunits and linearunits == 'cm', ("Scene linear units must " "be centimeters") assert angularunits and angularunits == 'deg', ("Scene angular units " "must be degrees") - assert fps and fps == 25.0, "Scene must be 25 FPS" + assert fps and fps == project_fps, "Scene must be %s FPS" % project_fps @classmethod def repair(cls): @@ -44,7 +48,5 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): current_linear = cmds.currentUnit(query=True, linear=True) cls.log.debug(current_linear) - cls.log.info("Setting time unit to 'PAL'") - cmds.currentUnit(time="pal") - current_time = cmds.currentUnit(query=True, time=True) - cls.log.debug(current_time) + cls.log.info("Setting time unit to match project") + mayalib.set_project_fps() From 024403ea69f0c3a6e24d7071384960260600bab1 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 18:38:37 +0200 Subject: [PATCH 0817/1249] added set_project_fps --- colorbleed/maya/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1faad51d99..1803e04ac2 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1305,8 +1305,10 @@ def set_project_fps(): if int(fps) == 24: cmds.currentUnit(time="film") log.info("Updated FPS to 24 (film)") + elif int(fps) == 25: cmds.currentUnit(time="pal") log.info("Updated FPS to 25 (pal)") + else: raise RuntimeError("Cannot translate FPS: `%s`" % fps) From 2e9a63c4101d13fa7dcd46d17aad9edbb91ebd49 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 28 Jun 2018 18:45:58 +0200 Subject: [PATCH 0818/1249] added check for parent window --- colorbleed/maya/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 60fb897c46..456d782d7a 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -126,15 +126,17 @@ def on_save(_): QtWidgets.QApplication.topLevelWidgets()} parent = top_level_widgets.get("MayaWindow", None) + if parent is None: + pass + else: + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix") + # Set new text for button (add optional argument for the popup?) + dialog.widgets["show"].setText("Fix") + dialog.on_show.connect(lib.set_project_fps) - dialog = popup.Popup(parent=parent) - dialog.setWindowTitle("Maya scene not in line with project") - dialog.setMessage("The FPS is out of sync, please fix") - # Set new text for button (could be an optional argument for the popup) - dialog.widgets["show"].setText("Fix") - dialog.on_show.connect(lib.set_project_fps) - - dialog.show() + dialog.show() def on_open(_): From 954d82ccbc4a1d3cb3c488e774d100f39a19567b Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 29 Jun 2018 10:08:16 +0200 Subject: [PATCH 0819/1249] fix issue with project config query --- colorbleed/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index e1fe8b7e69..3865c00932 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -259,7 +259,7 @@ def get_project_fps(): project_name = io.active_project() project = io.find_one({"name": project_name, "type": "project"}, - projection={"config.fps": True}) + projection={"config": True}) config = project.get("config", None) assert config, "This is a bug" From 735eacb83924322d9f246d108fc09c1287ebf71b Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 29 Jun 2018 14:27:33 +0200 Subject: [PATCH 0820/1249] refactored out dependency --- colorbleed/maya/lib.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1493f7385e..5ba2122072 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -13,8 +13,6 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel from avalon import api, maya, io, pipeline -from cb.utils.maya import core -import cb.utils.maya.context log = logging.getLogger(__name__) @@ -287,6 +285,26 @@ def renderlayer(layer): cmds.editRenderLayerGlobals(currentRenderLayer=original) +@contextlib.contextmanager +def evaluation(mode="off"): + """Set the evaluation manager during context. + + Arguments: + mode (str): The mode to apply during context. + "off": The standard DG evaluation (stable) + "serial": A serial DG evaluation + "parallel": The Maya 2016+ parallel evaluation + + """ + + original = cmds.evaluationManager(query=True, mode=1)[0] + try: + cmds.evaluationManager(mode=mode) + yield + finally: + cmds.evaluationManager(mode=original) + + def get_renderer(layer): with renderlayer(layer): return cmds.getAttr("defaultRenderGlobals.currentRenderer") @@ -579,7 +597,7 @@ def extract_alembic(file, # Disable the parallel evaluation temporarily to ensure no buggy # exports are made. (PLN-31) # TODO: Make sure this actually fixes the issues - with cb.utils.maya.context.evaluation("off"): + with evaluation("off"): cmds.AbcExport(j=job_str, verbose=verbose) if verbose: From c4362004c5f31a6645908c56cf834ba5726d55c3 Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 2 Jul 2018 14:32:44 +0200 Subject: [PATCH 0821/1249] skipping None values --- colorbleed/plugins/maya/load/load_yeti_cache.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index ec7ab11474..dd6ebbeb05 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -179,10 +179,14 @@ class YetiCacheLoader(api.Loader): # Apply attributes to pgYetiMaya node kwargs = {} for attr, value in node_settings["attrs"].items(): + if value is None: + continue + attribute = "%s.%s" % (yeti_node, attr) if isinstance(value, (str, unicode)): cmds.setAttr(attribute, value, type="string") continue + cmds.setAttr(attribute, value, **kwargs) # Ensure the node has no namespace identifiers From 10c1cd69dfbaa1148edb561023acba575b26170f Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 3 Jul 2018 16:59:38 +0200 Subject: [PATCH 0822/1249] created validate_fps function, removed from on_init --- colorbleed/maya/__init__.py | 34 ++++---------------- colorbleed/maya/lib.py | 64 ++++++++++++++++++++++++++++++++----- 2 files changed, 62 insertions(+), 36 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 456d782d7a..1123fbd1b3 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -10,7 +10,6 @@ from pyblish import api as pyblish from ..lib import ( update_task_from_path, any_outdated, - get_project_fps ) from . import menu from . import lib @@ -94,13 +93,13 @@ def on_init(_): from .customize import override_component_mask_commands safe_deferred(override_component_mask_commands) - safe_deferred(lib.set_project_fps) def on_save(_): """Automatically add IDs to new nodes - Any transform of a mesh, without an existing ID, - is given one automatically on file save. + + Any transform of a mesh, without an existing ID, is given one + automatically on file save. """ avalon.logger.info("Running callback on save..") @@ -113,30 +112,7 @@ def on_save(_): for node, new_id in lib.generate_ids(nodes): lib.set_id(node, new_id, overwrite=False) - # Valid FPS - current_fps = mel.eval('currentTimeUnitToFPS()') # returns float - fps = get_project_fps() - if fps != current_fps: - - from avalon.vendor.Qt import QtWidgets - from ..widgets import popup - - # Find maya main window - top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} - - parent = top_level_widgets.get("MayaWindow", None) - if parent is None: - pass - else: - dialog = popup.Popup(parent=parent) - dialog.setWindowTitle("Maya scene not in line with project") - dialog.setMessage("The FPS is out of sync, please fix") - # Set new text for button (add optional argument for the popup?) - dialog.widgets["show"].setText("Fix") - dialog.on_show.connect(lib.set_project_fps) - - dialog.show() + lib.validate_fps() def on_open(_): @@ -198,3 +174,5 @@ def on_task_changed(*args): # Run maya.pipeline._on_task_changed() + + diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1803e04ac2..74c87e8fec 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1295,20 +1295,68 @@ def get_id_from_history(node): def set_project_fps(): - """Set FPS from project configuration""" + """Set FPS from project configuration + + Returns: + None + + """ + + int_fps = [15, 24, 5, 30, 48, 50, 60, 44100, 48000] + float_fps = [23.976, 29.97, 29.97, 47.952, 59.94] fps = lib.get_project_fps() + if not isinstance(fps, (int, float)): raise ValueError("Set value for project's FPS is not a number. " "Only accepts floats and integers") - if int(fps) == 24: - cmds.currentUnit(time="film") - log.info("Updated FPS to 24 (film)") + if int(fps) in int_fps: + update_str = "{:d}fps".format(int(fps)) - elif int(fps) == 25: - cmds.currentUnit(time="pal") - log.info("Updated FPS to 25 (pal)") + elif fps in float_fps: + update_str = "{:f}fps".format(fps) else: - raise RuntimeError("Cannot translate FPS: `%s`" % fps) + raise RuntimeError("Unsupported FPS value: `%s`" % fps) + + log.info("Updating FPS to '{}'".format(update_str)) + + cmds.currentUnit(time=update_str) + + +# Valid FPS +def validate_fps(): + """Validate current scene FPS and show pop-up when it is incorrect + + Returns: + None + + """ + + current_fps = mel.eval('currentTimeUnitToFPS()') # returns float + + # + fps = lib.get_project_fps() + + if fps != current_fps: + + from avalon.vendor.Qt import QtWidgets + from ..widgets import popup + + # Find maya main window + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + + parent = top_level_widgets.get("MayaWindow", None) + if parent is None: + pass + else: + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix") + # Set new text for button (add optional argument for the popup?) + dialog.widgets["show"].setText("Fix") + dialog.on_show.connect(set_project_fps) + + dialog.show() From a664a305f6a9c4474d38cd0594db62ed7cc61a87 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 29 Jun 2018 14:27:33 +0200 Subject: [PATCH 0823/1249] fixed conflict --- colorbleed/maya/lib.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 74c87e8fec..20e7fdfe94 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -14,7 +14,7 @@ from maya import cmds, mel from avalon import api, maya, io, pipeline from colorbleed import lib -import cb.utils.maya.context + log = logging.getLogger(__name__) @@ -287,6 +287,26 @@ def renderlayer(layer): cmds.editRenderLayerGlobals(currentRenderLayer=original) +@contextlib.contextmanager +def evaluation(mode="off"): + """Set the evaluation manager during context. + + Arguments: + mode (str): The mode to apply during context. + "off": The standard DG evaluation (stable) + "serial": A serial DG evaluation + "parallel": The Maya 2016+ parallel evaluation + + """ + + original = cmds.evaluationManager(query=True, mode=1)[0] + try: + cmds.evaluationManager(mode=mode) + yield + finally: + cmds.evaluationManager(mode=original) + + def get_renderer(layer): with renderlayer(layer): return cmds.getAttr("defaultRenderGlobals.currentRenderer") @@ -579,7 +599,7 @@ def extract_alembic(file, # Disable the parallel evaluation temporarily to ensure no buggy # exports are made. (PLN-31) # TODO: Make sure this actually fixes the issues - with cb.utils.maya.context.evaluation("off"): + with evaluation("off"): cmds.AbcExport(j=job_str, verbose=verbose) if verbose: @@ -1335,10 +1355,7 @@ def validate_fps(): """ current_fps = mel.eval('currentTimeUnitToFPS()') # returns float - - # fps = lib.get_project_fps() - if fps != current_fps: from avalon.vendor.Qt import QtWidgets From 09bf9b2ba6f75cc971c7adb0a2a2192f560fc8af Mon Sep 17 00:00:00 2001 From: wijnand Date: Wed, 4 Jul 2018 12:08:26 +0200 Subject: [PATCH 0824/1249] Validate animation is toggled, added repair --- .../maya/publish/validate_rendersettings.py | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 42579727ce..49d1024290 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -15,12 +15,18 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): * Frame Padding must be: * default: 4 + + NOTE: + The repair function of this plugin will does not repair animation + setting of the render settings due to multiple possibilities. + """ order = colorbleed.api.ValidateContentsOrder label = "Render Settings" hosts = ["maya"] families = ["colorbleed.renderlayer"] + actions = [colorbleed.api.RepairAction] DEFAULT_PADDING = 4 RENDERER_PREFIX = {"vray": "/_/"} @@ -31,6 +37,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): renderer = instance.data['renderer'] layer_node = instance.data['setMembers'] + # Main check animation + animation = cmds.getAttr("defaultRenderGlobals.animation") + assert animation is True, ("Animation needs to be enabled in the " + "render settings") + # Collect the filename prefix in the renderlayer with lib.renderlayer(layer_node): @@ -43,11 +54,40 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): prefix = cmds.getAttr("{}.{}".format(node, prefix_attr)) padding = cmds.getAttr("{}.{}".format(node, padding_attr)) - fname_prefix = self.RENDERER_PREFIX.get(renderer, self.DEFAULT_PREFIX) - assert prefix == fname_prefix, ( - "Wrong file name prefix, expecting %s" % fname_prefix - ) - assert padding == self.DEFAULT_PADDING, ( - "Expecting padding of {} ( {} )".format(self.DEFAULT_PADDING, - "0"*self.DEFAULT_PADDING) - ) + anim_override = cmds.getAttr("defaultRenderGlobals.animation") + assert anim_override == animation, ( + "Animation neesd to be enabled. Use the same frame for start " + "and end to render singel frame") + + fname_prefix = self.RENDERER_PREFIX.get(renderer, + self.DEFAULT_PREFIX) + assert prefix == fname_prefix, ( + "Wrong file name prefix, expecting %s" % fname_prefix + ) + assert padding == self.DEFAULT_PADDING, ( + "Expecting padding of {} ( {} )".format( + self.DEFAULT_PADDING, "0"*self.DEFAULT_PADDING) + ) + + @classmethod + def repair(cls, instance): + + renderer = instance.data['renderer'] + layer_node = instance.data['setMembers'] + + with lib.renderlayer(layer_node): + default = lib.RENDER_ATTRS['default'] + render_attrs = lib.RENDER_ATTRS.get(renderer, default) + + # Repair prefix + node = render_attrs["node"] + prefix_attr = render_attrs["prefix"] + + fname_prefix = cls.RENDERER_PREFIX.get(renderer, cls.DEFAULT_PREFIX) + cmds.setAttr("{}.{}".format(node, prefix_attr), + fname_prefix, type="string") + + # Repair padding + padding_attr = render_attrs["padding"] + cmds.setAttr("{}.{}".format(node, padding_attr), + cls.DEFAULT_PADDING) From 547bfcb5fffe5ca3e93c29d215ac93535a2647cc Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 10:01:13 +0200 Subject: [PATCH 0825/1249] changed set_project_fps to validate_fps --- colorbleed/maya/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 1123fbd1b3..b4c0809020 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -122,7 +122,7 @@ def on_open(_): from ..widgets import popup # Ensure scene's FPS is set to project config - lib.set_project_fps() + lib.validate_fps() # Update current task for the current scene update_task_from_path(cmds.file(query=True, sceneName=True)) @@ -174,5 +174,3 @@ def on_task_changed(*args): # Run maya.pipeline._on_task_changed() - - From f8f85ae09b7449a6ce7515fc64db2fa84fd0722c Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 10:03:42 +0200 Subject: [PATCH 0826/1249] improved set_project_fps --- colorbleed/maya/lib.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 20e7fdfe94..5d01ff3e4a 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1322,27 +1322,22 @@ def set_project_fps(): """ - int_fps = [15, 24, 5, 30, 48, 50, 60, 44100, 48000] - float_fps = [23.976, 29.97, 29.97, 47.952, 59.94] + int_fps = {15, 24, 5, 30, 48, 50, 60, 44100, 48000} + float_fps = {23.976, 29.97, 29.97, 47.952, 59.94} fps = lib.get_project_fps() - if not isinstance(fps, (int, float)): - raise ValueError("Set value for project's FPS is not a number. " - "Only accepts floats and integers") + if isinstance(fps, float) and fps in float_fps: + unit = "{:f}fps".format(int(fps)) - if int(fps) in int_fps: - update_str = "{:d}fps".format(int(fps)) - - elif fps in float_fps: - update_str = "{:f}fps".format(fps) + elif int(fps) in int_fps: + unit = "{:d}fps".format(int(fps)) else: - raise RuntimeError("Unsupported FPS value: `%s`" % fps) + raise ("Unsupported FPS value: `%s`" % fps) - log.info("Updating FPS to '{}'".format(update_str)) - - cmds.currentUnit(time=update_str) + log.info("Updating FPS to '{}'".format(unit)) + cmds.currentUnit(time=unit) # Valid FPS From cef0cd57a0877bc323fa5b71fa22ee08a4e39935 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Jul 2018 10:40:04 +0200 Subject: [PATCH 0827/1249] Simplify validate transfers and improve non-verbose log readability --- colorbleed/plugins/maya/publish/validate_transfers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/colorbleed/plugins/maya/publish/validate_transfers.py index 7fb50567c1..5f1f5b3f41 100644 --- a/colorbleed/plugins/maya/publish/validate_transfers.py +++ b/colorbleed/plugins/maya/publish/validate_transfers.py @@ -28,22 +28,21 @@ class ValidateTransfers(pyblish.api.InstancePlugin): for source, destination in transfers: collected[destination.lower()].add(source.lower()) - invalid = False invalid_destinations = list() for destination, sources in collected.items(): if len(sources) > 1: + invalid_destinations.append(destination) + if verbose: self.log.error("Non-unique file transfer for resources: " "{0} (sources: {1})".format(destination, sources)) - invalid = True - invalid_destinations.append(destination) - if invalid: + if invalid_destinations: if not verbose: # If not verbose then still log the resource destination as # opposed to every individual file transfer self.log.error("Non-unique file transfers to destinations: " - "%s" % invalid_destinations) + "%s" % "\n".join(invalid_destinations)) raise RuntimeError("Invalid transfers in queue.") From ca291a4757419ef94b569de833c2404afcf93322 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Jul 2018 10:44:59 +0200 Subject: [PATCH 0828/1249] Improve readability validate_shape_render_stats --- .../publish/validate_shape_render_stats.py | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py index 87b716650d..9a3067badb 100644 --- a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py +++ b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py @@ -10,8 +10,6 @@ class ValidateShapeRenderStats(pyblish.api.Validator): order = colorbleed.api.ValidateMeshOrder hosts = ['maya'] families = ['colorbleed.model'] - category = 'model' - version = (0, 1, 0) label = 'Shape Default Render Stats' actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] @@ -26,19 +24,17 @@ class ValidateShapeRenderStats(pyblish.api.Validator): 'doubleSided': 1, 'opposite': 0} - @staticmethod - def get_invalid(instance): + @classmethod + def get_invalid(cls, instance): # It seems the "surfaceShape" and those derived from it have # `renderStat` attributes. shapes = cmds.ls(instance, long=True, type='surfaceShape') invalid = [] for shape in shapes: - for attr, requiredValue in \ - ValidateShapeRenderStats.defaults.iteritems(): - + for attr, default_value in cls.defaults.iteritems(): if cmds.attributeQuery(attr, node=shape, exists=True): value = cmds.getAttr('{}.{}'.format(shape, attr)) - if value != requiredValue: + if value != default_value: invalid.append(shape) return invalid @@ -48,14 +44,13 @@ class ValidateShapeRenderStats(pyblish.api.Validator): invalid = self.get_invalid(instance) if invalid: - raise ValueError("Shapes with non-standard renderStats " + raise ValueError("Shapes with non-default renderStats " "found: {0}".format(invalid)) - @staticmethod - def repair(instance): - shape_render_defaults = ValidateShapeRenderStats.defaults - for shape in ValidateShapeRenderStats.get_invalid(instance): - for attr, default_value in shape_render_defaults.iteritems(): + @classmethod + def repair(cls, instance): + for shape in cls.get_invalid(instance): + for attr, default_value in cls.defaults.iteritems(): if cmds.attributeQuery(attr, node=shape, exists=True): plug = '{0}.{1}'.format(shape, attr) From bd80df60848f4c549902d8eb3fc4a75fdb4e520c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Jul 2018 12:39:58 +0200 Subject: [PATCH 0829/1249] Allow any_outdated to ignore invalid containers --- colorbleed/lib.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 73bd80f9ff..11184e9098 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -16,16 +16,14 @@ def is_latest(representation): """Return whether the representation is from latest version Args: - representation (str or io.ObjectId): The representation id. + representation (dict): The representation document from the database. Returns: bool: Whether the representation is of latest version. """ - rep = io.find_one({"_id": io.ObjectId(representation), - "type": "representation"}) - version = io.find_one({"_id": rep['parent']}) + version = io.find_one({"_id": representation['parent']}) # Get highest version under the parent highest_version = io.find_one({ @@ -49,8 +47,15 @@ def any_outdated(): if representation in checked: continue - if not is_latest(container['representation']): + representation_doc = io.find_one({"_id": io.ObjectId(representation), + "type": "representation"}, + projection={"parent": True}) + if representation_doc and not is_latest(representation_doc): return True + elif not representation_doc: + log.debug("Container '{objectName}' has an invalid " + "representation, it is missing in the " + "database".format(**container)) checked.add(representation) return False From ed8ef0f5387703e3c8a4a7c71bf1cd33c6ef6489 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 5 Jul 2018 12:47:12 +0200 Subject: [PATCH 0830/1249] Optimize slightly with projection --- colorbleed/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 11184e9098..0c88b622f3 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -29,7 +29,7 @@ def is_latest(representation): highest_version = io.find_one({ "type": "version", "parent": version["parent"] - }, sort=[("name", -1)]) + }, sort=[("name", -1)], projection={"name": True}) if version['name'] == highest_version['name']: return True From 3b69876754300a51bf7a4927c3d5ce588ec6897c Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 15:05:10 +0200 Subject: [PATCH 0831/1249] added before save function --- colorbleed/maya/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index b4c0809020..da56fb973b 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,7 +2,7 @@ import os import logging import weakref -from maya import utils, cmds, mel +from maya import utils, cmds, mel, OpenMaya as om from avalon import api as avalon, pipeline, maya from pyblish import api as pyblish @@ -37,6 +37,8 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) + avalon.before("save", before_save_check) + log.info("Overriding existing event 'taskChanged'") override_event("taskChanged", on_task_changed) @@ -95,6 +97,11 @@ def on_init(_): safe_deferred(override_component_mask_commands) +def before_save_check(return_code, _): + """Run validation for scene's FPS prior to saving""" + return lib.validate_fps() + + def on_save(_): """Automatically add IDs to new nodes @@ -112,8 +119,6 @@ def on_save(_): for node, new_id in lib.generate_ids(nodes): lib.set_id(node, new_id, overwrite=False) - lib.validate_fps() - def on_open(_): """On scene open let's assume the containers have changed.""" From e0a1d6e91ea98ee61622a86758eb685fe81dbe3b Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 15:06:40 +0200 Subject: [PATCH 0832/1249] dialog modal, fix toggles file modified --- colorbleed/maya/lib.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5d01ff3e4a..b49e649778 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -16,7 +16,6 @@ from avalon import api, maya, io, pipeline from colorbleed import lib - log = logging.getLogger(__name__) ATTRIBUTE_DICT = {"int": {"attributeType": "long"}, @@ -1339,13 +1338,16 @@ def set_project_fps(): log.info("Updating FPS to '{}'".format(unit)) cmds.currentUnit(time=unit) + # Force file stated to 'modified' + cmds.file(modified=True) + # Valid FPS def validate_fps(): """Validate current scene FPS and show pop-up when it is incorrect Returns: - None + bool """ @@ -1365,10 +1367,16 @@ def validate_fps(): pass else: dialog = popup.Popup(parent=parent) + dialog.setModal(True) dialog.setWindowTitle("Maya scene not in line with project") dialog.setMessage("The FPS is out of sync, please fix") + # Set new text for button (add optional argument for the popup?) dialog.widgets["show"].setText("Fix") dialog.on_show.connect(set_project_fps) dialog.show() + + return False + + return True From 22ba0283787afaeadc57b6f6931a709156d27fcb Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 15:10:46 +0200 Subject: [PATCH 0833/1249] removed redundant import --- colorbleed/maya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index da56fb973b..de8a809818 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -2,7 +2,7 @@ import os import logging import weakref -from maya import utils, cmds, mel, OpenMaya as om +from maya import utils, cmds, mel from avalon import api as avalon, pipeline, maya from pyblish import api as pyblish From 5cfc41c4518ba40ba214b3980d995dd3e47cfc6e Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 15:52:08 +0200 Subject: [PATCH 0834/1249] Added new popup with a checkbox --- colorbleed/widgets/popup.py | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/colorbleed/widgets/popup.py b/colorbleed/widgets/popup.py index 805eae8c23..d9c084dc4c 100644 --- a/colorbleed/widgets/popup.py +++ b/colorbleed/widgets/popup.py @@ -104,6 +104,107 @@ class Popup(QtWidgets.QDialog): return QtCore.QRect(x, y, width, height) +class Popup2(QtWidgets.QDialog): + + on_show = QtCore.Signal() + + def __init__(self, parent=None, *args, **kwargs): + QtWidgets.QDialog.__init__(self, parent=parent, *args, **kwargs) + self.setContentsMargins(0, 0, 0, 0) + + # Layout + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(10, 5, 10, 10) + message = QtWidgets.QLabel("") + message.setStyleSheet(""" + QLabel { + font-size: 12px; + } + """) + show = QtWidgets.QPushButton("Fix") + show.setSizePolicy(QtWidgets.QSizePolicy.Maximum, + QtWidgets.QSizePolicy.Maximum) + show.setStyleSheet("""QPushButton { background-color: #BB0000 }""") + + toggle = QtWidgets.QCheckBox() + toggle.setText("Update Keys") + + layout.addWidget(message, 1) # Ensure stretch + layout.addWidget(toggle) + layout.addWidget(show) + + # Size + self.resize(400, 40) + geometry = self.calculate_window_geometry() + self.setGeometry(geometry) + + self.widgets = { + "message": message, + "show": show, + "toggle": toggle, + } + + # Signals + show.clicked.connect(self._on_show_clicked) + + # Set default title + self.setWindowTitle("Popup") + + def setMessage(self, message): + self.widgets['message'].setText(message) + + def _on_show_clicked(self): + """Callback for when the 'show' button is clicked. + + Raises the parent (if any) + + """ + + parent = self.parent() + self.close() + + # Trigger the signal + self.on_show.emit() + + if parent: + parent.raise_() + + def calculate_window_geometry(self): + """Respond to status changes + + On creation, align window with screen bottom right. + + """ + + window = self + + width = window.width() + width = max(width, window.minimumWidth()) + + height = window.height() + height = max(height, window.sizeHint().height()) + + desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() + screen_geometry = window.geometry() + + screen_width = screen_geometry.width() + screen_height = screen_geometry.height() + + # Calculate width and height of system tray + systray_width = screen_geometry.width() - desktop_geometry.width() + systray_height = screen_geometry.height() - desktop_geometry.height() + + padding = 10 + + x = screen_width - width + y = screen_height - height + + x -= systray_width + padding + y -= systray_height + padding + + return QtCore.QRect(x, y, width, height) + + @contextlib.contextmanager def application(): app = QtWidgets.QApplication(sys.argv) From 85a320392e824299dd89ea426c32b4e6145d78c4 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 15:52:37 +0200 Subject: [PATCH 0835/1249] use other popup, pass toggle to function --- colorbleed/maya/lib.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b49e649778..d22ca5a0a9 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1313,9 +1313,12 @@ def get_id_from_history(node): return _id -def set_project_fps(): +def set_project_fps(update=True): """Set FPS from project configuration + Args: + update(bool): toggle update animation, default is True + Returns: None @@ -1336,7 +1339,7 @@ def set_project_fps(): raise ("Unsupported FPS value: `%s`" % fps) log.info("Updating FPS to '{}'".format(unit)) - cmds.currentUnit(time=unit) + cmds.currentUnit(time=unit, updateAnimation=update) # Force file stated to 'modified' cmds.file(modified=True) @@ -1366,14 +1369,15 @@ def validate_fps(): if parent is None: pass else: - dialog = popup.Popup(parent=parent) + dialog = popup.Popup2(parent=parent) dialog.setModal(True) dialog.setWindowTitle("Maya scene not in line with project") dialog.setMessage("The FPS is out of sync, please fix") # Set new text for button (add optional argument for the popup?) - dialog.widgets["show"].setText("Fix") - dialog.on_show.connect(set_project_fps) + toggle = dialog.widgets["toggle"] + update = toggle.isChecked() + dialog.on_show.connect(lambda: set_project_fps(update)) dialog.show() From 78c497292a9d92623ea63aa6598b9946a6d487dc Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 17:35:01 +0200 Subject: [PATCH 0836/1249] removed redundant getAttr --- .../plugins/maya/publish/validate_rendersettings.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 49d1024290..0a08f5db46 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -17,7 +17,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): * default: 4 NOTE: - The repair function of this plugin will does not repair animation + The repair function of this plugin does not repair animation setting of the render settings due to multiple possibilities. """ @@ -37,11 +37,6 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): renderer = instance.data['renderer'] layer_node = instance.data['setMembers'] - # Main check animation - animation = cmds.getAttr("defaultRenderGlobals.animation") - assert animation is True, ("Animation needs to be enabled in the " - "render settings") - # Collect the filename prefix in the renderlayer with lib.renderlayer(layer_node): @@ -55,7 +50,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): padding = cmds.getAttr("{}.{}".format(node, padding_attr)) anim_override = cmds.getAttr("defaultRenderGlobals.animation") - assert anim_override == animation, ( + assert anim_override is True, ( "Animation neesd to be enabled. Use the same frame for start " "and end to render singel frame") From 38e00773bc7d142023f0be5eea0a84cea86fdf6e Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 5 Jul 2018 18:01:48 +0200 Subject: [PATCH 0837/1249] changed method name --- colorbleed/plugins/maya/load/actions.py | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py index 12a3dfe248..53aa426aa3 100644 --- a/colorbleed/plugins/maya/load/actions.py +++ b/colorbleed/plugins/maya/load/actions.py @@ -101,6 +101,10 @@ class ImportMayaLoader(api.Loader): from avalon import maya from avalon.maya import lib + choice = self.display_warning() + if choice is False: + return + asset = context['asset'] namespace = namespace or lib.unique_namespace( @@ -119,3 +123,28 @@ class ImportMayaLoader(api.Loader): # We do not containerize imported content, it remains unmanaged return + + def display_warning(self): + """Show warning to ensure the user can't import models by accident + + Returns: + bool + + """ + + from avalon.vendor.Qt import QtWidgets + + accept = QtWidgets.QMessageBox.Ok + buttons = accept | QtWidgets.QMessageBox.Cancel + + message = "Are you sure you want import this" + state = QtWidgets.QMessageBox.warning(None, + "Are you sure?", + message, + buttons=buttons, + defaultButton=accept) + + if state != accept: + return False + + return True From f8b7860d28df5474acd933f775ebeeaf1ab2789d Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 09:35:56 +0200 Subject: [PATCH 0838/1249] added houdini plugins --- colorbleed/houdini/__init__.py | 21 +---------- .../houdini/create/create_pointcache.py | 35 +++++++++++++++++++ .../plugins/houdini/load/load_alembic.py | 15 ++++++++ 3 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 colorbleed/plugins/houdini/create/create_pointcache.py create mode 100644 colorbleed/plugins/houdini/load/load_alembic.py diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 91a6df3edd..8272f8622b 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -33,7 +33,6 @@ def install(): avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] - def on_init(): pass @@ -48,22 +47,4 @@ def on_open(): def on_task_changed(*args): """Wrapped function of app initialize and maya's on task changed""" - - # Inputs (from the switched session and running app) - session = avalon.Session.copy() - app_name = os.environ["AVALON_APP_NAME"] - - # Find the application definition - app_definition = pipeline.lib.get_application(app_name) - - App = type("app_%s" % app_name, - (avalon.Application,), - {"config": app_definition.copy()}) - - # Initialize within the new session's environment - app = App() - env = app.environ(session) - app.initialize(env) - - # Run - houdini.pipeline._on_task_changed() \ No newline at end of file + pass \ No newline at end of file diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py new file mode 100644 index 0000000000..eedddc5fb7 --- /dev/null +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -0,0 +1,35 @@ +from collections import OrderedDict + +import avalon.api + + +class CreatePointCache(avalon.api.Creator): + """Alembic pointcache for animated data""" + + name = "pointcache" + label = "Point Cache" + family = "colorbleed.pointcache" + icon = "gears" + + def __init__(self, *args, **kwargs): + super(CreatePointCache, self).__init__(*args, **kwargs) + + # create an ordered dict with the existing data first + data = OrderedDict(**self.data) + + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + data[key] = value + + # Write vertex colors with the geometry. + data["writeColorSets"] = False + + # Include only renderable visible shapes. + # Skips locators and empty transforms + data["renderableOnly"] = False + + # Include only nodes that are visible at least once during the + # frame range. + data["visibleOnly"] = False + + self.data = data \ No newline at end of file diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py new file mode 100644 index 0000000000..ed2258373b --- /dev/null +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -0,0 +1,15 @@ +import avalon.api + + +class AbcLoader(avalon.api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.animation", "colorbleed.pointcache"] + label = "Reference animation" + representations = ["abc"] + order = -10 + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + print("Not implemented") \ No newline at end of file From 4dff814cc92df53a72a08e6f7d4804140f4c643e Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:38:40 +0200 Subject: [PATCH 0839/1249] using inheritence --- colorbleed/widgets/popup.py | 102 ++++-------------------------------- 1 file changed, 11 insertions(+), 91 deletions(-) diff --git a/colorbleed/widgets/popup.py b/colorbleed/widgets/popup.py index d9c084dc4c..8f28dc5269 100644 --- a/colorbleed/widgets/popup.py +++ b/colorbleed/widgets/popup.py @@ -104,105 +104,25 @@ class Popup(QtWidgets.QDialog): return QtCore.QRect(x, y, width, height) -class Popup2(QtWidgets.QDialog): +class Popup2(Popup): on_show = QtCore.Signal() def __init__(self, parent=None, *args, **kwargs): - QtWidgets.QDialog.__init__(self, parent=parent, *args, **kwargs) - self.setContentsMargins(0, 0, 0, 0) + Popup.__init__(self, parent=parent, *args, **kwargs) - # Layout - layout = QtWidgets.QHBoxLayout(self) - layout.setContentsMargins(10, 5, 10, 10) - message = QtWidgets.QLabel("") - message.setStyleSheet(""" - QLabel { - font-size: 12px; - } - """) - show = QtWidgets.QPushButton("Fix") - show.setSizePolicy(QtWidgets.QSizePolicy.Maximum, - QtWidgets.QSizePolicy.Maximum) - show.setStyleSheet("""QPushButton { background-color: #BB0000 }""") + layout = self.layout() - toggle = QtWidgets.QCheckBox() - toggle.setText("Update Keys") + # Add toggle + toggle = QtWidgets.QCheckBox("Update Keys") + layout.insertWidget(1, toggle) + self.widgets["toggle"] = toggle - layout.addWidget(message, 1) # Ensure stretch - layout.addWidget(toggle) - layout.addWidget(show) + layout.insertStretch(1, 1) - # Size - self.resize(400, 40) - geometry = self.calculate_window_geometry() - self.setGeometry(geometry) - - self.widgets = { - "message": message, - "show": show, - "toggle": toggle, - } - - # Signals - show.clicked.connect(self._on_show_clicked) - - # Set default title - self.setWindowTitle("Popup") - - def setMessage(self, message): - self.widgets['message'].setText(message) - - def _on_show_clicked(self): - """Callback for when the 'show' button is clicked. - - Raises the parent (if any) - - """ - - parent = self.parent() - self.close() - - # Trigger the signal - self.on_show.emit() - - if parent: - parent.raise_() - - def calculate_window_geometry(self): - """Respond to status changes - - On creation, align window with screen bottom right. - - """ - - window = self - - width = window.width() - width = max(width, window.minimumWidth()) - - height = window.height() - height = max(height, window.sizeHint().height()) - - desktop_geometry = QtWidgets.QDesktopWidget().availableGeometry() - screen_geometry = window.geometry() - - screen_width = screen_geometry.width() - screen_height = screen_geometry.height() - - # Calculate width and height of system tray - systray_width = screen_geometry.width() - desktop_geometry.width() - systray_height = screen_geometry.height() - desktop_geometry.height() - - padding = 10 - - x = screen_width - width - y = screen_height - height - - x -= systray_width + padding - y -= systray_height + padding - - return QtCore.QRect(x, y, width, height) + # Update button text + fix = self.widgets["show"] + fix.setText("Fix") @contextlib.contextmanager From dafb033c05e09bae9b20ad2673039cb6a041a7b2 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:39:48 +0200 Subject: [PATCH 0840/1249] improved validation, fixed formatting bug --- colorbleed/maya/lib.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index d22ca5a0a9..7fd69f884d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -85,6 +85,9 @@ _alembic_options = { "selection": bool } +INT_FPS = {15, 24, 5, 30, 48, 50, 60, 44100, 48000} +FLOAT_FPS = {23.976, 29.97, 29.97, 47.952, 59.94} + def matrix_equals(a, b, tolerance=1e-10): """ @@ -1313,10 +1316,11 @@ def get_id_from_history(node): return _id -def set_project_fps(update=True): +def set_scene_fps(fps, update=True): """Set FPS from project configuration Args: + fps (int, float): desired FPS update(bool): toggle update animation, default is True Returns: @@ -1324,19 +1328,14 @@ def set_project_fps(update=True): """ - int_fps = {15, 24, 5, 30, 48, 50, 60, 44100, 48000} - float_fps = {23.976, 29.97, 29.97, 47.952, 59.94} + if isinstance(fps, float) and fps in FLOAT_FPS: + unit = "{:f}fps".format(fps) - fps = lib.get_project_fps() - - if isinstance(fps, float) and fps in float_fps: - unit = "{:f}fps".format(int(fps)) - - elif int(fps) in int_fps: - unit = "{:d}fps".format(int(fps)) + elif isinstance(fps, int) and fps in INT_FPS: + unit = "{:d}fps".format(fps) else: - raise ("Unsupported FPS value: `%s`" % fps) + raise ValueError("Unsupported FPS value: `%s`" % fps) log.info("Updating FPS to '{}'".format(unit)) cmds.currentUnit(time=unit, updateAnimation=update) @@ -1354,9 +1353,20 @@ def validate_fps(): """ + fps = lib.get_project_fps() # can be int or float current_fps = mel.eval('currentTimeUnitToFPS()') # returns float - fps = lib.get_project_fps() - if fps != current_fps: + + # Check if FPS is supported in Maya and compare with current fps + if isinstance(fps, int) and fps in INT_FPS: + valid = int(current_fps) == fps + + elif isinstance(fps, float) and fps in FLOAT_FPS: + valid = current_fps == fps + + else: + valid = False + + if not valid: from avalon.vendor.Qt import QtWidgets from ..widgets import popup @@ -1377,7 +1387,7 @@ def validate_fps(): # Set new text for button (add optional argument for the popup?) toggle = dialog.widgets["toggle"] update = toggle.isChecked() - dialog.on_show.connect(lambda: set_project_fps(update)) + dialog.on_show.connect(lambda: set_scene_fps(fps, update)) dialog.show() From c8bb56bd02f95d0fa6d4f4958bfe1f1cca493f63 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:41:27 +0200 Subject: [PATCH 0841/1249] removed type conversion on return --- colorbleed/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index e4397d104d..c0bff9d2e3 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -251,7 +251,7 @@ def get_project_fps(): """Returns project's FPS, if not found will return 25 by default Returns: - float + int, float """ project_name = io.active_project() @@ -264,4 +264,4 @@ def get_project_fps(): fps = config.get("fps", 25.0) - return float(fps) + return fps From 20994d38cdc0c08314eef88991dd9757ca800d9f Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:41:49 +0200 Subject: [PATCH 0842/1249] renamed function, removed redudant comma --- colorbleed/maya/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index de8a809818..74ecebba1f 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -9,7 +9,7 @@ from pyblish import api as pyblish from ..lib import ( update_task_from_path, - any_outdated, + any_outdated ) from . import menu from . import lib @@ -37,7 +37,7 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) - avalon.before("save", before_save_check) + avalon.before("save", on_before_save) log.info("Overriding existing event 'taskChanged'") override_event("taskChanged", on_task_changed) @@ -97,7 +97,7 @@ def on_init(_): safe_deferred(override_component_mask_commands) -def before_save_check(return_code, _): +def on_before_save(return_code, _): """Run validation for scene's FPS prior to saving""" return lib.validate_fps() From 3703cc5983689bdf2a194c3eee61dd169c2840a5 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:43:55 +0200 Subject: [PATCH 0843/1249] updated validator --- colorbleed/plugins/maya/publish/validate_maya_units.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 69f22bb74f..6548e8b6c3 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -49,4 +49,5 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): cls.log.debug(current_linear) cls.log.info("Setting time unit to match project") - mayalib.set_project_fps() + project_fps = lib.get_project_fps() + mayalib.set_scene_fps(project_fps) From 7af4debad5e4410795b855bb212b1aa0dfd134bc Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:58:38 +0200 Subject: [PATCH 0844/1249] simplified method --- colorbleed/plugins/maya/load/actions.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py index 53aa426aa3..f255a2fe9f 100644 --- a/colorbleed/plugins/maya/load/actions.py +++ b/colorbleed/plugins/maya/load/actions.py @@ -144,7 +144,4 @@ class ImportMayaLoader(api.Loader): buttons=buttons, defaultButton=accept) - if state != accept: - return False - - return True + return state == accept \ No newline at end of file From 4738aa1eea84bb415a12570934f7f6ce62f3d720 Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 11:59:08 +0200 Subject: [PATCH 0845/1249] end of file --- colorbleed/plugins/maya/load/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/actions.py b/colorbleed/plugins/maya/load/actions.py index f255a2fe9f..440fabf124 100644 --- a/colorbleed/plugins/maya/load/actions.py +++ b/colorbleed/plugins/maya/load/actions.py @@ -144,4 +144,4 @@ class ImportMayaLoader(api.Loader): buttons=buttons, defaultButton=accept) - return state == accept \ No newline at end of file + return state == accept From 9a13ee30fecfc0fa9c4a34358773f4e5d48a3d0c Mon Sep 17 00:00:00 2001 From: wijnand Date: Fri, 6 Jul 2018 15:56:08 +0200 Subject: [PATCH 0846/1249] simplified comparison --- colorbleed/maya/lib.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7fd69f884d..0f7e842e3e 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1356,17 +1356,7 @@ def validate_fps(): fps = lib.get_project_fps() # can be int or float current_fps = mel.eval('currentTimeUnitToFPS()') # returns float - # Check if FPS is supported in Maya and compare with current fps - if isinstance(fps, int) and fps in INT_FPS: - valid = int(current_fps) == fps - - elif isinstance(fps, float) and fps in FLOAT_FPS: - valid = current_fps == fps - - else: - valid = False - - if not valid: + if current_fps != fps: from avalon.vendor.Qt import QtWidgets from ..widgets import popup From 252824ff9f101a1cfa4533ec5f610673a363dfcb Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 9 Jul 2018 17:02:52 +0200 Subject: [PATCH 0847/1249] added attribute_values contenxtmanager function --- colorbleed/maya/lib.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5ba2122072..90063f5c9b 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -13,6 +13,7 @@ from collections import OrderedDict, defaultdict from maya import cmds, mel from avalon import api, maya, io, pipeline +from avalon.vendor.six import string_types log = logging.getLogger(__name__) @@ -104,7 +105,7 @@ def matrix_equals(a, b, tolerance=1e-10): def unique(name): - assert isinstance(name, basestring), "`name` must be string" + assert isinstance(name, string_types), "`name` must be string" while cmds.objExists(name): matches = re.findall(r"\d+$", name) @@ -272,6 +273,31 @@ def collect_animation_data(): return data +@contextlib.contextmanager +def attribute_values(attr_values): + """Remaps node attributes to values during context. + + Arguments: + attr_values (dict): Dictionary with (attr, value) + + """ + + original = [(attr, cmds.getAttr(attr)) for attr in attr_values] + try: + for attr, value in attr_values.items(): + if isinstance(value, string_types): + cmds.setAttr(attr, value, type="string") + else: + cmds.setAttr(attr, value) + yield + finally: + for attr, value in original: + if isinstance(value, string_types): + cmds.setAttr(attr, value, type="string") + else: + cmds.setAttr(attr, value) + + @contextlib.contextmanager def renderlayer(layer): """Set the renderlayer during the context""" From 14d4294608be66dd96cf5b442e4dc7afbf09e8ee Mon Sep 17 00:00:00 2001 From: wijnand Date: Mon, 9 Jul 2018 17:04:02 +0200 Subject: [PATCH 0848/1249] updated call to attributes_values function --- colorbleed/plugins/maya/publish/extract_look.py | 3 ++- colorbleed/plugins/maya/publish/extract_yeti_rig.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index d6767bea49..7e8fc2b436 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -7,6 +7,7 @@ from maya import cmds import pyblish.api import avalon.maya import colorbleed.api +import colorbleed.maya.lib as maya from cb.utils.maya import context @@ -65,7 +66,7 @@ class ExtractLook(colorbleed.api.Extractor): with context.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with context.empty_sets(sets, force=True): - with context.attribute_values(remap): + with maya.attribute_values(remap): with avalon.maya.maintained_selection(): cmds.select(sets, noExpand=True) cmds.file(maya_path, diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 923d9a6ba9..7806c0642f 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -6,7 +6,7 @@ from maya import cmds import avalon.maya.lib as lib import colorbleed.api -from cb.utils.maya import context +import colorbleed.maya.lib as maya @contextlib.contextmanager @@ -25,7 +25,7 @@ def disconnected_attributes(settings, members): try: source = sources[0] except IndexError: - print "source_id:", input["sourceID"] + print("source_id:", input["sourceID"]) continue # Get destination shapes (the shapes used as hook up) @@ -52,7 +52,7 @@ def disconnected_attributes(settings, members): try: cmds.connectAttr(connection[0], connection[1]) except Exception as e: - print e, + print(e) continue @@ -112,7 +112,7 @@ class ExtractYetiRig(colorbleed.api.Extractor): nodes = instance.data["setMembers"] with disconnected_attributes(settings, members): - with context.attribute_values(attr_value): + with maya.attribute_values(attr_value): cmds.select(nodes, noExpand=True) cmds.file(maya_path, force=True, From 3ffee9750132515f08a3407faebfc2c1a191afc8 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 10 Jul 2018 09:37:07 +0200 Subject: [PATCH 0849/1249] added extendFrames to options --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index f1495a0b89..efac261372 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -134,5 +134,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Suspend publish job state = "Suspended" if attributes["suspendPublishJob"] else "Active" options["publishJobState"] = state + options["extendFrames"] = attributes.get("extendFrames", False) return options From 23f8cb59f8c9720aa25daa4ab571c8eb278ac734 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 10 Jul 2018 11:06:13 +0200 Subject: [PATCH 0850/1249] added extendFrames toggle to instance --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 3ca857a11c..ee8aac2d66 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -25,6 +25,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data = OrderedDict(**self.data) data["suspendPublishJob"] = False + data["extendFrames"] = False data["includeDefaultRenderLayer"] = False data["useLegacyRenderLayers"] = True data["priority"] = 50 From 57beb4ec735a010f2c20af80175ca84ac8be4d1a Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 10 Jul 2018 11:39:36 +0200 Subject: [PATCH 0851/1249] improved check for sharedRefence node --- colorbleed/maya/plugin.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index ff212a9e5a..d89bfd092f 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -76,6 +76,8 @@ class ReferenceLoader(api.Loader): def _get_reference_node(self, members): """Get the reference node from the container members + Args: + members: list of node names Returns: str: Reference node name. @@ -89,8 +91,8 @@ class ReferenceLoader(api.Loader): references = set() for ref in cmds.ls(members, exactType="reference", objectsOnly=True): - # Ignore `sharedReferenceNode` - if ref == "sharedReferenceNode": + # Ignore any `sharedReferenceNode` + if ref.startswith("sharedReference"): continue references.add(ref) @@ -155,9 +157,9 @@ class ReferenceLoader(api.Loader): # Fix PLN-40 for older containers created with Avalon that had the # `.verticesOnlySet` set to True. - if cmds.getAttr(node + ".verticesOnlySet"): + if cmds.getAttr("{}.verticesOnlySet".format(node)): self.log.info("Setting %s.verticesOnlySet to False", node) - cmds.setAttr(node + ".verticesOnlySet", False) + cmds.setAttr("{}.verticesOnlySet".format(node), False) # Add new nodes of the reference to the container cmds.sets(content, forceElement=node) @@ -170,7 +172,7 @@ class ReferenceLoader(api.Loader): cmds.sets(invalid, remove=node) # Update metadata - cmds.setAttr(node + ".representation", + cmds.setAttr("{}.representation".format(node), str(representation["_id"]), type="string") From 86146f8598240747f01d182af5f5e51006ba7569 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 14:21:00 +0200 Subject: [PATCH 0852/1249] changed attribute for override option --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index ee8aac2d66..97a12deba4 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -26,6 +26,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["suspendPublishJob"] = False data["extendFrames"] = False + data["overrideExistingFrame"] = True data["includeDefaultRenderLayer"] = False data["useLegacyRenderLayers"] = True data["priority"] = 50 From 1d272b86cc54642f4976a7d923eb60d944c29511 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 14:21:51 +0200 Subject: [PATCH 0853/1249] ensure override only works when extend frames is True --- .../plugins/maya/publish/collect_renderlayers.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index efac261372..752d461d08 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -22,8 +22,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): try: render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: - self.log.info("Cannot collect renderlayers without " - "renderGlobals node") + self.log.error("Cannot collect renderlayers without " + "renderGlobals node") return # Get start and end frame @@ -134,6 +134,15 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Suspend publish job state = "Suspended" if attributes["suspendPublishJob"] else "Active" options["publishJobState"] = state - options["extendFrames"] = attributes.get("extendFrames", False) + + # Override frames should be False if extendFrames is False. This is + # to ensure it doesn't go off doing crazy unpredictable things + override_frames = False + extend_frames = attributes.get("extendFrames", False) + if extend_frames: + override_frames = attributes.get("overrideExistingFrame", False) + + options["extendFrames"] = extend_frames + options["overrideExistingFrame"] = override_frames return options From 0cbbed43b181d50f687db83cae16a857cce16a05 Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 14:24:32 +0200 Subject: [PATCH 0854/1249] implemented extend frame logic --- .../global/publish/submit_publish_job.py | 151 +++++++++++++++++- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index c13f7e3dce..87151f28b4 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -2,8 +2,8 @@ import os import json import re -from avalon import api -from avalon.vendor import requests +from avalon import api, io +from avalon.vendor import requests, clique import pyblish.api @@ -23,6 +23,73 @@ def _get_script(): return module_path +# Logic to retrieve latest files concerning extendFrames +def get_latest_version(asset_name, subset_name, family): + # Get asset + asset_name = io.find_one({"type": "asset", + "name": asset_name}, + projection={"name": True}) + + subset = io.find_one({"type": "subset", + "name": subset_name, + "parent": asset_name["_id"]}, + projection={"_id": True, "name": True}) + + # Check if subsets actually exists (pre-run check) + assert subset, "No subsets found, please publish with `extendFrames` off" + + # Get version + version_projection = {"name": True, + "data.startFrame": True, + "data.endFrame": True, + "parent": True} + + version = io.find_one({"type": "version", + "parent": subset["_id"], + "data.families": family}, + projection=version_projection, + sort=[("name", -1)]) + + assert version, "No version found, this is a bug" + + return version + + +def get_resources(version, extension=None): + """ + Get the files from the specific version + """ + query = {"type": "representation", "parent": version["_id"]} + if extension: + query["name"] = extension + + representation = io.find_one(query) + assert representation, "This is a bug" + + directory = api.get_representation_path(representation) + print("Source: ", directory) + resources = sorted([os.path.normpath(os.path.join(directory, fname)) + for fname in os.listdir(directory)]) + + return resources + + +def get_resource_files(resources, frame_range, override=True): + + res_collections, _ = clique.assemble(resources) + assert len(res_collections) == 1, "Multiple collections found" + res_collection = res_collections[0] + + # Remove any frames + if override: + for frame in frame_range: + if frame not in res_collection.indexes: + continue + res_collection.indexes.remove(frame) + + return list(res_collection) + + class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): """Submit image sequence publish jobs to Deadline. @@ -69,8 +136,9 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): raise RuntimeError("Can't continue without valid deadline " "submission prior to this plug-in.") - subset = instance.data["subset"] - state = instance.data.get("publishJobState", "Suspended") + data = instance.data.copy() + subset = data["subset"] + state = data.get("publishJobState", "Suspended") job_name = "{batch} - {subset} [publish image sequence]".format( batch=job["Props"]["Name"], subset=subset @@ -80,6 +148,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): context = instance.context start = instance.data.get("startFrame", context.data["startFrame"]) end = instance.data.get("endFrame", context.data["endFrame"]) + resources = [] # Add in regex for sequence filename # This assumes the output files start with subset name and ends with @@ -93,8 +162,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ext=ext) # Write metadata for publish job - data = instance.data.copy() - data.pop("deadlineSubmissionJob") + render_job = data.pop("deadlineSubmissionJob") metadata = { "regex": regex, "startFrame": start, @@ -114,6 +182,58 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): if not os.path.isdir(output_dir): os.makedirs(output_dir) + if data.get("extendFrames", False): + + family = "colorbleed.imagesequence" + override = data["overrideFrames"] + + # override = data.get("overrideExistingFrame", False) + out_file = render_job.get("OutFile") + if not out_file: + raise RuntimeError("OutFile not found in render job!") + + extension = os.path.splitext(out_file[0])[1] + _ext = extension[1:] + + # Frame comparison + prev_start = None + prev_end = None + resource_range = range(int(start)-1, int(end)+1) + + # Gather all the subset files (one subset per render pass!) + subset_names = [data["subset"]] + subset_names.extend(data.get("renderPasses", [])) + + for subset_name in subset_names: + version = get_latest_version(asset_name=data["asset"], + subset_name=subset_name, + family=family) + + # Set prev start / end frames for comparison + if not prev_start and not prev_end: + prev_start = version["data"]["startFrame"] + prev_end = version["data"]["endFrame"] + + subset_resources = get_resources(version, _ext) + get_resource_files(subset_resources, resource_range, override) + + resources.extend(subset_resources) + + updated_start = start if start < prev_start else prev_start + updated_end = end if end > prev_end else prev_end + + # Update metadata and instance start / end frame + self.log.info("Updating start / end frame : " + "{} - {}".format(updated_start, updated_end)) + + # Start frame + metadata["startFrame"] = updated_start + metadata["metadata"]["instance"]["startFrame"] = updated_start + + # End frame + metadata["endFrame"] = updated_end + metadata["metadata"]["instance"]["endFrame"] = updated_end + metadata_filename = "{}_metadata.json".format(subset) metadata_path = os.path.join(output_dir, metadata_filename) with open(metadata_path, "w") as f: @@ -159,3 +279,22 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): response = requests.post(url, json=payload) if not response.ok: raise Exception(response.text) + + # Copy files from previous render if extendFrame is True + self.log.info("Submitting done!") + + # return + + if data.get("extendFrames", False): + # TODO: implement subset / render element + + self.log.info("Preparing to copy ..") + import shutil + + dest_path = data["outputDir"] + for source in resources: + src_file = os.path.basename(source) + dest = os.path.join(dest_path, src_file) + shutil.copy(source, dest) + + self.log.info("Finished copying %i files" % len(resources)) From f43253c773f1c8a64d74564ba84c3000c3e64fee Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 14:26:10 +0200 Subject: [PATCH 0855/1249] added collect and validate aovs for extend logic --- .../maya/publish/collect_render_layer_aovs.py | 91 +++++++++++++++++++ .../maya/publish/validate_renderlayer_aovs.py | 60 ++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_render_layer_aovs.py create mode 100644 colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py new file mode 100644 index 0000000000..64adb11258 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -0,0 +1,91 @@ +from maya import cmds + +import pyblish.api + +import colorbleed.maya.lib as lib + + +class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): + """Validate all render layer's AOVs / Render Elements are registered in + the database + + This validator is important to be able to Extend Frames + + Technical information: + Each renderer uses different logic to work with render passes. + VRay - RenderElement + Simple node connection to the actual renderLayer node + + Arnold - AOV: + Uses its own render settings node and connects an aiOAV to it + + Redshift - AOV: + Uses its own render settings node and RedshiftAOV node. It is not + connected but all AOVs are enabled for all render layers by default. + + """ + + order = pyblish.api.CollectorOrder + 0.01 + label = "Render Elements / AOVs" + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + + # Get renderer + renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") + + self.log.info("Renderer found: {}".format(renderer)) + + rp_node_types = {"vray": "VRayRenderElement", + "arnold": "aiAOV", + "redshift": "RedshiftAOV"} + + if renderer not in rp_node_types.keys(): + self.log.error("Unsupported renderer found: '{}'".format(renderer)) + return + + result = [] + + # Collect all AOVs / Render Elements + with lib.renderlayer(instance.name): + + node_type = rp_node_types[renderer] + render_elements = cmds.ls(type=node_type) + + # Check if AOVs / Render Elements are enabled + for element in render_elements: + enabled = cmds.getAttr("{}.enabled".format(element)) + if not enabled: + continue + + pass_name = self.get_pass_name(renderer, element) + render_pass = "%s.%s" % (instance.name, pass_name) + + result.append(render_pass) + + self.log.info("Found {} render elements / AOVs for " + "'{}'".format(len(result), instance.name)) + + instance.data["renderPasses"] = result + + def get_pass_name(self, renderer, node): + + if renderer == "vray": + vray_node_attr = next(attr for attr in cmds.listAttr(node) + if attr.startswith("vray_name")) + + pass_type = vray_node_attr.rsplit("_", 1)[-1] + if pass_type == "extratex": + vray_node_attr = "vray_explicit_name_extratex" + + # Node type is in the attribute name but we need to check if value + # of the attribute as it can be changed + pass_name = cmds.getAttr("{}.{}".format(node, vray_node_attr)) + + elif renderer in ["arnold", "redshift"]: + pass_name = cmds.getAttr("{}.name".format(node)) + else: + raise RuntimeError("Unsupported renderer: '{}'".format(renderer)) + + return pass_name \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py new file mode 100644 index 0000000000..de54be2147 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py @@ -0,0 +1,60 @@ +import pyblish.api + +from avalon import io, api +import colorbleed.api + + +class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): + """Validate created AOVs / RenderElement is registered in the database + + Each render element is registered as a subset which is formatted based on + the render layer and the render element, example: + + . + + This translates to something like this: + + CHAR.diffuse + + This check is only used when the Extend Frames in 'renderGlobalsDefault' + is toggled on. + + This check is needed to ensure the render output is still complete + + """ + + order = pyblish.api.ValidatorOrder + 0.1 + label = "Render Passes / AOVs Are Registered" + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found unregistered subsets: {}".format(invalid)) + + def get_invalid(self, instance): + + invalid = [] + + asset_name = api.Session["AVALON_ASSET"] + render_passses = instance.data.get("renderPasses", []) + for render_pass in render_passses: + + is_valid = self.validate_subset_registered(asset_name, render_pass) + if not is_valid: + invalid.append(render_pass) + + return invalid + + def validate_subset_registered(self, asset_name, subset_name): + """Check if subset is registered in the database under the asset""" + + asset = io.find_one({"type": "asset", "name": asset_name}) + is_valid = io.find_one({"type": "subset", + "name": subset_name, + "parent": asset["_id"]}) + + return is_valid + From f5179dd513e7705c06bd1171d2f0ac0f1df222ea Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 14:32:59 +0200 Subject: [PATCH 0856/1249] updated keyword --- colorbleed/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 87151f28b4..0017a8e4f8 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -185,7 +185,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): if data.get("extendFrames", False): family = "colorbleed.imagesequence" - override = data["overrideFrames"] + override = data["overrideExistingFrame"] # override = data.get("overrideExistingFrame", False) out_file = render_job.get("OutFile") From 6bcdc6de4752bfabd1b60a07b35ceb4b6a27992f Mon Sep 17 00:00:00 2001 From: wijnand Date: Thu, 12 Jul 2018 17:47:49 +0200 Subject: [PATCH 0857/1249] Temp storage --- colorbleed/houdini/__init__.py | 3 ++- res/houdini/123.py | 5 ++++ res/houdini/MainMenuCommon.XML | 46 ++++++++++++++++++++++++++++++++++ res/houdini/houdini.env | 22 ++++++++++++++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 res/houdini/123.py create mode 100644 res/houdini/MainMenuCommon.XML create mode 100644 res/houdini/houdini.env diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 8272f8622b..f12720b395 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -2,7 +2,7 @@ import os import logging -from avalon import api as avalon, pipeline, houdini +from avalon import api as avalon # pipeline, houdini from pyblish import api as pyblish @@ -18,6 +18,7 @@ log = logging.getLogger("colorbleed.houdini") def install(): + pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) diff --git a/res/houdini/123.py b/res/houdini/123.py new file mode 100644 index 0000000000..f47f185604 --- /dev/null +++ b/res/houdini/123.py @@ -0,0 +1,5 @@ +from colorbleed import houdini + +houdini.install() + +print("BANANA") \ No newline at end of file diff --git a/res/houdini/MainMenuCommon.XML b/res/houdini/MainMenuCommon.XML new file mode 100644 index 0000000000..37a058635b --- /dev/null +++ b/res/houdini/MainMenuCommon.XML @@ -0,0 +1,46 @@ + + + + + + + + + $HOME/scripts/first_script.py + + + + + $HOME/scripts/generic_script.py + -q -n camera + + + + + $HOME/scripts/generic_script.py + -q -n camera + + + + + + import hou + from avalon.tools import publish + + parent = hou.qt.mainWindow() + run = publish.show(parent) + + return True + + + + + + + + $HOME/scripts/generic_script.py + -q -n light + + + + diff --git a/res/houdini/houdini.env b/res/houdini/houdini.env new file mode 100644 index 0000000000..3645ebea8c --- /dev/null +++ b/res/houdini/houdini.env @@ -0,0 +1,22 @@ +# +# Houdini Environment Settings +# +# The contents of this file are read into the environment +# at startup. They will override any existing entries in +# the environment. +# +# The syntax is one entry per line as follows: +# VAR = VALUE +# +# Values may be quoted +# VAR = "VALUE" +# +# Values may be empty +# VAR = +# + +# Example: +# +# HOUDINI_NO_SPLASH = 1 + +HOUDINI_PATH = $COLORBLEED_CONFIG/res/houdini From 94c1d5def687d3f869b3cf90ad0e83f09bfd346b Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 17 Jul 2018 09:46:06 +0200 Subject: [PATCH 0858/1249] removed default startup setting to avalon core --- res/houdini/123.py | 5 ---- res/houdini/MainMenuCommon.XML | 46 ---------------------------------- res/houdini/houdini.env | 22 ---------------- 3 files changed, 73 deletions(-) delete mode 100644 res/houdini/123.py delete mode 100644 res/houdini/MainMenuCommon.XML delete mode 100644 res/houdini/houdini.env diff --git a/res/houdini/123.py b/res/houdini/123.py deleted file mode 100644 index f47f185604..0000000000 --- a/res/houdini/123.py +++ /dev/null @@ -1,5 +0,0 @@ -from colorbleed import houdini - -houdini.install() - -print("BANANA") \ No newline at end of file diff --git a/res/houdini/MainMenuCommon.XML b/res/houdini/MainMenuCommon.XML deleted file mode 100644 index 37a058635b..0000000000 --- a/res/houdini/MainMenuCommon.XML +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - $HOME/scripts/first_script.py - - - - - $HOME/scripts/generic_script.py - -q -n camera - - - - - $HOME/scripts/generic_script.py - -q -n camera - - - - - - import hou - from avalon.tools import publish - - parent = hou.qt.mainWindow() - run = publish.show(parent) - - return True - - - - - - - - $HOME/scripts/generic_script.py - -q -n light - - - - diff --git a/res/houdini/houdini.env b/res/houdini/houdini.env deleted file mode 100644 index 3645ebea8c..0000000000 --- a/res/houdini/houdini.env +++ /dev/null @@ -1,22 +0,0 @@ -# -# Houdini Environment Settings -# -# The contents of this file are read into the environment -# at startup. They will override any existing entries in -# the environment. -# -# The syntax is one entry per line as follows: -# VAR = VALUE -# -# Values may be quoted -# VAR = "VALUE" -# -# Values may be empty -# VAR = -# - -# Example: -# -# HOUDINI_NO_SPLASH = 1 - -HOUDINI_PATH = $COLORBLEED_CONFIG/res/houdini From 901f0d688d00e20cd823c150f0a0e0744ab81726 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 17 Jul 2018 09:46:54 +0200 Subject: [PATCH 0859/1249] added install logic for houdini --- colorbleed/houdini/__init__.py | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index f12720b395..f70b2d6198 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -1,10 +1,20 @@ import os - import logging -from avalon import api as avalon # pipeline, houdini +import hou + from pyblish import api as pyblish +from avalon import api as avalon +from avalon.houdini import pipeline as houdini + +from colorbleed.houdini import lib + +from colorbleed.lib import ( + any_outdated, + update_task_from_path +) + PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) @@ -19,6 +29,8 @@ log = logging.getLogger("colorbleed.houdini") def install(): + # Set + pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -34,16 +46,52 @@ def install(): avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] -def on_init(): - pass +def on_init(_): + houdini.on_houdini_initialize() -def on_save(): - pass +def on_save(_): + + avalon.logger.info("Running callback on save..") + + update_task_from_path(hou.hipFile.path()) + + nodes = lib.get_id_required_nodes() + for node, new_id in lib.generate_ids(nodes): + lib.set_id(node, new_id, overwrite=False) def on_open(): - pass + + update_task_from_path(hou.hipFile.path()) + + if any_outdated(): + from avalon.vendor.Qt import QtWidgets + from ..widgets import popup + + log.warning("Scene has outdated content.") + + # Find maya main window + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + parent = top_level_widgets.get("MayaWindow", None) + + if parent is None: + log.info("Skipping outdated content pop-up " + "because Maya window can't be found.") + else: + + # Show outdated pop-up + def _on_show_inventory(): + import avalon.tools.cbsceneinventory as tool + tool.show(parent=parent) + + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene has outdated content") + dialog.setMessage("There are outdated containers in " + "your Maya scene.") + dialog.on_show.connect(_on_show_inventory) + dialog.show() def on_task_changed(*args): From d2580fc963338edd5291760ec3779ee4ae6e7649 Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 17 Jul 2018 09:47:15 +0200 Subject: [PATCH 0860/1249] added alembic loader --- .../plugins/houdini/load/load_alembic.py | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index ed2258373b..cbf76ace07 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -1,5 +1,9 @@ import avalon.api +from avalon.houdini import pipeline, lib +reload(pipeline) +reload(lib) + class AbcLoader(avalon.api.Loader): """Specific loader of Alembic for the avalon.animation family""" @@ -12,4 +16,68 @@ class AbcLoader(avalon.api.Loader): color = "orange" def load(self, context, name=None, namespace=None, data=None): - print("Not implemented") \ No newline at end of file + + import os + import hou + + # Format file name, Houdini only wants forward slashes + file_path = os.path.normpath(self.fname) + file_path = file_path.replace("\\", "/") + + # Get the root node + obj = hou.node("/obj") + + # Create a unique name + counter = 1 + namespace = namespace if namespace else context["asset"]["name"] + formatted = "{}_{}".format(namespace, name) if namespace else name + node_name = "{0}_{1:03d}".format(formatted, counter) + + children = lib.children_as_string(hou.node("/obj")) + while node_name in children: + counter += 1 + node_name = "{0}_{1:03d}".format(formatted, counter) + + # Create a new geo node + container = obj.createNode("geo", node_name=node_name) + + # Remove the file node, it only loads static meshes + node_path = "/obj/{}/file1".format(node_name) + file_node = hou.node(node_path) + file_node.destroy() + + # Create an alembic node (supports animation) + alembic = container.createNode("alembic", node_name=node_name) + alembic.setParms({"fileName": file_path}) + + # Add unpack node + unpack = container.createNode("unpack") + unpack.setInput(0, alembic) + unpack.setParms({"transfer_attributes": "path"}) + + # Set new position for unpack node else it gets cluttered + unpack.setPosition([0, -1]) + + # set unpack as display node + unpack.setDisplayFlag(True) + + null_node = container.createNode("null", + node_name="OUT_{}".format(name)) + null_node.setPosition([0, -2]) + null_node.setInput(0, unpack) + + nodes = [container, alembic, unpack, null_node] + + self[:] = nodes + + return pipeline.containerise(node_name, + namespace, + nodes, + context, + self.__class__.__name__) + + def update(self, container, representation): + pass + + def remove(self, container): + print(">>>", container["objectName"]) From 3739de1f150207a2c0fdfcef82c8634fb5c563fc Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 17 Jul 2018 09:47:54 +0200 Subject: [PATCH 0861/1249] added point cache creator plugin --- .../houdini/create/create_pointcache.py | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index eedddc5fb7..b94e7575ff 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -1,9 +1,11 @@ from collections import OrderedDict -import avalon.api +import hou + +from avalon import houdini -class CreatePointCache(avalon.api.Creator): +class CreatePointCache(houdini.Creator): """Alembic pointcache for animated data""" name = "pointcache" @@ -17,19 +19,9 @@ class CreatePointCache(avalon.api.Creator): # create an ordered dict with the existing data first data = OrderedDict(**self.data) - # get basic animation data : start / end / handles / steps - for key, value in lib.collect_animation_data().items(): - data[key] = value + # Collect animation data for point cache exporting + start, end = hou.playbar.timelineRange() + data["startFrame"] = start + data["endFrame"] = end - # Write vertex colors with the geometry. - data["writeColorSets"] = False - - # Include only renderable visible shapes. - # Skips locators and empty transforms - data["renderableOnly"] = False - - # Include only nodes that are visible at least once during the - # frame range. - data["visibleOnly"] = False - - self.data = data \ No newline at end of file + self.data = data From b825b3a775bf727b6699ff647c11c133da1592ab Mon Sep 17 00:00:00 2001 From: wijnand Date: Tue, 17 Jul 2018 09:48:27 +0200 Subject: [PATCH 0862/1249] added lib and pyblish plugins --- colorbleed/houdini/lib.py | 93 +++++++++++++++++++ .../houdini/publish/collect_alembic_nodes.py | 9 ++ .../houdini/publish/collect_instances.py | 34 +++++++ 3 files changed, 136 insertions(+) create mode 100644 colorbleed/houdini/lib.py create mode 100644 colorbleed/plugins/houdini/publish/collect_alembic_nodes.py create mode 100644 colorbleed/plugins/houdini/publish/collect_instances.py diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py new file mode 100644 index 0000000000..fbd190731c --- /dev/null +++ b/colorbleed/houdini/lib.py @@ -0,0 +1,93 @@ +import uuid + +import hou + +from avalon import io +from avalon.houdini import lib + + +def set_id(node, unique_id, overwrite=False): + + exists = node.parm("id") + if not exists: + lib.imprint(node, {"id": unique_id}) + + if not exists and overwrite: + node.setParm("id", unique_id) + + +def get_id(node): + """ + Get the `cbId` attribute of the given node + Args: + node (hou.Node): the name of the node to retrieve the attribute from + + Returns: + str + + """ + + if node is None: + return + + id = node.parm("id") + if node is None: + return + return id + + +def generate_ids(nodes, asset_id=None): + """Returns new unique ids for the given nodes. + + Note: This does not assign the new ids, it only generates the values. + + To assign new ids using this method: + >>> nodes = ["a", "b", "c"] + >>> for node, id in generate_ids(nodes): + >>> set_id(node, id) + + To also override any existing values (and assign regenerated ids): + >>> nodes = ["a", "b", "c"] + >>> for node, id in generate_ids(nodes): + >>> set_id(node, id, overwrite=True) + + Args: + nodes (list): List of nodes. + asset_id (str or bson.ObjectId): The database id for the *asset* to + generate for. When None provided the current asset in the + active session is used. + + Returns: + list: A list of (node, id) tuples. + + """ + + if asset_id is None: + # Get the asset ID from the database for the asset of current context + asset_data = io.find_one({"type": "asset", + "name": api.Session["AVALON_ASSET"]}, + projection={"_id": True}) + assert asset_data, "No current asset found in Session" + asset_id = asset_data['_id'] + + node_ids = [] + for node in nodes: + _, uid = str(uuid.uuid4()).rsplit("-", 1) + unique_id = "{}:{}".format(asset_id, uid) + node_ids.append((node, unique_id)) + + return node_ids + + +def get_id_required_nodes(): + + valid_types = ["geometry"] + nodes = {n for n in hou.node("/out").children() if + n.type().name() in valid_types} + + return list(nodes) + + +def get_additional_data(container): + """Not implemented yet!""" + pass \ No newline at end of file diff --git a/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py b/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py new file mode 100644 index 0000000000..dc0de42126 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py @@ -0,0 +1,9 @@ +import pyblish.api + + +class CollectAlembicNodes(pyblish.api.InstancePlugin): + + label = "Collect Alembic Nodes" + + def process(self, instance): + pass \ No newline at end of file diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py new file mode 100644 index 0000000000..8912611005 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -0,0 +1,34 @@ +import hou + +import pyblish.api + +from avalon.houdini import lib + + +class CollectInstances(pyblish.api.ContextPlugin): + + label = "Collect Instances" + order = pyblish.api.CollectorOrder + hosts = ["houdini"] + + def process(self, context): + + instances = [] + + nodes = hou.node("/out").children() + for node in nodes: + if node.parm("id"): + continue + + if not node.parm("id") != "pyblish.avalon.instance": + continue + + has_family = node.parm("family") + assert has_family, "'%s' is missing 'family'" % node.name() + + # TODO: Ensure not all data passes through! + data = lib.read(node) + instance = context.create_instance(data.get("name", node.name())) + instance[:] = [node] + + From 4653cabb8acb6c1feea7e57cbc7eec008e46f314 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 11:04:53 +0200 Subject: [PATCH 0863/1249] added current file check --- .../houdini/publish/collect_current_file.py | 15 +++++++++++++++ .../plugins/houdini/publish/extract_alembic.py | 0 2 files changed, 15 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collect_current_file.py create mode 100644 colorbleed/plugins/houdini/publish/extract_alembic.py diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/colorbleed/plugins/houdini/publish/collect_current_file.py new file mode 100644 index 0000000000..e8612bdc12 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_current_file.py @@ -0,0 +1,15 @@ +import hou + +import pyblish.api + + +class CollectMayaCurrentFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder - 0.5 + label = "Houdini Current File" + hosts = ['houdini'] + + def process(self, context): + """Inject the current working file""" + context.data['currentFile'] = hou.hipFile.path() diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py new file mode 100644 index 0000000000..e69de29bb2 From 03c111f58da2cbbc7ec121d5f7cb27e890087b84 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 11:09:44 +0200 Subject: [PATCH 0864/1249] added context function for attributes --- colorbleed/houdini/lib.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index fbd190731c..c9ce643751 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -1,8 +1,10 @@ import uuid +from contextlib import contextmanager + import hou -from avalon import io +from avalon import api, io from avalon.houdini import lib @@ -81,7 +83,7 @@ def generate_ids(nodes, asset_id=None): def get_id_required_nodes(): - valid_types = ["geometry"] + valid_types = ["geometry", "geometry"] nodes = {n for n in hou.node("/out").children() if n.type().name() in valid_types} @@ -90,4 +92,22 @@ def get_id_required_nodes(): def get_additional_data(container): """Not implemented yet!""" - pass \ No newline at end of file + pass + + +@contextmanager +def attribute_values(node, data): + + previous_attrs = {key: node.parm(key).eval() for key in data.keys()} + print("before", previous_attrs) + try: + node.setParms(data) + during_attrs = {key: node.parm(key).eval() for key in data.keys()} + print("during", during_attrs) + yield + except Exception as exc: + print(exc) + pass + finally: + print("reset") + node.setParms(previous_attrs) From 59fe720e8c2ab8467202e145164b74fd3d47bb25 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 11:10:39 +0200 Subject: [PATCH 0865/1249] updated plugins --- .../houdini/create/create_pointcache.py | 3 ++ .../houdini/publish/collect_instances.py | 43 +++++++++++++++++-- .../houdini/publish/extract_alembic.py | 35 +++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index b94e7575ff..93cd83a6b3 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -19,6 +19,9 @@ class CreatePointCache(houdini.Creator): # create an ordered dict with the existing data first data = OrderedDict(**self.data) + # Set node type to create for output + data["node_type"] = "alembic" + # Collect animation data for point cache exporting start, end = hou.playbar.timelineRange() data["startFrame"] = start diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 8912611005..2a9edb5667 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -6,6 +6,23 @@ from avalon.houdini import lib class CollectInstances(pyblish.api.ContextPlugin): + """Gather instances by all node in out graph and pre-defined attributes + + This collector takes into account assets that are associated with + an specific node and marked with a unique identifier; + + Identifier: + id (str): "pyblish.avalon.instance + + Specific node: + The specific node is important because it dictates in which way the subset + is being exported. + + alembic: will export Alembic file which supports cascading attributes + like 'cbId' and 'path' + geometry: Can export a wide range of file types, default out + + """ label = "Collect Instances" order = pyblish.api.CollectorOrder @@ -14,21 +31,39 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): instances = [] + keys = ["active", "id", "family", "asset", "subset"] nodes = hou.node("/out").children() for node in nodes: - if node.parm("id"): + + if not node.parm("id"): continue - if not node.parm("id") != "pyblish.avalon.instance": + if node.parm("id").eval() != "pyblish.avalon.instance": continue - has_family = node.parm("family") + has_family = node.parm("family").eval() assert has_family, "'%s' is missing 'family'" % node.name() - # TODO: Ensure not all data passes through! data = lib.read(node) + + # temporarily translation of `active` to `publish` till issue has + # been resolved, https://github.com/pyblish/pyblish-base/issues/307 + if "active" in data: + data["publish"] = data["active"] + instance = context.create_instance(data.get("name", node.name())) + instance[:] = [node] + instance.data.update(data) + instances.append(instance) + def sort_by_family(instance): + """Sort by family""" + return instance.data.get("families", instance.data.get("family")) + + # Sort/grouped by family (preserving local index) + context[:] = sorted(context, key=sort_by_family) + + return context diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index e69de29bb2..d427a747fb 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -0,0 +1,35 @@ +import os + +import hou + +import pyblish.api +import colorbleed.api +from colorbleed.houdini import lib + + +class ExtractAlembic(colorbleed.api.Extractor): + + order = pyblish.api.ExtractorOrder + label = "Extract Pointcache (Alembic)" + hosts = ["houdini"] + families = ["colorbleed.pointcache"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + + file_name = "{}.abc".format(instance.data["subset"]) + tmp_filepath = os.path.join(staging_dir, file_name) + + ropnode = instance[0] + attributes = {"trange": 1, + "f1": instance.data["startFrame"], + "f2": instance.data["endFrame"]} + + with lib.attribute_values(ropnode, attributes): + ropnode.execute() + + if "files" not in instance.data: + instance.data["files"] = [] + + instance.data["files"].append(tmp_filepath) From 70f249034d32faa28d8f9aac66db75a7a9bb9bbe Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 12:04:31 +0200 Subject: [PATCH 0866/1249] fetch asset from instance, removed redundant import --- .../plugins/maya/publish/validate_renderlayer_aovs.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py index de54be2147..387f4b4881 100644 --- a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py +++ b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py @@ -1,6 +1,6 @@ import pyblish.api -from avalon import io, api +from avalon import io import colorbleed.api @@ -16,9 +16,6 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): CHAR.diffuse - This check is only used when the Extend Frames in 'renderGlobalsDefault' - is toggled on. - This check is needed to ensure the render output is still complete """ @@ -38,10 +35,9 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): invalid = [] - asset_name = api.Session["AVALON_ASSET"] + asset_name = instance.data["asset"] render_passses = instance.data.get("renderPasses", []) for render_pass in render_passses: - is_valid = self.validate_subset_registered(asset_name, render_pass) if not is_valid: invalid.append(render_pass) From d128e42b48cf1b0c80d498eb5d85216ee4602794 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 16:56:33 +0200 Subject: [PATCH 0867/1249] fixed start frame of resource range --- .../global/publish/submit_publish_job.py | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 0017a8e4f8..34a09c9b81 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -1,5 +1,6 @@ import os import json +import pprint import re from avalon import api, io @@ -198,7 +199,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Frame comparison prev_start = None prev_end = None - resource_range = range(int(start)-1, int(end)+1) + resource_range = range(int(start), int(end)+1) # Gather all the subset files (one subset per render pass!) subset_names = [data["subset"]] @@ -215,17 +216,37 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): prev_end = version["data"]["endFrame"] subset_resources = get_resources(version, _ext) - get_resource_files(subset_resources, resource_range, override) + resource_files = get_resource_files(subset_resources, + resource_range, + override) - resources.extend(subset_resources) + resources.extend(resource_files) - updated_start = start if start < prev_start else prev_start - updated_end = end if end > prev_end else prev_end + updated_start = min(start, prev_start) + updated_end = max(end, prev_end) # Update metadata and instance start / end frame self.log.info("Updating start / end frame : " "{} - {}".format(updated_start, updated_end)) + # TODO : Improve logic to get new frame range for the + # publish job (publish_filesequence.py) + # The current approach is not following Pyblish logic which is based + # on Collect / Validate / Extract. + + # ---- Collect Plugins --- + # Collect Extend Frames - Only run if extendFrames is toggled + # # # Store in instance: + # # # Previous rendered files per subset based on frames + # # # --> Add to instance.data[resources] + # # # Update publish frame range + + # ---- Validate Plugins --- + # Validate Extend Frames + # # # Check if instance has the requirements to extend frames + # There might have been some things which can be added to the list + # Please do so when fixing this. + # Start frame metadata["startFrame"] = updated_start metadata["metadata"]["instance"]["startFrame"] = updated_start @@ -281,12 +302,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): raise Exception(response.text) # Copy files from previous render if extendFrame is True - self.log.info("Submitting done!") - - # return - if data.get("extendFrames", False): - # TODO: implement subset / render element self.log.info("Preparing to copy ..") import shutil From 79b8154d1fe9c58210a88f247690d4e1298b8e4f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 16:57:00 +0200 Subject: [PATCH 0868/1249] added pools options --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 97a12deba4..66ebc54bfe 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -32,6 +32,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" + data["pools"] = "" self.data = data self.options = {"useSelection": False} # Force no content From 77f1dee3df78a6a141354d9135c6c4e91e7847ac Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 16:59:52 +0200 Subject: [PATCH 0869/1249] added support for pools --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 752d461d08..f5e086e55e 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -122,6 +122,15 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options = {"renderGlobals": {}} options["renderGlobals"]["Priority"] = attributes["priority"] + + # Check for specific pools + pool_str = attributes.get("pools", None) + if pool_str is not None and not "": + pools = pool_str.split(";") + options["renderGlobals"]["Pool"] = pools[0] + if len(pools) > 1: + options["renderGlobals"]["SecondaryPool"] = pools[1] + legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy From d0931c861325d9c8e310dedb6dc1887d27e28a45 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 17:00:30 +0200 Subject: [PATCH 0870/1249] skip collection if extendFrames is False --- colorbleed/plugins/maya/publish/collect_render_layer_aovs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index 64adb11258..19ab891004 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -32,6 +32,10 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): def process(self, instance): + # Check if Extend Frames is toggled + if not instance.data("extendFrames", False): + return + # Get renderer renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") From 598e1558d252d7d0c035b1e23226bfc16e1e47d6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 16:57:00 +0200 Subject: [PATCH 0871/1249] added pools options --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 3ca857a11c..3cf147a14d 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -30,6 +30,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" + data["pools"] = "" self.data = data self.options = {"useSelection": False} # Force no content From 7957e854ca3392be8ef16f1d49bd44db30cd1abd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 16:59:52 +0200 Subject: [PATCH 0872/1249] added support for pools --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index f1495a0b89..2a377a933e 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -122,6 +122,15 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options = {"renderGlobals": {}} options["renderGlobals"]["Priority"] = attributes["priority"] + + # Check for specific pools + pool_str = attributes.get("pools", None) + if pool_str is not None and not "": + pools = pool_str.split(";") + options["renderGlobals"]["Pool"] = pools[0] + if len(pools) > 1: + options["renderGlobals"]["SecondaryPool"] = pools[1] + legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy From 2f054758eada000e7e3803e14249639932b7fafd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 17:14:28 +0200 Subject: [PATCH 0873/1249] Revert "added support for pools" This reverts commit 77f1dee3df78a6a141354d9135c6c4e91e7847ac. --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index f5e086e55e..752d461d08 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -122,15 +122,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options = {"renderGlobals": {}} options["renderGlobals"]["Priority"] = attributes["priority"] - - # Check for specific pools - pool_str = attributes.get("pools", None) - if pool_str is not None and not "": - pools = pool_str.split(";") - options["renderGlobals"]["Pool"] = pools[0] - if len(pools) > 1: - options["renderGlobals"]["SecondaryPool"] = pools[1] - legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy From 542bbbf32cd0719f380eee91f776320cd926473b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 18 Jul 2018 17:14:32 +0200 Subject: [PATCH 0874/1249] Revert "added pools options" This reverts commit 79b8154d1fe9c58210a88f247690d4e1298b8e4f. --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 66ebc54bfe..97a12deba4 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -32,7 +32,6 @@ class CreateRenderGlobals(avalon.maya.Creator): data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" - data["pools"] = "" self.data = data self.options = {"useSelection": False} # Force no content From b02cb7e3db427ff99df03cc970efe441ace156da Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 19 Jul 2018 11:08:15 +0200 Subject: [PATCH 0875/1249] simplified check for pools --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 2a377a933e..c8156836a2 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -125,7 +125,7 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Check for specific pools pool_str = attributes.get("pools", None) - if pool_str is not None and not "": + if pool_str: pools = pool_str.split(";") options["renderGlobals"]["Pool"] = pools[0] if len(pools) > 1: From b642d2f60b9076a40f26fd6cf8209ad55ddc71d8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 Jul 2018 16:45:27 +0200 Subject: [PATCH 0876/1249] Fix PLN-143 # Conflicts: # colorbleed/maya/plugin.py --- colorbleed/maya/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index ff212a9e5a..0b02da5727 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -89,8 +89,8 @@ class ReferenceLoader(api.Loader): references = set() for ref in cmds.ls(members, exactType="reference", objectsOnly=True): - # Ignore `sharedReferenceNode` - if ref == "sharedReferenceNode": + # Ignore any `:sharedReferenceNode` + if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"): continue references.add(ref) From 301a8e5c19ae7dc5c90f2835f56719a1dd909242 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 20 Jul 2018 16:55:19 +0200 Subject: [PATCH 0877/1249] toggle reflection / refraction on --- colorbleed/plugins/maya/load/load_yeti_cache.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index dd6ebbeb05..90d7cf5678 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -7,6 +7,7 @@ from maya import cmds from avalon import api from avalon.maya import lib as avalon_lib, pipeline +from avalon.vendor import six from colorbleed.maya import lib @@ -176,14 +177,19 @@ class YetiCacheLoader(api.Loader): nodes.append(transform_node) nodes.append(yeti_node) + # Update visibility reflaction and refraction in node settings + attributes = node_settings["attrs"] + attributes.update({"visibleInReflections": True, + "visibleInRefractions": True}) + # Apply attributes to pgYetiMaya node kwargs = {} - for attr, value in node_settings["attrs"].items(): + for attr, value in attributes.items(): if value is None: continue attribute = "%s.%s" % (yeti_node, attr) - if isinstance(value, (str, unicode)): + if isinstance(value, (str, six.string_types)): cmds.setAttr(attribute, value, type="string") continue From 361b41ba54c7bc7d8cbcdf617e685588563ba6ed Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 Jul 2018 16:58:43 +0200 Subject: [PATCH 0878/1249] Remove yetiCache from validate_sequence_frames --- colorbleed/plugins/global/publish/validate_sequence_frames.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py index e015648da8..ae3eb75783 100644 --- a/colorbleed/plugins/global/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/global/publish/validate_sequence_frames.py @@ -11,7 +11,7 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Sequence Frames" - families = ["colorbleed.imagesequence", "colorbleed.yeticache"] + families = ["colorbleed.imagesequence"] hosts = ["shell"] def process(self, instance): From 0db2f87e8e1cc378ece70057f5fdefa17afd954a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 20 Jul 2018 23:34:13 +0200 Subject: [PATCH 0879/1249] Log frame range mismatch instead of no clear error --- .../global/publish/validate_sequence_frames.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/colorbleed/plugins/global/publish/validate_sequence_frames.py index ae3eb75783..bd14b4c0c3 100644 --- a/colorbleed/plugins/global/publish/validate_sequence_frames.py +++ b/colorbleed/plugins/global/publish/validate_sequence_frames.py @@ -17,12 +17,18 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): def process(self, instance): collection = instance[0] + self.log.info(collection) - self.log.warning(collection) frames = list(collection.indexes) - assert frames[0] == instance.data["startFrame"] - assert frames[-1] == instance.data["endFrame"] + current_range = (frames[0], frames[-1]) + required_range = (instance.data["startFrame"], + instance.data["endFrame"]) + + if current_range != required_range: + raise ValueError("Invalid frame range: {0} - " + "expected: {1}".format(current_range, + required_range)) missing = collection.holes().indexes assert not missing, "Missing frames: %s" % (missing,) From ce3861dbaa78ff60e63e06bbc559f2b62a81be15 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Jul 2018 00:06:54 +0200 Subject: [PATCH 0880/1249] Fix false positives for invalidation on path differences with backslash and frontslash --- colorbleed/plugins/maya/publish/validate_transfers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/colorbleed/plugins/maya/publish/validate_transfers.py index 5f1f5b3f41..41544b63cf 100644 --- a/colorbleed/plugins/maya/publish/validate_transfers.py +++ b/colorbleed/plugins/maya/publish/validate_transfers.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import os from collections import defaultdict @@ -26,7 +27,12 @@ class ValidateTransfers(pyblish.api.InstancePlugin): # Collect all destination with its sources collected = defaultdict(set) for source, destination in transfers: - collected[destination.lower()].add(source.lower()) + + # Use normalized paths in comparison and ignore case sensitivity + source = os.path.normpath(source).lower() + destination = os.path.normpath(destination).lower() + + collected[destination].add(source) invalid_destinations = list() for destination, sources in collected.items(): From 0b7ce6857130bfc00c7f56ef148cecc87b016817 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Jul 2018 13:21:28 +0200 Subject: [PATCH 0881/1249] Fix bug in code so it actually checks whether path is absolute --- colorbleed/scripts/fusion_switch_shot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 07bbafa057..5fcfa638d6 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -158,7 +158,7 @@ def switch(asset_name, filepath=None, new=True): """ # Ensure filename is absolute - if not os.path.abspath(filepath): + if not os.path.isabs(filepath): filepath = os.path.abspath(filepath) # Get current project From 40640eb598bb79d4d2d29a2e257dc8c1741aa4b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Jul 2018 14:50:58 +0200 Subject: [PATCH 0882/1249] Fix FUS-48 loader update not offsetting correctly to its frame time upon update/switch --- .../plugins/fusion/load/load_sequence.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/colorbleed/plugins/fusion/load/load_sequence.py index 6d3d743c8a..81313992ed 100644 --- a/colorbleed/plugins/fusion/load/load_sequence.py +++ b/colorbleed/plugins/fusion/load/load_sequence.py @@ -82,9 +82,13 @@ def loader_shift(loader, frame, relative=True): comp = loader.Comp() time = comp.TIME_UNDEFINED - if not relative: - start = loader["GlobalIn"][time] - frame -= start + old_in = loader["GlobalIn"][time] + old_out = loader["GlobalOut"][time] + + if relative: + shift = frame + else: + shift = frame - old_in # Shifting global in will try to automatically compensate for the change # in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those @@ -93,9 +97,17 @@ def loader_shift(loader, frame, relative=True): "ClipTimeEnd", "HoldFirstFrame", "HoldLastFrame"]): - loader["GlobalIn"][time] = loader["GlobalIn"][time] + frame - return int(frame) + # GlobalIn cannot be set past GlobalOut or vice versa + # so we must apply them in the order of the shift. + if shift > 0: + loader["GlobalOut"][time] = old_out + shift + loader["GlobalIn"][time] = old_in + shift + else: + loader["GlobalIn"][time] = old_in + shift + loader["GlobalOut"][time] = old_out + shift + + return int(shift) class FusionLoadSequence(api.Loader): From 2dfbbeb093d8f20d795f728501ebb66d7fa005a4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Jul 2018 14:52:38 +0200 Subject: [PATCH 0883/1249] Improve absolute filename check to be explicit - Also move asset assertion up (micro-optimization) --- colorbleed/scripts/fusion_switch_shot.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/colorbleed/scripts/fusion_switch_shot.py b/colorbleed/scripts/fusion_switch_shot.py index 5fcfa638d6..4254d6c4ce 100644 --- a/colorbleed/scripts/fusion_switch_shot.py +++ b/colorbleed/scripts/fusion_switch_shot.py @@ -157,19 +157,22 @@ def switch(asset_name, filepath=None, new=True): """ - # Ensure filename is absolute - if not os.path.isabs(filepath): - filepath = os.path.abspath(filepath) + # If filepath provided, ensure it is valid absolute path + if filepath is not None: + if not os.path.isabs(filepath): + filepath = os.path.abspath(filepath) - # Get current project - self._project = io.find_one({"type": "project", - "name": api.Session["AVALON_PROJECT"]}) + assert os.path.exists(filepath), "%s must exist " % filepath # Assert asset name exists # It is better to do this here then to wait till switch_shot does it asset = io.find_one({"type": "asset", "name": asset_name}) assert asset, "Could not find '%s' in the database" % asset_name + # Get current project + self._project = io.find_one({"type": "project", + "name": api.Session["AVALON_PROJECT"]}) + # Go to comp if not filepath: current_comp = avalon.fusion.get_current_comp() @@ -189,7 +192,6 @@ def switch(asset_name, filepath=None, new=True): representation = colorbleed.switch_item(container, asset_name=asset_name) representations.append(representation) - current_comp.Print(str(representation["_id"]) + "\n") except Exception as e: current_comp.Print("Error in switching! %s\n" % e.message) From 1278983ce641f4945faeed13bd31c52b5abf3ab6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 21 Jul 2018 17:21:44 +0200 Subject: [PATCH 0884/1249] Add Fix RenderLayer Missing Referenced Nodes Overrides script to menu --- colorbleed/maya/menu.json | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 55e4553fbe..692b6c04a6 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -928,7 +928,22 @@ "shading", "fixRenderLayerOutAdjustmentErrors" ], - "title": "Fix RenderLayerOut Adjustment Errors", + "title": "Fix RenderLayer Out Adjustment Errors", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", + "sourcetype": "file", + "tags": [ + "shading", + "renderlayer", + "missing", + "reference", + "switch", + "layer" + ], + "title": "Fix RenderLayer Missing Referenced Nodes Overrides", "tooltip": "" } ] From 7e156312bb40e2b50c3622f6c0ae7e6b08a98627 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 09:41:11 +0200 Subject: [PATCH 0885/1249] fixing update logic --- .../plugins/maya/load/load_yeti_cache.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 90d7cf5678..088b9c02f3 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -7,7 +7,7 @@ from maya import cmds from avalon import api from avalon.maya import lib as avalon_lib, pipeline -from avalon.vendor import six +from avalon.vendor import six, clique from colorbleed.maya import lib @@ -85,14 +85,22 @@ class YetiCacheLoader(api.Loader): def update(self, container, representation): path = api.get_representation_path(representation) + print("debug: %s" % path) namespace = "{}:".format(container["namespace"]) members = cmds.sets(container['objectName'], query=True) - yeti_node = cmds.ls(members, type="pgYetiMaya") + yeti_nodes = cmds.ls(members, type="pgYetiMaya") # TODO: Count the amount of nodes cached # To ensure new nodes get created or old nodes get destroyed - for node in yeti_node: + # Check the amount of caches + files = os.listdir(path) + patterns = [clique.PATTERNS["frames"]] + collections, _ = clique.assemble(files, patterns=patterns) + if len(collections) != len(yeti_nodes): + pass + + for node in yeti_nodes: # Remove local given namespace node_name = node.split(namespace, 1)[-1] file_name = node_name.replace(":", "_") @@ -135,6 +143,14 @@ class YetiCacheLoader(api.Loader): All caches with more than 1 frame need to be called with `%04d` If the cache has only one frame we return that file name as we assume it is a snapshot. + + Args: + filename(str) + pattern(str) + + Returns: + str + """ glob_pattern = filename.replace(pattern, "*") From ac9e33f3b6f9db964ed0a260ff2ce97e146fedda Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 09:52:57 +0200 Subject: [PATCH 0886/1249] fixed typos --- colorbleed/plugins/maya/publish/validate_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 0a08f5db46..37a69d36c9 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -51,8 +51,8 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): anim_override = cmds.getAttr("defaultRenderGlobals.animation") assert anim_override is True, ( - "Animation neesd to be enabled. Use the same frame for start " - "and end to render singel frame") + "Animation needs to be enabled. Use the same frame for start " + "and end to render single frame") fname_prefix = self.RENDERER_PREFIX.get(renderer, self.DEFAULT_PREFIX) From 95760348c67dc9dcbf08deb4a14e98d042518555 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 10:01:56 +0200 Subject: [PATCH 0887/1249] Updated doc strings --- .../plugins/maya/publish/validate_rendersettings.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 37a69d36c9..d33fa545f6 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -16,8 +16,13 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): * Frame Padding must be: * default: 4 + * Animation must be toggle on, in Render Settings - Common tab: + * vray: Animation on standard of specific + * arnold: Frame / Animation ext: Any choice without "(Single Frame)" + * redshift: Animation toggled on + NOTE: - The repair function of this plugin does not repair animation + The repair function of this plugin does not repair the animation setting of the render settings due to multiple possibilities. """ @@ -37,7 +42,7 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): renderer = instance.data['renderer'] layer_node = instance.data['setMembers'] - # Collect the filename prefix in the renderlayer + # Collect the filename prefix in the render layer with lib.renderlayer(layer_node): render_attrs = lib.RENDER_ATTRS.get(renderer, From 71f22029fdbddeebe8f7688c2cec1f792cfdbde8 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 10:25:59 +0200 Subject: [PATCH 0888/1249] improved plugin, explicit error logging --- .../maya/publish/validate_rendersettings.py | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index d33fa545f6..17d872f7d3 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -39,6 +39,16 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Invalid render settings found for '%s'!" + % instance.name) + + @classmethod + def get_invalid(cls, instance): + + invalid = False + renderer = instance.data['renderer'] layer_node = instance.data['setMembers'] @@ -55,19 +65,24 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): padding = cmds.getAttr("{}.{}".format(node, padding_attr)) anim_override = cmds.getAttr("defaultRenderGlobals.animation") - assert anim_override is True, ( - "Animation needs to be enabled. Use the same frame for start " - "and end to render single frame") + if not anim_override: + invalid = True + cls.log.error("Animation needs to be enabled. Use the same " + "frame for start and end to render single frame") - fname_prefix = self.RENDERER_PREFIX.get(renderer, - self.DEFAULT_PREFIX) - assert prefix == fname_prefix, ( - "Wrong file name prefix, expecting %s" % fname_prefix - ) - assert padding == self.DEFAULT_PADDING, ( - "Expecting padding of {} ( {} )".format( - self.DEFAULT_PADDING, "0"*self.DEFAULT_PADDING) - ) + fname_prefix = cls.RENDERER_PREFIX.get(renderer, + cls.DEFAULT_PREFIX) + if prefix != fname_prefix: + invalid = True + cls.log.error("Wrong file name prefix, expecting %s" + % fname_prefix) + + if padding != cls.DEFAULT_PADDING: + invalid = True + cls.log.error("Expecting padding of {} ( {} )".format( + cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) + + return invalid @classmethod def repair(cls, instance): From 38726aa624f4cc74d779117605b78811d7a031bd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 12:20:50 +0200 Subject: [PATCH 0889/1249] more explicit check for secondary pool --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index a8ef364295..64bce44151 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -126,10 +126,10 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Check for specific pools pool_str = attributes.get("pools", None) if pool_str: - pools = pool_str.split(";") - options["renderGlobals"]["Pool"] = pools[0] - if len(pools) > 1: - options["renderGlobals"]["SecondaryPool"] = pools[1] + pool_a, pool_b = pool_str.split(";") + options["renderGlobals"].update({"Pool": pool_a}) + if pool_b and pool_b != "-": + options["renderGlobals"].update({"SecondaryPool": pool_a}) legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy From fd67595c1a838e7bc32bd57ad0d46581cebc5966 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 13:36:12 +0200 Subject: [PATCH 0890/1249] fixing update logic --- .../plugins/maya/load/load_yeti_cache.py | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 088b9c02f3..19e15d1d7a 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -85,10 +85,14 @@ class YetiCacheLoader(api.Loader): def update(self, container, representation): path = api.get_representation_path(representation) - print("debug: %s" % path) namespace = "{}:".format(container["namespace"]) members = cmds.sets(container['objectName'], query=True) - yeti_nodes = cmds.ls(members, type="pgYetiMaya") + yeti_nodes = {n.rsplit(":", 1)[-1]: n for n in + cmds.ls(members, type="pgYetiMaya")} + + # Get shortest path, splitting on | due to dagpath. + # Split actual node and its transform; root|transform|pgYetiMaya + root = min([n.rsplit("|", 2)[0] for n in yeti_nodes]) # TODO: Count the amount of nodes cached # To ensure new nodes get created or old nodes get destroyed @@ -96,21 +100,32 @@ class YetiCacheLoader(api.Loader): # Check the amount of caches files = os.listdir(path) patterns = [clique.PATTERNS["frames"]] - collections, _ = clique.assemble(files, patterns=patterns) - if len(collections) != len(yeti_nodes): - pass + collections, _ = clique.assemble(files, patterns=patterns, + minimum_items=1) - for node in yeti_nodes: - # Remove local given namespace - node_name = node.split(namespace, 1)[-1] - file_name = node_name.replace(":", "_") + # Build look up + current = set(yeti_nodes.keys()) + collection_lookup = {c.head.strip("."): c for c in collections} + to_create = set( + [k for k in collection_lookup.keys() if k not in yeti_nodes.keys()]) - # Check if the node has a cache - tmp_cache = os.path.join(path, "{}.%04d.fur".format(file_name)) - fpath = self.validate_cache(os.path.normpath(tmp_cache)) + to_update = current | to_create + to_remove = current - to_create - # Update the attribute - cmds.setAttr("{}.cacheFileName".format(node), fpath, type="string") + with avalon_lib.suspended_refresh(): + if to_remove: + to_remove_nodes = [cmds.listRelatives(yeti_nodes[r], p=True)[0] + for r in to_remove] + cmds.delete(to_remove_nodes) + + if to_update: + for node in to_update: + n = yeti_nodes[node] + collection = collection_lookup[n] + seq_name = "".join([collection.head, + "%04d", + collection.tail]) + new_file = os.path.join(path, seq_name) # Update the container cmds.setAttr("{}.representation".format(container["objectName"]), From fc4ece3ddfefa8ec0ff31a218c019b69bd2349b9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 23 Jul 2018 14:17:05 +0200 Subject: [PATCH 0891/1249] using empty string instead of '-' --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 64bce44151..4fa1c4a846 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -128,8 +128,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): if pool_str: pool_a, pool_b = pool_str.split(";") options["renderGlobals"].update({"Pool": pool_a}) - if pool_b and pool_b != "-": - options["renderGlobals"].update({"SecondaryPool": pool_a}) + if pool_b: + options["renderGlobals"].update({"SecondaryPool": pool_b}) legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy From d63f738464a91e66fc41d8bbf208207975223478 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 24 Jul 2018 11:57:51 +0200 Subject: [PATCH 0892/1249] stashing changes --- .../plugins/maya/load/load_yeti_cache.py | 57 ++++++++++++------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 19e15d1d7a..a7e950c244 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -84,49 +84,64 @@ class YetiCacheLoader(api.Loader): def update(self, container, representation): - path = api.get_representation_path(representation) namespace = "{}:".format(container["namespace"]) + path = api.get_representation_path(representation) + + # Get all node data + fname, ext = os.path.splitext(path) + settings_fname = "{}.fursettings".format(fname) + with open(settings_fname, "r") as fp: + settings = json.load(fp) + + if "nodes" not in settings: + raise RuntimeError("Encountered invalid data, expect 'nodes' in " + "fursettings.") + + node_data = settings["nodes"] + nodes = ["{}:{}".format(namespace, data["name"]) for data in node_data] + print("node data nodes:", cmds.ls(nodes)) + members = cmds.sets(container['objectName'], query=True) yeti_nodes = {n.rsplit(":", 1)[-1]: n for n in cmds.ls(members, type="pgYetiMaya")} # Get shortest path, splitting on | due to dagpath. # Split actual node and its transform; root|transform|pgYetiMaya - root = min([n.rsplit("|", 2)[0] for n in yeti_nodes]) - - # TODO: Count the amount of nodes cached - # To ensure new nodes get created or old nodes get destroyed - - # Check the amount of caches - files = os.listdir(path) - patterns = [clique.PATTERNS["frames"]] - collections, _ = clique.assemble(files, patterns=patterns, - minimum_items=1) + root = lib.get_container_transforms(container, + members=members, + root=True) # Build look up current = set(yeti_nodes.keys()) - collection_lookup = {c.head.strip("."): c for c in collections} - to_create = set( - [k for k in collection_lookup.keys() if k not in yeti_nodes.keys()]) + discover = set(nodes) - current - to_update = current | to_create - to_remove = current - to_create + to_update = current & discover + to_remove = current - discover + + # Create set with every node which actually needs to be created + to_create = discover - to_update with avalon_lib.suspended_refresh(): if to_remove: to_remove_nodes = [cmds.listRelatives(yeti_nodes[r], p=True)[0] for r in to_remove] + print("to remove:", to_remove_nodes) cmds.delete(to_remove_nodes) if to_update: for node in to_update: - n = yeti_nodes[node] - collection = collection_lookup[n] - seq_name = "".join([collection.head, - "%04d", - collection.tail]) + yeti_node = yeti_nodes[node] + seq_name = "{}.%04d.fur".format(node) new_file = os.path.join(path, seq_name) + cmds.setAttr("{}.cacheFileName".format(yeti_node), + new_file, + type="string") + + if to_create: + new_nodes = self.create_nodes(namespace, node_data) + cmds.parent(new_nodes, root) + # Update the container cmds.setAttr("{}.representation".format(container["objectName"]), str(representation["_id"]), From df0395b8216193bd28b41896716849869cd765b2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 24 Jul 2018 12:15:30 +0200 Subject: [PATCH 0893/1249] fixed bug, simplified comparison --- colorbleed/maya/lib.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index a6608fc54b..7ca7e109ed 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -87,7 +87,7 @@ _alembic_options = { "selection": bool } -INT_FPS = {15, 24, 5, 30, 48, 50, 60, 44100, 48000} +INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} FLOAT_FPS = {23.976, 29.97, 29.97, 47.952, 59.94} @@ -1355,11 +1355,11 @@ def set_scene_fps(fps, update=True): """ - if isinstance(fps, float) and fps in FLOAT_FPS: + if fps in FLOAT_FPS: unit = "{:f}fps".format(fps) - elif isinstance(fps, int) and fps in INT_FPS: - unit = "{:d}fps".format(fps) + elif fps in INT_FPS: + unit = "{:d}fps".format(int(fps)) else: raise ValueError("Unsupported FPS value: `%s`" % fps) From 9801457c78a9a40c37998fcbd05be1421d51b8a7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 24 Jul 2018 13:19:04 +0200 Subject: [PATCH 0894/1249] Added update functionality --- .../plugins/maya/load/load_yeti_cache.py | 189 +++++++++--------- 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index a7e950c244..46e34743ce 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -2,12 +2,12 @@ import os import json import re import glob +from collections import defaultdict from maya import cmds from avalon import api from avalon.maya import lib as avalon_lib, pipeline -from avalon.vendor import six, clique from colorbleed.maya import lib @@ -84,7 +84,8 @@ class YetiCacheLoader(api.Loader): def update(self, container, representation): - namespace = "{}:".format(container["namespace"]) + namespace = container["namespace"] + container_node = container["objectName"] path = api.get_representation_path(representation) # Get all node data @@ -93,59 +94,78 @@ class YetiCacheLoader(api.Loader): with open(settings_fname, "r") as fp: settings = json.load(fp) - if "nodes" not in settings: - raise RuntimeError("Encountered invalid data, expect 'nodes' in " - "fursettings.") + # Collect scene information of asset + set_members = cmds.sets(container["objectName"], query=True) + container_root = lib.get_container_transforms(container, + members=set_members, + root=True) + scene_nodes = cmds.ls(set_members, type="pgYetiMaya", long=True) - node_data = settings["nodes"] - nodes = ["{}:{}".format(namespace, data["name"]) for data in node_data] - print("node data nodes:", cmds.ls(nodes)) + # Build lookup with cbId as keys + scene_lookup = defaultdict(list) + for node in scene_nodes: + cb_id = lib.get_id(node) + scene_lookup[cb_id].append(node) - members = cmds.sets(container['objectName'], query=True) - yeti_nodes = {n.rsplit(":", 1)[-1]: n for n in - cmds.ls(members, type="pgYetiMaya")} + # Re-assemble metadata with cbId as keys + meta_data_lookup = {n["cbId"]: n for n in settings["nodes"]} - # Get shortest path, splitting on | due to dagpath. - # Split actual node and its transform; root|transform|pgYetiMaya - root = lib.get_container_transforms(container, - members=members, - root=True) + # Compare look ups and get the nodes which ar not relevant any more + to_delete_lookup = {cb_id for cb_id in scene_lookup.keys() if + cb_id not in meta_data_lookup} + if to_delete_lookup: - # Build look up - current = set(yeti_nodes.keys()) - discover = set(nodes) - current + # Get nodes and remove entry from lookup + to_remove = [] + for _id in to_delete_lookup: + # Get all related nodes + shapes = scene_lookup[_id] + # Get the parents of all shapes under the ID + transforms = cmds.listRelatives(shapes, parent=True, + fullPath=True) or [] + to_remove.extend(shapes + transforms) - to_update = current & discover - to_remove = current - discover + # Remove id from look uop + scene_lookup.pop(_id, None) - # Create set with every node which actually needs to be created - to_create = discover - to_update + cmds.delete(to_remove) - with avalon_lib.suspended_refresh(): - if to_remove: - to_remove_nodes = [cmds.listRelatives(yeti_nodes[r], p=True)[0] - for r in to_remove] - print("to remove:", to_remove_nodes) - cmds.delete(to_remove_nodes) + for cb_id, data in meta_data_lookup.items(): - if to_update: - for node in to_update: - yeti_node = yeti_nodes[node] - seq_name = "{}.%04d.fur".format(node) - new_file = os.path.join(path, seq_name) + # Update cache file name + file_name = data["name"].replace(":", "_") + cache_file_path = "{}.%04d.fur".format(file_name) + data["attrs"]["cacheFileName"] = os.path.join(path, cache_file_path) - cmds.setAttr("{}.cacheFileName".format(yeti_node), - new_file, - type="string") + if cb_id not in scene_lookup: + new_nodes = self.create_nodes(namespace, [data]) + cmds.sets(*new_nodes, addElement=container_node) + cmds.parent(new_nodes, container_root) - if to_create: - new_nodes = self.create_nodes(namespace, node_data) - cmds.parent(new_nodes, root) + else: + # Update the matching nodes + scene_nodes = scene_lookup[cb_id] + match_name = meta_data_lookup[cb_id]["name"] - # Update the container - cmds.setAttr("{}.representation".format(container["objectName"]), + for scene_node in scene_nodes: + # Remove the namespace root ":namespace:" + compare_name = scene_node.split(namespace)[-1][1:] + if compare_name != match_name: + self.log.info("Renaming %s to %s" % + (scene_node, match_name)) + + # Get transform node, this makes renaming easier + transform_node = scene_node.rstrip("Shape") + + new_name = "{}:{}".format(namespace, match_name) + cmds.rename(transform_node, new_name) + + for attr, value in data["attrs"].items(): + lib.set_attribute(attr, value, scene_node) + + cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), - type="string") + typ="string") def switch(self, container, representation): self.update(container, representation) @@ -199,75 +219,56 @@ class YetiCacheLoader(api.Loader): return filename def create_nodes(self, namespace, settings): + """Create nodes with the correct namespace and settings + + Args: + namespace(str): namespace + settings(list): list of dictionaries + + Returns: + list + + """ - # Get node name from JSON nodes = [] for node_settings in settings: - # Create transform node - transform = node_settings["transform"] - transform_name = "{}:{}".format(namespace, transform["name"]) - transform_node = cmds.createNode("transform", name=transform_name) - - lib.set_id(transform_node, transform["cbId"]) - # Create pgYetiMaya node original_node = node_settings["name"] node_name = "{}:{}".format(namespace, original_node) - yeti_node = cmds.createNode("pgYetiMaya", - name=node_name, - parent=transform_node) + yeti_node = cmds.createNode("pgYetiMaya", name=node_name) + # Create transform node + transform_node = node_name.rstrip("Shape") + + lib.set_id(transform_node, node_settings["transform"]["cbId"]) lib.set_id(yeti_node, node_settings["cbId"]) - nodes.append(transform_node) - nodes.append(yeti_node) + nodes.extend([transform_node, yeti_node]) - # Update visibility reflaction and refraction in node settings + # Ensure the node has no namespace identifiers attributes = node_settings["attrs"] - attributes.update({"visibleInReflections": True, + + # Check if cache file name is stored + if "cacheFileName" not in attributes: + file_name = original_node.replace(":", "_") + cache = os.path.join(self.fname, + "{}.%04d.fur".format(file_name)) + self.validate_cache(cache) + attributes["cacheFileName"] = cache + + # Update attributes with requirements + attributes.update({"viewportDensity": 0.1, + "verbosity": 2, + "fileMode": 1, + "visibleInReflections": True, "visibleInRefractions": True}) # Apply attributes to pgYetiMaya node - kwargs = {} for attr, value in attributes.items(): - if value is None: - continue - - attribute = "%s.%s" % (yeti_node, attr) - if isinstance(value, (str, six.string_types)): - cmds.setAttr(attribute, value, type="string") - continue - - cmds.setAttr(attribute, value, **kwargs) - - # Ensure the node has no namespace identifiers - node_name = original_node.replace(":", "_") - - # Create full cache path - cache = os.path.join(self.fname, "{}.%04d.fur".format(node_name)) - cache = os.path.normpath(cache) - cache_fname = self.validate_cache(cache) - cache_path = os.path.join(self.fname, cache_fname) - - # Preset the viewport density - cmds.setAttr("%s.viewportDensity" % yeti_node, 0.1) - - # Add filename to `cacheFileName` attribute - cmds.setAttr("%s.cacheFileName" % yeti_node, - cache_path, - type="string") - - # Set verbosity for debug purposes - cmds.setAttr("%s.verbosity" % yeti_node, 2) - - # Enable the cache by setting the file mode - cmds.setAttr("%s.fileMode" % yeti_node, 1) + lib.set_attribute(attr, value, yeti_node) # Connect to the time node cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) - nodes.append(yeti_node) - nodes.append(transform_node) - return nodes From 106116ed47dba6273aca3e45218b7e3ba653f371 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:00 +0200 Subject: [PATCH 0895/1249] renamed for clearity --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 0 colorbleed/plugins/maya/publish/collect_vray_scene.py | 0 .../maya/publish/{submit_deadline.py => submit_maya_deadline.py} | 0 colorbleed/plugins/maya/publish/submit_vray_deadline.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 colorbleed/plugins/maya/create/colorbleed_vrayscene.py create mode 100644 colorbleed/plugins/maya/publish/collect_vray_scene.py rename colorbleed/plugins/maya/publish/{submit_deadline.py => submit_maya_deadline.py} (100%) create mode 100644 colorbleed/plugins/maya/publish/submit_vray_deadline.py diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_maya_deadline.py similarity index 100% rename from colorbleed/plugins/maya/publish/submit_deadline.py rename to colorbleed/plugins/maya/publish/submit_maya_deadline.py diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py new file mode 100644 index 0000000000..e69de29bb2 From 257e61d7690554b45dec61a2ec545a718646f80e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:23 +0200 Subject: [PATCH 0896/1249] added submit vray to deadline logic --- .../maya/publish/submit_vray_deadline.py | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index e69de29bb2..ac2a005694 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -0,0 +1,212 @@ +import getpass +import json +import os +from copy import deepcopy + +import pyblish.api + +from avalon import api +from avalon.vendor import requests + +from maya import cmds + + +class VraySubmitDeadline(pyblish.api.InstancePlugin): + """""" + label = "Submit to Deadline ( vrscene )" + order = pyblish.api.IntegratorOrder + hosts = ["maya"] + families = ["colorbleed.vrayscene"] + + def process(self, instance): + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", + "http://localhost:8082") + assert AVALON_DEADLINE, "Requires AVALON_DEADLINE" + + context = instance.context + + deadline_url = context.data["deadlineUrl"] + deadline_user = context.data.get("deadlineUser", getpass.getuser()) + + filepath = context.data["currentFile"] + filename = os.path.basename(filepath) + task_name = "{} - {}".format(filename, instance.name) + + batch_name = "VRay Scene Export - {}".format(filename) + + output_filepath = context.data["outputFilePath"].replace("\\", "/") + + # This is also the input file for the render job + first_file = self.format_output_filename_zero(instance, filename) + + # Primary job + self.log.info("Submitting export job ..") + + payload = { + "JobInfo": { + # Top-level group name + "BatchName": batch_name, + + # Job name, as seen in Monitor + "Name": task_name, + + # Arbitrary username, for visualisation in Monitor + "UserName": deadline_user, + + "Plugin": "MayaCmd", + "Frames": "1", + + "Comment": context.data.get("comment", ""), + "Whitelist": "cb7" + }, + "PluginInfo": { + + # Mandatory for Deadline + "Version": cmds.about(version=True), + + # Input + "SceneFile": filepath, + + # Output directory and filename + "OutputFilePath": output_filepath, + + "CommandLineOptions": self.build_command(instance), + + "UseOnlyCommandLineOptions": True, + + "SkipExistingFrames": True, + }, + + # Mandatory for Deadline, may be empty + "AuxFiles": [] + } + + environment = dict(AVALON_TOOLS="global;python36;maya2018") + + jobinfo_environment = self.build_jobinfo_environment(environment) + + payload["JobInfo"].update(jobinfo_environment) + + self.log.info("Job Data:\n{}".format(json.dumps(payload))) + + response = requests.post(url=deadline_url, json=payload) + if not response.ok: + raise RuntimeError(response.text) + + # Secondary job + # Store job to create dependency chain + dependency = response.json() + + self.log.info("Submitting render job ..") + + start_frame = int(instance.data["startFrame"]) + end_frame = int(instance.data["endFrame"]) + + payload_b = { + "JobInfo": { + + "JobDependency0": dependency["_id"], + "BatchName": batch_name, + "Name": "Render {}".format(task_name), + "UserName": deadline_user, + + "Frames": "{}-{}".format(start_frame, end_frame), + + "Plugin": "Vray", + "OverrideTaskExtraInfoNames": False, + "Whitelist": "cb7" + }, + "PluginInfo": { + + "InputFilename": first_file, + "Threads": 0, + "OutputFilename": "", + "SeparateFilesPerFrame": True, + "VRayEngine": "V-Ray", + + "Width": instance.data["resolution"][0], + "Height": instance.data["resolution"][1], + + }, + "AuxFiles": [], + } + + tools = environment["AVALON_TOOLS"] + ";vrayrenderslave" + environment_b = deepcopy(environment) + environment_b["AVALON_TOOLS"] = tools + + jobinfo_environment_b = self.build_jobinfo_environment(environment_b) + payload_b["JobInfo"].update(jobinfo_environment_b) + + self.log.info(json.dumps(payload_b)) + + response_b = requests.post(url=deadline_url, json=payload_b) + if not response_b.ok: + raise RuntimeError(response_b.text) + + print(response_b.text) + + def build_command(self, instance): + """Create command for Render.exe to export vray scene + + Returns: + str + + """ + + cmd = ('-r vray -proj {project} -cam {cam} -noRender -s {startFrame} ' + '-e {endFrame} -rl {layer} -exportFramesSeparate') + + return cmd.format(project=instance.context.data["workspaceDir"], + cam=instance.data.get("cam", "persp"), + startFrame=instance.data["startFrame"], + endFrame=instance.data["endFrame"], + layer=instance.name) + + def build_jobinfo_environment(self, env): + """Format environment keys and values to match Deadline rquirements + + Returns: + dict + + """ + return {"EnvironmentKeyValue%d" % index: "%s=%s" % (k, env[k]) + for index, k in enumerate(env)} + + def format_output_filename_zero(self, instance, filename): + """Format the expected output file of the Export job + + Example: + //_ + shot010 + + Args: + instance: + filename(str): + + Returns: + str + """ + + def smart_replace(string, key_values): + new_string = string + for key, value in key_values.items(): + new_string = new_string.replace(key, value) + return new_string + + # Ensure filename has no extension + file_name, _ = os.path.splitext(filename) + output_filename = instance.context.data["outputFilePath"] + + # Reformat without tokens + output_path = smart_replace(output_filename, + {"": file_name, + "": instance.name}) + + start_frame = int(instance.data["startFrame"]) + filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) + + result = filename_zero.replace("\\", "/") + + return result From 85f28aefb904a4ab75d13831255de3e7c89e73c9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:07:44 +0200 Subject: [PATCH 0897/1249] added vrayscene plugin --- .../maya/create/colorbleed_vrayscene.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index e69de29bb2..0f84db8971 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -0,0 +1,30 @@ +from collections import OrderedDict + +import avalon.maya + + +class CreateVRayScene(avalon.maya.Creator): + + label = "VRay Scene" + family = "colorbleed.vrayscene" + # icon = "blocks" + + def __init__(self, *args, **kwargs): + super(CreateVRayScene, self).__init__(*args, **kwargs) + + # We won't be publishing this one + self.data["id"] = "avalon.vrayscene" + + # We don't need subset or asset attributes + self.data.pop("subset", None) + self.data.pop("asset", None) + self.data.pop("active", None) + + data = OrderedDict(**self.data) + + data["camera"] = "persp" + data["pools"] = "" + + self.data = data + + self.options = {"useSelection": False} # Force no content From e20f0ac009573826aed92e4bb34b2fcd68919112 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:08:04 +0200 Subject: [PATCH 0898/1249] added vray scene collector --- .../maya/publish/collect_vray_scene.py | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index e69de29bb2..f99a1a8e86 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -0,0 +1,95 @@ +import os + +import pyblish.api + +from avalon import api, maya + +from maya import cmds + + +class CollectVRayScene(pyblish.api.ContextPlugin): + + order = pyblish.api.CollectorOrder + label = "Collect VRay Scene" + hosts = ["maya"] + + def process(self, context): + + asset = api.Session["AVALON_ASSET"] + + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", None) + assert AVALON_DEADLINE, "Can't submit without Deadline connection!" + + context.data["deadlineUrl"] = "{}/api/jobs".format(AVALON_DEADLINE) + + # Create output file path with template + file_name = context.data["currentFile"].replace("\\", "/") + output_filepath = os.path.join(context.data["workspaceDir"], + "vrayscene", + "", + "", + "_") + + context.data["outputFilePath"] = output_filepath + + # Get VRay Scene instance + vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") + if not vray_scenes: + self.log.info("No instance found of family: `colorbleed.vrayscene`") + return + + assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" + vray_scene = vray_scenes[0] + + camera = cmds.getAttr("{}.camera".format(vray_scene)) or "persp" + + # Animation data + start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") + end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") + context.data["startFrame"] = int(start_frame) + context.data["endFrame"] = int(end_frame) + + # Get render layers + renderlayers = [i for i in cmds.ls(type="renderLayer") if + cmds.getAttr("{}.renderable".format(i)) and not + cmds.referenceQuery(i, isNodeReferenced=True)] + + # Sort by displayOrder + def sort_by_display_order(layer): + return cmds.getAttr("%s.displayOrder" % layer) + + renderlayers = sorted(renderlayers, key=sort_by_display_order) + + resolution = (cmds.getAttr("defaultResolution.width"), + cmds.getAttr("defaultResolution.height")) + + for layer in renderlayers: + + if layer.endswith("defaultRenderLayer"): + layer = "masterLayer" + + data = { + "subset": layer, + "setMembers": layer, + + "camera": camera, + "startFrame": start_frame, + "endFrame": end_frame, + "renderer": "vray", + "resolution": resolution, + + # instance subset + "family": "VRay Scene", + "families": ["colorbleed.vrayscene"], + "asset": asset, + "time": api.time(), + "author": context.data["user"], + + # Add source to allow tracing back to the scene from + # which was submitted originally + "source": file_name + } + + instance = context.create_instance(layer) + self.log.info("Created: %s" % instance.name) + instance.data.update(data) From b1fdec440bc784d806b429ad18e692aa3a7e656b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 26 Jul 2018 18:09:11 +0200 Subject: [PATCH 0899/1249] removed debug whitelist --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index ac2a005694..d1fbe3efab 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -58,7 +58,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "Frames": "1", "Comment": context.data.get("comment", ""), - "Whitelist": "cb7" }, "PluginInfo": { From 84236373e926c9a5f64d172c98d0e0c9c11f233d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:14:59 +0200 Subject: [PATCH 0900/1249] added validator for translator settings --- .../publish/validate_translator_settings.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_translator_settings.py diff --git a/colorbleed/plugins/maya/publish/validate_translator_settings.py b/colorbleed/plugins/maya/publish/validate_translator_settings.py new file mode 100644 index 0000000000..7c7479792c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_translator_settings.py @@ -0,0 +1,45 @@ +import pyblish.api +import colorbleed.api + +from maya import cmds + + +class ValidateTranslatorEnabled(pyblish.api.ContextPlugin): + + order = colorbleed.api.ValidateContentsOrder + label = "VRay Translator Settings" + families = ["colorbleed.vrayscene"] + actions = [colorbleed.api.RepairContextAction] + + def process(self, context): + + # Get vraySettings node + vray_settings = cmds.ls(type="VRaySettingsNode") + assert vray_settings, "Please ensure a VRay Settings Node is present" + + node = vray_settings[0] + + if not cmds.getAttr("{}.vrscene_on".format(node)): + self.info.error("Export vrscene not enabled") + + if not cmds.getAttr("{}.misc_eachFrameInFile".format(node)): + self.info.error("Each Frame in File not enabled") + + vrscene_filename = cmds.getAttr("{}.vrscene_filename".format(node)) + if vrscene_filename != "vrayscene//_/": + self.info.error("Template for file name is wrong") + + @classmethod + def repair(cls, context): + + vray_settings = cmds.ls(type="VRaySettingsNode") + if not vray_settings: + node = cmds.createNode("VRaySettingsNode") + else: + node = vray_settings[0] + + cmds.setAttr("{}.vrscene_on".format(node), True) + cmds.setAttr("{}.misc_eachFrameInFile".format(node), True) + cmds.setAttr("{}.vrscene_filename".format(node), + "vrayscene//_/", + type="string") From 5d6c355b45c7072ab2f767cab78f317c1a4eed61 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:16:15 +0200 Subject: [PATCH 0901/1249] added colorbleed.vrayscene to plugin --- .../plugins/global/publish/submit_publish_job.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 34a09c9b81..5cbba4f800 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -123,7 +123,9 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion", "maya"] - families = ["colorbleed.saver.deadline", "colorbleed.renderlayer"] + families = ["colorbleed.saver.deadline", + "colorbleed.renderlayer", + "colorbleed.vrayscene"] def process(self, instance): @@ -162,8 +164,10 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), ext=ext) + # Remove deadline submission job, not needed in metadata + data.pop("deadlineSubmissionJob") + # Write metadata for publish job - render_job = data.pop("deadlineSubmissionJob") metadata = { "regex": regex, "startFrame": start, @@ -189,7 +193,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): override = data["overrideExistingFrame"] # override = data.get("overrideExistingFrame", False) - out_file = render_job.get("OutFile") + out_file = job.get("OutFile") if not out_file: raise RuntimeError("OutFile not found in render job!") From ad55a128585964f1ba9fe3a046c0617b8b952d39 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 13:16:44 +0200 Subject: [PATCH 0902/1249] Extended options for vrayscene instance --- .../maya/create/colorbleed_vrayscene.py | 4 + .../maya/publish/collect_vray_scene.py | 74 +++++++++++-------- .../maya/publish/submit_vray_deadline.py | 58 ++++++++++++--- 3 files changed, 93 insertions(+), 43 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 0f84db8971..e30f4ac3ce 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -23,6 +23,10 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) data["camera"] = "persp" + data["suspendRenderJob"] = False + data["suspendPublishJob"] = False + data["includeDefaultRenderLayer"] = False + data["extendFrames"] = False data["pools"] = "" self.data = data diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index f99a1a8e86..6ecb07f123 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -8,6 +8,8 @@ from maya import cmds class CollectVRayScene(pyblish.api.ContextPlugin): + """Collect all information prior for exporting vrscenes + """ order = pyblish.api.CollectorOrder label = "Collect VRay Scene" @@ -15,22 +17,12 @@ class CollectVRayScene(pyblish.api.ContextPlugin): def process(self, context): + # Sort by displayOrder + def sort_by_display_order(layer): + return cmds.getAttr("%s.displayOrder" % layer) + asset = api.Session["AVALON_ASSET"] - - AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", None) - assert AVALON_DEADLINE, "Can't submit without Deadline connection!" - - context.data["deadlineUrl"] = "{}/api/jobs".format(AVALON_DEADLINE) - - # Create output file path with template - file_name = context.data["currentFile"].replace("\\", "/") - output_filepath = os.path.join(context.data["workspaceDir"], - "vrayscene", - "", - "", - "_") - - context.data["outputFilePath"] = output_filepath + work_dir = context.data["workspaceDir"] # Get VRay Scene instance vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") @@ -41,29 +33,48 @@ class CollectVRayScene(pyblish.api.ContextPlugin): assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" vray_scene = vray_scenes[0] - camera = cmds.getAttr("{}.camera".format(vray_scene)) or "persp" + vrscene_data = {k: cmds.getAttr("%s.%s" % (vray_scene, k)) for + k in cmds.listAttr(vray_scene, userDefined=True)} - # Animation data - start_frame = cmds.getAttr("defaultRenderGlobals.startFrame") - end_frame = cmds.getAttr("defaultRenderGlobals.endFrame") - context.data["startFrame"] = int(start_frame) - context.data["endFrame"] = int(end_frame) + # Output data + start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) + end_frame = int(cmds.getAttr("defaultRenderGlobals.endFrame")) - # Get render layers - renderlayers = [i for i in cmds.ls(type="renderLayer") if - cmds.getAttr("{}.renderable".format(i)) and not - cmds.referenceQuery(i, isNodeReferenced=True)] + # Create output file path with template + file_name = context.data["currentFile"].replace("\\", "/") + vrscene = ("vrayscene", "", "_", "") + vrscene_output = os.path.join(work_dir, *vrscene) - # Sort by displayOrder - def sort_by_display_order(layer): - return cmds.getAttr("%s.displayOrder" % layer) + vrscene_data["startFrame"] = start_frame + vrscene_data["endFrame"] = end_frame + vrscene_data["vrsceneOutput"] = vrscene_output - renderlayers = sorted(renderlayers, key=sort_by_display_order) + context.data["startFrame"] = start_frame + context.data["endFrame"] = end_frame + # Check and create render output template for render job + # outputDir is required for submit_publish_job + if not vrscene_data.get("suspendRenderJob", False): + renders = ("renders", "", "_", "") + output_renderpath = os.path.join(work_dir, *renders) + vrscene_data["outputDir"] = output_renderpath + + # Get resolution resolution = (cmds.getAttr("defaultResolution.width"), cmds.getAttr("defaultResolution.height")) - for layer in renderlayers: + # Get render layers + render_layers = [i for i in cmds.ls(type="renderLayer") if + cmds.getAttr("{}.renderable".format(i)) and not + cmds.referenceQuery(i, isNodeReferenced=True)] + + # Check if we need to filter out the default render layer + if vrscene_data.get("includeDefaultRenderLayer", True): + render_layers = [r for r in render_layers + if r != "defaultRenderLayer"] + + render_layers = sorted(render_layers, key=sort_by_display_order) + for layer in render_layers: if layer.endswith("defaultRenderLayer"): layer = "masterLayer" @@ -72,7 +83,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "subset": layer, "setMembers": layer, - "camera": camera, "startFrame": start_frame, "endFrame": end_frame, "renderer": "vray", @@ -90,6 +100,8 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "source": file_name } + data.update(vrscene_data) + instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) instance.data.update(data) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index d1fbe3efab..77cb8f2ec6 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -12,7 +12,15 @@ from maya import cmds class VraySubmitDeadline(pyblish.api.InstancePlugin): - """""" + """Export the scene to `.vrscene` files per frame per render layer + + vrscene files will be written out based on the following template: + /vrayscene//_/ + + A dependency job will be added for each layer to render the framer + through VRay Standalone + + """ label = "Submit to Deadline ( vrscene )" order = pyblish.api.IntegratorOrder hosts = ["maya"] @@ -26,7 +34,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): context = instance.context - deadline_url = context.data["deadlineUrl"] + deadline_url = "{}/api/jobs".format(AVALON_DEADLINE) deadline_user = context.data.get("deadlineUser", getpass.getuser()) filepath = context.data["currentFile"] @@ -35,10 +43,13 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): batch_name = "VRay Scene Export - {}".format(filename) - output_filepath = context.data["outputFilePath"].replace("\\", "/") + # Get the output template for vrscenes + vrscene_output = instance.data["vrsceneOutput"] # This is also the input file for the render job - first_file = self.format_output_filename_zero(instance, filename) + first_file = self.format_output_filename(instance, + filename, + vrscene_output) # Primary job self.log.info("Submitting export job ..") @@ -68,7 +79,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "SceneFile": filepath, # Output directory and filename - "OutputFilePath": output_filepath, + "OutputFilePath": vrscene_output.replace("\\", "/"), "CommandLineOptions": self.build_command(instance), @@ -97,11 +108,26 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Store job to create dependency chain dependency = response.json() + if instance.data["suspendRenderJob"]: + self.log.info("Skipping render job and publish job") + return + self.log.info("Submitting render job ..") start_frame = int(instance.data["startFrame"]) end_frame = int(instance.data["endFrame"]) + # Create output directory for renders + render_ouput = self.format_output_filename(instance, + filename, + instance.data["outputDir"], + dir=True) + + self.log.info("Render output: %s" % render_ouput) + + # Update output dir + instance.data["outputDir"] = render_ouput + payload_b = { "JobInfo": { @@ -120,7 +146,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "InputFilename": first_file, "Threads": 0, - "OutputFilename": "", + "OutputFilename": render_ouput, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", @@ -131,6 +157,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [], } + # Add vray renderslave to environment tools = environment["AVALON_TOOLS"] + ";vrayrenderslave" environment_b = deepcopy(environment) environment_b["AVALON_TOOLS"] = tools @@ -140,11 +167,14 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): self.log.info(json.dumps(payload_b)) + # Post job to deadline response_b = requests.post(url=deadline_url, json=payload_b) if not response_b.ok: raise RuntimeError(response_b.text) - print(response_b.text) + # Add job for publish job + if not instance.data.get("suspendPublishJob", False): + instance.data["deadlineSubmissionJob"] = response_b.json() def build_command(self, instance): """Create command for Render.exe to export vray scene @@ -173,19 +203,21 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): return {"EnvironmentKeyValue%d" % index: "%s=%s" % (k, env[k]) for index, k in enumerate(env)} - def format_output_filename_zero(self, instance, filename): + def format_output_filename(self, instance, filename, template, dir=False): """Format the expected output file of the Export job Example: - //_ - shot010 + /_/ + "shot010_v006/shot010_v006_CHARS/CHARS" Args: instance: filename(str): + dir(bool): Returns: str + """ def smart_replace(string, key_values): @@ -196,13 +228,15 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Ensure filename has no extension file_name, _ = os.path.splitext(filename) - output_filename = instance.context.data["outputFilePath"] # Reformat without tokens - output_path = smart_replace(output_filename, + output_path = smart_replace(template, {"": file_name, "": instance.name}) + if dir: + return output_path.replace("\\", "/") + start_frame = int(instance.data["startFrame"]) filename_zero = "{}_{:04d}.vrscene".format(output_path, start_frame) From 72ceefc5484458ac3ca6faeef7da13c4592d94fb Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 18:10:59 +0200 Subject: [PATCH 0903/1249] Don't throw errors, not needed --- colorbleed/plugins/global/publish/submit_publish_job.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 5cbba4f800..ca5e186825 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -136,8 +136,10 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Get a submission job job = instance.data.get("deadlineSubmissionJob") if not job: - raise RuntimeError("Can't continue without valid deadline " - "submission prior to this plug-in.") + self.log.warning("Can't continue without valid deadline " + "submission prior to this plug-in.") + self.log.info("Skipping Publish Job") + return data = instance.data.copy() subset = data["subset"] @@ -162,7 +164,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ext = "\.\D+" regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=ext) + ext=re.escape(subset)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From ccf36bffb3289ae3b935937aa9fbe310316a841c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 27 Jul 2018 18:11:25 +0200 Subject: [PATCH 0904/1249] Added extension and fixed outputfilename --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 4 ++++ colorbleed/plugins/maya/publish/submit_vray_deadline.py | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 6ecb07f123..cedd527106 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -63,6 +63,9 @@ class CollectVRayScene(pyblish.api.ContextPlugin): resolution = (cmds.getAttr("defaultResolution.width"), cmds.getAttr("defaultResolution.height")) + # Get format extension + extension = cmds.getAttr("vraySettings.imageFormatStr") + # Get render layers render_layers = [i for i in cmds.ls(type="renderLayer") if cmds.getAttr("{}.renderable".format(i)) and not @@ -87,6 +90,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "endFrame": end_frame, "renderer": "vray", "resolution": resolution, + "ext": extension, # instance subset "family": "VRay Scene", diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 77cb8f2ec6..925013c693 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -77,7 +77,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Input "SceneFile": filepath, - # Output directory and filename "OutputFilePath": vrscene_output.replace("\\", "/"), @@ -93,6 +92,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): } environment = dict(AVALON_TOOLS="global;python36;maya2018") + environment.update(api.Session.copy()) jobinfo_environment = self.build_jobinfo_environment(environment) @@ -116,6 +116,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): start_frame = int(instance.data["startFrame"]) end_frame = int(instance.data["endFrame"]) + ext = instance.data.get("ext", "exr") # Create output directory for renders render_ouput = self.format_output_filename(instance, @@ -128,6 +129,10 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): # Update output dir instance.data["outputDir"] = render_ouput + # Format output file name + sequence_fiename = ".".join([instance.name, "%04d", ext]) + output_filename = os.path.join(render_ouput, sequence_fiename) + payload_b = { "JobInfo": { @@ -146,7 +151,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "InputFilename": first_file, "Threads": 0, - "OutputFilename": render_ouput, + "OutputFilename": output_filename, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", From be668ac27ab5250f8af18af84270af72162cbb0b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 09:48:44 +0200 Subject: [PATCH 0905/1249] reverting unwanted change --- colorbleed/plugins/global/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index ca5e186825..1c77d5c845 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -164,7 +164,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ext = "\.\D+" regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=re.escape(subset)) + ext=re.escape(ext)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From c16013beec3d2bafc47b6dbf4bc8cede551649a4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 12:00:02 +0200 Subject: [PATCH 0906/1249] include the '.' in the regex --- colorbleed/plugins/global/publish/submit_publish_job.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 1c77d5c845..184ed0b43d 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -159,12 +159,12 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # This assumes the output files start with subset name and ends with # a file extension. if "ext" in instance.data: - ext = re.escape(instance.data["ext"]) + ext = instance.data["ext"].strip(".") else: ext = "\.\D+" - regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), - ext=re.escape(ext)) + regex = "^{subset}.*\d+\.{ext}$".format(subset=re.escape(subset), + ext=re.escape(ext)) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From 5f6c0b9e574c18c9b69ce318ac1a3aa0d3b0b433 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 30 Jul 2018 12:27:00 +0200 Subject: [PATCH 0907/1249] Fix typo --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 925013c693..97c26497f8 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -130,8 +130,8 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): instance.data["outputDir"] = render_ouput # Format output file name - sequence_fiename = ".".join([instance.name, "%04d", ext]) - output_filename = os.path.join(render_ouput, sequence_fiename) + sequence_filename = ".".join([instance.name, "%04d", ext]) + output_filename = os.path.join(render_ouput, sequence_filename) payload_b = { "JobInfo": { @@ -150,7 +150,6 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "PluginInfo": { "InputFilename": first_file, - "Threads": 0, "OutputFilename": output_filename, "SeparateFilesPerFrame": True, "VRayEngine": "V-Ray", From 9df5ab09dfadc05893fb452b150e5ed7b40e96f0 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 31 Jul 2018 15:33:38 +0200 Subject: [PATCH 0908/1249] Created context validator for accessible render layers --- .../validate_renderlayers_accessible.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py diff --git a/colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py b/colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py new file mode 100644 index 0000000000..ecf8488e3e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py @@ -0,0 +1,76 @@ +from collections import defaultdict + +import pyblish.api + +from maya import cmds + + +class ValidateMayaRenderLayersAccessible(pyblish.api.ContextPlugin): + """Validate if the render is accessible to iterate through + + A bug in Maya can result in inaccessible render layers. + The render layer is not visible in the render layer manager or outliner + but can be found through code. The bug occurs when a referenced file is + imported. + + Sometimes the issue is an unresolved adjustment in render layers. + + """ + + order = pyblish.api.CollectorOrder + label = "Render Layer Accessible" + hosts = ["maya"] + + def process(self, context): + + # Get render globals node + try: + render_globals = cmds.ls("renderglobalsDefault")[0] + except IndexError: + self.log.error("Cannot collect renderlayers without " + "renderGlobals node") + return + + loaded, referenced = self.get_invalid() + if loaded or referenced: + message_a = ("Discovered inaccessible loaded renderlayers: %s" + % list(loaded)) if loaded else "" + + message_b = ("Discovered inaccessible referenced renderlayers: %s" + % list(referenced)) if referenced else "" + + raise RuntimeError("{}\n{}".format(message_a, message_b)) + + @classmethod + def get_invalid(cls): + + invalid = [] + invalid_referenced = [] + + lookup = defaultdict(list) + render_layers = cmds.ls(type="renderLayer") + + # Create look up + for layer in render_layers: + parts = layer.rsplit(":", 1) + lookup[parts[-1]].append(layer) + + for name, layers in lookup.items(): + cls.log.info("Checking if %i '%s' layers can be accessed" % + (len(layers), name)) + + for layer in layers: + try: + cmds.editRenderLayerGlobals(currentRenderLayer=layer) + except RuntimeError: + if cmds.referenceQuery(layer, isNodeReferenced=True): + cls.log.error("Referenced layer '%s' cannot " + "be accessed" % layer) + invalid_referenced.append(layer) + else: + cls.log.error("Non-referenced layer '%s' cannot " + "be accessed" % layer) + + invalid.append(layer) + + return invalid, invalid_referenced From 2e321b451c03cd53d7c092a37b67f880e2ec5001 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 31 Jul 2018 15:34:08 +0200 Subject: [PATCH 0909/1249] Move the plugin below the validator --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 4fa1c4a846..48d8f204a9 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -9,7 +9,7 @@ import colorbleed.maya.lib as lib class CollectMayaRenderlayers(pyblish.api.ContextPlugin): """Gather instances by active render layers""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.01 hosts = ["maya"] label = "Render Layers" From cbd361c4ebd505952bf3949396a7e4745668a464 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 1 Aug 2018 12:20:59 +0200 Subject: [PATCH 0910/1249] added new copy file plugin --- colorbleed/plugins/global/load/copy_file.py | 35 +++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 colorbleed/plugins/global/load/copy_file.py diff --git a/colorbleed/plugins/global/load/copy_file.py b/colorbleed/plugins/global/load/copy_file.py new file mode 100644 index 0000000000..738dc9f188 --- /dev/null +++ b/colorbleed/plugins/global/load/copy_file.py @@ -0,0 +1,35 @@ +from avalon import api, style + + +class CopyFile(api.Loader): + """Copy the published file to be pasted at the desired location""" + + representations = ["*"] + families = ["*"] + + label = "Copy File" + order = 10 + icon = "copy" + color = style.colors.default + + def load(self, context, name=None, namespace=None, data=None): + self.log.info("Added copy to clipboard: {0}".format(self.fname)) + self.copy_file_to_clipboard(self.fname) + + @staticmethod + def copy_file_to_clipboard(path): + from avalon.vendor.Qt import QtCore, QtWidgets + + app = QtWidgets.QApplication.instance() + assert app, "Must have running QApplication instance" + + # Build mime data for clipboard + file_path = QtCore.QUrl.fromLocalFile(path) + byte_array = QtCore.QByteArray("copy\n").append(file_path) + + mime = QtCore.QMimeData() + mime.setData("text/uri-list", byte_array) + + # Set to Clipboard + clipboard = app.clipboard() + clipboard.setMimeData(mime) From 8f80297fe5cce65c04e8b8c3ddd001e586fd37b7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 1 Aug 2018 12:21:16 +0200 Subject: [PATCH 0911/1249] added register_loader_plugins --- colorbleed/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index e5d1aee374..8e5f25390f 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -24,3 +24,9 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + + +def register_loader_plugins(): + """Wrap to install Loader plugins for standalone""" + + install() From 5e6757071ed346150a9592f832be14f408f2a6cf Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 1 Aug 2018 12:22:38 +0200 Subject: [PATCH 0912/1249] improved naming of plugin --- colorbleed/plugins/global/load/copy_to_clipboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/global/load/copy_to_clipboard.py b/colorbleed/plugins/global/load/copy_to_clipboard.py index c773ebfeb3..5f23981a34 100644 --- a/colorbleed/plugins/global/load/copy_to_clipboard.py +++ b/colorbleed/plugins/global/load/copy_to_clipboard.py @@ -6,13 +6,13 @@ class CopyToClipboardLoader(api.Loader): representations = ["*"] families = ["*"] - label = "Copy to Clipboard" + label = "Copy file path to Clipboard" order = 20 icon = "clipboard" color = "#999999" def load(self, context, name=None, namespace=None, data=None): - self.log.info("Added to clipboard: {0}".format(self.fname)) + self.log.info("Added file path to clipboard: {0}".format(self.fname)) self.copy_file_to_clipboard(self.fname) @staticmethod From aaaf1e6ef58a5376117117e175280a7257a3e1d3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 11:46:04 +0200 Subject: [PATCH 0913/1249] added vrayproxy creator --- colorbleed/plugins/maya/create/colorbleed_vrayproxy.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 colorbleed/plugins/maya/create/colorbleed_vrayproxy.py diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py new file mode 100644 index 0000000000..5e5653b90b --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py @@ -0,0 +1,10 @@ +import avalon.maya + + +class CreatePointCache(avalon.maya.Creator): + """Alembic pointcache for animated data""" + + name = "vrayproxy" + label = "VRay Proxy" + family = "colorbleed.vrayproxy" + icon = "gears" From 9744fec1b799ef56af6d7e43a145141cf2f46c0b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:32:45 +0200 Subject: [PATCH 0914/1249] added loader for vrayproxy --- .../plugins/maya/load/load_vrayproxy.py | 98 +++++++++++++++++++ .../plugins/maya/publish/extract_vrayproxy.py | 0 .../maya/publish/validate_vrayproxy.py | 0 3 files changed, 98 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_vrayproxy.py create mode 100644 colorbleed/plugins/maya/publish/extract_vrayproxy.py create mode 100644 colorbleed/plugins/maya/publish/validate_vrayproxy.py diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py new file mode 100644 index 0000000000..87369d5a3c --- /dev/null +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -0,0 +1,98 @@ +from avalon.maya import lib +import colorbleed.maya.plugin + +import maya.cmds as cmds + + +class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): + """Load vrmesh files""" + + families = ["colorbleed.vrayproxy"] + representations = ["vrmesh"] + + label = "Reference VRay Proxy" + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, data): + + asset_name = context['asset']["name"] + print("---", namespace) + + namespace = namespace or lib.unique_namespace( + asset_name + "_", + prefix="_" if asset_name[0].isdigit() else "", + suffix="_", + ) + + print(">>>", namespace) + + # Pre-run check + if not cmds.objExists("vraySettings"): + cmds.createNode("VRaySettingsNode", name="vraySettings") + + # Add namespace + cmds.namespace(set=":") + cmds.namespace(add=namespace) + cmds.namespace(set=namespace) + + with lib.maintained_selection(): + nodes = self.create_vray_proxy(name) + + self[:] = nodes + + return nodes + + def create_vray_proxy(self, name, attrs=None): + """Re-create the structure created by VRay to support vrmeshes + + Args: + name(str): name of the asset + + Returns: + nodes(list) + """ + + # Create nodes + vray_mesh = cmds.createNode('VRayMesh', name="{}_VRMS".format(name)) + mesh_shape = cmds.createNode("mesh", name="{}_GEOShape".format(name)) + vray_mat = cmds.createNode("VRayMeshMaterial", + name="{}_VRMM".format(name)) + vray_mat_sg = cmds.createNode("shadingEngine", + name="{}_VRSG".format(name)) + + cmds.setAttr("{}.fileName2".format(vray_mesh), + self.fname, + type="string") + + # Apply attributes from export + # cmds.setAttr("{}.animType".format(vray_mesh), 3) + + # Create important connections + cmds.connectAttr("{}.fileName2".format(vray_mesh), + "{}.fileName".format(vray_mat)) + cmds.connectAttr("{}.instancing".format(vray_mesh), + "{}.instancing".format(vray_mat)) + cmds.connectAttr("{}.output".format(vray_mesh), + "{}.inMesh".format(mesh_shape)) + cmds.connectAttr("{}.overrideFileName".format(vray_mesh), + "{}.overrideFileName".format(vray_mat)) + cmds.connectAttr("{}.currentFrame".format(vray_mesh), + "{}.currentFrame".format(vray_mat)) + + # Set surface shader input + cmds.connectAttr("{}.outColor".format(vray_mat), + "{}.surfaceShader".format(vray_mat_sg)) + + # Connect mesh to shader + cmds.sets([mesh_shape], addElement=vray_mat_sg) + + group_node = cmds.group(empty=True, name="{}_GRP".format(name)) + mesh_transform = cmds.listRelatives(mesh_shape, + parent=True, fullPath=True) + cmds.parent(mesh_transform, group_node) + + nodes = [vray_mesh, mesh_shape, vray_mat, vray_mat_sg, group_node] + + return nodes diff --git a/colorbleed/plugins/maya/publish/extract_vrayproxy.py b/colorbleed/plugins/maya/publish/extract_vrayproxy.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy.py b/colorbleed/plugins/maya/publish/validate_vrayproxy.py new file mode 100644 index 0000000000..e69de29bb2 From 15d09df4af6276969ca1373b7c521cdaa04c4c5d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:33:20 +0200 Subject: [PATCH 0915/1249] added message for assertion --- colorbleed/plugins/global/publish/integrate.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 2418971546..edcb9ded77 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -32,6 +32,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.pointcache", "colorbleed.setdress", "colorbleed.rig", + "colorbleed.vrayproxy", "colorbleed.yetiRig", "colorbleed.yeticache"] @@ -191,7 +192,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # |_______| # fname = files - assert not os.path.isabs(fname) + assert not os.path.isabs(fname), ( + "Given file name is a full path" + ) _, ext = os.path.splitext(fname) template_data["representation"] = ext[1:] From 2273a61717db658ba74dd59f6a93ed15b03a2407 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:33:36 +0200 Subject: [PATCH 0916/1249] added options --- .../plugins/maya/create/colorbleed_vrayproxy.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py index 5e5653b90b..e100c31bd0 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py @@ -1,10 +1,23 @@ +from collections import OrderedDict + import avalon.maya -class CreatePointCache(avalon.maya.Creator): +class CreateVrayProxy(avalon.maya.Creator): """Alembic pointcache for animated data""" name = "vrayproxy" label = "VRay Proxy" family = "colorbleed.vrayproxy" icon = "gears" + + def __init__(self, *args, **kwargs): + super(CreateVrayProxy, self).__init__(*args, **kwargs) + + data = OrderedDict(**self.data) + + data["animation"] = False + data["startFrame"] = 1 + data["endFrame"] = 1 + + self.data.update(data) From 6b2253263bdec7647165de1b6c4675ec6bc3bace Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:33:46 +0200 Subject: [PATCH 0917/1249] addeds simple validator --- .../maya/publish/validate_vrayproxy.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy.py b/colorbleed/plugins/maya/publish/validate_vrayproxy.py index e69de29bb2..40c45094db 100644 --- a/colorbleed/plugins/maya/publish/validate_vrayproxy.py +++ b/colorbleed/plugins/maya/publish/validate_vrayproxy.py @@ -0,0 +1,27 @@ +import pyblish.api + + +class ValidateVrayProxy(pyblish.api.InstancePlugin): + + order = pyblish.api.ValidatorOrder + label = 'VRay Proxy Settings' + hosts = ['maya'] + families = ['colorbleed.vrayproxy'] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("'%s' has invalid settings for VRay Proxy " + "export!" % instance.name) + + @classmethod + def get_invalid(cls, instance): + data = instance.data + + if not data["setMembers"]: + cls.log.error("'%s' is empty! This is a bug" % instance.name) + + if data["animation"]: + if data["endFrame"] < data["startFrame"]: + cls.log.error("End frame is smaller than start frame") From 7fd63ac1345243fa2574a3e74aeccf40e714ff39 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:34:06 +0200 Subject: [PATCH 0918/1249] added vray proxy extractor --- .../plugins/maya/publish/extract_vrayproxy.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_vrayproxy.py b/colorbleed/plugins/maya/publish/extract_vrayproxy.py index e69de29bb2..f1190d52ab 100644 --- a/colorbleed/plugins/maya/publish/extract_vrayproxy.py +++ b/colorbleed/plugins/maya/publish/extract_vrayproxy.py @@ -0,0 +1,60 @@ +import os + +import avalon.maya +import colorbleed.api + +from maya import cmds + + +class ExtractVRayProxy(colorbleed.api.Extractor): + """Extract the content of the instance to a vrmesh file + + Things to pay attention to: + - If animation is toggled, are the frames correct + - + """ + + label = "VRay Proxy (.vrmesh)" + hosts = ["maya"] + families = ["colorbleed.vrayproxy"] + + def process(self, instance): + + staging_dir = self.staging_dir(instance) + file_name = "{}.vrmesh".format(instance.name) + file_path = os.path.join(staging_dir, file_name) + + anim_on = instance.data["animation"] + if not anim_on: + # Remove animation information because it is not required for + # non-animated subsets + instance.data.pop("startFrame", None) + instance.data.pop("endFrame", None) + + start_frame = 1 + end_frame = 1 + else: + start_frame = instance.data["startFrame"] + end_frame = instance.data["endFrame"] + + # Write out vrmesh file + self.log.info("Writing: '%s'" % file_path) + with avalon.maya.maintained_selection(): + cmds.select(instance.data["setMembers"], noExpand=True) + cmds.vrayCreateProxy(exportType=1, + dir=staging_dir, + fname=file_name, + animOn=anim_on, + animType=3, + startFrame=start_frame, + endFrame=end_frame, + ignoreHiddenObjects=True, + createProxyNode=False) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(file_name) + + self.log.info("Extracted instance '%s' to: %s" + % (instance.name, staging_dir)) From c4c5c3e0e34621a8ea2074e88ad2f674d6ec6259 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 2 Aug 2018 17:35:42 +0200 Subject: [PATCH 0919/1249] removed unwanted commit --- colorbleed/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 8e5f25390f..e5d1aee374 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -24,9 +24,3 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - - -def register_loader_plugins(): - """Wrap to install Loader plugins for standalone""" - - install() From 30f18f139aebb5d73cc10d3ebca7fd5ed0a78fc6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 9 Aug 2018 16:34:25 +0200 Subject: [PATCH 0920/1249] Fixed referrence namespace bug with vrproxy loader --- colorbleed/plugins/maya/load/load_vrayproxy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py index 87369d5a3c..0773479e30 100644 --- a/colorbleed/plugins/maya/load/load_vrayproxy.py +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -41,6 +41,9 @@ class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): nodes = self.create_vray_proxy(name) self[:] = nodes + # Make sure to restore the default namespace, or anything imported or + # refereced after this gets added to this namespace + cmds.namespace(set=":") return nodes From 156f7305586aafbd9a3803d59312527f4aafb65e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 9 Aug 2018 16:36:52 +0200 Subject: [PATCH 0921/1249] Readability --- colorbleed/plugins/maya/load/load_vrayproxy.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py index 0773479e30..bb98f69815 100644 --- a/colorbleed/plugins/maya/load/load_vrayproxy.py +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -41,6 +41,7 @@ class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): nodes = self.create_vray_proxy(name) self[:] = nodes + # Make sure to restore the default namespace, or anything imported or # refereced after this gets added to this namespace cmds.namespace(set=":") From 2dbd7fe0179dc1d56bfd1a9633ca96d191b95279 Mon Sep 17 00:00:00 2001 From: Sven Neve Date: Thu, 16 Aug 2018 14:34:56 +0200 Subject: [PATCH 0922/1249] Added sub menu entry for image 2 tiled exr tool --- colorbleed/maya/menu.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 692b6c04a6..ad21949180 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -945,6 +945,18 @@ ], "title": "Fix RenderLayer Missing Referenced Nodes Overrides", "tooltip": "" + }, + { + "type": "action", + "title": "Image 2 Tiled EXR", + "command": "$COLORBLEED_SCRIPTS\\shading\\open_img2exr.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "exr" + ] } ] }, From f082f72acf328923573b8189e7e420e1e002782c Mon Sep 17 00:00:00 2001 From: Sven Neve Date: Fri, 17 Aug 2018 11:10:40 +0200 Subject: [PATCH 0923/1249] Fix for YETI-6 --- colorbleed/plugins/maya/load/load_yeti_cache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index dd6ebbeb05..3806bc5623 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -171,6 +171,12 @@ class YetiCacheLoader(api.Loader): name=node_name, parent=transform_node) + # Fix for : YETI-6 + # Fixes the render stats (this is literally taken from Perigrene's + # ../scripts/pgYetiNode.mel script) + cmds.setAttr(yeti_node + ".visibleInReflections", 1) + cmds.setAttr(yeti_node + ".visibleInRefractions", 1) + lib.set_id(yeti_node, node_settings["cbId"]) nodes.append(transform_node) From e719fbc6aef6a06fc8fb5926cde7046bee2f7a8d Mon Sep 17 00:00:00 2001 From: Sven Neve Date: Fri, 17 Aug 2018 11:13:54 +0200 Subject: [PATCH 0924/1249] Added local pipeline added edits. --- colorbleed/maya/menu.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index ad21949180..d394f5dba1 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1505,6 +1505,18 @@ "tooltip": "" }, {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", + "sourcetype": "file", + "tags": [ + "particles", + "houdini", "houdiniTool", "houdiniEngine" + ], + "title": "Replicate Houdini Tool", + "tooltip": "" + }, + {"type": "separator"}, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\particles\\clearInitialState.py", From a098bc619b2e8106905b5102c9977a4fddb993ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Aug 2018 18:49:18 +0200 Subject: [PATCH 0925/1249] Implement V-Ray Proxy support correctly --- colorbleed/maya/lib.py | 25 ++++++ .../plugins/maya/load/load_vrayproxy.py | 90 +++++++++++++------ 2 files changed, 86 insertions(+), 29 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7ca7e109ed..bb2690e3f8 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -14,6 +14,7 @@ from maya import cmds, mel from avalon import api, maya, io, pipeline from avalon.vendor.six import string_types +import avalon.maya.lib from colorbleed import lib @@ -364,6 +365,30 @@ def no_undo(flush=False): cmds.undoInfo(**{keyword: original}) +@contextlib.contextmanager +def namespaced(namespace, new=True): + """Work inside namespace during context + + Args: + new (bool): When enabled this will rename the namespace to a unique + namespace if the input namespace already exists. + + Yields: + str: The namespace that is used during the context + + """ + original = cmds.namespaceInfo(cur=True) + if new: + namespace = avalon.maya.lib.unique_namespace(namespace) + cmds.namespace(add=namespace) + + try: + cmds.namespace(set=namespace) + yield namespace + finally: + cmds.namespace(set=original) + + def polyConstraint(components, *args, **kwargs): """Return the list of *components* with the constraints applied. diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py index bb98f69815..ba3a1d3e45 100644 --- a/colorbleed/plugins/maya/load/load_vrayproxy.py +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -1,54 +1,84 @@ from avalon.maya import lib -import colorbleed.maya.plugin +from avalon import api import maya.cmds as cmds -class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): - """Load vrmesh files""" +class VRayProxyLoader(api.Loader): + """Load VRayMesh proxy""" families = ["colorbleed.vrayproxy"] representations = ["vrmesh"] - label = "Reference VRay Proxy" + label = "Import VRay Proxy" order = -10 icon = "code-fork" color = "orange" - def process_reference(self, context, name, namespace, data): + def load(self, context, name, namespace, data): + + from avalon.maya.pipeline import containerise + from colorbleed.maya.lib import namespaced asset_name = context['asset']["name"] - print("---", namespace) - namespace = namespace or lib.unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", ) - print(">>>", namespace) - - # Pre-run check - if not cmds.objExists("vraySettings"): - cmds.createNode("VRaySettingsNode", name="vraySettings") - - # Add namespace - cmds.namespace(set=":") - cmds.namespace(add=namespace) - cmds.namespace(set=namespace) + # Ensure V-Ray for Maya is loaded. + cmds.loadPlugin("vrayformaya", quiet=True) with lib.maintained_selection(): - nodes = self.create_vray_proxy(name) + cmds.namespace(addNamespace=namespace) + with namespaced(namespace, new=False): + nodes = self.create_vray_proxy(name, + filename=self.fname) self[:] = nodes + if not nodes: + return - # Make sure to restore the default namespace, or anything imported or - # refereced after this gets added to this namespace - cmds.namespace(set=":") + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) - return nodes + def update(self, container, representation): - def create_vray_proxy(self, name, attrs=None): + node = container['objectName'] + assert cmds.objExists(node), "Missing container" + + members = cmds.sets(node, query=True) or [] + vraymeshes = cmds.ls(members, type="VRayMesh") + assert vraymeshes, "Cannot find VRayMesh in container" + + filename = api.get_representation_path(representation) + + for vray_mesh in vraymeshes: + cmds.setAttr("{}.fileName".format(vray_mesh), + filename, + type="string") + + # Update metadata + cmds.setAttr("{}.representation".format(node), + str(representation["_id"]), + type="string") + + def remove(self, container): + + # Delete container and its contents + if cmds.objExists(container['objectName']): + members = cmds.sets(container['objectName'], query=True) or [] + cmds.delete([container['objectName']] + members) + + def switch(self, container, representation): + self.update(container, representation) + + def create_vray_proxy(self, name, filename): """Re-create the structure created by VRay to support vrmeshes Args: @@ -66,14 +96,13 @@ class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): vray_mat_sg = cmds.createNode("shadingEngine", name="{}_VRSG".format(name)) - cmds.setAttr("{}.fileName2".format(vray_mesh), - self.fname, + cmds.setAttr("{}.fileName".format(vray_mesh), + filename, type="string") - # Apply attributes from export - # cmds.setAttr("{}.animType".format(vray_mesh), 3) - # Create important connections + cmds.connectAttr("time1.outTime", + "{0}.currentFrame".format(vray_mesh)) cmds.connectAttr("{}.fileName2".format(vray_mesh), "{}.fileName".format(vray_mat)) cmds.connectAttr("{}.instancing".format(vray_mesh), @@ -96,7 +125,10 @@ class VRayProxyLoader(colorbleed.maya.plugin.ReferenceLoader): mesh_transform = cmds.listRelatives(mesh_shape, parent=True, fullPath=True) cmds.parent(mesh_transform, group_node) - nodes = [vray_mesh, mesh_shape, vray_mat, vray_mat_sg, group_node] + # Fix: Force refresh so the mesh shows correctly after creation + cmds.refresh() + cmds.setAttr("{}.geomType".format(vray_mesh), 2) + return nodes From 6b48df378dfffd7f7708918c018579932037f90c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Aug 2018 19:00:15 +0200 Subject: [PATCH 0926/1249] Remove namespace correctly if empty --- colorbleed/plugins/maya/load/load_vrayproxy.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py index ba3a1d3e45..025bc963f7 100644 --- a/colorbleed/plugins/maya/load/load_vrayproxy.py +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -75,6 +75,16 @@ class VRayProxyLoader(api.Loader): members = cmds.sets(container['objectName'], query=True) or [] cmds.delete([container['objectName']] + members) + # Remove the namespace, if empty + namespace = container['namespace'] + if cmds.namespace(exists=namespace): + members = cmds.namespaceInfo(namespace, listNamespace=True) + if not members: + cmds.namespace(removeNamespace=namespace) + else: + self.log.warning("Namespace not deleted because it " + "still has members: %s", namespace) + def switch(self, container, representation): self.update(container, representation) From b008f9a01c788a43b7758f4b597cbe835c167d40 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 22 Aug 2018 12:41:08 +0200 Subject: [PATCH 0927/1249] Add extra menu items to menu.json --- colorbleed/maya/menu.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 692b6c04a6..d394f5dba1 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -945,6 +945,18 @@ ], "title": "Fix RenderLayer Missing Referenced Nodes Overrides", "tooltip": "" + }, + { + "type": "action", + "title": "Image 2 Tiled EXR", + "command": "$COLORBLEED_SCRIPTS\\shading\\open_img2exr.py", + "sourcetype": "file", + "tooltip": "", + "tags": [ + "shading", + "vray", + "exr" + ] } ] }, @@ -1493,6 +1505,18 @@ "tooltip": "" }, {"type": "separator"}, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", + "sourcetype": "file", + "tags": [ + "particles", + "houdini", "houdiniTool", "houdiniEngine" + ], + "title": "Replicate Houdini Tool", + "tooltip": "" + }, + {"type": "separator"}, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\particles\\clearInitialState.py", From 1b0e26c5d9718c07e82ab6fc1dc6ab0eccea4d8c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 14:19:33 +0200 Subject: [PATCH 0928/1249] refactored code --- .../plugins/maya/load/load_yeti_cache.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 46e34743ce..7fdc738414 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -139,7 +139,7 @@ class YetiCacheLoader(api.Loader): if cb_id not in scene_lookup: new_nodes = self.create_nodes(namespace, [data]) - cmds.sets(*new_nodes, addElement=container_node) + cmds.sets(new_nodes, addElement=container_node) cmds.parent(new_nodes, container_root) else: @@ -155,8 +155,12 @@ class YetiCacheLoader(api.Loader): (scene_node, match_name)) # Get transform node, this makes renaming easier - transform_node = scene_node.rstrip("Shape") + transforms = cmds.listRelatives(scene_node, + parent=True, + fullPath=True) or [] + assert len(transforms) == 1, "This is a bug!" + transform_node = transforms[0] new_name = "{}:{}".format(namespace, match_name) cmds.rename(transform_node, new_name) @@ -260,14 +264,18 @@ class YetiCacheLoader(api.Loader): # Update attributes with requirements attributes.update({"viewportDensity": 0.1, "verbosity": 2, - "fileMode": 1, - "visibleInReflections": True, - "visibleInRefractions": True}) + "fileMode": 1}) # Apply attributes to pgYetiMaya node for attr, value in attributes.items(): lib.set_attribute(attr, value, yeti_node) + # Fix for : YETI-6 + # Fixes the render stats + # (see Perigrene's ../scripts/pgYetiNode.mel script) + cmds.setAttr("{}.visibleInReflections".format(yeti_node), True) + cmds.setAttr("{}.visibleInRefractions".format(yeti_node), True) + # Connect to the time node cmds.connectAttr("time1.outTime", "%s.currentTime" % yeti_node) From 905ab5692ecdfb6b3b136886160fbe937b079d14 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 14:21:11 +0200 Subject: [PATCH 0929/1249] separated fix for attributes with comment --- colorbleed/plugins/maya/load/load_yeti_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 7fdc738414..5578b4e5c3 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -271,8 +271,8 @@ class YetiCacheLoader(api.Loader): lib.set_attribute(attr, value, yeti_node) # Fix for : YETI-6 - # Fixes the render stats - # (see Perigrene's ../scripts/pgYetiNode.mel script) + # Fixes the render stats (this is literally taken from Perigrene's + # ../scripts/pgYetiNode.mel script) cmds.setAttr("{}.visibleInReflections".format(yeti_node), True) cmds.setAttr("{}.visibleInRefractions".format(yeti_node), True) From 762acc9f43ff98bf877c16223b2696d208433d37 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 16:42:54 +0200 Subject: [PATCH 0930/1249] fixed issue with renaming node if changed, minor cosmetics --- .../plugins/maya/load/load_yeti_cache.py | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index 5578b4e5c3..c3a3cdfefe 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -121,7 +121,8 @@ class YetiCacheLoader(api.Loader): # Get all related nodes shapes = scene_lookup[_id] # Get the parents of all shapes under the ID - transforms = cmds.listRelatives(shapes, parent=True, + transforms = cmds.listRelatives(shapes, + parent=True, fullPath=True) or [] to_remove.extend(shapes + transforms) @@ -138,6 +139,9 @@ class YetiCacheLoader(api.Loader): data["attrs"]["cacheFileName"] = os.path.join(path, cache_file_path) if cb_id not in scene_lookup: + + self.log.info("Creating new nodes ..") + new_nodes = self.create_nodes(namespace, [data]) cmds.sets(new_nodes, addElement=container_node) cmds.parent(new_nodes, container_root) @@ -145,27 +149,38 @@ class YetiCacheLoader(api.Loader): else: # Update the matching nodes scene_nodes = scene_lookup[cb_id] - match_name = meta_data_lookup[cb_id]["name"] + lookup_result = meta_data_lookup[cb_id]["name"] + + # Remove namespace if any (e.g.: "character_01_:head_YNShape") + node_name = lookup_result.rsplit(":", 1)[-1] for scene_node in scene_nodes: - # Remove the namespace root ":namespace:" - compare_name = scene_node.split(namespace)[-1][1:] - if compare_name != match_name: - self.log.info("Renaming %s to %s" % - (scene_node, match_name)) - # Get transform node, this makes renaming easier - transforms = cmds.listRelatives(scene_node, - parent=True, - fullPath=True) or [] - assert len(transforms) == 1, "This is a bug!" + # Get transform node, this makes renaming easier + transforms = cmds.listRelatives(scene_node, + parent=True, + fullPath=True) or [] + assert len(transforms) == 1, "This is a bug!" - transform_node = transforms[0] - new_name = "{}:{}".format(namespace, match_name) - cmds.rename(transform_node, new_name) + # Get scene node's namespace and rename the transform node + lead = scene_node.rsplit(":", 1)[0] + namespace = ":{}".format(lead.rsplit("|")[-1]) + + new_shape_name = "{}:{}".format(namespace, node_name) + new_trans_name = new_shape_name.rsplit("Shape", 1)[0] + + transform_node = transforms[0] + cmds.rename(transform_node, + new_trans_name, + ignoreShape=False) + + # Get the newly named shape node + yeti_nodes = cmds.listRelatives(new_trans_name, + children=True) + yeti_node = yeti_nodes[0] for attr, value in data["attrs"].items(): - lib.set_attribute(attr, value, scene_node) + lib.set_attribute(attr, value, yeti_node) cmds.setAttr("{}.representation".format(container_node), str(representation["_id"]), @@ -256,8 +271,9 @@ class YetiCacheLoader(api.Loader): # Check if cache file name is stored if "cacheFileName" not in attributes: file_name = original_node.replace(":", "_") - cache = os.path.join(self.fname, - "{}.%04d.fur".format(file_name)) + cache_name = "{}.%04d.fur".format(file_name) + cache = os.path.join(self.fname, cache_name) + self.validate_cache(cache) attributes["cacheFileName"] = cache From 2a1205ae9d6d80c362747a5f2e7fb7cf48b2cfca Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 17:29:50 +0200 Subject: [PATCH 0931/1249] removed redundant wrap --- colorbleed/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 8e5f25390f..e5d1aee374 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -24,9 +24,3 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - - -def register_loader_plugins(): - """Wrap to install Loader plugins for standalone""" - - install() From d4ae8b7c50a59e15ae25c33f6be959d3bf6ad155 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 17:30:17 +0200 Subject: [PATCH 0932/1249] fixed copy-paste functionality --- colorbleed/plugins/global/load/copy_file.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/global/load/copy_file.py b/colorbleed/plugins/global/load/copy_file.py index 738dc9f188..bbb8e1d6f7 100644 --- a/colorbleed/plugins/global/load/copy_file.py +++ b/colorbleed/plugins/global/load/copy_file.py @@ -24,12 +24,10 @@ class CopyFile(api.Loader): assert app, "Must have running QApplication instance" # Build mime data for clipboard - file_path = QtCore.QUrl.fromLocalFile(path) - byte_array = QtCore.QByteArray("copy\n").append(file_path) - - mime = QtCore.QMimeData() - mime.setData("text/uri-list", byte_array) + data = QtCore.QMimeData() + url = QtCore.QUrl.fromLocalFile(path) + data.setUrls([url]) # Set to Clipboard clipboard = app.clipboard() - clipboard.setMimeData(mime) + clipboard.setMimeData(data) From 311e57a6594e29f4f457960f68c41d680d033bdc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 22 Aug 2018 17:52:56 +0200 Subject: [PATCH 0933/1249] reverted changes --- colorbleed/__init__.py | 5 +++++ colorbleed/plugins/global/load/copy_file.py | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index e5d1aee374..9d9a050ac3 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -24,3 +24,8 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) + + +def register_loader_plugins(): + """Wrap to install Loader plugins for standalone""" + install() diff --git a/colorbleed/plugins/global/load/copy_file.py b/colorbleed/plugins/global/load/copy_file.py index bbb8e1d6f7..5bb2b11bb1 100644 --- a/colorbleed/plugins/global/load/copy_file.py +++ b/colorbleed/plugins/global/load/copy_file.py @@ -24,10 +24,12 @@ class CopyFile(api.Loader): assert app, "Must have running QApplication instance" # Build mime data for clipboard - data = QtCore.QMimeData() - url = QtCore.QUrl.fromLocalFile(path) - data.setUrls([url]) + file_path = QtCore.QUrl.fromLocalFile(path) + byte_array = QtCore.QByteArray("copy\n").append(file_path) + + mime = QtCore.QMimeData() + mime.setData("text/uri-list", byte_array) # Set to Clipboard clipboard = app.clipboard() - clipboard.setMimeData(data) + clipboard.setMimeData(mime) \ No newline at end of file From d2dc4332f346940f3acda91fc545a66791b026e7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 23 Aug 2018 09:51:50 +0200 Subject: [PATCH 0934/1249] refactored duplicate to copy file path --- ...copy_to_clipboard.py => copy_file_path.py} | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) rename colorbleed/plugins/global/load/{copy_to_clipboard.py => copy_file_path.py} (54%) diff --git a/colorbleed/plugins/global/load/copy_to_clipboard.py b/colorbleed/plugins/global/load/copy_file_path.py similarity index 54% rename from colorbleed/plugins/global/load/copy_to_clipboard.py rename to colorbleed/plugins/global/load/copy_file_path.py index 5f23981a34..cfda9dc271 100644 --- a/colorbleed/plugins/global/load/copy_to_clipboard.py +++ b/colorbleed/plugins/global/load/copy_file_path.py @@ -1,32 +1,29 @@ +import os + from avalon import api -class CopyToClipboardLoader(api.Loader): - """Copy published file to clipboard to allow to paste the content""" +class CopyFilePath(api.Loader): + """Copy published file path to clipboard""" representations = ["*"] families = ["*"] - label = "Copy file path to Clipboard" + label = "Copy File Path" order = 20 icon = "clipboard" color = "#999999" def load(self, context, name=None, namespace=None, data=None): self.log.info("Added file path to clipboard: {0}".format(self.fname)) - self.copy_file_to_clipboard(self.fname) + self.copy_path_to_clipboard(self.fname) @staticmethod - def copy_file_to_clipboard(path): + def copy_path_to_clipboard(path): from avalon.vendor.Qt import QtCore, QtWidgets app = QtWidgets.QApplication.instance() assert app, "Must have running QApplication instance" - # Build mime data for clipboard - mime = QtCore.QMimeData() - mime.setText(path) - mime.setUrls([QtCore.QUrl.fromLocalFile(path)]) - # Set to Clipboard clipboard = app.clipboard() - clipboard.setMimeData(mime) + clipboard.setText(os.path.normpath(path)) From 79e2045d1a12aa02f4dac5af1d0dc211e378ab78 Mon Sep 17 00:00:00 2001 From: Sven Neve Date: Thu, 23 Aug 2018 17:43:33 +0200 Subject: [PATCH 0935/1249] Removed a hardcoded string replace for linux render farm test --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 7a4f9c9d79..5e2c3334ff 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -222,7 +222,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): payload["JobInfo"].update({ "EnvironmentKeyValue%d" % index: "{key}={value}".format( key=key, - value=environment[key].replace("P:", "/mnt/P") + value=environment[key] ) for index, key in enumerate(environment) }) From 743a8a123b965e6c4bebac6b49ba1d52f874b006 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 12:28:05 +0200 Subject: [PATCH 0936/1249] added check renderlayers are connected to renderlayer manager --- ...ible.py => _validate_renderlayers_accessible.py} | 0 .../plugins/maya/publish/collect_renderlayers.py | 13 ++++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) rename colorbleed/plugins/maya/publish/{validate_renderlayers_accessible.py => _validate_renderlayers_accessible.py} (100%) diff --git a/colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py b/colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_renderlayers_accessible.py rename to colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 48d8f204a9..2b6d437e95 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -32,7 +32,12 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): context.data["startFrame"] = start_frame context.data["endFrame"] = end_frame - # Get render layers + # Get all linked renderlayers + rlm_attribute = "renderLayerManager.renderLayerId" + connected_layers = cmds.listConnections(rlm_attribute) or [] + valid_layers = set(connected_layers) + + # Get all renderlayers and check their state renderlayers = [i for i in cmds.ls(type="renderLayer") if cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] @@ -51,6 +56,12 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): renderlayers = sorted(renderlayers, key=sort_by_display_order) for layer in renderlayers: + + # Check if layer is in valid (linked) layers + if layer not in valid_layers: + self.log.warning("%s is invalid, skipping" % layer) + continue + if layer.endswith("defaultRenderLayer"): layername = "masterLayer" else: From c5f6302cc4af2208e31ab1e690a91664820364cc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 12:34:07 +0200 Subject: [PATCH 0937/1249] removed redundant plugin --- .../_validate_renderlayers_accessible.py | 76 ------------------- 1 file changed, 76 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py diff --git a/colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py b/colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py deleted file mode 100644 index ecf8488e3e..0000000000 --- a/colorbleed/plugins/maya/publish/_validate_renderlayers_accessible.py +++ /dev/null @@ -1,76 +0,0 @@ -from collections import defaultdict - -import pyblish.api - -from maya import cmds - - -class ValidateMayaRenderLayersAccessible(pyblish.api.ContextPlugin): - """Validate if the render is accessible to iterate through - - A bug in Maya can result in inaccessible render layers. - The render layer is not visible in the render layer manager or outliner - but can be found through code. The bug occurs when a referenced file is - imported. - - Sometimes the issue is an unresolved adjustment in render layers. - - """ - - order = pyblish.api.CollectorOrder - label = "Render Layer Accessible" - hosts = ["maya"] - - def process(self, context): - - # Get render globals node - try: - render_globals = cmds.ls("renderglobalsDefault")[0] - except IndexError: - self.log.error("Cannot collect renderlayers without " - "renderGlobals node") - return - - loaded, referenced = self.get_invalid() - if loaded or referenced: - message_a = ("Discovered inaccessible loaded renderlayers: %s" - % list(loaded)) if loaded else "" - - message_b = ("Discovered inaccessible referenced renderlayers: %s" - % list(referenced)) if referenced else "" - - raise RuntimeError("{}\n{}".format(message_a, message_b)) - - @classmethod - def get_invalid(cls): - - invalid = [] - invalid_referenced = [] - - lookup = defaultdict(list) - render_layers = cmds.ls(type="renderLayer") - - # Create look up - for layer in render_layers: - parts = layer.rsplit(":", 1) - lookup[parts[-1]].append(layer) - - for name, layers in lookup.items(): - cls.log.info("Checking if %i '%s' layers can be accessed" % - (len(layers), name)) - - for layer in layers: - try: - cmds.editRenderLayerGlobals(currentRenderLayer=layer) - except RuntimeError: - if cmds.referenceQuery(layer, isNodeReferenced=True): - cls.log.error("Referenced layer '%s' cannot " - "be accessed" % layer) - invalid_referenced.append(layer) - else: - cls.log.error("Non-referenced layer '%s' cannot " - "be accessed" % layer) - - invalid.append(layer) - - return invalid, invalid_referenced From 8e63de922bad1eff0decb65866d93e8a9a8a1356 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 14:42:29 +0200 Subject: [PATCH 0938/1249] fetch and display pools --- .../maya/create/colorbleed_renderglobals.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 0a1aa6ad1a..086aa16831 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,8 +1,10 @@ from collections import OrderedDict -import maya.cmds as cmds +from maya import cmds +from avalon.vendor import requests import avalon.maya +from avalon import api class CreateRenderGlobals(avalon.maya.Creator): @@ -17,6 +19,16 @@ class CreateRenderGlobals(avalon.maya.Creator): # We won't be publishing this one self.data["id"] = "avalon.renderglobals" + # get pools + AVALON_DEADLINE = api.Session["AVALON_DEADLINE"] + argument = "{}/api/pools?NamesOnly=true".format(AVALON_DEADLINE) + response = requests.get(argument) + if not response.ok: + self.log.warning("No pools retrieved") + pools = [] + else: + pools = response.json() + # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) @@ -27,13 +39,13 @@ class CreateRenderGlobals(avalon.maya.Creator): data["suspendPublishJob"] = False data["extendFrames"] = False data["overrideExistingFrame"] = True - data["includeDefaultRenderLayer"] = False data["useLegacyRenderLayers"] = True data["priority"] = 50 data["whitelist"] = False data["machineList"] = "" - data["pools"] = "" data["useMayaBatch"] = True + data["primaryPool"] = pools + data["secondaryPool"] = pools self.data = data self.options = {"useSelection": False} # Force no content From 4482d2b726ecf207e5f440dd72f93ee9bceba7ea Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 14:46:57 +0200 Subject: [PATCH 0939/1249] reset order and added comment --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 2b6d437e95..122a70973d 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -9,7 +9,7 @@ import colorbleed.maya.lib as lib class CollectMayaRenderlayers(pyblish.api.ContextPlugin): """Gather instances by active render layers""" - order = pyblish.api.CollectorOrder + 0.01 + order = pyblish.api.CollectorOrder hosts = ["maya"] label = "Render Layers" @@ -32,7 +32,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): context.data["startFrame"] = start_frame context.data["endFrame"] = end_frame - # Get all linked renderlayers + # Get all valid renderlayers + # This is how Maya populates the renderlayer display rlm_attribute = "renderLayerManager.renderLayerId" connected_layers = cmds.listConnections(rlm_attribute) or [] valid_layers = set(connected_layers) From 93eff49430a06b80f0f0d823e98bf69f879dd84c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 15:15:05 +0200 Subject: [PATCH 0940/1249] added empty entry to support no secondary pool --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 086aa16831..5cacd9e167 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -45,7 +45,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["machineList"] = "" data["useMayaBatch"] = True data["primaryPool"] = pools - data["secondaryPool"] = pools + data["secondaryPool"] = ["-"] + pools self.data = data self.options = {"useSelection": False} # Force no content From aad896f1f457ded905c32e135c746a36778d670d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 24 Aug 2018 17:06:13 +0200 Subject: [PATCH 0941/1249] Remove verbosity - since non-verbose output was way too confusing for artists as it didn't show the causing conflicts --- .../plugins/maya/publish/validate_transfers.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/colorbleed/plugins/maya/publish/validate_transfers.py index 41544b63cf..d1ea7d362e 100644 --- a/colorbleed/plugins/maya/publish/validate_transfers.py +++ b/colorbleed/plugins/maya/publish/validate_transfers.py @@ -22,8 +22,6 @@ class ValidateTransfers(pyblish.api.InstancePlugin): if not transfers: return - verbose = instance.data.get('verbose', False) - # Collect all destination with its sources collected = defaultdict(set) for source, destination in transfers: @@ -39,16 +37,9 @@ class ValidateTransfers(pyblish.api.InstancePlugin): if len(sources) > 1: invalid_destinations.append(destination) - if verbose: - self.log.error("Non-unique file transfer for resources: " - "{0} (sources: {1})".format(destination, - sources)) + self.log.error("Non-unique file transfer for resources: " + "{0} (sources: {1})".format(destination, + list(sources))) if invalid_destinations: - if not verbose: - # If not verbose then still log the resource destination as - # opposed to every individual file transfer - self.log.error("Non-unique file transfers to destinations: " - "%s" % "\n".join(invalid_destinations)) - raise RuntimeError("Invalid transfers in queue.") From 148441081b686cd49c76cef3394af4f4d6d19770 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 17:13:23 +0200 Subject: [PATCH 0942/1249] added discover_pools method with backwards compatibility --- .../maya/publish/collect_renderlayers.py | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index fff81cce2b..29c5156175 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -124,12 +124,10 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options["renderGlobals"]["Priority"] = attributes["priority"] # Check for specific pools - pool_str = attributes.get("pools", None) - if pool_str: - pool_a, pool_b = pool_str.split(";") - options["renderGlobals"].update({"Pool": pool_a}) - if pool_b: - options["renderGlobals"].update({"SecondaryPool": pool_b}) + pool_a, pool_b = self._discover_pools(attributes) + options["renderGlobals"].update({"Pool": pool_a}) + if pool_b: + options["renderGlobals"].update({"SecondaryPool": pool_b}) legacy = attributes["useLegacyRenderLayers"] options["renderGlobals"]["UseLegacyRenderLayers"] = legacy @@ -158,3 +156,26 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options["mayaRenderPlugin"] = maya_render_plugin return options + + def _discover_pools(self, attributes): + + pool_a = None + pool_b = None + + # Check for specific pools + if "primaryPool" in attributes: + pool_a = attributes["primaryPool"] + pool_b = attributes["secondaryPool"] + + else: + # Backwards compatibility + pool_str = attributes.get("pools", None) + if pool_str: + print("XXX", pool_str.split(";")) + pool_a, pool_b = pool_str.split(";") + + # Ensure empty entry token is caught + if pool_b == "-": + pool_b = None + + return pool_a, pool_b From fcdf1402ff90fd66ad735bf3c327fb87f61b4e43 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 17:14:37 +0200 Subject: [PATCH 0943/1249] refactored for PEP08 --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 29c5156175..cfd3a18632 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -152,7 +152,10 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): options["extendFrames"] = extend_frames options["overrideExistingFrame"] = override_frames - maya_render_plugin = "MayaBatch" if attributes.get("useMayaBatch", True) else "MayaCmd" + maya_render_plugin = "MayaBatch" + if not attributes.get("useMayaBatch", True): + maya_render_plugin = "MayaCmd" + options["mayaRenderPlugin"] = maya_render_plugin return options From 8664602faebc4e617733d7bea8c3cfc25635fec7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 24 Aug 2018 17:15:14 +0200 Subject: [PATCH 0944/1249] added primary and secondary pool to instance --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 5cacd9e167..83173a31d0 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -45,6 +45,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["machineList"] = "" data["useMayaBatch"] = True data["primaryPool"] = pools + # We add a string "-" to allow the user to not set any secondary pools data["secondaryPool"] = ["-"] + pools self.data = data From d00d4244177d296aefb3e92a26473dbaab01d7d3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 10:38:37 +0200 Subject: [PATCH 0945/1249] removed debug print --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index cfd3a18632..86b02fcd46 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -174,7 +174,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # Backwards compatibility pool_str = attributes.get("pools", None) if pool_str: - print("XXX", pool_str.split(";")) pool_a, pool_b = pool_str.split(";") # Ensure empty entry token is caught From 39feb19c83e13d4b883235871b1df1972833a832 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 10:39:20 +0200 Subject: [PATCH 0946/1249] removed remainder of other PR --- colorbleed/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/colorbleed/__init__.py b/colorbleed/__init__.py index 9d9a050ac3..e5d1aee374 100644 --- a/colorbleed/__init__.py +++ b/colorbleed/__init__.py @@ -24,8 +24,3 @@ def uninstall(): print("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - - -def register_loader_plugins(): - """Wrap to install Loader plugins for standalone""" - install() From bc2e998c557858595763ce0390aa27ea26fb92ad Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 10:39:43 +0200 Subject: [PATCH 0947/1249] new line at end of file --- colorbleed/plugins/global/load/copy_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/load/copy_file.py b/colorbleed/plugins/global/load/copy_file.py index 5bb2b11bb1..738dc9f188 100644 --- a/colorbleed/plugins/global/load/copy_file.py +++ b/colorbleed/plugins/global/load/copy_file.py @@ -32,4 +32,4 @@ class CopyFile(api.Loader): # Set to Clipboard clipboard = app.clipboard() - clipboard.setMimeData(mime) \ No newline at end of file + clipboard.setMimeData(mime) From 6d0a7d73444423fc9dd85d2928532eefc3ea1a6c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 12:57:17 +0200 Subject: [PATCH 0948/1249] removed redundant getAttr --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 86b02fcd46..427e93f5b4 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -37,13 +37,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] - # Include/exclude default render layer - default_layer = "{}.includeDefaultRenderLayer".format(render_globals) - use_defaultlayer = cmds.getAttr(default_layer) - if not use_defaultlayer: - renderlayers = [i for i in renderlayers if - not i.endswith("defaultRenderLayer")] - # Sort by displayOrder def sort_by_display_order(layer): return cmds.getAttr("%s.displayOrder" % layer) From 283018b34040b972551298f6007d2bd09f90f492 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:41:40 +0200 Subject: [PATCH 0949/1249] get camera from renderable cameras --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index e30f4ac3ce..7310eda302 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -22,7 +22,7 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) - data["camera"] = "persp" + data["camera"] = self._get_camera() data["suspendRenderJob"] = False data["suspendPublishJob"] = False data["includeDefaultRenderLayer"] = False @@ -32,3 +32,9 @@ class CreateVRayScene(avalon.maya.Creator): self.data = data self.options = {"useSelection": False} # Force no content + + def _get_camera(self): + from maya import cmds + + return [c for c in cmds.ls(type="camera") + if cmds.getAttr("%s.renderable" % i)] From 981f165b62ecedb121c9942e726b02f63ecfb048 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:42:10 +0200 Subject: [PATCH 0950/1249] allow user to control active state of instance --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 7310eda302..5689e0883b 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -18,7 +18,6 @@ class CreateVRayScene(avalon.maya.Creator): # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) - self.data.pop("active", None) data = OrderedDict(**self.data) From 09c6a9430a21c2ab88eeb73635c1a1c5f6945851 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 14:45:07 +0200 Subject: [PATCH 0951/1249] fix bug --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 5689e0883b..965c7b37f0 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -36,4 +36,4 @@ class CreateVRayScene(avalon.maya.Creator): from maya import cmds return [c for c in cmds.ls(type="camera") - if cmds.getAttr("%s.renderable" % i)] + if cmds.getAttr("%s.renderable" % c)] From 383a5447734f189ac5a03d9497f0f86b076ad0f8 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:12:13 +0200 Subject: [PATCH 0952/1249] renamed method, changed icon to cubes --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 965c7b37f0..632e3d54a7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -7,7 +7,7 @@ class CreateVRayScene(avalon.maya.Creator): label = "VRay Scene" family = "colorbleed.vrayscene" - # icon = "blocks" + icon = "cubes" def __init__(self, *args, **kwargs): super(CreateVRayScene, self).__init__(*args, **kwargs) @@ -21,10 +21,9 @@ class CreateVRayScene(avalon.maya.Creator): data = OrderedDict(**self.data) - data["camera"] = self._get_camera() + data["camera"] = self._get_cameras() data["suspendRenderJob"] = False data["suspendPublishJob"] = False - data["includeDefaultRenderLayer"] = False data["extendFrames"] = False data["pools"] = "" @@ -32,7 +31,7 @@ class CreateVRayScene(avalon.maya.Creator): self.options = {"useSelection": False} # Force no content - def _get_camera(self): + def _get_cameras(self): from maya import cmds return [c for c in cmds.ls(type="camera") From 3d2537ee94bab5d344a97d9aea0b4e08011eb1d3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:13:37 +0200 Subject: [PATCH 0953/1249] use registerd_host to get host functions, log settings in debug --- .../plugins/maya/publish/collect_vray_scene.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index cedd527106..8cfa5f2dcc 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -2,10 +2,10 @@ import os import pyblish.api -from avalon import api, maya - from maya import cmds +from avalon import api + class CollectVRayScene(pyblish.api.ContextPlugin): """Collect all information prior for exporting vrscenes @@ -21,11 +21,13 @@ class CollectVRayScene(pyblish.api.ContextPlugin): def sort_by_display_order(layer): return cmds.getAttr("%s.displayOrder" % layer) + host = api.registered_host() + asset = api.Session["AVALON_ASSET"] work_dir = context.data["workspaceDir"] # Get VRay Scene instance - vray_scenes = maya.lsattr("family", "colorbleed.vrayscene") + vray_scenes = host.lsattr("family", "colorbleed.vrayscene") if not vray_scenes: self.log.info("No instance found of family: `colorbleed.vrayscene`") return @@ -33,8 +35,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" vray_scene = vray_scenes[0] - vrscene_data = {k: cmds.getAttr("%s.%s" % (vray_scene, k)) for - k in cmds.listAttr(vray_scene, userDefined=True)} + vrscene_data = host.read(vray_scene) # Output data start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) @@ -71,11 +72,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): cmds.getAttr("{}.renderable".format(i)) and not cmds.referenceQuery(i, isNodeReferenced=True)] - # Check if we need to filter out the default render layer - if vrscene_data.get("includeDefaultRenderLayer", True): - render_layers = [r for r in render_layers - if r != "defaultRenderLayer"] - render_layers = sorted(render_layers, key=sort_by_display_order) for layer in render_layers: @@ -108,4 +104,5 @@ class CollectVRayScene(pyblish.api.ContextPlugin): instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) + self.log.debug("VRay Data: %s" % vrscene_data) instance.data.update(data) From c31cbefa26e7d24494483d62bceb93ebf8f93770 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 27 Aug 2018 15:15:06 +0200 Subject: [PATCH 0954/1249] updated name of key in data --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 97c26497f8..6e161d8a4e 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -192,7 +192,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): '-e {endFrame} -rl {layer} -exportFramesSeparate') return cmd.format(project=instance.context.data["workspaceDir"], - cam=instance.data.get("cam", "persp"), + cam=instance.data.get("camera", "persp"), startFrame=instance.data["startFrame"], endFrame=instance.data["endFrame"], layer=instance.name) From 10d7bab7d7ef0f7974087b0c481fb1ecfc859669 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 30 Aug 2018 09:34:02 +0200 Subject: [PATCH 0955/1249] added framesPerTask option to instance --- colorbleed/plugins/maya/create/colorbleed_renderglobals.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 83173a31d0..1d12d9fe9d 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -41,6 +41,7 @@ class CreateRenderGlobals(avalon.maya.Creator): data["overrideExistingFrame"] = True data["useLegacyRenderLayers"] = True data["priority"] = 50 + data["framesPerTask"] = 1 data["whitelist"] = False data["machineList"] = "" data["useMayaBatch"] = True From 4ba7fec77e3278378299f445ac0cca7c48deb363 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 30 Aug 2018 10:17:24 +0200 Subject: [PATCH 0956/1249] Added chunksize support with backwards compatibility --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 917f58fae2..907b9367f5 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -147,6 +147,9 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): state = "Suspended" if attributes["suspendPublishJob"] else "Active" options["publishJobState"] = state + chunksize = attributes.get("framesPerTask", 1) + options["renderGlobals"]["ChunkSize"] = chunksize + # Override frames should be False if extendFrames is False. This is # to ensure it doesn't go off doing crazy unpredictable things override_frames = False From d1ecf25388f6ca1911673a24bc7080eeae26e769 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 31 Aug 2018 15:24:58 +0200 Subject: [PATCH 0957/1249] reversed change, throwing runtimer error --- colorbleed/plugins/global/publish/submit_publish_job.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 184ed0b43d..b70382dd54 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -136,10 +136,8 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Get a submission job job = instance.data.get("deadlineSubmissionJob") if not job: - self.log.warning("Can't continue without valid deadline " - "submission prior to this plug-in.") - self.log.info("Skipping Publish Job") - return + raise RuntimeError("Can't continue without valid deadline " + "submission prior to this plug-in.") data = instance.data.copy() subset = data["subset"] From 4108c680aa20ae3949446d14041bf45a045062f5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Sep 2018 09:52:56 +0200 Subject: [PATCH 0958/1249] Fix `extract_alembic` not supporting writeUVSets --- colorbleed/maya/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index bb2690e3f8..5b247d88a0 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -76,6 +76,7 @@ _alembic_options = { "writeColorSets": bool, "writeFaceSets": bool, "writeCreases": bool, # Maya 2015 Ext1+ + "writeUVSets": bool, # Maya 2017+ "dataFormat": str, "root": (list, tuple), "attr": (list, tuple), From 8b02cce5b1ff2e9a655fdb40d77c56bcf02909a6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Sep 2018 10:36:33 +0200 Subject: [PATCH 0959/1249] Correct message, and remove verbose nodes logging --- colorbleed/plugins/maya/publish/extract_pointcache.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 405d1e6549..4cfb8c9c25 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -35,11 +35,9 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): # Get extra export arguments writeColorSets = instance.data.get("writeColorSets", False) - self.log.info("Extracting animation..") + self.log.info("Extracting pointcache..") dirname = self.staging_dir(instance) - self.log.info("nodes: %s" % str(nodes)) - parent_dir = self.staging_dir(instance) filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) From 4d224517e4c7d4cc2ad781d864146d5e4213b5a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Sep 2018 10:57:11 +0200 Subject: [PATCH 0960/1249] Log a warning when option key is being ignored --- colorbleed/maya/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5b247d88a0..bb41b4a738 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -608,6 +608,8 @@ def extract_alembic(file, # Discard unknown options if key not in _alembic_options: + log.warning("extract_alembic() does not support option '%s'. " + "Flag will be ignored..", key) options.pop(key) continue From 0e3034a18a8860194f7f9a9700bf37d987839510 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 6 Sep 2018 15:09:31 +0200 Subject: [PATCH 0961/1249] added steps attribute --- colorbleed/plugins/maya/create/colorbleed_yeti_cache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 1c390db4ba..59d37429bd 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -15,11 +15,12 @@ class CreateYetiCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiCache, self).__init__(*args, **kwargs) - data = OrderedDict(self.data) + data = OrderedDict(**self.data) data["peroll"] = 0 anim_data = lib.collect_animation_data() data.update({"startFrame": anim_data["startFrame"], - "endFrame": anim_data["endFrame"]}) + "endFrame": anim_data["endFrame"], + "samples": 3}) self.data = data From 39ee722b6c3ff26f7f60c7ab025001bf8adff24c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 6 Sep 2018 15:10:02 +0200 Subject: [PATCH 0962/1249] added support for samples --- colorbleed/plugins/maya/publish/extract_yeti_cache.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_cache.py b/colorbleed/plugins/maya/publish/extract_yeti_cache.py index 479bf52813..481586374a 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_cache.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_cache.py @@ -37,6 +37,13 @@ class ExtractYetiCache(colorbleed.api.Extractor): if preroll > 0: start_frame -= preroll + kwargs = {} + samples = instance.data.get("samples", 0) + if samples == 0: + kwargs.update({"sampleTimes": "0.0 1.0"}) + else: + kwargs.update({"samples": samples}) + self.log.info("Writing out cache") # Start writing the files for snap shot # will be replace by the Yeti node name @@ -44,9 +51,9 @@ class ExtractYetiCache(colorbleed.api.Extractor): cmds.pgYetiCommand(yeti_nodes, writeCache=path, range=(start_frame, end_frame), - sampleTimes="0.0 1.0", updateViewport=False, - generatePreview=False) + generatePreview=False, + **kwargs) cache_files = [x for x in os.listdir(dirname) if x.endswith(".fur")] From fe924c6b297db605d1e842cc9560703752f645cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 6 Sep 2018 16:41:55 +0200 Subject: [PATCH 0963/1249] Add draft for labeling asset and frame range in Pyblish instance name --- .../plugins/maya/publish/collect_instances.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index e40893c71c..f23f0aa396 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -102,9 +102,18 @@ class CollectInstances(pyblish.api.ContextPlugin): parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) - # Create the instance + # Define nice label name = cmds.ls(objset, long=False)[0] # use short name - instance = context.create_instance(data.get("name", name)) + label = "{0} ({1})".format(name, + data["asset"]) + + # Append start frame and end frame to label if present + if "startFrame" and "endFrame" in data: + label += " [{0}-{1}]".format(int(data["startFrame"]), + int(data["endFrame"])) + + # Create the instance + instance = context.create_instance(label) instance[:] = members_hierarchy instance.data["setMembers"] = members instance.data.update(data) From b5a09cb4d02dc61f58f4bc1a74487f8700258622 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 10 Sep 2018 10:39:16 +0200 Subject: [PATCH 0964/1249] Ensure explicit export settings for readability --- colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py index faa99035ae..bcbd39fc3d 100644 --- a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py +++ b/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py @@ -44,7 +44,10 @@ class ExtractMayaAsciiRaw(colorbleed.api.Extractor): typ="mayaAscii", exportSelected=True, preserveReferences=True, - constructionHistory=True) + constructionHistory=True, + shader=True, + constraints=True, + expressions=True) if "files" not in instance.data: instance.data["files"] = list() From 87e3c101cac904350889bca21c7283a612cac092 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 10 Sep 2018 10:42:29 +0200 Subject: [PATCH 0965/1249] Fix shader creation for V-Ray proxies so shaders work as expected on export selection, etc. --- colorbleed/plugins/maya/load/load_vrayproxy.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/colorbleed/plugins/maya/load/load_vrayproxy.py index 025bc963f7..6bf9bdb484 100644 --- a/colorbleed/plugins/maya/load/load_vrayproxy.py +++ b/colorbleed/plugins/maya/load/load_vrayproxy.py @@ -101,10 +101,12 @@ class VRayProxyLoader(api.Loader): # Create nodes vray_mesh = cmds.createNode('VRayMesh', name="{}_VRMS".format(name)) mesh_shape = cmds.createNode("mesh", name="{}_GEOShape".format(name)) - vray_mat = cmds.createNode("VRayMeshMaterial", + vray_mat = cmds.shadingNode("VRayMeshMaterial", asShader=True, name="{}_VRMM".format(name)) - vray_mat_sg = cmds.createNode("shadingEngine", - name="{}_VRSG".format(name)) + vray_mat_sg = cmds.sets(name="{}_VRSG".format(name), + empty=True, + renderable=True, + noSurfaceShader=True) cmds.setAttr("{}.fileName".format(vray_mesh), filename, From 6f220c80aac0367a0e2059c98c83c155a55f6156 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 10 Sep 2018 10:55:53 +0200 Subject: [PATCH 0966/1249] Add V-Ray Proxy vertex colors support --- colorbleed/plugins/maya/create/colorbleed_vrayproxy.py | 3 +++ colorbleed/plugins/maya/publish/extract_vrayproxy.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py index e100c31bd0..85e9e71b6d 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py @@ -20,4 +20,7 @@ class CreateVrayProxy(avalon.maya.Creator): data["startFrame"] = 1 data["endFrame"] = 1 + # Write vertex colors + data["vertexColors"] = False + self.data.update(data) diff --git a/colorbleed/plugins/maya/publish/extract_vrayproxy.py b/colorbleed/plugins/maya/publish/extract_vrayproxy.py index f1190d52ab..265828b20d 100644 --- a/colorbleed/plugins/maya/publish/extract_vrayproxy.py +++ b/colorbleed/plugins/maya/publish/extract_vrayproxy.py @@ -37,6 +37,8 @@ class ExtractVRayProxy(colorbleed.api.Extractor): start_frame = instance.data["startFrame"] end_frame = instance.data["endFrame"] + vertex_colors = instance.data.get("vertexColors", False) + # Write out vrmesh file self.log.info("Writing: '%s'" % file_path) with avalon.maya.maintained_selection(): @@ -48,6 +50,7 @@ class ExtractVRayProxy(colorbleed.api.Extractor): animType=3, startFrame=start_frame, endFrame=end_frame, + vertexColorsOn=vertex_colors, ignoreHiddenObjects=True, createProxyNode=False) From 4d378dcf56348e846f44546473b24d70dc3d79a8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 10 Sep 2018 14:28:11 +0200 Subject: [PATCH 0967/1249] Force forward slashes for resource destination values (Fix PLN-151 + PLN-89) --- .../plugins/global/publish/collect_assumed_destination.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/global/publish/collect_assumed_destination.py b/colorbleed/plugins/global/publish/collect_assumed_destination.py index 00e56cd2bf..2021a17dff 100644 --- a/colorbleed/plugins/global/publish/collect_assumed_destination.py +++ b/colorbleed/plugins/global/publish/collect_assumed_destination.py @@ -35,6 +35,12 @@ class CollectAssumedDestination(pyblish.api.InstancePlugin): # Add destination to the resource source_filename = os.path.basename(resource["source"]) destination = os.path.join(mock_destination, source_filename) + + # Force forward slashes to fix issue with software unable + # to work correctly with backslashes in specific scenarios + # (e.g. escape characters in PLN-151 V-Ray UDIM) + destination = destination.replace("\\", "/") + resource['destination'] = destination # Collect transfers for the individual files of the resource From f658e3faac0f650f198c2a3dfef17fbd58f85e0c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 16:19:29 +0200 Subject: [PATCH 0968/1249] Added get_project_data, update get_project_fps function --- colorbleed/lib.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 1297aba606..45bb569af4 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -257,16 +257,33 @@ def get_project_fps(): Returns: int, float + + """ + + data = get_project_data() + fps = data.get("fps", 25.0) + + return fps + + +def get_project_data(): + """Get the data of the current project + + The data of the project can contain things like: + resolution + fps + renderer + + Returns: + dict: + """ project_name = io.active_project() project = io.find_one({"name": project_name, "type": "project"}, - projection={"config": True}) + projection={"data": True}) - config = project.get("config", None) - assert config, "This is a bug" + data = project.get("data", {}) - fps = config.get("fps", 25.0) - - return fps + return data From 9258d860dac295ed3bb0cd63bede7862925a1b62 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 16:24:04 +0200 Subject: [PATCH 0969/1249] Added set_project_settings and set_scene_resolution functions --- colorbleed/maya/lib.py | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index bb41b4a738..5371f5abc6 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1371,6 +1371,7 @@ def get_id_from_history(node): return _id +# Project settings def set_scene_fps(fps, update=True): """Set FPS from project configuration @@ -1399,6 +1400,58 @@ def set_scene_fps(fps, update=True): cmds.file(modified=True) +def set_scene_resolution(resolution, renderer): + """Set the render resolution + + Args: + resolution(str): x, eg: '1920x1080' + renderer(str): name of the curren renderer; vray / redshift / arnold + + Returns: + bool: True if successful + + """ + + control_node = "defaultResolution" + cmds.setAttr("defaultRenderGlobals.currentRenderer", + renderer, + type="string") + + width, height = resolution.split("x") + + # Give VRay a helping hand as it is slightly different from the rest + if renderer == "vray": + control_node = "vraySettings" + if not cmds.objExists(type=control_node): + cmds.createNode("VRaySettingsNode", name=control_node) + + cmds.setAttr("%s.width" % control_node, int(width)) + cmds.setAttr("%s.height" % control_node, int(height)) + + log.info("Set project resolution to: %s" % resolution) + + return True + + +def set_project_settings(): + """Apply the project settings from the project definition + + Returns: + None + """ + + # Todo (Wijnand): apply renderer and resolution of project + + # Get project settings + data = lib.get_project_data() + fps = data.get("fps", None) + + if fps is None: + return + + set_scene_fps(fps) + + # Valid FPS def validate_fps(): """Validate current scene FPS and show pop-up when it is incorrect From 913f53116966b378297ef661a63c12d7d2aa40ca Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 16:44:04 +0200 Subject: [PATCH 0970/1249] added on_new callback --- colorbleed/maya/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 74ecebba1f..1ce509f170 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -39,6 +39,8 @@ def install(): avalon.before("save", on_before_save) + avalon.on("new", on_new) + log.info("Overriding existing event 'taskChanged'") override_event("taskChanged", on_task_changed) @@ -158,6 +160,13 @@ def on_open(_): dialog.show() +def on_new(_): + """Set project resolution and fps when create a new file""" + + with maya.suspended_refresh(): + lib.set_project_settings() + + def on_task_changed(*args): """Wrapped function of app initialize and maya's on task changed""" From e51bc87ae041759462adfcb09dcb114b30713d72 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 10 Sep 2018 17:15:35 +0200 Subject: [PATCH 0971/1249] Ignore _UNKNOWN_REF_NODE_ --- colorbleed/maya/plugin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 21a074e874..5a06c5d9f2 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -95,6 +95,10 @@ class ReferenceLoader(api.Loader): if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"): continue + # Ignore _UNKNOWN_REF_NODE_ (PLN-160) + if ref.endswith("_UNKNOWN_REF_NODE_"): + continue + references.add(ref) assert references, "No reference node found in container" From 2637a3ea1d938d5d6d9727195582b1516c1c0acc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 17:45:26 +0200 Subject: [PATCH 0972/1249] Refactored function, added asset override and fallback --- colorbleed/maya/lib.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5371f5abc6..d1649fa977 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1433,9 +1433,17 @@ def set_scene_resolution(resolution, renderer): return True -def set_project_settings(): +def set_context_settings(): """Apply the project settings from the project definition + Settings can be overwritten by an asset if the asset.data contains + any information regarding those settings. + + Examples of settings: + fps + resolution + renderer + Returns: None """ @@ -1444,8 +1452,7 @@ def set_project_settings(): # Get project settings data = lib.get_project_data() - fps = data.get("fps", None) - + fps = lib.get_asset_fps() or data.get("fps", None) if fps is None: return From 3c2e536f22436d3f2c914684c86f939cb6a0f4c9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 17:45:50 +0200 Subject: [PATCH 0973/1249] Added get_asset_data and fps --- colorbleed/lib.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 45bb569af4..ccc2369a86 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -287,3 +287,22 @@ def get_project_data(): data = project.get("data", {}) return data + + +def get_asset_data(): + + asset_name = avalon.api.Session["AVALON_ASSET"] + document = io.find_one({"name": asset_name, + "type": "asset"}) + + data = document.get("data", {}) + + return data + + +def get_asset_fps(): + + data = get_asset_data() + fps = data.get("fps", None) + + return fps From a70e3012b188afb0df6c0df65412dbb4ac82d34e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 10 Sep 2018 17:46:23 +0200 Subject: [PATCH 0974/1249] Refactored function name --- colorbleed/maya/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 1ce509f170..5463cb0097 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -162,9 +162,9 @@ def on_open(_): def on_new(_): """Set project resolution and fps when create a new file""" - + avalon.logger.info("Running callback on new..") with maya.suspended_refresh(): - lib.set_project_settings() + lib.set_context_settings() def on_task_changed(*args): From 5ede6c44db88009720ada4f19460c3a9b0ea8889 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 11 Sep 2018 10:46:00 +0200 Subject: [PATCH 0975/1249] Removed create node for performance, cosmetics for readability --- colorbleed/lib.py | 18 ++++++++++++++++-- colorbleed/maya/lib.py | 9 ++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index ccc2369a86..e37a41eec3 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -289,9 +289,17 @@ def get_project_data(): return data -def get_asset_data(): +def get_asset_data(asset=None): + """Get the data from the current asset + + Args: + asset(str, Optional): name of the asset, eg: + + Returns: + dict + """ - asset_name = avalon.api.Session["AVALON_ASSET"] + asset_name = asset or avalon.api.Session["AVALON_ASSET"] document = io.find_one({"name": asset_name, "type": "asset"}) @@ -301,6 +309,12 @@ def get_asset_data(): def get_asset_fps(): + """Return the FPS from the asset data if found else None + + Returns: + int, float, None + + """ data = get_asset_data() fps = data.get("fps", None) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index d1649fa977..1acdf4092c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1423,7 +1423,9 @@ def set_scene_resolution(resolution, renderer): if renderer == "vray": control_node = "vraySettings" if not cmds.objExists(type=control_node): - cmds.createNode("VRaySettingsNode", name=control_node) + log.error("Cannot set resolution because there is no node named:" + "`%s`" % control_node) + return cmds.setAttr("%s.width" % control_node, int(width)) cmds.setAttr("%s.height" % control_node, int(height)) @@ -1451,8 +1453,9 @@ def set_context_settings(): # Todo (Wijnand): apply renderer and resolution of project # Get project settings - data = lib.get_project_data() - fps = lib.get_asset_fps() or data.get("fps", None) + project_data = lib.get_project_data() + asset_data = lib.get_asset_data() + fps = asset_data.get("fps", project_data.get("fps", None)) if fps is None: return From 1c0ce2043363ddc47226c8e988efcaca42ac7165 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 11 Sep 2018 12:07:45 +0200 Subject: [PATCH 0976/1249] Removed double value, added rounding float, updated set_scene_resolution --- colorbleed/maya/lib.py | 54 +++++++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1acdf4092c..b641ac8019 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -90,7 +90,7 @@ _alembic_options = { } INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} -FLOAT_FPS = {23.976, 29.97, 29.97, 47.952, 59.94} +FLOAT_FPS = {23.976, 29.97, 47.952, 59.94} def matrix_equals(a, b, tolerance=1e-10): @@ -1385,7 +1385,8 @@ def set_scene_fps(fps, update=True): """ if fps in FLOAT_FPS: - unit = "{:f}fps".format(fps) + # :.2f means . + unit = "{:.2f}fps".format(fps) elif fps in INT_FPS: unit = "{:d}fps".format(int(fps)) @@ -1400,39 +1401,33 @@ def set_scene_fps(fps, update=True): cmds.file(modified=True) -def set_scene_resolution(resolution, renderer): +def set_scene_resolution(width, height): """Set the render resolution Args: - resolution(str): x, eg: '1920x1080' - renderer(str): name of the curren renderer; vray / redshift / arnold + width(int): value of the width + height(int): value of the height Returns: - bool: True if successful + None """ control_node = "defaultResolution" - cmds.setAttr("defaultRenderGlobals.currentRenderer", - renderer, - type="string") - - width, height = resolution.split("x") + current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") # Give VRay a helping hand as it is slightly different from the rest - if renderer == "vray": - control_node = "vraySettings" - if not cmds.objExists(type=control_node): - log.error("Cannot set resolution because there is no node named:" - "`%s`" % control_node) - return + if current_renderer == "vray": + vray_node = "vraySettings" + if cmds.objExists(vray_node): + control_node = vray_node + else: + log.error("Can't set VRay resolution because there is no node " + "named: `%s`" % vray_node) - cmds.setAttr("%s.width" % control_node, int(width)) - cmds.setAttr("%s.height" % control_node, int(height)) - - log.info("Set project resolution to: %s" % resolution) - - return True + log.info("Setting project resolution to: %s x %s" % (width, height)) + cmds.setAttr("%s.width" % control_node, width) + cmds.setAttr("%s.height" % control_node, height) def set_context_settings(): @@ -1452,15 +1447,24 @@ def set_context_settings(): # Todo (Wijnand): apply renderer and resolution of project - # Get project settings project_data = lib.get_project_data() asset_data = lib.get_asset_data() + + # Set project fps fps = asset_data.get("fps", project_data.get("fps", None)) if fps is None: return - set_scene_fps(fps) + # Set project resolution + width_key = "resolution_width" + height_key = "resolution_height" + + width = asset_data.get(width_key, project_data.get(width_key, 1920)) + height = asset_data.get(height_key, project_data.get(height_key, 1080)) + + set_scene_resolution(width, height) + # Valid FPS def validate_fps(): From 1779fccafcbef32e06962d31e96d8279af8643e2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 11 Sep 2018 13:07:50 +0200 Subject: [PATCH 0977/1249] resolved feedback: simplified format --- colorbleed/maya/lib.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b641ac8019..66f06172dc 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1385,11 +1385,10 @@ def set_scene_fps(fps, update=True): """ if fps in FLOAT_FPS: - # :.2f means . - unit = "{:.2f}fps".format(fps) + unit = "{}fps".format(fps) elif fps in INT_FPS: - unit = "{:d}fps".format(int(fps)) + unit = "{}fps".format(int(fps)) else: raise ValueError("Unsupported FPS value: `%s`" % fps) From a99873d6d37c3d5cae09ed0c281f44a703b34385 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 11 Sep 2018 13:58:13 +0200 Subject: [PATCH 0978/1249] Updated validate function to check asset fps too --- colorbleed/maya/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 66f06172dc..59ea54c20d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1450,9 +1450,7 @@ def set_context_settings(): asset_data = lib.get_asset_data() # Set project fps - fps = asset_data.get("fps", project_data.get("fps", None)) - if fps is None: - return + fps = asset_data.get("fps", project_data.get("fps", 25)) set_scene_fps(fps) # Set project resolution @@ -1474,7 +1472,8 @@ def validate_fps(): """ - fps = lib.get_project_fps() # can be int or float + asset_data = lib.get_asset_data() + fps = asset_data.get("fps", lib.get_project_fps()) # can be int or float current_fps = mel.eval('currentTimeUnitToFPS()') # returns float if current_fps != fps: From 389a9d9ab86cfd7bb673480c1e36e8aecbad504a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 11 Sep 2018 13:58:28 +0200 Subject: [PATCH 0979/1249] Removed redundant function --- colorbleed/lib.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index e37a41eec3..119dcd4c25 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -306,17 +306,3 @@ def get_asset_data(asset=None): data = document.get("data", {}) return data - - -def get_asset_fps(): - """Return the FPS from the asset data if found else None - - Returns: - int, float, None - - """ - - data = get_asset_data() - fps = data.get("fps", None) - - return fps From b2f83d7c192725c86bbe5691898b12801c0d27cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 11 Sep 2018 14:05:12 +0200 Subject: [PATCH 0980/1249] Also capture _UNKNOWN_REF_NODE_1, or other variations (noticed that being present in one scene) --- colorbleed/maya/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 5a06c5d9f2..327cf47cbd 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -96,7 +96,7 @@ class ReferenceLoader(api.Loader): continue # Ignore _UNKNOWN_REF_NODE_ (PLN-160) - if ref.endswith("_UNKNOWN_REF_NODE_"): + if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"): continue references.add(ref) From 2234fcabd31b779b8e5651ad3edccf2279fdad9a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 11:56:21 +0200 Subject: [PATCH 0981/1249] Added update and remove function --- .../plugins/houdini/load/load_alembic.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index cbf76ace07..d17ad692df 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -1,11 +1,11 @@ -import avalon.api +from avalon import api from avalon.houdini import pipeline, lib reload(pipeline) reload(lib) -class AbcLoader(avalon.api.Loader): +class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", "colorbleed.pointcache"] @@ -43,8 +43,7 @@ class AbcLoader(avalon.api.Loader): # Remove the file node, it only loads static meshes node_path = "/obj/{}/file1".format(node_name) - file_node = hou.node(node_path) - file_node.destroy() + hou.node(node_path) # Create an alembic node (supports animation) alembic = container.createNode("alembic", node_name=node_name) @@ -77,7 +76,25 @@ class AbcLoader(avalon.api.Loader): self.__class__.__name__) def update(self, container, representation): - pass + + node = container["node"] + try: + alembic_node = next(n for n in node.children() if + n.type().name() == "alembic") + except StopIteration: + self.log.error("Could not find node of type `alembic`") + return + + # Update the file path + file_path = api.get_representation_path(representation) + file_path = file_path.replace("\\", "/") + + alembic_node.setParms({"fileName": file_path}) + + # Update attribute + node.setParms({"representation": str(representation["_id"])}) def remove(self, container): - print(">>>", container["objectName"]) + + node = container["node"] + node.destroy() From 5aa46689af6895e061253137229158e12e78c917 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 11:57:25 +0200 Subject: [PATCH 0982/1249] removed redundant reloads --- colorbleed/plugins/houdini/load/load_alembic.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index d17ad692df..f3fcc3ad37 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -1,8 +1,6 @@ from avalon import api from avalon.houdini import pipeline, lib -reload(pipeline) -reload(lib) class AbcLoader(api.Loader): From f7d8642718b75d5e866a3cf4c873ecb99157d135 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 11:58:25 +0200 Subject: [PATCH 0983/1249] removed unused variable --- colorbleed/plugins/houdini/publish/collect_instances.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 2a9edb5667..5f77b9d805 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -31,7 +31,6 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): instances = [] - keys = ["active", "id", "family", "asset", "subset"] nodes = hou.node("/out").children() for node in nodes: From 5da32ff95f67ea2b7f02e71b10c57d8e8e7a00f0 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:25:39 +0200 Subject: [PATCH 0984/1249] Added validator to check for create intermediate dir toggle --- .../publish/validate_mkpaths_toggled.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py new file mode 100644 index 0000000000..8dcc6e0509 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -0,0 +1,38 @@ +import pyblish.api +import colorbleed.api + + +class ValidatIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): + """Validate if node attribute Create intermediate Directories is turned on + + Rules: + * The node must have Create intermediate Directories turned on to + ensure the output file will be created + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.pointcache'] + hosts = ['houdini'] + label = 'Create Intermediate Directories Checked' + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Found ROP nodes with Create Intermediate " + "Directories turned off") + + @classmethod + def get_invalid(cls, instance): + + result = [] + + for node in instance[:]: + if node.parm("mkpath").eval() != 1: + cls.log.error("Invalid settings found on `%s`" % node.path()) + result.append(node.path()) + + return result + + From 28067766d5f15f2c131aea124267bb0563ce5f32 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:26:14 +0200 Subject: [PATCH 0985/1249] Updated extract plugin --- .../plugins/houdini/publish/extract_alembic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index d427a747fb..098020b905 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -1,7 +1,5 @@ import os -import hou - import pyblish.api import colorbleed.api from colorbleed.houdini import lib @@ -21,15 +19,17 @@ class ExtractAlembic(colorbleed.api.Extractor): file_name = "{}.abc".format(instance.data["subset"]) tmp_filepath = os.path.join(staging_dir, file_name) + start_frame = float(instance.data["startFrame"]) + end_frame = float(instance.data["endFrame"]) + ropnode = instance[0] - attributes = {"trange": 1, - "f1": instance.data["startFrame"], - "f2": instance.data["endFrame"]} + attributes = {"filename": tmp_filepath, + "trange": 2} with lib.attribute_values(ropnode, attributes): - ropnode.execute() + ropnode.render(frame_range=(start_frame, end_frame, 1)) if "files" not in instance.data: instance.data["files"] = [] - instance.data["files"].append(tmp_filepath) + instance.data["files"].append(file_name) From 1edaad61b5b812618cad503b271ebd17e25e78f3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:27:31 +0200 Subject: [PATCH 0986/1249] Added validator for outnode --- .../publish/validate_outnode_exists.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_outnode_exists.py diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py new file mode 100644 index 0000000000..479579a8f0 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api + + +class ValidatOutputNodeExists(pyblish.api.InstancePlugin): + """Validate if node attribute Create intermediate Directories is turned on + + Rules: + * The node must have Create intermediate Directories turned on to + ensure the output file will be created + + """ + + order = colorbleed.api.ValidateContentsOrder + families = ["*"] + hosts = ['houdini'] + label = "Output Node Exists" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Could not find output node(s)!") + + @classmethod + def get_invalid(cls, instance): + + import hou + + result = set() + + node = instance[0] + sop_path = node.parm("sop_path").eval() + if not sop_path.endswith("OUT"): + cls.log.error("SOP Path does not end path at output node") + result.add(node.path()) + + if hou.node(sop_path) is None: + cls.log.error("Node at '%s' does not exist" % sop_path) + result.add(node.path()) + + return result From cd5c41942434ac5efba5301b04332ea178704355 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:28:30 +0200 Subject: [PATCH 0987/1249] Fix get_additional_data to make items visible in scene inventory --- colorbleed/houdini/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index c9ce643751..0a0ba4b18d 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -92,7 +92,7 @@ def get_id_required_nodes(): def get_additional_data(container): """Not implemented yet!""" - pass + return container @contextmanager From 251f28190998340c7b683cea3acb24d244caf7a6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:29:57 +0200 Subject: [PATCH 0988/1249] Removed debug prints, removed double value --- colorbleed/houdini/lib.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index 0a0ba4b18d..cf526ba2b9 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -83,7 +83,7 @@ def generate_ids(nodes, asset_id=None): def get_id_required_nodes(): - valid_types = ["geometry", "geometry"] + valid_types = ["geometry"] nodes = {n for n in hou.node("/out").children() if n.type().name() in valid_types} @@ -99,15 +99,10 @@ def get_additional_data(container): def attribute_values(node, data): previous_attrs = {key: node.parm(key).eval() for key in data.keys()} - print("before", previous_attrs) try: node.setParms(data) - during_attrs = {key: node.parm(key).eval() for key in data.keys()} - print("during", during_attrs) yield except Exception as exc: - print(exc) pass finally: - print("reset") node.setParms(previous_attrs) From 7f55d394e468b33decd336d86426f8825799861c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:37:44 +0200 Subject: [PATCH 0989/1249] Remove print --- colorbleed/lib.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 8e1d0becc6..73bd80f9ff 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -242,8 +242,6 @@ def collect_container_metadata(container): hostlib = importlib.import_module(package_name) if not hasattr(hostlib, "get_additional_data"): - print("{} has no function called " - "get_additional_data".format(package_name)) return {} return hostlib.get_additional_data(container) From 7616158dabf5ca2e03d5e2e1060767e315da5241 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 12 Sep 2018 15:38:17 +0200 Subject: [PATCH 0990/1249] Renamed plugin --- colorbleed/plugins/houdini/load/load_alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index f3fcc3ad37..5043ba5a0d 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -7,7 +7,7 @@ class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" families = ["colorbleed.animation", "colorbleed.pointcache"] - label = "Reference animation" + label = "Load Animation" representations = ["abc"] order = -10 icon = "code-fork" From 822fa2c745132861b97d64a7b60f51807af7e83c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 10:50:58 +0200 Subject: [PATCH 0991/1249] Added collect animation plugin --- .../houdini/publish/collection_animation.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collection_animation.py diff --git a/colorbleed/plugins/houdini/publish/collection_animation.py b/colorbleed/plugins/houdini/publish/collection_animation.py new file mode 100644 index 0000000000..a959d9d5c6 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collection_animation.py @@ -0,0 +1,30 @@ +import pyblish.api + + +class CollectAnimation(pyblish.api.InstancePlugin): + """Collect the animation data for the data base + + Data collected: + - start frame + - end frame + - nr of steps + + """ + + label = "Collect Animation" + families = ["colorbleed.pointcache"] + + def process(self, instance): + + node = instance[0] + + # Get animation parameters for data + parameters = {"f1": "startFrame", + "f2": "endFrame", + "f3": "steps"} + + data = {} + for par, translation in parameters.items(): + data[translation] = node.parm(par).eval() + + instance.data.update(data) From b494ce0041eecf92eb270d204276933ccaf0f845 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 10:54:39 +0200 Subject: [PATCH 0992/1249] Allow node settings to dictate output settings --- colorbleed/plugins/houdini/publish/extract_alembic.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index 098020b905..f8ae69a635 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -19,15 +19,14 @@ class ExtractAlembic(colorbleed.api.Extractor): file_name = "{}.abc".format(instance.data["subset"]) tmp_filepath = os.path.join(staging_dir, file_name) - start_frame = float(instance.data["startFrame"]) - end_frame = float(instance.data["endFrame"]) - ropnode = instance[0] - attributes = {"filename": tmp_filepath, - "trange": 2} + # Set file name to staging dir + file name + attributes = {"filename": tmp_filepath} + + # We run the render with the input settings set by the user with lib.attribute_values(ropnode, attributes): - ropnode.render(frame_range=(start_frame, end_frame, 1)) + ropnode.render() if "files" not in instance.data: instance.data["files"] = [] From 940e0b3458dc65ff4f1d271791bb20b32c0a0ae5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 10:56:31 +0200 Subject: [PATCH 0993/1249] Set extra parameters --- .../plugins/houdini/create/create_pointcache.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 93cd83a6b3..4995912d8e 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -1,7 +1,5 @@ from collections import OrderedDict -import hou - from avalon import houdini @@ -22,9 +20,9 @@ class CreatePointCache(houdini.Creator): # Set node type to create for output data["node_type"] = "alembic" - # Collect animation data for point cache exporting - start, end = hou.playbar.timelineRange() - data["startFrame"] = start - data["endFrame"] = end - self.data = data + + def process(self): + instance = super(CreatePointCache, self).process() + instance.setParms({"build_from_path": 1, + "path_attrib": "path"}) From 390ccb4a094b03532fcd78f93ad97f790c65a945 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 14:47:08 +0200 Subject: [PATCH 0994/1249] REN-52: Ensure no default camera is renderable --- .../validate_render_no_default_cameras.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py new file mode 100644 index 0000000000..4f55670a8c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -0,0 +1,40 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): + """Ensure no default (startup) cameras are to be rendered.""" + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.renderlayer'] + label = "No Default Cameras Renderable" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + layer = instance.data["setMembers"] + + # Collect default cameras + cameras = cmds.ls(type='camera', long=True) + defaults = [cam for cam in cameras if + cmds.camera(cam, query=True, startupCamera=True)] + + invalid = [] + with lib.renderlayer(layer): + for cam in defaults: + if cmds.getAttr(cam + ".renderable"): + invalid.append(cam) + + return invalid + + def process(self, instance): + """Process all the cameras in the instance""" + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Renderable default cameras " + "found: {0}".format(invalid)) From 147b3320c069a82f183080e1a70098ddf6269a92 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 14:56:28 +0200 Subject: [PATCH 0995/1249] Validate only a single camera is renderable in a layer --- .../publish/validate_render_single_camera.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_render_single_camera.py diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py new file mode 100644 index 0000000000..e45f5f186c --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -0,0 +1,48 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + + +class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): + """Only one camera may be renderable in a layer. + + Currently the pipeline supports only a single camera per layer. + This is because when multiple cameras are rendered the output files + automatically get different names because the render token + is not in the output path. As such the output files conflict with how + our pipeline expects the output. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.renderlayer'] + label = "Render Single Camera" + actions = [colorbleed.api.SelectInvalidAction] + + @staticmethod + def get_invalid(instance): + + layer = instance.data["setMembers"] + + cameras = cmds.ls(type='camera', long=True) + + with lib.renderlayer(layer): + renderable = [cam for cam in cameras if + cmds.getAttr(cam + ".renderable")] + + if len(renderable) == 0: + raise RuntimeError("No renderable cameras found.") + elif len(renderable) > 1: + return renderable + else: + return [] + + def process(self, instance): + """Process all the cameras in the instance""" + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Multiple renderable cameras" + "found: {0}".format(invalid)) From 559e910190c6302da292a80af485d2f8760cc39c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 15:31:06 +0200 Subject: [PATCH 0996/1249] Improved flexibility, assume sop path points to output node --- .../houdini/publish/validate_outnode_exists.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py index 479579a8f0..011a1f63e7 100644 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py @@ -29,13 +29,21 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin): result = set() node = instance[0] - sop_path = node.parm("sop_path").eval() - if not sop_path.endswith("OUT"): - cls.log.error("SOP Path does not end path at output node") - result.add(node.path()) + if node.type().name() == "alembic": + soppath_parm = "sop_path" + else: + # Fall back to geometry node + soppath_parm = "soppath" - if hou.node(sop_path) is None: + sop_path = node.parm(soppath_parm).eval() + output_node = hou.node(sop_path) + + if output_node is None: cls.log.error("Node at '%s' does not exist" % sop_path) result.add(node.path()) + if output_node.type().name() != "output": + cls.log.error("SOP Path does not end path at output node") + result.add(node.path()) + return result From 774ff4bf0d83b375fa729977e7c3c3433c3c125c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 15:39:56 +0200 Subject: [PATCH 0997/1249] Validate FPS on_open() after update_task_from_path --- colorbleed/maya/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 5463cb0097..3f75ffd872 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -128,12 +128,13 @@ def on_open(_): from avalon.vendor.Qt import QtWidgets from ..widgets import popup - # Ensure scene's FPS is set to project config - lib.validate_fps() - # Update current task for the current scene update_task_from_path(cmds.file(query=True, sceneName=True)) + # Validate FPS after update_task_from_path to + # ensure it is using correct FPS for the asset + lib.validate_fps() + if any_outdated(): log.warning("Scene has outdated content.") From e9ec64b7ac9f5746ef5cdad8992744ff5ed0a2cb Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 16:05:00 +0200 Subject: [PATCH 0998/1249] Added instnace for vdb --- .../houdini/create/create_vbd_cache.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 colorbleed/plugins/houdini/create/create_vbd_cache.py diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py new file mode 100644 index 0000000000..0ba9431403 --- /dev/null +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -0,0 +1,23 @@ +from collections import OrderedDict + +from avalon import houdini + + +class CreateVDBCache(houdini.Creator): + """Alembic pointcache for animated data""" + + name = "vbdcache" + label = "VDB Cache" + family = "colorbleed.vbdcache" + icon = "cloud" + + def __init__(self, *args, **kwargs): + super(CreateVDBCache, self).__init__(*args, **kwargs) + + # create an ordered dict with the existing data first + data = OrderedDict(**self.data) + + # Set node type to create for output + data["node_type"] = "geometry" + + self.data = data From be890bf95af61ee0481a0e8320e8b1bf7431eca9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 16:51:45 +0200 Subject: [PATCH 0999/1249] Refactored plugins to match Creator logic --- .../plugins/houdini/create/create_pointcache.py | 13 +++++++++++-- .../plugins/houdini/create/create_vbd_cache.py | 9 +++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 4995912d8e..9169135245 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -24,5 +24,14 @@ class CreatePointCache(houdini.Creator): def process(self): instance = super(CreatePointCache, self).process() - instance.setParms({"build_from_path": 1, - "path_attrib": "path"}) + + parms = {"build_from_path": 1, + "path_attrib": "path", + "use_sop_path": True, + "filename": "$HIP/%s.abc" % self.name} + + if self.nodes: + node = self.nodes[0] + parms.update({"sop_path": "%s/OUT" % node.path()}) + + instance.setParms(parms) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 0ba9431403..c76ebd05c1 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -21,3 +21,12 @@ class CreateVDBCache(houdini.Creator): data["node_type"] = "geometry" self.data = data + + def process(self): + instance = super(CreateVDBCache, self).process() + + parms = {} + if self.nodes: + parms.update({"soppath": self.nodes[0].path()}) + + instance.setParms() From cb63ec54c572137c5a9ee9af13a5ebe0ebf031f6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 13 Sep 2018 16:52:55 +0200 Subject: [PATCH 1000/1249] Refactored class name --- colorbleed/plugins/houdini/publish/collect_current_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/colorbleed/plugins/houdini/publish/collect_current_file.py index e8612bdc12..7852943b34 100644 --- a/colorbleed/plugins/houdini/publish/collect_current_file.py +++ b/colorbleed/plugins/houdini/publish/collect_current_file.py @@ -3,7 +3,7 @@ import hou import pyblish.api -class CollectMayaCurrentFile(pyblish.api.ContextPlugin): +class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): """Inject the current working file into context""" order = pyblish.api.CollectorOrder - 0.5 From bc48a8df2bf920f46a8b45c85c1ed39c2ba748da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 17:55:27 +0200 Subject: [PATCH 1001/1249] Set nice pyblish instance name with frame range in Pyblish --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 907b9367f5..48f7fe113c 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -103,7 +103,12 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): overrides = self.parse_options(render_globals) data.update(**overrides) - instance = context.create_instance(layername) + # Define nice label + label = "{0} ({1})".format(layername, data["asset"]) + label += " [{0}-{1}]".format(int(data["startFrame"]), + int(data["endFrame"])) + + instance = context.create_instance(label) instance.data.update(data) def get_render_attribute(self, attr): From 1bfff0a74aef0ecb037b528b538341938a2dc6fd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 17:56:17 +0200 Subject: [PATCH 1002/1249] Fix getting AOVs code --- .../maya/publish/collect_render_layer_aovs.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index 19ab891004..16039fe5e4 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -41,7 +41,7 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): self.log.info("Renderer found: {}".format(renderer)) - rp_node_types = {"vray": "VRayRenderElement", + rp_node_types = {"vray": ("VRayRenderElement", "VRayRenderElementSet"), "arnold": "aiAOV", "redshift": "RedshiftAOV"} @@ -52,7 +52,8 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): result = [] # Collect all AOVs / Render Elements - with lib.renderlayer(instance.name): + layer = instance.data["setMembers"] + with lib.renderlayer(layer): node_type = rp_node_types[renderer] render_elements = cmds.ls(type=node_type) @@ -64,32 +65,36 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): continue pass_name = self.get_pass_name(renderer, element) - render_pass = "%s.%s" % (instance.name, pass_name) + render_pass = "%s.%s" % (instance.data["subset"], pass_name) result.append(render_pass) self.log.info("Found {} render elements / AOVs for " - "'{}'".format(len(result), instance.name)) + "'{}'".format(len(result), instance.data["subset"])) instance.data["renderPasses"] = result def get_pass_name(self, renderer, node): if renderer == "vray": + + # Get render element pass type vray_node_attr = next(attr for attr in cmds.listAttr(node) if attr.startswith("vray_name")) - pass_type = vray_node_attr.rsplit("_", 1)[-1] + + # Support V-Ray extratex explicit name (if set by user) if pass_type == "extratex": - vray_node_attr = "vray_explicit_name_extratex" + explicit_attr = "{}.vray_explicit_name_extratex".format(node) + explicit_name = cmds.getAttr(explicit_attr) + if explicit_name: + return explicit_name # Node type is in the attribute name but we need to check if value # of the attribute as it can be changed - pass_name = cmds.getAttr("{}.{}".format(node, vray_node_attr)) + return cmds.getAttr("{}.{}".format(node, vray_node_attr)) elif renderer in ["arnold", "redshift"]: - pass_name = cmds.getAttr("{}.name".format(node)) + return cmds.getAttr("{}.name".format(node)) else: raise RuntimeError("Unsupported renderer: '{}'".format(renderer)) - - return pass_name \ No newline at end of file From 4d763157d3f85cc5fe27465d364290e36e114b9f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 18:01:10 +0200 Subject: [PATCH 1003/1249] Fix renderlayer name (since nice label change instance.name) --- colorbleed/plugins/maya/publish/submit_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 5e2c3334ff..dbb987a603 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -115,7 +115,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): scene = os.path.splitext(filename)[0] dirname = os.path.join(workspace, "renders") renderlayer = instance.data['setMembers'] # rs_beauty - renderlayer_name = instance.name # beauty + renderlayer_name = instance.data['subset'] # beauty renderlayer_globals = instance.data["renderGlobals"] legacy_layers = renderlayer_globals["UseLegacyRenderLayers"] deadline_user = context.data.get("deadlineUser", getpass.getuser()) From b2ca58723f28b457349eca28f282963221ea9b68 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Sep 2018 18:17:54 +0200 Subject: [PATCH 1004/1249] Fix FPS check for asset overridden values (and simplify getting required asset fps) --- colorbleed/lib.py | 20 ++++++++++++++----- colorbleed/maya/lib.py | 3 +-- .../maya/publish/validate_maya_units.py | 11 +++++----- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index 119dcd4c25..bc98cf0cc5 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -252,7 +252,7 @@ def collect_container_metadata(container): return hostlib.get_additional_data(container) -def get_project_fps(): +def get_asset_fps(): """Returns project's FPS, if not found will return 25 by default Returns: @@ -260,10 +260,20 @@ def get_project_fps(): """ - data = get_project_data() - fps = data.get("fps", 25.0) + key = "fps" - return fps + # FPS from asset data (if set) + asset_data = get_asset_data() + if key in asset_data: + return asset_data[key] + + # FPS from project data (if set) + project_data = get_project_data() + if key in project_data: + return project_data[key] + + # Fallback to 25 FPS + return 25.0 def get_project_data(): @@ -298,7 +308,7 @@ def get_asset_data(asset=None): Returns: dict """ - + asset_name = asset or avalon.api.Session["AVALON_ASSET"] document = io.find_one({"name": asset_name, "type": "asset"}) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 59ea54c20d..b1351cc19c 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1472,8 +1472,7 @@ def validate_fps(): """ - asset_data = lib.get_asset_data() - fps = asset_data.get("fps", lib.get_project_fps()) # can be int or float + fps = lib.get_asset_fps() current_fps = mel.eval('currentTimeUnitToFPS()') # returns float if current_fps != fps: diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 6548e8b6c3..04db95fdde 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -16,11 +16,12 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): def process(self, context): + # Collected units linearunits = context.data('linearUnits') angularunits = context.data('angularUnits') - fps = context.data['fps'] - project_fps = lib.get_project_fps() + + asset_fps = lib.get_asset_fps() self.log.info('Units (linear): {0}'.format(linearunits)) self.log.info('Units (angular): {0}'.format(angularunits)) @@ -32,7 +33,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): assert angularunits and angularunits == 'deg', ("Scene angular units " "must be degrees") - assert fps and fps == project_fps, "Scene must be %s FPS" % project_fps + assert fps and fps == asset_fps, "Scene must be %s FPS" % asset_fps @classmethod def repair(cls): @@ -49,5 +50,5 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): cls.log.debug(current_linear) cls.log.info("Setting time unit to match project") - project_fps = lib.get_project_fps() - mayalib.set_scene_fps(project_fps) + asset_fps = lib.get_asset_fps() + mayalib.set_scene_fps(asset_fps) From 7e424816f97e41f3a89cedc177818e51d459c4e7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 09:38:16 +0200 Subject: [PATCH 1005/1249] Removed staging dir to support large write cache --- .../plugins/houdini/publish/extract_alembic.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index f8ae69a635..29450d0824 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -2,7 +2,6 @@ import os import pyblish.api import colorbleed.api -from colorbleed.houdini import lib class ExtractAlembic(colorbleed.api.Extractor): @@ -14,19 +13,14 @@ class ExtractAlembic(colorbleed.api.Extractor): def process(self, instance): - staging_dir = self.staging_dir(instance) - - file_name = "{}.abc".format(instance.data["subset"]) - tmp_filepath = os.path.join(staging_dir, file_name) - ropnode = instance[0] - # Set file name to staging dir + file name - attributes = {"filename": tmp_filepath} + # Get the filename from the filename parameter + # `.eval()` will make sure all tokens are resolved + file_name = os.path.basename(ropnode.parm("filename").eval()) - # We run the render with the input settings set by the user - with lib.attribute_values(ropnode, attributes): - ropnode.render() + # We run the render + ropnode.render() if "files" not in instance.data: instance.data["files"] = [] From 13aa727f5ab21afe8d4893100095a18404bdcdb2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 09:39:29 +0200 Subject: [PATCH 1006/1249] Passed argument to setParm, added sop ouput --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index c76ebd05c1..65cac4a7fd 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -25,8 +25,8 @@ class CreateVDBCache(houdini.Creator): def process(self): instance = super(CreateVDBCache, self).process() - parms = {} + parms = {"sopoutput": "$HIP/geo/%s.$F.vdb" % self.name} if self.nodes: parms.update({"soppath": self.nodes[0].path()}) - instance.setParms() + instance.setParms(parms) From 13f43f1925b023577711924790e685f14b7e75cf Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 10:16:13 +0200 Subject: [PATCH 1007/1249] Added host and order --- .../plugins/houdini/publish/collection_animation.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collection_animation.py b/colorbleed/plugins/houdini/publish/collection_animation.py index a959d9d5c6..835e52eb68 100644 --- a/colorbleed/plugins/houdini/publish/collection_animation.py +++ b/colorbleed/plugins/houdini/publish/collection_animation.py @@ -11,8 +11,10 @@ class CollectAnimation(pyblish.api.InstancePlugin): """ - label = "Collect Animation" + order = pyblish.api.CollectorOrder families = ["colorbleed.pointcache"] + hosts = ["houdini"] + label = "Collect Animation" def process(self, instance): @@ -23,8 +25,7 @@ class CollectAnimation(pyblish.api.InstancePlugin): "f2": "endFrame", "f3": "steps"} - data = {} - for par, translation in parameters.items(): - data[translation] = node.parm(par).eval() + data = {name: node.parm(par).eval() for par, name in + parameters.items()} instance.data.update(data) From 61faa08752bc2507fe9f468be286f6cfa6629681 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 10:46:26 +0200 Subject: [PATCH 1008/1249] Fixed family name typo, added frame padding of 4 for frames --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 65cac4a7fd..b103a046fa 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -8,7 +8,7 @@ class CreateVDBCache(houdini.Creator): name = "vbdcache" label = "VDB Cache" - family = "colorbleed.vbdcache" + family = "colorbleed.vdbcache" icon = "cloud" def __init__(self, *args, **kwargs): @@ -25,7 +25,7 @@ class CreateVDBCache(houdini.Creator): def process(self): instance = super(CreateVDBCache, self).process() - parms = {"sopoutput": "$HIP/geo/%s.$F.vdb" % self.name} + parms = {"sopoutput": "$HIP/geo/%s.$F4.vdb" % self.name} if self.nodes: parms.update({"soppath": self.nodes[0].path()}) From 7d162aa5bb8727e70bdbc2f8ec3a551be6a51509 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:01:54 +0200 Subject: [PATCH 1009/1249] Check for animation data --- .../houdini/publish/collect_instances.py | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 5f77b9d805..0e5301a6a1 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -24,8 +24,8 @@ class CollectInstances(pyblish.api.ContextPlugin): """ + order = pyblish.api.CollectorOrder - 0.01 label = "Collect Instances" - order = pyblish.api.CollectorOrder hosts = ["houdini"] def process(self, context): @@ -51,7 +51,17 @@ class CollectInstances(pyblish.api.ContextPlugin): if "active" in data: data["publish"] = data["active"] - instance = context.create_instance(data.get("name", node.name())) + data.update(self.get_frame_data(node)) + + # Create nice name + # All nodes in the Outputs graph have the 'Valid Frame Range' + # attribute, we check here if any frames are set + label = data.get("name", node.name()) + if "startFrame" in data: + frames = "[{startFrame} - {endFrame}]".format(**data) + label = "{} {}".format(label, frames) + + instance = context.create_instance(label) instance[:] = [node] instance.data.update(data) @@ -66,3 +76,27 @@ class CollectInstances(pyblish.api.ContextPlugin): context[:] = sorted(context, key=sort_by_family) return context + + def get_frame_data(self, node): + """Get the frame data: start frame, end frame and steps + Args: + node(hou.Node) + + Returns: + dict + + """ + + data = {} + + if node.parm("trange") is None: + return data + + if node.parm("trange").eval() == 0: + return data + + data["startFrame"] = node.parm("f1").eval() + data["endFrame"] = node.parm("f2").eval() + data["steps"] = node.parm("f3").eval() + + return data From 4abee5618c16cf7a130df3b1070248557ae98e18 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:27:48 +0200 Subject: [PATCH 1010/1249] Collect currently set output of render node --- .../houdini/publish/collect_output_node.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collect_output_node.py diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py new file mode 100644 index 0000000000..dbfe8a5890 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -0,0 +1,27 @@ +import pyblish.api + + +class CollectOutputNode(pyblish.api.InstancePlugin): + """Collect the out node which of the instance""" + + order = pyblish.api.CollectorOrder + families = ["*"] + hosts = ["houdini"] + label = "Collect Output Node" + + def process(self, instance): + + import hou + + node = instance[0] + + # Get sop path + if node.type().name() == "alembic": + sop_path_parm = "sop_path" + else: + sop_path_parm = "soppath" + + sop_path = node.parm(sop_path_parm).eval() + out_node = hou.node(sop_path) + + instance.data["output_node"] = out_node From 7001766c93aafd61485c90f16ef2ea5e64887151 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:28:06 +0200 Subject: [PATCH 1011/1249] Generic checks for output node --- .../houdini/publish/validate_output_node.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_output_node.py diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py new file mode 100644 index 0000000000..be7551cf0d --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -0,0 +1,43 @@ +import pyblish.api + + +class ValidateOutputNode(pyblish.api.InstancePlugin): + """Validate if output node: + - exists + - is of type 'output' + - has an input""" + + order = pyblish.api.ValidatorOrder + families = ["*"] + hosts = ["houdini"] + label = "Validate Output Node" + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Output node(s) `%s` are incorrect" % invalid) + + @classmethod + def get_invalid(cls, instance): + + output_node = instance.data["output_node"] + + if output_node is None: + node = instance[0] + cls.log.error("Output node at '%s' does not exist, see source" % + node.path()) + + return node.path() + + # Check if type is correct + if output_node.type().name() != "output": + cls.log.error("Output node `%s` is not if type `output`" % + output_node.path()) + return output_node.path() + + # Check if node has incoming connections + if not output_node.inputConnections(): + cls.log.error("Output node `%s` has no incoming connections" + % output_node.path()) + return output_node.path() From c94d59994597d8aefd40620d7a3b7b003470f7e7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:28:23 +0200 Subject: [PATCH 1012/1249] Check for vdb output --- .../houdini/publish/valiate_vdb_input_node.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py diff --git a/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py b/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py new file mode 100644 index 0000000000..25a8d86ce3 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py @@ -0,0 +1,47 @@ +import pyblish.api +import colorbleed.api + + +class ValidateVDBInputNode(pyblish.api.InstancePlugin): + """Validate that the node connected to the output node is of type VDB + + Regardless of the amount of VDBs create the output will need to have an + equal amount of VDBs, points, primitives and vertices + + A VDB is an inherited type of Prim, holds the following data: + - Primitives: 1 + - Points: 1 + - Vertices: 1 + - VDBs: 1 + + """ + + order = colorbleed.api.ValidateContentsOrder + 0.1 + families = ["colorbleed.vdbcache"] + hosts = ["houdini"] + label = "Validate Input Node (VDB)" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Node connected to the output node is not" + "of type VDB!") + + @classmethod + def get_invalid(cls, instance): + + node = instance.data["output_node"] + + prims = node.geometry().prims() + nr_of_prims = len(prims) + + nr_of_points = len(node.geometry().points()) + if nr_of_points != nr_of_prims: + cls.log.error("The number of primitives and points do not match") + return [instance] + + for prim in prims: + # print(">>", prim.name()) + if prim.numVertices() != 1: + cls.log.error("Found primitive with more than 1 vertex!") + return [instance] From 925dcdf4bc2379648e7e03ecfb909809ce512667 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:28:50 +0200 Subject: [PATCH 1013/1249] Fixed typo --- colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 8dcc6e0509..7e298ce952 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -2,7 +2,7 @@ import pyblish.api import colorbleed.api -class ValidatIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): +class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate if node attribute Create intermediate Directories is turned on Rules: From c7584523e559627b6178d014565fc3f063b78cdf Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 13:29:07 +0200 Subject: [PATCH 1014/1249] Extractor for VDB ROPs --- .../houdini/publish/extract_vdb_cache.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/extract_vdb_cache.py diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py new file mode 100644 index 0000000000..ad85a5daf0 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -0,0 +1,42 @@ +import os +import re + +import pyblish.api +import colorbleed.api + + +class ExtractVDBCache(colorbleed.api.Extractor): + + order = pyblish.api.ExtractorOrder + 0.1 + label = "Extract VDB Cache" + families = ["colorbleed.vdbcache"] + hosts = ["houdini"] + + def process(self, instance): + + ropnode = instance[0] + + # Get the filename from the filename parameter + # `.eval()` will make sure all tokens are resolved + output = ropnode.parm("sopoutput").eval() + staging_dir = os.path.dirname(output) + instance.data["stagingDir"] = staging_dir + + # Replace the 4 digits to match file sequence token '%04d' if we have + # a sequence of frames + file_name = os.path.basename(output) + has_frame = re.match("\w\.(d+)\.vdb", file_name) + if has_frame: + frame_nr = has_frame.group() + file_name.replace(frame_nr, "%04d") + + # We run the render + self.log.info( + "Starting render: {startFrame} - {endFrame}".format(**instance.data) + ) + ropnode.render() + + if "files" not in instance.data: + instance.data["files"] = [] + + instance.data["files"].append(file_name) From 13903ebbad379774b7062165b5b0c736a7a3c10f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 14 Sep 2018 14:16:27 +0200 Subject: [PATCH 1015/1249] Cosmetics - make all a list for consistency --- .../plugins/maya/publish/collect_render_layer_aovs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index 16039fe5e4..f39330e10f 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -41,9 +41,9 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): self.log.info("Renderer found: {}".format(renderer)) - rp_node_types = {"vray": ("VRayRenderElement", "VRayRenderElementSet"), - "arnold": "aiAOV", - "redshift": "RedshiftAOV"} + rp_node_types = {"vray": ["VRayRenderElement", "VRayRenderElementSet"], + "arnold": ["aiAOV"], + "redshift": ["RedshiftAOV"]} if renderer not in rp_node_types.keys(): self.log.error("Unsupported renderer found: '{}'".format(renderer)) From f1bf3d806626dc40148578893e2a2b4affe23052 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 16:54:39 +0200 Subject: [PATCH 1016/1249] Removed commented line --- colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py b/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py index 25a8d86ce3..24606046fa 100644 --- a/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py +++ b/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py @@ -41,7 +41,6 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): return [instance] for prim in prims: - # print(">>", prim.name()) if prim.numVertices() != 1: cls.log.error("Found primitive with more than 1 vertex!") return [instance] From d91ca5c1d4cc766d00332e422fc45c3c59531b67 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 17:18:21 +0200 Subject: [PATCH 1017/1249] Added alembic output node validator --- .../publish/validate_alembic_input_node.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_alembic_input_node.py diff --git a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py new file mode 100644 index 0000000000..91f9e9f97e --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py @@ -0,0 +1,37 @@ +import pyblish.api +import colorbleed.api + + +class ValidateAlembicInputNode(pyblish.api.InstancePlugin): + """Validate that the node connected to the output is correct + + The connected node cannot be of the following types for Alembic: + - VDB + - Volumne + + """ + + order = colorbleed.api.ValidateContentsOrder + 0.1 + families = ["colorbleed.pointcache"] + hosts = ["houdini"] + label = "Validate Input Node (Abc)" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Node connected to the output node incorrect") + + @classmethod + def get_invalid(cls, instance): + + invalid_nodes = ["VDB", "Volume"] + node = instance.data["output_node"] + + prims = node.geometry().prims() + + for prim in prims: + prim_type = prim.type().name() + if prim_type in invalid_nodes: + cls.log.error("Found a primitive which is of type '%s' !" + % prim_type) + return [instance] From 5ddc333999fc81a4027fcdf7fbf1d4f7c94dbfb7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 14 Sep 2018 17:18:43 +0200 Subject: [PATCH 1018/1249] Set staging dir to what user has entered --- colorbleed/plugins/houdini/publish/extract_alembic.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index 29450d0824..f66b5bde72 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -17,9 +17,14 @@ class ExtractAlembic(colorbleed.api.Extractor): # Get the filename from the filename parameter # `.eval()` will make sure all tokens are resolved - file_name = os.path.basename(ropnode.parm("filename").eval()) + output = ropnode.parm("filename").eval() + staging_dir = os.path.dirname(output) + instance.data["stagingDir"] = staging_dir + + file_name = os.path.basename(output) # We run the render + self.log.info("Writing alembic '%s' to '%s'" % (file_name, staging_dir)) ropnode.render() if "files" not in instance.data: From d7ca17c4f94b0a3c0cd91bf0229e3a2a93ffd773 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 17 Sep 2018 09:30:35 +0200 Subject: [PATCH 1019/1249] Added new family --- colorbleed/plugins/global/publish/integrate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 2418971546..6917b43f6f 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -30,6 +30,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "colorbleed.mayaAscii", "colorbleed.model", "colorbleed.pointcache", + "colorbleed.vdbcache", "colorbleed.setdress", "colorbleed.rig", "colorbleed.yetiRig", From f03a6b31c4cfb3a908287de0ae5a974252f844cc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 17 Sep 2018 09:32:09 +0200 Subject: [PATCH 1020/1249] Updated doc strings --- colorbleed/plugins/maya/load/load_alembic.py | 2 +- colorbleed/plugins/maya/load/load_camera.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/colorbleed/plugins/maya/load/load_alembic.py index afea761ab8..d34530a8d5 100644 --- a/colorbleed/plugins/maya/load/load_alembic.py +++ b/colorbleed/plugins/maya/load/load_alembic.py @@ -2,7 +2,7 @@ import colorbleed.maya.plugin class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" + """Specific loader of Alembic for the colorbleed.animation family""" families = ["colorbleed.animation", "colorbleed.pointcache"] diff --git a/colorbleed/plugins/maya/load/load_camera.py b/colorbleed/plugins/maya/load/load_camera.py index 1d66433a6f..067c4c0cde 100644 --- a/colorbleed/plugins/maya/load/load_camera.py +++ b/colorbleed/plugins/maya/load/load_camera.py @@ -2,7 +2,7 @@ import colorbleed.maya.plugin class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" + """Specific loader of Alembic for the colorbleed.camera family""" families = ["colorbleed.camera"] label = "Reference camera" From b229fc20cd78a9f1f971f82eac804f4a2d8fabc7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 17 Sep 2018 10:09:54 +0200 Subject: [PATCH 1021/1249] Added VDB cache loader --- .../plugins/maya/load/load_vdb_to_vray.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_vdb_to_vray.py diff --git a/colorbleed/plugins/maya/load/load_vdb_to_vray.py b/colorbleed/plugins/maya/load/load_vdb_to_vray.py new file mode 100644 index 0000000000..45294845d4 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_vdb_to_vray.py @@ -0,0 +1,64 @@ +from avalon import api +# import colorbleed.maya.plugin + + +class LoadVDBtoVRay(api.Loader): + + families = ["colorbleed.vdbcache"] + representations = ["vdb"] + + name = "Load VDB to VRay" + icon = "cloud" + color = "orange" + + def load(self, context, name, namespace, data): + + # import pprint + from maya import cmds + import avalon.maya.lib as lib + from avalon.maya.pipeline import containerise + + # Check if viewport drawing engine is Open GL Core (compat) + render_engine = None + compatible = "OpenGLCoreProfileCompat" + if cmds.optionVar(exists="vp2RenderingEngine"): + render_engine = cmds.optionVar(query="vp2RenderingEngine") + + if not render_engine or render_engine != compatible: + raise RuntimeError("Current scene's settings are incompatible." + "See Preferences > Display > Viewport 2.0 to " + "set the render engine to '%s'" % compatible) + + asset = context['asset'] + version = context["version"] + + asset_name = asset["name"] + namespace = namespace or lib.unique_namespace( + asset_name + "_", + prefix="_" if asset_name[0].isdigit() else "", + suffix="_", + ) + + # Root group + label = "{}:{}".format(namespace, name) + root = cmds.group(name=label, empty=True) + + # Create VR + grid_node = cmds.createNode("VRayVolumeGrid", + name="{}VVGShape".format(label), + parent=root) + + # Set attributes + cmds.setAttr("{}.inFile".format(grid_node), self.fname, type="string") + cmds.setAttr("{}.inReadOffset".format(grid_node), + version["startFrames"]) + + nodes = [root, grid_node] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) From 27992f68e1b540a4fb31a275903996a42556267c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 18 Sep 2018 13:09:15 +0200 Subject: [PATCH 1022/1249] Added load alembic camera --- .../plugins/houdini/load/load_camera.py | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 colorbleed/plugins/houdini/load/load_camera.py diff --git a/colorbleed/plugins/houdini/load/load_camera.py b/colorbleed/plugins/houdini/load/load_camera.py new file mode 100644 index 0000000000..f827244c6b --- /dev/null +++ b/colorbleed/plugins/houdini/load/load_camera.py @@ -0,0 +1,119 @@ +from avalon import api + +from avalon.houdini import pipeline, lib + + +class CameraLoader(api.Loader): + """Specific loader of Alembic for the avalon.animation family""" + + families = ["colorbleed.camera"] + label = "Load Camera (abc)" + representations = ["abc"] + order = -10 + + icon = "code-fork" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + import os + import hou + + # Format file name, Houdini only wants forward slashes + file_path = os.path.normpath(self.fname) + file_path = file_path.replace("\\", "/") + + # Get the root node + obj = hou.node("/obj") + + # Create a unique name + counter = 1 + asset_name = context["asset"]["name"] + + namespace = namespace if namespace else asset_name + formatted = "{}_{}".format(namespace, name) if namespace else name + node_name = "{0}_{1:03d}".format(formatted, counter) + + children = lib.children_as_string(hou.node("/obj")) + while node_name in children: + counter += 1 + node_name = "{0}_{1:03d}".format(formatted, counter) + + # Create a archive node + container = self.create_and_connect(obj, "alembicarchive", node_name) + + # TODO: add FPS of project / asset + container.setParms({"fileName": file_path, + "channelRef": True}) + + # Apply some magic + container.parm("buildHierarchy").pressButton() + container.moveToGoodPosition() + + # Create an alembic xform node + nodes = [container] + + self[:] = nodes + + return pipeline.containerise(node_name, + namespace, + nodes, + context, + self.__class__.__name__) + + def update(self, container, representation): + + node = container["node"] + + # Update the file path + file_path = api.get_representation_path(representation) + file_path = file_path.replace("\\", "/") + + # Update attributes + node.setParms({"fileName": file_path, + "representation": str(representation["_id"])}) + + # Rebuild + node.parm("buildHierarchy").pressButton() + + def remove(self, container): + + node = container["node"] + node.destroy() + + def create_and_connect(self, node, node_type, name=None): + """Create a node within a node which and connect it to the input + + Args: + node(hou.Node): parent of the new node + node_type(str) name of the type of node, eg: 'alembic' + name(str, Optional): name of the node + + Returns: + hou.Node + + """ + + import hou + + try: + + if name: + new_node = node.createNode(node_type, node_name=name) + else: + new_node = node.createNode(node_type) + + new_node.moveToGoodPosition() + + try: + input_node = next(i for i in node.allItems() if + isinstance(i, hou.SubnetIndirectInput)) + except StopIteration: + return new_node + + new_node.setInput(0, input_node) + return new_node + + except Exception: + raise RuntimeError("Could not created node type `%s` in node `%s`" + % (node_type, node)) From 3f933d07367c1db68c79e596a525cc6666bf1f44 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 18 Sep 2018 14:16:02 +0200 Subject: [PATCH 1023/1249] Renamed plugin, added colorbleed.model --- colorbleed/plugins/houdini/load/load_alembic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index 5043ba5a0d..94e3894d05 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -6,8 +6,10 @@ from avalon.houdini import pipeline, lib class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", "colorbleed.pointcache"] - label = "Load Animation" + families = ["colorbleed.model", + "colorbleed.animation", + "colorbleed.pointcache"] + label = "Load Alembic" representations = ["abc"] order = -10 icon = "code-fork" From ad0f846a028da00e37bb71df1d8fe9c43758364f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 18 Sep 2018 14:23:35 +0200 Subject: [PATCH 1024/1249] Refactored parameter evaluation --- colorbleed/plugins/houdini/publish/collect_instances.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 0e5301a6a1..effd6e392e 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -92,11 +92,11 @@ class CollectInstances(pyblish.api.ContextPlugin): if node.parm("trange") is None: return data - if node.parm("trange").eval() == 0: + if node.evalParm("trange") == 0: return data - data["startFrame"] = node.parm("f1").eval() - data["endFrame"] = node.parm("f2").eval() - data["steps"] = node.parm("f3").eval() + data["startFrame"] = node.evalParm("f1") + data["endFrame"] = node.evalParm("f2") + data["steps"] = node.evalParm("f3") return data From a3a32950d15116fdb38ffbd5284df76ca2c84417 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 10:40:32 +0200 Subject: [PATCH 1025/1249] Added alembic camera instance --- .../houdini/create/create_alembic_camera.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 colorbleed/plugins/houdini/create/create_alembic_camera.py diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py new file mode 100644 index 0000000000..43213f46b0 --- /dev/null +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -0,0 +1,36 @@ +from collections import OrderedDict + +from avalon import houdini + + +class CreateAlembicCamera(houdini.Creator): + + name = "camera" + label = "Camera (Abc)" + family = "colorbleed.camera" + icon = "camera" + + def __init__(self, *args, **kwargs): + super(CreateAlembicCamera, self).__init__(*args, **kwargs) + + # create an ordered dict with the existing data first + data = OrderedDict(**self.data) + + # Set node type to create for output + data["node_type"] = "alembic" + + self.data = data + + def process(self): + instance = super(CreateAlembicCamera, self).process() + + parms = {"use_sop_path": True, + "build_from_path": True, + "path_attrib": "path", + "filename": "$HIP/%s.abc" % self.name} + + if self.nodes: + node = self.nodes[0] + parms.update({"sop_path": node.path()}) + + instance.setParms(parms) From e9aac130390f90ddbf25d962c25aa0fa0fd9457e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 10:41:26 +0200 Subject: [PATCH 1026/1249] Cosmetics --- colorbleed/plugins/houdini/create/create_pointcache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 9169135245..25671c787e 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -25,9 +25,9 @@ class CreatePointCache(houdini.Creator): def process(self): instance = super(CreatePointCache, self).process() - parms = {"build_from_path": 1, + parms = {"use_sop_path": True, + "build_from_path": True, "path_attrib": "path", - "use_sop_path": True, "filename": "$HIP/%s.abc" % self.name} if self.nodes: From abbcd30f86078664177b84edb57622abe8df7d83 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 10:42:42 +0200 Subject: [PATCH 1027/1249] Renamed and updated plugin, added colorbleed.camera --- colorbleed/plugins/houdini/publish/extract_alembic.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index f66b5bde72..632b0a7d7b 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -7,17 +7,16 @@ import colorbleed.api class ExtractAlembic(colorbleed.api.Extractor): order = pyblish.api.ExtractorOrder - label = "Extract Pointcache (Alembic)" + label = "Extract Alembic" hosts = ["houdini"] - families = ["colorbleed.pointcache"] + families = ["colorbleed.pointcache", "colorbleed.camera"] def process(self, instance): ropnode = instance[0] # Get the filename from the filename parameter - # `.eval()` will make sure all tokens are resolved - output = ropnode.parm("filename").eval() + output = ropnode.evalParm("filename") staging_dir = os.path.dirname(output) instance.data["stagingDir"] = staging_dir From d1d6064e1ea8cf60e77caec37a397766ae820e3c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 10:50:02 +0200 Subject: [PATCH 1028/1249] Improved validator to accept node type camera --- .../houdini/publish/validate_output_node.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index be7551cf0d..18fcb4898f 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -31,13 +31,15 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): return node.path() # Check if type is correct - if output_node.type().name() != "output": - cls.log.error("Output node `%s` is not if type `output`" % + type_name = output_node.type().name() + if type_name not in ["output", "cam"]: + cls.log.error("Output node `%s` is an accepted type `output` " + "or `camera`" % output_node.path()) - return output_node.path() + return [output_node.path()] - # Check if node has incoming connections - if not output_node.inputConnections(): + # Check if output node has incoming connections + if type_name == "output" and not output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) - return output_node.path() + return [output_node.path()] From 8369629415e5a945a7caf13d506c648103ac4f86 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 10:55:57 +0200 Subject: [PATCH 1029/1249] Added cam to accepted type --- colorbleed/plugins/houdini/publish/validate_outnode_exists.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py index 011a1f63e7..b9f24faa32 100644 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py @@ -42,7 +42,8 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin): cls.log.error("Node at '%s' does not exist" % sop_path) result.add(node.path()) - if output_node.type().name() != "output": + # Added cam as this is a legit output type (cameras can't + if output_node.type().name() not in ["output", "cam"]: cls.log.error("SOP Path does not end path at output node") result.add(node.path()) From 9ee8485af663318e5bdedfc4ea2c9250a00dfc34 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 12:29:02 +0200 Subject: [PATCH 1030/1249] Removed commented lines --- colorbleed/plugins/maya/load/load_vdb_to_vray.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/maya/load/load_vdb_to_vray.py b/colorbleed/plugins/maya/load/load_vdb_to_vray.py index 45294845d4..04f623a3fb 100644 --- a/colorbleed/plugins/maya/load/load_vdb_to_vray.py +++ b/colorbleed/plugins/maya/load/load_vdb_to_vray.py @@ -1,5 +1,4 @@ from avalon import api -# import colorbleed.maya.plugin class LoadVDBtoVRay(api.Loader): @@ -13,7 +12,6 @@ class LoadVDBtoVRay(api.Loader): def load(self, context, name, namespace, data): - # import pprint from maya import cmds import avalon.maya.lib as lib from avalon.maya.pipeline import containerise From 924c80d0956294e4480ba0a6cf155f5aa46c167a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 12:50:31 +0200 Subject: [PATCH 1031/1249] Ensure label is used in RMB menu --- colorbleed/plugins/maya/load/load_vdb_to_vray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/load/load_vdb_to_vray.py b/colorbleed/plugins/maya/load/load_vdb_to_vray.py index 04f623a3fb..5d868174ce 100644 --- a/colorbleed/plugins/maya/load/load_vdb_to_vray.py +++ b/colorbleed/plugins/maya/load/load_vdb_to_vray.py @@ -6,7 +6,7 @@ class LoadVDBtoVRay(api.Loader): families = ["colorbleed.vdbcache"] representations = ["vdb"] - name = "Load VDB to VRay" + label = "Load VDB to VRay" icon = "cloud" color = "orange" From 4e544893b91fb413612d384063b63b1d6e0bec83 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 12:51:13 +0200 Subject: [PATCH 1032/1249] Added VDB to Redshift --- .../plugins/maya/load/load_vdb_to_redshift.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 colorbleed/plugins/maya/load/load_vdb_to_redshift.py diff --git a/colorbleed/plugins/maya/load/load_vdb_to_redshift.py b/colorbleed/plugins/maya/load/load_vdb_to_redshift.py new file mode 100644 index 0000000000..faaf2314e5 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_vdb_to_redshift.py @@ -0,0 +1,68 @@ +from avalon import api + + +class LoadVDBtoRedShift(api.Loader): + + families = ["colorbleed.vdbcache"] + representations = ["vdb"] + + label = "Load VDB to RedShift" + icon = "cloud" + color = "orange" + + def load(self, context, name=None, namespace=None, data=None): + + from maya import cmds + import avalon.maya.lib as lib + from avalon.maya.pipeline import containerise + + # Check if the plugin for redshift is available on the pc + try: + cmds.loadPlugin("redshift4maya", quiet=True) + except Exception as exc: + self.log.error("Encountered exception:\n%s" % exc) + return + + # Check if viewport drawing engine is Open GL Core (compat) + render_engine = None + compatible = "OpenGL" + if cmds.optionVar(exists="vp2RenderingEngine"): + render_engine = cmds.optionVar(query="vp2RenderingEngine") + + if not render_engine or not render_engine.startswith(compatible): + raise RuntimeError("Current scene's settings are incompatible." + "See Preferences > Display > Viewport 2.0 to " + "set the render engine to '%s'" + % compatible) + + asset = context['asset'] + + asset_name = asset["name"] + namespace = namespace or lib.unique_namespace( + asset_name + "_", + prefix="_" if asset_name[0].isdigit() else "", + suffix="_", + ) + + # Root group + label = "{}:{}".format(namespace, name) + root = cmds.group(name=label, empty=True) + + # Create VR + volume_node = cmds.createNode("RedshiftVolumeShape", + name="{}RVSShape".format(label), + parent=root) + + cmds.setAttr("{}.fileName".format(volume_node), + self.fname, + type="string") + + nodes = [root, volume_node] + self[:] = nodes + + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__) From e82f524687015291192683467718ff1b7c97daf5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 12:52:45 +0200 Subject: [PATCH 1033/1249] Added doc string --- colorbleed/plugins/maya/load/load_vdb_to_redshift.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/load/load_vdb_to_redshift.py b/colorbleed/plugins/maya/load/load_vdb_to_redshift.py index faaf2314e5..bafb529bda 100644 --- a/colorbleed/plugins/maya/load/load_vdb_to_redshift.py +++ b/colorbleed/plugins/maya/load/load_vdb_to_redshift.py @@ -2,6 +2,7 @@ from avalon import api class LoadVDBtoRedShift(api.Loader): + """Load OpenVDB in a Redshift Volume Shape""" families = ["colorbleed.vdbcache"] representations = ["vdb"] From cadb916df9a1b980cb8dff3734b002c31f6b92e4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 14:55:34 +0200 Subject: [PATCH 1034/1249] Fixed fetching parent widget for scene inventory, removed arg and comment --- colorbleed/houdini/__init__.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index f70b2d6198..7e8284e4aa 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -29,8 +29,6 @@ log = logging.getLogger("colorbleed.houdini") def install(): - # Set - pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -50,7 +48,7 @@ def on_init(_): houdini.on_houdini_initialize() -def on_save(_): +def on_save(): avalon.logger.info("Running callback on save..") @@ -66,15 +64,12 @@ def on_open(): update_task_from_path(hou.hipFile.path()) if any_outdated(): - from avalon.vendor.Qt import QtWidgets from ..widgets import popup log.warning("Scene has outdated content.") - # Find maya main window - top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} - parent = top_level_widgets.get("MayaWindow", None) + # Get main window + parent = hou.ui.mainQtWindow() if parent is None: log.info("Skipping outdated content pop-up " @@ -96,4 +91,4 @@ def on_open(): def on_task_changed(*args): """Wrapped function of app initialize and maya's on task changed""" - pass \ No newline at end of file + pass From c3d2453ee7780624232f08f66d0b1da84a6623cc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 19 Sep 2018 15:00:19 +0200 Subject: [PATCH 1035/1249] Removed unused function --- colorbleed/houdini/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 7e8284e4aa..4025ef731a 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -87,8 +87,3 @@ def on_open(): "your Maya scene.") dialog.on_show.connect(_on_show_inventory) dialog.show() - - -def on_task_changed(*args): - """Wrapped function of app initialize and maya's on task changed""" - pass From 9c6642c25bc83563372d15730ba5190b5bcdc864 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 21 Sep 2018 11:31:08 +0200 Subject: [PATCH 1036/1249] Remove file node, add normal on points, minor improvements --- .../plugins/houdini/load/load_alembic.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index 94e3894d05..b8a7949acf 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -42,8 +42,8 @@ class AbcLoader(api.Loader): container = obj.createNode("geo", node_name=node_name) # Remove the file node, it only loads static meshes - node_path = "/obj/{}/file1".format(node_name) - hou.node(node_path) + file_node = hou.node("/obj/{}/file1".format(node_name)) + file_node.destroy() # Create an alembic node (supports animation) alembic = container.createNode("alembic", node_name=node_name) @@ -54,18 +54,23 @@ class AbcLoader(api.Loader): unpack.setInput(0, alembic) unpack.setParms({"transfer_attributes": "path"}) - # Set new position for unpack node else it gets cluttered - unpack.setPosition([0, -1]) - # set unpack as display node unpack.setDisplayFlag(True) - null_node = container.createNode("null", - node_name="OUT_{}".format(name)) - null_node.setPosition([0, -2]) - null_node.setInput(0, unpack) + # Add normal to points + # normal types order ['point', 'vertex', 'prim', 'detail'] + normal_node = container.createNode("normal") + normal_node.setParms({"type": 0}) - nodes = [container, alembic, unpack, null_node] + normal_node.setInput(0, unpack) + + null = container.createNode("null", node_name="OUT_{}".format(name)) + null.setInput(0, normal_node) + + # Set new position for unpack node else it gets cluttered + nodes = [container, alembic, unpack, normal_node, null] + for nr, node in enumerate(nodes): + node.setPosition([0, (0 - nr)]) self[:] = nodes From f8c05c7c4f74372da0bd013572b553ffa7b2053f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 21 Sep 2018 17:09:19 +0200 Subject: [PATCH 1037/1249] fixed bug with None being passed as value --- colorbleed/plugins/maya/load/load_yeti_cache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/colorbleed/plugins/maya/load/load_yeti_cache.py index c3a3cdfefe..5a1e404975 100644 --- a/colorbleed/plugins/maya/load/load_yeti_cache.py +++ b/colorbleed/plugins/maya/load/load_yeti_cache.py @@ -284,6 +284,8 @@ class YetiCacheLoader(api.Loader): # Apply attributes to pgYetiMaya node for attr, value in attributes.items(): + if value is None: + continue lib.set_attribute(attr, value, yeti_node) # Fix for : YETI-6 From 2864682a909fa2009041175b9af4c912f8f3e3a6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 24 Sep 2018 10:53:52 +0200 Subject: [PATCH 1038/1249] Improved naming of utility nodes, set last node to display --- colorbleed/plugins/houdini/load/load_alembic.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index b8a7949acf..ca5741ecc3 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -50,23 +50,25 @@ class AbcLoader(api.Loader): alembic.setParms({"fileName": file_path}) # Add unpack node - unpack = container.createNode("unpack") + unpack_name = "unpack_{}".format(name) + unpack = container.createNode("unpack", node_name=unpack_name) unpack.setInput(0, alembic) unpack.setParms({"transfer_attributes": "path"}) - # set unpack as display node - unpack.setDisplayFlag(True) - # Add normal to points - # normal types order ['point', 'vertex', 'prim', 'detail'] - normal_node = container.createNode("normal") + # Order of menu ['point', 'vertex', 'prim', 'detail'] + normal_name = "normal_{}".format(name) + normal_node = container.createNode("normal", node_name=normal_name) normal_node.setParms({"type": 0}) normal_node.setInput(0, unpack) - null = container.createNode("null", node_name="OUT_{}".format(name)) + null = container.createNode("null", node_name="OUT".format(name)) null.setInput(0, normal_node) + # Set display on last node + null.setDisplayFlag(True) + # Set new position for unpack node else it gets cluttered nodes = [container, alembic, unpack, normal_node, null] for nr, node in enumerate(nodes): From 9f7b85372dd205ff15346f32c3e0d8f1dbebded1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 24 Sep 2018 13:57:26 +0200 Subject: [PATCH 1039/1249] Fix #142 - allow to ignore menu install on missing "scriptsmenu" dependency --- colorbleed/maya/menu.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/menu.py b/colorbleed/maya/menu.py index 1e411e1c78..9223271d65 100644 --- a/colorbleed/maya/menu.py +++ b/colorbleed/maya/menu.py @@ -23,10 +23,15 @@ def _get_menu(): def deferred(): - import scriptsmenu.launchformaya as launchformaya - import scriptsmenu.scriptsmenu as scriptsmenu + log.info("Attempting to install scripts menu..") - log.info("Attempting to install ...") + try: + import scriptsmenu.launchformaya as launchformaya + import scriptsmenu.scriptsmenu as scriptsmenu + except ImportError: + log.warning("Skipping colorbleed.menu install, because " + "'scriptsmenu' module seems unavailable.") + return # load configuration of custom menu config_path = os.path.join(os.path.dirname(__file__), "menu.json") @@ -44,7 +49,7 @@ def uninstall(): menu = _get_menu() if menu: - log.info("Attempting to uninstall ..") + log.info("Attempting to uninstall..") try: menu.deleteLater() From 8c8e3789fbfa30a9c4b648018ee0614f2f568b46 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 25 Sep 2018 11:43:46 +0200 Subject: [PATCH 1040/1249] Throw warning instead of assertion error --- colorbleed/plugins/maya/publish/collect_animation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/colorbleed/plugins/maya/publish/collect_animation.py index 53251cadc3..c724759180 100644 --- a/colorbleed/plugins/maya/publish/collect_animation.py +++ b/colorbleed/plugins/maya/publish/collect_animation.py @@ -29,8 +29,11 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): out_set = next((i for i in instance.data["setMembers"] if i.endswith("out_SET")), None) - assert out_set, ("Expecting out_SET for instance of family" - " '%s'" % family) + if out_set is None: + warning = "Expecting out_SET for instance of family '%s'" % family + self.log.warning(warning) + return + members = cmds.ls(cmds.sets(out_set, query=True), long=True) # Get all the relatives of the members From 5bc4a2bb9f07dc236c0816bb58a943a7aaa091f1 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 25 Sep 2018 12:10:26 +0200 Subject: [PATCH 1041/1249] Added check for out set --- .../plugins/maya/publish/validate_animation_content.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index 0725281705..da1ec9e84a 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -20,6 +20,12 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): def get_invalid(cls, instance): assert 'out_hierarchy' in instance.data, "Missing `out_hierarchy` data" + out_set = next((i for i in instance.data["setMembers"] if + i.endswith("out_SET")), None) + + assert out_set, ("Instance '%s' has no objectSet named: `OUT_set`" % + instance.name) + # All nodes in the `out_hierarchy` must be among the nodes that are # in the instance. The nodes in the instance are found from the top # group, as such this tests whether all nodes are under that top group. From 35923049263db00cef2023f23a4a0da8ce8b8de0 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 25 Sep 2018 12:12:42 +0200 Subject: [PATCH 1042/1249] Improved error message --- .../plugins/maya/publish/validate_animation_content.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index da1ec9e84a..c5cc53ba2e 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -23,8 +23,10 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): out_set = next((i for i in instance.data["setMembers"] if i.endswith("out_SET")), None) - assert out_set, ("Instance '%s' has no objectSet named: `OUT_set`" % - instance.name) + assert out_set, ("Instance '%s' has no objectSet named: `OUT_set`." + "If this instance is an unloaded reference, " + "please deactivate by toggling the 'Active' attribute" + % instance.name) # All nodes in the `out_hierarchy` must be among the nodes that are # in the instance. The nodes in the instance are found from the top From 43a9305984d95b0deaf294c53943e56c95857cb5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 25 Sep 2018 12:18:00 +0200 Subject: [PATCH 1043/1249] Changed order, out_hierarchy is dependend on out_SET --- colorbleed/plugins/maya/publish/validate_animation_content.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index c5cc53ba2e..cce7732803 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -18,7 +18,6 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - assert 'out_hierarchy' in instance.data, "Missing `out_hierarchy` data" out_set = next((i for i in instance.data["setMembers"] if i.endswith("out_SET")), None) @@ -28,6 +27,8 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): "please deactivate by toggling the 'Active' attribute" % instance.name) + assert 'out_hierarchy' in instance.data, "Missing `out_hierarchy` data" + # All nodes in the `out_hierarchy` must be among the nodes that are # in the instance. The nodes in the instance are found from the top # group, as such this tests whether all nodes are under that top group. From 00dad9a4f5f3c4d6d5def065d8a4c5826d34107c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 25 Sep 2018 12:21:20 +0200 Subject: [PATCH 1044/1249] Fixed space in error message --- colorbleed/plugins/maya/publish/validate_animation_content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index cce7732803..efa7d07c8e 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -22,7 +22,7 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): out_set = next((i for i in instance.data["setMembers"] if i.endswith("out_SET")), None) - assert out_set, ("Instance '%s' has no objectSet named: `OUT_set`." + assert out_set, ("Instance '%s' has no objectSet named: `OUT_set`. " "If this instance is an unloaded reference, " "please deactivate by toggling the 'Active' attribute" % instance.name) From 66e5471604db93bc66df4e117d4e74d504669041 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 27 Sep 2018 16:09:43 +0200 Subject: [PATCH 1045/1249] Optimize get_id (+/- 3x faster) and set_id (minor tweak) --- colorbleed/maya/lib.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index b1351cc19c..2ea618c178 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -11,6 +11,7 @@ import contextlib from collections import OrderedDict, defaultdict from maya import cmds, mel +import maya.api.OpenMaya as om from avalon import api, maya, io, pipeline from avalon.vendor.six import string_types @@ -764,10 +765,20 @@ def get_id(node): if node is None: return - if not cmds.attributeQuery("cbId", node=node, exists=True): + sel = om.MSelectionList() + sel.add(node) + + api_node = sel.getDependNode(0) + fn = om.MFnDependencyNode(api_node) + + if not fn.hasAttribute("cbId"): return - return cmds.getAttr("{}.cbId".format(node)) + try: + return fn.findPlug("cbId", False).asString() + except RuntimeError: + log.warning("Failed to retrieve cbId on %s", node) + return def generate_ids(nodes, asset_id=None): @@ -828,7 +839,6 @@ def set_id(node, unique_id, overwrite=False): """ - attr = "{0}.cbId".format(node) exists = cmds.attributeQuery("cbId", node=node, exists=True) # Add the attribute if it does not exist yet @@ -837,6 +847,7 @@ def set_id(node, unique_id, overwrite=False): # Set the value if not exists or overwrite: + attr = "{0}.cbId".format(node) cmds.setAttr(attr, unique_id, type="string") From ddefd09bc2cdf1d7ee87f761c073dcabe959888a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 10:45:40 +0200 Subject: [PATCH 1046/1249] added support for writing vertex colors --- colorbleed/plugins/maya/create/colorbleed_model.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index b55b3dc3dd..d4fa5c5a3d 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -1,3 +1,5 @@ +from collections import OrderedDict + import avalon.maya @@ -8,3 +10,14 @@ class CreateModel(avalon.maya.Creator): label = "Model" family = "colorbleed.model" icon = "cube" + + def __init__(self, *args, **kwargs): + super(CreateModel, self).__init__(*args, **kwargs) + + # create an ordered dict with the existing data first + data = OrderedDict(**self.data) + + # Write vertex colors with the geometry. + data["writeColorSets"] = False + + self.data = data From 21bbcc200a7d03a63dc5d43e46b383d08dffb757 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 10:47:48 +0200 Subject: [PATCH 1047/1249] Set default value to True --- colorbleed/plugins/maya/create/colorbleed_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index d4fa5c5a3d..c69e4bbf7b 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -18,6 +18,6 @@ class CreateModel(avalon.maya.Creator): data = OrderedDict(**self.data) # Write vertex colors with the geometry. - data["writeColorSets"] = False + data["writeColorSets"] = True self.data = data From 307c635e918acb91638b0fad81c78fa36aa03fd3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 10:51:34 +0200 Subject: [PATCH 1048/1249] disabled log, gave error on missing keys when exporting static frame --- colorbleed/plugins/houdini/publish/extract_vdb_cache.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index ad85a5daf0..4721486131 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -31,9 +31,9 @@ class ExtractVDBCache(colorbleed.api.Extractor): file_name.replace(frame_nr, "%04d") # We run the render - self.log.info( - "Starting render: {startFrame} - {endFrame}".format(**instance.data) - ) + #self.log.info( + # "Starting render: {startFrame} - {endFrame}".format(**instance.data) + #) ropnode.render() if "files" not in instance.data: From ebea792845475101ff993e2bf08e1c69e8705a5b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 10:53:51 +0200 Subject: [PATCH 1049/1249] cosmetic --- colorbleed/plugins/houdini/publish/collection_animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collection_animation.py b/colorbleed/plugins/houdini/publish/collection_animation.py index 835e52eb68..60204d0eec 100644 --- a/colorbleed/plugins/houdini/publish/collection_animation.py +++ b/colorbleed/plugins/houdini/publish/collection_animation.py @@ -25,7 +25,7 @@ class CollectAnimation(pyblish.api.InstancePlugin): "f2": "endFrame", "f3": "steps"} - data = {name: node.parm(par).eval() for par, name in + data = {name: node.evalParm(parm) for parm, name in parameters.items()} instance.data.update(data) From 48d01aea92cb03c913e77ca327286a56bac9b771 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 15:18:31 +0200 Subject: [PATCH 1050/1249] Use $HIP/pyblsih as output directory --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 2 +- colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 43213f46b0..aee47a55ad 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -27,7 +27,7 @@ class CreateAlembicCamera(houdini.Creator): parms = {"use_sop_path": True, "build_from_path": True, "path_attrib": "path", - "filename": "$HIP/%s.abc" % self.name} + "filename": "$HIP/pyblish/%s.abc" % self.name} if self.nodes: node = self.nodes[0] diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 25671c787e..03c7fc3deb 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -28,7 +28,7 @@ class CreatePointCache(houdini.Creator): parms = {"use_sop_path": True, "build_from_path": True, "path_attrib": "path", - "filename": "$HIP/%s.abc" % self.name} + "filename": "$HIP/pyblish/%s.abc" % self.name} if self.nodes: node = self.nodes[0] diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index b103a046fa..0c26e68053 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -25,7 +25,7 @@ class CreateVDBCache(houdini.Creator): def process(self): instance = super(CreateVDBCache, self).process() - parms = {"sopoutput": "$HIP/geo/%s.$F4.vdb" % self.name} + parms = {"sopoutput": "$HIP/pyblish/%s.$F4.vdb" % self.name} if self.nodes: parms.update({"soppath": self.nodes[0].path()}) From cb3f8fcad99b7efcfbc191254cdbf2da7fc513c5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 28 Sep 2018 18:01:55 +0200 Subject: [PATCH 1051/1249] Added support for executeBackground --- .../houdini/create/create_vbd_cache.py | 13 +++----- .../houdini/publish/extract_vdb_cache.py | 31 ++++++++++++++----- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 0c26e68053..325ec32cbe 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from avalon import houdini @@ -14,13 +12,10 @@ class CreateVDBCache(houdini.Creator): def __init__(self, *args, **kwargs): super(CreateVDBCache, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) - - # Set node type to create for output - data["node_type"] = "geometry" - - self.data = data + self.data.update({ + "node_type": "geometry", # Set node type to create for output + "executeBackground": True # Render node in background + }) def process(self): instance = super(CreateVDBCache, self).process() diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index 4721486131..ed9e211f63 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -4,6 +4,8 @@ import re import pyblish.api import colorbleed.api +import hou + class ExtractVDBCache(colorbleed.api.Extractor): @@ -17,9 +19,9 @@ class ExtractVDBCache(colorbleed.api.Extractor): ropnode = instance[0] # Get the filename from the filename parameter - # `.eval()` will make sure all tokens are resolved - output = ropnode.parm("sopoutput").eval() - staging_dir = os.path.dirname(output) + # `.evalParm(parameter)` will make sure all tokens are resolved + output = ropnode.evalParm("sopoutput") + staging_dir = os.path.normpath(os.path.dirname(output)) instance.data["stagingDir"] = staging_dir # Replace the 4 digits to match file sequence token '%04d' if we have @@ -31,10 +33,25 @@ class ExtractVDBCache(colorbleed.api.Extractor): file_name.replace(frame_nr, "%04d") # We run the render - #self.log.info( - # "Starting render: {startFrame} - {endFrame}".format(**instance.data) - #) - ropnode.render() + start_frame = instance.data.get("startFrame", None) + end_frame = instance.data.get("endFrame", None) + if all(f for f in [start_frame, end_frame]): + self.log.info( + "Starting render: {} - {}".format(start_frame, end_frame) + ) + + # Ensure output folder exists + if not os.path.isdir(staging_dir): + os.makedirs(staging_dir) + + assert os.path.exists(staging_dir) + + if instance.data.get("executeBackground", True): + self.log.info("Creating background task..") + ropnode.parm("executebackground").pressButton() + self.log.info("Finished") + else: + ropnode.render() if "files" not in instance.data: instance.data["files"] = [] From 2bad4947abe41deb341a2654f6ab1350a016b54e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 1 Oct 2018 15:39:31 +0200 Subject: [PATCH 1052/1249] Added support for parameter callbacks --- colorbleed/houdini/lib.py | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index cf526ba2b9..949d970841 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -95,6 +95,59 @@ def get_additional_data(container): return container +def set_parameter_callback(node, parameter, language, callback): + """Link a callback to a parameter of a node + + Args: + node(hou.Node): instance of the nodee + parameter(str): name of the parameter + language(str): name of the language, e.g.: python + callback(str): command which needs to be triggered + + Returns: + None + + """ + + template_grp = node.parmTemplateGroup() + template = template_grp.find(parameter) + if not template: + return + + script_language = (hou.scriptLanguage.Python if language == "python" else + hou.scriptLanguage.Hscript) + + template.setScriptCallbackLanguage(script_language) + template.setScriptCallback(callback) + + template.setTags({"script_callback": callback, + "script_callback_language": language.lower()}) + + # Replace the existing template with the adjusted one + template_grp.replace(parameter, template) + + node.setParmTemplateGroup(template_grp) + + +def set_parameter_callbacks(node, parameter_callbacks): + """Set callbacks for multiple parameters of a node + + Args: + node(hou.Node): instance of a hou.Node + parameter_callbacks(dict): collection of parameter and callback data + example: {"active" : + {"language": "python", + "callback": "print('hello world)'"} + } + Returns: + None + """ + for parameter, data in parameter_callbacks.items(): + language = data["language"] + callback = data["callback"] + + set_parameter_callback(node, parameter, language, callback) + @contextmanager def attribute_values(node, data): From 2856dbee8706569588271929ed224e4136bb3d4f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 1 Oct 2018 16:07:21 +0200 Subject: [PATCH 1053/1249] Updated instances --- .../plugins/houdini/create/create_alembic_camera.py | 8 +++----- colorbleed/plugins/houdini/create/create_pointcache.py | 9 +++------ colorbleed/plugins/houdini/create/create_vbd_cache.py | 3 +++ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index aee47a55ad..6d35e3bd02 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -13,13 +13,11 @@ class CreateAlembicCamera(houdini.Creator): def __init__(self, *args, **kwargs): super(CreateAlembicCamera, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) # Set node type to create for output - data["node_type"] = "alembic" - - self.data = data + self.data.update({"node_type": "alembic"}) def process(self): instance = super(CreateAlembicCamera, self).process() diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 03c7fc3deb..fdc5ff3598 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -14,13 +14,10 @@ class CreatePointCache(houdini.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) - # Set node type to create for output - data["node_type"] = "alembic" - - self.data = data + self.data.update({"node_type": "alembic"}) def process(self): instance = super(CreatePointCache, self).process() diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 325ec32cbe..cf84b10742 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -12,6 +12,9 @@ class CreateVDBCache(houdini.Creator): def __init__(self, *args, **kwargs): super(CreateVDBCache, self).__init__(*args, **kwargs) + # Remove the active, we are checking the bypass flag of the nodes + self.data.pop("active", None) + self.data.update({ "node_type": "geometry", # Set node type to create for output "executeBackground": True # Render node in background From 9ae437e2dfde03369ac61ecdcd987d41e40417fd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 1 Oct 2018 16:10:30 +0200 Subject: [PATCH 1054/1249] Updated method of getting parameter values. Check active state with `not isBypassed` --- colorbleed/plugins/houdini/publish/collect_instances.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index effd6e392e..387c587b25 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -38,13 +38,15 @@ class CollectInstances(pyblish.api.ContextPlugin): if not node.parm("id"): continue - if node.parm("id").eval() != "pyblish.avalon.instance": + if node.evalParm("id") != "pyblish.avalon.instance": continue - has_family = node.parm("family").eval() + has_family = node.evalParm("family") assert has_family, "'%s' is missing 'family'" % node.name() data = lib.read(node) + # Check bypass state and reverse + data.update({"active": not node.isBypassed}) # temporarily translation of `active` to `publish` till issue has # been resolved, https://github.com/pyblish/pyblish-base/issues/307 From 381a955b3ffd30f2d9723fa5d0cd9385842969ea Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 1 Oct 2018 16:13:54 +0200 Subject: [PATCH 1055/1249] Added missing parentheses --- colorbleed/plugins/houdini/publish/collect_instances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 387c587b25..61d4cdbe0b 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -46,7 +46,7 @@ class CollectInstances(pyblish.api.ContextPlugin): data = lib.read(node) # Check bypass state and reverse - data.update({"active": not node.isBypassed}) + data.update({"active": not node.isBypassed()}) # temporarily translation of `active` to `publish` till issue has # been resolved, https://github.com/pyblish/pyblish-base/issues/307 From 48ed86a1fb150267bbc8761fae30e2ff453ac3ce Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:44:58 +0200 Subject: [PATCH 1056/1249] Added validator for vdb animation settings --- .../publish/validate_animation_settings.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_animation_settings.py diff --git a/colorbleed/plugins/houdini/publish/validate_animation_settings.py b/colorbleed/plugins/houdini/publish/validate_animation_settings.py new file mode 100644 index 0000000000..92955efbe8 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_animation_settings.py @@ -0,0 +1,50 @@ +import pyblish.api + +from colorbleed.houdini import lib + + +class ValidateAnimationSettings(pyblish.api.InstancePlugin): + """Validate if the unexpanded string contains the frame ('$F') token + + This validator will only check the output parameter of the node if + the Valid Frame Range is not set to 'Render Current Frame' + + Rules: + If you render out a frame range it is mandatory to have the + frame token - '$F4' or similar - to ensure that each frame gets + written. If this is not the case you will override the same file + every time a frame is written out. + + Examples: + Good: 'my_vbd_cache.$F4.vdb' + Bad: 'my_vbd_cache.vdb' + + """ + + order = pyblish.api.ValidatorOrder + label = "Validate Frame Settings" + families = ["colorbleed.vdbcache"] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Output settings do no match for '%s'" % + instance) + + @classmethod + def get_invalid(cls, instance): + + node = instance[0] + + # Check trange parm, 0 means Render Current Frame + frame_range = node.evalParm("trange") + if frame_range == 0: + return [] + + output_parm = lib.get_output_parameter(node) + unexpanded_str = output_parm.unexpandedString() + + if "$F" not in unexpanded_str: + cls.log.error("No frame token found in '%s'" % node.path()) + return [instance] From 455a7dbf60eebb76dcbc6fc83716ae7c660c4965 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:45:39 +0200 Subject: [PATCH 1057/1249] Added estamated file list for vdbcache --- .../plugins/houdini/publish/collect_frames.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collect_frames.py diff --git a/colorbleed/plugins/houdini/publish/collect_frames.py b/colorbleed/plugins/houdini/publish/collect_frames.py new file mode 100644 index 0000000000..d08162d332 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_frames.py @@ -0,0 +1,72 @@ +import os +import re + +import pyblish.api + + +class CollectFrames(pyblish.api.InstancePlugin): + """Collect all frames which would be a resukl""" + + order = pyblish.api.CollectorOrder + label = "Collect Frames" + families = ["colorbleed.vdbcache"] + + def process(self, instance): + + ropnode = instance[0] + + node_type = ropnode.type().name() + if node_type == "geometry": + output_parm = "sopoutput" + elif node_type == "alembic": + output_parm = "filename" + else: + raise TypeError("Node type '%s' not supported" % node_type) + + output = ropnode.evalParm(output_parm) + + file_name = os.path.basename(output) + match = re.match("(\w+)\.(\d+)\.vdb", file_name) + result = file_name + + start_frame = instance.data.get("startFrame", None) + end_frame = instance.data.get("endFrame", None) + + if match and start_frame is not None: + + # Check if frames are bigger than 1 (file collection) + # override the result + if end_frame - start_frame > 1: + result = self.create_file_list(match, + int(start_frame), + int(end_frame)) + + instance.data.update({"frames": result}) + + def create_file_list(self, match, start_frame, end_frame): + """Collect files based on frame range and regex.match + + Args: + match(re.match): match object + start_frame(int): start of the animation + end_frame(int): end of the animation + + Returns: + list + + """ + + result = [] + + padding = len(match.group(2)) + name = match.group(1) + padding_format = "{number:0{width}d}" + + count = start_frame + while count <= end_frame: + str_count = padding_format.format(number=count, width=padding) + file_name = "{}.{}.vdb".format(name, str_count) + result.append(file_name) + count += 1 + + return result From 01bf82484565d81a5790ebbaf44639bb024106c5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:46:08 +0200 Subject: [PATCH 1058/1249] Added comments about settings, force format on creation --- colorbleed/plugins/houdini/create/create_pointcache.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index fdc5ff3598..698594796d 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from avalon import houdini @@ -22,9 +20,10 @@ class CreatePointCache(houdini.Creator): def process(self): instance = super(CreatePointCache, self).process() - parms = {"use_sop_path": True, - "build_from_path": True, - "path_attrib": "path", + parms = {"use_sop_path": True, # Export single node from SOP Path + "build_from_path": True, # Direct path of primitive in output + "path_attrib": "path", # Pass path attribute for output + "format": 2, # Set format to Ogawa "filename": "$HIP/pyblish/%s.abc" % self.name} if self.nodes: From 447029a6a665de5f4de1b0fc57524e29fd8a5103 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:46:52 +0200 Subject: [PATCH 1059/1249] Moved chunk for file collection to collector --- .../houdini/publish/extract_vdb_cache.py | 33 +++---------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index ed9e211f63..a489eb2dbb 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -1,11 +1,8 @@ import os -import re import pyblish.api import colorbleed.api -import hou - class ExtractVDBCache(colorbleed.api.Extractor): @@ -20,32 +17,10 @@ class ExtractVDBCache(colorbleed.api.Extractor): # Get the filename from the filename parameter # `.evalParm(parameter)` will make sure all tokens are resolved - output = ropnode.evalParm("sopoutput") - staging_dir = os.path.normpath(os.path.dirname(output)) + sop_output = ropnode.evalParm("sopoutput") + staging_dir = os.path.normpath(os.path.dirname(sop_output)) instance.data["stagingDir"] = staging_dir - # Replace the 4 digits to match file sequence token '%04d' if we have - # a sequence of frames - file_name = os.path.basename(output) - has_frame = re.match("\w\.(d+)\.vdb", file_name) - if has_frame: - frame_nr = has_frame.group() - file_name.replace(frame_nr, "%04d") - - # We run the render - start_frame = instance.data.get("startFrame", None) - end_frame = instance.data.get("endFrame", None) - if all(f for f in [start_frame, end_frame]): - self.log.info( - "Starting render: {} - {}".format(start_frame, end_frame) - ) - - # Ensure output folder exists - if not os.path.isdir(staging_dir): - os.makedirs(staging_dir) - - assert os.path.exists(staging_dir) - if instance.data.get("executeBackground", True): self.log.info("Creating background task..") ropnode.parm("executebackground").pressButton() @@ -56,4 +31,6 @@ class ExtractVDBCache(colorbleed.api.Extractor): if "files" not in instance.data: instance.data["files"] = [] - instance.data["files"].append(file_name) + output = instance.data["frames"] + + instance.data["files"].append(output) From 32936a19ab36ed974427a9a2084e68fae668a722 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:47:11 +0200 Subject: [PATCH 1060/1249] Added get_output_parameter function --- colorbleed/houdini/lib.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index 949d970841..78adbc5790 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -148,6 +148,35 @@ def set_parameter_callbacks(node, parameter_callbacks): set_parameter_callback(node, parameter, language, callback) + +def get_output_parameter(node): + """Return the render output parameter name of the given node + + Example: + root = hou.node("/obj") + my_alembic_node = root.createNode("alembic") + get_output_parameter(my_alembic_node) + # Result: "output" + + Args: + node(hou.Node): node instance + + Returns: + hou.Parm + + """ + + node_type = node.type().name() + if node_type == "geometry": + return node.parm("sopoutput") + + elif node_type == "alembic": + return node.parm("filename") + + else: + raise TypeError("Node type '%s' not supported" % node_type) + + @contextmanager def attribute_values(node, data): From b3de7e2ec5394a01e5fc102208e7a63c57b1c78f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:48:56 +0200 Subject: [PATCH 1061/1249] Updated plugin --- colorbleed/plugins/houdini/publish/collect_frames.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_frames.py b/colorbleed/plugins/houdini/publish/collect_frames.py index d08162d332..49fa7378c5 100644 --- a/colorbleed/plugins/houdini/publish/collect_frames.py +++ b/colorbleed/plugins/houdini/publish/collect_frames.py @@ -2,6 +2,7 @@ import os import re import pyblish.api +from colorbleed.houdini import lib class CollectFrames(pyblish.api.InstancePlugin): @@ -15,15 +16,8 @@ class CollectFrames(pyblish.api.InstancePlugin): ropnode = instance[0] - node_type = ropnode.type().name() - if node_type == "geometry": - output_parm = "sopoutput" - elif node_type == "alembic": - output_parm = "filename" - else: - raise TypeError("Node type '%s' not supported" % node_type) - - output = ropnode.evalParm(output_parm) + output_parm = lib.get_output_parameter(ropnode) + output = output_parm.eval() file_name = os.path.basename(output) match = re.match("(\w+)\.(\d+)\.vdb", file_name) From 6ab87afd9c5c8e6679f7bbf3cda4f473c8bead4c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:50:14 +0200 Subject: [PATCH 1062/1249] Fixed error message --- colorbleed/plugins/houdini/publish/validate_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 18fcb4898f..d3393e1c33 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -33,7 +33,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): # Check if type is correct type_name = output_node.type().name() if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is an accepted type `output` " + cls.log.error("Output node `%s` is not an accepted type `output` " "or `camera`" % output_node.path()) return [output_node.path()] From b751dd3a1585583a71668982c4549fb14e4885d3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 10:51:22 +0200 Subject: [PATCH 1063/1249] Minor improvement --- colorbleed/plugins/houdini/load/load_alembic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index ca5741ecc3..235d316c05 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -42,7 +42,7 @@ class AbcLoader(api.Loader): container = obj.createNode("geo", node_name=node_name) # Remove the file node, it only loads static meshes - file_node = hou.node("/obj/{}/file1".format(node_name)) + file_node = container.node("file1".format(node_name)) file_node.destroy() # Create an alembic node (supports animation) From ba482725bff60ce4cb871d21f9185287e8005b76 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 11:23:02 +0200 Subject: [PATCH 1064/1249] Toggle initsim at creation --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index cf84b10742..5a52f6da7f 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -23,7 +23,9 @@ class CreateVDBCache(houdini.Creator): def process(self): instance = super(CreateVDBCache, self).process() - parms = {"sopoutput": "$HIP/pyblish/%s.$F4.vdb" % self.name} + parms = {"sopoutput": "$HIP/pyblish/%s.$F4.vdb" % self.name, + "initsim": True} + if self.nodes: parms.update({"soppath": self.nodes[0].path()}) From 343799731e954a3dfff61159c0762ceb2f1eecf7 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 2 Oct 2018 11:23:57 +0200 Subject: [PATCH 1065/1249] Removed unused modules --- .../houdini/publish/collect_alembic_nodes.py | 9 ------ .../houdini/publish/collection_animation.py | 31 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 colorbleed/plugins/houdini/publish/collect_alembic_nodes.py delete mode 100644 colorbleed/plugins/houdini/publish/collection_animation.py diff --git a/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py b/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py deleted file mode 100644 index dc0de42126..0000000000 --- a/colorbleed/plugins/houdini/publish/collect_alembic_nodes.py +++ /dev/null @@ -1,9 +0,0 @@ -import pyblish.api - - -class CollectAlembicNodes(pyblish.api.InstancePlugin): - - label = "Collect Alembic Nodes" - - def process(self, instance): - pass \ No newline at end of file diff --git a/colorbleed/plugins/houdini/publish/collection_animation.py b/colorbleed/plugins/houdini/publish/collection_animation.py deleted file mode 100644 index 60204d0eec..0000000000 --- a/colorbleed/plugins/houdini/publish/collection_animation.py +++ /dev/null @@ -1,31 +0,0 @@ -import pyblish.api - - -class CollectAnimation(pyblish.api.InstancePlugin): - """Collect the animation data for the data base - - Data collected: - - start frame - - end frame - - nr of steps - - """ - - order = pyblish.api.CollectorOrder - families = ["colorbleed.pointcache"] - hosts = ["houdini"] - label = "Collect Animation" - - def process(self, instance): - - node = instance[0] - - # Get animation parameters for data - parameters = {"f1": "startFrame", - "f2": "endFrame", - "f3": "steps"} - - data = {name: node.evalParm(parm) for parm, name in - parameters.items()} - - instance.data.update(data) From abf79377b56efc38d49516f117565eca07ce070c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Oct 2018 17:37:09 +0200 Subject: [PATCH 1066/1249] Implement draft for ValidateLookIdReferenceEdits (LKD-16) --- .../validate_look_id_reference_edits.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py diff --git a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py new file mode 100644 index 0000000000..3b5615c9f4 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py @@ -0,0 +1,44 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): + """Validate nodes in look have no reference edits to cbId.""" + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.look'] + hosts = ['maya'] + label = 'Look Id Reference Edits' + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("Invalid nodes %s" % (invalid,)) + + @staticmethod + def get_invalid(instance): + + # Collect all referenced members + referenced_nodes = [] + relationships = instance.data["lookData"]["relationships"] + for relationship in relationships.values(): + for member in relationship['members']: + node = member["name"] + + if cmds.referenceQuery(node, isNodeReferenced=True): + referenced_nodes.append(node) + + # Validate whether any has changes to 'cbId' attribute + # TODO: optimize this query (instead of per node) + invalid = list() + for node in referenced_nodes: + edits = set(cmds.referenceQuery(node, editAttrs=True)) + if "cbId" in edits: + invalid.append(node) + + return invalid From 3296583c814483e9097b6dd4c69464983444cc16 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 3 Oct 2018 12:10:09 +0200 Subject: [PATCH 1067/1249] Improve query speed, add repair functionality --- .../validate_look_id_reference_edits.py | 73 ++++++++++++++++--- 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py index 3b5615c9f4..b2608b1cdf 100644 --- a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py +++ b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py @@ -1,3 +1,4 @@ +from collections import defaultdict from maya import cmds import pyblish.api @@ -5,16 +6,23 @@ import colorbleed.api class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): - """Validate nodes in look have no reference edits to cbId.""" + """Validate nodes in look have no reference edits to cbId. + + Note: + This only validates the cbId edits on the referenced nodes that are + used in the look. For example, a transform can have its cbId changed + without being invalidated when it is not used in the look's assignment. + + """ order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Id Reference Edits' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] def process(self, instance): - invalid = self.get_invalid(instance) if invalid: @@ -24,21 +32,66 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): def get_invalid(instance): # Collect all referenced members - referenced_nodes = [] + references = defaultdict(set) relationships = instance.data["lookData"]["relationships"] for relationship in relationships.values(): for member in relationship['members']: node = member["name"] if cmds.referenceQuery(node, isNodeReferenced=True): - referenced_nodes.append(node) + ref = cmds.referenceQuery(node, referenceNode=True) + references[ref].add(node) # Validate whether any has changes to 'cbId' attribute - # TODO: optimize this query (instead of per node) invalid = list() - for node in referenced_nodes: - edits = set(cmds.referenceQuery(node, editAttrs=True)) - if "cbId" in edits: - invalid.append(node) + for ref, nodes in references.items(): + edits = cmds.referenceQuery(editAttrs=True, + editNodes=True, + showDagPath=True, + showNamespace=True, + onReferenceNode=ref) + for edit in edits: + + # Ensure it is an attribute ending with .cbId + # thus also ignore just node edits (like parenting) + if not edit.endswith(".cbId"): + continue + + # Ensure the attribute is 'cbId' (and not a nested attribute) + node, attr = edit.split(".", 1) + if attr != "cbId": + continue + + if node in nodes: + invalid.append(node) return invalid + + @classmethod + def repair(cls, instance): + + invalid = cls.get_invalid(instance) + + # Group invalid nodes by reference node + references = defaultdict(set) + for node in invalid: + ref = cmds.referenceQuery(node, referenceNode=True) + references[ref].add(node) + + # Remove the reference edits on the nodes per reference node + for ref, nodes in references.items(): + for node in nodes: + + # Somehow this only works if you run the the removal + # per edit command. + for command in ["addAttr", + "connectAttr", + "deleteAttr", + "disconnectAttr", + "setAttr"]: + cmds.referenceEdit("{}.cbId".format(node), + removeEdits=True, + successfulEdits=True, + failedEdits=True, + editCommand=command, + onReferenceNode=ref) From 792bae854bc04cb86121ae26c08165d71c309a47 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 3 Oct 2018 12:18:04 +0200 Subject: [PATCH 1068/1249] Removed out from the sop_path value --- colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 698594796d..481404dcec 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -28,6 +28,6 @@ class CreatePointCache(houdini.Creator): if self.nodes: node = self.nodes[0] - parms.update({"sop_path": "%s/OUT" % node.path()}) + parms.update({"sop_path": node.path()}) instance.setParms(parms) From cfe8548bdb6d8b9904969ae92d7216b611718425 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 3 Oct 2018 12:18:46 +0200 Subject: [PATCH 1069/1249] Consistency update --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 5a52f6da7f..aadd222d41 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -27,6 +27,7 @@ class CreateVDBCache(houdini.Creator): "initsim": True} if self.nodes: - parms.update({"soppath": self.nodes[0].path()}) + node = self.nodes[0] + parms.update({"sop_path": node.path()}) instance.setParms(parms) From 63ce0b8218f8747a3a15eddd532ce3b0eb8b134e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 3 Oct 2018 17:16:39 +0200 Subject: [PATCH 1070/1249] fixed bug in assertion --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 3198120374..feb7a19199 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -26,7 +26,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): def process(self, instance): - assert "input_SET" in cmds.sets(instance.name, query=True), ( + assert "input_SET" in instance.data["setMembers"], ( "Yeti Rig must have an input_SET") # Get the input meshes information From 3b1cd4743eec308176108f4b1cd145f0ca5c812d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 4 Oct 2018 10:17:48 +0200 Subject: [PATCH 1071/1249] ensure time slider state is maintained when updating FPS --- colorbleed/maya/lib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ea618c178..73794b4462 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1404,9 +1404,19 @@ def set_scene_fps(fps, update=True): else: raise ValueError("Unsupported FPS value: `%s`" % fps) + # Get time slider current state + start_frame = cmds.playbackOptions(query=True, minTime=True) + end_frame = cmds.playbackOptions(query=True, maxTime=True) + current_frame = cmds.currentTime(query=True) + log.info("Updating FPS to '{}'".format(unit)) cmds.currentUnit(time=unit, updateAnimation=update) + # Set time slider data back to previous state + cmds.playbackOptions(edit=True, minTime=start_frame) + cmds.playbackOptions(edit=True, maxTime=end_frame) + cmds.currentTime(current_frame, edit=True, update=True) + # Force file stated to 'modified' cmds.file(modified=True) From 8b17f5b6f3a0472a03780b0516a5f165c6a8ac7d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 4 Oct 2018 10:34:55 +0200 Subject: [PATCH 1072/1249] maintain animation start and end frames --- colorbleed/maya/lib.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 73794b4462..d4b0b8aa73 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1407,6 +1407,11 @@ def set_scene_fps(fps, update=True): # Get time slider current state start_frame = cmds.playbackOptions(query=True, minTime=True) end_frame = cmds.playbackOptions(query=True, maxTime=True) + + # Get animation data + animation_start = cmds.playbackOptions(query=True, animationStartTime=True) + animation_end = cmds.playbackOptions(query=True, animationEndTime=True) + current_frame = cmds.currentTime(query=True) log.info("Updating FPS to '{}'".format(unit)) @@ -1415,6 +1420,11 @@ def set_scene_fps(fps, update=True): # Set time slider data back to previous state cmds.playbackOptions(edit=True, minTime=start_frame) cmds.playbackOptions(edit=True, maxTime=end_frame) + + # Set animation data + cmds.playbackOptions(edit=True, animationStartTime=animation_start) + cmds.playbackOptions(edit=True, animationEndTime=animation_end) + cmds.currentTime(current_frame, edit=True, update=True) # Force file stated to 'modified' From 7114fcf6fc31ce564806e9d365a93e62503593e0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 4 Oct 2018 13:46:51 +0200 Subject: [PATCH 1073/1249] Implement MOD-10 validate default "map1" uv set --- .../maya/publish/validate_mesh_uv_set_map1.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py b/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py new file mode 100644 index 0000000000..878549b539 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py @@ -0,0 +1,90 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): + """Validate model's default set exists and is named 'map1'. + + In Maya meshes by default have a uv set named "map1" that cannot be + deleted. It can be renamed however, introducing some issues with some + renderers. As such we ensure the first (default) UV set index is named + "map1". + + """ + + order = colorbleed.api.ValidateMeshOrder + hosts = ['maya'] + families = ['colorbleed.model'] + optional = True + label = "Mesh has map1 UV Set" + actions = [colorbleed.api.SelectInvalidAction, + colorbleed.api.RepairAction] + + @staticmethod + def get_invalid(instance): + + meshes = cmds.ls(instance, type='mesh', long=True) + + invalid = [] + for mesh in meshes: + + # Get existing mapping of uv sets by index + indices = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) + maps = cmds.polyUVSet(mesh, query=True, allUVSets=True) + mapping = dict(zip(indices, maps)) + + # Get the uv set at index zero. + name = mapping[0] + if name != "map1": + invalid.append(mesh) + + return invalid + + def process(self, instance): + """Process all the nodes in the instance 'objectSet'""" + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Meshes found without 'map1' " + "UV set: {0}".format(invalid)) + + @classmethod + def repair(cls, instance): + """Rename uv map at index zero to map1""" + + for mesh in cls.get_invalid(instance): + + # Get existing mapping of uv sets by index + indices = cmds.polyUVSet(mesh, query=True, allUVSetsIndices=True) + maps = cmds.polyUVSet(mesh, query=True, allUVSets=True) + mapping = dict(zip(indices, maps)) + + # Ensure there is no uv set named map1 to avoid + # a clash on renaming the "default uv set" to map1 + existing = set(maps) + if "map1" in existing: + + # Find a unique name index + i = 2 + while True: + name = "map{0}".format(i) + if name not in existing: + break + i += 1 + + cls.log.warning("Renaming clashing uv set name on mesh" + " %s to '%s'", mesh, name) + + cmds.polyUVSet(mesh, + rename=True, + uvSet="map1", + newUVSet=name) + + # Rename the initial index to map1 + original = mapping[0] + cmds.polyUVSet(mesh, + rename=True, + uvSet=original, + newUVSet="map1") From 3ec2bb4993ce4fb916437019b9d34db57e334f04 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 09:57:49 +0200 Subject: [PATCH 1074/1249] Added validator for Yeti Rig Settings --- .../publish/validate_yeti_rig_settings.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py new file mode 100644 index 0000000000..8b6219d989 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py @@ -0,0 +1,38 @@ +import pyblish.api + + +class ValidateYetiRigSettings(pyblish.api.InstancePlugin): + order = pyblish.api.ValidatorOrder + label = "Validate Yeti Rig Settings" + families = ["colorbleed.yetiRig"] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Detected invalid Yeti Rig data. " + "Tip: Save the scene") + + @classmethod + def get_invalid(cls, instance): + + rigsettings = instance.data.get("rigsettings", {}) + if not rigsettings: + cls.log.error("MAJOR ERROR: No rig settings found!") + return True + + # Get inputs + inputs = rigsettings.get("inputs", []) + for input in inputs: + source_id = input["sourceID"] + if source_id is None: + cls.log.error("Discovered source with 'None' as ID, please " + "check if the input shape has an cbId") + return True + + destination_id = input["destinationID"] + if destination_id is None: + cls.log.error("Discovered None as destination ID value") + return True + + return False From b4e22263013a938ca48db035c4ca5150f2f405c1 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 10:05:03 +0200 Subject: [PATCH 1075/1249] Renamed function, improved code --- .../plugins/maya/publish/extract_yeti_rig.py | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 7806c0642f..6548bab60b 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -10,7 +10,7 @@ import colorbleed.maya.lib as maya @contextlib.contextmanager -def disconnected_attributes(settings, members): +def disconnect_plugs(settings, members): members = cmds.ls(members, long=True) original_connections = [] @@ -19,35 +19,32 @@ def disconnected_attributes(settings, members): # Get source shapes source_nodes = lib.lsattr("cbId", input["sourceID"]) - sources = [i for i in source_nodes if - not cmds.referenceQuery(i, isNodeReferenced=True) - and i in members] - try: - source = sources[0] - except IndexError: - print("source_id:", input["sourceID"]) + if not source_nodes: continue + source = next(s for s in source_nodes if s not in members) + # Get destination shapes (the shapes used as hook up) destination_nodes = lib.lsattr("cbId", input["destinationID"]) - destinations = [i for i in destination_nodes if i not in members - and i not in sources] - destination = destinations[0] + destination = next(i for i in destination_nodes if i in members) - # Break connection + # Create full connection connections = input["connections"] src_attribute = "%s.%s" % (source, connections[0]) dst_attribute = "%s.%s" % (destination, connections[1]) - # store connection pair + # Check if there is an actual connection if not cmds.isConnected(src_attribute, dst_attribute): + print("No connection between %s and %s" % ( + src_attribute, dst_attribute)) continue + # Break and store connection cmds.disconnectAttr(src_attribute, dst_attribute) original_connections.append([src_attribute, dst_attribute]) yield finally: - # restore connections + # Restore previous connections for connection in original_connections: try: cmds.connectAttr(connection[0], connection[1]) @@ -57,12 +54,7 @@ def disconnected_attributes(settings, members): class ExtractYetiRig(colorbleed.api.Extractor): - """Produce an alembic of just point positions and normals. - - Positions and normals are preserved, but nothing more, - for plain and predictable point caches. - - """ + """Extract the Yeti rig to a MayaAscii and write the Yeti rig data""" label = "Extract Yeti Rig" hosts = ["maya"] @@ -85,7 +77,7 @@ class ExtractYetiRig(colorbleed.api.Extractor): image_search_path = "" settings = instance.data.get("rigsettings", None) - if settings is not None: + if settings: # Create assumed destination folder for imageSearchPath assumed_temp_data = instance.data["assumedTemplateData"] @@ -100,18 +92,22 @@ class ExtractYetiRig(colorbleed.api.Extractor): with open(settings_path, "w") as fp: json.dump(settings, fp, ensure_ascii=False) + # Ensure the imageSearchPath is being remapped to the publish folder attr_value = {"%s.imageSearchPath" % n: str(image_search_path) for n in yeti_nodes} # Get input_SET members - input_set = [i for i in instance if i == "input_SET"] + input_set = next(i for i in instance if i == "input_SET") + # Get all items - set_members = cmds.sets(input_set[0], query=True) - members = cmds.listRelatives(set_members, ad=True, fullPath=True) or [] - members += cmds.ls(set_members, long=True) + set_members = cmds.sets(input_set, query=True) + set_members += cmds.listRelatives(set_members, + allDescendents=True, + fullPath=True) or [] + members = cmds.ls(set_members, long=True, objects=True) nodes = instance.data["setMembers"] - with disconnected_attributes(settings, members): + with disconnect_plugs(settings, members): with maya.attribute_values(attr_value): cmds.select(nodes, noExpand=True) cmds.file(maya_path, From f40f4a3bcbb019092ec69e2056b0bb67f7b1fe13 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 10:06:30 +0200 Subject: [PATCH 1076/1249] Ensure to get all nodes --- .../plugins/maya/publish/collect_yeti_rig.py | 19 ++++++++++--------- .../plugins/maya/publish/validate_node_ids.py | 3 ++- .../maya/publish/validate_node_ids_unique.py | 3 ++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index feb7a19199..e63bad48ca 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -30,16 +30,18 @@ class CollectYetiRig(pyblish.api.InstancePlugin): "Yeti Rig must have an input_SET") # Get the input meshes information - input_content = cmds.sets("input_SET", query=True) - input_nodes = cmds.listRelatives(input_content, - allDescendents=True, - fullPath=True) or input_content + input_content = cmds.ls(cmds.sets("input_SET", query=True), long=True) - # Get all the shapes - input_shapes = cmds.ls(input_nodes, long=True, noIntermediate=True) + # Include children + input_content += cmds.listRelatives(input_content, + allDescendents=True, + fullPath=True) or [] + + # Ignore intermediate objects + input_content = cmds.ls(input_content, long=True, noIntermediate=True) # Store all connections - connections = cmds.listConnections(input_shapes, + connections = cmds.listConnections(input_content, source=True, destination=False, connections=True, @@ -150,7 +152,6 @@ class CollectYetiRig(pyblish.api.InstancePlugin): if re.match(re_pattern, f)] pattern = [clique.PATTERNS["frames"]] - collection, remainder = clique.assemble(files, - patterns=pattern) + collection, remainder = clique.assemble(files, patterns=pattern) return collection diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 3bd4471788..22369ccb65 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -21,7 +21,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): "colorbleed.rig", "colorbleed.pointcache", "colorbleed.animation", - "colorbleed.setdress"] + "colorbleed.setdress", + "colorbleed.yetiRig"] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py index b8cbbd552d..61fed9faad 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py @@ -16,7 +16,8 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["colorbleed.model", "colorbleed.look", - "colorbleed.rig"] + "colorbleed.rig", + "colorbleed.yetiRig"] actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.GenerateUUIDsOnInvalidAction] From dcac192e9cc70ef81d997cb641889ae1179aeea0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 10:35:25 +0200 Subject: [PATCH 1077/1249] Validate V-Ray proxy frame range with generic frame range validator --- colorbleed/plugins/maya/publish/validate_frame_range.py | 3 ++- colorbleed/plugins/maya/publish/validate_vrayproxy.py | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/colorbleed/plugins/maya/publish/validate_frame_range.py index 010422df38..05b64ccf17 100644 --- a/colorbleed/plugins/maya/publish/validate_frame_range.py +++ b/colorbleed/plugins/maya/publish/validate_frame_range.py @@ -20,7 +20,8 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): families = ["colorbleed.animation", "colorbleed.pointcache", "colorbleed.camera", - "colorbleed.renderlayer"] + "colorbleed.renderlayer", + "oolorbleed.vrayproxy"] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy.py b/colorbleed/plugins/maya/publish/validate_vrayproxy.py index 40c45094db..f722dc6129 100644 --- a/colorbleed/plugins/maya/publish/validate_vrayproxy.py +++ b/colorbleed/plugins/maya/publish/validate_vrayproxy.py @@ -21,7 +21,3 @@ class ValidateVrayProxy(pyblish.api.InstancePlugin): if not data["setMembers"]: cls.log.error("'%s' is empty! This is a bug" % instance.name) - - if data["animation"]: - if data["endFrame"] < data["startFrame"]: - cls.log.error("End frame is smaller than start frame") From f8f8bd2ebf2852eb51f26c9ceff6a0b994878d3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 12:20:57 +0200 Subject: [PATCH 1078/1249] Keep a reference to the instance objectSet upon collection --- colorbleed/plugins/maya/publish/collect_instances.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index f23f0aa396..8ed6242036 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -115,7 +115,13 @@ class CollectInstances(pyblish.api.ContextPlugin): # Create the instance instance = context.create_instance(label) instance[:] = members_hierarchy + + # Store a reference to the object set of this instance + instance.data["objectSet"] = objset + + # Store the exact members of the object set instance.data["setMembers"] = members + instance.data.update(data) # Produce diagnostic message for any graphical From e0d7a61f7f555adfdc59ed598640279a9a165a03 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 14:20:26 +0200 Subject: [PATCH 1079/1249] Fix Validate VRay Proxy actually returning a value and invalidating something --- .../publish/validate_vrayproxy_members.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_vrayproxy_members.py diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py new file mode 100644 index 0000000000..84a29fbc12 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py @@ -0,0 +1,37 @@ +import pyblish.api +import colorbleed.api + +from maya import cmds + + +class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): + """Validate whether the V-Ray Proxy instance has shape members""" + + order = pyblish.api.ValidatorOrder + label = 'VRay Proxy Members' + hosts = ['maya'] + families = ['colorbleed.vrayproxy'] + actions = [colorbleed.api.SelectInvalidAction] + + def process(self, instance): + + invalid = self.get_invalid(instance) + + if invalid: + raise RuntimeError("'%s' is invalid VRay Proxy for " + "export!" % instance.name) + + @classmethod + def get_invalid(cls, instance): + + shapes = cmds.ls(instance, + shapes=True, + noIntermediate=True, + long=True) + + if not shapes: + cls.log.error("'%s' contains no shapes." % instance.name) + + # Return the instance itself + return [instance.data["objectSet"]] + From b1c6035d0a6240ee83190974ba7fe029bb2f3054 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 14:27:17 +0200 Subject: [PATCH 1080/1249] Label the instances correctly instead of revising name --- .../plugins/maya/publish/collect_instances.py | 17 ++++++++--------- .../maya/publish/collect_renderlayers.py | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 8ed6242036..807b57c710 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -102,6 +102,13 @@ class CollectInstances(pyblish.api.ContextPlugin): parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) + # Create the instance + instance = context.create_instance(objset) + instance[:] = members_hierarchy + + # Store the exact members of the object set + instance.data["setMembers"] = members + # Define nice label name = cmds.ls(objset, long=False)[0] # use short name label = "{0} ({1})".format(name, @@ -112,15 +119,7 @@ class CollectInstances(pyblish.api.ContextPlugin): label += " [{0}-{1}]".format(int(data["startFrame"]), int(data["endFrame"])) - # Create the instance - instance = context.create_instance(label) - instance[:] = members_hierarchy - - # Store a reference to the object set of this instance - instance.data["objectSet"] = objset - - # Store the exact members of the object set - instance.data["setMembers"] = members + instance.data["label"] = label instance.data.update(data) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 48f7fe113c..03a879d17b 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -108,7 +108,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): label += " [{0}-{1}]".format(int(data["startFrame"]), int(data["endFrame"])) - instance = context.create_instance(label) + instance = context.create_instance(layername) + instance.data["label"] = label instance.data.update(data) def get_render_attribute(self, attr): From 608edcb056bf59c0a94f3ef5b7e2bb06f0b76ad5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 14:48:19 +0200 Subject: [PATCH 1081/1249] Fix validate V-Ray proxy members --- .../maya/publish/validate_vrayproxy.py | 23 ------------------- .../publish/validate_vrayproxy_members.py | 2 +- 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_vrayproxy.py diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy.py b/colorbleed/plugins/maya/publish/validate_vrayproxy.py deleted file mode 100644 index f722dc6129..0000000000 --- a/colorbleed/plugins/maya/publish/validate_vrayproxy.py +++ /dev/null @@ -1,23 +0,0 @@ -import pyblish.api - - -class ValidateVrayProxy(pyblish.api.InstancePlugin): - - order = pyblish.api.ValidatorOrder - label = 'VRay Proxy Settings' - hosts = ['maya'] - families = ['colorbleed.vrayproxy'] - - def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("'%s' has invalid settings for VRay Proxy " - "export!" % instance.name) - - @classmethod - def get_invalid(cls, instance): - data = instance.data - - if not data["setMembers"]: - cls.log.error("'%s' is empty! This is a bug" % instance.name) diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py index 84a29fbc12..0ffbf3bd80 100644 --- a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py +++ b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py @@ -33,5 +33,5 @@ class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): cls.log.error("'%s' contains no shapes." % instance.name) # Return the instance itself - return [instance.data["objectSet"]] + return [instance.name] From b97b784255199baf15344b3509ec57ac652d4c15 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 5 Oct 2018 16:18:27 +0200 Subject: [PATCH 1082/1249] Disallow GenerateUUIDsOnInvalidAction to regenerate ids on referenced nodes --- colorbleed/action.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index b8e3c21d49..ba017354f0 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -41,7 +41,7 @@ def get_errored_plugins_from_data(context): class RepairAction(pyblish.api.Action): """Repairs the action - To process the repairing this requires a static `repair(instance)` method + To process the repairing this requires a static `repair(instance)` method is available on the plugin. """ @@ -67,7 +67,7 @@ class RepairAction(pyblish.api.Action): class RepairContextAction(pyblish.api.Action): """Repairs the action - To process the repairing this requires a static `repair(instance)` method + To process the repairing this requires a static `repair(instance)` method is available on the plugin. """ @@ -153,6 +153,8 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): def process(self, context, plugin): + from maya import cmds + self.log.info("Finding bad nodes..") # Get the errored instances @@ -170,6 +172,18 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): all_invalid = [] for instance in instances: invalid = plugin.get_invalid(instance) + + # Don't allow referenced nodes to get their ids regenerated to + # avoid loaded content getting messed up with reference edits + if invalid: + referenced = {node for node in invalid if + cmds.referenceQuery(node, isNodeReferenced=True)} + if referenced: + self.log.warning("Skipping UUID generation on referenced " + "nodes: {}".format(list(referenced))) + invalid = [node for node in invalid + if node not in referenced] + if invalid: self.log.info("Fixing instance {}".format(instance.name)) From 55dd74ddd583f5fb465f62a3091135aa5da24d05 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 17:14:03 +0200 Subject: [PATCH 1083/1249] moved plugin to global --- colorbleed/plugins/{maya => global}/publish/validate_resources.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/{maya => global}/publish/validate_resources.py (100%) diff --git a/colorbleed/plugins/maya/publish/validate_resources.py b/colorbleed/plugins/global/publish/validate_resources.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_resources.py rename to colorbleed/plugins/global/publish/validate_resources.py From d8030186cd097be18479ddd7bcf1f795f58eb568 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 17:15:21 +0200 Subject: [PATCH 1084/1249] collect reference files, extended docstrings --- .../plugins/maya/publish/collect_yeti_rig.py | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index e63bad48ca..c0d7cba2d2 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -64,10 +64,9 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Collect any textures if used yeti_resources = [] - yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya") + yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya", long=True) for node in yeti_nodes: # Get Yeti resources (textures) - # TODO: referenced files in Yeti Graph resources = self.get_yeti_resources(node) yeti_resources.extend(resources) @@ -80,11 +79,16 @@ class CollectYetiRig(pyblish.api.InstancePlugin): instance.data["endFrame"] = 1 def get_yeti_resources(self, node): - """Get all texture file paths + """Get all resource file paths If a texture is a sequence it gathers all sibling files to ensure the texture sequence is complete. + References can be used in the Yeti graph, this means that it is + possible to load previously caches files. The information will need + to be stored and, if the file not publish, copied to the resource + folder. + Args: node (str): node name of the pgYetiMaya node @@ -93,15 +97,25 @@ class CollectYetiRig(pyblish.api.InstancePlugin): """ resources = [] image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) + + # List all related textures texture_filenames = cmds.pgYetiCommand(node, listTextures=True) + self.log.info("Found %i texture(s)" % len(texture_filenames)) + + # Get all reference nodes + reference_nodes = cmds.pgYetiGraph(node, + listNodes=True, + type="reference") + self.log.info("Found %i reference node(s)" % len(reference_nodes)) if texture_filenames and not image_search_path: raise ValueError("pgYetiMaya node '%s' is missing the path to the " "files in the 'imageSearchPath " "atttribute'" % node) + # Collect all texture files for texture in texture_filenames: - node_resources = {"files": [], "source": texture, "node": node} + item = {"files": [], "source": texture, "node": node} texture_filepath = os.path.join(image_search_path, texture) if len(texture.split(".")) > 2: @@ -109,20 +123,46 @@ class CollectYetiRig(pyblish.api.InstancePlugin): if "" in texture: sequences = self.get_sequence(texture_filepath, pattern="") - node_resources["files"].extend(sequences) + item["files"].extend(sequences) # Based textures (animated masks f.e) elif "%04d" in texture: sequences = self.get_sequence(texture_filepath, pattern="%04d") - node_resources["files"].extend(sequences) + item["files"].extend(sequences) # Assuming it is a fixed name else: - node_resources["files"].append(texture_filepath) + item["files"].append(texture_filepath) else: - node_resources["files"].append(texture_filepath) + item["files"].append(texture_filepath) - resources.append(node_resources) + resources.append(item) + + # Collect all referenced files + for reference_node in reference_nodes: + ref_file = cmds.pgYetiGraph(node, + node=reference_node, + param="reference_file", + getParamValue=True) + + if not os.path.isfile(ref_file): + raise RuntimeError("Reference file must be a full file path!") + + # Create resource dict + item = {"files": [], + "source": ref_file, + "node": node, + "graphnode": reference_node, + "param": "reference_file"} + + ref_file_name = os.path.basename(ref_file) + if "%04d" in ref_file_name: + ref_files = self.get_sequence(ref_file) + item["files"].extend(ref_files) + else: + item["files"].append(ref_file) + + resources.append(item) return resources @@ -141,7 +181,6 @@ class CollectYetiRig(pyblish.api.InstancePlugin): list: file sequence. """ - from avalon.vendor import clique escaped = re.escape(filename) From 85a32ba4879d8845ef3ed9518de499579eb4adf9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 5 Oct 2018 17:16:31 +0200 Subject: [PATCH 1085/1249] added contextmanager to map graph node attributes --- .../plugins/maya/publish/extract_yeti_rig.py | 78 ++++++++++++++----- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/colorbleed/plugins/maya/publish/extract_yeti_rig.py index 6548bab60b..68fe9b118d 100644 --- a/colorbleed/plugins/maya/publish/extract_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/extract_yeti_rig.py @@ -53,6 +53,41 @@ def disconnect_plugs(settings, members): continue +@contextlib.contextmanager +def yetigraph_attribute_values(assumed_destination, resources): + + try: + for resource in resources: + if "graphnode" not in resource: + continue + + fname = os.path.basename(resource["source"]) + new_fpath = os.path.join(assumed_destination, fname) + new_fpath = new_fpath.replace("\\", "/") + + try: + cmds.pgYetiGraph(resource["node"], + node=resource["graphnode"], + param=resource["param"], + setParamValueString=new_fpath) + except Exception as exc: + print(">>> Exception:", exc) + yield + + finally: + for resource in resources: + if "graphnode" not in resources: + continue + + try: + cmds.pgYetiGraph(resource["node"], + node=resource["graphnode"], + param=resource["param"], + setParamValue=resource["source"]) + except RuntimeError: + pass + + class ExtractYetiRig(colorbleed.api.Extractor): """Extract the Yeti rig to a MayaAscii and write the Yeti rig data""" @@ -75,19 +110,18 @@ class ExtractYetiRig(colorbleed.api.Extractor): self.log.info("Writing metadata file") - image_search_path = "" + # Create assumed destination folder for imageSearchPath + assumed_temp_data = instance.data["assumedTemplateData"] + template = instance.data["template"] + template_formatted = template.format(**assumed_temp_data) + + destination_folder = os.path.dirname(template_formatted) + + image_search_path = os.path.join(destination_folder, "resources") + image_search_path = os.path.normpath(image_search_path) + settings = instance.data.get("rigsettings", None) if settings: - - # Create assumed destination folder for imageSearchPath - assumed_temp_data = instance.data["assumedTemplateData"] - template = instance.data["template"] - template_formatted = template.format(**assumed_temp_data) - - destination_folder = os.path.dirname(template_formatted) - image_search_path = os.path.join(destination_folder, "resources") - image_search_path = os.path.normpath(image_search_path) - settings["imageSearchPath"] = image_search_path with open(settings_path, "w") as fp: json.dump(settings, fp, ensure_ascii=False) @@ -104,19 +138,21 @@ class ExtractYetiRig(colorbleed.api.Extractor): set_members += cmds.listRelatives(set_members, allDescendents=True, fullPath=True) or [] - members = cmds.ls(set_members, long=True, objects=True) + members = cmds.ls(set_members, long=True) nodes = instance.data["setMembers"] + resources = instance.data.get("resources", {}) with disconnect_plugs(settings, members): - with maya.attribute_values(attr_value): - cmds.select(nodes, noExpand=True) - cmds.file(maya_path, - force=True, - exportSelected=True, - typ="mayaAscii", - preserveReferences=False, - constructionHistory=True, - shader=False) + with yetigraph_attribute_values(destination_folder, resources): + with maya.attribute_values(attr_value): + cmds.select(nodes, noExpand=True) + cmds.file(maya_path, + force=True, + exportSelected=True, + typ="mayaAscii", + preserveReferences=False, + constructionHistory=True, + shader=False) # Ensure files can be stored if "files" not in instance.data: From b48541f9ea743b2fa4c6b7e1cf85bcdda6457313 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Oct 2018 16:43:43 +0200 Subject: [PATCH 1086/1249] Fix PLN-178 --- .../maya/publish/extract_camera_mayaAscii.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index c2c1f32b01..c29aaed4fe 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -1,4 +1,5 @@ import os +from itertools import izip_longest from maya import cmds @@ -35,6 +36,49 @@ def massage_ma_file(path): f.close() +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks + + Examples: + grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx + + """ + + args = [iter(iterable)] * n + return izip_longest(fillvalue=fillvalue, *args) + + +def unlock(plug): + """Unlocks attribute and disconnects inputs for a plug. + + This will also recursively unlock the attribute + upwards to any parent attributes for compound + attributes, to ensure it's fully unlocked and free + to change the value. + + """ + node, attr = plug.rsplit(".", 1) + + # Unlock attribute + cmds.setAttr(plug, lock=False) + + # Also unlock any parent attribute (if compound) + parents = cmds.attributeQuery(attr, node=node, listParent=True) + if parents: + for parent in parents: + unlock("{0}.{1}".format(node, parent)) + + # Break incoming connections + connections = cmds.listConnections(plug, + source=True, + destination=False, + plugs=True, + connections=True) + if connections: + for destination, source in grouper(connections, 2): + cmds.disconnectAttr(source, destination) + + class ExtractCameraMayaAscii(colorbleed.api.Extractor): """Extract a Camera as Maya Ascii. @@ -107,6 +151,17 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): shapes=True, long=True) + # Fix PLN-178: Don't allow background color to be non-black + for cam in baked_shapes: + attrs = {"backgroundColorR": 0.0, + "backgroundColorG": 0.0, + "backgroundColorB": 0.0, + "overscan": 1.0} + for attr, value in attrs.items(): + plug = "{0}.{1}".format(cam, attr) + unlock(plug) + cmds.setAttr(plug, value) + self.log.info("Performing extraction..") cmds.select(baked_shapes, noExpand=True) cmds.file(path, From a39dc850837764cd70af9004fc4602fbb8da89c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Oct 2018 18:00:16 +0200 Subject: [PATCH 1087/1249] Simplify logic by using `get_errored_instances_from_context` function --- colorbleed/action.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index ba017354f0..534f34751d 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -157,13 +157,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): self.log.info("Finding bad nodes..") - # Get the errored instances - errored_instances = [] - for result in context.data["results"]: - if result["error"] is not None and result["instance"] is not None: - if result["error"]: - instance = result["instance"] - errored_instances.append(instance) + errored_instances = get_errored_instances_from_context(context) # Apply pyblish logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(errored_instances, plugin) From 4d16d20b6d55f1e9c659f2cfaf862f0a1233b436 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Oct 2018 18:05:00 +0200 Subject: [PATCH 1088/1249] Move Maya Pyblish actions to colorbleed.maya module --- colorbleed/action.py | 120 +--------------- colorbleed/api.py | 5 - colorbleed/maya/action.py | 128 ++++++++++++++++++ .../publish/validate_animation_content.py | 3 +- ...date_animation_out_set_related_node_ids.py | 3 +- .../publish/validate_camera_attributes.py | 3 +- .../maya/publish/validate_camera_contents.py | 5 +- .../publish/validate_instance_has_members.py | 3 +- .../maya/publish/validate_joints_hidden.py | 3 +- .../maya/publish/validate_look_contents.py | 3 +- .../validate_look_id_reference_edits.py | 3 +- .../publish/validate_look_members_unique.py | 5 +- .../validate_look_no_default_shaders.py | 3 +- .../maya/publish/validate_look_sets.py | 3 +- .../publish/validate_look_single_shader.py | 3 +- .../maya/publish/validate_mesh_has_uv.py | 5 +- .../publish/validate_mesh_lamina_faces.py | 3 +- .../validate_mesh_no_negative_scale.py | 3 +- .../publish/validate_mesh_non_manifold.py | 3 +- .../publish/validate_mesh_non_zero_edge.py | 3 +- .../publish/validate_mesh_normals_unlocked.py | 3 +- .../validate_mesh_shader_connections.py | 3 +- .../publish/validate_mesh_single_uv_set.py | 3 +- .../maya/publish/validate_mesh_uv_set_map1.py | 3 +- .../validate_mesh_vertices_have_edges.py | 3 +- .../maya/publish/validate_model_content.py | 3 +- .../maya/publish/validate_no_animation.py | 3 +- .../publish/validate_no_default_camera.py | 3 +- .../maya/publish/validate_no_namespace.py | 3 +- .../publish/validate_no_null_transforms.py | 4 +- .../maya/publish/validate_no_unknown_nodes.py | 3 +- .../plugins/maya/publish/validate_node_ids.py | 11 +- .../validate_node_ids_deformed_shapes.py | 3 +- .../publish/validate_node_ids_in_database.py | 3 +- .../maya/publish/validate_node_ids_related.py | 5 +- .../maya/publish/validate_node_ids_unique.py | 5 +- .../maya/publish/validate_node_no_ghosting.py | 3 +- .../validate_render_no_default_cameras.py | 3 +- .../publish/validate_render_single_camera.py | 3 +- .../maya/publish/validate_renderlayer_aovs.py | 3 +- .../maya/publish/validate_rig_controllers.py | 4 +- ...idate_rig_controllers_arnold_attributes.py | 4 +- .../publish/validate_rig_out_set_node_ids.py | 3 +- .../publish/validate_setdress_namespaces.py | 3 +- .../publish/validate_setdress_transforms.py | 4 +- .../publish/validate_shape_default_names.py | 3 +- .../publish/validate_shape_render_stats.py | 4 +- .../maya/publish/validate_step_size.py | 3 +- .../validate_transform_naming_suffix.py | 4 +- .../maya/publish/validate_transform_zero.py | 3 +- .../publish/validate_vrayproxy_members.py | 4 +- .../validate_yeti_rig_input_in_instance.py | 3 +- .../publish/validate_yetirig_cache_state.py | 4 +- 53 files changed, 245 insertions(+), 184 deletions(-) create mode 100644 colorbleed/maya/action.py diff --git a/colorbleed/action.py b/colorbleed/action.py index 534f34751d..ea1f5e13f4 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -1,5 +1,6 @@ # absolute_import is needed to counter the `module has no cmds error` in Maya from __future__ import absolute_import + import pyblish.api @@ -89,122 +90,3 @@ class RepairContextAction(pyblish.api.Action): plugin.repair() -class SelectInvalidAction(pyblish.api.Action): - """Select invalid nodes in Maya when plug-in failed. - - To retrieve the invalid nodes this assumes a static `get_invalid()` - method is available on the plugin. - - """ - label = "Select invalid" - on = "failed" # This action is only available on a failed plug-in - icon = "search" # Icon from Awesome Icon - - def process(self, context, plugin): - - try: - from maya import cmds - except ImportError: - raise ImportError("Current host is not Maya") - - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish.logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - - # Get the invalid nodes for the plug-ins - self.log.info("Finding invalid nodes..") - invalid = list() - for instance in instances: - invalid_nodes = plugin.get_invalid(instance) - if invalid_nodes: - if isinstance(invalid_nodes, (list, tuple)): - invalid.extend(invalid_nodes) - else: - self.log.warning("Plug-in returned to be invalid, " - "but has no selectable nodes.") - - # Ensure unique (process each node only once) - invalid = list(set(invalid)) - - if invalid: - self.log.info("Selecting invalid nodes: %s" % ", ".join(invalid)) - cmds.select(invalid, replace=True, noExpand=True) - else: - self.log.info("No invalid nodes found.") - cmds.select(deselect=True) - - -class GenerateUUIDsOnInvalidAction(pyblish.api.Action): - """Generate UUIDs on the invalid nodes in the instance. - - Invalid nodes are those returned by the plugin's `get_invalid` method. - As such it is the plug-in's responsibility to ensure the nodes that - receive new UUIDs are actually invalid. - - Requires: - - instance.data["asset"] - - """ - - label = "Regenerate UUIDs" - on = "failed" # This action is only available on a failed plug-in - icon = "wrench" # Icon from Awesome Icon - - def process(self, context, plugin): - - from maya import cmds - - self.log.info("Finding bad nodes..") - - errored_instances = get_errored_instances_from_context(context) - - # Apply pyblish logic to get the instances for the plug-in - instances = pyblish.api.instances_by_plugin(errored_instances, plugin) - - # Get the nodes from the all instances that ran through this plug-in - all_invalid = [] - for instance in instances: - invalid = plugin.get_invalid(instance) - - # Don't allow referenced nodes to get their ids regenerated to - # avoid loaded content getting messed up with reference edits - if invalid: - referenced = {node for node in invalid if - cmds.referenceQuery(node, isNodeReferenced=True)} - if referenced: - self.log.warning("Skipping UUID generation on referenced " - "nodes: {}".format(list(referenced))) - invalid = [node for node in invalid - if node not in referenced] - - if invalid: - - self.log.info("Fixing instance {}".format(instance.name)) - self._update_id_attribute(instance, invalid) - - all_invalid.extend(invalid) - - if not all_invalid: - self.log.info("No invalid nodes found.") - return - - all_invalid = list(set(all_invalid)) - self.log.info("Generated ids on nodes: {0}".format(all_invalid)) - - def _update_id_attribute(self, instance, nodes): - """Delete the id attribute - - Args: - instance: The instance we're fixing for - nodes (list): all nodes to regenerate ids on - """ - - import colorbleed.maya.lib as lib - import avalon.io as io - - asset = instance.data['asset'] - asset_id = io.find_one({"name": asset, "type": "asset"}, - projection={"_id": True})['_id'] - for node, _id in lib.generate_ids(nodes, asset_id=asset_id): - lib.set_id(node, _id, overwrite=True) diff --git a/colorbleed/api.py b/colorbleed/api.py index 531a63a50d..c332a98391 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -12,10 +12,7 @@ from .plugin import ( # temporary fix, might from .action import ( - get_errored_instances_from_context, - SelectInvalidAction, - GenerateUUIDsOnInvalidAction, RepairAction, RepairContextAction ) @@ -30,7 +27,5 @@ all = [ "ValidateMeshOrder", # action "get_errored_instances_from_context", - "SelectInvalidAction", - "GenerateUUIDsOnInvalidAction", "RepairAction" ] diff --git a/colorbleed/maya/action.py b/colorbleed/maya/action.py new file mode 100644 index 0000000000..f0f94516c0 --- /dev/null +++ b/colorbleed/maya/action.py @@ -0,0 +1,128 @@ +# absolute_import is needed to counter the `module has no cmds error` in Maya +from __future__ import absolute_import + +import pyblish.api + + +from ..action import get_errored_instances_from_context + + +class GenerateUUIDsOnInvalidAction(pyblish.api.Action): + """Generate UUIDs on the invalid nodes in the instance. + + Invalid nodes are those returned by the plugin's `get_invalid` method. + As such it is the plug-in's responsibility to ensure the nodes that + receive new UUIDs are actually invalid. + + Requires: + - instance.data["asset"] + + """ + + label = "Regenerate UUIDs" + on = "failed" # This action is only available on a failed plug-in + icon = "wrench" # Icon from Awesome Icon + + def process(self, context, plugin): + + from maya import cmds + + self.log.info("Finding bad nodes..") + + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the nodes from the all instances that ran through this plug-in + all_invalid = [] + for instance in instances: + invalid = plugin.get_invalid(instance) + + # Don't allow referenced nodes to get their ids regenerated to + # avoid loaded content getting messed up with reference edits + if invalid: + referenced = {node for node in invalid if + cmds.referenceQuery(node, isNodeReferenced=True)} + if referenced: + self.log.warning("Skipping UUID generation on referenced " + "nodes: {}".format(list(referenced))) + invalid = [node for node in invalid + if node not in referenced] + + if invalid: + + self.log.info("Fixing instance {}".format(instance.name)) + self._update_id_attribute(instance, invalid) + + all_invalid.extend(invalid) + + if not all_invalid: + self.log.info("No invalid nodes found.") + return + + all_invalid = list(set(all_invalid)) + self.log.info("Generated ids on nodes: {0}".format(all_invalid)) + + def _update_id_attribute(self, instance, nodes): + """Delete the id attribute + + Args: + instance: The instance we're fixing for + nodes (list): all nodes to regenerate ids on + """ + + import colorbleed.maya.lib as lib + import avalon.io as io + + asset = instance.data['asset'] + asset_id = io.find_one({"name": asset, "type": "asset"}, + projection={"_id": True})['_id'] + for node, _id in lib.generate_ids(nodes, asset_id=asset_id): + lib.set_id(node, _id, overwrite=True) + + +class SelectInvalidAction(pyblish.api.Action): + """Select invalid nodes in Maya when plug-in failed. + + To retrieve the invalid nodes this assumes a static `get_invalid()` + method is available on the plugin. + + """ + label = "Select invalid" + on = "failed" # This action is only available on a failed plug-in + icon = "search" # Icon from Awesome Icon + + def process(self, context, plugin): + + try: + from maya import cmds + except ImportError: + raise ImportError("Current host is not Maya") + + errored_instances = get_errored_instances_from_context(context) + + # Apply pyblish.logic to get the instances for the plug-in + instances = pyblish.api.instances_by_plugin(errored_instances, plugin) + + # Get the invalid nodes for the plug-ins + self.log.info("Finding invalid nodes..") + invalid = list() + for instance in instances: + invalid_nodes = plugin.get_invalid(instance) + if invalid_nodes: + if isinstance(invalid_nodes, (list, tuple)): + invalid.extend(invalid_nodes) + else: + self.log.warning("Plug-in returned to be invalid, " + "but has no selectable nodes.") + + # Ensure unique (process each node only once) + invalid = list(set(invalid)) + + if invalid: + self.log.info("Selecting invalid nodes: %s" % ", ".join(invalid)) + cmds.select(invalid, replace=True, noExpand=True) + else: + self.log.info("No invalid nodes found.") + cmds.select(deselect=True) \ No newline at end of file diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/colorbleed/plugins/maya/publish/validate_animation_content.py index 0725281705..dc25f7391e 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_content.py +++ b/colorbleed/plugins/maya/publish/validate_animation_content.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateAnimationContent(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.animation"] label = "Animation Content" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 63705dd3ba..0c05d211ca 100644 --- a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -2,6 +2,7 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -19,7 +20,7 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): families = ['colorbleed.animation', "colorbleed.pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' - actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] def process(self, instance): """Process all meshes""" diff --git a/colorbleed/plugins/maya/publish/validate_camera_attributes.py b/colorbleed/plugins/maya/publish/validate_camera_attributes.py index 46b9de7ecd..9c43e9e6ed 100644 --- a/colorbleed/plugins/maya/publish/validate_camera_attributes.py +++ b/colorbleed/plugins/maya/publish/validate_camera_attributes.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateCameraAttributes(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): families = ['colorbleed.camera'] hosts = ['maya'] label = 'Camera Attributes' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] DEFAULTS = [ ("filmFitOffset", 0.0), diff --git a/colorbleed/plugins/maya/publish/validate_camera_contents.py b/colorbleed/plugins/maya/publish/validate_camera_contents.py index 09f5d5392b..0b7ba9735d 100644 --- a/colorbleed/plugins/maya/publish/validate_camera_contents.py +++ b/colorbleed/plugins/maya/publish/validate_camera_contents.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateCameraContents(pyblish.api.InstancePlugin): @@ -9,7 +10,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): A Camera instance may only hold a SINGLE camera's transform, nothing else. - It may hold a "locator" as shape, but different shapes are down the + It may hold a "locator" as shape, but different shapes are down the hierarchy. """ @@ -18,7 +19,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): families = ['colorbleed.camera'] hosts = ['maya'] label = 'Camera Contents' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/colorbleed/plugins/maya/publish/validate_instance_has_members.py b/colorbleed/plugins/maya/publish/validate_instance_has_members.py index d209505378..e0ea93b85c 100644 --- a/colorbleed/plugins/maya/publish/validate_instance_has_members.py +++ b/colorbleed/plugins/maya/publish/validate_instance_has_members.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): @@ -8,7 +9,7 @@ class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ["maya"] label = 'Instance has members' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/colorbleed/plugins/maya/publish/validate_joints_hidden.py index 7772372ad4..1c8e44e843 100644 --- a/colorbleed/plugins/maya/publish/validate_joints_hidden.py +++ b/colorbleed/plugins/maya/publish/validate_joints_hidden.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -22,7 +23,7 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): category = 'rig' version = (0, 1, 0) label = "Joints Hidden" - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @staticmethod diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/colorbleed/plugins/maya/publish/validate_look_contents.py index 5f47ea39cc..2f515e970c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_contents.py +++ b/colorbleed/plugins/maya/publish/validate_look_contents.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateLookContents(pyblish.api.InstancePlugin): @@ -20,7 +21,7 @@ class ValidateLookContents(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Data Contents' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py index b2608b1cdf..454320b00c 100644 --- a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py +++ b/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py @@ -3,6 +3,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): @@ -19,7 +20,7 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Id Reference Edits' - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/colorbleed/plugins/maya/publish/validate_look_members_unique.py index a42410c123..b611bdbb33 100644 --- a/colorbleed/plugins/maya/publish/validate_look_members_unique.py +++ b/colorbleed/plugins/maya/publish/validate_look_members_unique.py @@ -2,6 +2,7 @@ from collections import defaultdict import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): @@ -24,8 +25,8 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['colorbleed.look'] - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py index 9315359184..7485fc3cc1 100644 --- a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @@ -26,7 +27,7 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look No Default Shaders' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] DEFAULT_SHADERS = {"lambert1", "initialShadingGroup", "initialParticleSE", "particleCloud1"} diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index bf0567cc88..31884ab48f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -1,3 +1,4 @@ +import colorbleed.maya.action from colorbleed.maya import lib import pyblish.api @@ -35,7 +36,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Sets' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/colorbleed/plugins/maya/publish/validate_look_single_shader.py index f56f8eb64e..687dd2b84f 100644 --- a/colorbleed/plugins/maya/publish/validate_look_single_shader.py +++ b/colorbleed/plugins/maya/publish/validate_look_single_shader.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateSingleShader(pyblish.api.InstancePlugin): @@ -15,7 +16,7 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] # The default connections to check def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py index bc7c19e5ad..4233674ddd 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def len_flattened(components): @@ -13,7 +14,7 @@ def len_flattened(components): when requesting with `maya.cmds.ls` without the `flatten` flag. Though enabling `flatten` on a large list (e.g. millions) will result in a slow result. This command will return the amount - of entries in a non-flattened list by parsing the result with + of entries in a non-flattened list by parsing the result with regex. Args: @@ -49,7 +50,7 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): families = ['colorbleed.model'] category = 'geometry' label = 'Mesh Has UVs' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] optional = True @classmethod diff --git a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py index 80a6968e55..ca54648108 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): category = 'geometry' version = (0, 1, 0) label = 'Mesh Lamina Faces' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py index 91fc720cbe..de60300931 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): @@ -20,7 +21,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): hosts = ['maya'] families = ['colorbleed.model'] label = 'Mesh No Negative Scale' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py b/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py index c1185cf587..1c5ce731fa 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateMeshNonManifold(pyblish.api.Validator): @@ -16,7 +17,7 @@ class ValidateMeshNonManifold(pyblish.api.Validator): hosts = ['maya'] families = ['colorbleed.model'] label = 'Mesh Non-Manifold Vertices/Edges' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py index 02019da523..6f35f512a0 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -21,7 +22,7 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): category = 'geometry' version = (0, 1, 0) label = 'Mesh Edge Length Non Zero' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] __tolerance = 1e-5 diff --git a/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py b/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py index 68049fd60a..09fe2b4e4b 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateMeshNormalsUnlocked(pyblish.api.Validator): @@ -18,7 +19,7 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): category = 'geometry' version = (0, 1, 0) label = 'Mesh Normals Unlocked' - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] optional = True diff --git a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py index 5f44bf1f9f..2d9d82eaa5 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def pairs(iterable): @@ -76,7 +77,7 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['colorbleed.model'] label = "Mesh Shader Connections" - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py index 77ec1a0661..774c7899d2 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -21,7 +22,7 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): optional = True version = (0, 1, 0) label = "Mesh Single UV Set" - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @staticmethod diff --git a/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py b/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py index 878549b539..00c9d800cf 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): @@ -19,7 +20,7 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): families = ['colorbleed.model'] optional = True label = "Mesh has map1 UV Set" - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @staticmethod diff --git a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py index 90eca6e6f4..41efd27f89 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def len_flattened(components): @@ -61,7 +62,7 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): families = ['colorbleed.model'] category = 'geometry' label = 'Mesh Vertices Have Edges' - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @classmethod diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 1e43754c6d..1ea26047ee 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -17,7 +18,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.model"] label = "Model Content" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/colorbleed/plugins/maya/publish/validate_no_animation.py b/colorbleed/plugins/maya/publish/validate_no_animation.py index 8b0da47d89..bde5775fe7 100644 --- a/colorbleed/plugins/maya/publish/validate_no_animation.py +++ b/colorbleed/plugins/maya/publish/validate_no_animation.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateNoAnimation(pyblish.api.Validator): @@ -18,7 +19,7 @@ class ValidateNoAnimation(pyblish.api.Validator): hosts = ["maya"] families = ["colorbleed.model"] optional = True - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_no_default_camera.py b/colorbleed/plugins/maya/publish/validate_no_default_camera.py index 6bdb830f0b..e41d2eea99 100644 --- a/colorbleed/plugins/maya/publish/validate_no_default_camera.py +++ b/colorbleed/plugins/maya/publish/validate_no_default_camera.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): families = ['colorbleed.camera'] version = (0, 1, 0) label = "No Default Cameras" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_no_namespace.py b/colorbleed/plugins/maya/publish/validate_no_namespace.py index 0f0bbad1c1..46f1dbe49c 100644 --- a/colorbleed/plugins/maya/publish/validate_no_namespace.py +++ b/colorbleed/plugins/maya/publish/validate_no_namespace.py @@ -3,6 +3,7 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def get_namespace(node_name): @@ -21,7 +22,7 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin): category = 'cleanup' version = (0, 1, 0) label = 'No Namespaces' - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @staticmethod diff --git a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py index d94938493b..d9dff290b8 100644 --- a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_no_null_transforms.py @@ -2,6 +2,7 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def has_shape_children(node): @@ -42,7 +43,8 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): category = 'cleanup' version = (0, 1, 0) label = 'No Empty/Null Transforms' - actions = [colorbleed.api.RepairAction, colorbleed.api.SelectInvalidAction] + actions = [colorbleed.api.RepairAction, + colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py index 221e8f8b61..3fdd087b3d 100644 --- a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py +++ b/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): @@ -20,7 +21,7 @@ class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): families = ['colorbleed.model', 'colorbleed.rig'] optional = True label = "Unknown Nodes" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/colorbleed/plugins/maya/publish/validate_node_ids.py index 3bd4471788..f2d21b6158 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids.py @@ -1,14 +1,15 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action from colorbleed.maya import lib class ValidateNodeIDs(pyblish.api.InstancePlugin): """Validate nodes have a Colorbleed Id. - - When IDs are missing from nodes *save your scene* and they should be - automatically generated because IDs are created on non-referenced nodes + + When IDs are missing from nodes *save your scene* and they should be + automatically generated because IDs are created on non-referenced nodes in Maya upon scene save. """ @@ -23,8 +24,8 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): "colorbleed.animation", "colorbleed.setdress"] - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py index 4513acb6e1..99df6a1b63 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -19,7 +20,7 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): families = ['colorbleed.look'] hosts = ['maya'] label = 'Deformed shape ids' - actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 0493779da2..7b0fddee30 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -3,6 +3,7 @@ import pyblish.api import avalon.io as io import colorbleed.api +import colorbleed.maya.action from colorbleed.maya import lib @@ -22,7 +23,7 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["*"] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/colorbleed/plugins/maya/publish/validate_node_ids_related.py index 985bd54dda..c9185b4e02 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_related.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_related.py @@ -2,6 +2,7 @@ import pyblish.api import colorbleed.api import avalon.io as io +import colorbleed.maya.action from colorbleed.maya import lib @@ -19,8 +20,8 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): "colorbleed.rig"] optional = True - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all nodes in instance (including hierarchy)""" diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py index b8cbbd552d..09551ef2ef 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_unique.py @@ -2,6 +2,7 @@ from collections import defaultdict import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -18,8 +19,8 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): "colorbleed.look", "colorbleed.rig"] - actions = [colorbleed.api.SelectInvalidAction, - colorbleed.api.GenerateUUIDsOnInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py b/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py index ca5c4a1edc..360c3cdce8 100644 --- a/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py +++ b/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): @@ -20,7 +21,7 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['colorbleed.model', 'colorbleed.rig'] label = "No Ghosting" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] _attributes = {'ghosting': 0} diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py index 4f55670a8c..588c66eb39 100644 --- a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -12,7 +13,7 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['colorbleed.renderlayer'] label = "No Default Cameras Renderable" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index e45f5f186c..94448d1585 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -20,7 +21,7 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): hosts = ['maya'] families = ['colorbleed.renderlayer'] label = "Render Single Camera" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py index 387f4b4881..06e0c68184 100644 --- a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py +++ b/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py @@ -1,5 +1,6 @@ import pyblish.api +import colorbleed.maya.action from avalon import io import colorbleed.api @@ -24,7 +25,7 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): label = "Render Passes / AOVs Are Registered" hosts = ["maya"] families = ["colorbleed.renderlayer"] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 2ae799fa71..6117f46597 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -4,6 +4,8 @@ import pyblish.api import colorbleed.api from cb.utils.maya.context import undo_chunk +import colorbleed.maya.action + class ValidateRigControllers(pyblish.api.InstancePlugin): """Validate rig controllers. @@ -28,7 +30,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.rig"] actions = [colorbleed.api.RepairAction, - colorbleed.api.SelectInvalidAction] + colorbleed.maya.action.SelectInvalidAction] # Default controller values CONTROLLER_DEFAULTS = { diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py index b6a1191006..6655e59a67 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py @@ -4,6 +4,8 @@ import pyblish.api import colorbleed.api from cb.utils.maya.context import undo_chunk +import colorbleed.maya.action + class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): """Validate rig control curves have no keyable arnold attributes. @@ -29,7 +31,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.rig"] actions = [colorbleed.api.RepairAction, - colorbleed.api.SelectInvalidAction] + colorbleed.maya.action.SelectInvalidAction] attributes = [ "rcurve", diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py index 6b8fc9d28c..72e0db311c 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -2,6 +2,7 @@ import maya.cmds as cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action import colorbleed.maya.lib as lib @@ -19,7 +20,7 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): families = ["colorbleed.rig"] hosts = ['maya'] label = 'Rig Out Set Node Ids' - actions = [colorbleed.api.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] def process(self, instance): """Process all meshes""" diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py index 1eda02cf74..a8a28a10ca 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): @@ -17,7 +18,7 @@ class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): label = "Validate Setdress Namespaces" order = pyblish.api.ValidatorOrder families = ["colorbleed.setdress"] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py index c3547f963e..8ed4a81b5f 100644 --- a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py +++ b/colorbleed/plugins/maya/publish/validate_setdress_transforms.py @@ -3,6 +3,8 @@ import colorbleed.api from maya import cmds +import colorbleed.maya.action + class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): """Verify only root nodes of the loaded asset have transformations. @@ -26,7 +28,7 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder + 0.49 label = "Setdress Model Transforms" families = ["colorbleed.setdress"] - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] prompt_message = ("You are about to reset the matrix to the default values." diff --git a/colorbleed/plugins/maya/publish/validate_shape_default_names.py b/colorbleed/plugins/maya/publish/validate_shape_default_names.py index 75fd8f3f1e..b4249d69aa 100644 --- a/colorbleed/plugins/maya/publish/validate_shape_default_names.py +++ b/colorbleed/plugins/maya/publish/validate_shape_default_names.py @@ -4,6 +4,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action def short_name(node): @@ -37,7 +38,7 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): optional = True version = (0, 1, 0) label = "Shape Default Naming" - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] @staticmethod diff --git a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py index 9a3067badb..544470c3a7 100644 --- a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py +++ b/colorbleed/plugins/maya/publish/validate_shape_render_stats.py @@ -3,6 +3,8 @@ import colorbleed.api from maya import cmds +import colorbleed.maya.action + class ValidateShapeRenderStats(pyblish.api.Validator): """Ensure all render stats are set to the default values.""" @@ -11,7 +13,7 @@ class ValidateShapeRenderStats(pyblish.api.Validator): hosts = ['maya'] families = ['colorbleed.model'] label = 'Shape Default Render Stats' - actions = [colorbleed.api.SelectInvalidAction, + actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] defaults = {'castsShadows': 1, diff --git a/colorbleed/plugins/maya/publish/validate_step_size.py b/colorbleed/plugins/maya/publish/validate_step_size.py index 7267d99a35..07d61ec933 100644 --- a/colorbleed/plugins/maya/publish/validate_step_size.py +++ b/colorbleed/plugins/maya/publish/validate_step_size.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateStepSize(pyblish.api.InstancePlugin): @@ -14,7 +15,7 @@ class ValidateStepSize(pyblish.api.InstancePlugin): families = ['colorbleed.camera', 'colorbleed.pointcache', 'colorbleed.animation'] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] MIN = 0.01 MAX = 1.0 diff --git a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py index 51d63ad505..0c100126ce 100644 --- a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py +++ b/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api - +import colorbleed.maya.action SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP", "_OSD"], 'nurbsCurve': ["_CRV"], @@ -38,7 +38,7 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): optional = True version = (0, 1, 0) label = 'Suffix Naming Conventions' - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] @staticmethod def is_valid_name(node_name, shape_type): diff --git a/colorbleed/plugins/maya/publish/validate_transform_zero.py b/colorbleed/plugins/maya/publish/validate_transform_zero.py index fa87539d96..895248933c 100644 --- a/colorbleed/plugins/maya/publish/validate_transform_zero.py +++ b/colorbleed/plugins/maya/publish/validate_transform_zero.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateTransformZero(pyblish.api.Validator): @@ -19,7 +20,7 @@ class ValidateTransformZero(pyblish.api.Validator): category = "geometry" version = (0, 1, 0) label = "Transform Zero (Freeze)" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] _identity = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py index 0ffbf3bd80..7d9ecdaac5 100644 --- a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py +++ b/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py @@ -3,6 +3,8 @@ import colorbleed.api from maya import cmds +import colorbleed.maya.action + class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): """Validate whether the V-Ray Proxy instance has shape members""" @@ -11,7 +13,7 @@ class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): label = 'VRay Proxy Members' hosts = ['maya'] families = ['colorbleed.vrayproxy'] - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py index 58d9834617..423cc18360 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py @@ -2,6 +2,7 @@ from maya import cmds import pyblish.api import colorbleed.api +import colorbleed.maya.action class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): @@ -11,7 +12,7 @@ class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): hosts = ["maya"] families = ["colorbleed.yetiRig"] label = "Yeti Rig Input Shapes In Instance" - actions = [colorbleed.api.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py index 2c4d4dbc72..94a46d2821 100644 --- a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py +++ b/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py @@ -4,6 +4,8 @@ import colorbleed.action import maya.cmds as cmds +import colorbleed.maya.action + class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): """Validate the I/O attributes of the node @@ -19,7 +21,7 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.yetiRig"] actions = [colorbleed.action.RepairAction, - colorbleed.action.SelectInvalidAction] + colorbleed.maya.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) From e3fc962add0a7dc0338ce074001ce28f9ef034b4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Oct 2018 18:07:47 +0200 Subject: [PATCH 1089/1249] Fix all to __all__ --- colorbleed/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/api.py b/colorbleed/api.py index c332a98391..8e49c17309 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -17,7 +17,7 @@ from .action import ( RepairContextAction ) -all = [ +__all__ = [ # plugin classes "Extractor", # ordering From e1eaef8a69a7a7dca5964ebafe416f621fa3133a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Oct 2018 18:08:10 +0200 Subject: [PATCH 1090/1249] Remove OrderedDict from api (since it's Python built-in) --- colorbleed/api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/api.py b/colorbleed/api.py index 8e49c17309..e665d40535 100644 --- a/colorbleed/api.py +++ b/colorbleed/api.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from .plugin import ( Extractor, From 128b2856543078ba8a25824ad6895e4ca98bc4ff Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 12:57:50 +0200 Subject: [PATCH 1091/1249] Embed all internal Colorbleed library (cb) code into config --- colorbleed/lib.py | 19 + colorbleed/maya/lib.py | 493 ++++++++++++++++++ .../plugins/maya/create/colorbleed_camera.py | 2 +- .../plugins/maya/publish/collect_look.py | 142 ++++- .../maya/publish/extract_camera_alembic.py | 10 +- .../maya/publish/extract_camera_mayaAscii.py | 36 +- .../plugins/maya/publish/extract_look.py | 11 +- .../plugins/maya/publish/extract_model.py | 21 +- .../maya/publish/validate_look_sets.py | 4 +- .../maya/publish/validate_rig_controllers.py | 4 +- ...idate_rig_controllers_arnold_attributes.py | 4 +- 11 files changed, 689 insertions(+), 57 deletions(-) diff --git a/colorbleed/lib.py b/colorbleed/lib.py index bc98cf0cc5..edd911a461 100644 --- a/colorbleed/lib.py +++ b/colorbleed/lib.py @@ -2,6 +2,7 @@ import os import re import logging import importlib +import itertools from .vendor import pather from .vendor.pather.error import ParseError @@ -12,6 +13,24 @@ import avalon.api log = logging.getLogger(__name__) +def pairwise(iterable): + """s -> (s0,s1), (s2,s3), (s4, s5), ...""" + a = iter(iterable) + return itertools.izip(a, a) + + +def grouper(iterable, n, fillvalue=None): + """Collect data into fixed-length chunks or blocks + + Examples: + grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx + + """ + + args = [iter(iterable)] * n + return itertools.izip_longest(fillvalue=fillvalue, *args) + + def is_latest(representation): """Return whether the representation is from latest version diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index d4b0b8aa73..dcb9a91620 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -94,6 +94,11 @@ INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000} FLOAT_FPS = {23.976, 29.97, 47.952, 59.94} +def _get_mel_global(name): + """Return the value of a mel global variable""" + return mel.eval("$%s = $%s;" % (name, name)) + + def matrix_equals(a, b, tolerance=1e-10): """ Compares two matrices with an imperfection tolerance @@ -306,6 +311,33 @@ def attribute_values(attr_values): cmds.setAttr(attr, value) +@contextlib.contextmanager +def keytangent_default(in_tangent_type='auto', + out_tangent_type='auto'): + """Set the default keyTangent for new keys during this context""" + + original_itt = cmds.keyTangent(query=True, g=True, itt=True)[0] + original_ott = cmds.keyTangent(query=True, g=True, ott=True)[0] + cmds.keyTangent(g=True, itt=in_tangent_type) + cmds.keyTangent(g=True, ott=out_tangent_type) + try: + yield + finally: + cmds.keyTangent(g=True, itt=original_itt) + cmds.keyTangent(g=True, ott=original_ott) + + +@contextlib.contextmanager +def undo_chunk(): + """Open a undo chunk during context.""" + + try: + cmds.undoInfo(openChunk=True) + yield + finally: + cmds.undoInfo(closeChunk=True) + + @contextlib.contextmanager def renderlayer(layer): """Set the renderlayer during the context""" @@ -339,6 +371,126 @@ def evaluation(mode="off"): cmds.evaluationManager(mode=original) +@contextlib.contextmanager +def no_refresh(): + """Temporarily disables Maya's UI updates + + Note: + This only disabled the main pane and will sometimes still + trigger updates in torn off panels. + + """ + + pane = _get_mel_global('gMainPane') + state = cmds.paneLayout(pane, query=True, manage=True) + cmds.paneLayout(pane, edit=True, manage=False) + + try: + yield + finally: + cmds.paneLayout(pane, edit=True, manage=state) + + +@contextlib.contextmanager +def empty_sets(sets, force=False): + """Remove all members of the sets during the context""" + + assert isinstance(sets, (list, tuple)) + + original = dict() + original_connections = [] + + # Store original state + for obj_set in sets: + members = cmds.sets(obj_set, query=True) + original[obj_set] = members + + try: + for obj_set in sets: + cmds.sets(clear=obj_set) + if force: + # Break all connections if force is enabled, this way we + # prevent Maya from exporting any reference nodes which are + # connected with placeHolder[x] attributes + plug = "%s.dagSetMembers" % obj_set + connections = cmds.listConnections(plug, + source=True, + destination=False, + plugs=True, + connections=True) or [] + original_connections.extend(connections) + for dest, src in pairwise(connections): + cmds.disconnectAttr(src, dest) + yield + finally: + + for dest, src in pairwise(original_connections): + cmds.connectAttr(src, dest) + + # Restore original members + for origin_set, members in original.iteritems(): + cmds.sets(members, forceElement=origin_set) + + +@contextlib.contextmanager +def renderlayer(layer): + """Set the renderlayer during the context + + Arguments: + layer (str): Name of layer to switch to. + + """ + + original = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) + + try: + cmds.editRenderLayerGlobals(currentRenderLayer=layer) + yield + finally: + cmds.editRenderLayerGlobals(currentRenderLayer=original) + + +class delete_after(object): + """Context Manager that will delete collected nodes after exit. + + This allows to ensure the nodes added to the context are deleted + afterwards. This is useful if you want to ensure nodes are deleted + even if an error is raised. + + Examples: + with delete_after() as delete_bin: + cube = maya.cmds.polyCube() + delete_bin.extend(cube) + # cube exists + # cube deleted + + """ + + def __init__(self, nodes=None): + + self._nodes = list() + + if nodes: + self.extend(nodes) + + def append(self, node): + self._nodes.append(node) + + def extend(self, nodes): + self._nodes.extend(nodes) + + def __iter__(self): + return iter(self._nodes) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + if self._nodes: + cmds.delete(self._nodes) + + def get_renderer(layer): with renderlayer(layer): return cmds.getAttr("defaultRenderGlobals.currentRenderer") @@ -367,6 +519,157 @@ def no_undo(flush=False): cmds.undoInfo(**{keyword: original}) +def get_shader_assignments_from_shapes(shapes): + """Return the shape assignment per related shading engines. + + Returns a dictionary where the keys are shadingGroups and the values are + lists of assigned shapes or shape-components. + + For the 'shapes' this will return a dictionary like: + { + "shadingEngineX": ["nodeX", "nodeY"], + "shadingEngineY": ["nodeA", "nodeB"] + } + + Args: + shapes (list): The shapes to collect the assignments for. + + Returns: + dict: The {shadingEngine: shapes} relationships + + """ + + shapes = cmds.ls(shapes, + long=True, + selection=True, + shapes=True, + objectsOnly=True) + if not shapes: + return {} + + # Collect shading engines and their shapes + assignments = defaultdict(list) + for shape in shapes: + + # Get unique shading groups for the shape + shading_groups = cmds.listConnections(shape, + type="shadingEngine") or [] + shading_groups = list(set(shading_groups)) + for shading_group in shading_groups: + assignments[shading_group].add(shape) + + return dict(assignments) + + +@contextlib.contextmanager +def shader(nodes, shadingEngine="initialShadingGroup"): + """Assign a shader to nodes during the context""" + + shapes = cmds.ls(nodes, dag=1, o=1, shapes=1, long=1) + original = get_shader_assignments_from_shapes(shapes) + + try: + # Assign override shader + if shapes: + cmds.sets(shapes, edit=True, forceElement=shadingEngine) + yield + finally: + + # Assign original shaders + for sg, members in original.items(): + if members: + cmds.sets(shapes, edit=True, forceElement=shadingEngine) + + +@contextlib.contextmanager +def displaySmoothness(nodes, + divisionsU=0, + divisionsV=0, + pointsWire=4, + pointsShaded=1, + polygonObject=1): + """Set the displaySmoothness during the context""" + + # Ensure only non-intermediate shapes + nodes = cmds.ls(nodes, + dag=1, + shapes=1, + long=1, + noIntermediate=True) + + def parse(node): + """Parse the current state of a node""" + state = {} + for key in ["divisionsU", + "divisionsV", + "pointsWire", + "pointsShaded", + "polygonObject"]: + value = cmds.displaySmoothness(node, query=1, **{key: True}) + if value is not None: + state[key] = value[0] + return state + + originals = dict((node, parse(node)) for node in nodes) + + try: + # Apply current state + cmds.displaySmoothness(nodes, + divisionsU=divisionsU, + divisionsV=divisionsV, + pointsWire=pointsWire, + pointsShaded=pointsShaded, + polygonObject=polygonObject) + yield + finally: + # Revert state + for node, state in originals.iteritems(): + if state: + cmds.displaySmoothness(node, **state) + + +@contextlib.contextmanager +def no_display_layers(nodes): + """Ensure nodes are not in a displayLayer during context. + + Arguments: + nodes (list): The nodes to remove from any display layer. + + """ + + # Ensure long names + nodes = cmds.ls(nodes, long=True) + + # Get the original state + lookup = set(nodes) + original = {} + for layer in cmds.ls(type='displayLayer'): + + # Skip default layer + if layer == "defaultLayer": + continue + + members = cmds.editDisplayLayerMembers(layer, + query=True, + fullNames=True) + if not members: + continue + members = set(members) + + included = lookup.intersection(members) + if included: + original[layer] = list(included) + + try: + # Add all nodes to default layer + cmds.editDisplayLayerMembers("defaultLayer", nodes, noRecurse=True) + yield + finally: + # Restore original members + for layer, members in original.iteritems(): + cmds.editDisplayLayerMembers(layer, members, noRecurse=True) + + @contextlib.contextmanager def namespaced(namespace, new=True): """Work inside namespace during context @@ -1534,3 +1837,193 @@ def validate_fps(): return False return True + + +def bake(nodes, + frame_range=None, + step=1.0, + simulation=True, + preserve_outside_keys=False, + disable_implicit_control=True, + shape=True): + """Bake the given nodes over the time range. + + This will bake all attributes of the node, including custom attributes. + + Args: + nodes (list): Names of transform nodes, eg. camera, light. + frame_range (tuple): frame range with start and end frame. + or if None then takes timeSliderRange + simulation (bool): Whether to perform a full simulation of the + attributes over time. + preserve_outside_keys (bool): Keep keys that are outside of the baked + range. + disable_implicit_control (bool): When True will disable any + constraints to the object. + shape (bool): When True also bake attributes on the children shapes. + step (float): The step size to sample by. + + Returns: + None + + """ + + # Parse inputs + if not nodes: + return + + assert isinstance(nodes, (list, tuple)), "Nodes must be a list or tuple" + + # If frame range is None fall back to time slider range + if frame_range is None: + frame_range = getTimeSliderRange() + + # If frame range is single frame bake one frame more, + # otherwise maya.cmds.bakeResults gets confused + if frame_range[1] == frame_range[0]: + frame_range[1] += 1 + + # Bake it + with keytangent_default(in_tangent_type='auto', + out_tangent_type='auto'): + cmds.bakeResults(nodes, + simulation=simulation, + preserveOutsideKeys=preserve_outside_keys, + disableImplicitControl=disable_implicit_control, + shape=shape, + sampleBy=step, + time=(frame_range[0], frame_range[1])) + + +def bake_to_world_space(nodes, + frameRange=None, + simulation=True, + preserveOutsideKeys=False, + disableImplicitControl=True, + shape=True, + step=1.0): + """Bake the nodes to world space transformation (incl. other attributes) + + Bakes the transforms to world space (while maintaining all its animated + attributes and settings) by duplicating the node. Then parents it to world + and constrains to the original. + + Other attributes are also baked by connecting all attributes directly. + Baking is then done using Maya's bakeResults command. + + See `bake` for the argument documentation. + + Returns: + list: The newly created and baked node names. + + """ + + def _get_attrs(node): + """Workaround for buggy shape attribute listing with listAttr""" + attrs = cmds.listAttr(node, + write=True, + scalar=True, + settable=True, + connectable=True, + keyable=True, + shortNames=True) or [] + valid_attrs = [] + for attr in attrs: + node_attr = '{0}.{1}'.format(node, attr) + + # Sometimes Maya returns 'non-existent' attributes for shapes + # so we filter those out + if not cmds.attributeQuery(attr, node=node, exists=True): + continue + + # We only need those that have a connection, just to be safe + # that it's actually keyable/connectable anyway. + if cmds.connectionInfo(node_attr, + isDestination=True): + valid_attrs.append(attr) + + return valid_attrs + + transform_attrs = set(["t", "r", "s", + "tx", "ty", "tz", + "rx", "ry", "rz", + "sx", "sy", "sz"]) + + world_space_nodes = [] + with delete_after() as delete_bin: + + # Create the duplicate nodes that are in world-space connected to + # the originals + for node in nodes: + + # Duplicate the node + short_name = node.rsplit("|", 1)[-1] + new_name = "{0}_baked".format(short_name) + new_node = cmds.duplicate(node, + name=new_name, + renameChildren=True)[0] + + # Connect all attributes on the node except for transform + # attributes + attrs = _get_attrs(node) + attrs = set(attrs) - transform_attrs if attrs else [] + + for attr in attrs: + orig_node_attr = '{0}.{1}'.format(node, attr) + new_node_attr = '{0}.{1}'.format(new_node, attr) + + # unlock to avoid connection errors + cmds.setAttr(new_node_attr, lock=False) + + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) + + # If shapes are also baked then connect those keyable attributes + if shape: + children_shapes = cmds.listRelatives(new_node, + children=True, + fullPath=True, + shapes=True) + if children_shapes: + orig_children_shapes = cmds.listRelatives(node, + children=True, + fullPath=True, + shapes=True) + for orig_shape, new_shape in zip(orig_children_shapes, + children_shapes): + attrs = _get_attrs(orig_shape) + for attr in attrs: + orig_node_attr = '{0}.{1}'.format(orig_shape, attr) + new_node_attr = '{0}.{1}'.format(new_shape, attr) + + # unlock to avoid connection errors + cmds.setAttr(new_node_attr, lock=False) + + cmds.connectAttr(orig_node_attr, + new_node_attr, + force=True) + + # Parent to world + if cmds.listRelatives(new_node, parent=True): + new_node = cmds.parent(new_node, world=True)[0] + + # Unlock transform attributes so constraint can be created + for attr in transform_attrs: + cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False) + + # Constraints + delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) + delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) + + world_space_nodes.append(new_node) + + bake(world_space_nodes, + frame_range=frameRange, + step=step, + simulation=simulation, + preserve_outside_keys=preserveOutsideKeys, + disable_implicit_control=disableImplicitControl, + shape=shape) + + return world_space_nodes diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 94c1a82225..1a87744d38 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -22,6 +22,6 @@ class CreateCamera(avalon.maya.Creator): # Bake to world space by default, when this is False it will also # include the parent hierarchy in the baked results - data['bakeToWorldSpace'] = True + data['bake_to_world_space'] = True self.data = data diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/colorbleed/plugins/maya/publish/collect_look.py index 93080b5312..1decca6e2e 100644 --- a/colorbleed/plugins/maya/publish/collect_look.py +++ b/colorbleed/plugins/maya/publish/collect_look.py @@ -1,7 +1,10 @@ +import re +import os +import glob + from maya import cmds import pyblish.api import colorbleed.maya.lib as lib -from cb.utils.maya import context, shaders SHAPE_ATTRS = ["castsShadows", "receiveShadows", @@ -48,6 +51,139 @@ def get_look_attrs(node): return result +def node_uses_image_sequence(node): + """Return whether file node uses an image sequence or single image. + + Determine if a node uses an image sequence or just a single image, + not always obvious from its file path alone. + + Args: + node (str): Name of the Maya node + + Returns: + bool: True if node uses an image sequence + + """ + + # useFrameExtension indicates an explicit image sequence + node_path = get_file_node_path(node).lower() + + # The following tokens imply a sequence + patterns = ["", "", "", "u_v", ".tif will return as /path/to/texture.*.tif. + + Args: + path (str): the image sequence path + + Returns: + str: Return glob string that matches the filename pattern. + + """ + + if path is None: + return path + + # If any of the patterns, convert the pattern + patterns = { + "": "", + "": "", + "": "", + "#": "#", + "u_v": "|", + "", + "": "" + } + + lower = path.lower() + has_pattern = False + for pattern, regex_pattern in patterns.items(): + if pattern in lower: + path = re.sub(regex_pattern, "*", path, flags=re.IGNORECASE) + has_pattern = True + + if has_pattern: + return path + + base = os.path.basename(path) + matches = list(re.finditer(r'\d+', base)) + if matches: + match = matches[-1] + new_base = '{0}*{1}'.format(base[:match.start()], + base[match.end():]) + head = os.path.dirname(path) + return os.path.join(head, new_base) + else: + return path + + +def get_file_node_path(node): + """Get the file path used by a Maya file node. + + Args: + node (str): Name of the Maya file node + + Returns: + str: the file path in use + + """ + # if the path appears to be sequence, use computedFileTextureNamePattern, + # this preserves the <> tag + if cmds.attributeQuery('computedFileTextureNamePattern', + node=node, + exists=True): + plug = '{0}.computedFileTextureNamePattern'.format(node) + texture_pattern = cmds.getAttr(plug) + + patterns = ["", + "", + "u_v", + "", + ""] + lower = texture_pattern.lower() + if any(pattern in lower for pattern in patterns): + return texture_pattern + + # otherwise use fileTextureName + return cmds.getAttr('{0}.fileTextureName'.format(node)) + + +def get_file_node_files(node): + """Return the file paths related to the file node + + Note: + Will only return existing files. Returns an empty list + if not valid existing files are linked. + + Returns: + list: List of full file paths. + + """ + + path = get_file_node_path(node) + path = cmds.workspace(expandName=path) + if node_uses_image_sequence(node): + glob_pattern = seq_to_glob(path) + return glob.glob(glob_pattern) + elif os.path.exists(path): + return [path] + else: + return [] + + class CollectLook(pyblish.api.InstancePlugin): """Collect look data for instance. @@ -74,7 +210,7 @@ class CollectLook(pyblish.api.InstancePlugin): def process(self, instance): """Collect the Look in the instance with the correct layer settings""" - with context.renderlayer(instance.data["renderlayer"]): + with lib.renderlayer(instance.data["renderlayer"]): self.collect(instance) def collect(self, instance): @@ -268,7 +404,7 @@ class CollectLook(pyblish.api.InstancePlugin): # paths as the computed patterns source = source.replace("\\", "/") - files = shaders.get_file_node_files(node) + files = get_file_node_files(node) if len(files) == 0: self.log.error("No valid files found from node `%s`" % node) diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index 2b18ced96f..efacf7b528 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -5,14 +5,14 @@ from maya import cmds import avalon.maya import colorbleed.api -import cb.utils.maya.context as context +import colorbleed.maya.lib as lib class ExtractCameraAlembic(colorbleed.api.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's - `bakeToWorldSpace` is set to False it will include its full hierarchy. + `bake_to_world_space` is set to False it will include its full hierarchy. """ @@ -27,7 +27,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): instance.data.get("endFrame", 1)] handles = instance.data.get("handles", 0) step = instance.data.get("step", 1.0) - bake_to_worldspace = instance.data("bakeToWorldSpace", True) + bake_to_worldspace = instance.data("bake_to_world_space", True) # get cameras members = instance.data['setMembers'] @@ -66,8 +66,8 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): job_str += ' -file "{0}"'.format(path) - with context.evaluation("off"): - with context.no_refresh(): + with lib.evaluation("off"): + with lib.no_refresh(): cmds.AbcExport(j=job_str, verbose=False) if "files" not in instance.data: diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index c29aaed4fe..bd0eeba05f 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -1,13 +1,11 @@ import os -from itertools import izip_longest from maya import cmds import avalon.maya import colorbleed.api - -import cb.utils.maya.context as context -from cb.utils.maya.animation import bakeToWorldSpace +from colorbleed.lib import grouper +from colorbleed.maya import lib def massage_ma_file(path): @@ -36,18 +34,6 @@ def massage_ma_file(path): f.close() -def grouper(iterable, n, fillvalue=None): - """Collect data into fixed-length chunks or blocks - - Examples: - grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx - - """ - - args = [iter(iterable)] * n - return izip_longest(fillvalue=fillvalue, *args) - - def unlock(plug): """Unlocks attribute and disconnects inputs for a plug. @@ -87,7 +73,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): will be published. The cameras gets baked to world space by default. Only when the instance's - `bakeToWorldSpace` is set to False it will include its full hierarchy. + `bake_to_world_space` is set to False it will include its full hierarchy. Note: The extracted Maya ascii file gets "massaged" removing the uuid values @@ -106,12 +92,12 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): instance.data.get("endFrame", 1)] handles = instance.data.get("handles", 0) step = instance.data.get("step", 1.0) - bake_to_worldspace = instance.data("bakeToWorldSpace", True) + bake_to_worldspace = instance.data("bake_to_world_space", True) # TODO: Implement a bake to non-world space # Currently it will always bake the resulting camera to world-space # and it does not allow to include the parent hierarchy, even though - # with `bakeToWorldSpace` set to False it should include its hierarchy + # with `bake_to_world_space` set to False it should include its hierarchy # to be correct with the family implementation. if not bake_to_worldspace: self.log.warning("Camera (Maya Ascii) export only supports world" @@ -140,11 +126,13 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): # Perform extraction self.log.info("Performing camera bakes for: {0}".format(transform)) with avalon.maya.maintained_selection(): - with context.evaluation("off"): - with context.no_refresh(): - baked = bakeToWorldSpace(transform, - frameRange=range_with_handles, - step=step) + with lib.evaluation("off"): + with lib.no_refresh(): + baked = lib.bake_to_worldspace( + transform, + frameRange=range_with_handles, + step=step + ) baked_shapes = cmds.ls(baked, type="camera", dag=True, diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index 7e8fc2b436..c359fc10b5 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -6,10 +6,9 @@ from maya import cmds import pyblish.api import avalon.maya -import colorbleed.api -import colorbleed.maya.lib as maya -from cb.utils.maya import context +import colorbleed.api +import colorbleed.maya.lib as lib class ExtractLook(colorbleed.api.Extractor): @@ -63,10 +62,10 @@ class ExtractLook(colorbleed.api.Extractor): # Extract in correct render layer layer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(layer): + with lib.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides - with context.empty_sets(sets, force=True): - with maya.attribute_values(remap): + with lib.empty_sets(sets, force=True): + with lib.attribute_values(remap): with avalon.maya.maintained_selection(): cmds.select(sets, noExpand=True) cmds.file(maya_path, diff --git a/colorbleed/plugins/maya/publish/extract_model.py b/colorbleed/plugins/maya/publish/extract_model.py index e1be53d59a..466ee73174 100644 --- a/colorbleed/plugins/maya/publish/extract_model.py +++ b/colorbleed/plugins/maya/publish/extract_model.py @@ -4,8 +4,7 @@ from maya import cmds import avalon.maya import colorbleed.api - -from cb.utils.maya import context +import colorbleed.maya.lib as lib class ExtractModel(colorbleed.api.Extractor): @@ -47,15 +46,15 @@ class ExtractModel(colorbleed.api.Extractor): noIntermediate=True, long=True) - with context.no_display_layers(instance): - with context.displaySmoothness(members, - divisionsU=0, - divisionsV=0, - pointsWire=4, - pointsShaded=1, - polygonObject=1): - with context.shader(members, - shadingEngine="initialShadingGroup"): + with lib.no_display_layers(instance): + with lib.displaySmoothness(members, + divisionsU=0, + divisionsV=0, + pointsWire=4, + pointsShaded=1, + polygonObject=1): + with lib.shader(members, + shadingEngine="initialShadingGroup"): with avalon.maya.maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 31884ab48f..aeec58674a 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -4,8 +4,6 @@ from colorbleed.maya import lib import pyblish.api import colorbleed.api -from cb.utils.maya import context - class ValidateLookSets(pyblish.api.InstancePlugin): """Validate if any sets are missing from the instance and look data @@ -57,7 +55,7 @@ class ValidateLookSets(pyblish.api.InstancePlugin): invalid = [] renderlayer = instance.data.get("renderlayer", "defaultRenderLayer") - with context.renderlayer(renderlayer): + with lib.renderlayer(renderlayer): for node in instance: # get the connected objectSets of the node sets = lib.get_related_sets(node) diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/colorbleed/plugins/maya/publish/validate_rig_controllers.py index 6117f46597..ecb3b687d1 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers.py @@ -1,10 +1,10 @@ from maya import cmds import pyblish.api -import colorbleed.api -from cb.utils.maya.context import undo_chunk +import colorbleed.api import colorbleed.maya.action +from colorbleed.maya.lib import undo_chunk class ValidateRigControllers(pyblish.api.InstancePlugin): diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py index 6655e59a67..43bfde918f 100644 --- a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py +++ b/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py @@ -2,8 +2,8 @@ from maya import cmds import pyblish.api import colorbleed.api -from cb.utils.maya.context import undo_chunk +import colorbleed.maya.lib as lib import colorbleed.maya.action @@ -83,7 +83,7 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): def repair(cls, instance): invalid = cls.get_invalid(instance) - with undo_chunk(): + with lib.undo_chunk(): for node in invalid: for attribute in cls.attributes: if cmds.attributeQuery(attribute, node=node, exists=True): From 959e35b3b4028b0d7d7d43e6441289a07118b3f0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:02:27 +0200 Subject: [PATCH 1092/1249] Fix missing pairwise function --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index dcb9a91620..9727666765 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -419,12 +419,12 @@ def empty_sets(sets, force=False): plugs=True, connections=True) or [] original_connections.extend(connections) - for dest, src in pairwise(connections): + for dest, src in lib.pairwise(connections): cmds.disconnectAttr(src, dest) yield finally: - for dest, src in pairwise(original_connections): + for dest, src in lib.pairwise(original_connections): cmds.connectAttr(src, dest) # Restore original members From 4e760c593d2547d3b8772f3dc630fc1823ea1bc7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:02:49 +0200 Subject: [PATCH 1093/1249] Make listConnections call for getting shaders more explicit --- colorbleed/maya/lib.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 9727666765..3e6e7cb9d0 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -553,6 +553,10 @@ def get_shader_assignments_from_shapes(shapes): # Get unique shading groups for the shape shading_groups = cmds.listConnections(shape, + source=False, + destination=True, + plugs=False, + connections=False, type="shadingEngine") or [] shading_groups = list(set(shading_groups)) for shading_group in shading_groups: From 3de333ef67215978fc3c2a59ee79ce265653b4a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:12:52 +0200 Subject: [PATCH 1094/1249] Fix baking to world space --- colorbleed/maya/lib.py | 7 ++++--- .../plugins/maya/publish/extract_camera_mayaAscii.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 3e6e7cb9d0..fe2ac48035 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1856,7 +1856,7 @@ def bake(nodes, Args: nodes (list): Names of transform nodes, eg. camera, light. - frame_range (tuple): frame range with start and end frame. + frame_range (list): frame range with start and end frame. or if None then takes timeSliderRange simulation (bool): Whether to perform a full simulation of the attributes over time. @@ -1878,9 +1878,10 @@ def bake(nodes, assert isinstance(nodes, (list, tuple)), "Nodes must be a list or tuple" - # If frame range is None fall back to time slider range + # If frame range is None fall back to time slider playback time range if frame_range is None: - frame_range = getTimeSliderRange() + frame_range = [cmds.playbackOptions(query=True, minTime=True), + cmds.playbackOptions(query=True, maxTime=True)] # If frame range is single frame bake one frame more, # otherwise maya.cmds.bakeResults gets confused diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index bd0eeba05f..47eef59221 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -97,8 +97,8 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): # TODO: Implement a bake to non-world space # Currently it will always bake the resulting camera to world-space # and it does not allow to include the parent hierarchy, even though - # with `bake_to_world_space` set to False it should include its hierarchy - # to be correct with the family implementation. + # with `bake_to_world_space` set to False it should include its + # hierarchy to be correct with the family implementation. if not bake_to_worldspace: self.log.warning("Camera (Maya Ascii) export only supports world" "space baked camera extractions. The disabled " @@ -128,7 +128,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): with avalon.maya.maintained_selection(): with lib.evaluation("off"): with lib.no_refresh(): - baked = lib.bake_to_worldspace( + baked = lib.bake_to_world_space( transform, frameRange=range_with_handles, step=step From 4a5c4dafd1c684704be53c96e48b647371c2e1c4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:15:31 +0200 Subject: [PATCH 1095/1249] Cosmetics / PEP08 / consistency with 'bake' function --- colorbleed/maya/lib.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index fe2ac48035..5a6aed8d01 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1901,10 +1901,10 @@ def bake(nodes, def bake_to_world_space(nodes, - frameRange=None, + frame_range=None, simulation=True, - preserveOutsideKeys=False, - disableImplicitControl=True, + preserve_outside_keys=False, + disable_implicit_control=True, shape=True, step=1.0): """Bake the nodes to world space transformation (incl. other attributes) @@ -2024,11 +2024,11 @@ def bake_to_world_space(nodes, world_space_nodes.append(new_node) bake(world_space_nodes, - frame_range=frameRange, + frame_range=frame_range, step=step, simulation=simulation, - preserve_outside_keys=preserveOutsideKeys, - disable_implicit_control=disableImplicitControl, + preserve_outside_keys=preserve_outside_keys, + disable_implicit_control=disable_implicit_control, shape=shape) return world_space_nodes From 25365e872756dad2855559a0b38b01e225c45053 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:56:02 +0200 Subject: [PATCH 1096/1249] Cosmetics / PEP08 / consistency with 'bake' function --- colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index 47eef59221..a600c6c5a0 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -130,7 +130,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): with lib.no_refresh(): baked = lib.bake_to_world_space( transform, - frameRange=range_with_handles, + frame_range=range_with_handles, step=step ) baked_shapes = cmds.ls(baked, From cc1ca111cf5a509ffad1bce9c3a32c52af5694bc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 13:57:49 +0200 Subject: [PATCH 1097/1249] Revert back key change on "bakeToWorldSpace" in instance --- colorbleed/plugins/maya/create/colorbleed_camera.py | 2 +- colorbleed/plugins/maya/publish/extract_camera_alembic.py | 4 ++-- colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 1a87744d38..94c1a82225 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -22,6 +22,6 @@ class CreateCamera(avalon.maya.Creator): # Bake to world space by default, when this is False it will also # include the parent hierarchy in the baked results - data['bake_to_world_space'] = True + data['bakeToWorldSpace'] = True self.data = data diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index efacf7b528..0b315a8d6b 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -12,7 +12,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's - `bake_to_world_space` is set to False it will include its full hierarchy. + `bakeToWorldSpace` is set to False it will include its full hierarchy. """ @@ -27,7 +27,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): instance.data.get("endFrame", 1)] handles = instance.data.get("handles", 0) step = instance.data.get("step", 1.0) - bake_to_worldspace = instance.data("bake_to_world_space", True) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) # get cameras members = instance.data['setMembers'] diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index a600c6c5a0..09e1d535e4 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -73,7 +73,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): will be published. The cameras gets baked to world space by default. Only when the instance's - `bake_to_world_space` is set to False it will include its full hierarchy. + `bakeToWorldSpace` is set to False it will include its full hierarchy. Note: The extracted Maya ascii file gets "massaged" removing the uuid values @@ -92,12 +92,12 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): instance.data.get("endFrame", 1)] handles = instance.data.get("handles", 0) step = instance.data.get("step", 1.0) - bake_to_worldspace = instance.data("bake_to_world_space", True) + bake_to_worldspace = instance.data("bakeToWorldSpace", True) # TODO: Implement a bake to non-world space # Currently it will always bake the resulting camera to world-space # and it does not allow to include the parent hierarchy, even though - # with `bake_to_world_space` set to False it should include its + # with `bakeToWorldSpace` set to False it should include its # hierarchy to be correct with the family implementation. if not bake_to_worldspace: self.log.warning("Camera (Maya Ascii) export only supports world" From cf524eac6a4627beadf38efacad2d93b0dab4190 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 15:57:00 +0200 Subject: [PATCH 1098/1249] Fix PLN-149 for Deadline renders, avoid Redshift creating .lock files --- colorbleed/plugins/maya/publish/submit_deadline.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index dbb987a603..45ef683757 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -207,6 +207,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # todo: This is a temporary fix for yeti variables "PEREGRINEL_LICENSE", "REDSHIFT_MAYAEXTENSIONSPATH", + "REDSHIFT_DISABLEOUTPUTLOCKFILES" "VRAY_FOR_MAYA2018_PLUGINS_X64", "VRAY_PLUGINS_X64", "VRAY_USE_THREAD_AFFINITY", From 729617989c52bba297c6ff235a8c07c97eb5ed79 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Oct 2018 18:56:39 +0200 Subject: [PATCH 1099/1249] Remove redundant repair method (dead code) and docstring tweak --- .../maya/publish/validate_look_sets.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index aeec58674a..1e7645bfaf 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -8,25 +8,31 @@ import colorbleed.api class ValidateLookSets(pyblish.api.InstancePlugin): """Validate if any sets are missing from the instance and look data - A node might have a relationship with a shader but has no Colorbleed ID. + A shader can be assigned to a node that is missing a Colorbleed ID. Because it is missing the ID it has not been collected in the instance. + This validator ensures no relationships and thus considers it invalid + if a relationship was not collected. + When the relationship needs to be maintained the artist might need to create a different* relationship or ensure the node has the Colorbleed ID. - * The relationship might be too broad (assigned to top node if hierarchy). + *The relationship might be too broad (assigned to top node of hierarchy). This can be countered by creating the relationship on the shape or its - transform. - In essence, ensure item the shader is assigned to has the Colorbleed ID! + transform. In essence, ensure item the shader is assigned to has the + Colorbleed ID! - Displacement shaders: - Ensure all geometry is added to the displacement objectSet. - It is best practice to add the transform group of the shape to the - displacement objectSet - Example content: - [asset_GRP|geometry_GRP|body_GES, - asset_GRP|geometry_GRP|L_eye_GES, - asset_GRP|geometry_GRP|R_eye_GES, - asset_GRP|geometry_GRP|wings_GEO] + Examples: + + - Displacement objectSets (like V-Ray): + + It is best practice to add the transform group of the shape to the + displacement objectSet. + + Example content: + [asset_GRP|geometry_GRP|body_GES, + asset_GRP|geometry_GRP|L_eye_GES, + asset_GRP|geometry_GRP|R_eye_GES, + asset_GRP|geometry_GRP|wings_GEO] """ @@ -90,7 +96,3 @@ class ValidateLookSets(pyblish.api.InstancePlugin): continue return invalid - - @classmethod - def repair(cls, context, instance): - pass From 51995c6246a7e1aac60bd9dd52e2e3f7601bd084 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Oct 2018 10:58:26 +0200 Subject: [PATCH 1100/1249] REN-55: Avoid scene open/save/new callbacks during Maya batch rendering. --- colorbleed/maya/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 3f75ffd872..86106ee593 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -5,6 +5,7 @@ import weakref from maya import utils, cmds, mel from avalon import api as avalon, pipeline, maya +from avalon.maya.pipeline import IS_HEADLESS from pyblish import api as pyblish from ..lib import ( @@ -34,12 +35,18 @@ def install(): log.info("Installing callbacks ... ") avalon.on("init", on_init) + + # Callbacks below are not required for headless mode, the `init` however + # is important to load referenced Alembics correctly at rendertime. + if IS_HEADLESS: + log.info("Running in headless mode, skipping Colorbleed Maya " + "save/open/new callback installation..") + return + avalon.on("save", on_save) avalon.on("open", on_open) - - avalon.before("save", on_before_save) - avalon.on("new", on_new) + avalon.before("save", on_before_save) log.info("Overriding existing event 'taskChanged'") override_event("taskChanged", on_task_changed) From 437a9c37f3b7184f33bb24695e17fac7b30300c4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Oct 2018 08:54:45 +0200 Subject: [PATCH 1101/1249] More consistent logging message (cosmetics) --- colorbleed/maya/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 5a6aed8d01..2ca874dd7d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -1721,7 +1721,7 @@ def set_scene_fps(fps, update=True): current_frame = cmds.currentTime(query=True) - log.info("Updating FPS to '{}'".format(unit)) + log.info("Setting scene FPS to: '{}'".format(unit)) cmds.currentUnit(time=unit, updateAnimation=update) # Set time slider data back to previous state @@ -1762,7 +1762,7 @@ def set_scene_resolution(width, height): log.error("Can't set VRay resolution because there is no node " "named: `%s`" % vray_node) - log.info("Setting project resolution to: %s x %s" % (width, height)) + log.info("Setting scene resolution to: %s x %s" % (width, height)) cmds.setAttr("%s.width" % control_node, width) cmds.setAttr("%s.height" % control_node, height) From fe73f55a5d506b65919f19b7390b3124c80127b3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 09:26:24 +0200 Subject: [PATCH 1102/1249] updated tools --- colorbleed/maya/menu.json | 3726 ++++++++++++++++++------------------- 1 file changed, 1767 insertions(+), 1959 deletions(-) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index d394f5dba1..0c620d3e4c 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1,1959 +1,1767 @@ -[ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", - "sourcetype": "file", - "title": "Version Up", - "tooltip": "Incremental save with a specific format" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\show_current_scene_in_explorer.py", - "sourcetype": "file", - "title": "Explore current scene..", - "tooltip": "Show current scene in Explorer" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\avalon\\launch_manager.py", - "sourcetype": "file", - "title": "Project Manager", - "tooltip": "Add assets to the project" - }, - { - "type": "separator" - }, - { - "type": "menu", - "title": "Modeling", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", - "sourcetype": "file", - "tags": [ - "modeling", - "duplicate", - "normalized" - ], - "title": "Duplicate Normalized", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", - "sourcetype": "file", - "tags": [ - "modeling", - "transfer", - "uv" - ], - "title": "Transfer UVs", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", - "sourcetype": "file", - "tags": [ - "modeling", - "mirror", - "symmetry" - ], - "title": "Mirror Symmetry", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI.py", - "sourcetype": "file", - "tags": [ - "modeling", - "select", - "outline", - "ui" - ], - "title": "Select Outline UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", - "sourcetype": "file", - "tags": [ - "modeling", - "polygon", - "uvset", - "delete" - ], - "title": "Polygon Delete Other UV Sets", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", - "sourcetype": "file", - "tags": [ - "modeling", - "combine", - "polygon", - "quick" - ], - "title": "Polygon Combine Quick", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\separateMeshPerShader.py", - "sourcetype": "file", - "tags": [ - "modeling", - "separateMeshPerShader" - ], - "title": "Separate Mesh Per Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", - "sourcetype": "file", - "tags": [ - "modeling", - "poly", - "detach", - "separate" - ], - "title": "Polygon Detach and Separate", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", - "sourcetype": "file", - "tags": [ - "modeling", - "relax", - "verts" - ], - "title": "Polygon Relax Vertices", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", - "sourcetype": "file", - "tags": [ - "modeling", - "select", - "nth", - "edge", - "ui" - ], - "title": "Select Every Nth Edge" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", - "sourcetype": "file", - "tags": [ - "modeling", - "djPFX", - "UVs" - ], - "title": "dj PFX UVs", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Rigging", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", - "sourcetype": "file", - "tags": [ - "rigging", - "addCurveBetween", - "file" - ], - "title": "Add Curve Between" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", - "sourcetype": "file", - "tags": [ - "rigging", - "average", - "skin weights", - "file" - ], - "title": "Average Skin Weights" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "cbSmoothSkinWeightUI", - "file" - ], - "title": "CB Smooth Skin Weight UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "channelBoxManagerUI", - "file" - ], - "title": "Channel Box Manager UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", - "sourcetype": "file", - "tags": [ - "rigging", - "characterAutorigger", - "file" - ], - "title": "Character Auto Rigger" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "connectUI", - "file" - ], - "title": "Connect UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", - "sourcetype": "file", - "tags": [ - "rigging", - "copySkinWeightsLocal", - "file" - ], - "title": "Copy Skin Weights Local" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", - "sourcetype": "file", - "tags": [ - "rigging", - "createCenterLocator", - "file" - ], - "title": "Create Center Locator" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", - "sourcetype": "file", - "tags": [ - "rigging", - "freezeTransformToGroup", - "file" - ], - "title": "Freeze Transform To Group" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", - "sourcetype": "file", - "tags": [ - "rigging", - "groupSelected", - "file" - ], - "title": "Group Selected" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", - "sourcetype": "file", - "tags": [ - "rigging", - "ikHandlePoleVectorLocator", - "file" - ], - "title": "IK Handle Pole Vector Locator" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "jointOrientUI", - "file" - ], - "title": "Joint Orient UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", - "sourcetype": "file", - "tags": [ - "rigging", - "jointsOnCurve", - "file" - ], - "title": "Joints On Curve" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", - "sourcetype": "file", - "tags": [ - "rigging", - "resetBindSelectedSkinJoints", - "file" - ], - "title": "Reset Bind Selected Skin Joints" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedComponents", - "file" - ], - "title": "Select Skincluster Joints From Selected Components" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", - "sourcetype": "file", - "tags": [ - "rigging", - "selectSkinclusterJointsFromSelectedMesh", - "file" - ], - "title": "Select Skincluster Joints From Selected Mesh" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setJointLabels", - "file" - ], - "title": "Set Joint Labels" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setJointOrientationFromCurrentRotation", - "file" - ], - "title": "Set Joint Orientation From Current Rotation" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setSelectedJointsOrientationZero", - "file" - ], - "title": "Set Selected Joints Orientation Zero" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", - "sourcetype": "file", - "tags": [ - "rigging", - "mirrorCurveShape", - "file" - ], - "title": "Mirror Curve Shape" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "setRotationOrderUI", - "file" - ], - "title": "Set Rotation Order UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "paintItNowUI", - "file" - ], - "title": "Paint It Now UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", - "sourcetype": "file", - "tags": [ - "rigging", - "parentScaleConstraint", - "file" - ], - "title": "Parent Scale Constraint" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", - "sourcetype": "file", - "tags": [ - "rigging", - "quickSetWeightsUI", - "file" - ], - "title": "Quick Set Weights UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", - "sourcetype": "file", - "tags": [ - "rigging", - "rapidRig", - "file" - ], - "title": "Rapid Rig" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", - "sourcetype": "file", - "tags": [ - "rigging", - "regenerate_blendshape_targets", - "file" - ], - "title": "Regenerate Blendshape Targets" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", - "sourcetype": "file", - "tags": [ - "rigging", - "removeRotationAxis", - "file" - ], - "title": "Remove Rotation Axis" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", - "sourcetype": "file", - "tags": [ - "rigging", - "resetBindSelectedMeshes", - "file" - ], - "title": "Reset Bind Selected Meshes" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", - "sourcetype": "file", - "tags": [ - "rigging", - "simpleControllerOnSelection", - "file" - ], - "title": "Simple Controller On Selection" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", - "sourcetype": "file", - "tags": [ - "rigging", - "simpleControllerOnSelectionHierarchy", - "file" - ], - "title": "Simple Controller On Selection Hierarchy" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", - "sourcetype": "file", - "tags": [ - "rigging", - "superRelativeCluster", - "file" - ], - "title": "Super Relative Cluster" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", - "sourcetype": "file", - "tags": [ - "rigging", - "tfSmoothSkinWeight", - "file" - ], - "title": "TF Smooth Skin Weight" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleIntermediates", - "file" - ], - "title": "Toggle Intermediates" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleSegmentScaleCompensate", - "file" - ], - "title": "Toggle Segment Scale Compensate" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", - "sourcetype": "file", - "tags": [ - "rigging", - "toggleSkinclusterDeformNormals", - "file" - ], - "title": "Toggle Skincluster Deform Normals" - } - ] - }, - { - "type": "menu", - "title": "Shading", - "items": [ - { - "type": "menu", - "title": "VRay", - "items": [ - { - "type": "action", - "title": "Import Proxies", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", - "sourcetype": "file", - "tags": ["shading", "vray", "import", "proxies"], - "tooltip": "" - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Select All GES", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "select All GES" - ] - }, - { - "type": "action", - "title": "Select All GES Under Selection", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "select", "all", "GES"] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Selection To VRay Mesh", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "selection", "vraymesh"] - }, - { - "type": "action", - "title": "Add VRay Round Edges Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "round edges", "attribute"] - }, - { - "type": "action", - "title": "Add Gamma", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", - "sourcetype": "file", - "tooltip": "", - "tags": ["shading", "vray", "add gamma"] - }, - { - "type": "separator" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", - "sourcetype": "file", - "title": "Select Unconnected Shader Materials", - "tags": [ - "shading", - "vray", - "select", - "vraymesh", - "materials", - "unconnected shader slots" - ], - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", - "sourcetype": "file", - "title": "Merge Similar VRay Mesh Materials", - "tags": [ - "shading", - "vray", - "Merge", - "VRayMesh", - "Materials" - ], - "tooltip": "" - }, - { - "type": "action", - "title": "Create Two Sided Material", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", - "sourcetype": "file", - "tooltip": "Creates two sided material for selected material and renames it", - "tags": [ - "shading", - "vray", - "two sided", - "material" - ] - }, - { - "type": "action", - "title": "Create Two Sided Material For Selected", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", - "sourcetype": "file", - "tooltip": "Select material to create a two sided version from it", - "tags": [ - "shading", - "vray", - "Create2SidedMtlForSelectedMtl.py" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add OpenSubdiv Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "open subdiv", - "attribute" - ] - }, - { - "type": "action", - "title": "Remove OpenSubdiv Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "opensubdiv", - "attributee" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add Subdivision Attribute", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVraySubdivisionAttribute" - ] - }, - { - "type": "action", - "title": "Remove Subdivision Attribute.py", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "remove", - "subdivision", - "attribute" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Add Vray Object Ids", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "add", - "object id" - ] - }, - { - "type": "action", - "title": "Add Vray Material Ids", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "addVrayMaterialIds.py" - ] - }, - { - "type": "separator" - }, - { - "type": "action", - "title": "Set Physical DOF Depth", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "physical", - "DOF ", - "Depth" - ] - }, - { - "type": "action", - "title": "Magic Vray Proxy UI", - "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "magicVrayProxyUI" - ] - } - ] - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", - "sourcetype": "file", - "tags": [ - "shading", - "lookdev", - "assign", - "shaders", - "prefix", - "filename", - "render" - ], - "title": "Set filename prefix", - "tooltip": "Set the render file name prefix." - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\look_manager_ui.py", - "sourcetype": "file", - "tags": [ - "shading", - "look", - "assign", - "shaders", - "auto" - ], - "title": "Look Manager", - "tooltip": "Open the Look Manager UI for look assignment" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", - "sourcetype": "file", - "tags": [ - "shading", - "light", - "link", - "ui" - ], - "title": "Light Link UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", - "sourcetype": "file", - "tags": [ - "shading", - "look", - "vray", - "displacement", - "shaders", - "auto" - ], - "title": "VRay Displ Viewer", - "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", - "sourcetype": "file", - "tags": [ - "shading", - "CLRImage", - "textures", - "preview" - ], - "title": "Set Texture Preview To CLRImage", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", - "sourcetype": "file", - "tags": [ - "shading", - "fix", - "DefaultShaderSet", - "Behavior" - ], - "title": "Fix Default Shader Set Behavior", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", - "sourcetype": "file", - "tags": [ - "shading", - "fix", - "Selected", - "Shapes", - "Reference", - "Assignments" - ], - "title": "Fix Shapes Reference Assignments", - "tooltip": "Select shapes to fix the reference assignments" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", - "sourcetype": "file", - "tags": [ - "shading", - "selectLambert1Members" - ], - "title": "Select Lambert1 Members", - "tooltip": "Selects all objects which have the Lambert1 shader assigned" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", - "sourcetype": "file", - "tags": [ - "shading", - "selectShapesWithoutShader" - ], - "title": "Select Shapes Without Shader", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", - "sourcetype": "file", - "tags": [ - "shading", - "fixRenderLayerOutAdjustmentErrors" - ], - "title": "Fix RenderLayer Out Adjustment Errors", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", - "sourcetype": "file", - "tags": [ - "shading", - "renderlayer", - "missing", - "reference", - "switch", - "layer" - ], - "title": "Fix RenderLayer Missing Referenced Nodes Overrides", - "tooltip": "" - }, - { - "type": "action", - "title": "Image 2 Tiled EXR", - "command": "$COLORBLEED_SCRIPTS\\shading\\open_img2exr.py", - "sourcetype": "file", - "tooltip": "", - "tags": [ - "shading", - "vray", - "exr" - ] - } - ] - }, - { - "type": "menu", - "title": "Rendering", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", - "sourcetype": "file", - "tags": [ - "settings", - "deadline", - "globals", - "render" - ], - "title": "DL Submission Settings UI", - "tooltip": "Open the Deadline Submission Settings UI" - } - ] - }, - { - "type": "menu", - "title": "Animation", - "items": [ - { - "type": "menu", - "title": "Attributes", - "tooltip": "", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes" - ], - "title": "Copy Values", - "tooltip": "Copy attribute values" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "incoming" - ], - "title": "Copy In Connections", - "tooltip": "Copy incoming connections" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "connections", - "out" - ], - "title": "Copy Out Connections", - "tooltip": "Copy outcoming connections" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "local" - ], - "title": "Copy Local Transfroms", - "tooltip": "Copy local transfroms" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "matrix" - ], - "title": "Copy Matrix Transfroms", - "tooltip": "Copy Matrix transfroms" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI" - ], - "title": "Copy Transforms UI", - "tooltip": "Open the Copy Transforms UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", - "sourcetype": "file", - "tags": [ - "animation", - "copy", - "attributes", - "transforms", - "UI", - "simple" - ], - "title": "Simple Copy UI", - "tooltip": "Open the simple Copy Transforms UI" - } - ] - }, - { - "type": "menu", - "title": "Optimize", - "tooltip": "Optimization scripts", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", - "sourcetype": "file", - "tags": [ - "animation", - "hierarchy", - "toggle", - "freeze" - ], - "title": "Toggle Freeze Hierarchy", - "tooltip": "Freeze and unfreeze hierarchy" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", - "sourcetype": "file", - "tags": [ - "animation", - "nucleus", - "toggle", - "parallel" - ], - "title": "Toggle Parallel Nucleus", - "tooltip": "Toggle parallel nucleus" - } - ] - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", - "tags": ["animation", "bake","selection", "worldspace.py"], - "title": "Bake Selected To Worldspace", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", - "tags": ["animation", "time","stepper"], - "title": "Time Stepper", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", - "tags": ["animation", "capture", "ui", "screen", "movie", "image"], - "title": "Capture UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", - "tags": ["animation", "simple", "playblast", "ui"], - "title": "Simple Playblast UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", - "tags": ["animation", "tween", "machine"], - "title": "Tween Machine UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", - "tags": ["animation", "select", "curves"], - "title": "Select All Animation Curves", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", - "tags": ["animation", "path", "along"], - "title": "Path Animation", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", - "tags": [ - "animation", - "offsetSelectedObjectsUI.py" - ], - "title": "Offset Selected Objects UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", - "tags": [ - "animation", - "key", "amplifier" - ], - "title": "Key Amplifier UI", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", - "tags": [ - "animation", - "anim_scene_optimizer.py" - ], - "title": "Anim_Scene_Optimizer", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", - "tags": [ - "animation", - "zvParentMaster.py" - ], - "title": "ZV Parent Master", - "type": "action" - }, - { - "sourcetype": "file", - "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", - "tags": [ - "animation", - "poseLibrary.py" - ], - "title": "Pose Library", - "type": "action" - } - ] - }, - { - "type": "menu", - "title": "Layout", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "align", - "Distribute", - "UI" - ], - "title": "Align Distribute UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "align", - "UI", - "Simple" - ], - "title": "Align Simple UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", - "sourcetype": "file", - "tags": [ - "layout", - "center", - "locator" - ], - "title": "Center Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", - "sourcetype": "file", - "tags": [ - "layout", - "average", - "locator" - ], - "title": "Average Locator", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "select", - "proximity", - "ui" - ], - "title": "Select Within Proximity UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "Duplicate", - "Curve", - "UI" - ], - "title": "Duplicate Curve UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "random", - "Deselect", - "UI" - ], - "title": "Random Deselect UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "multi", - "reference" - ], - "title": "Multi Referencer UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "duplicate", - "offset", - "UI" - ], - "title": "Duplicate Offset UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\spPaint3d.py", - "sourcetype": "file", - "tags": [ - "layout", - "spPaint3d", - "paint", - "tool" - ], - "title": "SP Paint 3d", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "randomize", - "UI" - ], - "title": "Randomize UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", - "sourcetype": "file", - "tags": [ - "layout", - "distribute", - "ObjectUI", - "within" - ], - "title": "Distribute Within Object UI", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Particles", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjects.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjects" - ], - "title": "Instancer To Objects", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstances.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstances" - ], - "title": "Instancer To Objects Instances", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", - "sourcetype": "file", - "tags": ["particles", "objects", "Particles", "Instancer", "Clean", "Source"], - "title": "Objects To Particles & Instancer - Clean Source", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\particleComponentsToLocators.py", - "sourcetype": "file", - "tags": ["particles", "components", "locators"], - "title": "Particle Components To Locators", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", - "sourcetype": "file", - "tags": [ - "particles", "objects", "particles", "instancer"], - "title": "Objects To Particles And Instancer", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\spawnParticlesOnMesh.py", - "sourcetype": "file", - "tags": ["particles", "spawn","on","mesh"], - "title": "Spawn Particles On Mesh", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsInstancesWithAnimation" - ], - "title": "Instancer To Objects Instances With Animation", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticles.py", - "sourcetype": "file", - "tags": [ - "particles", - "objectsToParticles" - ], - "title": "Objects To Particles", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", - "sourcetype": "file", - "tags": [ - "particles", - "add_particle_cacheFile_attrs" - ], - "title": "Add Particle CacheFile Attributes", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\mergeParticleSystems.py", - "sourcetype": "file", - "tags": [ - "particles", - "mergeParticleSystems" - ], - "title": "Merge Particle Systems", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\particlesToLocators.py", - "sourcetype": "file", - "tags": [ - "particles", - "particlesToLocators" - ], - "title": "Particles To Locators", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", - "sourcetype": "file", - "tags": [ - "particles", - "instancerToObjectsWithAnimation" - ], - "title": "Instancer To Objects With Animation", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", - "sourcetype": "file", - "tags": [ - "particles", - "houdini", "houdiniTool", "houdiniEngine" - ], - "title": "Replicate Houdini Tool", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\clearInitialState.py", - "sourcetype": "file", - "tags": [ - "particles", - "clearInitialState" - ], - "title": "Clear Initial State", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\particles\\killSelectedParticles.py", - "sourcetype": "file", - "tags": [ - "particles", - "killSelectedParticles" - ], - "title": "Kill Selected Particles", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Cleanup", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\repair_faulty_containers.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "repair", "containers" - ], - "title": "Find and Repair Containers", - "tooltip": "" - },{ - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectByType.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "selectByType" - ], - "title": "Select By Type", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectIntermediateObjects.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "selectIntermediateObjects" - ], - "title": "Select Intermediate Objects", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectNonUniqueNames.py", - "sourcetype": "file", - "tags": ["cleanup", "select", "non unique", "names"], - "title": "Select Non Unique Names", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeNamespaces.py", - "sourcetype": "file", - "tags": ["cleanup", "remove", "namespaces"], - "title": "Remove Namespaces", - "tooltip": "Remove all namespaces" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "remove_user_defined_attributes" - ], - "title": "Remove User Defined Attributes", - "tooltip": "Remove all user-defined attributs from all nodes" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnknownNodes" - ], - "title": "Remove Unknown Nodes", - "tooltip": "Remove all unknown nodes" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnloadedReferences" - ], - "title": "Remove Unloaded References", - "tooltip": "Remove all unloaded references" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeReferencesFailedEdits" - ], - "title": "Remove References Failed Edits", - "tooltip": "Remove failed edits for all references" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_unused_looks.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "removeUnusedLooks" - ], - "title": "Remove Unused Looks", - "tooltip": "Remove all loaded yet unused Avalon look containers" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "deleteGhostIntermediateObjects" - ], - "title": "Delete Ghost Intermediate Objects", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\resetViewportCache.py", - "sourcetype": "file", - "tags": ["cleanup", "reset","viewport", "cache"], - "title": "Reset Viewport Cache", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\uniqifyNodeNames.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "uniqifyNodeNames" - ], - "title": "Uniqify Node Names", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\autoRenameFileNodes.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "auto", "rename","filenodes" - ], - "title": "Auto Rename File Nodes", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\update_asset_id.py", - "sourcetype": "file", - "tags":["cleanup", "update", "database", "asset", "id"], - "title": "Update Asset ID", - "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\colorbleedRename.py", - "sourcetype": "file", - "tags": ["cleanup", "rename", "ui"], - "title": "Colorbleed Renamer", - "tooltip": "Colorbleed Rename UI" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\renameShapesToTransform.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "renameShapesToTransform" - ], - "title": "Rename Shapes To Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\reorderUI.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "reorderUI" - ], - "title": "Reorder UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\pastedCleaner.py", - "sourcetype": "file", - "tags": [ - "cleanup", - "pastedCleaner" - ], - "title": "Pasted Cleaner", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Others", - "items": [ - { - "type": "menu", - "sourcetype": "file", - "title": "Yeti", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", - "sourcetype": "file", - "tags": ["others", "yeti", "cache", "selected"], - "title": "Cache Selected Yeti Nodes", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "title": "Hair", - "tooltip": "", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", - "sourcetype": "file", - "tags": ["others", "selectSoftSelection"], - "title": "Select Soft Selection", - "tooltip": "" - } - ] - }, - { - "type": "menu", - "command": "$COLORBLEED_SCRIPTS\\others\\display", - "sourcetype": "file", - "tags": [ - "others", - "display" - ], - "title": "Display", - "items": [ - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", - "sourcetype": "file", - "tags": ["others", "wireframe","selected","objects"], - "title": "Wireframe Selected Objects", - "tooltip": "" - } - ] - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\archiveSceneUI.py", - "sourcetype": "file", - "tags": [ - "others", - "archiveSceneUI" - ], - "title": "Archive Scene UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\getSimilarMeshes.py", - "sourcetype": "file", - "tags": [ - "others", - "getSimilarMeshes" - ], - "title": "Get Similar Meshes", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\createBoundingBoxEachSelected.py", - "sourcetype": "file", - "tags": [ - "others", - "createBoundingBoxEachSelected" - ], - "title": "Create BoundingBox Each Selected", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\curveFromPositionEveryFrame.py", - "sourcetype": "file", - "tags": [ - "others", - "curveFromPositionEveryFrame" - ], - "title": "Curve From Position", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\instanceLeafSmartTransform.py", - "sourcetype": "file", - "tags": ["others", "instance","leaf", "smart", "transform"], - "title": "Instance Leaf Smart Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\instanceSmartTransform.py", - "sourcetype": "file", - "tags": ["others", "instance", "smart", "transform"], - "title": "Instance Smart Transform", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", - "sourcetype": "file", - "tags": [ - "others", - "randomizeUVShellsSelectedObjects" - ], - "title": "Randomize UV Shells", - "tooltip": "Select objects before running action" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\centerPivotGroup.py", - "sourcetype": "file", - "tags": [ - "others", - "centerPivotGroup" - ], - "title": "Center Pivot Group", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnSelectedFaces.py", - "sourcetype": "file", - "tags": [ - "others", - "locatorsOnSelectedFaces" - ], - "title": "Locators On Selected Faces", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", - "sourcetype": "file", - "tags": [ - "others", - "locatorsOnEdgeSelectionPrompt" - ], - "title": "Locators On Edge Selection Prompt", - "tooltip": "" - }, - {"type": "separator"}, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\copyDeformers.py", - "sourcetype": "file", - "tags": [ - "others", - "copyDeformers" - ], - "title": "Copy Deformers", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\selectInReferenceEditor.py", - "sourcetype": "file", - "tags": [ - "others", - "selectInReferenceEditor" - ], - "title": "Select In Reference Editor", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\selectConstrainingObject.py", - "sourcetype": "file", - "tags": [ - "others", - "selectConstrainingObject" - ], - "title": "Select Constraining Object", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\deformerSetRelationsUI.py", - "sourcetype": "file", - "tags": [ - "others", - "deformerSetRelationsUI" - ], - "title": "Deformer Set Relations UI", - "tooltip": "" - }, - { - "type": "action", - "command": "$COLORBLEED_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", - "sourcetype": "file", - "tags": ["others", "recreate","base", "nodes", "lattice"], - "title": "Recreate Base Nodes For Lattice Nodes", - "tooltip": "" - } - ] - } -] \ No newline at end of file +[{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\save_scene_incremental.py", + "sourcetype": "file", + "title": "Version Up", + "tooltip": "Incremental save with a specific format" +}, +{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\show_current_scene_in_explorer.py", + "sourcetype": "file", + "title": "Explore current scene..", + "tooltip": "Show current scene in Explorer" +}, +{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\avalon\\launch_manager.py", + "sourcetype": "file", + "title": "Project Manager", + "tooltip": "Add assets to the project" +}, +{ + "type": "separator" +}, +{ + "type": "menu", + "title": "Modeling", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\duplicate_normalized.py", + "sourcetype": "file", + "tags": ["modeling", + "duplicate", + "normalized"], + "title": "Duplicate Normalized", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\transferUVs.py", + "sourcetype": "file", + "tags": ["modeling", + "transfer", + "uv"], + "title": "Transfer UVs", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\mirrorSymmetry.py", + "sourcetype": "file", + "tags": ["modeling", + "mirror", + "symmetry"], + "title": "Mirror Symmetry", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\selectOutlineUI.py", + "sourcetype": "file", + "tags": ["modeling", + "select", + "outline", + "ui"], + "title": "Select Outline UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDeleteOtherUVSets.py", + "sourcetype": "file", + "tags": ["modeling", + "polygon", + "uvset", + "delete"], + "title": "Polygon Delete Other UV Sets", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyCombineQuick.py", + "sourcetype": "file", + "tags": ["modeling", + "combine", + "polygon", + "quick"], + "title": "Polygon Combine Quick", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\separateMeshPerShader.py", + "sourcetype": "file", + "tags": ["modeling", + "separateMeshPerShader"], + "title": "Separate Mesh Per Shader", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyDetachSeparate.py", + "sourcetype": "file", + "tags": ["modeling", + "poly", + "detach", + "separate"], + "title": "Polygon Detach and Separate", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polyRelaxVerts.py", + "sourcetype": "file", + "tags": ["modeling", + "relax", + "verts"], + "title": "Polygon Relax Vertices", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\polySelectEveryNthEdgeUI.py", + "sourcetype": "file", + "tags": ["modeling", + "select", + "nth", + "edge", + "ui"], + "title": "Select Every Nth Edge" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\modeling\\djPFXUVs.py", + "sourcetype": "file", + "tags": ["modeling", + "djPFX", + "UVs"], + "title": "dj PFX UVs", + "tooltip": "" + }] +}, +{ + "type": "menu", + "title": "Rigging", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\addCurveBetween.py", + "sourcetype": "file", + "tags": ["rigging", + "addCurveBetween", + "file"], + "title": "Add Curve Between" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\averageSkinWeights.py", + "sourcetype": "file", + "tags": ["rigging", + "average", + "skin weights", + "file"], + "title": "Average Skin Weights" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\cbSmoothSkinWeightUI.py", + "sourcetype": "file", + "tags": ["rigging", + "cbSmoothSkinWeightUI", + "file"], + "title": "CB Smooth Skin Weight UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\channelBoxManagerUI.py", + "sourcetype": "file", + "tags": ["rigging", + "channelBoxManagerUI", + "file"], + "title": "Channel Box Manager UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\characterAutorigger.py", + "sourcetype": "file", + "tags": ["rigging", + "characterAutorigger", + "file"], + "title": "Character Auto Rigger" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\connectUI.py", + "sourcetype": "file", + "tags": ["rigging", + "connectUI", + "file"], + "title": "Connect UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\copySkinWeightsLocal.py", + "sourcetype": "file", + "tags": ["rigging", + "copySkinWeightsLocal", + "file"], + "title": "Copy Skin Weights Local" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\createCenterLocator.py", + "sourcetype": "file", + "tags": ["rigging", + "createCenterLocator", + "file"], + "title": "Create Center Locator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\freezeTransformToGroup.py", + "sourcetype": "file", + "tags": ["rigging", + "freezeTransformToGroup", + "file"], + "title": "Freeze Transform To Group" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\groupSelected.py", + "sourcetype": "file", + "tags": ["rigging", + "groupSelected", + "file"], + "title": "Group Selected" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\ikHandlePoleVectorLocator.py", + "sourcetype": "file", + "tags": ["rigging", + "ikHandlePoleVectorLocator", + "file"], + "title": "IK Handle Pole Vector Locator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointOrientUI.py", + "sourcetype": "file", + "tags": ["rigging", + "jointOrientUI", + "file"], + "title": "Joint Orient UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\jointsOnCurve.py", + "sourcetype": "file", + "tags": ["rigging", + "jointsOnCurve", + "file"], + "title": "Joints On Curve" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedSkinJoints.py", + "sourcetype": "file", + "tags": ["rigging", + "resetBindSelectedSkinJoints", + "file"], + "title": "Reset Bind Selected Skin Joints" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedComponents.py", + "sourcetype": "file", + "tags": ["rigging", + "selectSkinclusterJointsFromSelectedComponents", + "file"], + "title": "Select Skincluster Joints From Selected Components" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\selectSkinclusterJointsFromSelectedMesh.py", + "sourcetype": "file", + "tags": ["rigging", + "selectSkinclusterJointsFromSelectedMesh", + "file"], + "title": "Select Skincluster Joints From Selected Mesh" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointLabels.py", + "sourcetype": "file", + "tags": ["rigging", + "setJointLabels", + "file"], + "title": "Set Joint Labels" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setJointOrientationFromCurrentRotation.py", + "sourcetype": "file", + "tags": ["rigging", + "setJointOrientationFromCurrentRotation", + "file"], + "title": "Set Joint Orientation From Current Rotation" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setSelectedJointsOrientationZero.py", + "sourcetype": "file", + "tags": ["rigging", + "setSelectedJointsOrientationZero", + "file"], + "title": "Set Selected Joints Orientation Zero" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\mirrorCurveShape.py", + "sourcetype": "file", + "tags": ["rigging", + "mirrorCurveShape", + "file"], + "title": "Mirror Curve Shape" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\setRotationOrderUI.py", + "sourcetype": "file", + "tags": ["rigging", + "setRotationOrderUI", + "file"], + "title": "Set Rotation Order UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\paintItNowUI.py", + "sourcetype": "file", + "tags": ["rigging", + "paintItNowUI", + "file"], + "title": "Paint It Now UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\parentScaleConstraint.py", + "sourcetype": "file", + "tags": ["rigging", + "parentScaleConstraint", + "file"], + "title": "Parent Scale Constraint" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\quickSetWeightsUI.py", + "sourcetype": "file", + "tags": ["rigging", + "quickSetWeightsUI", + "file"], + "title": "Quick Set Weights UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\rapidRig.py", + "sourcetype": "file", + "tags": ["rigging", + "rapidRig", + "file"], + "title": "Rapid Rig" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\regenerate_blendshape_targets.py", + "sourcetype": "file", + "tags": ["rigging", + "regenerate_blendshape_targets", + "file"], + "title": "Regenerate Blendshape Targets" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\removeRotationAxis.py", + "sourcetype": "file", + "tags": ["rigging", + "removeRotationAxis", + "file"], + "title": "Remove Rotation Axis" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\resetBindSelectedMeshes.py", + "sourcetype": "file", + "tags": ["rigging", + "resetBindSelectedMeshes", + "file"], + "title": "Reset Bind Selected Meshes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelection.py", + "sourcetype": "file", + "tags": ["rigging", + "simpleControllerOnSelection", + "file"], + "title": "Simple Controller On Selection" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\simpleControllerOnSelectionHierarchy.py", + "sourcetype": "file", + "tags": ["rigging", + "simpleControllerOnSelectionHierarchy", + "file"], + "title": "Simple Controller On Selection Hierarchy" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\superRelativeCluster.py", + "sourcetype": "file", + "tags": ["rigging", + "superRelativeCluster", + "file"], + "title": "Super Relative Cluster" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\tfSmoothSkinWeight.py", + "sourcetype": "file", + "tags": ["rigging", + "tfSmoothSkinWeight", + "file"], + "title": "TF Smooth Skin Weight" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleIntermediates.py", + "sourcetype": "file", + "tags": ["rigging", + "toggleIntermediates", + "file"], + "title": "Toggle Intermediates" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSegmentScaleCompensate.py", + "sourcetype": "file", + "tags": ["rigging", + "toggleSegmentScaleCompensate", + "file"], + "title": "Toggle Segment Scale Compensate" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\rigging\\toggleSkinclusterDeformNormals.py", + "sourcetype": "file", + "tags": ["rigging", + "toggleSkinclusterDeformNormals", + "file"], + "title": "Toggle Skincluster Deform Normals" + }] +}, +{ + "type": "menu", + "title": "Shading", + "items": [{ + "type": "menu", + "title": "VRay", + "items": [{ + "type": "action", + "title": "Import Proxies", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayImportProxies.py", + "sourcetype": "file", + "tags": ["shading", + "vray", + "import", + "proxies"], + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Select All GES", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGES.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "select All GES"] + }, + { + "type": "action", + "title": "Select All GES Under Selection", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectAllGESUnderSelection.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "select", + "all", + "GES"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Selection To VRay Mesh", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\selectionToVrayMesh.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "selection", + "vraymesh"] + }, + { + "type": "action", + "title": "Add VRay Round Edges Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayRoundEdgesAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "round edges", + "attribute"] + }, + { + "type": "action", + "title": "Add Gamma", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayAddGamma.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "add gamma"] + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\select_vraymesh_materials_with_unconnected_shader_slots.py", + "sourcetype": "file", + "title": "Select Unconnected Shader Materials", + "tags": ["shading", + "vray", + "select", + "vraymesh", + "materials", + "unconnected shader slots"], + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayMergeSimilarVRayMeshMaterials.py", + "sourcetype": "file", + "title": "Merge Similar VRay Mesh Materials", + "tags": ["shading", + "vray", + "Merge", + "VRayMesh", + "Materials"], + "tooltip": "" + }, + { + "type": "action", + "title": "Create Two Sided Material", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtlRenamed.py", + "sourcetype": "file", + "tooltip": "Creates two sided material for selected material and renames it", + "tags": ["shading", + "vray", + "two sided", + "material"] + }, + { + "type": "action", + "title": "Create Two Sided Material For Selected", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayCreate2SidedMtlForSelectedMtl.py", + "sourcetype": "file", + "tooltip": "Select material to create a two sided version from it", + "tags": ["shading", + "vray", + "Create2SidedMtlForSelectedMtl.py"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "add", + "open subdiv", + "attribute"] + }, + { + "type": "action", + "title": "Remove OpenSubdiv Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVrayOpenSubdivAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "remove", + "opensubdiv", + "attributee"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add Subdivision Attribute", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "addVraySubdivisionAttribute"] + }, + { + "type": "action", + "title": "Remove Subdivision Attribute.py", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\removeVraySubdivisionAttribute.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "remove", + "subdivision", + "attribute"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Add Vray Object Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayObjectIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "add", + "object id"] + }, + { + "type": "action", + "title": "Add Vray Material Ids", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\addVrayMaterialIds.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "addVrayMaterialIds.py"] + }, + { + "type": "separator" + }, + { + "type": "action", + "title": "Set Physical DOF Depth", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\vrayPhysicalDOFSetDepth.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "physical", + "DOF ", + "Depth"] + }, + { + "type": "action", + "title": "Magic Vray Proxy UI", + "command": "$COLORBLEED_SCRIPTS\\shading\\vray\\magicVrayProxyUI.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "magicVrayProxyUI"] + }] + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\pyblish\\lighting\\set_filename_prefix.py", + "sourcetype": "file", + "tags": ["shading", + "lookdev", + "assign", + "shaders", + "prefix", + "filename", + "render"], + "title": "Set filename prefix", + "tooltip": "Set the render file name prefix." + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\look_manager_ui.py", + "sourcetype": "file", + "tags": ["shading", + "look", + "assign", + "shaders", + "auto"], + "title": "Look Manager", + "tooltip": "Open the Look Manager UI for look assignment" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\LightLinkUi.py", + "sourcetype": "file", + "tags": ["shading", + "light", + "link", + "ui"], + "title": "Light Link UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\vdviewer_ui.py", + "sourcetype": "file", + "tags": ["shading", + "look", + "vray", + "displacement", + "shaders", + "auto"], + "title": "VRay Displ Viewer", + "tooltip": "Open the VRay Displacement Viewer, select and control the content of the set" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\setTexturePreviewToCLRImage.py", + "sourcetype": "file", + "tags": ["shading", + "CLRImage", + "textures", + "preview"], + "title": "Set Texture Preview To CLRImage", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixDefaultShaderSetBehavior.py", + "sourcetype": "file", + "tags": ["shading", + "fix", + "DefaultShaderSet", + "Behavior"], + "title": "Fix Default Shader Set Behavior", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixSelectedShapesReferenceAssignments.py", + "sourcetype": "file", + "tags": ["shading", + "fix", + "Selected", + "Shapes", + "Reference", + "Assignments"], + "title": "Fix Shapes Reference Assignments", + "tooltip": "Select shapes to fix the reference assignments" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\selectLambert1Members.py", + "sourcetype": "file", + "tags": ["shading", + "selectLambert1Members"], + "title": "Select Lambert1 Members", + "tooltip": "Selects all objects which have the Lambert1 shader assigned" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\selectShapesWithoutShader.py", + "sourcetype": "file", + "tags": ["shading", + "selectShapesWithoutShader"], + "title": "Select Shapes Without Shader", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fixRenderLayerOutAdjustmentErrors.py", + "sourcetype": "file", + "tags": ["shading", + "fixRenderLayerOutAdjustmentErrors"], + "title": "Fix RenderLayer Out Adjustment Errors", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\shading\\fix_renderlayer_missing_node_override.py", + "sourcetype": "file", + "tags": ["shading", + "renderlayer", + "missing", + "reference", + "switch", + "layer"], + "title": "Fix RenderLayer Missing Referenced Nodes Overrides", + "tooltip": "" + }, + { + "type": "action", + "title": "Image 2 Tiled EXR", + "command": "$COLORBLEED_SCRIPTS\\shading\\open_img2exr.py", + "sourcetype": "file", + "tooltip": "", + "tags": ["shading", + "vray", + "exr"] + }] +}, +{ + "type": "menu", + "title": "Rendering", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\pyblish\\open_deadline_submission_settings.py", + "sourcetype": "file", + "tags": ["settings", + "deadline", + "globals", + "render"], + "title": "DL Submission Settings UI", + "tooltip": "Open the Deadline Submission Settings UI" + }] +}, +{ + "type": "menu", + "title": "Animation", + "items": [{ + "type": "menu", + "title": "Attributes", + "tooltip": "", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyValues.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes"], + "title": "Copy Values", + "tooltip": "Copy attribute values" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyInConnections.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "connections", + "incoming"], + "title": "Copy In Connections", + "tooltip": "Copy incoming connections" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyOutConnections.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "connections", + "out"], + "title": "Copy Out Connections", + "tooltip": "Copy outcoming connections" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformLocal.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "transforms", + "local"], + "title": "Copy Local Transfroms", + "tooltip": "Copy local transfroms" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformMatrix.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "transforms", + "matrix"], + "title": "Copy Matrix Transfroms", + "tooltip": "Copy Matrix transfroms" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\copyTransformUI.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "transforms", + "UI"], + "title": "Copy Transforms UI", + "tooltip": "Open the Copy Transforms UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\attributes\\simpleCopyUI.py", + "sourcetype": "file", + "tags": ["animation", + "copy", + "attributes", + "transforms", + "UI", + "simple"], + "title": "Simple Copy UI", + "tooltip": "Open the simple Copy Transforms UI" + }] + }, + { + "type": "menu", + "title": "Optimize", + "tooltip": "Optimization scripts", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleFreezeHierarchy.py", + "sourcetype": "file", + "tags": ["animation", + "hierarchy", + "toggle", + "freeze"], + "title": "Toggle Freeze Hierarchy", + "tooltip": "Freeze and unfreeze hierarchy" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\animation\\optimize\\toggleParallelNucleus.py", + "sourcetype": "file", + "tags": ["animation", + "nucleus", + "toggle", + "parallel"], + "title": "Toggle Parallel Nucleus", + "tooltip": "Toggle parallel nucleus" + }] + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\bakeSelectedToWorldSpace.py", + "tags": ["animation", + "bake", + "selection", + "worldspace.py"], + "title": "Bake Selected To Worldspace", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\timeStepper.py", + "tags": ["animation", + "time", + "stepper"], + "title": "Time Stepper", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\capture_ui.py", + "tags": ["animation", + "capture", + "ui", + "screen", + "movie", + "image"], + "title": "Capture UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\simplePlayblastUI.py", + "tags": ["animation", + "simple", + "playblast", + "ui"], + "title": "Simple Playblast UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\tweenMachineUI.py", + "tags": ["animation", + "tween", + "machine"], + "title": "Tween Machine UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\selectAllAnimationCurves.py", + "tags": ["animation", + "select", + "curves"], + "title": "Select All Animation Curves", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\pathAnimation.py", + "tags": ["animation", + "path", + "along"], + "title": "Path Animation", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\offsetSelectedObjectsUI.py", + "tags": ["animation", + "offsetSelectedObjectsUI.py"], + "title": "Offset Selected Objects UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\key_amplifier_ui.py", + "tags": ["animation", + "key", + "amplifier"], + "title": "Key Amplifier UI", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\anim_scene_optimizer.py", + "tags": ["animation", + "anim_scene_optimizer.py"], + "title": "Anim_Scene_Optimizer", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\zvParentMaster.py", + "tags": ["animation", + "zvParentMaster.py"], + "title": "ZV Parent Master", + "type": "action" + }, + { + "sourcetype": "file", + "command": "$COLORBLEED_SCRIPTS\\animation\\poseLibrary.py", + "tags": ["animation", + "poseLibrary.py"], + "title": "Pose Library", + "type": "action" + }] +}, +{ + "type": "menu", + "title": "Layout", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignDistributeUI.py", + "sourcetype": "file", + "tags": ["layout", + "align", + "Distribute", + "UI"], + "title": "Align Distribute UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\alignSimpleUI.py", + "sourcetype": "file", + "tags": ["layout", + "align", + "UI", + "Simple"], + "title": "Align Simple UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\center_locator.py", + "sourcetype": "file", + "tags": ["layout", + "center", + "locator"], + "title": "Center Locator", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\average_locator.py", + "sourcetype": "file", + "tags": ["layout", + "average", + "locator"], + "title": "Average Locator", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\selectWithinProximityUI.py", + "sourcetype": "file", + "tags": ["layout", + "select", + "proximity", + "ui"], + "title": "Select Within Proximity UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\dupCurveUI.py", + "sourcetype": "file", + "tags": ["layout", + "Duplicate", + "Curve", + "UI"], + "title": "Duplicate Curve UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomDeselectUI.py", + "sourcetype": "file", + "tags": ["layout", + "random", + "Deselect", + "UI"], + "title": "Random Deselect UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\multiReferencerUI.py", + "sourcetype": "file", + "tags": ["layout", + "multi", + "reference"], + "title": "Multi Referencer UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\duplicateOffsetUI.py", + "sourcetype": "file", + "tags": ["layout", + "duplicate", + "offset", + "UI"], + "title": "Duplicate Offset UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\spPaint3d.py", + "sourcetype": "file", + "tags": ["layout", + "spPaint3d", + "paint", + "tool"], + "title": "SP Paint 3d", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\randomizeUI.py", + "sourcetype": "file", + "tags": ["layout", + "randomize", + "UI"], + "title": "Randomize UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\layout\\distributeWithinObjectUI.py", + "sourcetype": "file", + "tags": ["layout", + "distribute", + "ObjectUI", + "within"], + "title": "Distribute Within Object UI", + "tooltip": "" + }] +}, +{ + "type": "menu", + "title": "Particles", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjects.py", + "sourcetype": "file", + "tags": ["particles", + "instancerToObjects"], + "title": "Instancer To Objects", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstances.py", + "sourcetype": "file", + "tags": ["particles", + "instancerToObjectsInstances"], + "title": "Instancer To Objects Instances", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancerCleanSource.py", + "sourcetype": "file", + "tags": ["particles", + "objects", + "Particles", + "Instancer", + "Clean", + "Source"], + "title": "Objects To Particles & Instancer - Clean Source", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\particleComponentsToLocators.py", + "sourcetype": "file", + "tags": ["particles", + "components", + "locators"], + "title": "Particle Components To Locators", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticlesAndInstancer.py", + "sourcetype": "file", + "tags": ["particles", + "objects", + "particles", + "instancer"], + "title": "Objects To Particles And Instancer", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\spawnParticlesOnMesh.py", + "sourcetype": "file", + "tags": ["particles", + "spawn", + "on", + "mesh"], + "title": "Spawn Particles On Mesh", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsInstancesWithAnimation.py", + "sourcetype": "file", + "tags": ["particles", + "instancerToObjectsInstancesWithAnimation"], + "title": "Instancer To Objects Instances With Animation", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\objectsToParticles.py", + "sourcetype": "file", + "tags": ["particles", + "objectsToParticles"], + "title": "Objects To Particles", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\add_particle_cacheFile_attrs.py", + "sourcetype": "file", + "tags": ["particles", + "add_particle_cacheFile_attrs"], + "title": "Add Particle CacheFile Attributes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\mergeParticleSystems.py", + "sourcetype": "file", + "tags": ["particles", + "mergeParticleSystems"], + "title": "Merge Particle Systems", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\particlesToLocators.py", + "sourcetype": "file", + "tags": ["particles", + "particlesToLocators"], + "title": "Particles To Locators", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\instancerToObjectsWithAnimation.py", + "sourcetype": "file", + "tags": ["particles", + "instancerToObjectsWithAnimation"], + "title": "Instancer To Objects With Animation", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\mayaReplicateHoudiniTool.py", + "sourcetype": "file", + "tags": ["particles", + "houdini", + "houdiniTool", + "houdiniEngine"], + "title": "Replicate Houdini Tool", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\clearInitialState.py", + "sourcetype": "file", + "tags": ["particles", + "clearInitialState"], + "title": "Clear Initial State", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\particles\\killSelectedParticles.py", + "sourcetype": "file", + "tags": ["particles", + "killSelectedParticles"], + "title": "Kill Selected Particles", + "tooltip": "" + }] +}, +{ + "type": "menu", + "title": "Yeti", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\yeti\\yeti_rig_manager.py", + "sourcetype": "file", + "tags": ["yeti", + "rig", + "fur", + "manager"], + "title": "Open Yeti Rig Manager", + "tooltip": "" + }] +}, +{ + "type": "menu", + "title": "Cleanup", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\repair_faulty_containers.py", + "sourcetype": "file", + "tags": ["cleanup", + "repair", + "containers"], + "title": "Find and Repair Containers", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectByType.py", + "sourcetype": "file", + "tags": ["cleanup", + "selectByType"], + "title": "Select By Type", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectIntermediateObjects.py", + "sourcetype": "file", + "tags": ["cleanup", + "selectIntermediateObjects"], + "title": "Select Intermediate Objects", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\selectNonUniqueNames.py", + "sourcetype": "file", + "tags": ["cleanup", + "select", + "non unique", + "names"], + "title": "Select Non Unique Names", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeNamespaces.py", + "sourcetype": "file", + "tags": ["cleanup", + "remove", + "namespaces"], + "title": "Remove Namespaces", + "tooltip": "Remove all namespaces" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_user_defined_attributes.py", + "sourcetype": "file", + "tags": ["cleanup", + "remove_user_defined_attributes"], + "title": "Remove User Defined Attributes", + "tooltip": "Remove all user-defined attributs from all nodes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownNodes.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeUnknownNodes"], + "title": "Remove Unknown Nodes", + "tooltip": "Remove all unknown nodes" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeUnloadedReferences"], + "title": "Remove Unloaded References", + "tooltip": "Remove all unloaded references" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeReferencesFailedEdits.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeReferencesFailedEdits"], + "title": "Remove References Failed Edits", + "tooltip": "Remove failed edits for all references" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\remove_unused_looks.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeUnusedLooks"], + "title": "Remove Unused Looks", + "tooltip": "Remove all loaded yet unused Avalon look containers" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\deleteGhostIntermediateObjects.py", + "sourcetype": "file", + "tags": ["cleanup", + "deleteGhostIntermediateObjects"], + "title": "Delete Ghost Intermediate Objects", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\resetViewportCache.py", + "sourcetype": "file", + "tags": ["cleanup", + "reset", + "viewport", + "cache"], + "title": "Reset Viewport Cache", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\uniqifyNodeNames.py", + "sourcetype": "file", + "tags": ["cleanup", + "uniqifyNodeNames"], + "title": "Uniqify Node Names", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\autoRenameFileNodes.py", + "sourcetype": "file", + "tags": ["cleanup", + "auto", + "rename", + "filenodes"], + "title": "Auto Rename File Nodes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\update_asset_id.py", + "sourcetype": "file", + "tags": ["cleanup", + "update", + "database", + "asset", + "id"], + "title": "Update Asset ID", + "tooltip": "Will replace the Colorbleed ID with a new one (asset ID : Unique number)" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\colorbleedRename.py", + "sourcetype": "file", + "tags": ["cleanup", + "rename", + "ui"], + "title": "Colorbleed Renamer", + "tooltip": "Colorbleed Rename UI" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\renameShapesToTransform.py", + "sourcetype": "file", + "tags": ["cleanup", + "renameShapesToTransform"], + "title": "Rename Shapes To Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\reorderUI.py", + "sourcetype": "file", + "tags": ["cleanup", + "reorderUI"], + "title": "Reorder UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\pastedCleaner.py", + "sourcetype": "file", + "tags": ["cleanup", + "pastedCleaner"], + "title": "Pasted Cleaner", + "tooltip": "" + }] +}, +{ + "type": "menu", + "title": "Others", + "items": [{ + "type": "menu", + "sourcetype": "file", + "title": "Yeti", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\yeti\\cache_selected_yeti_nodes.py", + "sourcetype": "file", + "tags": ["others", + "yeti", + "cache", + "selected"], + "title": "Cache Selected Yeti Nodes", + "tooltip": "" + }] + }, + { + "type": "menu", + "title": "Hair", + "tooltip": "", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\hair\\recolorHairCurrentCurve", + "sourcetype": "file", + "tags": ["others", + "selectSoftSelection"], + "title": "Select Soft Selection", + "tooltip": "" + }] + }, + { + "type": "menu", + "command": "$COLORBLEED_SCRIPTS\\others\\display", + "sourcetype": "file", + "tags": ["others", + "display"], + "title": "Display", + "items": [{ + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\display\\wireframeSelectedObjects.py", + "sourcetype": "file", + "tags": ["others", + "wireframe", + "selected", + "objects"], + "title": "Wireframe Selected Objects", + "tooltip": "" + }] + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\archiveSceneUI.py", + "sourcetype": "file", + "tags": ["others", + "archiveSceneUI"], + "title": "Archive Scene UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\getSimilarMeshes.py", + "sourcetype": "file", + "tags": ["others", + "getSimilarMeshes"], + "title": "Get Similar Meshes", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\createBoundingBoxEachSelected.py", + "sourcetype": "file", + "tags": ["others", + "createBoundingBoxEachSelected"], + "title": "Create BoundingBox Each Selected", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\curveFromPositionEveryFrame.py", + "sourcetype": "file", + "tags": ["others", + "curveFromPositionEveryFrame"], + "title": "Curve From Position", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\instanceLeafSmartTransform.py", + "sourcetype": "file", + "tags": ["others", + "instance", + "leaf", + "smart", + "transform"], + "title": "Instance Leaf Smart Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\instanceSmartTransform.py", + "sourcetype": "file", + "tags": ["others", + "instance", + "smart", + "transform"], + "title": "Instance Smart Transform", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\randomizeUVShellsSelectedObjects.py", + "sourcetype": "file", + "tags": ["others", + "randomizeUVShellsSelectedObjects"], + "title": "Randomize UV Shells", + "tooltip": "Select objects before running action" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\centerPivotGroup.py", + "sourcetype": "file", + "tags": ["others", + "centerPivotGroup"], + "title": "Center Pivot Group", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnSelectedFaces.py", + "sourcetype": "file", + "tags": ["others", + "locatorsOnSelectedFaces"], + "title": "Locators On Selected Faces", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\locatorsOnEdgeSelectionPrompt.py", + "sourcetype": "file", + "tags": ["others", + "locatorsOnEdgeSelectionPrompt"], + "title": "Locators On Edge Selection Prompt", + "tooltip": "" + }, + { + "type": "separator" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\copyDeformers.py", + "sourcetype": "file", + "tags": ["others", + "copyDeformers"], + "title": "Copy Deformers", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\selectInReferenceEditor.py", + "sourcetype": "file", + "tags": ["others", + "selectInReferenceEditor"], + "title": "Select In Reference Editor", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\selectConstrainingObject.py", + "sourcetype": "file", + "tags": ["others", + "selectConstrainingObject"], + "title": "Select Constraining Object", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\deformerSetRelationsUI.py", + "sourcetype": "file", + "tags": ["others", + "deformerSetRelationsUI"], + "title": "Deformer Set Relations UI", + "tooltip": "" + }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\others\\recreateBaseNodesForAllLatticeNodes.py", + "sourcetype": "file", + "tags": ["others", + "recreate", + "base", + "nodes", + "lattice"], + "title": "Recreate Base Nodes For Lattice Nodes", + "tooltip": "" + }] +}] \ No newline at end of file From 992262e09c53b86e5a75564cd7ff915d734067c3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 12:12:41 +0200 Subject: [PATCH 1103/1249] added validator for pre render callbacks scripts --- .../validate_renderscript_callbacks.py | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py diff --git a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py new file mode 100644 index 0000000000..4dc4ba171e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py @@ -0,0 +1,96 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api + + +class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): + """Check if the render script callbacks will be used during the rendering + + In order to ensure the render tasks are executed properly we need to check + if the pre and post render callbacks are actually used. + + For example: + Yeti is not loaded but its callback scripts are still set in the + render settings. This will cause an error because Maya tries to find + and execute the callbacks. + + Developer note: + The pre and post render callbacks cannot be overridden + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "Render Script Callbacks" + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise ValueError("Invalid render callbacks found for '%s'!" + % instance.name) + + @classmethod + def get_invalid(cls, instance): + + invalid = False + + # lookup per render + render_scripts = {"vray": + {"pre": "catch(`pgYetiVRayPreRender`)", + "post": "catch(`pgYetiVRayPostRender`)"}, + "arnold": + {"pre": "pgYetiPreRender"} + } + + yeti_loaded = cmds.pluginInfo("pgYetiMaya", query=True, loaded=True) + + renderer = instance.data["renderer"] + if renderer == "redshift": + cls.log.info("Redshift ignores any pre and post render callbacks") + return False + + callback_lookup = render_scripts.get(renderer, {}) + if not callback_lookup: + cls.log.error( + "Renderer '%s' is not supported in this plugin" % renderer + ) + + pre_render_callback = cmds.getAttr("defaultRenderGlobals.preMel") + post_render_callback = cmds.getAttr("defaultRenderGlobals.postMel") + + pre_script = callback_lookup.get("pre", "") + post_script = callback_lookup.get("post", "") + + # If not loaded + if not yeti_loaded: + if pre_script and pre_script in pre_render_callback: + cls.log.error( + "Found pre render callback which is not uses!" + ) + invalid = True + + if post_script and post_script in post_render_callback: + cls.log.error( + "Found post render callback which is not used!" + ) + invalid = True + else: + if pre_script: + pre_callbacks = pre_render_callback.split(";") + if pre_script not in pre_callbacks: + cls.log.error( + "Could not find required pre render callback " + "`%s`" % pre_script) + invalid = True + + if post_script: + post_callbacks = post_render_callback.split(";") + if post_script not in post_callbacks: + cls.log.error("Could not find required post render callback" + " `%s`" % post_script) + invalid = True + + return invalid From 0a1419ab47aae3b96dd67edba2f7b06431483228 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 12:17:05 +0200 Subject: [PATCH 1104/1249] improved validator --- .../validate_renderscript_callbacks.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py index 4dc4ba171e..7b0144de57 100644 --- a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py @@ -61,25 +61,25 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): pre_render_callback = cmds.getAttr("defaultRenderGlobals.preMel") post_render_callback = cmds.getAttr("defaultRenderGlobals.postMel") + pre_callbacks = pre_render_callback.split(";") + post_callbacks = post_render_callback.split(";") + pre_script = callback_lookup.get("pre", "") post_script = callback_lookup.get("post", "") # If not loaded if not yeti_loaded: - if pre_script and pre_script in pre_render_callback: - cls.log.error( - "Found pre render callback which is not uses!" - ) + if pre_script and pre_script in pre_callbacks: + cls.log.error("Found pre render callback '%s' which is not " + "uses!" % pre_script) invalid = True - if post_script and post_script in post_render_callback: - cls.log.error( - "Found post render callback which is not used!" - ) + if post_script and post_script in post_callbacks: + cls.log.error("Found post render callback '%s which is " + "not used!" % post_script) invalid = True else: if pre_script: - pre_callbacks = pre_render_callback.split(";") if pre_script not in pre_callbacks: cls.log.error( "Could not find required pre render callback " @@ -87,7 +87,6 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): invalid = True if post_script: - post_callbacks = post_render_callback.split(";") if post_script not in post_callbacks: cls.log.error("Could not find required post render callback" " `%s`" % post_script) From 6367af132202af6c42ea14c2c3268f023e54ae6f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 12:25:57 +0200 Subject: [PATCH 1105/1249] Changed error to warning for non supported renderers --- .../maya/publish/validate_renderscript_callbacks.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py index 7b0144de57..f0f4721449 100644 --- a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py @@ -35,8 +35,6 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - invalid = False - # lookup per render render_scripts = {"vray": {"pre": "catch(`pgYetiVRayPreRender`)", @@ -54,9 +52,9 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): callback_lookup = render_scripts.get(renderer, {}) if not callback_lookup: - cls.log.error( - "Renderer '%s' is not supported in this plugin" % renderer - ) + cls.log.warning("Renderer '%s' is not supported in this plugin" + % renderer) + return False pre_render_callback = cmds.getAttr("defaultRenderGlobals.preMel") post_render_callback = cmds.getAttr("defaultRenderGlobals.postMel") @@ -68,6 +66,7 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): post_script = callback_lookup.get("post", "") # If not loaded + invalid = False if not yeti_loaded: if pre_script and pre_script in pre_callbacks: cls.log.error("Found pre render callback '%s' which is not " From 2932118b6c103b6b21b3fc474998adbb9f1ea5ba Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 12:45:44 +0200 Subject: [PATCH 1106/1249] PEP08 --- colorbleed/plugins/maya/publish/submit_deadline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_deadline.py b/colorbleed/plugins/maya/publish/submit_deadline.py index 45ef683757..d2b394c1d1 100644 --- a/colorbleed/plugins/maya/publish/submit_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_deadline.py @@ -231,7 +231,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): render_globals = instance.data.get("renderGlobals", {}) payload["JobInfo"].update(render_globals) - self.log.info("using render plugin : {}".format(payload["JobInfo"]["Plugin"])) + plugin = payload["JobInfo"]["Plugin"] + self.log.info("using render plugin : {}".format(plugin)) self.preflight_check(instance) From 5415bd943fa4519e9d2309b4498c5bea84348be5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 12:46:12 +0200 Subject: [PATCH 1107/1249] Force default pools for publish job --- colorbleed/plugins/global/publish/submit_publish_job.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index 34a09c9b81..4a2daa1d40 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -293,6 +293,10 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): ) for index, key in enumerate(environment) }) + # Avoid copied pools and remove secondary pool + payload["JobInfo"]["Pool"] = "none" + payload["JobInfo"].pop("SecondaryPool", None) + self.log.info("Submitting..") self.log.info(json.dumps(payload, indent=4, sort_keys=True)) From 79db80ba66504981ad8aab60aaa4e66509a5858d Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 13:02:52 +0200 Subject: [PATCH 1108/1249] set primitve to detail pattern at creation --- colorbleed/plugins/houdini/create/create_pointcache.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 481404dcec..8fc0603bd1 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -22,7 +22,8 @@ class CreatePointCache(houdini.Creator): parms = {"use_sop_path": True, # Export single node from SOP Path "build_from_path": True, # Direct path of primitive in output - "path_attrib": "path", # Pass path attribute for output + "path_attrib": "path", # Pass path attribute for output\ + "prim_to_detail_pattern": "cbId", "format": 2, # Set format to Ogawa "filename": "$HIP/pyblish/%s.abc" % self.name} From 0caf0ff76a61ed567a4cb265d33a9cde2979caca Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 15:17:53 +0200 Subject: [PATCH 1109/1249] added *args to function signatures --- colorbleed/houdini/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 4025ef731a..4d407af609 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -44,11 +44,11 @@ def install(): avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] -def on_init(_): +def on_init(*args): houdini.on_houdini_initialize() -def on_save(): +def on_save(*args): avalon.logger.info("Running callback on save..") @@ -59,7 +59,9 @@ def on_save(): lib.set_id(node, new_id, overwrite=False) -def on_open(): +def on_open(*args): + + avalon.logger.info("Running callback on open..") update_task_from_path(hou.hipFile.path()) From 3ee17b8c119c37fffe07a79a37ef4c2ae167d86f Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 15:29:02 +0200 Subject: [PATCH 1110/1249] Lock prim_to_detail_pattern parameter after creation --- colorbleed/plugins/houdini/create/create_pointcache.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 8fc0603bd1..c54a6c91a6 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -32,3 +32,9 @@ class CreatePointCache(houdini.Creator): parms.update({"sop_path": node.path()}) instance.setParms(parms) + + # Lock any parameters in this list + to_lock = ["prim_to_detail_pattern"] + for name in to_lock: + parm = instance.parm(name) + parm.lock(True) From b55a5bd89ebdbc172c793dc10803c45df81cffbc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 11 Oct 2018 15:59:01 +0200 Subject: [PATCH 1111/1249] Renamed module, class and label for clearification --- ...t_callbacks.py => validate_yeti_renderscript_callbacks.py} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename colorbleed/plugins/maya/publish/{validate_renderscript_callbacks.py => validate_yeti_renderscript_callbacks.py} (96%) diff --git a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py similarity index 96% rename from colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py rename to colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index f0f4721449..ad4d1db911 100644 --- a/colorbleed/plugins/maya/publish/validate_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -4,7 +4,7 @@ import pyblish.api import colorbleed.api -class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): +class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): """Check if the render script callbacks will be used during the rendering In order to ensure the render tasks are executed properly we need to check @@ -21,7 +21,7 @@ class ValidateRenderScriptCallbacks(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder - label = "Render Script Callbacks" + label = "Yeti Render Script Callbacks" hosts = ["maya"] families = ["colorbleed.renderlayer"] From 18794e920f929f684aa80f1de4b40ec10aa34cfe Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 12 Oct 2018 12:42:44 +0200 Subject: [PATCH 1112/1249] check for file node, only destroy if it exists --- colorbleed/plugins/houdini/load/load_alembic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/colorbleed/plugins/houdini/load/load_alembic.py index 235d316c05..690b15c736 100644 --- a/colorbleed/plugins/houdini/load/load_alembic.py +++ b/colorbleed/plugins/houdini/load/load_alembic.py @@ -42,8 +42,10 @@ class AbcLoader(api.Loader): container = obj.createNode("geo", node_name=node_name) # Remove the file node, it only loads static meshes - file_node = container.node("file1".format(node_name)) - file_node.destroy() + # Houdini 17 has removed the file node from the geo node + file_node = container.node("file1") + if file_node: + file_node.destroy() # Create an alembic node (supports animation) alembic = container.createNode("alembic", node_name=node_name) From a95d7da3dbb6121768d7c940d163ac50b4b3a752 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:22:04 +0200 Subject: [PATCH 1113/1249] Validate if current render layer has renderable camera --- ...validate_current_renderlayer_renderable.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py new file mode 100644 index 0000000000..c9ab333d74 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py @@ -0,0 +1,28 @@ +import pyblish.api + +from maya import cmds + + +class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): + """Validate if current render layer has a renderable camera + + There is a bug in Redshift which occurs when the current render layer + at file open has no renderable camera. The error raised is as follows: + + "No renderable cameras found. Aborting render" + + This error is raised even if that render layer will not be rendered. + + """ + + label = "Current Render Layer Has Renderable Camera" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + cameras = cmds.ls(type="camera", long=True) + renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) + assert renderable, ("Current render layer %s has no renderable camera" + % layer) From a09b90b1a8f580978368b179ce4911bb8ec74eaa Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:22:51 +0200 Subject: [PATCH 1114/1249] Created multi line assertion message --- .../plugins/maya/publish/validate_deadline_connection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index 7f57e5b96a..828fc61232 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -22,5 +22,6 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): # Check response response = requests.get(AVALON_DEADLINE) assert response.ok, "Response must be ok" - assert response.text.startswith("Deadline Web Service "), \ - "Web service did not respond with 'Deadline Web Service'" \ No newline at end of file + assert response.text.startswith("Deadline Web Service "), ( + "Web service did not respond with 'Deadline Web Service'" + ) From bffccc88679481d60e1268c4d4c5859c70f100d4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:26:07 +0200 Subject: [PATCH 1115/1249] reversed plugin creation on master branch --- ...validate_current_renderlayer_renderable.py | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py deleted file mode 100644 index c9ab333d74..0000000000 --- a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py +++ /dev/null @@ -1,28 +0,0 @@ -import pyblish.api - -from maya import cmds - - -class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): - """Validate if current render layer has a renderable camera - - There is a bug in Redshift which occurs when the current render layer - at file open has no renderable camera. The error raised is as follows: - - "No renderable cameras found. Aborting render" - - This error is raised even if that render layer will not be rendered. - - """ - - label = "Current Render Layer Has Renderable Camera" - order = pyblish.api.ValidatorOrder - hosts = ["maya"] - families = ["colorbleed.renderlayer"] - - def process(self, instance): - layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) - cameras = cmds.ls(type="camera", long=True) - renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) - assert renderable, ("Current render layer %s has no renderable camera" - % layer) From 03dd0749e7aee3847e7e19909aca70681b5062fe Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:26:35 +0200 Subject: [PATCH 1116/1249] reversed change on master branch --- .../plugins/maya/publish/validate_deadline_connection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index 828fc61232..7f57e5b96a 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -22,6 +22,5 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): # Check response response = requests.get(AVALON_DEADLINE) assert response.ok, "Response must be ok" - assert response.text.startswith("Deadline Web Service "), ( - "Web service did not respond with 'Deadline Web Service'" - ) + assert response.text.startswith("Deadline Web Service "), \ + "Web service did not respond with 'Deadline Web Service'" \ No newline at end of file From 792319e827e6a852bc2099afedfc710ea5fbcb24 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:29:18 +0200 Subject: [PATCH 1117/1249] cosmetics --- .../plugins/maya/publish/validate_deadline_connection.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index 7f57e5b96a..a893f39572 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -22,5 +22,6 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): # Check response response = requests.get(AVALON_DEADLINE) assert response.ok, "Response must be ok" - assert response.text.startswith("Deadline Web Service "), \ - "Web service did not respond with 'Deadline Web Service'" \ No newline at end of file + assert response.text.startswith("Deadline Web Service "), ( + "Web service did not respond with 'Deadline Web Service'" + ) \ No newline at end of file From 1a3a7da7a25f9900b7d090ca232fbc38fb5687e9 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 15 Oct 2018 11:30:41 +0200 Subject: [PATCH 1118/1249] Added validator for renderable camera set for current render layer --- ...validate_current_renderlayer_renderable.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py new file mode 100644 index 0000000000..04ed259d48 --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py @@ -0,0 +1,28 @@ +import pyblish.api + +from maya import cmds + + +class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): + """Validate if current render layer has a renderable camera + + There is a bug in Redshift which occurs when the current render layer + at file open has no renderable camera. The error raised is as follows: + + "No renderable cameras found. Aborting render" + + This error is raised even if that render layer will not be rendered. + + """ + + label = "Current Render Layer Has Renderable Camera" + order = pyblish.api.ValidatorOrder + hosts = ["maya"] + families = ["colorbleed.renderlayer"] + + def process(self, instance): + layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) + cameras = cmds.ls(type="camera", long=True) + renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) + assert renderable, ("Current render layer '%s' has no renderable " + "camera" % layer) From 1a13d448d5bf3ebeb24183073db555c275258d56 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 11:06:08 +0200 Subject: [PATCH 1119/1249] Added attr and attrPrefix for alembic export, removed OrderedDict --- .../maya/create/colorbleed_pointcache.py | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index c6a4edc5c1..c24711a838 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya from colorbleed.maya import lib @@ -15,22 +13,14 @@ class CreatePointCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + data = {"writeColorSets": False, # Vertex colors with the geometry. + "renderableOnly": False, # Only renderable visible shapes + "visibleOnly": False, # only nodes that are visible + "attr": "cbId", # Add options for custom attributes + "attrPrefix": ""} # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): data[key] = value - # Write vertex colors with the geometry. - data["writeColorSets"] = False - - # Include only renderable visible shapes. - # Skips locators and empty transforms - data["renderableOnly"] = False - - # Include only nodes that are visible at least once during the - # frame range. - data["visibleOnly"] = False - - self.data = data \ No newline at end of file + self.data.update(data) From 11677142be824e246bdf7088075a2f5d52f0a836 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 11:08:49 +0200 Subject: [PATCH 1120/1249] Fetch attribute from instance for alembic export --- colorbleed/plugins/maya/publish/extract_pointcache.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 4cfb8c9c25..eb0bf75f66 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -32,6 +32,12 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): start -= handles end += handles + attrs = instance.data.get("attr", "").split(";") + if not attrs: + attrs = ["cbId"] + + attr_prefixes = instance.data.get("attrPrefix", "").split(";") + # Get extra export arguments writeColorSets = instance.data.get("writeColorSets", False) @@ -44,7 +50,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): options = { "step": instance.data.get("step", 1.0), - "attr": ["cbId"], + "attr": attrs, + "attrPrefix": attr_prefixes, "writeVisibility": True, "writeCreases": True, "writeColorSets": writeColorSets, From 721cfca954d2d0016855b2fadabac3f908ecedf4 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:07:49 +0200 Subject: [PATCH 1121/1249] Force add cbId, mandatory attribute --- colorbleed/plugins/maya/publish/extract_pointcache.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index eb0bf75f66..ccf222408f 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -33,8 +33,7 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): end += handles attrs = instance.data.get("attr", "").split(";") - if not attrs: - attrs = ["cbId"] + attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") From 1473e54796cc428ada58e1895ff522c2dd666f8e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:09:17 +0200 Subject: [PATCH 1122/1249] Removed cbId, made mandatory during export --- colorbleed/plugins/maya/create/colorbleed_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index c24711a838..02deb0a469 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -16,7 +16,7 @@ class CreatePointCache(avalon.maya.Creator): data = {"writeColorSets": False, # Vertex colors with the geometry. "renderableOnly": False, # Only renderable visible shapes "visibleOnly": False, # only nodes that are visible - "attr": "cbId", # Add options for custom attributes + "attr": "", # Add options for custom attributes "attrPrefix": ""} # get basic animation data : start / end / handles / steps From 576b905aa94e15ad26e8bf71e2742b539e8c1081 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Tue, 16 Oct 2018 12:17:56 +0200 Subject: [PATCH 1123/1249] Add attribute and attribute Prefix support for pointcache --- colorbleed/plugins/maya/create/colorbleed_model.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index c69e4bbf7b..47411e6c52 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -14,10 +14,8 @@ class CreateModel(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) - # create an ordered dict with the existing data first - data = OrderedDict(**self.data) + data = {"writeColorSets": False, # Vertex colors with the geometry. + "attr": "", # Add options for custom attributes + "attrPrefix": ""} - # Write vertex colors with the geometry. - data["writeColorSets"] = True - - self.data = data + self.data.update(data) From 0b8f0d33de2fd129fba26407abb8b1ba1c2e1130 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 17 Oct 2018 12:36:15 +0200 Subject: [PATCH 1124/1249] Added vrayscene family to validator --- .../plugins/maya/publish/increment_current_file_deadline.py | 3 ++- .../plugins/maya/publish/validate_render_single_camera.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py index cb8374a7e1..40a7634fb7 100644 --- a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py +++ b/colorbleed/plugins/maya/publish/increment_current_file_deadline.py @@ -11,7 +11,8 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["colorbleed.renderlayer", + "colorbleed.vrayscene"] optional = True def process(self, context): diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 94448d1585..9762b562fd 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -19,7 +19,8 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.renderlayer'] + families = ['colorbleed.renderlayer', + "colorbleed.vrayscene"] label = "Render Single Camera" actions = [colorbleed.maya.action.SelectInvalidAction] From e88c32e021eed5fb03298dc8f212d6c0751a9ef5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 17 Oct 2018 12:36:39 +0200 Subject: [PATCH 1125/1249] Renamed module and class for clearity --- ...nslator_settings.py => validate_vray_translator_settings.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename colorbleed/plugins/maya/publish/{validate_translator_settings.py => validate_vray_translator_settings.py} (95%) diff --git a/colorbleed/plugins/maya/publish/validate_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py similarity index 95% rename from colorbleed/plugins/maya/publish/validate_translator_settings.py rename to colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7c7479792c..7f0f737886 100644 --- a/colorbleed/plugins/maya/publish/validate_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -4,7 +4,7 @@ import colorbleed.api from maya import cmds -class ValidateTranslatorEnabled(pyblish.api.ContextPlugin): +class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): order = colorbleed.api.ValidateContentsOrder label = "VRay Translator Settings" From 9f73077013b3c77d418ff690480e281ebbca6bd3 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:06:10 +0200 Subject: [PATCH 1126/1249] Added collector for renderable cameras for render layers --- .../maya/publish/collect_renderable_camera.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_renderable_camera.py diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py new file mode 100644 index 0000000000..9eed039873 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -0,0 +1,24 @@ +import pyblish.api + +from maya import cmds + +from colorbleed.maya import lib + + +class CollectRenderableCamera(pyblish.api.InstancePlugin): + """Collect the renderable camera(s) for the render layer""" + + order = pyblish.api.CollectorOrder + label = "Collect Renderable Camera(s)" + hosts = ["maya"] + families = ["colorbleed.vrayscene", + "colorbleed.renderlayer"] + + def process(self, instance): + layer = instance.data["setMembers"] + + cameras = cmds.ls(type="camera", long=True) + with lib.renderlayer(layer): + renderable = [c for c in cameras if + cmds.getAttr("%s.renderable" % c)] + instance.data.update({"camera": renderable}) From a473890e36f73589cd6fcf023fdf031f7941aff5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:06:33 +0200 Subject: [PATCH 1127/1249] Simplified validator --- .../publish/validate_render_single_camera.py | 31 +++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 9762b562fd..581c1e920d 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -1,9 +1,6 @@ -from maya import cmds - import pyblish.api import colorbleed.api import colorbleed.maya.action -import colorbleed.maya.lib as lib class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): @@ -18,33 +15,15 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): """ order = colorbleed.api.ValidateContentsOrder + label = "Render Single Camera" hosts = ['maya'] families = ['colorbleed.renderlayer', "colorbleed.vrayscene"] - label = "Render Single Camera" + actions = [colorbleed.maya.action.SelectInvalidAction] - @staticmethod - def get_invalid(instance): - - layer = instance.data["setMembers"] - - cameras = cmds.ls(type='camera', long=True) - - with lib.renderlayer(layer): - renderable = [cam for cam in cameras if - cmds.getAttr(cam + ".renderable")] - - if len(renderable) == 0: - raise RuntimeError("No renderable cameras found.") - elif len(renderable) > 1: - return renderable - else: - return [] - def process(self, instance): """Process all the cameras in the instance""" - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Multiple renderable cameras" - "found: {0}".format(invalid)) + cameras = instance.data.get("camera", []) + assert len(cameras) == 1, ("Multiple renderable cameras" "found: %s " % + instance.data["setMembers"]) From 8201c0f62feeeba72741c70baf07a3ba99601509 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:08:20 +0200 Subject: [PATCH 1128/1249] Removed debug log --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 8cfa5f2dcc..fe1015ee65 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -104,5 +104,4 @@ class CollectVRayScene(pyblish.api.ContextPlugin): instance = context.create_instance(layer) self.log.info("Created: %s" % instance.name) - self.log.debug("VRay Data: %s" % vrscene_data) instance.data.update(data) From 95174b07a64f672e79425e608d6a76dafa9f1836 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:08:55 +0200 Subject: [PATCH 1129/1249] Added check for vrayscene instances --- .../publish/validate_vray_translator_settings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7f0f737886..b882b195b4 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -13,6 +13,20 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + # Check if there are any vray scene instances + vrayscene_instances = [] + for inst in context[:]: + if inst.data["family"] in self.families: + # Skip if instances is inactive + if not inst.data["active"]: + continue + + vrayscene_instances.append(inst) + + if not vrayscene_instances: + self.log.info("No VRay Scene instances found, skipping..") + return + # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From 34009e6750e5fa1f35de299821a7ddf68a1fe092 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:10:53 +0200 Subject: [PATCH 1130/1249] Added empty string fallback to counter NoneType error --- .../maya/publish/validate_yeti_renderscript_callbacks.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index ad4d1db911..b31e31ba52 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -56,8 +56,11 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): % renderer) return False - pre_render_callback = cmds.getAttr("defaultRenderGlobals.preMel") - post_render_callback = cmds.getAttr("defaultRenderGlobals.postMel") + pre_mel_attr = "defaultRenderGlobals.preMel" + post_mel_attr = "defaultRenderGlobals.postMel" + + pre_render_callback = cmds.getAttr(pre_mel_attr) or "" + post_render_callback = cmds.getAttr(post_mel_attr) or "" pre_callbacks = pre_render_callback.split(";") post_callbacks = post_render_callback.split(";") From 1b515188fc44f507cf63025fc92a08d2257c2f2b Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:42:52 +0200 Subject: [PATCH 1131/1249] Added log message --- .../plugins/maya/publish/collect_renderable_camera.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py index 9eed039873..dd9ec433bb 100644 --- a/colorbleed/plugins/maya/publish/collect_renderable_camera.py +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -8,7 +8,7 @@ from colorbleed.maya import lib class CollectRenderableCamera(pyblish.api.InstancePlugin): """Collect the renderable camera(s) for the render layer""" - order = pyblish.api.CollectorOrder + order = pyblish.api.CollectorOrder + 0.01 label = "Collect Renderable Camera(s)" hosts = ["maya"] families = ["colorbleed.vrayscene", @@ -21,4 +21,7 @@ class CollectRenderableCamera(pyblish.api.InstancePlugin): with lib.renderlayer(layer): renderable = [c for c in cameras if cmds.getAttr("%s.renderable" % c)] - instance.data.update({"camera": renderable}) + + self.log.info("Found cameras %s" % len(renderable)) + + instance.data.update({"cameras": renderable}) From 4ae1f2f481894bebe3134ca48f7981e6ac1325b2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:43:16 +0200 Subject: [PATCH 1132/1249] Extended comments --- .../plugins/maya/publish/validate_vray_translator_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index b882b195b4..305fa23314 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -14,6 +14,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): # Check if there are any vray scene instances + # The reason to not use host.lsattr() as used in collect_vray_scene + # is because that information is already available in the context vrayscene_instances = [] for inst in context[:]: if inst.data["family"] in self.families: From 91d43e07740de13654c2bfde895f1927b9af57c5 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:44:01 +0200 Subject: [PATCH 1133/1249] Added back get_invalid method --- .../maya/publish/validate_render_single_camera.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 581c1e920d..0a2eb997c5 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -20,10 +20,14 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): families = ['colorbleed.renderlayer', "colorbleed.vrayscene"] - actions = [colorbleed.maya.action.SelectInvalidAction] - def process(self, instance): """Process all the cameras in the instance""" - cameras = instance.data.get("camera", []) - assert len(cameras) == 1, ("Multiple renderable cameras" "found: %s " % - instance.data["setMembers"]) + + @classmethod + def get_invalid(cls, instance): + cameras = instance.data.get("cameras", []) + if len(cameras) != 1: + cls.log.error("Multiple renderable cameras" "found: %s " % + instance.data["setMembers"]) + + return [instance.data["setMembers"]] From 922c7159a5ca805c65a36925ae402afdba64bde8 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:09 +0200 Subject: [PATCH 1134/1249] Removed %04d, vray handles this on its own --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 6e161d8a4e..1c7c4b1035 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -130,7 +130,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): instance.data["outputDir"] = render_ouput # Format output file name - sequence_filename = ".".join([instance.name, "%04d", ext]) + sequence_filename = ".".join([instance.name, ext]) output_filename = os.path.join(render_ouput, sequence_filename) payload_b = { From 36cb9358e779b8261b5781e79e7eaeaa6d5156db Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:41 +0200 Subject: [PATCH 1135/1249] Get single camera from cameras in instance --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 1c7c4b1035..c213f24ced 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -191,8 +191,11 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): cmd = ('-r vray -proj {project} -cam {cam} -noRender -s {startFrame} ' '-e {endFrame} -rl {layer} -exportFramesSeparate') + # Get the camera + cammera = instance.data["cameras"][0] + return cmd.format(project=instance.context.data["workspaceDir"], - cam=instance.data.get("camera", "persp"), + cam=cammera, startFrame=instance.data["startFrame"], endFrame=instance.data["endFrame"], layer=instance.name) From dc2477cbebb04d4d7dc0fb29a3a7ab4baa1d5b85 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:53:52 +0200 Subject: [PATCH 1136/1249] Updated doctstrings --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index c213f24ced..a137a9b5e6 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -183,6 +183,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): def build_command(self, instance): """Create command for Render.exe to export vray scene + Args: + instance + Returns: str @@ -203,6 +206,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): def build_jobinfo_environment(self, env): """Format environment keys and values to match Deadline rquirements + Args: + env(dict): environment dictionary + Returns: dict From dd3b17fc9d3657b9672ede82af60a603689cf36e Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 10:55:18 +0200 Subject: [PATCH 1137/1249] Removed get camera logic --- .../maya/create/colorbleed_vrayscene.py | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 632e3d54a7..5f145685f8 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -12,27 +10,16 @@ class CreateVRayScene(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateVRayScene, self).__init__(*args, **kwargs) - # We won't be publishing this one - self.data["id"] = "avalon.vrayscene" - # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) - data = OrderedDict(**self.data) - - data["camera"] = self._get_cameras() - data["suspendRenderJob"] = False - data["suspendPublishJob"] = False - data["extendFrames"] = False - data["pools"] = "" - - self.data = data + self.data.update({ + "id": "avalon.vrayscene", # We won't be publishing this one + "suspendRenderJob": False, + "suspendPublishJob": False, + "extendFrames": False, + "pools": "" + }) self.options = {"useSelection": False} # Force no content - - def _get_cameras(self): - from maya import cmds - - return [c for c in cmds.ls(type="camera") - if cmds.getAttr("%s.renderable" % c)] From 836990b04a4a71727d5b0744c9f08ca6411137bc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 11:10:06 +0200 Subject: [PATCH 1138/1249] Remove active, mirror behavious from renderglobals --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index 5f145685f8..b9f404e1d3 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -13,6 +13,7 @@ class CreateVRayScene(avalon.maya.Creator): # We don't need subset or asset attributes self.data.pop("subset", None) self.data.pop("asset", None) + self.data.pop("active", None) self.data.update({ "id": "avalon.vrayscene", # We won't be publishing this one From e7e2afa8979181e09431f86c49419aefc9d854de Mon Sep 17 00:00:00 2001 From: wikoreman Date: Thu, 18 Oct 2018 18:04:32 +0200 Subject: [PATCH 1139/1249] Added GenerateUUIDsOnInvalidAction to plugin --- .../plugins/maya/publish/validate_node_ids_in_database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py index 7b0fddee30..d808152245 100644 --- a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py +++ b/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py @@ -23,7 +23,8 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): hosts = ['maya'] families = ["*"] - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [colorbleed.maya.action.SelectInvalidAction, + colorbleed.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) From 90e17109bca0ae0973f29090405b6a0e20d9c48f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 18:24:09 +0200 Subject: [PATCH 1140/1249] Implement first working version for FBX extraction (PLN-183) --- colorbleed/maya/plugin.py | 3 +- .../plugins/global/publish/integrate.py | 1 + .../plugins/maya/create/colorbleed_fbx.py | 18 ++ colorbleed/plugins/maya/load/load_fbx.py | 36 +++ .../plugins/maya/publish/extract_fbx.py | 212 ++++++++++++++++++ 5 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 colorbleed/plugins/maya/create/colorbleed_fbx.py create mode 100644 colorbleed/plugins/maya/load/load_fbx.py create mode 100644 colorbleed/plugins/maya/publish/extract_fbx.py diff --git a/colorbleed/maya/plugin.py b/colorbleed/maya/plugin.py index 327cf47cbd..86c6ebbd0f 100644 --- a/colorbleed/maya/plugin.py +++ b/colorbleed/maya/plugin.py @@ -131,7 +131,8 @@ class ReferenceLoader(api.Loader): file_type = { "ma": "mayaAscii", "mb": "mayaBinary", - "abc": "Alembic" + "abc": "Alembic", + "fbx": "FBX" }.get(representation["name"]) assert file_type, "Unsupported representation: %s" % representation diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index f5d07b339e..2dae53335d 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -25,6 +25,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder families = ["colorbleed.animation", "colorbleed.camera", + "colorbleed.fbx", "colorbleed.imagesequence", "colorbleed.look", "colorbleed.mayaAscii", diff --git a/colorbleed/plugins/maya/create/colorbleed_fbx.py b/colorbleed/plugins/maya/create/colorbleed_fbx.py new file mode 100644 index 0000000000..e6b57fa86f --- /dev/null +++ b/colorbleed/plugins/maya/create/colorbleed_fbx.py @@ -0,0 +1,18 @@ +import avalon.maya +from colorbleed.maya import lib + + +class CreateFBX(avalon.maya.Creator): + """FBX Export""" + + name = "fbxDefault" + label = "FBX" + family = "colorbleed.fbx" + icon = "plug" + + def __init__(self, *args, **kwargs): + super(CreateFBX, self).__init__(*args, **kwargs) + + # get basic animation data : start / end / handles / steps + for key, value in lib.collect_animation_data().items(): + self.data[key] = value diff --git a/colorbleed/plugins/maya/load/load_fbx.py b/colorbleed/plugins/maya/load/load_fbx.py new file mode 100644 index 0000000000..0dade5eca2 --- /dev/null +++ b/colorbleed/plugins/maya/load/load_fbx.py @@ -0,0 +1,36 @@ +import colorbleed.maya.plugin + + +class FBXLoader(colorbleed.maya.plugin.ReferenceLoader): + """Load the FBX""" + + families = ["colorbleed.fbx"] + representations = ["fbx"] + + label = "Reference FBX" + order = -10 + icon = "code-fork" + color = "orange" + + def process_reference(self, context, name, namespace, data): + + import maya.cmds as cmds + from avalon import maya + + # Ensure FBX plug-in is loaded + cmds.loadPlugin("fbxmaya", quiet=True) + + with maya.maintained_selection(): + nodes = cmds.file(self.fname, + namespace=namespace, + reference=True, + returnNewNodes=True, + groupReference=True, + groupName="{}:{}".format(namespace, name)) + + self[:] = nodes + + return nodes + + def switch(self, container, representation): + self.update(container, representation) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py new file mode 100644 index 0000000000..4ba28acb58 --- /dev/null +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -0,0 +1,212 @@ +import os + +from maya import cmds +import maya.mel as mel + +import pyblish.api +import avalon.maya + +import colorbleed.api + + +class ExtractFBX(colorbleed.api.Extractor): + """Extract FBX from Maya. + + This extracts reproducible FBX exports ignoring any of the settings set + on the local machine in the FBX export options window. + + All export settings are applied with the `FBXExport*` commands prior + to the `FBXExport` call itself. The options can be overridden with their + nice names as seen in the "options" property on this class. + + For more information on FBX exports see: + - https://knowledge.autodesk.com/support/maya/learn-explore/caas + /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4 + -9CB19C28F4E0-htm.html + - http://forums.cgsociety.org/archive/index.php?t-1032853.html + - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE + /LKs9hakE28kJ + + """ + + order = pyblish.api.ExtractorOrder + label = "Extract FBX" + families = ["colorbleed.fbx"] + + @property + def options(self): + """Overridable options for FBX Export + + Given in the following format + - {NAME: EXPECTED TYPE} + + If the overridden option's type does not match, + the option is not included and a warning is logged. + + """ + + return { + "smoothingGroups": bool, + "hardEdges": bool, + "tangents": bool, + "smoothMesh": bool, + "instances": bool, + # "referencedContainersContent": bool, # deprecated in Maya 2016+ + "bakeComplexAnimation": int, + "bakeComplexStart": int, + "bakeComplexEnd": int, + "bakeComplexStep": int, + "bakeResampleAnimation": bool, + "animationOnly": bool, + "useSceneName": bool, + "quaternion": str, # "euler" + "shapes": bool, + "skins": bool, + "constraints": bool, + "lights": bool, + "embeddedTextures": bool, + "inputConnections": bool, + "upAxis": str, # x, y or z, + "triangulate": bool + } + + @property + def default_options(self): + """The default options for FBX extraction. + + This includes shapes, skins, constraints, lights and incoming + connections and exports with the Y-axis as up-axis. + + By default this uses the time sliders start and end time. + + """ + + start_frame = int(cmds.playbackOptions(query=True, + animationStartTime=True)) + end_frame = int(cmds.playbackOptions(query=True, + animationEndTime=True)) + + return { + "smoothingGroups": False, + "hardEdges": False, + "tangents": False, + "smoothMesh": False, + "instances": False, + "bakeComplexAnimation": True, + "bakeComplexStart": start_frame, + "bakeComplexEnd": end_frame, + "bakeComplexStep": 1, + "bakeResampleAnimation": True, + "animationOnly": False, + "useSceneName": False, + "quaternion": "euler", + "shapes": True, + "skins": True, + "constraints": False, + "lights": True, + "embeddedTextures": True, + "inputConnections": True, + "upAxis": "y", + "triangulate": False + } + + def parse_overrides(self, instance, options): + """Inspect data of instance to determine overridden options + + An instance may supply any of the overridable options + as data, the option is then added to the extraction. + + """ + + for key in instance.data(): + if key not in self.options: + continue + + # Ensure the data is of correct type + value = instance.data(key) + if not isinstance(value, self.options[key]): + self.log.warning( + "Overridden attribute {key} was of " + "the wrong type: {invalid_type} " + "- should have been {valid_type}".format( + key=key, + invalid_type=type(value).__name__, + valid_type=self.options[key].__name__)) + continue + + options[key] = value + + return options + + def process(self, instance): + + # Ensure FBX plug-in is loaded + cmds.loadPlugin("fbxmaya", quiet=True) + + # Define output path + directory = self.staging_dir(instance) + filename = "{0}.fbx".format(instance.name) + path = os.path.join(directory, filename) + + # The export requires forward slashes because we need + # to format it into a string in a mel expression + path = path.replace('\\', '/') + + self.log.info("Extracting FBX to: {0}".format(path)) + + members = instance.data("setMembers") + self.log.info("Members: {0}".format(members)) + self.log.info("Instance: {0}".format(instance[:])) + + # Parse export options + options = self.default_options + options = self.parse_overrides(instance, options) + self.log.info("Export options: {0}".format(options)) + + # TODO: Move this out of this plug-in? (Colorbleed) + # Fallback to regular instance start and end frame data names + start = instance.data.get("startFrame", None) + if start is not None: + options['bakeComplexStart'] = start + end = instance.data.get("endFrame", None) + if end is not None: + options['bakeComplexEnd'] = end + + # First apply the default export settings to be fully consistent + # each time for successive publishes + mel.eval("FBXResetExport") + + # Apply the FBX overrides through MEL since the commands + # only work correctly in MEL according to online + # available discussions on the topic + for option, value in options.iteritems(): + key = option[0].upper() + option[1:] # uppercase first letter + + # Boolean must be passed as lower-case strings + # as to MEL standards + if isinstance(value, bool): + value = str(value).lower() + + template = "FBXExport{0} -v {1}" + if key == "UpAxis": + template = "FBXExport{0} {1}" + + cmd = template.format(key, value) + self.log.info(cmd) + mel.eval(cmd) + + # Never show the UI or generate a log + mel.eval("FBXExportShowUI -v false") + mel.eval("FBXExportGenerateLog -v false") + + # Export + with avalon.maya.maintained_selection(): + cmds.select(members, r=1, noExpand=True) + mel.eval('FBXExport -f "{}" -s'.format(path)) + + if "files" not in instance.data: + instance.data["files"] = list() + + instance.data["files"].append(filename) + + self.log.info("Extract FBX successful to: {0}".format(path)) From 3ce3d9e8aa0f9dce8da6355bd9315297ee7a6b07 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:14:29 +0200 Subject: [PATCH 1141/1249] Add FBXExportCameras option with "cameras" boolean key, default False --- colorbleed/plugins/maya/publish/extract_fbx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py index 4ba28acb58..0ec59a06ee 100644 --- a/colorbleed/plugins/maya/publish/extract_fbx.py +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -46,6 +46,7 @@ class ExtractFBX(colorbleed.api.Extractor): """ return { + "cameras": bool, "smoothingGroups": bool, "hardEdges": bool, "tangents": bool, @@ -87,6 +88,7 @@ class ExtractFBX(colorbleed.api.Extractor): animationEndTime=True)) return { + "cameras": False, "smoothingGroups": False, "hardEdges": False, "tangents": False, From 3224208a6ba931e451db8d3c9e574d8dae41daec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:37:52 +0200 Subject: [PATCH 1142/1249] Remove redundant OrderedDict() since it's implemented in avalon core --- .../plugins/maya/create/colorbleed_animation.py | 13 ++++--------- colorbleed/plugins/maya/create/colorbleed_camera.py | 7 ++----- colorbleed/plugins/maya/create/colorbleed_look.py | 6 +----- .../plugins/maya/create/colorbleed_renderglobals.py | 6 +----- .../plugins/maya/create/colorbleed_vrayproxy.py | 13 ++++--------- 5 files changed, 12 insertions(+), 33 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index b559e15ec9..6d87cb5078 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya from colorbleed.maya import lib @@ -16,21 +14,18 @@ class CreateAnimation(avalon.maya.Creator): super(CreateAnimation, self).__init__(*args, **kwargs) # create an ordered dict with the existing data first - data = OrderedDict(**self.data) # get basic animation data : start / end / handles / steps for key, value in lib.collect_animation_data().items(): - data[key] = value + self.data[key] = value # Write vertex colors with the geometry. - data["writeColorSets"] = False + self.data["writeColorSets"] = False # Include only renderable visible shapes. # Skips locators and empty transforms - data["renderableOnly"] = False + self.data["renderableOnly"] = False # Include only nodes that are visible at least once during the # frame range. - data["visibleOnly"] = False - - self.data = data \ No newline at end of file + self.data["visibleOnly"] = False diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/colorbleed/plugins/maya/create/colorbleed_camera.py index 94c1a82225..f38d8e0d43 100644 --- a/colorbleed/plugins/maya/create/colorbleed_camera.py +++ b/colorbleed/plugins/maya/create/colorbleed_camera.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -15,13 +14,11 @@ class CreateCamera(avalon.maya.Creator): super(CreateCamera, self).__init__(*args, **kwargs) # get basic animation data : start / end / handles / steps - data = OrderedDict(**self.data) animation_data = lib.collect_animation_data() for key, value in animation_data.items(): - data[key] = value + self.data[key] = value # Bake to world space by default, when this is False it will also # include the parent hierarchy in the baked results - data['bakeToWorldSpace'] = True + self.data['bakeToWorldSpace'] = True - self.data = data diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/colorbleed/plugins/maya/create/colorbleed_look.py index d5c0255360..011fdd4f92 100644 --- a/colorbleed/plugins/maya/create/colorbleed_look.py +++ b/colorbleed/plugins/maya/create/colorbleed_look.py @@ -1,4 +1,3 @@ -from collections import OrderedDict import avalon.maya from colorbleed.maya import lib @@ -14,7 +13,4 @@ class CreateLook(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateLook, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - data["renderlayer"] = lib.get_current_renderlayer() - - self.data = data + self.data["renderlayer"] = lib.get_current_renderlayer() diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 1d12d9fe9d..7d41c72d3a 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from maya import cmds from avalon.vendor import requests @@ -34,8 +32,7 @@ class CreateRenderGlobals(avalon.maya.Creator): self.data.pop("asset", None) self.data.pop("active", None) - data = OrderedDict(**self.data) - + data = self.data data["suspendPublishJob"] = False data["extendFrames"] = False data["overrideExistingFrame"] = True @@ -49,7 +46,6 @@ class CreateRenderGlobals(avalon.maya.Creator): # We add a string "-" to allow the user to not set any secondary pools data["secondaryPool"] = ["-"] + pools - self.data = data self.options = {"useSelection": False} # Force no content def process(self): diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py index 85e9e71b6d..e866a1d092 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -14,13 +12,10 @@ class CreateVrayProxy(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateVrayProxy, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - - data["animation"] = False - data["startFrame"] = 1 - data["endFrame"] = 1 + self.data["animation"] = False + self.data["startFrame"] = 1 + self.data["endFrame"] = 1 # Write vertex colors - data["vertexColors"] = False + self.data["vertexColors"] = False - self.data.update(data) From 9b7fc5302b0283508bf01f0cd1d0bd8a74a9d066 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:40:24 +0200 Subject: [PATCH 1143/1249] Remove OrderedDict and force correct order for animation data - Using a regular `dict` as intermediate type will not force any specific order and thus can have any sorting --- .../plugins/maya/create/colorbleed_yeti_cache.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 59d37429bd..2af35a41e6 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -15,12 +15,13 @@ class CreateYetiCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiCache, self).__init__(*args, **kwargs) - data = OrderedDict(**self.data) - data["peroll"] = 0 + self.data["peroll"] = 0 + # Add animation data without step and handles anim_data = lib.collect_animation_data() - data.update({"startFrame": anim_data["startFrame"], - "endFrame": anim_data["endFrame"], - "samples": 3}) + anim_data.pop("step") + anim_data.pop("handles") + self.data.update(anim_data) - self.data = data + # Add samples + self.data["samples"] = 3 From ea865601245ac8ff67558a8a36d66461aefea5aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:40:51 +0200 Subject: [PATCH 1144/1249] Fix the YetiCache `preroll` key name so it's actually being picked up :) --- colorbleed/plugins/maya/create/colorbleed_yeti_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py index 2af35a41e6..2430821f5f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py @@ -15,7 +15,7 @@ class CreateYetiCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateYetiCache, self).__init__(*args, **kwargs) - self.data["peroll"] = 0 + self.data["preroll"] = 0 # Add animation data without step and handles anim_data = lib.collect_animation_data() From 601004a9a83ba724084c6b0c09d14614cdb51e09 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:43:38 +0200 Subject: [PATCH 1145/1249] Cosmetics --- .../maya/create/colorbleed_renderglobals.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index 7d41c72d3a..e942a827d7 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -17,7 +17,7 @@ class CreateRenderGlobals(avalon.maya.Creator): # We won't be publishing this one self.data["id"] = "avalon.renderglobals" - # get pools + # Get available Deadline pools AVALON_DEADLINE = api.Session["AVALON_DEADLINE"] argument = "{}/api/pools?NamesOnly=true".format(AVALON_DEADLINE) response = requests.get(argument) @@ -32,19 +32,18 @@ class CreateRenderGlobals(avalon.maya.Creator): self.data.pop("asset", None) self.data.pop("active", None) - data = self.data - data["suspendPublishJob"] = False - data["extendFrames"] = False - data["overrideExistingFrame"] = True - data["useLegacyRenderLayers"] = True - data["priority"] = 50 - data["framesPerTask"] = 1 - data["whitelist"] = False - data["machineList"] = "" - data["useMayaBatch"] = True - data["primaryPool"] = pools + self.data["suspendPublishJob"] = False + self.data["extendFrames"] = False + self.data["overrideExistingFrame"] = True + self.data["useLegacyRenderLayers"] = True + self.data["priority"] = 50 + self.data["framesPerTask"] = 1 + self.data["whitelist"] = False + self.data["machineList"] = "" + self.data["useMayaBatch"] = True + self.data["primaryPool"] = pools # We add a string "-" to allow the user to not set any secondary pools - data["secondaryPool"] = ["-"] + pools + self.data["secondaryPool"] = ["-"] + pools self.options = {"useSelection": False} # Force no content @@ -52,7 +51,8 @@ class CreateRenderGlobals(avalon.maya.Creator): exists = cmds.ls(self.name) assert len(exists) <= 1, ( - "More than one renderglobal exists, this is a bug") + "More than one renderglobal exists, this is a bug" + ) if exists: return cmds.warning("%s already exists." % exists[0]) From 9c815635a85bb0f8b232296558afd18f92fe2f63 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 18 Oct 2018 22:45:06 +0200 Subject: [PATCH 1146/1249] Remove Unused Loader (old deprecated code, superseded by load_alembic) --- .../plugins/maya/load/_load_animation.py | 48 ------------------- 1 file changed, 48 deletions(-) delete mode 100644 colorbleed/plugins/maya/load/_load_animation.py diff --git a/colorbleed/plugins/maya/load/_load_animation.py b/colorbleed/plugins/maya/load/_load_animation.py deleted file mode 100644 index f4bcd6881d..0000000000 --- a/colorbleed/plugins/maya/load/_load_animation.py +++ /dev/null @@ -1,48 +0,0 @@ -import colorbleed.maya.plugin - - -class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the avalon.animation family""" - - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.pointcache"] - representations = ["abc"] - - label = "Reference animation" - order = -10 - icon = "code-fork" - color = "orange" - - def process_reference(self, context, name, namespace, data): - - import maya.cmds as cmds - from avalon import maya - - cmds.loadPlugin("AbcImport.mll", quiet=True) - # Prevent identical alembic nodes from being shared - # Create unique namespace for the cameras - - # Get name from asset being loaded - # Assuming name is subset name from the animation, we split the number - # suffix from the name to ensure the namespace is unique - name = name.split("_")[0] - namespace = maya.unique_namespace("{}_".format(name), - format="%03d", - suffix="_abc") - - # hero_001 (abc) - # asset_counter{optional} - - nodes = cmds.file(self.fname, - namespace=namespace, - sharedReferenceFile=False, - groupReference=True, - groupName="{}:{}".format(namespace, name), - reference=True, - returnNewNodes=True) - - # load colorbleed ID attribute - self[:] = nodes - - return nodes From cee27ac3e4c482baaac683635fc3316ff788cb83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 19 Oct 2018 13:10:19 +0200 Subject: [PATCH 1147/1249] Use new-style Pyblish instance.data (dict) access and use handles in frame range --- .../plugins/maya/publish/extract_fbx.py | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/colorbleed/plugins/maya/publish/extract_fbx.py index 0ec59a06ee..139c203672 100644 --- a/colorbleed/plugins/maya/publish/extract_fbx.py +++ b/colorbleed/plugins/maya/publish/extract_fbx.py @@ -120,12 +120,12 @@ class ExtractFBX(colorbleed.api.Extractor): """ - for key in instance.data(): + for key in instance.data: if key not in self.options: continue # Ensure the data is of correct type - value = instance.data(key) + value = instance.data[key] if not isinstance(value, self.options[key]): self.log.warning( "Overridden attribute {key} was of " @@ -156,7 +156,7 @@ class ExtractFBX(colorbleed.api.Extractor): self.log.info("Extracting FBX to: {0}".format(path)) - members = instance.data("setMembers") + members = instance.data["setMembers"] self.log.info("Members: {0}".format(members)) self.log.info("Instance: {0}".format(instance[:])) @@ -165,14 +165,16 @@ class ExtractFBX(colorbleed.api.Extractor): options = self.parse_overrides(instance, options) self.log.info("Export options: {0}".format(options)) - # TODO: Move this out of this plug-in? (Colorbleed) - # Fallback to regular instance start and end frame data names - start = instance.data.get("startFrame", None) - if start is not None: - options['bakeComplexStart'] = start - end = instance.data.get("endFrame", None) - if end is not None: - options['bakeComplexEnd'] = end + # Collect the start and end including handles + start = instance.data["startFrame"] + end = instance.data["endFrame"] + handles = instance.data.get("handles", 0) + if handles: + start -= handles + end += handles + + options['bakeComplexStart'] = start + options['bakeComplexEnd'] = end # First apply the default export settings to be fully consistent # each time for successive publishes From 8ae48b7b66d853efff316a85f27f04ad25e850dc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Oct 2018 11:10:46 +0200 Subject: [PATCH 1148/1249] Fix regex (PLN-188) --- colorbleed/plugins/global/publish/submit_publish_job.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index e91720a884..ed00cb9343 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -155,14 +155,14 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): # Add in regex for sequence filename # This assumes the output files start with subset name and ends with - # a file extension. + # a file extension. The "ext" key includes the dot with the extension. if "ext" in instance.data: - ext = instance.data["ext"].strip(".") + ext = re.escape(instance.data["ext"]) else: ext = "\.\D+" - regex = "^{subset}.*\d+\.{ext}$".format(subset=re.escape(subset), - ext=re.escape(ext)) + regex = "^{subset}.*\d+{ext}$".format(subset=re.escape(subset), + ext=ext) # Remove deadline submission job, not needed in metadata data.pop("deadlineSubmissionJob") From 333a2f5303c741bd29288e57e9ef3e79be211f8c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 11:25:29 +0200 Subject: [PATCH 1149/1249] Removed check for active as active has been removed from instance --- .../maya/publish/validate_vray_translator_settings.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 305fa23314..a41e8e00e4 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -16,14 +16,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): # Check if there are any vray scene instances # The reason to not use host.lsattr() as used in collect_vray_scene # is because that information is already available in the context - vrayscene_instances = [] - for inst in context[:]: - if inst.data["family"] in self.families: - # Skip if instances is inactive - if not inst.data["active"]: - continue - - vrayscene_instances.append(inst) + vrayscene_instances = [i for i in context[:] if i.data["family"] + in self.families] if not vrayscene_instances: self.log.info("No VRay Scene instances found, skipping..") From 612445011d95e032387f6a01af894df0b19848b1 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 11:59:49 +0200 Subject: [PATCH 1150/1249] Added get_invalid method, improved lookup speed, --- .../publish/validate_vray_translator_settings.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index a41e8e00e4..7f2c58aca5 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -13,14 +13,23 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + invalid = self.get_invalid(context) + if invalid: + raise RuntimeError("Found invalid VRay Translator settings!") + + @classmethod + def get_invalid(cls, context): + + invalid = False + # Check if there are any vray scene instances # The reason to not use host.lsattr() as used in collect_vray_scene # is because that information is already available in the context vrayscene_instances = [i for i in context[:] if i.data["family"] - in self.families] + in cls.families] if not vrayscene_instances: - self.log.info("No VRay Scene instances found, skipping..") + cls.log.info("No VRay Scene instances found, skipping..") return # Get vraySettings node From 2e3dbaf047c44c80758ab3dc77fddd1fd2473f5c Mon Sep 17 00:00:00 2001 From: wikoreman Date: Mon, 22 Oct 2018 12:00:51 +0200 Subject: [PATCH 1151/1249] Fixed logging issue, return validation result, changed decorator to staticmethod --- .../publish/validate_vray_translator_settings.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 7f2c58aca5..2a7c1584b1 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -39,17 +39,22 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): node = vray_settings[0] if not cmds.getAttr("{}.vrscene_on".format(node)): - self.info.error("Export vrscene not enabled") + cls.log.error("Export vrscene not enabled") + invalid = True if not cmds.getAttr("{}.misc_eachFrameInFile".format(node)): - self.info.error("Each Frame in File not enabled") + cls.log.error("Each Frame in File not enabled") + invalid = True vrscene_filename = cmds.getAttr("{}.vrscene_filename".format(node)) if vrscene_filename != "vrayscene//_/": - self.info.error("Template for file name is wrong") + cls.log.error("Template for file name is wrong") + invalid = True - @classmethod - def repair(cls, context): + return invalid + + @staticmethod + def repair(): vray_settings = cmds.ls(type="VRaySettingsNode") if not vray_settings: From ef46fa1bcb494588327b28974c8a077c268b68c9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 22 Oct 2018 14:22:42 +0200 Subject: [PATCH 1152/1249] Pass context along with RepairContextAction and refactor its usage --- colorbleed/action.py | 2 +- colorbleed/plugins/maya/publish/validate_maya_units.py | 2 +- .../plugins/maya/publish/validate_vray_translator_settings.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/action.py b/colorbleed/action.py index ea1f5e13f4..bda3b0d61a 100644 --- a/colorbleed/action.py +++ b/colorbleed/action.py @@ -87,6 +87,6 @@ class RepairContextAction(pyblish.api.Action): # Apply pyblish.logic to get the instances for the plug-in if plugin in errored_plugins: self.log.info("Attempting fix ...") - plugin.repair() + plugin.repair(context) diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/colorbleed/plugins/maya/publish/validate_maya_units.py index 04db95fdde..ba38fbe512 100644 --- a/colorbleed/plugins/maya/publish/validate_maya_units.py +++ b/colorbleed/plugins/maya/publish/validate_maya_units.py @@ -36,7 +36,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin): assert fps and fps == asset_fps, "Scene must be %s FPS" % asset_fps @classmethod - def repair(cls): + def repair(cls, context): """Fix the current FPS setting of the scene, set to PAL(25.0 fps)""" cls.log.info("Setting angular unit to 'degrees'") diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 2a7c1584b1..ecd97e6f95 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -53,8 +53,8 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): return invalid - @staticmethod - def repair(): + @classmethod + def repair(cls, context): vray_settings = cmds.ls(type="VRaySettingsNode") if not vray_settings: From b1facf5d37aa9b3a45012b9ef69b2715f0a04dd9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 23 Oct 2018 12:42:31 +0200 Subject: [PATCH 1153/1249] Fix published textures sometimes not being remapped correctly (LKD-17) --- .../plugins/maya/publish/extract_look.py | 63 +++++++++++++++---- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/colorbleed/plugins/maya/publish/extract_look.py index c359fc10b5..04db69a3bd 100644 --- a/colorbleed/plugins/maya/publish/extract_look.py +++ b/colorbleed/plugins/maya/publish/extract_look.py @@ -1,5 +1,7 @@ import os import json +import tempfile +import contextlib from collections import OrderedDict from maya import cmds @@ -11,6 +13,38 @@ import colorbleed.api import colorbleed.maya.lib as lib +@contextlib.contextmanager +def no_workspace_dir(): + """Force maya to a fake temporary workspace directory. + + Note: This is not maya.cmds.workspace 'rootDirectory' but the 'directory' + + This helps to avoid Maya automatically remapping image paths to files + relative to the currently set directory. + + """ + + # Store current workspace + original = cmds.workspace(query=True, directory=True) + + # Set a fake workspace + fake_workspace_dir = tempfile.mkdtemp() + cmds.workspace(directory=fake_workspace_dir) + + try: + yield + finally: + try: + cmds.workspace(directory=original) + except RuntimeError: + # If the original workspace directory didn't exist either + # ignore the fact that it fails to reset it to the old path + pass + + # Remove the temporary directory + os.rmdir(fake_workspace_dir) + + class ExtractLook(colorbleed.api.Extractor): """Extract Look (Maya Ascii + JSON) @@ -65,18 +99,23 @@ class ExtractLook(colorbleed.api.Extractor): with lib.renderlayer(layer): # TODO: Ensure membership edits don't become renderlayer overrides with lib.empty_sets(sets, force=True): - with lib.attribute_values(remap): - with avalon.maya.maintained_selection(): - cmds.select(sets, noExpand=True) - cmds.file(maya_path, - force=True, - typ="mayaAscii", - exportSelected=True, - preserveReferences=False, - channels=True, - constraints=True, - expressions=True, - constructionHistory=True) + # To avoid Maya trying to automatically remap the file + # textures relative to the `workspace -directory` we force + # it to a fake temporary workspace. This fixes textures + # getting incorrectly remapped. (LKD-17, PLN-101) + with no_workspace_dir(): + with lib.attribute_values(remap): + with avalon.maya.maintained_selection(): + cmds.select(sets, noExpand=True) + cmds.file(maya_path, + force=True, + typ="mayaAscii", + exportSelected=True, + preserveReferences=False, + channels=True, + constraints=True, + expressions=True, + constructionHistory=True) # Write the JSON data self.log.info("Extract json..") From 64dd82d08f55832eb33f1f44620d2cae2d5c0ddd Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:35:49 +0200 Subject: [PATCH 1154/1249] Updated submit to match MayaBatch job, added framesPerTask, improved job name --- .../maya/publish/submit_vray_deadline.py | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index a137a9b5e6..16fa6d02a2 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -51,6 +51,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): filename, vrscene_output) + start_frame = int(instance.data["startFrame"]) + end_frame = int(instance.data["endFrame"]) + # Primary job self.log.info("Submitting export job ..") @@ -60,31 +63,35 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "BatchName": batch_name, # Job name, as seen in Monitor - "Name": task_name, + "Name": "{} [{}-{}]".format(task_name, start_frame, end_frame), # Arbitrary username, for visualisation in Monitor "UserName": deadline_user, - "Plugin": "MayaCmd", - "Frames": "1", + "Plugin": "MayaBatch", + "Frames": "{}-{}".format(start_frame, end_frame), + "FramesPerTask": instance.data.get("framesPerTask", 1), "Comment": context.data.get("comment", ""), + + "OutputFilename0": os.path.dirname(first_file), }, "PluginInfo": { + # Renderer + "Renderer": "vray", + # Mandatory for Deadline "Version": cmds.about(version=True), # Input "SceneFile": filepath, - # Output directory and filename - "OutputFilePath": vrscene_output.replace("\\", "/"), - - "CommandLineOptions": self.build_command(instance), - - "UseOnlyCommandLineOptions": True, "SkipExistingFrames": True, + + "UsingRenderLayers": True, + + "UseLegacyRenderLayers": True }, # Mandatory for Deadline, may be empty @@ -133,19 +140,26 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): sequence_filename = ".".join([instance.name, ext]) output_filename = os.path.join(render_ouput, sequence_filename) + # Ensure folder exists: + if not os.path.exists(render_ouput): + os.makedirs(render_ouput) + payload_b = { "JobInfo": { "JobDependency0": dependency["_id"], "BatchName": batch_name, - "Name": "Render {}".format(task_name), + "Name": "Render {} [{}-{}]".format(task_name, + start_frame, + end_frame), "UserName": deadline_user, "Frames": "{}-{}".format(start_frame, end_frame), "Plugin": "Vray", "OverrideTaskExtraInfoNames": False, - "Whitelist": "cb7" + + "OutputFilename0": render_ouput, }, "PluginInfo": { From c3c11f63be7e7c31d7e9d270c46980a008197e90 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:37:25 +0200 Subject: [PATCH 1155/1249] Added vrscene_render_on to validator --- .../maya/publish/validate_vray_translator_settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index ecd97e6f95..f28801b708 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -38,6 +38,10 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): node = vray_settings[0] + if cmds.setAttr("{}.vrscene_render_on".format(node)): + cls.log.error("Render is enabled, this should be disabled") + invalid = True + if not cmds.getAttr("{}.vrscene_on".format(node)): cls.log.error("Export vrscene not enabled") invalid = True @@ -62,6 +66,7 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): else: node = vray_settings[0] + cmds.setAttr("{}.vrscene_render_on".format(node), False) cmds.setAttr("{}.vrscene_on".format(node), True) cmds.setAttr("{}.misc_eachFrameInFile".format(node), True) cmds.setAttr("{}.vrscene_filename".format(node), From 0ebabdb6f2635139818bca3fff7fdc7bffdf831a Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 12:37:53 +0200 Subject: [PATCH 1156/1249] Added frames per task --- colorbleed/plugins/maya/create/colorbleed_vrayscene.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py index b9f404e1d3..82f1b11682 100644 --- a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py +++ b/colorbleed/plugins/maya/create/colorbleed_vrayscene.py @@ -20,7 +20,8 @@ class CreateVRayScene(avalon.maya.Creator): "suspendRenderJob": False, "suspendPublishJob": False, "extendFrames": False, - "pools": "" + "pools": "", + "framesPerTask": 1 }) self.options = {"useSelection": False} # Force no content From 3aeac688087a117d216005bf4d232bcf3d9cc045 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Wed, 24 Oct 2018 14:18:33 +0200 Subject: [PATCH 1157/1249] Added . to extension to ensure regex works when publishing --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index fe1015ee65..c97d7fc44b 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -86,7 +86,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): "endFrame": end_frame, "renderer": "vray", "resolution": resolution, - "ext": extension, + "ext": ".{}".format(extension), # instance subset "family": "VRay Scene", From a2f764a5acc9ff6097c6ee6320eb7638199e8726 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 13:57:12 +0200 Subject: [PATCH 1158/1249] Collect machine name, add to context data --- .../plugins/global/publish/collect_machine_name.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 colorbleed/plugins/global/publish/collect_machine_name.py diff --git a/colorbleed/plugins/global/publish/collect_machine_name.py b/colorbleed/plugins/global/publish/collect_machine_name.py new file mode 100644 index 0000000000..e1c4a88669 --- /dev/null +++ b/colorbleed/plugins/global/publish/collect_machine_name.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectMachineName(pyblish.api.ContextPlugin): + label = "Local Machine Name" + order = pyblish.api.CollectorOrder + hosts = ["*"] + + def process(self, context): + import socket + + machine_name = socket.gethostname() + self.log.info("Machine name: %s" % machine_name) + context.data.update({"machine": machine_name}) From 8060691cad1bc064ae0c2f836cba86b7b98e75b2 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 13:57:27 +0200 Subject: [PATCH 1159/1249] Add machine to version data --- colorbleed/plugins/global/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 2dae53335d..5d757b3b1c 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -340,7 +340,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "time": context.data["time"], "author": context.data["user"], "source": source, - "comment": context.data.get("comment")} + "comment": context.data.get("comment"), + "machine": context.data.get("machine")} # Include optional data if present in optionals = ["startFrame", "endFrame", "step", "handles"] From 74e4b3b666a67a525491a66c891c433b40bf7929 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 14:57:40 +0200 Subject: [PATCH 1160/1249] Added FPS collector for Houdini --- .../houdini/publish/collect_workscene_fps.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/houdini/publish/collect_workscene_fps.py b/colorbleed/plugins/houdini/publish/collect_workscene_fps.py new file mode 100644 index 0000000000..c145eea519 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/collect_workscene_fps.py @@ -0,0 +1,15 @@ +import pyblish.api +import hou + + +class CollectWorksceneFPS(pyblish.api.ContextPlugin): + """Get the FPS of the work scene""" + + label = "Workscene FPS" + order = pyblish.api.CollectorOrder + hosts = ["houdini"] + + def process(self, context): + fps = hou.fps() + self.log.info("Workscene FPS: %s" % fps) + context.data.update({"fps": fps}) From fecf960bc84875c0966a3b0099fe22274dcfa612 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 14:57:55 +0200 Subject: [PATCH 1161/1249] Added workscene fps collector for Maya --- .../plugins/maya/publish/collect_workscene_fps.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/maya/publish/collect_workscene_fps.py b/colorbleed/plugins/maya/publish/collect_workscene_fps.py new file mode 100644 index 0000000000..41d6ffea33 --- /dev/null +++ b/colorbleed/plugins/maya/publish/collect_workscene_fps.py @@ -0,0 +1,15 @@ +import pyblish.api +from maya import mel + + +class CollectWorksceneFPS(pyblish.api.ContextPlugin): + """Get the FPS of the work scene""" + + label = "Workscene FPS" + order = pyblish.api.CollectorOrder + hosts = ["maya"] + + def process(self, context): + fps = mel.eval('currentTimeUnitToFPS()') + self.log.info("Workscene FPS: %s" % fps) + context.data.update({"fps": fps}) From 8daafb20b682f6d3de93737453f24d40efcaa30a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 26 Oct 2018 15:04:39 +0200 Subject: [PATCH 1162/1249] Fix PLN-97 `get_id_required_nodes` correctly preserving filtered input nodes --- colorbleed/maya/lib.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..b23ad97331 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -993,9 +993,14 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): nodes (set): list of filtered nodes """ + lookup = None if nodes is None: # Consider all nodes nodes = cmds.ls() + else: + # Build a lookup for the only allowed nodes in output based + # on `nodes` input of the function (+ ensure long names) + lookup = set(cmds.ls(nodes, long=True)) def _node_type_exists(node_type): try: @@ -1004,8 +1009,8 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): except RuntimeError: return False - # `readOnly` flag is obsolete as of Maya 2016 therefor we explicitly remove - # default nodes and reference nodes + # `readOnly` flag is obsolete as of Maya 2016 therefore we explicitly + # remove default nodes and reference nodes camera_shapes = ["frontShape", "sideShape", "topShape", "perspShape"] ignore = set() @@ -1029,8 +1034,7 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): if cmds.pluginInfo("pgYetiMaya", query=True, loaded=True): types.append("pgYetiMaya") - # We *always* ignore intermediate shapes, so we filter them out - # directly + # We *always* ignore intermediate shapes, so we filter them out directly nodes = cmds.ls(nodes, type=types, long=True, noIntermediate=True) # The items which need to pass the id to their parent @@ -1047,6 +1051,12 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): if not nodes: return nodes + # Ensure only nodes from the input `nodes` are returned when a + # filter was applied on function call because we also iterated + # to parents and alike + if lookup is not None: + nodes &= lookup + # Avoid locked nodes nodes_list = list(nodes) locked = cmds.lockNode(nodes_list, query=True, lock=True) From a186b67d6f8b64bdc59ef911a7bd525c55f4e4e6 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 15:16:57 +0200 Subject: [PATCH 1163/1249] Added fps to version data --- colorbleed/plugins/global/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/integrate.py b/colorbleed/plugins/global/publish/integrate.py index 5d757b3b1c..869291c91b 100644 --- a/colorbleed/plugins/global/publish/integrate.py +++ b/colorbleed/plugins/global/publish/integrate.py @@ -341,7 +341,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "author": context.data["user"], "source": source, "comment": context.data.get("comment"), - "machine": context.data.get("machine")} + "machine": context.data.get("machine"), + "fps": context.data.get("fps")} # Include optional data if present in optionals = ["startFrame", "endFrame", "step", "handles"] From 40d28e3004c8b187252130e74b748706309ec1cc Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 16:00:27 +0200 Subject: [PATCH 1164/1249] Added validate_fps and set fps functions --- colorbleed/houdini/lib.py | 48 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/colorbleed/houdini/lib.py b/colorbleed/houdini/lib.py index 78adbc5790..179cb0670d 100644 --- a/colorbleed/houdini/lib.py +++ b/colorbleed/houdini/lib.py @@ -4,15 +4,17 @@ from contextlib import contextmanager import hou +from colorbleed import lib + from avalon import api, io -from avalon.houdini import lib +from avalon.houdini import lib as houdini def set_id(node, unique_id, overwrite=False): exists = node.parm("id") if not exists: - lib.imprint(node, {"id": unique_id}) + houdini.imprint(node, {"id": unique_id}) if not exists and overwrite: node.setParm("id", unique_id) @@ -188,3 +190,45 @@ def attribute_values(node, data): pass finally: node.setParms(previous_attrs) + + +def set_scene_fps(fps): + hou.setFps(fps) + + +# Valid FPS +def validate_fps(): + """Validate current scene FPS and show pop-up when it is incorrect + + Returns: + bool + + """ + + fps = lib.get_asset_fps() + current_fps = hou.fps() # returns float + + if current_fps != fps: + + from ..widgets import popup + + # Find main window + parent = hou.ui.mainQtWindow() + if parent is None: + pass + else: + dialog = popup.Popup2(parent=parent) + dialog.setModal(True) + dialog.setWindowTitle("Maya scene not in line with project") + dialog.setMessage("The FPS is out of sync, please fix") + + # Set new text for button (add optional argument for the popup?) + toggle = dialog.widgets["toggle"] + toggle.setEnabled(False) + dialog.on_show.connect(lambda: set_scene_fps(fps)) + + dialog.show() + + return False + + return True From e5f86242a0a051f4fc8364e5d222dbe77968bd01 Mon Sep 17 00:00:00 2001 From: wikoreman Date: Fri, 26 Oct 2018 16:00:57 +0200 Subject: [PATCH 1165/1249] Added before_save to function for callback --- colorbleed/houdini/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index 4d407af609..e4640e2857 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -35,11 +35,10 @@ def install(): log.info("Installing callbacks ... ") avalon.on("init", on_init) + avalon.before("save", before_save) avalon.on("save", on_save) avalon.on("open", on_open) - log.info("Overriding existing event 'taskChanged'") - log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] @@ -48,6 +47,10 @@ def on_init(*args): houdini.on_houdini_initialize() +def before_save(*args): + return lib.validate_fps() + + def on_save(*args): avalon.logger.info("Running callback on save..") @@ -72,7 +75,6 @@ def on_open(*args): # Get main window parent = hou.ui.mainQtWindow() - if parent is None: log.info("Skipping outdated content pop-up " "because Maya window can't be found.") From f639a0c83a5d52f6cb97c867a370e0913c58ed12 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 09:47:49 +0100 Subject: [PATCH 1166/1249] Only validate mesh shader connections, ignore curves --- .../maya/publish/validate_mesh_shader_connections.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py index 2d9d82eaa5..723ff2d3e6 100644 --- a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py +++ b/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py @@ -24,7 +24,7 @@ def get_invalid_sets(shape): """ invalid = [] - sets = cmds.listSets(object=shape, t=1, extendToShape=False) + sets = cmds.listSets(object=shape, t=1, extendToShape=False) or [] for s in sets: members = cmds.sets(s, query=True, nodesOnly=True) if not members: @@ -93,7 +93,9 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): def get_invalid(instance): shapes = cmds.ls(instance[:], dag=1, leaf=1, shapes=1, long=True) - shapes = cmds.ls(shapes, shapes=True, noIntermediate=True, long=True) + + # todo: allow to check anything that can have a shader + shapes = cmds.ls(shapes, noIntermediate=True, long=True, type="mesh") invalid = [] for shape in shapes: From c0c5e7090711ea6ed42f736b9e1393c5168df38f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 09:48:16 +0100 Subject: [PATCH 1167/1249] Yeti rig input collection, only search connections to dagNodes --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c0d7cba2d2..c673581c8a 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -45,6 +45,9 @@ class CollectYetiRig(pyblish.api.InstancePlugin): source=True, destination=False, connections=True, + # Only allow inputs from dagNodes + # (avoid display layers, etc.) + type="dagNode", plugs=True) or [] # Group per source, destination pair. We need to reverse the connection From eb79702951147453ddb26a87a1d27028f600f6d6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 10:00:49 +0100 Subject: [PATCH 1168/1249] Fix bug on shader context manager (introduced when embedding dependencies) --- colorbleed/maya/lib.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..a0f9823f88 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -541,7 +541,6 @@ def get_shader_assignments_from_shapes(shapes): shapes = cmds.ls(shapes, long=True, - selection=True, shapes=True, objectsOnly=True) if not shapes: @@ -560,7 +559,7 @@ def get_shader_assignments_from_shapes(shapes): type="shadingEngine") or [] shading_groups = list(set(shading_groups)) for shading_group in shading_groups: - assignments[shading_group].add(shape) + assignments[shading_group].append(shape) return dict(assignments) @@ -569,7 +568,7 @@ def get_shader_assignments_from_shapes(shapes): def shader(nodes, shadingEngine="initialShadingGroup"): """Assign a shader to nodes during the context""" - shapes = cmds.ls(nodes, dag=1, o=1, shapes=1, long=1) + shapes = cmds.ls(nodes, dag=1, objectsOnly=1, shapes=1, long=1) original = get_shader_assignments_from_shapes(shapes) try: @@ -582,7 +581,7 @@ def shader(nodes, shadingEngine="initialShadingGroup"): # Assign original shaders for sg, members in original.items(): if members: - cmds.sets(shapes, edit=True, forceElement=shadingEngine) + cmds.sets(members, edit=True, forceElement=sg) @contextlib.contextmanager From f6a99d835fa01f328f73e7a8f2c074f8bee2250a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 1 Nov 2018 10:02:01 +0100 Subject: [PATCH 1169/1249] Force load objExport plug-in at Maya launch --- colorbleed/maya/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 86106ee593..4e7bb677b4 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -98,9 +98,14 @@ def on_init(_): utils.executeDeferred(_fn) except Exception as exc: print(exc) - + + # Force load Alembic so referenced alembics + # work correctly on scene open cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) + + # Force load objExport plug-in (requested by artists) + cmds.loadPlugin("objExport", quiet=True) from .customize import override_component_mask_commands safe_deferred(override_component_mask_commands) From e8833e207e84694094be24bf7dabaec84ee42cc1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sun, 4 Nov 2018 14:55:20 +0100 Subject: [PATCH 1170/1249] PLN-25 Ensure Creator plug-ins in Maya undo in one go. --- .../plugins/maya/create/colorbleed_renderglobals.py | 8 +++++--- colorbleed/plugins/maya/create/colorbleed_rig.py | 12 +++++++----- .../plugins/maya/create/colorbleed_yeti_rig.py | 11 ++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py index e942a827d7..9fc3e0ceab 100644 --- a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py +++ b/colorbleed/plugins/maya/create/colorbleed_renderglobals.py @@ -1,5 +1,7 @@ from maya import cmds +import colorbleed.maya.lib as lib + from avalon.vendor import requests import avalon.maya from avalon import api @@ -57,6 +59,6 @@ class CreateRenderGlobals(avalon.maya.Creator): if exists: return cmds.warning("%s already exists." % exists[0]) - super(CreateRenderGlobals, self).process() - - cmds.setAttr("{}.machineList".format(self.name), lock=True) + with lib.undo_chunk(): + super(CreateRenderGlobals, self).process() + cmds.setAttr("{}.machineList".format(self.name), lock=True) diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/colorbleed/plugins/maya/create/colorbleed_rig.py index 6947aaac31..1212abb6ce 100644 --- a/colorbleed/plugins/maya/create/colorbleed_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_rig.py @@ -1,5 +1,6 @@ from maya import cmds +import colorbleed.maya.lib as lib import avalon.maya @@ -12,10 +13,11 @@ class CreateRig(avalon.maya.Creator): icon = "wheelchair" def process(self): - instance = super(CreateRig, self).process() - self.log.info("Creating Rig instance set up ...") + with lib.undo_chunk(): + instance = super(CreateRig, self).process() - controls = cmds.sets(name="controls_SET", empty=True) - pointcache = cmds.sets(name="out_SET", empty=True) - cmds.sets([controls, pointcache], forceElement=instance) + self.log.info("Creating Rig instance set up ...") + controls = cmds.sets(name="controls_SET", empty=True) + pointcache = cmds.sets(name="out_SET", empty=True) + cmds.sets([controls, pointcache], forceElement=instance) diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py index 3b21c586a7..55051100ad 100644 --- a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py +++ b/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py @@ -1,5 +1,6 @@ from maya import cmds +import colorbleed.maya.lib as lib import avalon.maya @@ -12,9 +13,9 @@ class CreateYetiRig(avalon.maya.Creator): def process(self): - instance = super(CreateYetiRig, self).process() + with lib.undo_chunk(): + instance = super(CreateYetiRig, self).process() - self.log.info("Creating Rig instance set up ...") - - input_meshes = cmds.sets(name="input_SET", empty=True) - cmds.sets(input_meshes, forceElement=instance) + self.log.info("Creating Rig instance set up ...") + input_meshes = cmds.sets(name="input_SET", empty=True) + cmds.sets(input_meshes, forceElement=instance) From 03f267b716c10140114191e2dc9869da6fa9a70a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:37:58 +0100 Subject: [PATCH 1171/1249] Fix PLN-196: Pointcache creator attributes in correct order --- .../maya/create/colorbleed_pointcache.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 02deb0a469..495c433e87 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -13,14 +13,13 @@ class CreatePointCache(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) - data = {"writeColorSets": False, # Vertex colors with the geometry. - "renderableOnly": False, # Only renderable visible shapes - "visibleOnly": False, # only nodes that are visible - "attr": "", # Add options for custom attributes - "attrPrefix": ""} + # Add animation data + self.data.update(lib.collect_animation_data()) - # get basic animation data : start / end / handles / steps - for key, value in lib.collect_animation_data().items(): - data[key] = value + self.data["writeColorSets"] = False # Vertex colors with the geometry. + self.data["renderableOnly"] = False # Only renderable visible shapes + self.data["visibleOnly"] = False # only nodes that are visible - self.data.update(data) + # Add options for custom attributes + self.data["attr"] = "" + self.data["attrPrefix"] = "" From 002e612f81abb1244e5e987a11a79c0df453307e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:49:53 +0100 Subject: [PATCH 1172/1249] Fix PLN-195: Ignore empty values in list type options in extract_alembic --- colorbleed/maya/lib.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 2ca874dd7d..4bb68c3ef6 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -927,6 +927,18 @@ def extract_alembic(file, raise TypeError("Alembic option unsupported type: " "{0} (expected {1})".format(value, valid_types)) + # Ignore empty values, like an empty string, since they mess up how + # job arguments are built + if isinstance(value, (list, tuple)): + value = [x for x in value if x.strip()] + + # Ignore option completely if no values remaining + if not value: + options.pop(key) + continue + + options[key] = value + # The `writeCreases` argument was changed to `autoSubd` in Maya 2018+ maya_version = int(cmds.about(version=True)) if maya_version >= 2018: From d21d1bea88a6d87764ade6d6cc386d0723734888 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 5 Nov 2018 21:54:12 +0100 Subject: [PATCH 1173/1249] Ignore empty attribute entries in extract_pointcache plug-in --- colorbleed/plugins/maya/publish/extract_pointcache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index ccf222408f..4cd894d7f4 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -33,9 +33,11 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): end += handles attrs = instance.data.get("attr", "").split(";") + attrs = [value for value in attrs if value.strip()] attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") + attr_prefixes = [value for value in attr_prefixes if value.strip()] # Get extra export arguments writeColorSets = instance.data.get("writeColorSets", False) From b767b7b472ea04d4ceec51174fb814ad838100c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 09:32:18 +0100 Subject: [PATCH 1174/1249] Fix PLN-197: Return component assignments with `get_shader_assignments_from_shapes` --- colorbleed/maya/lib.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 1d632cf0e1..7fa1a3a16d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -519,12 +519,15 @@ def no_undo(flush=False): cmds.undoInfo(**{keyword: original}) -def get_shader_assignments_from_shapes(shapes): +def get_shader_assignments_from_shapes(shapes, components=True): """Return the shape assignment per related shading engines. Returns a dictionary where the keys are shadingGroups and the values are lists of assigned shapes or shape-components. + Since `maya.cmds.sets` returns shader members on the shapes as components + on the transform we correct that in this method too. + For the 'shapes' this will return a dictionary like: { "shadingEngineX": ["nodeX", "nodeY"], @@ -533,6 +536,7 @@ def get_shader_assignments_from_shapes(shapes): Args: shapes (list): The shapes to collect the assignments for. + components (bool): Whether to include the component assignments. Returns: dict: The {shadingEngine: shapes} relationships @@ -561,6 +565,36 @@ def get_shader_assignments_from_shapes(shapes): for shading_group in shading_groups: assignments[shading_group].append(shape) + if components: + # Note: Components returned from maya.cmds.sets are "listed" as if + # being assigned to the transform like: pCube1.f[0] as opposed + # to pCubeShape1.f[0] so we correct that here too. + + # Build a mapping from parent to shapes to include in lookup. + transforms = {shape.rsplit("|", 1)[0]: shape for shape in shapes} + lookup = set(shapes + transforms.keys()) + + component_assignments = defaultdict(list) + for shading_group in assignments.keys(): + members = cmds.ls(cmds.sets(shading_group, query=True), long=True) + for member in members: + + node = member.split(".", 1)[0] + if node not in lookup: + continue + + # Component + if "." in member: + component = member.split(".", 1)[1] + + # Fix transform to shape as shaders are assigned to shapes + if node in transforms: + shape = transforms[node] + member = "{0}.{1}".format(shape, component) + + component_assignments[shading_group].append(member) + assignments = component_assignments + return dict(assignments) From 4b2e9de4c43940695ac4d024117fab0a0eed9f7a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 09:41:11 +0100 Subject: [PATCH 1175/1249] Move `component` variable more local to where it's used. --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7fa1a3a16d..8395d5fe43 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -585,11 +585,11 @@ def get_shader_assignments_from_shapes(shapes, components=True): # Component if "." in member: - component = member.split(".", 1)[1] # Fix transform to shape as shaders are assigned to shapes if node in transforms: shape = transforms[node] + component = member.split(".", 1)[1] member = "{0}.{1}".format(shape, component) component_assignments[shading_group].append(member) From b9975d38f28751921f4e0df99157b0d92904179a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 6 Nov 2018 10:01:35 +0100 Subject: [PATCH 1176/1249] Fix PLN-194: Add Validator for case where FBX Export fails to export skinning. This captures the failure case of FBX when mesh is missing in deformer set for a skinCluster, thus the skinning is not exported along with the FBX export. --- .../validate_skinCluster_deformer_set.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py diff --git a/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py b/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py new file mode 100644 index 0000000000..61ccb8f29a --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py @@ -0,0 +1,72 @@ +from maya import cmds + +import pyblish.api +import colorbleed.api +import colorbleed.maya.action + + +class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): + """Validate skinClusters on meshes have valid member relationships. + + In rare cases it can happen that a mesh has a skinCluster in its history + but it is *not* included in the deformer relationship history. If this is + the case then FBX will not export the skinning. + + """ + + order = colorbleed.api.ValidateContentsOrder + hosts = ['maya'] + families = ['colorbleed.fbx'] + label = "Skincluster Deformer Relationships" + actions = [colorbleed.maya.action.SelectInvalidAction] + + def process(self, instance): + """Process all the transform nodes in the instance""" + invalid = self.get_invalid(instance) + + if invalid: + raise ValueError("Invalid skinCluster relationships " + "found on meshes: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + + meshes = cmds.ls(instance, type="mesh", noIntermediate=True, long=True) + invalid = list() + + for mesh in meshes: + history = cmds.listHistory(mesh) or [] + skins = cmds.ls(history, type="skinCluster") + + # Ensure at most one skinCluster + assert len(skins) <= 1, "Cannot have more than one skinCluster" + + if skins: + skin = skins[0] + + # Ensure the mesh is also in the skinCluster set + # otherwise the skin will not be exported correctly + # by the FBX Exporter. + deformer_sets = cmds.listSets(object=mesh, type=2) + for deformer_set in deformer_sets: + used_by = cmds.listConnections(deformer_set + ".usedBy", + source=True, + destination=False) + + # Ignore those that don't seem to have a usedBy connection + if not used_by: + continue + + # We have a matching deformer set relationship + if skin in set(used_by): + break + + else: + invalid.append(mesh) + cls.log.warning( + "Mesh has skinCluster in history but is not included " + "in its deformer relationship set: " + "{0} (skinCluster: {1})".format(mesh, skin) + ) + + return invalid From 46936419b6aaa0efb7f634b2683939ce1c7d388a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:42:05 +0100 Subject: [PATCH 1177/1249] Fix REN-58: VRay Scene collection fails on masterLayer --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index c97d7fc44b..2f7d4c2839 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -75,11 +75,12 @@ class CollectVRayScene(pyblish.api.ContextPlugin): render_layers = sorted(render_layers, key=sort_by_display_order) for layer in render_layers: - if layer.endswith("defaultRenderLayer"): - layer = "masterLayer" + subset = layer + if subset == "defaultRenderLayer": + subset = "masterLayer" data = { - "subset": layer, + "subset": subset, "setMembers": layer, "startFrame": start_frame, @@ -102,6 +103,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): data.update(vrscene_data) - instance = context.create_instance(layer) + instance = context.create_instance(subset) self.log.info("Created: %s" % instance.name) instance.data.update(data) From 7fcb0743517c08d09d2ed86aa3c58e2c294936b1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:42:35 +0100 Subject: [PATCH 1178/1249] Assert vraySettings node exists --- colorbleed/plugins/maya/publish/collect_vray_scene.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 2f7d4c2839..612670478e 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -37,6 +37,11 @@ class CollectVRayScene(pyblish.api.ContextPlugin): vrscene_data = host.read(vray_scene) + assert cmds.ls("vraySettings", type="VRaySettingsNode"), ( + "VRay Settings node does not exists. " + "Please ensure V-Ray is the current renderer." + ) + # Output data start_frame = int(cmds.getAttr("defaultRenderGlobals.startFrame")) end_frame = int(cmds.getAttr("defaultRenderGlobals.endFrame")) From 16c5015faa81dee63a23837c729c433eef354ecf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:46:31 +0100 Subject: [PATCH 1179/1249] Fix REN-57: Ignore VRayTranslator Settings validator when instances are off --- .../maya/publish/validate_vray_translator_settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index f28801b708..6ed1637e2d 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -32,6 +32,11 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): cls.log.info("No VRay Scene instances found, skipping..") return + # Ignore if no VRayScenes are enabled for publishing + if not any(i.data.get("publish", True) for i in vrayscene_instances): + cls.log.info("VRay Scene instances are disabled, skipping..") + return + # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From 1b35bf1c8cad8a18e3d9de40497c38ebc5adecab Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 10 Nov 2018 17:51:55 +0100 Subject: [PATCH 1180/1249] LKD-31: Improve Validate Look Sets docstring for artists for readability --- colorbleed/plugins/maya/publish/validate_look_sets.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/colorbleed/plugins/maya/publish/validate_look_sets.py index 1e7645bfaf..3f4bc99059 100644 --- a/colorbleed/plugins/maya/publish/validate_look_sets.py +++ b/colorbleed/plugins/maya/publish/validate_look_sets.py @@ -6,11 +6,11 @@ import colorbleed.api class ValidateLookSets(pyblish.api.InstancePlugin): - """Validate if any sets are missing from the instance and look data + """Validate if any sets relationships are not being collected. A shader can be assigned to a node that is missing a Colorbleed ID. Because it is missing the ID it has not been collected in the instance. - This validator ensures no relationships and thus considers it invalid + This validator ensures those relationships and thus considers it invalid if a relationship was not collected. When the relationship needs to be maintained the artist might need to @@ -25,8 +25,10 @@ class ValidateLookSets(pyblish.api.InstancePlugin): - Displacement objectSets (like V-Ray): - It is best practice to add the transform group of the shape to the - displacement objectSet. + It is best practice to add the transform of the shape to the + displacement objectSet. Any parent groups will not work as groups + do not receive a Colorbleed Id. As such the assignments need to be + made to the shapes and their transform. Example content: [asset_GRP|geometry_GRP|body_GES, From e795cd90d7ab4500be9acad30a08517d8fcf9457 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Nov 2018 23:37:13 +0100 Subject: [PATCH 1181/1249] PLN-198: Add quick icons for Loader/Manager in toolbox in Maya (draft) --- colorbleed/maya/__init__.py | 12 +++-- colorbleed/maya/customize.py | 76 +++++++++++++++++++++++++++- res/icons/colorbleed_logo_36x36.png | Bin 0 -> 20198 bytes res/icons/inventory.png | Bin 0 -> 15767 bytes res/icons/loader.png | Bin 0 -> 408 bytes 5 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 res/icons/colorbleed_logo_36x36.png create mode 100644 res/icons/inventory.png create mode 100644 res/icons/loader.png diff --git a/colorbleed/maya/__init__.py b/colorbleed/maya/__init__.py index 4e7bb677b4..6e6d0ca869 100644 --- a/colorbleed/maya/__init__.py +++ b/colorbleed/maya/__init__.py @@ -98,18 +98,24 @@ def on_init(_): utils.executeDeferred(_fn) except Exception as exc: print(exc) - + # Force load Alembic so referenced alembics # work correctly on scene open cmds.loadPlugin("AbcImport", quiet=True) cmds.loadPlugin("AbcExport", quiet=True) - + # Force load objExport plug-in (requested by artists) cmds.loadPlugin("objExport", quiet=True) - from .customize import override_component_mask_commands + from .customize import ( + override_component_mask_commands, + override_toolbox_ui + ) safe_deferred(override_component_mask_commands) + if not IS_HEADLESS: + safe_deferred(override_toolbox_ui) + def on_before_save(return_code, _): """Run validation for scene's FPS prior to saving""" diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 64f33d5aae..4de78fba43 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -3,6 +3,7 @@ import maya.cmds as mc import maya.mel as mel from functools import partial +import os import logging @@ -17,7 +18,7 @@ def override_component_mask_commands(): This implements special behavior for Maya's component mask menu items where a ctrl+click will instantly make it an isolated behavior disabling all others. - + Tested in Maya 2016 and 2018 """ @@ -64,3 +65,76 @@ def override_component_mask_commands(): original = COMPONENT_MASK_ORIGINAL[btn] new_fn = partial(on_changed_callback, original) mc.iconTextCheckBox(btn, edit=True, cc=new_fn) + + +def override_toolbox_ui(): + """Add custom buttons in Toolbox as replacement for Maya web help icon.""" + + import colorbleed + res = os.path.join(os.path.dirname(os.path.dirname(colorbleed.__file__)), + "res") + icons = os.path.join(res, "icons") + + import avalon.tools.cbsceneinventory as inventory + import avalon.tools.cbloader as loader + + # Ensure the maya web icon on toolbox exists + web_button = "ToolBox|MainToolboxLayout|mayaWebButton" + if not mc.iconTextButton(web_button, query=True, exists=True): + return + + mc.iconTextButton(web_button, edit=True, visible=False) + + # real = 32, but 36 with padding - according to toolbox mel script + icon_size = 36 + parent = web_button.rsplit("|", 1)[0] + + # Ensure the parent is a formLayout + if not mc.objectTypeUI(parent) == "formLayout": + return + + # Create our controls + controls = [] + + control = mc.iconTextButton( + "colorbleed_toolbox_loader", + annotation="Loader", + label="Loader", + image=os.path.join(icons, "loader.png"), + command=lambda: loader.show(use_context=True), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + control = mc.iconTextButton( + "colorbleed_toolbox_manager", + annotation="Inventory", + label="Inventory", + image=os.path.join(icons, "inventory.png"), + command=lambda: inventory.show(), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + control = mc.iconTextButton( + "colorbleed_toolbox", + annotation="Colorbleed", + label="Colorbleed", + image=os.path.join(icons, "colorbleed_logo_36x36.png"), + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + + # Add the buttons on the bottom and stack + # them above each other with side padding + controls.reverse() + for i, control in enumerate(controls): + previous = controls[i - 1] if i > 0 else web_button + + mc.formLayout(parent, edit=True, + attachControl=[control, "bottom", 1, previous], + attachForm=([control, "left", 1], + [control, "right", 1])) diff --git a/res/icons/colorbleed_logo_36x36.png b/res/icons/colorbleed_logo_36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..847a85c2282d4acd024bc8d719877c09660581df GIT binary patch literal 20198 zcmeHPdvH|ebwBs+d-ax(0Aa+k0f*OWclBCH7NQv00XxFf;2L)(ZC1N0*dl2aX)&@h z(>Ter)BMqNCevv%aobGWNvG3s(@duAB!RJ=I@l$lWhP?`#E6H4ct{9@q}9If_IK_+ zv|QkZHtEEF+;2F0@AsYWJLmWP&N<(``s4DoeS1GsX05jpQQ4l|Jul+AfyHdZ?^XKd zH*v8H?|x;JsHBEPpl|)vDx!|t!2<`w2YWx;=?e`udi|lkK;zipFlG~V?HC*O`i=y` zw!Xlj;Lvva-Q(}rZ9)Hb`^&ApuHNBYfy2Swua5-wzrOc?@AV_T4!?azx3z1m69xtY zVXtj$aA0V(b8Nd^w%dt&9(LMo@|5tA?e?AQ&~~tQpKVuYBw%Z8Y;*WrZ62GaqtV^! z@w9lh*_vGLR;R1k+3a#OHFvhSI$N4-xn}RS;;w7N-{1LS&$Bsq=xw(j4u^+3oz7#& zjx`=@ZVZhaa=JS@I-IU1XH$~{GaRF@4TZg9j-k;{WsT(R^aMtIBf;TtFf?RiyWYOg z(eQS=ogEcgZeN4L1xG`pIqtxsbId#JbT_)34_oB-71j(N9T||J`F+m7KwvO16duJo z_e0kW9}a~>qlZI}a9L14(lKJv+gtGcP`rbK4|P2ne&$tBkfZdWD@PB!HXLxi7#Iy5 z9q|R8c@8@(Zau>UosjV2^?YiDbFv5lgcN*kyjzc=iCEGxxxi|qJ)o&BMaL2tM_ zIOshTa1IY0>T)g*7R@YVRp+kIKxhQ80^QAB&W8;bt&%gXbI;Id*gND4?CI&oKw~iI z@AL)Q+PvT$HYTLO-bX0O-L*Wc3A=l1vmp61rPi-(wbz}4>H zD6;jnqAW%(%O~6Zz!&Zx%_|0i@(DHU9T^R9{cX4B3$Spc6;3Q(BiC`K*T-jDw~xys z;O}zYH@Xa~urjwS_+U66kAkfIqUDeFaqMtlNUglGrsP?VHZmIO4}ykN5p}*$Z^HwYKufr5skS*cTiN3>@qU;v633 z^Q5^gw??UW=)zYXwXjH$TbK=1(QBDpgV|BM^x=KeS$N@mpgaqw*st?uc}Kras|s71 z69pmaU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zev zeiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}b zE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia#U5zeveiT}bE(Ia# zU5zeveiT}bE(Ia#U5zeveiT}bE(Ia#T~)fQ%b#!thVb3yF?^u;R?V(2;Nwo4Z}*G6 zM6YiodgH4^@eHoFh>p65raw>Ai4R6shQ9IpC!Zx+fiJCg9vBnIzv9gqA(O$>Pf!y- zL0DucpEyAJiOEUgu!(|5m6ersdUNTfJ9G2Xl9aHR^nz$H8dGMAC1TL)m#_tvSTdE0 zqh(Ub6s4tff#kHcnv9qKl}g|4rHmfKItXWkP?+*dZ+IE$^*YjtqV@b#Z0G!)@$U0y zPF@@TcxoH6EWM((b^&q+GE-GqF^%POiDWV!i9{$>oQ^4I zGPFM<5OSNGB#{-sZVGxjj{aXmZa^MrdS(Z^7cSh8HqU+VhtF?~fA4p;R@Itr8{@Yp z-c8(^6e=et>+R$3ls$1}USG0MnTf5dGt#E&>YBu=`ua#iL&F{0x^>qZ*49r+M!Fa= zJwcSDJ-TxG6Dp-ffj`3rLzyIzv_wxz5$Yz9UI(xd0Oqf=?`y)c#*i221AInV4ydk`?5SI_aof6d6Q0G zmNtn$J^GdBEZ=!4ik&3ph(u#4>Fd^j=@N>5I7`ycu2SY2;!{nD^Xok)&#diucdE+v zae4e+_02iULiysQ_5O8#JJ7s2x`HZU9UGGy=MMwoKHr-mQIH5*IZy|n|7VQKO_-Rv zZaHykYG>QWzxDO4Ya^dd>FOwXYgz`CN*hQpt{_8o4W(jwn*YT-rOsX>^R)y;DoHxM zVf&9xta|RP%_S4|BMsjkwyud;iF2GCGb3EXEP1A~d4*5t0VV=aQgmVkgEp#&;0nVuyx@@yERe+(i0^lZdf znmB7O{mkF#sA4yd5k2ud88>Vr!{yV2M5XlN5*UIc=g3%HLKW+(DA~Q9OtA=Aq^VlE zhc$d4ltMTp44_!@pz}!C zz*7|977!Z)5HVvIE`ZA$;#`9ol_p0Sy%r%&XGOtSX12`yTNzFLJ;rh^ahGtQQ6`_1 ze2>KiVWX@|0qqi;-<_k({48aRxL*w}8<0>dkswyyJ}BXELWT1wyKmT%0dncX4icNh z1l4mv;NhSb4F-dW-X9?H;P5EhM44OfW*z2sA@6}fl1_u$clPiRb5RaZbUeh_fmFWf*$cOKuU7?mIP=CMmgMZo`*gn z%z&ab&y;3XH~`30G8a#om)Kc`<6XBIxo}RND1*V80w4y(!i{&C7a0%(gXl1US;K=Z zlSz;uft?iMl?Mm*$OuO!%+aM$Cn-D?7HQ_*V#U(c=u@J}Qa-(ush*`d*y;Ed341>x zs(%T2!A9Yfg_)8C4wDMW&*3YBPeGrCV0l3#>^21|5;&-0a1=!}@L=Nwm`NF=+_{?+ zzdmuzES8p-?k$)$Tp-X!wQcwTuywzkO~RZruCz=-KxN@t5cDx71C&8vVV+F}#KNp| znDgM+IOkA;cNfKJy+FdkB2El_Vn%BCuhrH1%VL!|NsE>3CujHsJq{or{sm<|LdBV3 zkby8G3B)9h>wSXxGPpc2%*nIFAY{pcx-aAm8pE7-X&hU;iA$+1KTno#Ia^_&+j>KK zfuy?Tx5uw+Iahn-qs=saMBap0p_ADlW+CS&lMe;#uu(2qk}CzfW)|cc1`>A2KOzP( zhjU#Xl0ym=PKFF3CNLnh^O+s*udQEs&1$vGi?LXgR@GKcU#WQcAL2X#6($&UBrU=O zhXU+o!-achhymuVGJCn4k`GXhgd8qh3k+dCgXP8>Lzi0$X~c*2#rW!pnabzh*|OGz z$Ads3UPq})yi2k5FTD9deDgWNQ-L>5F@z!t;a$OL24Rzd#c*XEK*@auECWYhW)dFx zs9+FzVGx`WtOuibRFEFwX8qgA&wXd(#*ODIN-eh&IIl!0ouMdRKR2u?yY=&iu`gbk z(Mc4`Wj_EB0LaP8%m9ciaxP&!31Kj~kY-tdmpl-@LS3j*R6G|~1_gvSMmn+3eLTKq z{}0@D^R+kl!4?M&*8NU1{+DC>h&=9;llb0KP?&k^PNrgzc3n2i%Iz7XNZ`|)nz0a zrv}Rc)p=g|(>E=D@U>frsE9`a$64g?a>qYfLwzu{i2MBoU(X_Vw{{fD>>$q^Z6_OfA;9Po0>qSg4-; z(6u@pu_9~WST6&UIF~pMKq~O5%vY{7z5=WKq-29oi5O>HVo4-J41a22_`Q3Lr+#Su z@>jdNp8C&i>q;k=qD&tz6XfU?0Lh~m;>3EQTug|o9H&o2H;hl;o?cZSyWUWWKO0g4 zjsmbH{gJCatWMskcIE-^yb^7bE z1RfoHCKNT{$M5~5xQhuANok2Jc+a^L7j-|ocyqIG?uQ4PrjPGyO0_Nv*CS(smlklAd0 zd_ZYDiTORnWHiyGd+E}TuTE|&x%}Sq_S>g=?8z%z>-BSG6~+wle}AQeaME%~b79`j z7?Ts}y2*=~C(m9sw4J0?o_BY&Zo1_DblpremZYVnB^k*73Xp^cVlu_?70HOdYz3W; zt}h9uiRroe^z?XR$-PUB)}xD#xMM&0^8WLu_Y9x}_6acISpwiM(yaJ_VFpmVJOe!GwjW({4PgK(Y{jzYN cNRe@iH~yn<<^DH#@}6h*_I&s0&wu5A0OFGkkpKVy literal 0 HcmV?d00001 diff --git a/res/icons/inventory.png b/res/icons/inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..34f6488233b392963ce954263e0218f0e0079885 GIT binary patch literal 15767 zcmeI3XH*m07RSeRuWvnlV#i)$>?KSlB&3)`fqQPFoMKynDdaou%4&a9PW=bZoE`<<^W}_;jA-RkvRX+5L43l)G*RG} z$9(?#7x>mJ8g&@_l(2-w*%0J^6_`&kWM*a+1d*?xVq)#F+O`TkZSunjT1WahOctO< z5M>L81=lB%cAk!mr_3t;o1J_4Jc>~9J4v)cttFUDpu*CuWOQ0Yj6N+};d_I%N z_2l)-WYKC}sm!)KdVnM9z%3}|Cqzvq)SIWx9!0tv>DAK~lWHN+DAGnJS@k54*YzGK zBpI|WueW~?C?uIQ+r5SYWA^(%-trTAmyadMYIK4o^eAa0O(2gAFu=S5uq4oS+Lk~U zkwkf|xsjdV~&xG^=kZq7NCJJd=5MXOb`vr=IC<3!8*rUKC?hd}%wMb*$fSDkS0^{fNl}1AEWwm$kzw4kDle6n6v2TanNTQ~ zglYoC7#0!`Bn}M>kxC?ifdN8U5Eh&t9&#-dHr#Bp<7PdX*K!K9oRsP$asnp>LVqD4 zFo?yV`Iv+h$Z?%mXuxzNNw``LxfcqpA+1z0NrYNylXJmXaI1}ECa;R0KYLv(ERU*l z`YWiyZ6d^g8-W=FLW$V!p~OkduU1tV9dByW0d3D08cce>zr$+}+V# zq}^REJXyjho1M0%LKRG-n5`t5>|L-lzrslUAvqU6u<8}Kp4p_-HXP7rpg?8rCg*xE ziE;&Ap#RxKDCAyrj+6w_tOmQbOn_kmOcW#XSBRtvOw^RwzJ>Wsb*hREVxtZA6x>P% z#)Ac?&OIg~^LGtia4>T$NO_GqQD%Zpu{F+*TF|ZN@cIPY98VHzG~Y45z|%#EGDByi z3XWWEX~6-_pB;rK0JF9g!B!Fn6C;MEjepV|PZxK)+|fu4ZYMoRW0*oL%_r>&lB@KH zp`{7Rkm@Byfze`OKuXUT;5;XAJN`#eU@a7f&?^kI)r8yClnIX~QHwcViMk6RrR!W! z1k*;^3YrDFuSC5SLte~@!0h2*BH_*@1NOB-WuE$2X7e&8IrUPJ5IiWxeCN5s)t;_# zzjuYJJzd?N9n6%S>6^2m9>tIsb8O|phz74}q&h$%@s}u3&tgxHNXkJPV>J{w&1}pj z;tw)8eLO3?I>r^{2vy`c<}MWIrOLUEa!bK08+cnpU9XD;mzZnk`j-uNkN)Mra5ydr zOoP?taIqqS1spDz2CL2CVnqZCI9xCdR-41ciU<~PxL_KrHiwH95iH=XafWrmTV6{11tcYL%hYO~`YIC?)5y1iu7fgfI=5Vnhf(0Bdm6%j1paKSWKZ4MVJB3QuTf@!eY94=Nwuz4`lKg(ApbNMzfXknGGF-?zm5I5#Ed(f*t(i?SCs{q^UAs2uX`MQC_nt6Q{a(XwUBz`($uprGL3V2wr-5)u*`8rrH=tFW-J@bK`~ty{Ng)240P zwpy(=A|j$)yLOS0k?q^JkBW+lj*gCriRsXxL&uICJ9X+58yg!J7uUIS=Pq5kbnV); zTeoiAyLa!=qesu4J#ie@>2!L%o*)R4Bn<{be0+RDLIOomy?XUZOiVNyjV6=HY&O$0 zZLwH-_wH@AT5UF)-EL1xN=i;n2G>m;4o7NgYFb)apFVy1_U+rRU%&qS`wti}AU!>O z;J|@{1`QfKc<_)RLxv6=I&9dm;lqcI7%^hx$dRK)jT$|A^q4VY#*Q63Zrr%>&pEz;i~6(;NioEj~+dG{P^*cCr_R}efsR#v**vBzj*QD<;$0^ zUcGw#`t_SPZ{EIr`|jPl_wV0-`0(N5$B&;refs?Q^OrAQzJC3hot^#d+qduEzyJ90 zBPS>4=g*&DVxN2$O@npgV~>i627g@-qI$|71}cKWHGwgX>$Q_8pa-@C(w8#0ozO4Km(Rv{`wSVv*z;Iu4Xz1|YEjqc8x7j=%=(2xWsx3;?~ssXP~h`ja$C z=@Cm-M2x?eD7Gu}JQpT=9;XW|XZVb_F7VB0dreHa17T-j2~Otop6E-!x7_P2(AoY1 zMjMt&F`&|e$5fIi!Mh6x`5u-PoH!x!@!%rl_2`=!MbBPf#Q^?0*LcyCN zkD4i Date: Fri, 23 Nov 2018 11:27:48 +0100 Subject: [PATCH 1182/1249] Avoid requerying renderer, since it's already collected + ls() nodes once since amount of nodes will remain static --- .../plugins/maya/publish/collect_render_layer_aovs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index f39330e10f..6f8c71a33f 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -37,8 +37,7 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): return # Get renderer - renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer") - + renderer = instance.data["renderer"] self.log.info("Renderer found: {}".format(renderer)) rp_node_types = {"vray": ["VRayRenderElement", "VRayRenderElementSet"], @@ -53,10 +52,10 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): # Collect all AOVs / Render Elements layer = instance.data["setMembers"] - with lib.renderlayer(layer): + node_type = rp_node_types[renderer] + render_elements = cmds.ls(type=node_type) - node_type = rp_node_types[renderer] - render_elements = cmds.ls(type=node_type) + with lib.renderlayer(layer): # Check if AOVs / Render Elements are enabled for element in render_elements: From b51cad8d88873205c151c19162e58e8aa4e0c38d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 13:32:52 +0100 Subject: [PATCH 1183/1249] Fix ValidateDeadlineConnection running per instance --- .../plugins/maya/publish/validate_deadline_connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index a893f39572..ef365b44bd 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -12,7 +12,7 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] - def process(self, instance): + def process(self, context): AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") @@ -24,4 +24,4 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): assert response.ok, "Response must be ok" assert response.text.startswith("Deadline Web Service "), ( "Web service did not respond with 'Deadline Web Service'" - ) \ No newline at end of file + ) From ea5043ada24d57eadedd77746d571bdde3e3ac77 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 13:58:30 +0100 Subject: [PATCH 1184/1249] ValidateSceneSetWorkspace is not families specific, remove families filter --- colorbleed/plugins/maya/publish/validate_scene_set_workspace.py | 1 - 1 file changed, 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py index 3f4f631897..ed37457164 100644 --- a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py +++ b/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py @@ -30,7 +30,6 @@ class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): order = colorbleed.api.ValidatePipelineOrder hosts = ['maya'] - families = ['colorbleed.model'] category = 'scene' version = (0, 1, 0) label = 'Maya Workspace Set' From 029dfc43c4e6c728d7dabcb8c62b4dcd761599d5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:00:07 +0100 Subject: [PATCH 1185/1249] Simplify code --- colorbleed/plugins/global/publish/collect_machine_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/global/publish/collect_machine_name.py b/colorbleed/plugins/global/publish/collect_machine_name.py index e1c4a88669..02360cff04 100644 --- a/colorbleed/plugins/global/publish/collect_machine_name.py +++ b/colorbleed/plugins/global/publish/collect_machine_name.py @@ -11,4 +11,4 @@ class CollectMachineName(pyblish.api.ContextPlugin): machine_name = socket.gethostname() self.log.info("Machine name: %s" % machine_name) - context.data.update({"machine": machine_name}) + context.data["machine"] = machine_name From 50678ed8386b2c99cc32828889214fdd8f9be7a5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:04:21 +0100 Subject: [PATCH 1186/1249] Use consistent workaround for ContextPlugin bug: pyblish-base#250 --- colorbleed/plugin.py | 35 +++++++++++++++++++ .../global/publish/collect_deadline_user.py | 5 +++ .../publish/validate_deadline_connection.py | 5 +++ .../validate_vray_translator_settings.py | 20 +++-------- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/colorbleed/plugin.py b/colorbleed/plugin.py index 0ba1fe5ded..e7c4c374d6 100644 --- a/colorbleed/plugin.py +++ b/colorbleed/plugin.py @@ -32,3 +32,38 @@ class Extractor(pyblish.api.InstancePlugin): instance.data['stagingDir'] = staging_dir return staging_dir + + +def contextplugin_should_run(plugin, context): + """Return whether the ContextPlugin should run on the given context. + + This is a helper function to work around a bug pyblish-base#250 + Whenever a ContextPlugin sets specific families it will still trigger even + when no instances are present that have those families. + + This actually checks it correctly and returns whether it should run. + + """ + required = set(plugin.families) + + # When no filter always run + if "*" in required: + return True + + for instance in context: + + # Ignore inactive instances + if (not instance.data.get("publish", True) or + not instance.data.get("active", True)): + continue + + families = instance.data.get("families", []) + if any(f in required for f in families): + return True + + family = instance.data.get("family") + if family and family in required: + return True + + return False + diff --git a/colorbleed/plugins/global/publish/collect_deadline_user.py b/colorbleed/plugins/global/publish/collect_deadline_user.py index 4f7af94419..f78d0b1c9d 100644 --- a/colorbleed/plugins/global/publish/collect_deadline_user.py +++ b/colorbleed/plugins/global/publish/collect_deadline_user.py @@ -2,6 +2,7 @@ import os import subprocess import pyblish.api +from colorbleed.plugin import contextplugin_should_run CREATE_NO_WINDOW = 0x08000000 @@ -40,6 +41,10 @@ class CollectDeadlineUser(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + user = deadline_command("GetCurrentUserName").strip() if not user: diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/colorbleed/plugins/maya/publish/validate_deadline_connection.py index ef365b44bd..0a962e7afc 100644 --- a/colorbleed/plugins/maya/publish/validate_deadline_connection.py +++ b/colorbleed/plugins/maya/publish/validate_deadline_connection.py @@ -2,6 +2,7 @@ import pyblish.api import avalon.api as api from avalon.vendor import requests +from colorbleed.plugin import contextplugin_should_run class ValidateDeadlineConnection(pyblish.api.ContextPlugin): @@ -14,6 +15,10 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): def process(self, context): + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + AVALON_DEADLINE = api.Session.get("AVALON_DEADLINE", "http://localhost:8082") diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py index 6ed1637e2d..0de41c9563 100644 --- a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py +++ b/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py @@ -1,5 +1,6 @@ import pyblish.api import colorbleed.api +from colorbleed.plugin import contextplugin_should_run from maya import cmds @@ -13,6 +14,10 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): def process(self, context): + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + invalid = self.get_invalid(context) if invalid: raise RuntimeError("Found invalid VRay Translator settings!") @@ -22,21 +27,6 @@ class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): invalid = False - # Check if there are any vray scene instances - # The reason to not use host.lsattr() as used in collect_vray_scene - # is because that information is already available in the context - vrayscene_instances = [i for i in context[:] if i.data["family"] - in cls.families] - - if not vrayscene_instances: - cls.log.info("No VRay Scene instances found, skipping..") - return - - # Ignore if no VRayScenes are enabled for publishing - if not any(i.data.get("publish", True) for i in vrayscene_instances): - cls.log.info("VRay Scene instances are disabled, skipping..") - return - # Get vraySettings node vray_settings = cmds.ls(type="VRaySettingsNode") assert vray_settings, "Please ensure a VRay Settings Node is present" From b333aa38cffd1d1f5acd26e8dd17c79d4b8ecee6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:15:52 +0100 Subject: [PATCH 1187/1249] Only try to collect context startFrame and endFrame when not on instance --- .../plugins/global/publish/submit_publish_job.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/colorbleed/plugins/global/publish/submit_publish_job.py index ed00cb9343..1806668433 100644 --- a/colorbleed/plugins/global/publish/submit_publish_job.py +++ b/colorbleed/plugins/global/publish/submit_publish_job.py @@ -147,11 +147,14 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): subset=subset ) - # Add in start/end frame + # Get start/end frame from instance, if not available get from context context = instance.context - start = instance.data.get("startFrame", context.data["startFrame"]) - end = instance.data.get("endFrame", context.data["endFrame"]) - resources = [] + start = instance.data.get("startFrame") + if start is None: + start = context.data["startFrame"] + end = instance.data.get("endFrame") + if end is None: + end = context.data["endFrame"] # Add in regex for sequence filename # This assumes the output files start with subset name and ends with @@ -187,6 +190,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): if not os.path.isdir(output_dir): os.makedirs(output_dir) + resources = [] if data.get("extendFrames", False): family = "colorbleed.imagesequence" From 52df84964926e1d479be72e9c6212d6cf354788d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:16:52 +0100 Subject: [PATCH 1188/1249] Remove startFrame+endFrame on context, they are per renderlayer instance --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 03a879d17b..a816dc12c3 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -26,12 +26,6 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): "renderGlobals node") return - # Get start and end frame - start_frame = self.get_render_attribute("startFrame") - end_frame = self.get_render_attribute("endFrame") - context.data["startFrame"] = start_frame - context.data["endFrame"] = end_frame - # Get all valid renderlayers # This is how Maya populates the renderlayer display rlm_attribute = "renderLayerManager.renderLayerId" From 51c1ed5dcd93c4a6f7ac7ca9c3a881a3ed467fff Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:22:24 +0100 Subject: [PATCH 1189/1249] Provide consistent log message for collecting renderlayers and vrayscene --- colorbleed/plugins/maya/publish/collect_renderlayers.py | 4 ++-- colorbleed/plugins/maya/publish/collect_vray_scene.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index a816dc12c3..9e6e97e725 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -22,8 +22,8 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): try: render_globals = cmds.ls("renderglobalsDefault")[0] except IndexError: - self.log.error("Cannot collect renderlayers without " - "renderGlobals node") + self.log.info("Skipping renderlayer collection, no " + "renderGlobalsDefault found..") return # Get all valid renderlayers diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 612670478e..911d49907c 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -29,7 +29,8 @@ class CollectVRayScene(pyblish.api.ContextPlugin): # Get VRay Scene instance vray_scenes = host.lsattr("family", "colorbleed.vrayscene") if not vray_scenes: - self.log.info("No instance found of family: `colorbleed.vrayscene`") + self.log.info("Skipping vrayScene collection, no " + "colorbleed.vrayscene instance found..") return assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" From 1ef9db118bfa06404077e75f53b9d0091128326f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:28:42 +0100 Subject: [PATCH 1190/1249] Skip storing startFrame + endFrame on context, as it is per instance data --- .../plugins/maya/publish/collect_vray_scene.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/colorbleed/plugins/maya/publish/collect_vray_scene.py index 911d49907c..e4a9c42364 100644 --- a/colorbleed/plugins/maya/publish/collect_vray_scene.py +++ b/colorbleed/plugins/maya/publish/collect_vray_scene.py @@ -52,13 +52,6 @@ class CollectVRayScene(pyblish.api.ContextPlugin): vrscene = ("vrayscene", "", "_", "") vrscene_output = os.path.join(work_dir, *vrscene) - vrscene_data["startFrame"] = start_frame - vrscene_data["endFrame"] = end_frame - vrscene_data["vrsceneOutput"] = vrscene_output - - context.data["startFrame"] = start_frame - context.data["endFrame"] = end_frame - # Check and create render output template for render job # outputDir is required for submit_publish_job if not vrscene_data.get("suspendRenderJob", False): @@ -104,7 +97,10 @@ class CollectVRayScene(pyblish.api.ContextPlugin): # Add source to allow tracing back to the scene from # which was submitted originally - "source": file_name + "source": file_name, + + # Store VRay Scene additional data + "vrsceneOutput": vrscene_output } data.update(vrscene_data) From 241069d24c97eed2d11b1f4efbb4a18667575f98 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 14:33:41 +0100 Subject: [PATCH 1191/1249] Submission cosmetics (improve labeling of submitted tasks) --- colorbleed/plugins/maya/publish/submit_vray_deadline.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/colorbleed/plugins/maya/publish/submit_vray_deadline.py index 16fa6d02a2..7036a9f1eb 100644 --- a/colorbleed/plugins/maya/publish/submit_vray_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_vray_deadline.py @@ -41,7 +41,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): filename = os.path.basename(filepath) task_name = "{} - {}".format(filename, instance.name) - batch_name = "VRay Scene Export - {}".format(filename) + batch_name = "{} - (vrscene)".format(filename) # Get the output template for vrscenes vrscene_output = instance.data["vrsceneOutput"] @@ -63,7 +63,9 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): "BatchName": batch_name, # Job name, as seen in Monitor - "Name": "{} [{}-{}]".format(task_name, start_frame, end_frame), + "Name": "Export {} [{}-{}]".format(task_name, + start_frame, + end_frame), # Arbitrary username, for visualisation in Monitor "UserName": deadline_user, From b4297b6a41e5b8de89e5b96c943fffb39c9d70cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 15:54:49 +0100 Subject: [PATCH 1192/1249] Workaround contextplugin running when instance not present bug --- .../publish/validate_current_renderlayer_renderable.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py index 04ed259d48..8f09941f06 100644 --- a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py +++ b/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py @@ -1,6 +1,7 @@ import pyblish.api from maya import cmds +from colorbleed.plugin import contextplugin_should_run class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): @@ -20,7 +21,12 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] - def process(self, instance): + def process(self, context): + + # Workaround bug pyblish-base#250 + if not contextplugin_should_run(self, context): + return + layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True) cameras = cmds.ls(type="camera", long=True) renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable")) From f7c0c1d88553466ac29872c5d73d36824fd8bad4 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 17:36:26 +0100 Subject: [PATCH 1193/1249] Fix render single camera validation (actually process something) - Also improve logged messages --- .../publish/validate_render_single_camera.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/colorbleed/plugins/maya/publish/validate_render_single_camera.py index 0a2eb997c5..447f52beec 100644 --- a/colorbleed/plugins/maya/publish/validate_render_single_camera.py +++ b/colorbleed/plugins/maya/publish/validate_render_single_camera.py @@ -17,17 +17,28 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder label = "Render Single Camera" hosts = ['maya'] - families = ['colorbleed.renderlayer', + families = ["colorbleed.renderlayer", "colorbleed.vrayscene"] + actions = [colorbleed.maya.action.SelectInvalidAction] def process(self, instance): """Process all the cameras in the instance""" + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("Invalid cameras for render.") @classmethod def get_invalid(cls, instance): - cameras = instance.data.get("cameras", []) - if len(cameras) != 1: - cls.log.error("Multiple renderable cameras" "found: %s " % - instance.data["setMembers"]) + cameras = instance.data.get("cameras", []) + + if len(cameras) > 1: + cls.log.error("Multiple renderable cameras found for %s: %s " % + (instance.data["setMembers"], cameras)) + return [instance.data["setMembers"]] + cameras + + elif len(cameras) < 1: + cls.log.error("No renderable cameras found for %s " % + instance.data["setMembers"]) return [instance.data["setMembers"]] + From 0fab828311389bc5a58c435b3681b7b38a2500af Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 17:51:20 +0100 Subject: [PATCH 1194/1249] Fix REN-59: Less renderlayer switching on render submission from maya - This is a good optimization for render scenes with multiple layers that are slow on switching renderlayers. - This will be slower for render setup because it's not optimized for render setup, see colorbleed.maya.lib.get_attr_in_layer() --- colorbleed/maya/lib.py | 54 +++++++++++++++++++ .../maya/publish/collect_render_layer_aovs.py | 24 ++++----- .../maya/publish/collect_renderable_camera.py | 9 ++-- .../maya/publish/collect_renderlayers.py | 47 ++++++++-------- .../validate_render_no_default_cameras.py | 7 ++- .../maya/publish/validate_rendersettings.py | 50 ++++++++--------- 6 files changed, 121 insertions(+), 70 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 8395d5fe43..7b5f8fab2d 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2087,3 +2087,57 @@ def bake_to_world_space(nodes, shape=shape) return world_space_nodes + + +def get_attr_in_layer(attr, layer): + """Return attribute value in specified renderlayer. + + Same as cmds.getAttr but this gets the attribute's value in a + given render layer without having to switch to it. + + Note: This is much faster for Maya's renderLayer system, yet the code + does no optimized query for render setup. + + Args: + attr (str): attribute name, ex. "node.attribute" + layer (str): layer name + + Returns: + The return value from `maya.cmds.getAttr` + + """ + if cmds.mayaHasRenderSetup(): + log.debug("lib.get_attr_in_layer is not optimized for render setup") + with renderlayer(layer): + return cmds.getAttr(attr) + + connections = cmds.listConnections(attr, + plugs=True, + source=False, + destination=True, + type="renderLayer") or [] + + connections = filter(lambda x: x.endswith(".plug"), connections) + if not connections: + return cmds.getAttr(attr) + + for connection in connections: + if connection.startswith(layer): + attr_split = connection.split(".") + if attr_split[0] == layer: + attr = ".".join(attr_split[0:-1]) + return cmds.getAttr("%s.value" % attr) + + else: + # When connections are present, but none + # to the specific renderlayer than the layer + # should have the "defaultRenderLayer"'s value + layer = "defaultRenderLayer" + for connection in connections: + if connection.startswith(layer): + attr_split = connection.split(".") + if attr_split[0] == "defaultRenderLayer": + attr = ".".join(attr_split[0:-1]) + return cmds.getAttr("%s.value" % attr) + + return cmds.getAttr(attr) diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py index 6f8c71a33f..e2153e0ee0 100644 --- a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py +++ b/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py @@ -6,10 +6,9 @@ import colorbleed.maya.lib as lib class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): - """Validate all render layer's AOVs / Render Elements are registered in - the database + """Collect all render layer's AOVs / Render Elements that will render. - This validator is important to be able to Extend Frames + This collector is important to be able to Extend Frames. Technical information: Each renderer uses different logic to work with render passes. @@ -55,18 +54,17 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): node_type = rp_node_types[renderer] render_elements = cmds.ls(type=node_type) - with lib.renderlayer(layer): + # Check if AOVs / Render Elements are enabled + for element in render_elements: + enabled = lib.get_attr_in_layer("{}.enabled".format(element), + layer=layer) + if not enabled: + continue - # Check if AOVs / Render Elements are enabled - for element in render_elements: - enabled = cmds.getAttr("{}.enabled".format(element)) - if not enabled: - continue + pass_name = self.get_pass_name(renderer, element) + render_pass = "%s.%s" % (instance.data["subset"], pass_name) - pass_name = self.get_pass_name(renderer, element) - render_pass = "%s.%s" % (instance.data["subset"], pass_name) - - result.append(render_pass) + result.append(render_pass) self.log.info("Found {} render elements / AOVs for " "'{}'".format(len(result), instance.data["subset"])) diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/colorbleed/plugins/maya/publish/collect_renderable_camera.py index dd9ec433bb..8cf33bd73a 100644 --- a/colorbleed/plugins/maya/publish/collect_renderable_camera.py +++ b/colorbleed/plugins/maya/publish/collect_renderable_camera.py @@ -18,10 +18,9 @@ class CollectRenderableCamera(pyblish.api.InstancePlugin): layer = instance.data["setMembers"] cameras = cmds.ls(type="camera", long=True) - with lib.renderlayer(layer): - renderable = [c for c in cameras if - cmds.getAttr("%s.renderable" % c)] + renderable = [c for c in cameras if + lib.get_attr_in_layer("%s.renderable" % c, layer=layer)] - self.log.info("Found cameras %s" % len(renderable)) + self.log.info("Found cameras %s: %s" % (len(renderable), renderable)) - instance.data.update({"cameras": renderable}) + instance.data["cameras"] = renderable diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/colorbleed/plugins/maya/publish/collect_renderlayers.py index 9e6e97e725..369b21de02 100644 --- a/colorbleed/plugins/maya/publish/collect_renderlayers.py +++ b/colorbleed/plugins/maya/publish/collect_renderlayers.py @@ -53,30 +53,34 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): if layer.endswith("defaultRenderLayer"): layername = "masterLayer" else: + # Remove Maya render setup prefix `rs_` layername = layer.split("rs_", 1)[-1] # Get layer specific settings, might be overrides - with lib.renderlayer(layer): - data = { - "subset": layername, - "setMembers": layer, - "publish": True, - "startFrame": self.get_render_attribute("startFrame"), - "endFrame": self.get_render_attribute("endFrame"), - "byFrameStep": self.get_render_attribute("byFrameStep"), - "renderer": self.get_render_attribute("currentRenderer"), + data = { + "subset": layername, + "setMembers": layer, + "publish": True, + "startFrame": self.get_render_attribute("startFrame", + layer=layer), + "endFrame": self.get_render_attribute("endFrame", + layer=layer), + "byFrameStep": self.get_render_attribute("byFrameStep", + layer=layer), + "renderer": self.get_render_attribute("currentRenderer", + layer=layer), - # instance subset - "family": "Render Layers", - "families": ["colorbleed.renderlayer"], - "asset": asset, - "time": api.time(), - "author": context.data["user"], + # instance subset + "family": "Render Layers", + "families": ["colorbleed.renderlayer"], + "asset": asset, + "time": api.time(), + "author": context.data["user"], - # Add source to allow tracing back to the scene from - # which was submitted originally - "source": filepath - } + # Add source to allow tracing back to the scene from + # which was submitted originally + "source": filepath + } # Apply each user defined attribute as data for attr in cmds.listAttr(layer, userDefined=True) or list(): @@ -106,8 +110,9 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): instance.data["label"] = label instance.data.update(data) - def get_render_attribute(self, attr): - return cmds.getAttr("defaultRenderGlobals.{}".format(attr)) + def get_render_attribute(self, attr, layer): + return lib.get_attr_in_layer("defaultRenderGlobals.{}".format(attr), + layer=layer) def parse_options(self, render_globals): """Get all overrides with a value, skip those without diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py index 588c66eb39..b7c1cbbeb8 100644 --- a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -26,10 +26,9 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): cmds.camera(cam, query=True, startupCamera=True)] invalid = [] - with lib.renderlayer(layer): - for cam in defaults: - if cmds.getAttr(cam + ".renderable"): - invalid.append(cam) + for cam in defaults: + if lib.get_attr_in_layer(cam + ".renderable", layer=layer): + invalid.append(cam) return invalid diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/colorbleed/plugins/maya/publish/validate_rendersettings.py index 17d872f7d3..8f8c86e51e 100644 --- a/colorbleed/plugins/maya/publish/validate_rendersettings.py +++ b/colorbleed/plugins/maya/publish/validate_rendersettings.py @@ -50,37 +50,33 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): invalid = False renderer = instance.data['renderer'] - layer_node = instance.data['setMembers'] + layer = instance.data['setMembers'] - # Collect the filename prefix in the render layer - with lib.renderlayer(layer_node): + # Get the node attributes for current renderer + attrs = lib.RENDER_ATTRS.get(renderer, lib.RENDER_ATTRS['default']) + prefix = lib.get_attr_in_layer("{node}.{prefix}".format(**attrs), + layer=layer) + padding = lib.get_attr_in_layer("{node}.{padding}".format(**attrs), + layer=layer) - render_attrs = lib.RENDER_ATTRS.get(renderer, - lib.RENDER_ATTRS['default']) - node = render_attrs["node"] - padding_attr = render_attrs["padding"] - prefix_attr = render_attrs["prefix"] + anim_override = lib.get_attr_in_layer("defaultRenderGlobals.animation", + layer=layer) + if not anim_override: + invalid = True + cls.log.error("Animation needs to be enabled. Use the same " + "frame for start and end to render single frame") - prefix = cmds.getAttr("{}.{}".format(node, prefix_attr)) - padding = cmds.getAttr("{}.{}".format(node, padding_attr)) + fname_prefix = cls.RENDERER_PREFIX.get(renderer, + cls.DEFAULT_PREFIX) + if prefix != fname_prefix: + invalid = True + cls.log.error("Wrong file name prefix: %s (expected: %s)" + % (prefix, fname_prefix)) - anim_override = cmds.getAttr("defaultRenderGlobals.animation") - if not anim_override: - invalid = True - cls.log.error("Animation needs to be enabled. Use the same " - "frame for start and end to render single frame") - - fname_prefix = cls.RENDERER_PREFIX.get(renderer, - cls.DEFAULT_PREFIX) - if prefix != fname_prefix: - invalid = True - cls.log.error("Wrong file name prefix, expecting %s" - % fname_prefix) - - if padding != cls.DEFAULT_PADDING: - invalid = True - cls.log.error("Expecting padding of {} ( {} )".format( - cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) + if padding != cls.DEFAULT_PADDING: + invalid = True + cls.log.error("Expecting padding of {} ( {} )".format( + cls.DEFAULT_PADDING, "0" * cls.DEFAULT_PADDING)) return invalid From 40bf199f6e87dc7103acff078a2480645264ede3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 Nov 2018 18:01:05 +0100 Subject: [PATCH 1195/1249] Use collected cameras in validation, instead of collecting again. --- .../publish/validate_render_no_default_cameras.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py index b7c1cbbeb8..fbfb50bd4e 100644 --- a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py @@ -3,7 +3,6 @@ from maya import cmds import pyblish.api import colorbleed.api import colorbleed.maya.action -import colorbleed.maya.lib as lib class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): @@ -18,19 +17,14 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): @staticmethod def get_invalid(instance): - layer = instance.data["setMembers"] + renderable = set(instance.data["cameras"]) # Collect default cameras cameras = cmds.ls(type='camera', long=True) - defaults = [cam for cam in cameras if - cmds.camera(cam, query=True, startupCamera=True)] + defaults = set(cam for cam in cameras if + cmds.camera(cam, query=True, startupCamera=True)) - invalid = [] - for cam in defaults: - if lib.get_attr_in_layer(cam + ".renderable", layer=layer): - invalid.append(cam) - - return invalid + return [cam for cam in renderable if cam in defaults] def process(self, instance): """Process all the cameras in the instance""" From 5e653bbb5511aca8300ca52cced971b14bb91f8e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 5 Dec 2018 08:39:05 +0100 Subject: [PATCH 1196/1249] Fix custom icons in Maya background color and positioning --- colorbleed/maya/customize.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 4de78fba43..58f2f6d8a5 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -94,6 +94,7 @@ def override_toolbox_ui(): return # Create our controls + background_color = (0.267, 0.267, 0.267) controls = [] control = mc.iconTextButton( @@ -102,6 +103,7 @@ def override_toolbox_ui(): label="Loader", image=os.path.join(icons, "loader.png"), command=lambda: loader.show(use_context=True), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -113,6 +115,7 @@ def override_toolbox_ui(): label="Inventory", image=os.path.join(icons, "inventory.png"), command=lambda: inventory.show(), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -123,6 +126,7 @@ def override_toolbox_ui(): annotation="Colorbleed", label="Colorbleed", image=os.path.join(icons, "colorbleed_logo_36x36.png"), + bgc=background_color, width=icon_size, height=icon_size, parent=parent) @@ -135,6 +139,6 @@ def override_toolbox_ui(): previous = controls[i - 1] if i > 0 else web_button mc.formLayout(parent, edit=True, - attachControl=[control, "bottom", 1, previous], + attachControl=[control, "bottom", 0, previous], attachForm=([control, "left", 1], [control, "right", 1])) From 8e6724805492e88ccbeac19e425a8f368b01d08c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:19:55 +0100 Subject: [PATCH 1197/1249] Fix Create VDB cache correctly assigning currently selected nodes --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index aadd222d41..30f467b3b0 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -28,6 +28,6 @@ class CreateVDBCache(houdini.Creator): if self.nodes: node = self.nodes[0] - parms.update({"sop_path": node.path()}) + parms.update({"soppath": node.path()}) instance.setParms(parms) From d09f088f14202b73f50c049930c69cfd4c958413 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:20:18 +0100 Subject: [PATCH 1198/1249] Fix filename typo --- .../{valiate_vdb_input_node.py => validate_vdb_input_node.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename colorbleed/plugins/houdini/publish/{valiate_vdb_input_node.py => validate_vdb_input_node.py} (100%) diff --git a/colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py b/colorbleed/plugins/houdini/publish/validate_vdb_input_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/valiate_vdb_input_node.py rename to colorbleed/plugins/houdini/publish/validate_vdb_input_node.py From 488703bfc73035c6f85610302e72e3c966745736 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:26:45 +0100 Subject: [PATCH 1199/1249] Remove redundant import --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 6d35e3bd02..63596b1cfc 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from avalon import houdini From e22bd1f26b2c6d698cbb7178d92fc0c057bbfd5c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:34:12 +0100 Subject: [PATCH 1200/1249] Clarify error messages more for the artists. --- .../houdini/publish/validate_output_node.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index d3393e1c33..bb9eec508e 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -2,10 +2,15 @@ import pyblish.api class ValidateOutputNode(pyblish.api.InstancePlugin): - """Validate if output node: - - exists - - is of type 'output' - - has an input""" + """Validate the instance SOP Output Node. + + This will ensure: + - The SOP Path is set. + - The SOP Path refers to an existing object. + - The SOP Path node is of type 'output' or 'camera' + - The SOP Path node has at least one input connection (has an input) + + """ order = pyblish.api.ValidatorOrder families = ["*"] @@ -16,7 +21,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Output node(s) `%s` are incorrect" % invalid) + raise RuntimeError("Output node(s) `%s` are incorrect. " + "See plug-in log for details." % invalid) @classmethod def get_invalid(cls, instance): @@ -25,16 +31,17 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): if output_node is None: node = instance[0] - cls.log.error("Output node at '%s' does not exist, see source" % - node.path()) + cls.log.error("SOP Output node in '%s' does not exist. " + "Ensure a valid SOP output path is set." + % node.path()) return node.path() # Check if type is correct type_name = output_node.type().name() if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is not an accepted type `output` " - "or `camera`" % + cls.log.error("Output node `%s` is not an accepted type." + "Expected types: `output` or `camera`" % output_node.path()) return [output_node.path()] From 7a39b50469c4bf8982eb85b8d450baee0022127b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:37:25 +0100 Subject: [PATCH 1201/1249] Remove redundant instances list that was unused --- colorbleed/plugins/houdini/publish/collect_instances.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 61d4cdbe0b..2e456a8d97 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -30,8 +30,6 @@ class CollectInstances(pyblish.api.ContextPlugin): def process(self, context): - instances = [] - nodes = hou.node("/out").children() for node in nodes: @@ -68,8 +66,6 @@ class CollectInstances(pyblish.api.ContextPlugin): instance[:] = [node] instance.data.update(data) - instances.append(instance) - def sort_by_family(instance): """Sort by family""" return instance.data.get("families", instance.data.get("family")) From 8c09ada8cb725cf031eb81eaf693689a777624bb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:39:27 +0100 Subject: [PATCH 1202/1249] Explicitly also check for endFrame + PEP08 and cosmetics --- .../plugins/houdini/publish/collect_instances.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/colorbleed/plugins/houdini/publish/collect_instances.py index 2e456a8d97..5f9fc7d6c9 100644 --- a/colorbleed/plugins/houdini/publish/collect_instances.py +++ b/colorbleed/plugins/houdini/publish/collect_instances.py @@ -15,8 +15,8 @@ class CollectInstances(pyblish.api.ContextPlugin): id (str): "pyblish.avalon.instance Specific node: - The specific node is important because it dictates in which way the subset - is being exported. + The specific node is important because it dictates in which way the + subset is being exported. alembic: will export Alembic file which supports cascading attributes like 'cbId' and 'path' @@ -53,11 +53,9 @@ class CollectInstances(pyblish.api.ContextPlugin): data.update(self.get_frame_data(node)) - # Create nice name - # All nodes in the Outputs graph have the 'Valid Frame Range' - # attribute, we check here if any frames are set + # Create nice name if the instance has a frame range. label = data.get("name", node.name()) - if "startFrame" in data: + if "startFrame" in data and "endFrame" in data: frames = "[{startFrame} - {endFrame}]".format(**data) label = "{} {}".format(label, frames) From 427a5a3ba05cbab24f4a4c15d7b83bde574c4bcc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 8 Dec 2018 19:42:00 +0100 Subject: [PATCH 1203/1249] Assert node is valid, we somehow had a case where the node was `None` --- colorbleed/plugins/houdini/publish/validate_outnode_exists.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py index b9f24faa32..ff682ee820 100644 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py @@ -29,6 +29,8 @@ class ValidatOutputNodeExists(pyblish.api.InstancePlugin): result = set() node = instance[0] + assert node, "No node in instance. This is a bug." + if node.type().name() == "alembic": soppath_parm = "sop_path" else: From 077df63286bbeae3374aad8c19ee6e5e71033780 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 08:43:18 +0100 Subject: [PATCH 1204/1249] Correctly perform attribute type value conversion for render layer overrides --- colorbleed/maya/lib.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 7b5f8fab2d..dd97e46be7 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2118,15 +2118,27 @@ def get_attr_in_layer(attr, layer): type="renderLayer") or [] connections = filter(lambda x: x.endswith(".plug"), connections) - if not connections: + if not connections or layer == cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True): return cmds.getAttr(attr) - + + # Some value types perform a conversion when assigning + # TODO: See if there's a maya method to allow this conversion + # instead of computing it ourselves. + attr_type = cmds.getAttr(attr, type=True) + conversion = None + if attr_type == "time": + conversion = mel.eval('currentTimeUnitToFPS()') # returns float + for connection in connections: - if connection.startswith(layer): + if connection.startswith(layer + "."): attr_split = connection.split(".") if attr_split[0] == layer: attr = ".".join(attr_split[0:-1]) - return cmds.getAttr("%s.value" % attr) + value = cmds.getAttr("%s.value" % attr) + if conversion: + value *= conversion + return value else: # When connections are present, but none @@ -2138,6 +2150,9 @@ def get_attr_in_layer(attr, layer): attr_split = connection.split(".") if attr_split[0] == "defaultRenderLayer": attr = ".".join(attr_split[0:-1]) - return cmds.getAttr("%s.value" % attr) + value = cmds.getAttr("%s.value" % attr) + if conversion: + value *= conversion * conversion + return value return cmds.getAttr(attr) From bb39de901d3697f4f6c9041fbdf62d545c820787 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 10:36:20 +0100 Subject: [PATCH 1205/1249] Some more work-in-progress on correct type conversion for get_attr_in_layer() --- colorbleed/maya/lib.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index dd97e46be7..20df970f74 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2095,6 +2095,12 @@ def get_attr_in_layer(attr, layer): Same as cmds.getAttr but this gets the attribute's value in a given render layer without having to switch to it. + Warning for parent attribute overrides: + Attributes that have render layer overrides to their parent attribute + are not captured correctly since they do not have a direct connection. + For example, an override to sphere.rotate when querying sphere.rotateX + will not return correctly! + Note: This is much faster for Maya's renderLayer system, yet the code does no optimized query for render setup. @@ -2106,22 +2112,27 @@ def get_attr_in_layer(attr, layer): The return value from `maya.cmds.getAttr` """ + if cmds.mayaHasRenderSetup(): log.debug("lib.get_attr_in_layer is not optimized for render setup") with renderlayer(layer): return cmds.getAttr(attr) + # Ignore complex query if we're in the layer anyway + current_layer = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) + if layer == current_layer: + return cmds.getAttr(attr) + connections = cmds.listConnections(attr, plugs=True, source=False, destination=True, type="renderLayer") or [] - connections = filter(lambda x: x.endswith(".plug"), connections) - if not connections or layer == cmds.editRenderLayerGlobals(query=True, - currentRenderLayer=True): + if not connections: return cmds.getAttr(attr) - + # Some value types perform a conversion when assigning # TODO: See if there's a maya method to allow this conversion # instead of computing it ourselves. @@ -2129,7 +2140,14 @@ def get_attr_in_layer(attr, layer): conversion = None if attr_type == "time": conversion = mel.eval('currentTimeUnitToFPS()') # returns float - + elif attr_type == "doubleAngle": + # Radians to Degrees: 180 / pi + # TODO: This will likely only be correct when Maya units are set + # to degrees + conversion = 57.2957795131 + elif attr_type == "doubleLinear": + raise NotImplementedError("doubleLinear conversion not implemented.") + for connection in connections: if connection.startswith(layer + "."): attr_split = connection.split(".") From 7711938d36f16889e2e331a435b7f318fe64c96e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 14:12:22 +0100 Subject: [PATCH 1206/1249] By default exclude parent hierarchy for pointcaches (PLN-205) - This adds in a toggle to export alembics without the parent hierarchy --- .../maya/create/colorbleed_animation.py | 3 ++ .../plugins/maya/create/colorbleed_model.py | 14 ++++---- .../maya/create/colorbleed_pointcache.py | 1 + .../plugins/maya/publish/collect_instances.py | 6 +++- .../plugins/maya/publish/extract_animation.py | 14 +++++--- .../maya/publish/extract_pointcache.py | 34 +++++++++++++++++++ .../maya/publish/validate_model_content.py | 3 +- 7 files changed, 63 insertions(+), 12 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 6d87cb5078..6a113bf74c 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -29,3 +29,6 @@ class CreateAnimation(avalon.maya.Creator): # Include only nodes that are visible at least once during the # frame range. self.data["visibleOnly"] = False + + # Include the groups above the out_SET content + self.data["includeParentHierarchy"] = False # Include parent groups diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/colorbleed/plugins/maya/create/colorbleed_model.py index 47411e6c52..956f9f0e4f 100644 --- a/colorbleed/plugins/maya/create/colorbleed_model.py +++ b/colorbleed/plugins/maya/create/colorbleed_model.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - import avalon.maya @@ -14,8 +12,12 @@ class CreateModel(avalon.maya.Creator): def __init__(self, *args, **kwargs): super(CreateModel, self).__init__(*args, **kwargs) - data = {"writeColorSets": False, # Vertex colors with the geometry. - "attr": "", # Add options for custom attributes - "attrPrefix": ""} + # Vertex colors with the geometry + self.data["writeColorSets"] = False - self.data.update(data) + # Include attributes by attribute name or prefix + self.data["attr"] = "" + self.data["attrPrefix"] = "" + + # Whether to include parent hierarchy of nodes in the instance + self.data["includeParentHierarchy"] = False diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 495c433e87..2fa0f6cb36 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -19,6 +19,7 @@ class CreatePointCache(avalon.maya.Creator): self.data["writeColorSets"] = False # Vertex colors with the geometry. self.data["renderableOnly"] = False # Only renderable visible shapes self.data["visibleOnly"] = False # only nodes that are visible + self.data["includeParentHierarchy"] = False # Include parent groups # Add options for custom attributes self.data["attr"] = "" diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 807b57c710..f3ce0aece3 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -99,7 +99,11 @@ class CollectInstances(pyblish.api.ContextPlugin): fullPath=True) or [] children = cmds.ls(children, noIntermediate=True, long=True) - parents = self.get_all_parents(members) + parents = [] + if data.get("includeParentHierarchy", True): + # If `includeParentHierarchy` then include the parents + # so they will also be picked up in the instance by validators + parents = self.get_all_parents(members) members_hierarchy = list(set(members + children + parents)) # Create the instance diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index d62c34e915..53e172fa56 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -27,12 +27,12 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): raise RuntimeError("Couldn't find exactly one out_SET: " "{0}".format(out_sets)) out_set = out_sets[0] - nodes = cmds.sets(out_set, query=True) + roots = cmds.sets(out_set, query=True) # Include all descendants - nodes += cmds.listRelatives(nodes, - allDescendents=True, - fullPath=True) or [] + nodes = roots + cmds.listRelatives(roots, + allDescendents=True, + fullPath=True) or [] # Collect the start and end including handles start = instance.data["startFrame"] @@ -58,6 +58,12 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): "selection": True } + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = roots + if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 4cd894d7f4..dc8eabe7ac 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,6 +7,34 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic +def iter_parents(node): + n = node.count("|") + for i in range(1, n): + yield node.rsplit("|", i)[0] + + +def get_highest_in_hierarchy(nodes): + """Return the highest in the hierachies from nodes + + This will return each highest node in separate hierarchies. + E.g. + get_highest_in_hierarchy(["|A|B|C", "A|B", "D|E"]) + # ["A|B", "D|E"] + + """ + # Ensure we use long names + nodes = cmds.ls(nodes, long=True) + lookup = set(nodes) + highest = [] + for node in nodes: + # If no parents are within the original list + # then this is a highest node + if not any(n in lookup for n in iter_parents(node)): + highest.append(node) + + return highest + + class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. @@ -60,6 +88,12 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): "selection": True } + if not instance.data.get("includeParentHierarchy", True): + # Set the root nodes if we don't want to include parents + # The roots are to be considered the ones that are the actual + # direct members of the set + options["root"] = instance.data.get("setMembers") + if int(cmds.about(version=True)) >= 2017: # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/colorbleed/plugins/maya/publish/validate_model_content.py index 1ea26047ee..c9bbb2c23e 100644 --- a/colorbleed/plugins/maya/publish/validate_model_content.py +++ b/colorbleed/plugins/maya/publish/validate_model_content.py @@ -63,7 +63,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin): cls.log.error("Must have exactly one top group") if len(assemblies) == 0: cls.log.warning("No top group found. " - "(Are there objects in the instance?)") + "(Are there objects in the instance?" + " Or is it parented in another group?)") return assemblies or True def _is_visible(node): From bff76636df08bcbbc56c24d3db0613fa53367c82 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 14:48:59 +0100 Subject: [PATCH 1207/1249] Remove unused code --- .../maya/publish/extract_pointcache.py | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index dc8eabe7ac..1e3b4a2ea2 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -7,34 +7,6 @@ import colorbleed.api from colorbleed.maya.lib import extract_alembic -def iter_parents(node): - n = node.count("|") - for i in range(1, n): - yield node.rsplit("|", i)[0] - - -def get_highest_in_hierarchy(nodes): - """Return the highest in the hierachies from nodes - - This will return each highest node in separate hierarchies. - E.g. - get_highest_in_hierarchy(["|A|B|C", "A|B", "D|E"]) - # ["A|B", "D|E"] - - """ - # Ensure we use long names - nodes = cmds.ls(nodes, long=True) - lookup = set(nodes) - highest = [] - for node in nodes: - # If no parents are within the original list - # then this is a highest node - if not any(n in lookup for n in iter_parents(node)): - highest.append(node) - - return highest - - class ExtractColorbleedAlembic(colorbleed.api.Extractor): """Produce an alembic of just point positions and normals. From e2413854a82a8c6d49c159a379c9d981ce766fcf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 21:10:53 +0100 Subject: [PATCH 1208/1249] Remove outdated docstring content --- .../plugins/maya/publish/collect_instances.py | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/colorbleed/plugins/maya/publish/collect_instances.py index 807b57c710..93d8a20c8a 100644 --- a/colorbleed/plugins/maya/publish/collect_instances.py +++ b/colorbleed/plugins/maya/publish/collect_instances.py @@ -12,29 +12,14 @@ class CollectInstances(pyblish.api.ContextPlugin): Identifier: id (str): "pyblish.avalon.instance" - Supported Families: - avalon.model: Geometric representation of artwork - avalon.rig: An articulated model for animators. - A rig may contain a series of sets in which to identify - its contents. - - - cache_SEL: Should contain cachable polygonal meshes - - controls_SEL: Should contain animatable controllers for animators - - resources_SEL: Should contain nodes that reference external files - - Limitations: - - Only Maya is supported - - One (1) rig per scene file - - Unmanaged history, it is up to the TD to ensure - history is up to par. - avalon.animation: Pointcache of `avalon.rig` - Limitations: - Does not take into account nodes connected to those within an objectSet. Extractors are assumed to export with history preserved, but this limits what they will be able to achieve and the amount of data available - to validators. + to validators. An additional collector could also + append this input data into the instance, as we do + for `colorbleed.rig` with collect_history. """ From 825a5587c3a67c7b5572a55b2fdf0a74a5da785d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 13 Dec 2018 21:12:38 +0100 Subject: [PATCH 1209/1249] Fix bug typo in get_attr_in_layer - This fixes a double conversion issue with renderlayer overrides on a layer that has no override set but other layers have for that same attribute. --- colorbleed/maya/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index dd97e46be7..9c2b0d4424 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -2152,7 +2152,7 @@ def get_attr_in_layer(attr, layer): attr = ".".join(attr_split[0:-1]) value = cmds.getAttr("%s.value" % attr) if conversion: - value *= conversion * conversion + value *= conversion return value return cmds.getAttr(attr) From da4b30c8a42dd9c544bac2a1e3a0807fe16910d2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 14 Dec 2018 11:50:01 +0100 Subject: [PATCH 1210/1249] Suspend all viewports during camera extraction (optimization) (PLN-202) --- colorbleed/plugins/maya/publish/extract_camera_alembic.py | 2 +- colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/colorbleed/plugins/maya/publish/extract_camera_alembic.py index 0b315a8d6b..7ed65e0d44 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_alembic.py +++ b/colorbleed/plugins/maya/publish/extract_camera_alembic.py @@ -67,7 +67,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): job_str += ' -file "{0}"'.format(path) with lib.evaluation("off"): - with lib.no_refresh(): + with avalon.maya.suspended_refresh(): cmds.AbcExport(j=job_str, verbose=False) if "files" not in instance.data: diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py index 09e1d535e4..e8a67d4f19 100644 --- a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py @@ -127,7 +127,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): self.log.info("Performing camera bakes for: {0}".format(transform)) with avalon.maya.maintained_selection(): with lib.evaluation("off"): - with lib.no_refresh(): + with avalon.maya.suspended_refresh(): baked = lib.bake_to_world_space( transform, frame_range=range_with_handles, From 7705c7ea5082e75ac28c92914a55ce498d9d0a1f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 14 Dec 2018 12:49:44 +0100 Subject: [PATCH 1211/1249] Remove unused function, favored by avalon.maya.suspended_refresh() --- colorbleed/maya/lib.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/colorbleed/maya/lib.py b/colorbleed/maya/lib.py index 20df970f74..67464360c5 100644 --- a/colorbleed/maya/lib.py +++ b/colorbleed/maya/lib.py @@ -371,26 +371,6 @@ def evaluation(mode="off"): cmds.evaluationManager(mode=original) -@contextlib.contextmanager -def no_refresh(): - """Temporarily disables Maya's UI updates - - Note: - This only disabled the main pane and will sometimes still - trigger updates in torn off panels. - - """ - - pane = _get_mel_global('gMainPane') - state = cmds.paneLayout(pane, query=True, manage=True) - cmds.paneLayout(pane, edit=True, manage=False) - - try: - yield - finally: - cmds.paneLayout(pane, edit=True, manage=state) - - @contextlib.contextmanager def empty_sets(sets, force=False): """Remove all members of the sets during the context""" From 714f98fcda3f1dfa06f5fb08ff8b7860dde5ca8d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 17 Dec 2018 12:58:17 +0100 Subject: [PATCH 1212/1249] PEP08 --- colorbleed/plugins/houdini/publish/extract_alembic.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index 632b0a7d7b..f817a25c86 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -23,7 +23,8 @@ class ExtractAlembic(colorbleed.api.Extractor): file_name = os.path.basename(output) # We run the render - self.log.info("Writing alembic '%s' to '%s'" % (file_name, staging_dir)) + self.log.info("Writing alembic '%s' to '%s'" % (file_name, + staging_dir)) ropnode.render() if "files" not in instance.data: From 75a032733e5f3117fc7670f206613613ab7968c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 17 Dec 2018 13:05:25 +0100 Subject: [PATCH 1213/1249] Remove render in background functionality for VDB as it was unstable - Publishing would just continue even though background render was not finished yet. --- colorbleed/plugins/houdini/create/create_vbd_cache.py | 6 ++---- colorbleed/plugins/houdini/publish/extract_vdb_cache.py | 9 +++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 30f467b3b0..617975a711 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -15,10 +15,8 @@ class CreateVDBCache(houdini.Creator): # Remove the active, we are checking the bypass flag of the nodes self.data.pop("active", None) - self.data.update({ - "node_type": "geometry", # Set node type to create for output - "executeBackground": True # Render node in background - }) + # Set node type to create for output + self.data["node_type"] = "geometry" def process(self): instance = super(CreateVDBCache, self).process() diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index a489eb2dbb..337460dd58 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -20,13 +20,10 @@ class ExtractVDBCache(colorbleed.api.Extractor): sop_output = ropnode.evalParm("sopoutput") staging_dir = os.path.normpath(os.path.dirname(sop_output)) instance.data["stagingDir"] = staging_dir + file_name = os.path.basename(sop_output) - if instance.data.get("executeBackground", True): - self.log.info("Creating background task..") - ropnode.parm("executebackground").pressButton() - self.log.info("Finished") - else: - ropnode.render() + self.log.info("Writing VDB '%s' to '%s'" % (file_name, staging_dir)) + ropnode.render() if "files" not in instance.data: instance.data["files"] = [] From 91fdc543e471440d60c9038d0d23d6a12b3af4e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:45:05 +0100 Subject: [PATCH 1214/1249] Update vendor/pather to 0.1.1 - This fixes ANM-15: Automatic switching of task for some paths failed with special characters in path --- colorbleed/vendor/pather/core.py | 4 ++-- colorbleed/vendor/pather/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/vendor/pather/core.py b/colorbleed/vendor/pather/core.py index f2a469dc8b..1203cf9e50 100644 --- a/colorbleed/vendor/pather/core.py +++ b/colorbleed/vendor/pather/core.py @@ -9,8 +9,8 @@ import glob from .error import ParseError # Regex pattern that matches valid file -# TODO: Implement complete pattern if required -RE_FILENAME = '[-\w.,; \[\]]' +# A filename may not contain \/:*?"<>| +RE_FILENAME = r"[^\\/:\"*?<>|]" def format(pattern, data, allow_partial=True): diff --git a/colorbleed/vendor/pather/version.py b/colorbleed/vendor/pather/version.py index 85f96b1e3f..63d31c8524 100644 --- a/colorbleed/vendor/pather/version.py +++ b/colorbleed/vendor/pather/version.py @@ -1,7 +1,7 @@ VERSION_MAJOR = 0 VERSION_MINOR = 1 -VERSION_PATCH = 0 +VERSION_PATCH = 1 version_info = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH) version = '%i.%i.%i' % version_info From 7cca8b80cb51f4834eece8725ed636e98f03c40d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:48:08 +0100 Subject: [PATCH 1215/1249] Don't force build_from_path on CreateAlembicCamera default instance --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 63596b1cfc..8da8c4d2b5 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -21,8 +21,6 @@ class CreateAlembicCamera(houdini.Creator): instance = super(CreateAlembicCamera, self).process() parms = {"use_sop_path": True, - "build_from_path": True, - "path_attrib": "path", "filename": "$HIP/pyblish/%s.abc" % self.name} if self.nodes: From 9fef84ef1dd8cbca80df5928d9e22a0725cdc6a8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 19 Dec 2018 10:48:35 +0100 Subject: [PATCH 1216/1249] Rename plug-in to be clearer and more explicit about what it does --- colorbleed/plugins/houdini/publish/collect_output_node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py index dbfe8a5890..a3f49761b9 100644 --- a/colorbleed/plugins/houdini/publish/collect_output_node.py +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -1,13 +1,13 @@ import pyblish.api -class CollectOutputNode(pyblish.api.InstancePlugin): - """Collect the out node which of the instance""" +class CollectOutputSOPPath(pyblish.api.InstancePlugin): + """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder families = ["*"] hosts = ["houdini"] - label = "Collect Output Node" + label = "Collect Output SOP Path" def process(self, instance): From 5ac8394797df215fe0fba987d9f34788b1d02265 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 09:22:04 +0100 Subject: [PATCH 1217/1249] Fix PLN-220: ROP render error hangs Pyblish - It's because Houdini errors are old-style class error, those seem to hang Pyblish QML - See: https://gitter.im/pyblish/pyblish?at=5c1bdb49b8760c21bbfbee7b --- .../plugins/houdini/publish/extract_alembic.py | 12 +++++++++++- .../plugins/houdini/publish/extract_vdb_cache.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/colorbleed/plugins/houdini/publish/extract_alembic.py index f817a25c86..8872372484 100644 --- a/colorbleed/plugins/houdini/publish/extract_alembic.py +++ b/colorbleed/plugins/houdini/publish/extract_alembic.py @@ -13,6 +13,8 @@ class ExtractAlembic(colorbleed.api.Extractor): def process(self, instance): + import hou + ropnode = instance[0] # Get the filename from the filename parameter @@ -25,7 +27,15 @@ class ExtractAlembic(colorbleed.api.Extractor): # We run the render self.log.info("Writing alembic '%s' to '%s'" % (file_name, staging_dir)) - ropnode.render() + try: + ropnode.render() + except hou.Error as exc: + # The hou.Error is not inherited from a Python Exception class, + # so we explicitly capture the houdini error, otherwise pyblish + # will remain hanging. + import traceback + traceback.print_exc() + raise RuntimeError("Render failed: {0}".format(exc)) if "files" not in instance.data: instance.data["files"] = [] diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py index 337460dd58..7fe443b5a1 100644 --- a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py +++ b/colorbleed/plugins/houdini/publish/extract_vdb_cache.py @@ -13,6 +13,8 @@ class ExtractVDBCache(colorbleed.api.Extractor): def process(self, instance): + import hou + ropnode = instance[0] # Get the filename from the filename parameter @@ -23,7 +25,15 @@ class ExtractVDBCache(colorbleed.api.Extractor): file_name = os.path.basename(sop_output) self.log.info("Writing VDB '%s' to '%s'" % (file_name, staging_dir)) - ropnode.render() + try: + ropnode.render() + except hou.Error as exc: + # The hou.Error is not inherited from a Python Exception class, + # so we explicitly capture the houdini error, otherwise pyblish + # will remain hanging. + import traceback + traceback.print_exc() + raise RuntimeError("Render failed: {0}".format(exc)) if "files" not in instance.data: instance.data["files"] = [] From 8959c6276010e1b6ba11c1bdeb4b6714080acab5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 15:56:13 +0100 Subject: [PATCH 1218/1249] Implement validation of prims attribute for Build from Hierarchy in ROP --- .../validate_primitive_hierarchy_paths.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py diff --git a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py new file mode 100644 index 0000000000..6563f4bd87 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py @@ -0,0 +1,74 @@ +import pyblish.api +import colorbleed.api + + +class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): + """Validate all primitives build hierarchy from attribute when enabled. + + The name of the attribute must exist on the prims and have the same name + as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic + ROP node whenever Build Hierarchy from Attribute is enabled. + + """ + + order = colorbleed.api.ValidateContentsOrder + 0.1 + families = ["colorbleed.pointcache"] + hosts = ["houdini"] + label = "Validate Prims Hierarchy Path" + + def process(self, instance): + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError("See log for details. " + "Invalid nodes: {0}".format(invalid)) + + @classmethod + def get_invalid(cls, instance): + + import hou + + output = instance.data["output_node"] + prims = output.geometry().prims() + + rop = instance[0] + build_from_path = rop.parm("build_from_path").eval() + if not build_from_path: + cls.log.debug("Alembic ROP has 'Build from Path' disabled. " + "Validation is ignored..") + + path_attr = rop.parm("path_attrib").eval() + if not path_attr: + cls.log.error("The Alembic ROP node has no Path Attribute" + "value set, but 'Build Hierarchy from Attribute'" + "is enabled.") + return [rop.path()] + + cls.log.debug("Checking for attribute: %s" % path_attr) + + missing_attr = [] + invalid_attr = [] + for prim in prims: + + try: + path = prim.stringAttribValue(path_attr) + except hou.OperationFailed: + # Attribute does not exist. + missing_attr.append(prim) + continue + + if not path: + # Empty path value is invalid. + invalid_attr.append(prim) + continue + + if missing_attr: + cls.log.info("Prims are missing attribute `%s`" % path_attr) + + if invalid_attr: + cls.log.info("Prims have no value for attribute `%s` " + "(%s of %s prims)" % (path_attr, + len(invalid_attr), + len(prims))) + + if missing_attr or invalid_attr: + return [output.path()] From 39fde42c407efaa6bf9d361dbbd86fd6285a80a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:00:16 +0100 Subject: [PATCH 1219/1249] Remove duplicate plug-in functionality - This is replaced by validate_output_node.py --- .../publish/validate_outnode_exists.py | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 colorbleed/plugins/houdini/publish/validate_outnode_exists.py diff --git a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py b/colorbleed/plugins/houdini/publish/validate_outnode_exists.py deleted file mode 100644 index ff682ee820..0000000000 --- a/colorbleed/plugins/houdini/publish/validate_outnode_exists.py +++ /dev/null @@ -1,52 +0,0 @@ -import pyblish.api -import colorbleed.api - - -class ValidatOutputNodeExists(pyblish.api.InstancePlugin): - """Validate if node attribute Create intermediate Directories is turned on - - Rules: - * The node must have Create intermediate Directories turned on to - ensure the output file will be created - - """ - - order = colorbleed.api.ValidateContentsOrder - families = ["*"] - hosts = ['houdini'] - label = "Output Node Exists" - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Could not find output node(s)!") - - @classmethod - def get_invalid(cls, instance): - - import hou - - result = set() - - node = instance[0] - assert node, "No node in instance. This is a bug." - - if node.type().name() == "alembic": - soppath_parm = "sop_path" - else: - # Fall back to geometry node - soppath_parm = "soppath" - - sop_path = node.parm(soppath_parm).eval() - output_node = hou.node(sop_path) - - if output_node is None: - cls.log.error("Node at '%s' does not exist" % sop_path) - result.add(node.path()) - - # Added cam as this is a legit output type (cameras can't - if output_node.type().name() not in ["output", "cam"]: - cls.log.error("SOP Path does not end path at output node") - result.add(node.path()) - - return result From ef179da1f2d4e2a2e8f8ccbac7c11d7119d8a86c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:03:00 +0100 Subject: [PATCH 1220/1249] Remove duplicated information in docstring, simplify readability. --- .../plugins/houdini/publish/validate_mkpaths_toggled.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 7e298ce952..1a83565d11 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -3,13 +3,7 @@ import colorbleed.api class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): - """Validate if node attribute Create intermediate Directories is turned on - - Rules: - * The node must have Create intermediate Directories turned on to - ensure the output file will be created - - """ + """Validate Create Intermediate Directories is enabled on ROP node.""" order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.pointcache'] From 6144af98df4dfb9f9abc421c0891b099080df5df Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 16:05:18 +0100 Subject: [PATCH 1221/1249] Actually ignore validation when it should. --- .../houdini/publish/validate_primitive_hierarchy_paths.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py index 6563f4bd87..ab91970b20 100644 --- a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py +++ b/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py @@ -35,6 +35,7 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): if not build_from_path: cls.log.debug("Alembic ROP has 'Build from Path' disabled. " "Validation is ignored..") + return path_attr = rop.parm("path_attrib").eval() if not path_attr: From d8bcf279c2294f55c04cfa26cb82d09e290a8180 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:22:38 +0100 Subject: [PATCH 1222/1249] Add docstring to Houdini Creators --- colorbleed/plugins/houdini/create/create_alembic_camera.py | 1 + colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- colorbleed/plugins/houdini/create/create_vbd_cache.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index 8da8c4d2b5..c548a6309a 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -2,6 +2,7 @@ from avalon import houdini class CreateAlembicCamera(houdini.Creator): + """Single baked camera from Alembic ROP""" name = "camera" label = "Camera (Abc)" diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index c54a6c91a6..4eab69fd98 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreatePointCache(houdini.Creator): - """Alembic pointcache for animated data""" + """Alembic ROP to pointcache""" name = "pointcache" label = "Point Cache" diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/colorbleed/plugins/houdini/create/create_vbd_cache.py index 617975a711..ebdb1271ce 100644 --- a/colorbleed/plugins/houdini/create/create_vbd_cache.py +++ b/colorbleed/plugins/houdini/create/create_vbd_cache.py @@ -2,7 +2,7 @@ from avalon import houdini class CreateVDBCache(houdini.Creator): - """Alembic pointcache for animated data""" + """OpenVDB from Geometry ROP""" name = "vbdcache" label = "VDB Cache" From 7098df26e7fc511d2c540784cfea4467adefd1a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:24:44 +0100 Subject: [PATCH 1223/1249] Cosmetics typo --- colorbleed/plugins/houdini/create/create_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/colorbleed/plugins/houdini/create/create_pointcache.py index 4eab69fd98..85993678c2 100644 --- a/colorbleed/plugins/houdini/create/create_pointcache.py +++ b/colorbleed/plugins/houdini/create/create_pointcache.py @@ -22,7 +22,7 @@ class CreatePointCache(houdini.Creator): parms = {"use_sop_path": True, # Export single node from SOP Path "build_from_path": True, # Direct path of primitive in output - "path_attrib": "path", # Pass path attribute for output\ + "path_attrib": "path", # Pass path attribute for output "prim_to_detail_pattern": "cbId", "format": 2, # Set format to Ogawa "filename": "$HIP/pyblish/%s.abc" % self.name} From c33e1274f35c4ada86ab21e7ad5e8bae023be3e6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 22:32:48 +0100 Subject: [PATCH 1224/1249] Fix docstring typo --- .../plugins/houdini/publish/validate_alembic_input_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py index 91f9e9f97e..663b1198eb 100644 --- a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py +++ b/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py @@ -7,7 +7,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): The connected node cannot be of the following types for Alembic: - VDB - - Volumne + - Volume """ From 3e2b506cf302d1ac27e134ef14d30f725231fd84 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:25:15 +0100 Subject: [PATCH 1225/1249] Implement Alembic Camera extraction in Houdini - the right way --- .../houdini/create/create_alembic_camera.py | 20 +++++++-- .../houdini/publish/collect_output_node.py | 3 +- .../houdini/publish/validate_camera_rop.py | 41 +++++++++++++++++++ .../publish/validate_mkpaths_toggled.py | 3 +- .../houdini/publish/validate_output_node.py | 10 ++--- 5 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 colorbleed/plugins/houdini/publish/validate_camera_rop.py diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/colorbleed/plugins/houdini/create/create_alembic_camera.py index c548a6309a..96449fb3db 100644 --- a/colorbleed/plugins/houdini/create/create_alembic_camera.py +++ b/colorbleed/plugins/houdini/create/create_alembic_camera.py @@ -21,11 +21,25 @@ class CreateAlembicCamera(houdini.Creator): def process(self): instance = super(CreateAlembicCamera, self).process() - parms = {"use_sop_path": True, - "filename": "$HIP/pyblish/%s.abc" % self.name} + parms = { + "filename": "$HIP/pyblish/%s.abc" % self.name, + "use_sop_path": False + } if self.nodes: node = self.nodes[0] - parms.update({"sop_path": node.path()}) + path = node.path() + + # Split the node path into the first root and the remainder + # So we can set the root and objects parameters correctly + _, root, remainder = path.split("/", 2) + parms.update({ + "root": "/" + root, + "objects": remainder + }) instance.setParms(parms) + + # Lock the Use Sop Path setting so the + # user doesn't accidentally enable it. + instance.parm("use_sop_path").lock(True) diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/colorbleed/plugins/houdini/publish/collect_output_node.py index a3f49761b9..d90898944f 100644 --- a/colorbleed/plugins/houdini/publish/collect_output_node.py +++ b/colorbleed/plugins/houdini/publish/collect_output_node.py @@ -5,7 +5,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Collect Output SOP Path" diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py new file mode 100644 index 0000000000..bf09b26df4 --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -0,0 +1,41 @@ +import pyblish.api +import colorbleed.api + + +class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): + """Validate Camera ROP settings.""" + + order = colorbleed.api.ValidateContentsOrder + families = ['colorbleed.camera'] + hosts = ['houdini'] + label = 'Camera ROP' + + def process(self, instance): + + import hou + + node = instance[0] + if node.parm("use_sop_path").eval(): + raise RuntimeError("Alembic ROP for Camera export should not be " + "set to 'Use Sop Path'. Please disable.") + + # Get the root and objects parameter of the Alembic ROP node + root = node.parm("root").eval() + objects = node.parm("objects").eval() + assert root, "Root parameter must be set on Alembic ROP" + assert root.startswith("/"), "Root parameter must start with slash /" + assert objects, "Objects parameter must be set on Alembic ROP" + assert len(objects.split(" ")) == 1, "Must have only a single object." + + # Check if the object exists and is a camera + path = root + "/" + objects + camera = hou.node(path) + + if not camera: + raise ValueError("Camera path does not exist: %s" % path) + + if not camera.type().name() == "cam": + raise ValueError("Object set in Alembic ROP is not a camera: " + "%s (type: %s)" % (camera, camera.type().name())) + + diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index 1a83565d11..a66c6754be 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -6,7 +6,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.pointcache'] + families = ['colorbleed.pointcache', + 'colorbleed.camera'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index bb9eec508e..8a088afec9 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -13,7 +13,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["*"] + families = ["colorbleed.pointcache", + "colorbleed.vdbcache"] hosts = ["houdini"] label = "Validate Output Node" @@ -39,10 +40,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): # Check if type is correct type_name = output_node.type().name() - if type_name not in ["output", "cam"]: - cls.log.error("Output node `%s` is not an accepted type." - "Expected types: `output` or `camera`" % - output_node.path()) + if type_name != "output": + cls.log.error("Output node `%s` is not an `output` type node." + % output_node.path()) return [output_node.path()] # Check if output node has incoming connections From fa11bb55cdaf539d8faeefa09191c08e505963a9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 21 Dec 2018 23:29:49 +0100 Subject: [PATCH 1226/1249] Refactor class name (fix typo) --- colorbleed/plugins/houdini/publish/validate_camera_rop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/colorbleed/plugins/houdini/publish/validate_camera_rop.py index bf09b26df4..83f4dd5a57 100644 --- a/colorbleed/plugins/houdini/publish/validate_camera_rop.py +++ b/colorbleed/plugins/houdini/publish/validate_camera_rop.py @@ -2,7 +2,7 @@ import pyblish.api import colorbleed.api -class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): +class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" order = colorbleed.api.ValidateContentsOrder From a6c6b1ee2d8e841c02245d3c01441ccf4bfbc39a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 31 Dec 2018 12:03:08 +0100 Subject: [PATCH 1227/1249] Add Work Files app as icon in toolbar in Maya --- colorbleed/maya/customize.py | 13 +++++++++++++ res/icons/workfiles.png | Bin 0 -> 205 bytes 2 files changed, 13 insertions(+) create mode 100644 res/icons/workfiles.png diff --git a/colorbleed/maya/customize.py b/colorbleed/maya/customize.py index 58f2f6d8a5..8b2dda7540 100644 --- a/colorbleed/maya/customize.py +++ b/colorbleed/maya/customize.py @@ -77,6 +77,7 @@ def override_toolbox_ui(): import avalon.tools.cbsceneinventory as inventory import avalon.tools.cbloader as loader + from avalon.maya.pipeline import launch_workfiles_app # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" @@ -97,6 +98,18 @@ def override_toolbox_ui(): background_color = (0.267, 0.267, 0.267) controls = [] + control = mc.iconTextButton( + "colorbleed_toolbox_workfiles", + annotation="Work Files", + label="Work Files", + image=os.path.join(icons, "workfiles.png"), + command=lambda: launch_workfiles_app(), + bgc=background_color, + width=icon_size, + height=icon_size, + parent=parent) + controls.append(control) + control = mc.iconTextButton( "colorbleed_toolbox_loader", annotation="Loader", diff --git a/res/icons/workfiles.png b/res/icons/workfiles.png new file mode 100644 index 0000000000000000000000000000000000000000..f17c869600c4deff23b1a556b54676a9b2943413 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5X*aCb)Tz}4y`Tzg_%-XM3KoO>rAiv=M3{STkcma9-o-U3d z6}PTjHsk}De&E~x`DG#^T+J76O!ejvIG|*~EO3O$p+n|C^U{wZh6YYdf7mX?-ev!C w{XRo=gJuHj1(vHK{$`$m_FrdfIX5seOnb>BSjqC?1kh*(Pgg&ebxsLQ00S>Xn*aa+ literal 0 HcmV?d00001 From 105657a0a3e4f7b49b0ee85db985f815c1660272 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 7 Jan 2019 18:28:58 +0100 Subject: [PATCH 1228/1249] Support Yeti multiple image search paths (split by os.path.pathsep) --- .../plugins/maya/publish/collect_yeti_rig.py | 88 ++++++++++++++----- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c673581c8a..0400de25d7 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -99,7 +99,13 @@ class CollectYetiRig(pyblish.api.InstancePlugin): list """ resources = [] - image_search_path = cmds.getAttr("{}.imageSearchPath".format(node)) + + image_search_paths = cmds.getAttr("{}.imageSearchPath".format(node)) + + # TODO: Somehow this uses OS environment path separator, `:` vs `;` + # Later on check whether this is pipeline OS cross-compatible. + image_search_paths = [p for p in + image_search_paths.split(os.path.pathsep) if p] # List all related textures texture_filenames = cmds.pgYetiCommand(node, listTextures=True) @@ -111,36 +117,33 @@ class CollectYetiRig(pyblish.api.InstancePlugin): type="reference") self.log.info("Found %i reference node(s)" % len(reference_nodes)) - if texture_filenames and not image_search_path: + if texture_filenames and not image_search_paths: raise ValueError("pgYetiMaya node '%s' is missing the path to the " "files in the 'imageSearchPath " "atttribute'" % node) # Collect all texture files for texture in texture_filenames: - item = {"files": [], "source": texture, "node": node} - texture_filepath = os.path.join(image_search_path, texture) - if len(texture.split(".")) > 2: - - # For UDIM based textures (tiles) - if "" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="") - item["files"].extend(sequences) - - # Based textures (animated masks f.e) - elif "%04d" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="%04d") - item["files"].extend(sequences) - # Assuming it is a fixed name - else: - item["files"].append(texture_filepath) - else: - item["files"].append(texture_filepath) + item = { + "files": self.search_textures(paths=image_search_paths, + texture=texture), + "source": texture, + "node": node + } resources.append(item) + # For now validate that every texture has at least a single file + # resolved. Since a 'resource' does not have the requirement of having + # a `files` explicitly mapped it's not explicitly validated. + # TODO: Validate this as a validator + invalid_resources = [] + for resource in resources: + if not resource['files']: + invalid_resources.append(resource) + if invalid_resources: + raise RuntimeError("Invalid resources") + # Collect all referenced files for reference_node in reference_nodes: ref_file = cmds.pgYetiGraph(node, @@ -149,6 +152,9 @@ class CollectYetiRig(pyblish.api.InstancePlugin): getParamValue=True) if not os.path.isfile(ref_file): + self.log.warning("Reference node '%s' has no valid file " + "path set: %s" % (reference_node, ref_file)) + # TODO: This should allow to pass and fail in Validator instead raise RuntimeError("Reference file must be a full file path!") # Create resource dict @@ -169,6 +175,44 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources + def search_textures(self, paths, texture): + """Search the texture source files in the image search paths. + + This also parses to full sequences. + + """ + + # TODO: Check if texture is abspath, if so don't search. + + # Collect the first matching texture from the search paths + for root in paths: + texture_filepath = os.path.join(root, texture) + + # Collect full sequence if it matches a sequence pattern + if len(texture.split(".")) > 2: + + # For UDIM based textures (tiles) + if "" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="") + if sequences: + return sequences + + # Based textures (animated masks f.e) + elif "%04d" in texture: + sequences = self.get_sequence(texture_filepath, + pattern="%04d") + if sequences: + return sequences + + # Assuming it is a fixed name + if os.path.exists(texture_filepath): + return [texture_filepath] + + self.log.warning("No texture found for: %s (searched: %s)" % (texture, + paths)) + return [] + def get_sequence(self, filename, pattern="%04d"): """Get sequence from filename From c6b8375cd2f542d631c35e09bb3238554e80eb49 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 14:20:11 +0100 Subject: [PATCH 1229/1249] Yeti: Add support for absolute path textures --- .../plugins/maya/publish/collect_yeti_rig.py | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0400de25d7..0f7b0ff6bd 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -124,9 +124,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Collect all texture files for texture in texture_filenames: + + files = [] + if os.path.isabs(texture): + self.log.debug("Texture is absolute path, ignoring " + "image search paths.. %s" % texture) + files = self.search_textures(texture) + else: + for root in image_search_paths: + filepath = os.path.join(root, image_search_paths) + files = self.search_textures(filepath) + if files: + # Break out on first match in search paths.. + break + + if not files: + self.log.warning( + "No texture found for: %s " + "(searched: %s)" % (texture, image_search_paths)) + item = { - "files": self.search_textures(paths=image_search_paths, - texture=texture), + "files": files, "source": texture, "node": node } @@ -175,46 +193,42 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources - def search_textures(self, paths, texture): + def search_textures(self, filepath): """Search the texture source files in the image search paths. This also parses to full sequences. """ + filename = os.path.basename(filepath) - # TODO: Check if texture is abspath, if so don't search. + # Collect full sequence if it matches a sequence pattern + if len(filename.split(".")) > 2: - # Collect the first matching texture from the search paths - for root in paths: - texture_filepath = os.path.join(root, texture) + # For UDIM based textures (tiles) + if "" in filename: + sequences = self.get_sequence(filepath, + pattern="") + if sequences: + return sequences - # Collect full sequence if it matches a sequence pattern - if len(texture.split(".")) > 2: + # Frame/time - Based textures (animated masks f.e) + elif "%04d" in filename: + sequences = self.get_sequence(filepath, + pattern="%04d") + if sequences: + return sequences - # For UDIM based textures (tiles) - if "" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="") - if sequences: - return sequences - - # Based textures (animated masks f.e) - elif "%04d" in texture: - sequences = self.get_sequence(texture_filepath, - pattern="%04d") - if sequences: - return sequences - - # Assuming it is a fixed name - if os.path.exists(texture_filepath): - return [texture_filepath] - - self.log.warning("No texture found for: %s (searched: %s)" % (texture, - paths)) + # Assuming it is a fixed name (single file) + if os.path.exists(filepath): + return [filepath] return [] def get_sequence(self, filename, pattern="%04d"): - """Get sequence from filename + """Get sequence from filename. + + This will only return files if they exist on disk as it tries + to collect the sequence using the filename pattern and searching + for them on disk. Supports negative frame ranges like -001, 0000, 0001 and -0001, 0000, 0001. From dd8e962818b94b7a3e2cba789110905e3da95a78 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:28:37 +0100 Subject: [PATCH 1230/1249] Fix typo and improve debug message readability --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0f7b0ff6bd..a2ab1c608b 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -128,11 +128,11 @@ class CollectYetiRig(pyblish.api.InstancePlugin): files = [] if os.path.isabs(texture): self.log.debug("Texture is absolute path, ignoring " - "image search paths.. %s" % texture) + "image search paths for: %s" % texture) files = self.search_textures(texture) else: for root in image_search_paths: - filepath = os.path.join(root, image_search_paths) + filepath = os.path.join(root, texture) files = self.search_textures(filepath) if files: # Break out on first match in search paths.. From 12005b62d11589e94858475c700f6baaafd0e156 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:32:14 +0100 Subject: [PATCH 1231/1249] Correctly check reference node content with animated %04d inputs --- .../plugins/maya/publish/collect_yeti_rig.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index a2ab1c608b..280721cd9d 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -169,25 +169,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): param="reference_file", getParamValue=True) - if not os.path.isfile(ref_file): - self.log.warning("Reference node '%s' has no valid file " - "path set: %s" % (reference_node, ref_file)) - # TODO: This should allow to pass and fail in Validator instead - raise RuntimeError("Reference file must be a full file path!") - # Create resource dict - item = {"files": [], - "source": ref_file, - "node": node, - "graphnode": reference_node, - "param": "reference_file"} + item = { + "source": ref_file, + "node": node, + "graphnode": reference_node, + "param": "reference_file", + "files": [] + } ref_file_name = os.path.basename(ref_file) if "%04d" in ref_file_name: - ref_files = self.get_sequence(ref_file) - item["files"].extend(ref_files) + item["files"] = self.get_sequence(ref_file) else: - item["files"].append(ref_file) + if os.path.exists(ref_file) and os.path.isfile(ref_file): + item["files"] = [ref_file] + + if not item["files"]: + self.log.warning("Reference node '%s' has no valid file " + "path set: %s" % (reference_node, ref_file)) + # TODO: This should allow to pass and fail in Validator instead + raise RuntimeError("Reference node must be a full file path!") resources.append(item) From 8d63ed0c059140220ac76712f37d3c6e29ae9a00 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:37:23 +0100 Subject: [PATCH 1232/1249] Improve docstring --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 280721cd9d..c9ac31e5a1 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -196,9 +196,17 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return resources def search_textures(self, filepath): - """Search the texture source files in the image search paths. + """Search all texture files on disk. - This also parses to full sequences. + This also parses to full sequences for those with dynamic patterns + like and %04d in the filename. + + Args: + filepath (str): The full path to the file, including any + dynamic patterns like or %04d + + Returns: + list: The files found on disk """ filename = os.path.basename(filepath) @@ -223,6 +231,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Assuming it is a fixed name (single file) if os.path.exists(filepath): return [filepath] + return [] def get_sequence(self, filename, pattern="%04d"): From 2cc5c1cc44f200a50fe66ef13741bfd01036d361 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 8 Jan 2019 16:38:53 +0100 Subject: [PATCH 1233/1249] Refactor 'filename' to 'filepath' as it refers to full file path --- colorbleed/plugins/maya/publish/collect_yeti_rig.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index c9ac31e5a1..0308b296ae 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -234,7 +234,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): return [] - def get_sequence(self, filename, pattern="%04d"): + def get_sequence(self, filepath, pattern="%04d"): """Get sequence from filename. This will only return files if they exist on disk as it tries @@ -245,7 +245,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): 0000, 0001. Arguments: - filename (str): The full path to filename containing the given + filepath (str): The full path to filename containing the given pattern. pattern (str): The pattern to swap with the variable frame number. @@ -255,10 +255,10 @@ class CollectYetiRig(pyblish.api.InstancePlugin): """ from avalon.vendor import clique - escaped = re.escape(filename) + escaped = re.escape(filepath) re_pattern = escaped.replace(pattern, "-?[0-9]+") - source_dir = os.path.dirname(filename) + source_dir = os.path.dirname(filepath) files = [f for f in os.listdir(source_dir) if re.match(re_pattern, f)] From 49bdf50384212f7c55f8fcec9d3477091e43b591 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 9 Jan 2019 21:07:02 +0100 Subject: [PATCH 1234/1249] Fix typo (missing comma) and refactor v-ray environment variables - The V-Ray Environment variables changed from V-Ray 3+ to V-Ray Next. The x64 suffix was dropped. --- colorbleed/plugins/maya/publish/submit_maya_deadline.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/maya/publish/submit_maya_deadline.py b/colorbleed/plugins/maya/publish/submit_maya_deadline.py index d2b394c1d1..430f121779 100644 --- a/colorbleed/plugins/maya/publish/submit_maya_deadline.py +++ b/colorbleed/plugins/maya/publish/submit_maya_deadline.py @@ -207,9 +207,9 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): # todo: This is a temporary fix for yeti variables "PEREGRINEL_LICENSE", "REDSHIFT_MAYAEXTENSIONSPATH", - "REDSHIFT_DISABLEOUTPUTLOCKFILES" - "VRAY_FOR_MAYA2018_PLUGINS_X64", - "VRAY_PLUGINS_X64", + "REDSHIFT_DISABLEOUTPUTLOCKFILES", + "VRAY_FOR_MAYA2018_PLUGINS", + "VRAY_PLUGINS", "VRAY_USE_THREAD_AFFINITY", "MAYA_MODULE_PATH" ] From 9b832b486e5ee469208e5a8f6b28c24aadcd99cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 14 Jan 2019 18:24:57 +0100 Subject: [PATCH 1235/1249] Don't force output node, but allow any SOP node. - Also validate there's actual .geometry() data in the node. --- .../houdini/publish/validate_output_node.py | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 8a088afec9..5f55e1507b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -7,8 +7,9 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): This will ensure: - The SOP Path is set. - The SOP Path refers to an existing object. - - The SOP Path node is of type 'output' or 'camera' + - The SOP Path node is a SOP node. - The SOP Path node has at least one input connection (has an input) + - The SOP Path has geometry data. """ @@ -28,6 +29,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): + import hou + output_node = instance.data["output_node"] if output_node is None: @@ -38,15 +41,33 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): return node.path() - # Check if type is correct - type_name = output_node.type().name() - if type_name != "output": - cls.log.error("Output node `%s` is not an `output` type node." - % output_node.path()) - return [output_node.path()] + # Output node must be a Sop node. + if not isinstance(output_node, hou.SopNode): + cls.log.error("Output node %s is not a SOP node. " + "SOP Path must point to a SOP node, " + "instead found category type: %s" % ( + output_node.path(), + output_node.type().category().name() + ) + ) + return [output_node] + + # For the sake of completeness also assert the category type + # is Sop to avoid potential edge case scenarios even though + # the isinstance check above should be stricter than this category + assert output_node.type().category().name() == "Sop", ( + "Output node %s is not of category Sop. This is a bug.." % + output_node.path() + ) # Check if output node has incoming connections - if type_name == "output" and not output_node.inputConnections(): + if output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) return [output_node.path()] + + # Ensure the output node has at least Geometry data + if not output_node.geometry(): + cls.log.error("Output node `%s` has no geometry data." + % output_node.path()) + return [output_node.path()] From 9d8133ef82cd1d23c7fb280dda958d330b4d8290 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:27:26 +0100 Subject: [PATCH 1236/1249] Improve Houdini saved filename collection, ignore default "untitled.hip" --- .../houdini/publish/collect_current_file.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/colorbleed/plugins/houdini/publish/collect_current_file.py index 7852943b34..ea954c4791 100644 --- a/colorbleed/plugins/houdini/publish/collect_current_file.py +++ b/colorbleed/plugins/houdini/publish/collect_current_file.py @@ -1,3 +1,4 @@ +import os import hou import pyblish.api @@ -12,4 +13,24 @@ class CollectHoudiniCurrentFile(pyblish.api.ContextPlugin): def process(self, context): """Inject the current working file""" - context.data['currentFile'] = hou.hipFile.path() + + filepath = hou.hipFile.path() + if not os.path.exists(filepath): + # By default Houdini will even point a new scene to a path. + # However if the file is not saved at all and does not exist, + # we assume the user never set it. + filepath = "" + + elif os.path.basename(filepath) == "untitled.hip": + # Due to even a new file being called 'untitled.hip' we are unable + # to confirm the current scene was ever saved because the file + # could have existed already. We will allow it if the file exists, + # but show a warning for this edge case to clarify the potential + # false positive. + self.log.warning("Current file is 'untitled.hip' and we are " + "unable to detect whether the current scene is " + "saved correctly.") + + context.data['currentFile'] = filepath + + From f95ef7dd1ca922864b2071df11f78266b95381d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 09:46:07 +0100 Subject: [PATCH 1237/1249] Houdini correctly set and validate ROP bypass state on instance toggle --- colorbleed/houdini/__init__.py | 19 +++++++++++ .../houdini/publish/validate_bypass.py | 34 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 colorbleed/plugins/houdini/publish/validate_bypass.py diff --git a/colorbleed/houdini/__init__.py b/colorbleed/houdini/__init__.py index e4640e2857..05d5195605 100644 --- a/colorbleed/houdini/__init__.py +++ b/colorbleed/houdini/__init__.py @@ -39,6 +39,8 @@ def install(): avalon.on("save", on_save) avalon.on("open", on_open) + pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) + log.info("Setting default family states for loader..") avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] @@ -91,3 +93,20 @@ def on_open(*args): "your Maya scene.") dialog.on_show.connect(_on_show_inventory) dialog.show() + + +def on_pyblish_instance_toggled(instance, new_value, old_value): + """Toggle saver tool passthrough states on instance toggles.""" + + nodes = instance[:] + if not nodes: + return + + # Assume instance node is first node + instance_node = nodes[0] + + if instance_node.isBypassed() != (not old_value): + print("%s old bypass state didn't match old instance state, " + "updating anyway.." % instance_node.path()) + + instance_node.bypass(not new_value) diff --git a/colorbleed/plugins/houdini/publish/validate_bypass.py b/colorbleed/plugins/houdini/publish/validate_bypass.py new file mode 100644 index 0000000000..9af8a2b9ae --- /dev/null +++ b/colorbleed/plugins/houdini/publish/validate_bypass.py @@ -0,0 +1,34 @@ +import pyblish.api +import colorbleed.api + + +class ValidateBypassed(pyblish.api.InstancePlugin): + """Validate all primitives build hierarchy from attribute when enabled. + + The name of the attribute must exist on the prims and have the same name + as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic + ROP node whenever Build Hierarchy from Attribute is enabled. + + """ + + order = colorbleed.api.ValidateContentsOrder - 0.1 + families = ["*"] + hosts = ["houdini"] + label = "Validate ROP Bypass" + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + rop = invalid[0] + raise RuntimeError( + "ROP node %s is set to bypass, publishing cannot continue.." % + rop.path() + ) + + @classmethod + def get_invalid(cls, instance): + + rop = instance[0] + if rop.isBypassed(): + return [rop] From 5c320088de197080093367d6ada017ae186a43ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:12:59 +0100 Subject: [PATCH 1238/1249] Improve error readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 1 + 1 file changed, 1 insertion(+) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 5f55e1507b..81b4e1e3d5 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,6 +23,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: + invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) From b5424e2954147eb2aaf4f6824f26e0641b74e48b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:13:36 +0100 Subject: [PATCH 1239/1249] Include colorbleed.vdbcache family, and report node path in error --- .../plugins/houdini/publish/validate_mkpaths_toggled.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py index a66c6754be..826dedf933 100644 --- a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -7,7 +7,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): order = colorbleed.api.ValidateContentsOrder families = ['colorbleed.pointcache', - 'colorbleed.camera'] + 'colorbleed.camera', + 'colorbleed.vdbcache'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' @@ -15,8 +16,8 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Found ROP nodes with Create Intermediate " - "Directories turned off") + raise RuntimeError("Found ROP node with Create Intermediate " + "Directories turned off: %s" % invalid) @classmethod def get_invalid(cls, instance): From f7d755d9fe80959f222c720c4ee7a5d7fc425dc2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 10:20:47 +0100 Subject: [PATCH 1240/1249] Correctly pass on node paths for error message readability --- colorbleed/plugins/houdini/publish/validate_output_node.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index 81b4e1e3d5..eb84ea721b 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -23,7 +23,6 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: - invalid = [node.path() for node in invalid] raise RuntimeError("Output node(s) `%s` are incorrect. " "See plug-in log for details." % invalid) @@ -40,7 +39,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): "Ensure a valid SOP output path is set." % node.path()) - return node.path() + return [node.path()] # Output node must be a Sop node. if not isinstance(output_node, hou.SopNode): @@ -51,7 +50,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): output_node.type().category().name() ) ) - return [output_node] + return [output_node.path()] # For the sake of completeness also assert the category type # is Sop to avoid potential edge case scenarios even though From a3daa895e9ffb7b9951e2f0bc2e187a224f424c6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 11:01:27 +0100 Subject: [PATCH 1241/1249] Fix validation check (typo) --- colorbleed/plugins/houdini/publish/validate_output_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/colorbleed/plugins/houdini/publish/validate_output_node.py index eb84ea721b..4b140f63df 100644 --- a/colorbleed/plugins/houdini/publish/validate_output_node.py +++ b/colorbleed/plugins/houdini/publish/validate_output_node.py @@ -61,7 +61,7 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): ) # Check if output node has incoming connections - if output_node.inputConnections(): + if not output_node.inputConnections(): cls.log.error("Output node `%s` has no incoming connections" % output_node.path()) return [output_node.path()] From 497463dc32457605dd3a3b9cd543f5e2cb9bcef7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 15 Jan 2019 17:47:50 +0100 Subject: [PATCH 1242/1249] Fix Yeti callback validation (compare with stripped commands) --- .../validate_yeti_renderscript_callbacks.py | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index b31e31ba52..8864637da2 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -25,6 +25,17 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): hosts = ["maya"] families = ["colorbleed.renderlayer"] + # Settings per renderer + callbacks = { + "vray": { + "pre": "catch(`pgYetiVRayPreRender`)", + "post": "catch(`pgYetiVRayPostRender`)" + }, + "arnold": { + "pre": "pgYetiPreRender" + } + } + def process(self, instance): invalid = self.get_invalid(instance) @@ -35,14 +46,6 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - # lookup per render - render_scripts = {"vray": - {"pre": "catch(`pgYetiVRayPreRender`)", - "post": "catch(`pgYetiVRayPostRender`)"}, - "arnold": - {"pre": "pgYetiPreRender"} - } - yeti_loaded = cmds.pluginInfo("pgYetiMaya", query=True, loaded=True) renderer = instance.data["renderer"] @@ -50,25 +53,29 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): cls.log.info("Redshift ignores any pre and post render callbacks") return False - callback_lookup = render_scripts.get(renderer, {}) + callback_lookup = cls.callbacks.get(renderer, {}) if not callback_lookup: cls.log.warning("Renderer '%s' is not supported in this plugin" % renderer) return False - pre_mel_attr = "defaultRenderGlobals.preMel" - post_mel_attr = "defaultRenderGlobals.postMel" + pre_mel = cmds.getAttr("defaultRenderGlobals.preMel") or "" + post_mel = cmds.getAttr("defaultRenderGlobals.postMel") or "" - pre_render_callback = cmds.getAttr(pre_mel_attr) or "" - post_render_callback = cmds.getAttr(post_mel_attr) or "" + if pre_mel.strip(): + cls.log.debug("Found pre mel: `%s`" % pre_mel) - pre_callbacks = pre_render_callback.split(";") - post_callbacks = post_render_callback.split(";") + if post_mel.strip(): + cls.log.debug("Found post mel: `%s`" % post_mel) + + # Strip callbacks and turn into a set for quick lookup + pre_callbacks = {cmd.strip() for cmd in pre_mel.split(";")} + post_callbacks = {cmd.strip() for cmd in post_mel.split(";")} pre_script = callback_lookup.get("pre", "") post_script = callback_lookup.get("post", "") - # If not loaded + # If Yeti is not loaded invalid = False if not yeti_loaded: if pre_script and pre_script in pre_callbacks: @@ -80,18 +87,19 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): cls.log.error("Found post render callback '%s which is " "not used!" % post_script) invalid = True - else: - if pre_script: - if pre_script not in pre_callbacks: - cls.log.error( - "Could not find required pre render callback " - "`%s`" % pre_script) - invalid = True - if post_script: - if post_script not in post_callbacks: - cls.log.error("Could not find required post render callback" - " `%s`" % post_script) - invalid = True + # If Yeti is loaded + else: + if pre_script and pre_script not in pre_callbacks: + cls.log.error( + "Could not find required pre render callback " + "`%s`" % pre_script) + invalid = True + + if post_script and post_script not in post_callbacks: + cls.log.error( + "Could not find required post render callback" + " `%s`" % post_script) + invalid = True return invalid From abf22276e759a4b454c4e551a3180a272494a5d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 18 Jan 2019 10:42:18 +0100 Subject: [PATCH 1243/1249] Validate V-Ray Distributed Rendering is ignored in batch mode --- .../validate_vray_distributed_rendering.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py diff --git a/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py b/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py new file mode 100644 index 0000000000..632fd2964e --- /dev/null +++ b/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py @@ -0,0 +1,55 @@ +import pyblish.api +import colorbleed.api +import colorbleed.maya.lib as lib + +from maya import cmds + + +class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): + """Validate V-Ray Distributed Rendering is ignored in batch mode. + + Whenever Distributed Rendering is enabled for V-Ray in the render settings + ensure that the "Ignore in batch mode" is enabled so the submitted job + won't try to render each frame with all machines resulting in faulty + errors. + + """ + + order = colorbleed.api.ValidateContentsOrder + label = "VRay Distributed Rendering" + families = ["colorbleed.renderlayer"] + actions = [colorbleed.api.RepairAction] + + # V-Ray attribute names + enabled_attr = "vraySettings.sys_distributed_rendering_on" + ignored_attr = "vraySettings.sys_distributed_rendering_ignore_batch" + + def process(self, instance): + + if instance.data.get("renderer") != "vray": + # If not V-Ray ignore.. + return + + vray_settings = cmds.ls("vraySettings", type="VRaySettingsNode") + assert vray_settings, "Please ensure a VRay Settings Node is present" + + renderlayer = instance.data['setMembers'] + + if not lib.get_attr_in_layer(self.enabled_attr, layer=renderlayer): + # If not distributed rendering enabled, ignore.. + return + + # If distributed rendering is enabled but it is *not* set to ignore + # during batch mode we invalidate the instance + if not lib.get_attr_in_layer(self.ignored_attr, layer=renderlayer): + raise RuntimeError("Renderlayer has distributed rendering enabled " + "but is not set to ignore in batch mode.") + + @classmethod + def repair(cls, instance): + + renderlayer = instance.data.get("setMembers") + with lib.renderlayer(renderlayer): + cls.log.info("Enabling Distributed Rendering " + "ignore in batch mode..") + cmds.setAttr(cls.ignored_attr, True) From f15577a9f596dc2a8fa95ea1492c9ee6e4df5ace Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 21 Jan 2019 17:33:44 +0100 Subject: [PATCH 1244/1249] Default to world-space output for pointcache+animation, plus add toggle --- colorbleed/plugins/maya/create/colorbleed_animation.py | 3 +++ colorbleed/plugins/maya/create/colorbleed_pointcache.py | 1 + colorbleed/plugins/maya/publish/extract_animation.py | 3 ++- colorbleed/plugins/maya/publish/extract_pointcache.py | 3 ++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/colorbleed/plugins/maya/create/colorbleed_animation.py index 6a113bf74c..16f321ee68 100644 --- a/colorbleed/plugins/maya/create/colorbleed_animation.py +++ b/colorbleed/plugins/maya/create/colorbleed_animation.py @@ -32,3 +32,6 @@ class CreateAnimation(avalon.maya.Creator): # Include the groups above the out_SET content self.data["includeParentHierarchy"] = False # Include parent groups + + # Default to exporting world-space + self.data["worldSpace"] = True diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/colorbleed/plugins/maya/create/colorbleed_pointcache.py index 2fa0f6cb36..2738ff6ddf 100644 --- a/colorbleed/plugins/maya/create/colorbleed_pointcache.py +++ b/colorbleed/plugins/maya/create/colorbleed_pointcache.py @@ -20,6 +20,7 @@ class CreatePointCache(avalon.maya.Creator): self.data["renderableOnly"] = False # Only renderable visible shapes self.data["visibleOnly"] = False # only nodes that are visible self.data["includeParentHierarchy"] = False # Include parent groups + self.data["worldSpace"] = True # Default to exporting world-space # Add options for custom attributes self.data["attr"] = "" diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/colorbleed/plugins/maya/publish/extract_animation.py index 53e172fa56..be56c6c937 100644 --- a/colorbleed/plugins/maya/publish/extract_animation.py +++ b/colorbleed/plugins/maya/publish/extract_animation.py @@ -55,7 +55,8 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): "writeVisibility": True, "writeCreases": True, "uvWrite": True, - "selection": True + "selection": True, + "worldSpace": instance.data.get("worldSpace", True) } if not instance.data.get("includeParentHierarchy", True): diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/colorbleed/plugins/maya/publish/extract_pointcache.py index 1e3b4a2ea2..3fcdf3cb58 100644 --- a/colorbleed/plugins/maya/publish/extract_pointcache.py +++ b/colorbleed/plugins/maya/publish/extract_pointcache.py @@ -57,7 +57,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): "writeCreases": True, "writeColorSets": writeColorSets, "uvWrite": True, - "selection": True + "selection": True, + "worldSpace": instance.data.get("worldSpace", True) } if not instance.data.get("includeParentHierarchy", True): From 3d8862b326488ed0a16b538a0371f3a220c7a8da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 11:13:55 +0100 Subject: [PATCH 1245/1249] Add Remove Unknown Plugins script to Maya scripts menu --- colorbleed/maya/menu.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/colorbleed/maya/menu.json b/colorbleed/maya/menu.json index 0c620d3e4c..a6ecea4b11 100644 --- a/colorbleed/maya/menu.json +++ b/colorbleed/maya/menu.json @@ -1443,6 +1443,15 @@ "title": "Remove Unknown Nodes", "tooltip": "Remove all unknown nodes" }, + { + "type": "action", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnknownPlugins.py", + "sourcetype": "file", + "tags": ["cleanup", + "removeUnknownPlugins"], + "title": "Remove Unknown Plugins UI", + "tooltip": "Remove unknown plugins UI" + }, { "type": "action", "command": "$COLORBLEED_SCRIPTS\\cleanup\\removeUnloadedReferences.py", From 98de50fe0d2ebf347891e101f0f2085cf4608b83 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 17:17:22 +0100 Subject: [PATCH 1246/1249] Ignore input connections in Yeti input_SET that are rig internal --- .../plugins/maya/publish/collect_yeti_rig.py | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/colorbleed/plugins/maya/publish/collect_yeti_rig.py index 0308b296ae..e6d4ab803e 100644 --- a/colorbleed/plugins/maya/publish/collect_yeti_rig.py +++ b/colorbleed/plugins/maya/publish/collect_yeti_rig.py @@ -6,6 +6,7 @@ from maya import cmds import pyblish.api from colorbleed.maya import lib +from colorbleed.lib import pairwise SETTINGS = {"renderDensity", @@ -29,6 +30,27 @@ class CollectYetiRig(pyblish.api.InstancePlugin): assert "input_SET" in instance.data["setMembers"], ( "Yeti Rig must have an input_SET") + input_connections = self.collect_input_connections(instance) + + # Collect any textures if used + yeti_resources = [] + yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya", long=True) + for node in yeti_nodes: + # Get Yeti resources (textures) + resources = self.get_yeti_resources(node) + yeti_resources.extend(resources) + + instance.data["rigsettings"] = {"inputs": input_connections} + + instance.data["resources"] = yeti_resources + + # Force frame range for export + instance.data["startFrame"] = 1 + instance.data["endFrame"] = 1 + + def collect_input_connections(self, instance): + """Collect the inputs for all nodes in the input_SET""" + # Get the input meshes information input_content = cmds.ls(cmds.sets("input_SET", query=True), long=True) @@ -39,6 +61,8 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # Ignore intermediate objects input_content = cmds.ls(input_content, long=True, noIntermediate=True) + if not input_content: + return [] # Store all connections connections = cmds.listConnections(input_content, @@ -49,37 +73,26 @@ class CollectYetiRig(pyblish.api.InstancePlugin): # (avoid display layers, etc.) type="dagNode", plugs=True) or [] - - # Group per source, destination pair. We need to reverse the connection - # list as it comes in with the shape used to query first while that - # shape is the destination of the connection - grouped = [(connections[i+1], item) for i, item in - enumerate(connections) if i % 2 == 0] + connections = cmds.ls(connections, long=True) # Ensure long names inputs = [] - for src, dest in grouped: + for dest, src in pairwise(connections): source_node, source_attr = src.split(".", 1) dest_node, dest_attr = dest.split(".", 1) + # Ensure the source of the connection is not included in the + # current instance's hierarchy. If so, we ignore that connection + # as we will want to preserve it even over a publish. + if source_node in instance: + self.log.debug("Ignoring input connection between nodes " + "inside the instance: %s -> %s" % (src, dest)) + continue + inputs.append({"connections": [source_attr, dest_attr], "sourceID": lib.get_id(source_node), "destinationID": lib.get_id(dest_node)}) - # Collect any textures if used - yeti_resources = [] - yeti_nodes = cmds.ls(instance[:], type="pgYetiMaya", long=True) - for node in yeti_nodes: - # Get Yeti resources (textures) - resources = self.get_yeti_resources(node) - yeti_resources.extend(resources) - - instance.data["rigsettings"] = {"inputs": inputs} - - instance.data["resources"] = yeti_resources - - # Force frame range for export - instance.data["startFrame"] = 1 - instance.data["endFrame"] = 1 + return inputs def get_yeti_resources(self, node): """Get all resource file paths From 9831801db439f3afedd563fcf743de00005132c7 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 22 Jan 2019 17:37:37 +0100 Subject: [PATCH 1247/1249] Allow to pass without rig settings, but give warning to user. --- .../publish/validate_yeti_rig_settings.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py index 8b6219d989..90ddfafa45 100644 --- a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py +++ b/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py @@ -2,32 +2,46 @@ import pyblish.api class ValidateYetiRigSettings(pyblish.api.InstancePlugin): + """Validate Yeti Rig Settings have collected input connections. + + The input connections are collected for the nodes in the `input_SET`. + When no input connections are found a warning is logged but it is allowed + to pass validation. + + """ + order = pyblish.api.ValidatorOrder - label = "Validate Yeti Rig Settings" + label = "Yeti Rig Settings" families = ["colorbleed.yetiRig"] def process(self, instance): invalid = self.get_invalid(instance) if invalid: - raise RuntimeError("Detected invalid Yeti Rig data. " + raise RuntimeError("Detected invalid Yeti Rig data. (See log) " "Tip: Save the scene") @classmethod def get_invalid(cls, instance): - rigsettings = instance.data.get("rigsettings", {}) - if not rigsettings: + rigsettings = instance.data.get("rigsettings", None) + if rigsettings is None: cls.log.error("MAJOR ERROR: No rig settings found!") return True # Get inputs inputs = rigsettings.get("inputs", []) + if not inputs: + # Empty rig settings dictionary + cls.log.warning("No rig inputs found. This can happen when " + "the rig has no inputs from outside the rig.") + return False + for input in inputs: source_id = input["sourceID"] if source_id is None: cls.log.error("Discovered source with 'None' as ID, please " - "check if the input shape has an cbId") + "check if the input shape has a cbId") return True destination_id = input["destinationID"] From c8ba94ccbfb5969d235534669f625555bad69c20 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 28 Jan 2019 20:31:55 +0100 Subject: [PATCH 1248/1249] rename cb to pype --- {colorbleed => pype}/__init__.py | 0 {colorbleed => pype}/action.py | 0 {colorbleed => pype}/api.py | 0 {colorbleed => pype}/fusion/__init__.py | 0 {colorbleed => pype}/fusion/lib.py | 0 {colorbleed => pype}/houdini/__init__.py | 0 {colorbleed => pype}/houdini/lib.py | 0 {colorbleed => pype}/launcher_actions.py | 0 {colorbleed => pype}/lib.py | 0 {colorbleed => pype}/maya/__init__.py | 0 {colorbleed => pype}/maya/action.py | 0 {colorbleed => pype}/maya/customize.py | 0 {colorbleed => pype}/maya/lib.py | 0 {colorbleed => pype}/maya/menu.json | 0 {colorbleed => pype}/maya/menu.py | 0 {colorbleed => pype}/maya/plugin.py | 0 {colorbleed => pype}/plugin.py | 0 {colorbleed => pype}/plugins/fusion/create/create_tiff_saver.py | 0 .../plugins/fusion/inventory/select_containers.py | 0 {colorbleed => pype}/plugins/fusion/inventory/set_tool_color.py | 0 {colorbleed => pype}/plugins/fusion/load/actions.py | 0 {colorbleed => pype}/plugins/fusion/load/load_sequence.py | 0 {colorbleed => pype}/plugins/fusion/publish/collect_comp.py | 0 .../plugins/fusion/publish/collect_fusion_version.py | 0 {colorbleed => pype}/plugins/fusion/publish/collect_instances.py | 0 .../plugins/fusion/publish/collect_render_target.py | 0 .../plugins/fusion/publish/increment_current_file_deadline.py | 0 .../plugins/fusion/publish/publish_image_sequences.py | 0 {colorbleed => pype}/plugins/fusion/publish/render_local.py | 0 {colorbleed => pype}/plugins/fusion/publish/save_scene.py | 0 {colorbleed => pype}/plugins/fusion/publish/submit_deadline.py | 0 .../plugins/fusion/publish/validate_background_depth.py | 0 .../plugins/fusion/publish/validate_comp_saved.py | 0 .../plugins/fusion/publish/validate_create_folder_checked.py | 0 .../plugins/fusion/publish/validate_filename_has_extension.py | 0 .../plugins/fusion/publish/validate_saver_has_input.py | 0 .../plugins/fusion/publish/validate_saver_passthrough.py | 0 .../plugins/fusion/publish/validate_unique_subsets.py | 0 {colorbleed => pype}/plugins/global/load/copy_file.py | 0 {colorbleed => pype}/plugins/global/load/copy_file_path.py | 0 {colorbleed => pype}/plugins/global/load/open_imagesequence.py | 0 {colorbleed => pype}/plugins/global/publish/cleanup.py | 0 .../plugins/global/publish/collect_assumed_destination.py | 0 {colorbleed => pype}/plugins/global/publish/collect_comment.py | 0 .../plugins/global/publish/collect_context_label.py | 0 .../plugins/global/publish/collect_current_shell_file.py | 0 .../plugins/global/publish/collect_deadline_user.py | 0 .../plugins/global/publish/collect_filesequences.py | 0 .../plugins/global/publish/collect_machine_name.py | 0 .../plugins/global/publish/collect_shell_workspace.py | 0 {colorbleed => pype}/plugins/global/publish/collect_time.py | 0 {colorbleed => pype}/plugins/global/publish/integrate.py | 0 {colorbleed => pype}/plugins/global/publish/submit_publish_job.py | 0 .../plugins/global/publish/validate_file_saved.py | 0 {colorbleed => pype}/plugins/global/publish/validate_resources.py | 0 .../plugins/global/publish/validate_sequence_frames.py | 0 .../plugins/houdini/create/create_alembic_camera.py | 0 {colorbleed => pype}/plugins/houdini/create/create_pointcache.py | 0 {colorbleed => pype}/plugins/houdini/create/create_vbd_cache.py | 0 {colorbleed => pype}/plugins/houdini/load/load_alembic.py | 0 {colorbleed => pype}/plugins/houdini/load/load_camera.py | 0 .../plugins/houdini/publish/collect_current_file.py | 0 {colorbleed => pype}/plugins/houdini/publish/collect_frames.py | 0 {colorbleed => pype}/plugins/houdini/publish/collect_instances.py | 0 .../plugins/houdini/publish/collect_output_node.py | 0 .../plugins/houdini/publish/collect_workscene_fps.py | 0 {colorbleed => pype}/plugins/houdini/publish/extract_alembic.py | 0 {colorbleed => pype}/plugins/houdini/publish/extract_vdb_cache.py | 0 .../plugins/houdini/publish/validate_alembic_input_node.py | 0 .../plugins/houdini/publish/validate_animation_settings.py | 0 {colorbleed => pype}/plugins/houdini/publish/validate_bypass.py | 0 .../plugins/houdini/publish/validate_camera_rop.py | 0 .../plugins/houdini/publish/validate_mkpaths_toggled.py | 0 .../plugins/houdini/publish/validate_output_node.py | 0 .../plugins/houdini/publish/validate_primitive_hierarchy_paths.py | 0 .../plugins/houdini/publish/validate_vdb_input_node.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_animation.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_camera.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_fbx.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_look.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_mayaascii.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_model.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_pointcache.py | 0 .../plugins/maya/create/colorbleed_renderglobals.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_rig.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_setdress.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_vrayproxy.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_vrayscene.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_yeti_cache.py | 0 {colorbleed => pype}/plugins/maya/create/colorbleed_yeti_rig.py | 0 {colorbleed => pype}/plugins/maya/load/actions.py | 0 {colorbleed => pype}/plugins/maya/load/load_alembic.py | 0 {colorbleed => pype}/plugins/maya/load/load_camera.py | 0 {colorbleed => pype}/plugins/maya/load/load_fbx.py | 0 {colorbleed => pype}/plugins/maya/load/load_look.py | 0 {colorbleed => pype}/plugins/maya/load/load_mayaascii.py | 0 {colorbleed => pype}/plugins/maya/load/load_model.py | 0 {colorbleed => pype}/plugins/maya/load/load_rig.py | 0 {colorbleed => pype}/plugins/maya/load/load_setdress.py | 0 {colorbleed => pype}/plugins/maya/load/load_vdb_to_redshift.py | 0 {colorbleed => pype}/plugins/maya/load/load_vdb_to_vray.py | 0 {colorbleed => pype}/plugins/maya/load/load_vrayproxy.py | 0 {colorbleed => pype}/plugins/maya/load/load_yeti_cache.py | 0 {colorbleed => pype}/plugins/maya/load/load_yeti_rig.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_animation.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_current_file.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_history.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_instances.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_look.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_maya_units.py | 0 .../plugins/maya/publish/collect_maya_workspace.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_model.py | 0 .../plugins/maya/publish/collect_render_layer_aovs.py | 0 .../plugins/maya/publish/collect_renderable_camera.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_renderlayers.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_setdress.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_vray_scene.py | 0 .../plugins/maya/publish/collect_workscene_fps.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_yeti_cache.py | 0 {colorbleed => pype}/plugins/maya/publish/collect_yeti_rig.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_animation.py | 0 .../plugins/maya/publish/extract_camera_alembic.py | 0 .../plugins/maya/publish/extract_camera_mayaAscii.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_fbx.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_look.py | 0 .../plugins/maya/publish/extract_maya_ascii_raw.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_model.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_pointcache.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_rig.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_setdress.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_vrayproxy.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_yeti_cache.py | 0 {colorbleed => pype}/plugins/maya/publish/extract_yeti_rig.py | 0 .../plugins/maya/publish/increment_current_file_deadline.py | 0 {colorbleed => pype}/plugins/maya/publish/save_scene.py | 0 {colorbleed => pype}/plugins/maya/publish/submit_maya_deadline.py | 0 {colorbleed => pype}/plugins/maya/publish/submit_vray_deadline.py | 0 .../plugins/maya/publish/validate_animation_content.py | 0 .../maya/publish/validate_animation_out_set_related_node_ids.py | 0 .../plugins/maya/publish/validate_camera_attributes.py | 0 .../plugins/maya/publish/validate_camera_contents.py | 0 .../maya/publish/validate_current_renderlayer_renderable.py | 0 .../plugins/maya/publish/validate_deadline_connection.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_frame_range.py | 0 .../plugins/maya/publish/validate_instance_has_members.py | 0 .../plugins/maya/publish/validate_instance_subset.py | 0 .../plugins/maya/publish/validate_instancer_content.py | 0 .../plugins/maya/publish/validate_instancer_frame_ranges.py | 0 .../plugins/maya/publish/validate_joints_hidden.py | 0 .../plugins/maya/publish/validate_look_contents.py | 0 .../maya/publish/validate_look_default_shaders_connections.py | 0 .../plugins/maya/publish/validate_look_id_reference_edits.py | 0 .../plugins/maya/publish/validate_look_members_unique.py | 0 .../plugins/maya/publish/validate_look_no_default_shaders.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_look_sets.py | 0 .../plugins/maya/publish/validate_look_single_shader.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_maya_units.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_mesh_has_uv.py | 0 .../plugins/maya/publish/validate_mesh_lamina_faces.py | 0 .../plugins/maya/publish/validate_mesh_no_negative_scale.py | 0 .../plugins/maya/publish/validate_mesh_non_manifold.py | 0 .../plugins/maya/publish/validate_mesh_non_zero_edge.py | 0 .../plugins/maya/publish/validate_mesh_normals_unlocked.py | 0 .../plugins/maya/publish/validate_mesh_shader_connections.py | 0 .../plugins/maya/publish/validate_mesh_single_uv_set.py | 0 .../plugins/maya/publish/validate_mesh_uv_set_map1.py | 0 .../plugins/maya/publish/validate_mesh_vertices_have_edges.py | 0 .../plugins/maya/publish/validate_model_content.py | 0 .../plugins/maya/publish/validate_no_animation.py | 0 .../plugins/maya/publish/validate_no_default_camera.py | 0 .../plugins/maya/publish/validate_no_namespace.py | 0 .../plugins/maya/publish/validate_no_null_transforms.py | 0 .../plugins/maya/publish/validate_no_unknown_nodes.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_no_vraymesh.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_node_ids.py | 0 .../plugins/maya/publish/validate_node_ids_deformed_shapes.py | 0 .../plugins/maya/publish/validate_node_ids_in_database.py | 0 .../plugins/maya/publish/validate_node_ids_related.py | 0 .../plugins/maya/publish/validate_node_ids_unique.py | 0 .../plugins/maya/publish/validate_node_no_ghosting.py | 0 .../plugins/maya/publish/validate_render_image_rule.py | 0 .../plugins/maya/publish/validate_render_no_default_cameras.py | 0 .../plugins/maya/publish/validate_render_single_camera.py | 0 .../plugins/maya/publish/validate_renderlayer_aovs.py | 0 .../plugins/maya/publish/validate_rendersettings.py | 0 .../plugins/maya/publish/validate_rig_contents.py | 0 .../plugins/maya/publish/validate_rig_controllers.py | 0 .../maya/publish/validate_rig_controllers_arnold_attributes.py | 0 .../plugins/maya/publish/validate_rig_out_set_node_ids.py | 0 .../plugins/maya/publish/validate_scene_set_workspace.py | 0 .../plugins/maya/publish/validate_setdress_namespaces.py | 0 .../plugins/maya/publish/validate_setdress_transforms.py | 0 .../plugins/maya/publish/validate_shape_default_names.py | 0 .../plugins/maya/publish/validate_shape_render_stats.py | 0 .../plugins/maya/publish/validate_single_assembly.py | 0 .../plugins/maya/publish/validate_skinCluster_deformer_set.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_step_size.py | 0 {colorbleed => pype}/plugins/maya/publish/validate_transfers.py | 0 .../plugins/maya/publish/validate_transform_naming_suffix.py | 0 .../plugins/maya/publish/validate_transform_zero.py | 0 .../plugins/maya/publish/validate_vray_distributed_rendering.py | 0 .../plugins/maya/publish/validate_vray_translator_settings.py | 0 .../plugins/maya/publish/validate_vrayproxy_members.py | 0 .../plugins/maya/publish/validate_yeti_renderscript_callbacks.py | 0 .../plugins/maya/publish/validate_yeti_rig_input_in_instance.py | 0 .../plugins/maya/publish/validate_yeti_rig_settings.py | 0 .../plugins/maya/publish/validate_yetirig_cache_state.py | 0 {colorbleed => pype}/scripts/__init__.py | 0 {colorbleed => pype}/scripts/fusion_switch_shot.py | 0 {colorbleed => pype}/scripts/publish_filesequence.py | 0 {colorbleed => pype}/setdress_api.py | 0 {colorbleed => pype}/vendor/__init__.py | 0 {colorbleed => pype}/vendor/pather/__init__.py | 0 {colorbleed => pype}/vendor/pather/core.py | 0 {colorbleed => pype}/vendor/pather/error.py | 0 {colorbleed => pype}/vendor/pather/version.py | 0 {colorbleed => pype}/widgets/__init__.py | 0 {colorbleed => pype}/widgets/popup.py | 0 218 files changed, 0 insertions(+), 0 deletions(-) rename {colorbleed => pype}/__init__.py (100%) rename {colorbleed => pype}/action.py (100%) rename {colorbleed => pype}/api.py (100%) rename {colorbleed => pype}/fusion/__init__.py (100%) rename {colorbleed => pype}/fusion/lib.py (100%) rename {colorbleed => pype}/houdini/__init__.py (100%) rename {colorbleed => pype}/houdini/lib.py (100%) rename {colorbleed => pype}/launcher_actions.py (100%) rename {colorbleed => pype}/lib.py (100%) rename {colorbleed => pype}/maya/__init__.py (100%) rename {colorbleed => pype}/maya/action.py (100%) rename {colorbleed => pype}/maya/customize.py (100%) rename {colorbleed => pype}/maya/lib.py (100%) rename {colorbleed => pype}/maya/menu.json (100%) rename {colorbleed => pype}/maya/menu.py (100%) rename {colorbleed => pype}/maya/plugin.py (100%) rename {colorbleed => pype}/plugin.py (100%) rename {colorbleed => pype}/plugins/fusion/create/create_tiff_saver.py (100%) rename {colorbleed => pype}/plugins/fusion/inventory/select_containers.py (100%) rename {colorbleed => pype}/plugins/fusion/inventory/set_tool_color.py (100%) rename {colorbleed => pype}/plugins/fusion/load/actions.py (100%) rename {colorbleed => pype}/plugins/fusion/load/load_sequence.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/collect_comp.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/collect_fusion_version.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/collect_instances.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/collect_render_target.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/increment_current_file_deadline.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/publish_image_sequences.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/render_local.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/save_scene.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/submit_deadline.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_background_depth.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_comp_saved.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_create_folder_checked.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_filename_has_extension.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_saver_has_input.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_saver_passthrough.py (100%) rename {colorbleed => pype}/plugins/fusion/publish/validate_unique_subsets.py (100%) rename {colorbleed => pype}/plugins/global/load/copy_file.py (100%) rename {colorbleed => pype}/plugins/global/load/copy_file_path.py (100%) rename {colorbleed => pype}/plugins/global/load/open_imagesequence.py (100%) rename {colorbleed => pype}/plugins/global/publish/cleanup.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_assumed_destination.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_comment.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_context_label.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_current_shell_file.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_deadline_user.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_filesequences.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_machine_name.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_shell_workspace.py (100%) rename {colorbleed => pype}/plugins/global/publish/collect_time.py (100%) rename {colorbleed => pype}/plugins/global/publish/integrate.py (100%) rename {colorbleed => pype}/plugins/global/publish/submit_publish_job.py (100%) rename {colorbleed => pype}/plugins/global/publish/validate_file_saved.py (100%) rename {colorbleed => pype}/plugins/global/publish/validate_resources.py (100%) rename {colorbleed => pype}/plugins/global/publish/validate_sequence_frames.py (100%) rename {colorbleed => pype}/plugins/houdini/create/create_alembic_camera.py (100%) rename {colorbleed => pype}/plugins/houdini/create/create_pointcache.py (100%) rename {colorbleed => pype}/plugins/houdini/create/create_vbd_cache.py (100%) rename {colorbleed => pype}/plugins/houdini/load/load_alembic.py (100%) rename {colorbleed => pype}/plugins/houdini/load/load_camera.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/collect_current_file.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/collect_frames.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/collect_instances.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/collect_output_node.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/collect_workscene_fps.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/extract_alembic.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/extract_vdb_cache.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_alembic_input_node.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_animation_settings.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_bypass.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_camera_rop.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_mkpaths_toggled.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_output_node.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_primitive_hierarchy_paths.py (100%) rename {colorbleed => pype}/plugins/houdini/publish/validate_vdb_input_node.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_animation.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_camera.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_fbx.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_look.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_mayaascii.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_model.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_pointcache.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_renderglobals.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_rig.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_setdress.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_vrayproxy.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_vrayscene.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_yeti_cache.py (100%) rename {colorbleed => pype}/plugins/maya/create/colorbleed_yeti_rig.py (100%) rename {colorbleed => pype}/plugins/maya/load/actions.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_alembic.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_camera.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_fbx.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_look.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_mayaascii.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_model.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_rig.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_setdress.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_vdb_to_redshift.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_vdb_to_vray.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_vrayproxy.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_yeti_cache.py (100%) rename {colorbleed => pype}/plugins/maya/load/load_yeti_rig.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_animation.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_current_file.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_history.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_instances.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_look.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_maya_units.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_maya_workspace.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_model.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_render_layer_aovs.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_renderable_camera.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_renderlayers.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_setdress.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_vray_scene.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_workscene_fps.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_yeti_cache.py (100%) rename {colorbleed => pype}/plugins/maya/publish/collect_yeti_rig.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_animation.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_camera_alembic.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_camera_mayaAscii.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_fbx.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_look.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_maya_ascii_raw.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_model.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_pointcache.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_rig.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_setdress.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_vrayproxy.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_yeti_cache.py (100%) rename {colorbleed => pype}/plugins/maya/publish/extract_yeti_rig.py (100%) rename {colorbleed => pype}/plugins/maya/publish/increment_current_file_deadline.py (100%) rename {colorbleed => pype}/plugins/maya/publish/save_scene.py (100%) rename {colorbleed => pype}/plugins/maya/publish/submit_maya_deadline.py (100%) rename {colorbleed => pype}/plugins/maya/publish/submit_vray_deadline.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_animation_content.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_animation_out_set_related_node_ids.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_camera_attributes.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_camera_contents.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_current_renderlayer_renderable.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_deadline_connection.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_frame_range.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_instance_has_members.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_instance_subset.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_instancer_content.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_instancer_frame_ranges.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_joints_hidden.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_contents.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_default_shaders_connections.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_id_reference_edits.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_members_unique.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_no_default_shaders.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_sets.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_look_single_shader.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_maya_units.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_has_uv.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_lamina_faces.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_no_negative_scale.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_non_manifold.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_non_zero_edge.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_normals_unlocked.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_shader_connections.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_single_uv_set.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_uv_set_map1.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_mesh_vertices_have_edges.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_model_content.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_animation.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_default_camera.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_namespace.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_null_transforms.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_unknown_nodes.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_no_vraymesh.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_ids.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_ids_deformed_shapes.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_ids_in_database.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_ids_related.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_ids_unique.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_node_no_ghosting.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_render_image_rule.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_render_no_default_cameras.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_render_single_camera.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_renderlayer_aovs.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_rendersettings.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_rig_contents.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_rig_controllers.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_rig_out_set_node_ids.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_scene_set_workspace.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_setdress_namespaces.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_setdress_transforms.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_shape_default_names.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_shape_render_stats.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_single_assembly.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_skinCluster_deformer_set.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_step_size.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_transfers.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_transform_naming_suffix.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_transform_zero.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_vray_distributed_rendering.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_vray_translator_settings.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_vrayproxy_members.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_yeti_renderscript_callbacks.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_yeti_rig_input_in_instance.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_yeti_rig_settings.py (100%) rename {colorbleed => pype}/plugins/maya/publish/validate_yetirig_cache_state.py (100%) rename {colorbleed => pype}/scripts/__init__.py (100%) rename {colorbleed => pype}/scripts/fusion_switch_shot.py (100%) rename {colorbleed => pype}/scripts/publish_filesequence.py (100%) rename {colorbleed => pype}/setdress_api.py (100%) rename {colorbleed => pype}/vendor/__init__.py (100%) rename {colorbleed => pype}/vendor/pather/__init__.py (100%) rename {colorbleed => pype}/vendor/pather/core.py (100%) rename {colorbleed => pype}/vendor/pather/error.py (100%) rename {colorbleed => pype}/vendor/pather/version.py (100%) rename {colorbleed => pype}/widgets/__init__.py (100%) rename {colorbleed => pype}/widgets/popup.py (100%) diff --git a/colorbleed/__init__.py b/pype/__init__.py similarity index 100% rename from colorbleed/__init__.py rename to pype/__init__.py diff --git a/colorbleed/action.py b/pype/action.py similarity index 100% rename from colorbleed/action.py rename to pype/action.py diff --git a/colorbleed/api.py b/pype/api.py similarity index 100% rename from colorbleed/api.py rename to pype/api.py diff --git a/colorbleed/fusion/__init__.py b/pype/fusion/__init__.py similarity index 100% rename from colorbleed/fusion/__init__.py rename to pype/fusion/__init__.py diff --git a/colorbleed/fusion/lib.py b/pype/fusion/lib.py similarity index 100% rename from colorbleed/fusion/lib.py rename to pype/fusion/lib.py diff --git a/colorbleed/houdini/__init__.py b/pype/houdini/__init__.py similarity index 100% rename from colorbleed/houdini/__init__.py rename to pype/houdini/__init__.py diff --git a/colorbleed/houdini/lib.py b/pype/houdini/lib.py similarity index 100% rename from colorbleed/houdini/lib.py rename to pype/houdini/lib.py diff --git a/colorbleed/launcher_actions.py b/pype/launcher_actions.py similarity index 100% rename from colorbleed/launcher_actions.py rename to pype/launcher_actions.py diff --git a/colorbleed/lib.py b/pype/lib.py similarity index 100% rename from colorbleed/lib.py rename to pype/lib.py diff --git a/colorbleed/maya/__init__.py b/pype/maya/__init__.py similarity index 100% rename from colorbleed/maya/__init__.py rename to pype/maya/__init__.py diff --git a/colorbleed/maya/action.py b/pype/maya/action.py similarity index 100% rename from colorbleed/maya/action.py rename to pype/maya/action.py diff --git a/colorbleed/maya/customize.py b/pype/maya/customize.py similarity index 100% rename from colorbleed/maya/customize.py rename to pype/maya/customize.py diff --git a/colorbleed/maya/lib.py b/pype/maya/lib.py similarity index 100% rename from colorbleed/maya/lib.py rename to pype/maya/lib.py diff --git a/colorbleed/maya/menu.json b/pype/maya/menu.json similarity index 100% rename from colorbleed/maya/menu.json rename to pype/maya/menu.json diff --git a/colorbleed/maya/menu.py b/pype/maya/menu.py similarity index 100% rename from colorbleed/maya/menu.py rename to pype/maya/menu.py diff --git a/colorbleed/maya/plugin.py b/pype/maya/plugin.py similarity index 100% rename from colorbleed/maya/plugin.py rename to pype/maya/plugin.py diff --git a/colorbleed/plugin.py b/pype/plugin.py similarity index 100% rename from colorbleed/plugin.py rename to pype/plugin.py diff --git a/colorbleed/plugins/fusion/create/create_tiff_saver.py b/pype/plugins/fusion/create/create_tiff_saver.py similarity index 100% rename from colorbleed/plugins/fusion/create/create_tiff_saver.py rename to pype/plugins/fusion/create/create_tiff_saver.py diff --git a/colorbleed/plugins/fusion/inventory/select_containers.py b/pype/plugins/fusion/inventory/select_containers.py similarity index 100% rename from colorbleed/plugins/fusion/inventory/select_containers.py rename to pype/plugins/fusion/inventory/select_containers.py diff --git a/colorbleed/plugins/fusion/inventory/set_tool_color.py b/pype/plugins/fusion/inventory/set_tool_color.py similarity index 100% rename from colorbleed/plugins/fusion/inventory/set_tool_color.py rename to pype/plugins/fusion/inventory/set_tool_color.py diff --git a/colorbleed/plugins/fusion/load/actions.py b/pype/plugins/fusion/load/actions.py similarity index 100% rename from colorbleed/plugins/fusion/load/actions.py rename to pype/plugins/fusion/load/actions.py diff --git a/colorbleed/plugins/fusion/load/load_sequence.py b/pype/plugins/fusion/load/load_sequence.py similarity index 100% rename from colorbleed/plugins/fusion/load/load_sequence.py rename to pype/plugins/fusion/load/load_sequence.py diff --git a/colorbleed/plugins/fusion/publish/collect_comp.py b/pype/plugins/fusion/publish/collect_comp.py similarity index 100% rename from colorbleed/plugins/fusion/publish/collect_comp.py rename to pype/plugins/fusion/publish/collect_comp.py diff --git a/colorbleed/plugins/fusion/publish/collect_fusion_version.py b/pype/plugins/fusion/publish/collect_fusion_version.py similarity index 100% rename from colorbleed/plugins/fusion/publish/collect_fusion_version.py rename to pype/plugins/fusion/publish/collect_fusion_version.py diff --git a/colorbleed/plugins/fusion/publish/collect_instances.py b/pype/plugins/fusion/publish/collect_instances.py similarity index 100% rename from colorbleed/plugins/fusion/publish/collect_instances.py rename to pype/plugins/fusion/publish/collect_instances.py diff --git a/colorbleed/plugins/fusion/publish/collect_render_target.py b/pype/plugins/fusion/publish/collect_render_target.py similarity index 100% rename from colorbleed/plugins/fusion/publish/collect_render_target.py rename to pype/plugins/fusion/publish/collect_render_target.py diff --git a/colorbleed/plugins/fusion/publish/increment_current_file_deadline.py b/pype/plugins/fusion/publish/increment_current_file_deadline.py similarity index 100% rename from colorbleed/plugins/fusion/publish/increment_current_file_deadline.py rename to pype/plugins/fusion/publish/increment_current_file_deadline.py diff --git a/colorbleed/plugins/fusion/publish/publish_image_sequences.py b/pype/plugins/fusion/publish/publish_image_sequences.py similarity index 100% rename from colorbleed/plugins/fusion/publish/publish_image_sequences.py rename to pype/plugins/fusion/publish/publish_image_sequences.py diff --git a/colorbleed/plugins/fusion/publish/render_local.py b/pype/plugins/fusion/publish/render_local.py similarity index 100% rename from colorbleed/plugins/fusion/publish/render_local.py rename to pype/plugins/fusion/publish/render_local.py diff --git a/colorbleed/plugins/fusion/publish/save_scene.py b/pype/plugins/fusion/publish/save_scene.py similarity index 100% rename from colorbleed/plugins/fusion/publish/save_scene.py rename to pype/plugins/fusion/publish/save_scene.py diff --git a/colorbleed/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py similarity index 100% rename from colorbleed/plugins/fusion/publish/submit_deadline.py rename to pype/plugins/fusion/publish/submit_deadline.py diff --git a/colorbleed/plugins/fusion/publish/validate_background_depth.py b/pype/plugins/fusion/publish/validate_background_depth.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_background_depth.py rename to pype/plugins/fusion/publish/validate_background_depth.py diff --git a/colorbleed/plugins/fusion/publish/validate_comp_saved.py b/pype/plugins/fusion/publish/validate_comp_saved.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_comp_saved.py rename to pype/plugins/fusion/publish/validate_comp_saved.py diff --git a/colorbleed/plugins/fusion/publish/validate_create_folder_checked.py b/pype/plugins/fusion/publish/validate_create_folder_checked.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_create_folder_checked.py rename to pype/plugins/fusion/publish/validate_create_folder_checked.py diff --git a/colorbleed/plugins/fusion/publish/validate_filename_has_extension.py b/pype/plugins/fusion/publish/validate_filename_has_extension.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_filename_has_extension.py rename to pype/plugins/fusion/publish/validate_filename_has_extension.py diff --git a/colorbleed/plugins/fusion/publish/validate_saver_has_input.py b/pype/plugins/fusion/publish/validate_saver_has_input.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_saver_has_input.py rename to pype/plugins/fusion/publish/validate_saver_has_input.py diff --git a/colorbleed/plugins/fusion/publish/validate_saver_passthrough.py b/pype/plugins/fusion/publish/validate_saver_passthrough.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_saver_passthrough.py rename to pype/plugins/fusion/publish/validate_saver_passthrough.py diff --git a/colorbleed/plugins/fusion/publish/validate_unique_subsets.py b/pype/plugins/fusion/publish/validate_unique_subsets.py similarity index 100% rename from colorbleed/plugins/fusion/publish/validate_unique_subsets.py rename to pype/plugins/fusion/publish/validate_unique_subsets.py diff --git a/colorbleed/plugins/global/load/copy_file.py b/pype/plugins/global/load/copy_file.py similarity index 100% rename from colorbleed/plugins/global/load/copy_file.py rename to pype/plugins/global/load/copy_file.py diff --git a/colorbleed/plugins/global/load/copy_file_path.py b/pype/plugins/global/load/copy_file_path.py similarity index 100% rename from colorbleed/plugins/global/load/copy_file_path.py rename to pype/plugins/global/load/copy_file_path.py diff --git a/colorbleed/plugins/global/load/open_imagesequence.py b/pype/plugins/global/load/open_imagesequence.py similarity index 100% rename from colorbleed/plugins/global/load/open_imagesequence.py rename to pype/plugins/global/load/open_imagesequence.py diff --git a/colorbleed/plugins/global/publish/cleanup.py b/pype/plugins/global/publish/cleanup.py similarity index 100% rename from colorbleed/plugins/global/publish/cleanup.py rename to pype/plugins/global/publish/cleanup.py diff --git a/colorbleed/plugins/global/publish/collect_assumed_destination.py b/pype/plugins/global/publish/collect_assumed_destination.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_assumed_destination.py rename to pype/plugins/global/publish/collect_assumed_destination.py diff --git a/colorbleed/plugins/global/publish/collect_comment.py b/pype/plugins/global/publish/collect_comment.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_comment.py rename to pype/plugins/global/publish/collect_comment.py diff --git a/colorbleed/plugins/global/publish/collect_context_label.py b/pype/plugins/global/publish/collect_context_label.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_context_label.py rename to pype/plugins/global/publish/collect_context_label.py diff --git a/colorbleed/plugins/global/publish/collect_current_shell_file.py b/pype/plugins/global/publish/collect_current_shell_file.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_current_shell_file.py rename to pype/plugins/global/publish/collect_current_shell_file.py diff --git a/colorbleed/plugins/global/publish/collect_deadline_user.py b/pype/plugins/global/publish/collect_deadline_user.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_deadline_user.py rename to pype/plugins/global/publish/collect_deadline_user.py diff --git a/colorbleed/plugins/global/publish/collect_filesequences.py b/pype/plugins/global/publish/collect_filesequences.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_filesequences.py rename to pype/plugins/global/publish/collect_filesequences.py diff --git a/colorbleed/plugins/global/publish/collect_machine_name.py b/pype/plugins/global/publish/collect_machine_name.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_machine_name.py rename to pype/plugins/global/publish/collect_machine_name.py diff --git a/colorbleed/plugins/global/publish/collect_shell_workspace.py b/pype/plugins/global/publish/collect_shell_workspace.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_shell_workspace.py rename to pype/plugins/global/publish/collect_shell_workspace.py diff --git a/colorbleed/plugins/global/publish/collect_time.py b/pype/plugins/global/publish/collect_time.py similarity index 100% rename from colorbleed/plugins/global/publish/collect_time.py rename to pype/plugins/global/publish/collect_time.py diff --git a/colorbleed/plugins/global/publish/integrate.py b/pype/plugins/global/publish/integrate.py similarity index 100% rename from colorbleed/plugins/global/publish/integrate.py rename to pype/plugins/global/publish/integrate.py diff --git a/colorbleed/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py similarity index 100% rename from colorbleed/plugins/global/publish/submit_publish_job.py rename to pype/plugins/global/publish/submit_publish_job.py diff --git a/colorbleed/plugins/global/publish/validate_file_saved.py b/pype/plugins/global/publish/validate_file_saved.py similarity index 100% rename from colorbleed/plugins/global/publish/validate_file_saved.py rename to pype/plugins/global/publish/validate_file_saved.py diff --git a/colorbleed/plugins/global/publish/validate_resources.py b/pype/plugins/global/publish/validate_resources.py similarity index 100% rename from colorbleed/plugins/global/publish/validate_resources.py rename to pype/plugins/global/publish/validate_resources.py diff --git a/colorbleed/plugins/global/publish/validate_sequence_frames.py b/pype/plugins/global/publish/validate_sequence_frames.py similarity index 100% rename from colorbleed/plugins/global/publish/validate_sequence_frames.py rename to pype/plugins/global/publish/validate_sequence_frames.py diff --git a/colorbleed/plugins/houdini/create/create_alembic_camera.py b/pype/plugins/houdini/create/create_alembic_camera.py similarity index 100% rename from colorbleed/plugins/houdini/create/create_alembic_camera.py rename to pype/plugins/houdini/create/create_alembic_camera.py diff --git a/colorbleed/plugins/houdini/create/create_pointcache.py b/pype/plugins/houdini/create/create_pointcache.py similarity index 100% rename from colorbleed/plugins/houdini/create/create_pointcache.py rename to pype/plugins/houdini/create/create_pointcache.py diff --git a/colorbleed/plugins/houdini/create/create_vbd_cache.py b/pype/plugins/houdini/create/create_vbd_cache.py similarity index 100% rename from colorbleed/plugins/houdini/create/create_vbd_cache.py rename to pype/plugins/houdini/create/create_vbd_cache.py diff --git a/colorbleed/plugins/houdini/load/load_alembic.py b/pype/plugins/houdini/load/load_alembic.py similarity index 100% rename from colorbleed/plugins/houdini/load/load_alembic.py rename to pype/plugins/houdini/load/load_alembic.py diff --git a/colorbleed/plugins/houdini/load/load_camera.py b/pype/plugins/houdini/load/load_camera.py similarity index 100% rename from colorbleed/plugins/houdini/load/load_camera.py rename to pype/plugins/houdini/load/load_camera.py diff --git a/colorbleed/plugins/houdini/publish/collect_current_file.py b/pype/plugins/houdini/publish/collect_current_file.py similarity index 100% rename from colorbleed/plugins/houdini/publish/collect_current_file.py rename to pype/plugins/houdini/publish/collect_current_file.py diff --git a/colorbleed/plugins/houdini/publish/collect_frames.py b/pype/plugins/houdini/publish/collect_frames.py similarity index 100% rename from colorbleed/plugins/houdini/publish/collect_frames.py rename to pype/plugins/houdini/publish/collect_frames.py diff --git a/colorbleed/plugins/houdini/publish/collect_instances.py b/pype/plugins/houdini/publish/collect_instances.py similarity index 100% rename from colorbleed/plugins/houdini/publish/collect_instances.py rename to pype/plugins/houdini/publish/collect_instances.py diff --git a/colorbleed/plugins/houdini/publish/collect_output_node.py b/pype/plugins/houdini/publish/collect_output_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/collect_output_node.py rename to pype/plugins/houdini/publish/collect_output_node.py diff --git a/colorbleed/plugins/houdini/publish/collect_workscene_fps.py b/pype/plugins/houdini/publish/collect_workscene_fps.py similarity index 100% rename from colorbleed/plugins/houdini/publish/collect_workscene_fps.py rename to pype/plugins/houdini/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/houdini/publish/extract_alembic.py b/pype/plugins/houdini/publish/extract_alembic.py similarity index 100% rename from colorbleed/plugins/houdini/publish/extract_alembic.py rename to pype/plugins/houdini/publish/extract_alembic.py diff --git a/colorbleed/plugins/houdini/publish/extract_vdb_cache.py b/pype/plugins/houdini/publish/extract_vdb_cache.py similarity index 100% rename from colorbleed/plugins/houdini/publish/extract_vdb_cache.py rename to pype/plugins/houdini/publish/extract_vdb_cache.py diff --git a/colorbleed/plugins/houdini/publish/validate_alembic_input_node.py b/pype/plugins/houdini/publish/validate_alembic_input_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_alembic_input_node.py rename to pype/plugins/houdini/publish/validate_alembic_input_node.py diff --git a/colorbleed/plugins/houdini/publish/validate_animation_settings.py b/pype/plugins/houdini/publish/validate_animation_settings.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_animation_settings.py rename to pype/plugins/houdini/publish/validate_animation_settings.py diff --git a/colorbleed/plugins/houdini/publish/validate_bypass.py b/pype/plugins/houdini/publish/validate_bypass.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_bypass.py rename to pype/plugins/houdini/publish/validate_bypass.py diff --git a/colorbleed/plugins/houdini/publish/validate_camera_rop.py b/pype/plugins/houdini/publish/validate_camera_rop.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_camera_rop.py rename to pype/plugins/houdini/publish/validate_camera_rop.py diff --git a/colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py b/pype/plugins/houdini/publish/validate_mkpaths_toggled.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_mkpaths_toggled.py rename to pype/plugins/houdini/publish/validate_mkpaths_toggled.py diff --git a/colorbleed/plugins/houdini/publish/validate_output_node.py b/pype/plugins/houdini/publish/validate_output_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_output_node.py rename to pype/plugins/houdini/publish/validate_output_node.py diff --git a/colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_primitive_hierarchy_paths.py rename to pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py diff --git a/colorbleed/plugins/houdini/publish/validate_vdb_input_node.py b/pype/plugins/houdini/publish/validate_vdb_input_node.py similarity index 100% rename from colorbleed/plugins/houdini/publish/validate_vdb_input_node.py rename to pype/plugins/houdini/publish/validate_vdb_input_node.py diff --git a/colorbleed/plugins/maya/create/colorbleed_animation.py b/pype/plugins/maya/create/colorbleed_animation.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_animation.py rename to pype/plugins/maya/create/colorbleed_animation.py diff --git a/colorbleed/plugins/maya/create/colorbleed_camera.py b/pype/plugins/maya/create/colorbleed_camera.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_camera.py rename to pype/plugins/maya/create/colorbleed_camera.py diff --git a/colorbleed/plugins/maya/create/colorbleed_fbx.py b/pype/plugins/maya/create/colorbleed_fbx.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_fbx.py rename to pype/plugins/maya/create/colorbleed_fbx.py diff --git a/colorbleed/plugins/maya/create/colorbleed_look.py b/pype/plugins/maya/create/colorbleed_look.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_look.py rename to pype/plugins/maya/create/colorbleed_look.py diff --git a/colorbleed/plugins/maya/create/colorbleed_mayaascii.py b/pype/plugins/maya/create/colorbleed_mayaascii.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_mayaascii.py rename to pype/plugins/maya/create/colorbleed_mayaascii.py diff --git a/colorbleed/plugins/maya/create/colorbleed_model.py b/pype/plugins/maya/create/colorbleed_model.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_model.py rename to pype/plugins/maya/create/colorbleed_model.py diff --git a/colorbleed/plugins/maya/create/colorbleed_pointcache.py b/pype/plugins/maya/create/colorbleed_pointcache.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_pointcache.py rename to pype/plugins/maya/create/colorbleed_pointcache.py diff --git a/colorbleed/plugins/maya/create/colorbleed_renderglobals.py b/pype/plugins/maya/create/colorbleed_renderglobals.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_renderglobals.py rename to pype/plugins/maya/create/colorbleed_renderglobals.py diff --git a/colorbleed/plugins/maya/create/colorbleed_rig.py b/pype/plugins/maya/create/colorbleed_rig.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_rig.py rename to pype/plugins/maya/create/colorbleed_rig.py diff --git a/colorbleed/plugins/maya/create/colorbleed_setdress.py b/pype/plugins/maya/create/colorbleed_setdress.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_setdress.py rename to pype/plugins/maya/create/colorbleed_setdress.py diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayproxy.py b/pype/plugins/maya/create/colorbleed_vrayproxy.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_vrayproxy.py rename to pype/plugins/maya/create/colorbleed_vrayproxy.py diff --git a/colorbleed/plugins/maya/create/colorbleed_vrayscene.py b/pype/plugins/maya/create/colorbleed_vrayscene.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_vrayscene.py rename to pype/plugins/maya/create/colorbleed_vrayscene.py diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_cache.py b/pype/plugins/maya/create/colorbleed_yeti_cache.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_yeti_cache.py rename to pype/plugins/maya/create/colorbleed_yeti_cache.py diff --git a/colorbleed/plugins/maya/create/colorbleed_yeti_rig.py b/pype/plugins/maya/create/colorbleed_yeti_rig.py similarity index 100% rename from colorbleed/plugins/maya/create/colorbleed_yeti_rig.py rename to pype/plugins/maya/create/colorbleed_yeti_rig.py diff --git a/colorbleed/plugins/maya/load/actions.py b/pype/plugins/maya/load/actions.py similarity index 100% rename from colorbleed/plugins/maya/load/actions.py rename to pype/plugins/maya/load/actions.py diff --git a/colorbleed/plugins/maya/load/load_alembic.py b/pype/plugins/maya/load/load_alembic.py similarity index 100% rename from colorbleed/plugins/maya/load/load_alembic.py rename to pype/plugins/maya/load/load_alembic.py diff --git a/colorbleed/plugins/maya/load/load_camera.py b/pype/plugins/maya/load/load_camera.py similarity index 100% rename from colorbleed/plugins/maya/load/load_camera.py rename to pype/plugins/maya/load/load_camera.py diff --git a/colorbleed/plugins/maya/load/load_fbx.py b/pype/plugins/maya/load/load_fbx.py similarity index 100% rename from colorbleed/plugins/maya/load/load_fbx.py rename to pype/plugins/maya/load/load_fbx.py diff --git a/colorbleed/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py similarity index 100% rename from colorbleed/plugins/maya/load/load_look.py rename to pype/plugins/maya/load/load_look.py diff --git a/colorbleed/plugins/maya/load/load_mayaascii.py b/pype/plugins/maya/load/load_mayaascii.py similarity index 100% rename from colorbleed/plugins/maya/load/load_mayaascii.py rename to pype/plugins/maya/load/load_mayaascii.py diff --git a/colorbleed/plugins/maya/load/load_model.py b/pype/plugins/maya/load/load_model.py similarity index 100% rename from colorbleed/plugins/maya/load/load_model.py rename to pype/plugins/maya/load/load_model.py diff --git a/colorbleed/plugins/maya/load/load_rig.py b/pype/plugins/maya/load/load_rig.py similarity index 100% rename from colorbleed/plugins/maya/load/load_rig.py rename to pype/plugins/maya/load/load_rig.py diff --git a/colorbleed/plugins/maya/load/load_setdress.py b/pype/plugins/maya/load/load_setdress.py similarity index 100% rename from colorbleed/plugins/maya/load/load_setdress.py rename to pype/plugins/maya/load/load_setdress.py diff --git a/colorbleed/plugins/maya/load/load_vdb_to_redshift.py b/pype/plugins/maya/load/load_vdb_to_redshift.py similarity index 100% rename from colorbleed/plugins/maya/load/load_vdb_to_redshift.py rename to pype/plugins/maya/load/load_vdb_to_redshift.py diff --git a/colorbleed/plugins/maya/load/load_vdb_to_vray.py b/pype/plugins/maya/load/load_vdb_to_vray.py similarity index 100% rename from colorbleed/plugins/maya/load/load_vdb_to_vray.py rename to pype/plugins/maya/load/load_vdb_to_vray.py diff --git a/colorbleed/plugins/maya/load/load_vrayproxy.py b/pype/plugins/maya/load/load_vrayproxy.py similarity index 100% rename from colorbleed/plugins/maya/load/load_vrayproxy.py rename to pype/plugins/maya/load/load_vrayproxy.py diff --git a/colorbleed/plugins/maya/load/load_yeti_cache.py b/pype/plugins/maya/load/load_yeti_cache.py similarity index 100% rename from colorbleed/plugins/maya/load/load_yeti_cache.py rename to pype/plugins/maya/load/load_yeti_cache.py diff --git a/colorbleed/plugins/maya/load/load_yeti_rig.py b/pype/plugins/maya/load/load_yeti_rig.py similarity index 100% rename from colorbleed/plugins/maya/load/load_yeti_rig.py rename to pype/plugins/maya/load/load_yeti_rig.py diff --git a/colorbleed/plugins/maya/publish/collect_animation.py b/pype/plugins/maya/publish/collect_animation.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_animation.py rename to pype/plugins/maya/publish/collect_animation.py diff --git a/colorbleed/plugins/maya/publish/collect_current_file.py b/pype/plugins/maya/publish/collect_current_file.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_current_file.py rename to pype/plugins/maya/publish/collect_current_file.py diff --git a/colorbleed/plugins/maya/publish/collect_history.py b/pype/plugins/maya/publish/collect_history.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_history.py rename to pype/plugins/maya/publish/collect_history.py diff --git a/colorbleed/plugins/maya/publish/collect_instances.py b/pype/plugins/maya/publish/collect_instances.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_instances.py rename to pype/plugins/maya/publish/collect_instances.py diff --git a/colorbleed/plugins/maya/publish/collect_look.py b/pype/plugins/maya/publish/collect_look.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_look.py rename to pype/plugins/maya/publish/collect_look.py diff --git a/colorbleed/plugins/maya/publish/collect_maya_units.py b/pype/plugins/maya/publish/collect_maya_units.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_maya_units.py rename to pype/plugins/maya/publish/collect_maya_units.py diff --git a/colorbleed/plugins/maya/publish/collect_maya_workspace.py b/pype/plugins/maya/publish/collect_maya_workspace.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_maya_workspace.py rename to pype/plugins/maya/publish/collect_maya_workspace.py diff --git a/colorbleed/plugins/maya/publish/collect_model.py b/pype/plugins/maya/publish/collect_model.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_model.py rename to pype/plugins/maya/publish/collect_model.py diff --git a/colorbleed/plugins/maya/publish/collect_render_layer_aovs.py b/pype/plugins/maya/publish/collect_render_layer_aovs.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_render_layer_aovs.py rename to pype/plugins/maya/publish/collect_render_layer_aovs.py diff --git a/colorbleed/plugins/maya/publish/collect_renderable_camera.py b/pype/plugins/maya/publish/collect_renderable_camera.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_renderable_camera.py rename to pype/plugins/maya/publish/collect_renderable_camera.py diff --git a/colorbleed/plugins/maya/publish/collect_renderlayers.py b/pype/plugins/maya/publish/collect_renderlayers.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_renderlayers.py rename to pype/plugins/maya/publish/collect_renderlayers.py diff --git a/colorbleed/plugins/maya/publish/collect_setdress.py b/pype/plugins/maya/publish/collect_setdress.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_setdress.py rename to pype/plugins/maya/publish/collect_setdress.py diff --git a/colorbleed/plugins/maya/publish/collect_vray_scene.py b/pype/plugins/maya/publish/collect_vray_scene.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_vray_scene.py rename to pype/plugins/maya/publish/collect_vray_scene.py diff --git a/colorbleed/plugins/maya/publish/collect_workscene_fps.py b/pype/plugins/maya/publish/collect_workscene_fps.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_workscene_fps.py rename to pype/plugins/maya/publish/collect_workscene_fps.py diff --git a/colorbleed/plugins/maya/publish/collect_yeti_cache.py b/pype/plugins/maya/publish/collect_yeti_cache.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_yeti_cache.py rename to pype/plugins/maya/publish/collect_yeti_cache.py diff --git a/colorbleed/plugins/maya/publish/collect_yeti_rig.py b/pype/plugins/maya/publish/collect_yeti_rig.py similarity index 100% rename from colorbleed/plugins/maya/publish/collect_yeti_rig.py rename to pype/plugins/maya/publish/collect_yeti_rig.py diff --git a/colorbleed/plugins/maya/publish/extract_animation.py b/pype/plugins/maya/publish/extract_animation.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_animation.py rename to pype/plugins/maya/publish/extract_animation.py diff --git a/colorbleed/plugins/maya/publish/extract_camera_alembic.py b/pype/plugins/maya/publish/extract_camera_alembic.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_camera_alembic.py rename to pype/plugins/maya/publish/extract_camera_alembic.py diff --git a/colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py b/pype/plugins/maya/publish/extract_camera_mayaAscii.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_camera_mayaAscii.py rename to pype/plugins/maya/publish/extract_camera_mayaAscii.py diff --git a/colorbleed/plugins/maya/publish/extract_fbx.py b/pype/plugins/maya/publish/extract_fbx.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_fbx.py rename to pype/plugins/maya/publish/extract_fbx.py diff --git a/colorbleed/plugins/maya/publish/extract_look.py b/pype/plugins/maya/publish/extract_look.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_look.py rename to pype/plugins/maya/publish/extract_look.py diff --git a/colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py b/pype/plugins/maya/publish/extract_maya_ascii_raw.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_maya_ascii_raw.py rename to pype/plugins/maya/publish/extract_maya_ascii_raw.py diff --git a/colorbleed/plugins/maya/publish/extract_model.py b/pype/plugins/maya/publish/extract_model.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_model.py rename to pype/plugins/maya/publish/extract_model.py diff --git a/colorbleed/plugins/maya/publish/extract_pointcache.py b/pype/plugins/maya/publish/extract_pointcache.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_pointcache.py rename to pype/plugins/maya/publish/extract_pointcache.py diff --git a/colorbleed/plugins/maya/publish/extract_rig.py b/pype/plugins/maya/publish/extract_rig.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_rig.py rename to pype/plugins/maya/publish/extract_rig.py diff --git a/colorbleed/plugins/maya/publish/extract_setdress.py b/pype/plugins/maya/publish/extract_setdress.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_setdress.py rename to pype/plugins/maya/publish/extract_setdress.py diff --git a/colorbleed/plugins/maya/publish/extract_vrayproxy.py b/pype/plugins/maya/publish/extract_vrayproxy.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_vrayproxy.py rename to pype/plugins/maya/publish/extract_vrayproxy.py diff --git a/colorbleed/plugins/maya/publish/extract_yeti_cache.py b/pype/plugins/maya/publish/extract_yeti_cache.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_yeti_cache.py rename to pype/plugins/maya/publish/extract_yeti_cache.py diff --git a/colorbleed/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py similarity index 100% rename from colorbleed/plugins/maya/publish/extract_yeti_rig.py rename to pype/plugins/maya/publish/extract_yeti_rig.py diff --git a/colorbleed/plugins/maya/publish/increment_current_file_deadline.py b/pype/plugins/maya/publish/increment_current_file_deadline.py similarity index 100% rename from colorbleed/plugins/maya/publish/increment_current_file_deadline.py rename to pype/plugins/maya/publish/increment_current_file_deadline.py diff --git a/colorbleed/plugins/maya/publish/save_scene.py b/pype/plugins/maya/publish/save_scene.py similarity index 100% rename from colorbleed/plugins/maya/publish/save_scene.py rename to pype/plugins/maya/publish/save_scene.py diff --git a/colorbleed/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py similarity index 100% rename from colorbleed/plugins/maya/publish/submit_maya_deadline.py rename to pype/plugins/maya/publish/submit_maya_deadline.py diff --git a/colorbleed/plugins/maya/publish/submit_vray_deadline.py b/pype/plugins/maya/publish/submit_vray_deadline.py similarity index 100% rename from colorbleed/plugins/maya/publish/submit_vray_deadline.py rename to pype/plugins/maya/publish/submit_vray_deadline.py diff --git a/colorbleed/plugins/maya/publish/validate_animation_content.py b/pype/plugins/maya/publish/validate_animation_content.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_animation_content.py rename to pype/plugins/maya/publish/validate_animation_content.py diff --git a/colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_animation_out_set_related_node_ids.py rename to pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_camera_attributes.py b/pype/plugins/maya/publish/validate_camera_attributes.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_camera_attributes.py rename to pype/plugins/maya/publish/validate_camera_attributes.py diff --git a/colorbleed/plugins/maya/publish/validate_camera_contents.py b/pype/plugins/maya/publish/validate_camera_contents.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_camera_contents.py rename to pype/plugins/maya/publish/validate_camera_contents.py diff --git a/colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py b/pype/plugins/maya/publish/validate_current_renderlayer_renderable.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_current_renderlayer_renderable.py rename to pype/plugins/maya/publish/validate_current_renderlayer_renderable.py diff --git a/colorbleed/plugins/maya/publish/validate_deadline_connection.py b/pype/plugins/maya/publish/validate_deadline_connection.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_deadline_connection.py rename to pype/plugins/maya/publish/validate_deadline_connection.py diff --git a/colorbleed/plugins/maya/publish/validate_frame_range.py b/pype/plugins/maya/publish/validate_frame_range.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_frame_range.py rename to pype/plugins/maya/publish/validate_frame_range.py diff --git a/colorbleed/plugins/maya/publish/validate_instance_has_members.py b/pype/plugins/maya/publish/validate_instance_has_members.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_instance_has_members.py rename to pype/plugins/maya/publish/validate_instance_has_members.py diff --git a/colorbleed/plugins/maya/publish/validate_instance_subset.py b/pype/plugins/maya/publish/validate_instance_subset.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_instance_subset.py rename to pype/plugins/maya/publish/validate_instance_subset.py diff --git a/colorbleed/plugins/maya/publish/validate_instancer_content.py b/pype/plugins/maya/publish/validate_instancer_content.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_instancer_content.py rename to pype/plugins/maya/publish/validate_instancer_content.py diff --git a/colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py b/pype/plugins/maya/publish/validate_instancer_frame_ranges.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_instancer_frame_ranges.py rename to pype/plugins/maya/publish/validate_instancer_frame_ranges.py diff --git a/colorbleed/plugins/maya/publish/validate_joints_hidden.py b/pype/plugins/maya/publish/validate_joints_hidden.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_joints_hidden.py rename to pype/plugins/maya/publish/validate_joints_hidden.py diff --git a/colorbleed/plugins/maya/publish/validate_look_contents.py b/pype/plugins/maya/publish/validate_look_contents.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_contents.py rename to pype/plugins/maya/publish/validate_look_contents.py diff --git a/colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py b/pype/plugins/maya/publish/validate_look_default_shaders_connections.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_default_shaders_connections.py rename to pype/plugins/maya/publish/validate_look_default_shaders_connections.py diff --git a/colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py b/pype/plugins/maya/publish/validate_look_id_reference_edits.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_id_reference_edits.py rename to pype/plugins/maya/publish/validate_look_id_reference_edits.py diff --git a/colorbleed/plugins/maya/publish/validate_look_members_unique.py b/pype/plugins/maya/publish/validate_look_members_unique.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_members_unique.py rename to pype/plugins/maya/publish/validate_look_members_unique.py diff --git a/colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py b/pype/plugins/maya/publish/validate_look_no_default_shaders.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_no_default_shaders.py rename to pype/plugins/maya/publish/validate_look_no_default_shaders.py diff --git a/colorbleed/plugins/maya/publish/validate_look_sets.py b/pype/plugins/maya/publish/validate_look_sets.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_sets.py rename to pype/plugins/maya/publish/validate_look_sets.py diff --git a/colorbleed/plugins/maya/publish/validate_look_single_shader.py b/pype/plugins/maya/publish/validate_look_single_shader.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_look_single_shader.py rename to pype/plugins/maya/publish/validate_look_single_shader.py diff --git a/colorbleed/plugins/maya/publish/validate_maya_units.py b/pype/plugins/maya/publish/validate_maya_units.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_maya_units.py rename to pype/plugins/maya/publish/validate_maya_units.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_has_uv.py b/pype/plugins/maya/publish/validate_mesh_has_uv.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_has_uv.py rename to pype/plugins/maya/publish/validate_mesh_has_uv.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py b/pype/plugins/maya/publish/validate_mesh_lamina_faces.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_lamina_faces.py rename to pype/plugins/maya/publish/validate_mesh_lamina_faces.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py b/pype/plugins/maya/publish/validate_mesh_no_negative_scale.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_no_negative_scale.py rename to pype/plugins/maya/publish/validate_mesh_no_negative_scale.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py b/pype/plugins/maya/publish/validate_mesh_non_manifold.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_non_manifold.py rename to pype/plugins/maya/publish/validate_mesh_non_manifold.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py b/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_non_zero_edge.py rename to pype/plugins/maya/publish/validate_mesh_non_zero_edge.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py b/pype/plugins/maya/publish/validate_mesh_normals_unlocked.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_normals_unlocked.py rename to pype/plugins/maya/publish/validate_mesh_normals_unlocked.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py b/pype/plugins/maya/publish/validate_mesh_shader_connections.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_shader_connections.py rename to pype/plugins/maya/publish/validate_mesh_shader_connections.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py b/pype/plugins/maya/publish/validate_mesh_single_uv_set.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_single_uv_set.py rename to pype/plugins/maya/publish/validate_mesh_single_uv_set.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py b/pype/plugins/maya/publish/validate_mesh_uv_set_map1.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_uv_set_map1.py rename to pype/plugins/maya/publish/validate_mesh_uv_set_map1.py diff --git a/colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_mesh_vertices_have_edges.py rename to pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py diff --git a/colorbleed/plugins/maya/publish/validate_model_content.py b/pype/plugins/maya/publish/validate_model_content.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_model_content.py rename to pype/plugins/maya/publish/validate_model_content.py diff --git a/colorbleed/plugins/maya/publish/validate_no_animation.py b/pype/plugins/maya/publish/validate_no_animation.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_animation.py rename to pype/plugins/maya/publish/validate_no_animation.py diff --git a/colorbleed/plugins/maya/publish/validate_no_default_camera.py b/pype/plugins/maya/publish/validate_no_default_camera.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_default_camera.py rename to pype/plugins/maya/publish/validate_no_default_camera.py diff --git a/colorbleed/plugins/maya/publish/validate_no_namespace.py b/pype/plugins/maya/publish/validate_no_namespace.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_namespace.py rename to pype/plugins/maya/publish/validate_no_namespace.py diff --git a/colorbleed/plugins/maya/publish/validate_no_null_transforms.py b/pype/plugins/maya/publish/validate_no_null_transforms.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_null_transforms.py rename to pype/plugins/maya/publish/validate_no_null_transforms.py diff --git a/colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py b/pype/plugins/maya/publish/validate_no_unknown_nodes.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_unknown_nodes.py rename to pype/plugins/maya/publish/validate_no_unknown_nodes.py diff --git a/colorbleed/plugins/maya/publish/validate_no_vraymesh.py b/pype/plugins/maya/publish/validate_no_vraymesh.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_no_vraymesh.py rename to pype/plugins/maya/publish/validate_no_vraymesh.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids.py b/pype/plugins/maya/publish/validate_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_ids.py rename to pype/plugins/maya/publish/validate_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py b/pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_ids_deformed_shapes.py rename to pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_in_database.py b/pype/plugins/maya/publish/validate_node_ids_in_database.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_ids_in_database.py rename to pype/plugins/maya/publish/validate_node_ids_in_database.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_related.py b/pype/plugins/maya/publish/validate_node_ids_related.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_ids_related.py rename to pype/plugins/maya/publish/validate_node_ids_related.py diff --git a/colorbleed/plugins/maya/publish/validate_node_ids_unique.py b/pype/plugins/maya/publish/validate_node_ids_unique.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_ids_unique.py rename to pype/plugins/maya/publish/validate_node_ids_unique.py diff --git a/colorbleed/plugins/maya/publish/validate_node_no_ghosting.py b/pype/plugins/maya/publish/validate_node_no_ghosting.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_node_no_ghosting.py rename to pype/plugins/maya/publish/validate_node_no_ghosting.py diff --git a/colorbleed/plugins/maya/publish/validate_render_image_rule.py b/pype/plugins/maya/publish/validate_render_image_rule.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_render_image_rule.py rename to pype/plugins/maya/publish/validate_render_image_rule.py diff --git a/colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py b/pype/plugins/maya/publish/validate_render_no_default_cameras.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_render_no_default_cameras.py rename to pype/plugins/maya/publish/validate_render_no_default_cameras.py diff --git a/colorbleed/plugins/maya/publish/validate_render_single_camera.py b/pype/plugins/maya/publish/validate_render_single_camera.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_render_single_camera.py rename to pype/plugins/maya/publish/validate_render_single_camera.py diff --git a/colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py b/pype/plugins/maya/publish/validate_renderlayer_aovs.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_renderlayer_aovs.py rename to pype/plugins/maya/publish/validate_renderlayer_aovs.py diff --git a/colorbleed/plugins/maya/publish/validate_rendersettings.py b/pype/plugins/maya/publish/validate_rendersettings.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_rendersettings.py rename to pype/plugins/maya/publish/validate_rendersettings.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_contents.py b/pype/plugins/maya/publish/validate_rig_contents.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_rig_contents.py rename to pype/plugins/maya/publish/validate_rig_contents.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers.py b/pype/plugins/maya/publish/validate_rig_controllers.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_rig_controllers.py rename to pype/plugins/maya/publish/validate_rig_controllers.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py rename to pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py diff --git a/colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py b/pype/plugins/maya/publish/validate_rig_out_set_node_ids.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_rig_out_set_node_ids.py rename to pype/plugins/maya/publish/validate_rig_out_set_node_ids.py diff --git a/colorbleed/plugins/maya/publish/validate_scene_set_workspace.py b/pype/plugins/maya/publish/validate_scene_set_workspace.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_scene_set_workspace.py rename to pype/plugins/maya/publish/validate_scene_set_workspace.py diff --git a/colorbleed/plugins/maya/publish/validate_setdress_namespaces.py b/pype/plugins/maya/publish/validate_setdress_namespaces.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_setdress_namespaces.py rename to pype/plugins/maya/publish/validate_setdress_namespaces.py diff --git a/colorbleed/plugins/maya/publish/validate_setdress_transforms.py b/pype/plugins/maya/publish/validate_setdress_transforms.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_setdress_transforms.py rename to pype/plugins/maya/publish/validate_setdress_transforms.py diff --git a/colorbleed/plugins/maya/publish/validate_shape_default_names.py b/pype/plugins/maya/publish/validate_shape_default_names.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_shape_default_names.py rename to pype/plugins/maya/publish/validate_shape_default_names.py diff --git a/colorbleed/plugins/maya/publish/validate_shape_render_stats.py b/pype/plugins/maya/publish/validate_shape_render_stats.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_shape_render_stats.py rename to pype/plugins/maya/publish/validate_shape_render_stats.py diff --git a/colorbleed/plugins/maya/publish/validate_single_assembly.py b/pype/plugins/maya/publish/validate_single_assembly.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_single_assembly.py rename to pype/plugins/maya/publish/validate_single_assembly.py diff --git a/colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py b/pype/plugins/maya/publish/validate_skinCluster_deformer_set.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_skinCluster_deformer_set.py rename to pype/plugins/maya/publish/validate_skinCluster_deformer_set.py diff --git a/colorbleed/plugins/maya/publish/validate_step_size.py b/pype/plugins/maya/publish/validate_step_size.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_step_size.py rename to pype/plugins/maya/publish/validate_step_size.py diff --git a/colorbleed/plugins/maya/publish/validate_transfers.py b/pype/plugins/maya/publish/validate_transfers.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_transfers.py rename to pype/plugins/maya/publish/validate_transfers.py diff --git a/colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py b/pype/plugins/maya/publish/validate_transform_naming_suffix.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_transform_naming_suffix.py rename to pype/plugins/maya/publish/validate_transform_naming_suffix.py diff --git a/colorbleed/plugins/maya/publish/validate_transform_zero.py b/pype/plugins/maya/publish/validate_transform_zero.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_transform_zero.py rename to pype/plugins/maya/publish/validate_transform_zero.py diff --git a/colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py b/pype/plugins/maya/publish/validate_vray_distributed_rendering.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_vray_distributed_rendering.py rename to pype/plugins/maya/publish/validate_vray_distributed_rendering.py diff --git a/colorbleed/plugins/maya/publish/validate_vray_translator_settings.py b/pype/plugins/maya/publish/validate_vray_translator_settings.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_vray_translator_settings.py rename to pype/plugins/maya/publish/validate_vray_translator_settings.py diff --git a/colorbleed/plugins/maya/publish/validate_vrayproxy_members.py b/pype/plugins/maya/publish/validate_vrayproxy_members.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_vrayproxy_members.py rename to pype/plugins/maya/publish/validate_vrayproxy_members.py diff --git a/colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_yeti_renderscript_callbacks.py rename to pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py b/pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_yeti_rig_input_in_instance.py rename to pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py diff --git a/colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py b/pype/plugins/maya/publish/validate_yeti_rig_settings.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_yeti_rig_settings.py rename to pype/plugins/maya/publish/validate_yeti_rig_settings.py diff --git a/colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py b/pype/plugins/maya/publish/validate_yetirig_cache_state.py similarity index 100% rename from colorbleed/plugins/maya/publish/validate_yetirig_cache_state.py rename to pype/plugins/maya/publish/validate_yetirig_cache_state.py diff --git a/colorbleed/scripts/__init__.py b/pype/scripts/__init__.py similarity index 100% rename from colorbleed/scripts/__init__.py rename to pype/scripts/__init__.py diff --git a/colorbleed/scripts/fusion_switch_shot.py b/pype/scripts/fusion_switch_shot.py similarity index 100% rename from colorbleed/scripts/fusion_switch_shot.py rename to pype/scripts/fusion_switch_shot.py diff --git a/colorbleed/scripts/publish_filesequence.py b/pype/scripts/publish_filesequence.py similarity index 100% rename from colorbleed/scripts/publish_filesequence.py rename to pype/scripts/publish_filesequence.py diff --git a/colorbleed/setdress_api.py b/pype/setdress_api.py similarity index 100% rename from colorbleed/setdress_api.py rename to pype/setdress_api.py diff --git a/colorbleed/vendor/__init__.py b/pype/vendor/__init__.py similarity index 100% rename from colorbleed/vendor/__init__.py rename to pype/vendor/__init__.py diff --git a/colorbleed/vendor/pather/__init__.py b/pype/vendor/pather/__init__.py similarity index 100% rename from colorbleed/vendor/pather/__init__.py rename to pype/vendor/pather/__init__.py diff --git a/colorbleed/vendor/pather/core.py b/pype/vendor/pather/core.py similarity index 100% rename from colorbleed/vendor/pather/core.py rename to pype/vendor/pather/core.py diff --git a/colorbleed/vendor/pather/error.py b/pype/vendor/pather/error.py similarity index 100% rename from colorbleed/vendor/pather/error.py rename to pype/vendor/pather/error.py diff --git a/colorbleed/vendor/pather/version.py b/pype/vendor/pather/version.py similarity index 100% rename from colorbleed/vendor/pather/version.py rename to pype/vendor/pather/version.py diff --git a/colorbleed/widgets/__init__.py b/pype/widgets/__init__.py similarity index 100% rename from colorbleed/widgets/__init__.py rename to pype/widgets/__init__.py diff --git a/colorbleed/widgets/popup.py b/pype/widgets/popup.py similarity index 100% rename from colorbleed/widgets/popup.py rename to pype/widgets/popup.py From ca57d2cd87bb61a1daf1c9e715aca5e682c6e0e9 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Mon, 28 Jan 2019 21:27:58 +0100 Subject: [PATCH 1249/1249] colorbleed to pype reformat --- pype/fusion/__init__.py | 6 ++-- pype/houdini/__init__.py | 8 +++--- pype/houdini/lib.py | 2 +- pype/lib.py | 2 +- pype/maya/__init__.py | 4 +-- pype/maya/action.py | 2 +- pype/maya/customize.py | 14 +++++----- pype/maya/lib.py | 10 +++---- pype/maya/menu.json | 2 +- pype/maya/menu.py | 6 ++-- .../fusion/create/create_tiff_saver.py | 2 +- pype/plugins/fusion/load/actions.py | 24 ++++++++-------- pype/plugins/fusion/load/load_sequence.py | 2 +- .../fusion/publish/collect_instances.py | 4 +-- .../fusion/publish/collect_render_target.py | 8 +++--- .../increment_current_file_deadline.py | 6 ++-- .../fusion/publish/publish_image_sequences.py | 8 +++--- pype/plugins/fusion/publish/render_local.py | 2 +- pype/plugins/fusion/publish/save_scene.py | 2 +- .../plugins/fusion/publish/submit_deadline.py | 2 +- .../publish/validate_background_depth.py | 4 +-- .../fusion/publish/validate_comp_saved.py | 2 +- .../publish/validate_create_folder_checked.py | 4 +-- .../validate_filename_has_extension.py | 2 +- .../publish/validate_saver_has_input.py | 2 +- .../publish/validate_saver_passthrough.py | 2 +- .../fusion/publish/validate_unique_subsets.py | 2 +- .../plugins/global/load/open_imagesequence.py | 2 +- .../global/publish/collect_deadline_user.py | 4 +-- .../global/publish/collect_filesequences.py | 2 +- pype/plugins/global/publish/integrate.py | 28 +++++++++---------- .../global/publish/submit_publish_job.py | 12 ++++---- .../global/publish/validate_resources.py | 4 +-- .../publish/validate_sequence_frames.py | 2 +- .../houdini/create/create_alembic_camera.py | 2 +- .../houdini/create/create_pointcache.py | 2 +- .../houdini/create/create_vbd_cache.py | 2 +- pype/plugins/houdini/load/load_alembic.py | 6 ++-- pype/plugins/houdini/load/load_camera.py | 2 +- .../plugins/houdini/publish/collect_frames.py | 4 +-- .../houdini/publish/collect_output_node.py | 4 +-- .../houdini/publish/extract_alembic.py | 6 ++-- .../houdini/publish/extract_vdb_cache.py | 6 ++-- .../publish/validate_alembic_input_node.py | 6 ++-- .../publish/validate_animation_settings.py | 4 +-- .../houdini/publish/validate_bypass.py | 4 +-- .../houdini/publish/validate_camera_rop.py | 6 ++-- .../publish/validate_mkpaths_toggled.py | 10 +++---- .../houdini/publish/validate_output_node.py | 4 +-- .../validate_primitive_hierarchy_paths.py | 6 ++-- .../publish/validate_vdb_input_node.py | 6 ++-- .../maya/create/colorbleed_animation.py | 4 +-- pype/plugins/maya/create/colorbleed_camera.py | 4 +-- pype/plugins/maya/create/colorbleed_fbx.py | 4 +-- pype/plugins/maya/create/colorbleed_look.py | 4 +-- .../maya/create/colorbleed_mayaascii.py | 2 +- pype/plugins/maya/create/colorbleed_model.py | 2 +- .../maya/create/colorbleed_pointcache.py | 4 +-- .../maya/create/colorbleed_renderglobals.py | 4 +-- pype/plugins/maya/create/colorbleed_rig.py | 4 +-- .../maya/create/colorbleed_setdress.py | 2 +- .../maya/create/colorbleed_vrayproxy.py | 2 +- .../maya/create/colorbleed_vrayscene.py | 2 +- .../maya/create/colorbleed_yeti_cache.py | 4 +-- .../maya/create/colorbleed_yeti_rig.py | 4 +-- pype/plugins/maya/load/actions.py | 12 ++++---- pype/plugins/maya/load/load_alembic.py | 10 +++---- pype/plugins/maya/load/load_camera.py | 8 +++--- pype/plugins/maya/load/load_fbx.py | 6 ++-- pype/plugins/maya/load/load_look.py | 6 ++-- pype/plugins/maya/load/load_mayaascii.py | 6 ++-- pype/plugins/maya/load/load_model.py | 8 +++--- pype/plugins/maya/load/load_rig.py | 8 +++--- pype/plugins/maya/load/load_setdress.py | 8 +++--- .../plugins/maya/load/load_vdb_to_redshift.py | 2 +- pype/plugins/maya/load/load_vdb_to_vray.py | 2 +- pype/plugins/maya/load/load_vrayproxy.py | 4 +-- pype/plugins/maya/load/load_yeti_cache.py | 4 +-- pype/plugins/maya/load/load_yeti_rig.py | 6 ++-- .../plugins/maya/publish/collect_animation.py | 2 +- pype/plugins/maya/publish/collect_history.py | 2 +- .../plugins/maya/publish/collect_instances.py | 2 +- pype/plugins/maya/publish/collect_look.py | 4 +-- pype/plugins/maya/publish/collect_model.py | 4 +-- .../maya/publish/collect_render_layer_aovs.py | 4 +-- .../maya/publish/collect_renderable_camera.py | 6 ++-- .../maya/publish/collect_renderlayers.py | 4 +-- pype/plugins/maya/publish/collect_setdress.py | 4 +-- .../maya/publish/collect_vray_scene.py | 6 ++-- .../maya/publish/collect_yeti_cache.py | 4 +-- pype/plugins/maya/publish/collect_yeti_rig.py | 6 ++-- .../plugins/maya/publish/extract_animation.py | 8 +++--- .../maya/publish/extract_camera_alembic.py | 8 +++--- .../maya/publish/extract_camera_mayaAscii.py | 10 +++---- pype/plugins/maya/publish/extract_fbx.py | 6 ++-- pype/plugins/maya/publish/extract_look.py | 8 +++--- .../maya/publish/extract_maya_ascii_raw.py | 6 ++-- pype/plugins/maya/publish/extract_model.py | 8 +++--- .../maya/publish/extract_pointcache.py | 10 +++---- pype/plugins/maya/publish/extract_rig.py | 6 ++-- pype/plugins/maya/publish/extract_setdress.py | 8 +++--- .../plugins/maya/publish/extract_vrayproxy.py | 6 ++-- .../maya/publish/extract_yeti_cache.py | 6 ++-- pype/plugins/maya/publish/extract_yeti_rig.py | 8 +++--- .../increment_current_file_deadline.py | 8 +++--- pype/plugins/maya/publish/save_scene.py | 2 +- .../maya/publish/submit_maya_deadline.py | 4 +-- .../maya/publish/submit_vray_deadline.py | 2 +- .../publish/validate_animation_content.py | 10 +++---- ...date_animation_out_set_related_node_ids.py | 12 ++++---- .../publish/validate_camera_attributes.py | 10 +++---- .../maya/publish/validate_camera_contents.py | 10 +++---- ...validate_current_renderlayer_renderable.py | 4 +-- .../publish/validate_deadline_connection.py | 4 +-- .../maya/publish/validate_frame_range.py | 12 ++++---- .../publish/validate_instance_has_members.py | 8 +++--- .../maya/publish/validate_instance_subset.py | 4 +-- .../publish/validate_instancer_content.py | 4 +-- .../validate_instancer_frame_ranges.py | 2 +- .../maya/publish/validate_joints_hidden.py | 14 +++++----- .../maya/publish/validate_look_contents.py | 10 +++---- ...lidate_look_default_shaders_connections.py | 6 ++-- .../validate_look_id_reference_edits.py | 12 ++++---- .../publish/validate_look_members_unique.py | 12 ++++---- .../validate_look_no_default_shaders.py | 10 +++---- .../maya/publish/validate_look_sets.py | 12 ++++---- .../publish/validate_look_single_shader.py | 10 +++---- .../maya/publish/validate_maya_units.py | 10 +++---- .../maya/publish/validate_mesh_has_uv.py | 10 +++---- .../publish/validate_mesh_lamina_faces.py | 10 +++---- .../validate_mesh_no_negative_scale.py | 10 +++---- .../publish/validate_mesh_non_manifold.py | 10 +++---- .../publish/validate_mesh_non_zero_edge.py | 12 ++++---- .../publish/validate_mesh_normals_unlocked.py | 12 ++++---- .../validate_mesh_shader_connections.py | 12 ++++---- .../publish/validate_mesh_single_uv_set.py | 14 +++++----- .../maya/publish/validate_mesh_uv_set_map1.py | 12 ++++---- .../validate_mesh_vertices_have_edges.py | 12 ++++---- .../maya/publish/validate_model_content.py | 12 ++++---- .../maya/publish/validate_no_animation.py | 10 +++---- .../publish/validate_no_default_camera.py | 10 +++---- .../maya/publish/validate_no_namespace.py | 12 ++++---- .../publish/validate_no_null_transforms.py | 12 ++++---- .../maya/publish/validate_no_unknown_nodes.py | 10 +++---- .../maya/publish/validate_no_vraymesh.py | 2 +- .../plugins/maya/publish/validate_node_ids.py | 26 ++++++++--------- .../validate_node_ids_deformed_shapes.py | 12 ++++---- .../publish/validate_node_ids_in_database.py | 12 ++++---- .../maya/publish/validate_node_ids_related.py | 18 ++++++------ .../maya/publish/validate_node_ids_unique.py | 20 ++++++------- .../maya/publish/validate_node_no_ghosting.py | 10 +++---- .../publish/validate_render_image_rule.py | 6 ++-- .../validate_render_no_default_cameras.py | 10 +++---- .../publish/validate_render_single_camera.py | 12 ++++---- .../maya/publish/validate_renderlayer_aovs.py | 8 +++--- .../maya/publish/validate_rendersettings.py | 10 +++---- .../maya/publish/validate_rig_contents.py | 6 ++-- .../maya/publish/validate_rig_controllers.py | 14 +++++----- ...idate_rig_controllers_arnold_attributes.py | 14 +++++----- .../publish/validate_rig_out_set_node_ids.py | 12 ++++---- .../publish/validate_scene_set_workspace.py | 4 +-- .../publish/validate_setdress_namespaces.py | 8 +++--- .../publish/validate_setdress_transforms.py | 14 +++++----- .../publish/validate_shape_default_names.py | 12 ++++---- .../publish/validate_shape_render_stats.py | 12 ++++---- .../maya/publish/validate_single_assembly.py | 6 ++-- .../validate_skinCluster_deformer_set.py | 10 +++---- .../maya/publish/validate_step_size.py | 14 +++++----- .../maya/publish/validate_transfers.py | 4 +-- .../validate_transform_naming_suffix.py | 10 +++---- .../maya/publish/validate_transform_zero.py | 10 +++---- .../validate_vray_distributed_rendering.py | 10 +++---- .../validate_vray_translator_settings.py | 10 +++---- .../publish/validate_vrayproxy_members.py | 8 +++--- .../validate_yeti_renderscript_callbacks.py | 6 ++-- .../validate_yeti_rig_input_in_instance.py | 10 +++---- .../publish/validate_yeti_rig_settings.py | 2 +- .../publish/validate_yetirig_cache_state.py | 10 +++---- pype/scripts/fusion_switch_shot.py | 10 +++---- pype/setdress_api.py | 8 +++--- 180 files changed, 646 insertions(+), 646 deletions(-) diff --git a/pype/fusion/__init__.py b/pype/fusion/__init__.py index 8ae042c934..d6efaa54d3 100644 --- a/pype/fusion/__init__.py +++ b/pype/fusion/__init__.py @@ -24,9 +24,9 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) # Disable all families except for the ones we explicitly want to see - family_states = ["colorbleed.imagesequence", - "colorbleed.camera", - "colorbleed.pointcache"] + family_states = ["imagesequence", + "camera", + "pointcache"] avalon.data["familiesStateDefault"] = False avalon.data["familiesStateToggled"] = family_states diff --git a/pype/houdini/__init__.py b/pype/houdini/__init__.py index 05d5195605..f432a4a5e5 100644 --- a/pype/houdini/__init__.py +++ b/pype/houdini/__init__.py @@ -8,9 +8,9 @@ from pyblish import api as pyblish from avalon import api as avalon from avalon.houdini import pipeline as houdini -from colorbleed.houdini import lib +from pype.houdini import lib -from colorbleed.lib import ( +from pype.lib import ( any_outdated, update_task_from_path ) @@ -24,7 +24,7 @@ PUBLISH_PATH = os.path.join(PLUGINS_DIR, "houdini", "publish") LOAD_PATH = os.path.join(PLUGINS_DIR, "houdini", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "houdini", "create") -log = logging.getLogger("colorbleed.houdini") +log = logging.getLogger("pype.houdini") def install(): @@ -42,7 +42,7 @@ def install(): pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) log.info("Setting default family states for loader..") - avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] + avalon.data["familiesStateToggled"] = ["imagesequence"] def on_init(*args): diff --git a/pype/houdini/lib.py b/pype/houdini/lib.py index 179cb0670d..e1e95912ee 100644 --- a/pype/houdini/lib.py +++ b/pype/houdini/lib.py @@ -4,7 +4,7 @@ from contextlib import contextmanager import hou -from colorbleed import lib +from pype import lib from avalon import api, io from avalon.houdini import lib as houdini diff --git a/pype/lib.py b/pype/lib.py index edd911a461..3ce1441e3d 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -262,7 +262,7 @@ def collect_container_metadata(container): """ # TODO: Improve method of getting the host lib module host_name = _get_host_name() - package_name = "colorbleed.{}.lib".format(host_name) + package_name = "pype.{}.lib".format(host_name) hostlib = importlib.import_module(package_name) if not hasattr(hostlib, "get_additional_data"): diff --git a/pype/maya/__init__.py b/pype/maya/__init__.py index 6e6d0ca869..68af530bcd 100644 --- a/pype/maya/__init__.py +++ b/pype/maya/__init__.py @@ -15,7 +15,7 @@ from ..lib import ( from . import menu from . import lib -log = logging.getLogger("colorbleed.maya") +log = logging.getLogger("pype.maya") PARENT_DIR = os.path.dirname(__file__) PACKAGE_DIR = os.path.dirname(PARENT_DIR) @@ -52,7 +52,7 @@ def install(): override_event("taskChanged", on_task_changed) log.info("Setting default family states for loader..") - avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"] + avalon.data["familiesStateToggled"] = ["imagesequence"] def uninstall(): diff --git a/pype/maya/action.py b/pype/maya/action.py index f0f94516c0..6281a82409 100644 --- a/pype/maya/action.py +++ b/pype/maya/action.py @@ -72,7 +72,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): nodes (list): all nodes to regenerate ids on """ - import colorbleed.maya.lib as lib + import pype.maya.lib as lib import avalon.io as io asset = instance.data['asset'] diff --git a/pype/maya/customize.py b/pype/maya/customize.py index 8b2dda7540..46c9ceb652 100644 --- a/pype/maya/customize.py +++ b/pype/maya/customize.py @@ -70,8 +70,8 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" - import colorbleed - res = os.path.join(os.path.dirname(os.path.dirname(colorbleed.__file__)), + import pype + res = os.path.join(os.path.dirname(os.path.dirname(pype.__file__)), "res") icons = os.path.join(res, "icons") @@ -99,7 +99,7 @@ def override_toolbox_ui(): controls = [] control = mc.iconTextButton( - "colorbleed_toolbox_workfiles", + "pype_toolbox_workfiles", annotation="Work Files", label="Work Files", image=os.path.join(icons, "workfiles.png"), @@ -111,7 +111,7 @@ def override_toolbox_ui(): controls.append(control) control = mc.iconTextButton( - "colorbleed_toolbox_loader", + "pype_toolbox_loader", annotation="Loader", label="Loader", image=os.path.join(icons, "loader.png"), @@ -123,7 +123,7 @@ def override_toolbox_ui(): controls.append(control) control = mc.iconTextButton( - "colorbleed_toolbox_manager", + "pype_toolbox_manager", annotation="Inventory", label="Inventory", image=os.path.join(icons, "inventory.png"), @@ -135,10 +135,10 @@ def override_toolbox_ui(): controls.append(control) control = mc.iconTextButton( - "colorbleed_toolbox", + "pype_toolbox", annotation="Colorbleed", label="Colorbleed", - image=os.path.join(icons, "colorbleed_logo_36x36.png"), + image=os.path.join(icons, "pype_logo_36x36.png"), bgc=background_color, width=icon_size, height=icon_size, diff --git a/pype/maya/lib.py b/pype/maya/lib.py index e78b185125..26c3cbcb3a 100644 --- a/pype/maya/lib.py +++ b/pype/maya/lib.py @@ -17,7 +17,7 @@ from avalon import api, maya, io, pipeline from avalon.vendor.six import string_types import avalon.maya.lib -from colorbleed import lib +from pype import lib log = logging.getLogger(__name__) @@ -1368,11 +1368,11 @@ def assign_look(nodes, subset="lookDefault"): # Group all nodes per asset id grouped = defaultdict(list) for node in nodes: - colorbleed_id = get_id(node) - if not colorbleed_id: + pype_id = get_id(node) + if not pype_id: continue - parts = colorbleed_id.split(":", 1) + parts = pype_id.split(":", 1) grouped[parts[0]].append(node) for asset_id, asset_nodes in grouped.items(): @@ -1395,7 +1395,7 @@ def assign_look(nodes, subset="lookDefault"): version = io.find_one({"parent": subset_data['_id'], "type": "version", "data.families": - {"$in": ["colorbleed.look"]} + {"$in": ["look"]} }, sort=[("name", -1)], projection={"_id": True, "name": True}) diff --git a/pype/maya/menu.json b/pype/maya/menu.json index a6ecea4b11..848410f002 100644 --- a/pype/maya/menu.json +++ b/pype/maya/menu.json @@ -1536,7 +1536,7 @@ }, { "type": "action", - "command": "$COLORBLEED_SCRIPTS\\cleanup\\colorbleedRename.py", + "command": "$COLORBLEED_SCRIPTS\\cleanup\\pypeRename.py", "sourcetype": "file", "tags": ["cleanup", "rename", diff --git a/pype/maya/menu.py b/pype/maya/menu.py index 9223271d65..ed5115c274 100644 --- a/pype/maya/menu.py +++ b/pype/maya/menu.py @@ -7,7 +7,7 @@ from avalon.vendor.Qt import QtWidgets, QtCore, QtGui import maya.cmds as cmds self = sys.modules[__name__] -self._menu = "colorbleed" +self._menu = "pype" log = logging.getLogger(__name__) @@ -29,7 +29,7 @@ def deferred(): import scriptsmenu.launchformaya as launchformaya import scriptsmenu.scriptsmenu as scriptsmenu except ImportError: - log.warning("Skipping colorbleed.menu install, because " + log.warning("Skipping pype.menu install, because " "'scriptsmenu' module seems unavailable.") return @@ -61,7 +61,7 @@ def uninstall(): def install(): if cmds.about(batch=True): - print("Skipping colorbleed.menu initialization in batch mode..") + print("Skipping pype.menu initialization in batch mode..") return uninstall() diff --git a/pype/plugins/fusion/create/create_tiff_saver.py b/pype/plugins/fusion/create/create_tiff_saver.py index b313ca994f..4911650ed2 100644 --- a/pype/plugins/fusion/create/create_tiff_saver.py +++ b/pype/plugins/fusion/create/create_tiff_saver.py @@ -9,7 +9,7 @@ class CreateTiffSaver(avalon.api.Creator): name = "tiffDefault" label = "Create Tiff Saver" hosts = ["fusion"] - family = "colorbleed.saver" + family = "saver" def process(self): diff --git a/pype/plugins/fusion/load/actions.py b/pype/plugins/fusion/load/actions.py index c986229748..d7ee13716b 100644 --- a/pype/plugins/fusion/load/actions.py +++ b/pype/plugins/fusion/load/actions.py @@ -8,11 +8,11 @@ from avalon import api class FusionSetFrameRangeLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.imagesequence", - "colorbleed.yeticache", - "colorbleed.pointcache"] + families = ["animation", + "camera", + "imagesequence", + "yeticache", + "pointcache"] representations = ["*"] label = "Set frame range" @@ -22,7 +22,7 @@ class FusionSetFrameRangeLoader(api.Loader): def load(self, context, name, namespace, data): - from colorbleed.fusion import lib + from pype.fusion import lib version = context['version'] version_data = version.get("data", {}) @@ -41,11 +41,11 @@ class FusionSetFrameRangeLoader(api.Loader): class FusionSetFrameRangeWithHandlesLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.imagesequence", - "colorbleed.yeticache", - "colorbleed.pointcache"] + families = ["animation", + "camera", + "imagesequence", + "yeticache", + "pointcache"] representations = ["*"] label = "Set frame range (with handles)" @@ -55,7 +55,7 @@ class FusionSetFrameRangeWithHandlesLoader(api.Loader): def load(self, context, name, namespace, data): - from colorbleed.fusion import lib + from pype.fusion import lib version = context['version'] version_data = version.get("data", {}) diff --git a/pype/plugins/fusion/load/load_sequence.py b/pype/plugins/fusion/load/load_sequence.py index 81313992ed..7b859e9b03 100644 --- a/pype/plugins/fusion/load/load_sequence.py +++ b/pype/plugins/fusion/load/load_sequence.py @@ -113,7 +113,7 @@ def loader_shift(loader, frame, relative=True): class FusionLoadSequence(api.Loader): """Load image sequence into Fusion""" - families = ["colorbleed.imagesequence"] + families = ["imagesequence"] representations = ["*"] label = "Load sequence" diff --git a/pype/plugins/fusion/publish/collect_instances.py b/pype/plugins/fusion/publish/collect_instances.py index 322197fc87..472e5d4741 100644 --- a/pype/plugins/fusion/publish/collect_instances.py +++ b/pype/plugins/fusion/publish/collect_instances.py @@ -76,8 +76,8 @@ class CollectInstances(pyblish.api.ContextPlugin): "outputDir": os.path.dirname(path), "ext": ext, # todo: should be redundant "label": label, - "families": ["colorbleed.saver"], - "family": "colorbleed.saver", + "families": ["saver"], + "family": "saver", "active": active, "publish": active # backwards compatibility }) diff --git a/pype/plugins/fusion/publish/collect_render_target.py b/pype/plugins/fusion/publish/collect_render_target.py index a9193eaee5..b6217f1ddf 100644 --- a/pype/plugins/fusion/publish/collect_render_target.py +++ b/pype/plugins/fusion/publish/collect_render_target.py @@ -13,7 +13,7 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): available tool does not visualize which render mode is set for the current comp, please run the following line in the console (Py2) - comp.GetData("colorbleed.rendermode") + comp.GetData("pype.rendermode") This will return the name of the current render mode as seen above under Options. @@ -23,7 +23,7 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Render Mode" hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["saver"] def process(self, instance): """Collect all image sequence tools""" @@ -34,11 +34,11 @@ class CollectFusionRenderMode(pyblish.api.InstancePlugin): raise RuntimeError("No comp previously collected, unable to " "retrieve Fusion version.") - rendermode = comp.GetData("colorbleed.rendermode") or "renderlocal" + rendermode = comp.GetData("pype.rendermode") or "renderlocal" assert rendermode in options, "Must be supported render mode" self.log.info("Render mode: {0}".format(rendermode)) # Append family - family = "colorbleed.saver.{0}".format(rendermode) + family = "saver.{0}".format(rendermode) instance.data["families"].append(family) diff --git a/pype/plugins/fusion/publish/increment_current_file_deadline.py b/pype/plugins/fusion/publish/increment_current_file_deadline.py index 9d766c426c..6545d84da3 100644 --- a/pype/plugins/fusion/publish/increment_current_file_deadline.py +++ b/pype/plugins/fusion/publish/increment_current_file_deadline.py @@ -11,13 +11,13 @@ class FusionIncrementCurrentFile(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["fusion"] - families = ["colorbleed.saver.deadline"] + families = ["saver.deadline"] optional = True def process(self, context): - from colorbleed.lib import version_up - from colorbleed.action import get_errored_plugins_from_data + from pype.lib import version_up + from pype.action import get_errored_plugins_from_data errored_plugins = get_errored_plugins_from_data(context) if any(plugin.__name__ == "FusionSubmitDeadline" diff --git a/pype/plugins/fusion/publish/publish_image_sequences.py b/pype/plugins/fusion/publish/publish_image_sequences.py index 33d6d2773d..26ae74676f 100644 --- a/pype/plugins/fusion/publish/publish_image_sequences.py +++ b/pype/plugins/fusion/publish/publish_image_sequences.py @@ -5,7 +5,7 @@ import subprocess import pyblish.api -from colorbleed.action import get_errored_plugins_from_data +from pype.action import get_errored_plugins_from_data def _get_script(): @@ -14,7 +14,7 @@ def _get_script(): # todo: use a more elegant way to get the python script try: - from colorbleed.scripts import publish_filesequence + from pype.scripts import publish_filesequence except Exception: raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") @@ -32,7 +32,7 @@ class PublishImageSequence(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder label = "Publish Rendered Image Sequence(s)" hosts = ["fusion"] - families = ["colorbleed.saver.renderlocal"] + families = ["saver.renderlocal"] def process(self, instance): @@ -55,7 +55,7 @@ class PublishImageSequence(pyblish.api.InstancePlugin): "regex": regex, "startFrame": instance.context.data["startFrame"], "endFrame": instance.context.data["endFrame"], - "families": ["colorbleed.imagesequence"], + "families": ["imagesequence"], } # Write metadata and store the path in the instance diff --git a/pype/plugins/fusion/publish/render_local.py b/pype/plugins/fusion/publish/render_local.py index 39ce0d45dc..c97fe1a13d 100644 --- a/pype/plugins/fusion/publish/render_local.py +++ b/pype/plugins/fusion/publish/render_local.py @@ -14,7 +14,7 @@ class FusionRenderLocal(pyblish.api.InstancePlugin): order = pyblish.api.ExtractorOrder label = "Render Local" hosts = ["fusion"] - families = ["colorbleed.saver.renderlocal"] + families = ["saver.renderlocal"] def process(self, instance): diff --git a/pype/plugins/fusion/publish/save_scene.py b/pype/plugins/fusion/publish/save_scene.py index e88b8ba769..850ac5c372 100644 --- a/pype/plugins/fusion/publish/save_scene.py +++ b/pype/plugins/fusion/publish/save_scene.py @@ -7,7 +7,7 @@ class FusionSaveComp(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.ExtractorOrder - 0.49 hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["saver"] def process(self, context): diff --git a/pype/plugins/fusion/publish/submit_deadline.py b/pype/plugins/fusion/publish/submit_deadline.py index 9fbf9b4003..6e1f405afd 100644 --- a/pype/plugins/fusion/publish/submit_deadline.py +++ b/pype/plugins/fusion/publish/submit_deadline.py @@ -19,7 +19,7 @@ class FusionSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["fusion"] - families = ["colorbleed.saver.deadline"] + families = ["saver.deadline"] def process(self, instance): diff --git a/pype/plugins/fusion/publish/validate_background_depth.py b/pype/plugins/fusion/publish/validate_background_depth.py index abf5dd248e..88a52ad52d 100644 --- a/pype/plugins/fusion/publish/validate_background_depth.py +++ b/pype/plugins/fusion/publish/validate_background_depth.py @@ -1,6 +1,6 @@ import pyblish.api -from colorbleed import action +from pype import action class ValidateBackgroundDepth(pyblish.api.InstancePlugin): @@ -10,7 +10,7 @@ class ValidateBackgroundDepth(pyblish.api.InstancePlugin): label = "Validate Background Depth 32 bit" actions = [action.RepairAction] hosts = ["fusion"] - families = ["colorbleed.saver"] + families = ["saver"] optional = True @classmethod diff --git a/pype/plugins/fusion/publish/validate_comp_saved.py b/pype/plugins/fusion/publish/validate_comp_saved.py index 18621ceb4f..425168fbdf 100644 --- a/pype/plugins/fusion/publish/validate_comp_saved.py +++ b/pype/plugins/fusion/publish/validate_comp_saved.py @@ -8,7 +8,7 @@ class ValidateFusionCompSaved(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Comp Saved" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_create_folder_checked.py b/pype/plugins/fusion/publish/validate_create_folder_checked.py index 7a10b1e538..00a8526c6b 100644 --- a/pype/plugins/fusion/publish/validate_create_folder_checked.py +++ b/pype/plugins/fusion/publish/validate_create_folder_checked.py @@ -1,6 +1,6 @@ import pyblish.api -from colorbleed import action +from pype import action class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): @@ -13,7 +13,7 @@ class ValidateCreateFolderChecked(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder actions = [action.RepairAction] label = "Validate Create Folder Checked" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_filename_has_extension.py b/pype/plugins/fusion/publish/validate_filename_has_extension.py index b7fe1f4c31..d3762ad290 100644 --- a/pype/plugins/fusion/publish/validate_filename_has_extension.py +++ b/pype/plugins/fusion/publish/validate_filename_has_extension.py @@ -14,7 +14,7 @@ class ValidateFilenameHasExtension(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Filename Has Extension" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] def process(self, instance): diff --git a/pype/plugins/fusion/publish/validate_saver_has_input.py b/pype/plugins/fusion/publish/validate_saver_has_input.py index 27ed77d9d9..6887a9704c 100644 --- a/pype/plugins/fusion/publish/validate_saver_has_input.py +++ b/pype/plugins/fusion/publish/validate_saver_has_input.py @@ -10,7 +10,7 @@ class ValidateSaverHasInput(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Has Input" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/fusion/publish/validate_saver_passthrough.py b/pype/plugins/fusion/publish/validate_saver_passthrough.py index ca58eba5bd..2da5cf2494 100644 --- a/pype/plugins/fusion/publish/validate_saver_passthrough.py +++ b/pype/plugins/fusion/publish/validate_saver_passthrough.py @@ -6,7 +6,7 @@ class ValidateSaverPassthrough(pyblish.api.ContextPlugin): order = pyblish.api.ValidatorOrder label = "Validate Saver Passthrough" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] def process(self, context): diff --git a/pype/plugins/fusion/publish/validate_unique_subsets.py b/pype/plugins/fusion/publish/validate_unique_subsets.py index 527b4acc69..2000e1c05d 100644 --- a/pype/plugins/fusion/publish/validate_unique_subsets.py +++ b/pype/plugins/fusion/publish/validate_unique_subsets.py @@ -6,7 +6,7 @@ class ValidateUniqueSubsets(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Unique Subsets" - families = ["colorbleed.saver"] + families = ["saver"] hosts = ["fusion"] @classmethod diff --git a/pype/plugins/global/load/open_imagesequence.py b/pype/plugins/global/load/open_imagesequence.py index 12fb9a0226..8cb16fc507 100644 --- a/pype/plugins/global/load/open_imagesequence.py +++ b/pype/plugins/global/load/open_imagesequence.py @@ -18,7 +18,7 @@ def open(filepath): class PlayImageSequence(api.Loader): """Open Image Sequence with system default""" - families = ["colorbleed.imagesequence"] + families = ["imagesequence"] representations = ["*"] label = "Play sequence" diff --git a/pype/plugins/global/publish/collect_deadline_user.py b/pype/plugins/global/publish/collect_deadline_user.py index f78d0b1c9d..624e455251 100644 --- a/pype/plugins/global/publish/collect_deadline_user.py +++ b/pype/plugins/global/publish/collect_deadline_user.py @@ -2,7 +2,7 @@ import os import subprocess import pyblish.api -from colorbleed.plugin import contextplugin_should_run +from pype.plugin import contextplugin_should_run CREATE_NO_WINDOW = 0x08000000 @@ -36,7 +36,7 @@ class CollectDeadlineUser(pyblish.api.ContextPlugin): order = pyblish.api.CollectorOrder + 0.499 label = "Deadline User" hosts = ['maya', 'fusion'] - families = ["colorbleed.renderlayer", "colorbleed.saver.deadline"] + families = ["renderlayer", "saver.deadline"] def process(self, context): """Inject the current working file""" diff --git a/pype/plugins/global/publish/collect_filesequences.py b/pype/plugins/global/publish/collect_filesequences.py index 9c2390d3dc..3dcf0fdea6 100644 --- a/pype/plugins/global/publish/collect_filesequences.py +++ b/pype/plugins/global/publish/collect_filesequences.py @@ -148,7 +148,7 @@ class CollectFileSequences(pyblish.api.ContextPlugin): raise RuntimeError("Invalid sequence") # Get family from the data - families = data.get("families", ["colorbleed.imagesequence"]) + families = data.get("families", ["imagesequence"]) assert isinstance(families, (list, tuple)), "Must be iterable" assert families, "Must have at least a single family" diff --git a/pype/plugins/global/publish/integrate.py b/pype/plugins/global/publish/integrate.py index 869291c91b..554de1fb08 100644 --- a/pype/plugins/global/publish/integrate.py +++ b/pype/plugins/global/publish/integrate.py @@ -23,20 +23,20 @@ class IntegrateAsset(pyblish.api.InstancePlugin): label = "Integrate Asset" order = pyblish.api.IntegratorOrder - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.fbx", - "colorbleed.imagesequence", - "colorbleed.look", - "colorbleed.mayaAscii", - "colorbleed.model", - "colorbleed.pointcache", - "colorbleed.vdbcache", - "colorbleed.setdress", - "colorbleed.rig", - "colorbleed.vrayproxy", - "colorbleed.yetiRig", - "colorbleed.yeticache"] + families = ["animation", + "camera", + "fbx", + "imagesequence", + "look", + "mayaAscii", + "model", + "pointcache", + "vdbcache", + "setdress", + "rig", + "vrayproxy", + "yetiRig", + "yeticache"] def process(self, instance): diff --git a/pype/plugins/global/publish/submit_publish_job.py b/pype/plugins/global/publish/submit_publish_job.py index 1806668433..762042dbed 100644 --- a/pype/plugins/global/publish/submit_publish_job.py +++ b/pype/plugins/global/publish/submit_publish_job.py @@ -12,7 +12,7 @@ import pyblish.api def _get_script(): """Get path to the image sequence script""" try: - from colorbleed.scripts import publish_filesequence + from pype.scripts import publish_filesequence except Exception as e: raise RuntimeError("Expected module 'publish_imagesequence'" "to be available") @@ -123,9 +123,9 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): label = "Submit image sequence jobs to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["fusion", "maya"] - families = ["colorbleed.saver.deadline", - "colorbleed.renderlayer", - "colorbleed.vrayscene"] + families = ["saver.deadline", + "renderlayer", + "vrayscene"] def process(self, instance): @@ -175,7 +175,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): "regex": regex, "startFrame": start, "endFrame": end, - "families": ["colorbleed.imagesequence"], + "families": ["imagesequence"], # Optional metadata (for debugging) "metadata": { @@ -193,7 +193,7 @@ class SubmitDependentImageSequenceJobDeadline(pyblish.api.InstancePlugin): resources = [] if data.get("extendFrames", False): - family = "colorbleed.imagesequence" + family = "imagesequence" override = data["overrideExistingFrame"] # override = data.get("overrideExistingFrame", False) diff --git a/pype/plugins/global/publish/validate_resources.py b/pype/plugins/global/publish/validate_resources.py index 2dc6f8c313..bc10d3003c 100644 --- a/pype/plugins/global/publish/validate_resources.py +++ b/pype/plugins/global/publish/validate_resources.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api import os @@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Resources" def process(self, instance): diff --git a/pype/plugins/global/publish/validate_sequence_frames.py b/pype/plugins/global/publish/validate_sequence_frames.py index bd14b4c0c3..cd54e6becc 100644 --- a/pype/plugins/global/publish/validate_sequence_frames.py +++ b/pype/plugins/global/publish/validate_sequence_frames.py @@ -11,7 +11,7 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Sequence Frames" - families = ["colorbleed.imagesequence"] + families = ["imagesequence"] hosts = ["shell"] def process(self, instance): diff --git a/pype/plugins/houdini/create/create_alembic_camera.py b/pype/plugins/houdini/create/create_alembic_camera.py index 96449fb3db..cf8ac41b62 100644 --- a/pype/plugins/houdini/create/create_alembic_camera.py +++ b/pype/plugins/houdini/create/create_alembic_camera.py @@ -6,7 +6,7 @@ class CreateAlembicCamera(houdini.Creator): name = "camera" label = "Camera (Abc)" - family = "colorbleed.camera" + family = "camera" icon = "camera" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/houdini/create/create_pointcache.py b/pype/plugins/houdini/create/create_pointcache.py index 85993678c2..ae7e845083 100644 --- a/pype/plugins/houdini/create/create_pointcache.py +++ b/pype/plugins/houdini/create/create_pointcache.py @@ -6,7 +6,7 @@ class CreatePointCache(houdini.Creator): name = "pointcache" label = "Point Cache" - family = "colorbleed.pointcache" + family = "pointcache" icon = "gears" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/houdini/create/create_vbd_cache.py b/pype/plugins/houdini/create/create_vbd_cache.py index ebdb1271ce..e862d5c96d 100644 --- a/pype/plugins/houdini/create/create_vbd_cache.py +++ b/pype/plugins/houdini/create/create_vbd_cache.py @@ -6,7 +6,7 @@ class CreateVDBCache(houdini.Creator): name = "vbdcache" label = "VDB Cache" - family = "colorbleed.vdbcache" + family = "vdbcache" icon = "cloud" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/houdini/load/load_alembic.py b/pype/plugins/houdini/load/load_alembic.py index 690b15c736..8fc2b6a61a 100644 --- a/pype/plugins/houdini/load/load_alembic.py +++ b/pype/plugins/houdini/load/load_alembic.py @@ -6,9 +6,9 @@ from avalon.houdini import pipeline, lib class AbcLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.model", - "colorbleed.animation", - "colorbleed.pointcache"] + families = ["model", + "animation", + "pointcache"] label = "Load Alembic" representations = ["abc"] order = -10 diff --git a/pype/plugins/houdini/load/load_camera.py b/pype/plugins/houdini/load/load_camera.py index f827244c6b..a3d67f6e5e 100644 --- a/pype/plugins/houdini/load/load_camera.py +++ b/pype/plugins/houdini/load/load_camera.py @@ -6,7 +6,7 @@ from avalon.houdini import pipeline, lib class CameraLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.camera"] + families = ["camera"] label = "Load Camera (abc)" representations = ["abc"] order = -10 diff --git a/pype/plugins/houdini/publish/collect_frames.py b/pype/plugins/houdini/publish/collect_frames.py index 49fa7378c5..7735d23fb7 100644 --- a/pype/plugins/houdini/publish/collect_frames.py +++ b/pype/plugins/houdini/publish/collect_frames.py @@ -2,7 +2,7 @@ import os import re import pyblish.api -from colorbleed.houdini import lib +from pype.houdini import lib class CollectFrames(pyblish.api.InstancePlugin): @@ -10,7 +10,7 @@ class CollectFrames(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder label = "Collect Frames" - families = ["colorbleed.vdbcache"] + families = ["vdbcache"] def process(self, instance): diff --git a/pype/plugins/houdini/publish/collect_output_node.py b/pype/plugins/houdini/publish/collect_output_node.py index d90898944f..c0587d5336 100644 --- a/pype/plugins/houdini/publish/collect_output_node.py +++ b/pype/plugins/houdini/publish/collect_output_node.py @@ -5,8 +5,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin): """Collect the out node's SOP Path value.""" order = pyblish.api.CollectorOrder - families = ["colorbleed.pointcache", - "colorbleed.vdbcache"] + families = ["pointcache", + "vdbcache"] hosts = ["houdini"] label = "Collect Output SOP Path" diff --git a/pype/plugins/houdini/publish/extract_alembic.py b/pype/plugins/houdini/publish/extract_alembic.py index 8872372484..f40f3d2d0e 100644 --- a/pype/plugins/houdini/publish/extract_alembic.py +++ b/pype/plugins/houdini/publish/extract_alembic.py @@ -1,15 +1,15 @@ import os import pyblish.api -import colorbleed.api +import pype.api -class ExtractAlembic(colorbleed.api.Extractor): +class ExtractAlembic(pype.api.Extractor): order = pyblish.api.ExtractorOrder label = "Extract Alembic" hosts = ["houdini"] - families = ["colorbleed.pointcache", "colorbleed.camera"] + families = ["pointcache", "camera"] def process(self, instance): diff --git a/pype/plugins/houdini/publish/extract_vdb_cache.py b/pype/plugins/houdini/publish/extract_vdb_cache.py index 7fe443b5a1..cfd9104744 100644 --- a/pype/plugins/houdini/publish/extract_vdb_cache.py +++ b/pype/plugins/houdini/publish/extract_vdb_cache.py @@ -1,14 +1,14 @@ import os import pyblish.api -import colorbleed.api +import pype.api -class ExtractVDBCache(colorbleed.api.Extractor): +class ExtractVDBCache(pype.api.Extractor): order = pyblish.api.ExtractorOrder + 0.1 label = "Extract VDB Cache" - families = ["colorbleed.vdbcache"] + families = ["vdbcache"] hosts = ["houdini"] def process(self, instance): diff --git a/pype/plugins/houdini/publish/validate_alembic_input_node.py b/pype/plugins/houdini/publish/validate_alembic_input_node.py index 663b1198eb..c68e3cdf5c 100644 --- a/pype/plugins/houdini/publish/validate_alembic_input_node.py +++ b/pype/plugins/houdini/publish/validate_alembic_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateAlembicInputNode(pyblish.api.InstancePlugin): @@ -11,8 +11,8 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + 0.1 - families = ["colorbleed.pointcache"] + order = pype.api.ValidateContentsOrder + 0.1 + families = ["pointcache"] hosts = ["houdini"] label = "Validate Input Node (Abc)" diff --git a/pype/plugins/houdini/publish/validate_animation_settings.py b/pype/plugins/houdini/publish/validate_animation_settings.py index 92955efbe8..238dfb4fa4 100644 --- a/pype/plugins/houdini/publish/validate_animation_settings.py +++ b/pype/plugins/houdini/publish/validate_animation_settings.py @@ -1,6 +1,6 @@ import pyblish.api -from colorbleed.houdini import lib +from pype.houdini import lib class ValidateAnimationSettings(pyblish.api.InstancePlugin): @@ -23,7 +23,7 @@ class ValidateAnimationSettings(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Validate Frame Settings" - families = ["colorbleed.vdbcache"] + families = ["vdbcache"] def process(self, instance): diff --git a/pype/plugins/houdini/publish/validate_bypass.py b/pype/plugins/houdini/publish/validate_bypass.py index 9af8a2b9ae..70505f0731 100644 --- a/pype/plugins/houdini/publish/validate_bypass.py +++ b/pype/plugins/houdini/publish/validate_bypass.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateBypassed(pyblish.api.InstancePlugin): @@ -11,7 +11,7 @@ class ValidateBypassed(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - 0.1 + order = pype.api.ValidateContentsOrder - 0.1 families = ["*"] hosts = ["houdini"] label = "Validate ROP Bypass" diff --git a/pype/plugins/houdini/publish/validate_camera_rop.py b/pype/plugins/houdini/publish/validate_camera_rop.py index 83f4dd5a57..48335cfb37 100644 --- a/pype/plugins/houdini/publish/validate_camera_rop.py +++ b/pype/plugins/houdini/publish/validate_camera_rop.py @@ -1,12 +1,12 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateCameraROP(pyblish.api.InstancePlugin): """Validate Camera ROP settings.""" - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.camera'] + order = pype.api.ValidateContentsOrder + families = ['camera'] hosts = ['houdini'] label = 'Camera ROP' diff --git a/pype/plugins/houdini/publish/validate_mkpaths_toggled.py b/pype/plugins/houdini/publish/validate_mkpaths_toggled.py index 826dedf933..21f50d7166 100644 --- a/pype/plugins/houdini/publish/validate_mkpaths_toggled.py +++ b/pype/plugins/houdini/publish/validate_mkpaths_toggled.py @@ -1,14 +1,14 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin): """Validate Create Intermediate Directories is enabled on ROP node.""" - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.pointcache', - 'colorbleed.camera', - 'colorbleed.vdbcache'] + order = pype.api.ValidateContentsOrder + families = ['pointcache', + 'camera', + 'vdbcache'] hosts = ['houdini'] label = 'Create Intermediate Directories Checked' diff --git a/pype/plugins/houdini/publish/validate_output_node.py b/pype/plugins/houdini/publish/validate_output_node.py index 4b140f63df..5e20ee40d6 100644 --- a/pype/plugins/houdini/publish/validate_output_node.py +++ b/pype/plugins/houdini/publish/validate_output_node.py @@ -14,8 +14,8 @@ class ValidateOutputNode(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder - families = ["colorbleed.pointcache", - "colorbleed.vdbcache"] + families = ["pointcache", + "vdbcache"] hosts = ["houdini"] label = "Validate Output Node" diff --git a/pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py b/pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py index ab91970b20..70e7873d3b 100644 --- a/pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py +++ b/pype/plugins/houdini/publish/validate_primitive_hierarchy_paths.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): @@ -11,8 +11,8 @@ class ValidatePrimitiveHierarchyPaths(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + 0.1 - families = ["colorbleed.pointcache"] + order = pype.api.ValidateContentsOrder + 0.1 + families = ["pointcache"] hosts = ["houdini"] label = "Validate Prims Hierarchy Path" diff --git a/pype/plugins/houdini/publish/validate_vdb_input_node.py b/pype/plugins/houdini/publish/validate_vdb_input_node.py index 24606046fa..e5bc118306 100644 --- a/pype/plugins/houdini/publish/validate_vdb_input_node.py +++ b/pype/plugins/houdini/publish/validate_vdb_input_node.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateVDBInputNode(pyblish.api.InstancePlugin): @@ -16,8 +16,8 @@ class ValidateVDBInputNode(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + 0.1 - families = ["colorbleed.vdbcache"] + order = pype.api.ValidateContentsOrder + 0.1 + families = ["vdbcache"] hosts = ["houdini"] label = "Validate Input Node (VDB)" diff --git a/pype/plugins/maya/create/colorbleed_animation.py b/pype/plugins/maya/create/colorbleed_animation.py index 16f321ee68..5eef2ac225 100644 --- a/pype/plugins/maya/create/colorbleed_animation.py +++ b/pype/plugins/maya/create/colorbleed_animation.py @@ -1,5 +1,5 @@ import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreateAnimation(avalon.maya.Creator): @@ -7,7 +7,7 @@ class CreateAnimation(avalon.maya.Creator): name = "animationDefault" label = "Animation" - family = "colorbleed.animation" + family = "animation" icon = "male" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_camera.py b/pype/plugins/maya/create/colorbleed_camera.py index f38d8e0d43..e472c2c2c6 100644 --- a/pype/plugins/maya/create/colorbleed_camera.py +++ b/pype/plugins/maya/create/colorbleed_camera.py @@ -1,5 +1,5 @@ import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreateCamera(avalon.maya.Creator): @@ -7,7 +7,7 @@ class CreateCamera(avalon.maya.Creator): name = "cameraDefault" label = "Camera" - family = "colorbleed.camera" + family = "camera" icon = "video-camera" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_fbx.py b/pype/plugins/maya/create/colorbleed_fbx.py index e6b57fa86f..a04d47c37d 100644 --- a/pype/plugins/maya/create/colorbleed_fbx.py +++ b/pype/plugins/maya/create/colorbleed_fbx.py @@ -1,5 +1,5 @@ import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreateFBX(avalon.maya.Creator): @@ -7,7 +7,7 @@ class CreateFBX(avalon.maya.Creator): name = "fbxDefault" label = "FBX" - family = "colorbleed.fbx" + family = "fbx" icon = "plug" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_look.py b/pype/plugins/maya/create/colorbleed_look.py index 011fdd4f92..23e4f034b2 100644 --- a/pype/plugins/maya/create/colorbleed_look.py +++ b/pype/plugins/maya/create/colorbleed_look.py @@ -1,5 +1,5 @@ import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreateLook(avalon.maya.Creator): @@ -7,7 +7,7 @@ class CreateLook(avalon.maya.Creator): name = "look" label = "Look" - family = "colorbleed.look" + family = "look" icon = "paint-brush" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_mayaascii.py b/pype/plugins/maya/create/colorbleed_mayaascii.py index d8cc9f9897..bf55c3d578 100644 --- a/pype/plugins/maya/create/colorbleed_mayaascii.py +++ b/pype/plugins/maya/create/colorbleed_mayaascii.py @@ -6,5 +6,5 @@ class CreateMayaAscii(avalon.maya.Creator): name = "mayaAscii" label = "Maya Ascii" - family = "colorbleed.mayaAscii" + family = "mayaAscii" icon = "file-archive-o" diff --git a/pype/plugins/maya/create/colorbleed_model.py b/pype/plugins/maya/create/colorbleed_model.py index 956f9f0e4f..449a5642be 100644 --- a/pype/plugins/maya/create/colorbleed_model.py +++ b/pype/plugins/maya/create/colorbleed_model.py @@ -6,7 +6,7 @@ class CreateModel(avalon.maya.Creator): name = "modelDefault" label = "Model" - family = "colorbleed.model" + family = "model" icon = "cube" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_pointcache.py b/pype/plugins/maya/create/colorbleed_pointcache.py index 2738ff6ddf..e5c82f7e10 100644 --- a/pype/plugins/maya/create/colorbleed_pointcache.py +++ b/pype/plugins/maya/create/colorbleed_pointcache.py @@ -1,5 +1,5 @@ import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreatePointCache(avalon.maya.Creator): @@ -7,7 +7,7 @@ class CreatePointCache(avalon.maya.Creator): name = "pointcache" label = "Point Cache" - family = "colorbleed.pointcache" + family = "pointcache" icon = "gears" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_renderglobals.py b/pype/plugins/maya/create/colorbleed_renderglobals.py index 9fc3e0ceab..6a8c6d3c72 100644 --- a/pype/plugins/maya/create/colorbleed_renderglobals.py +++ b/pype/plugins/maya/create/colorbleed_renderglobals.py @@ -1,6 +1,6 @@ from maya import cmds -import colorbleed.maya.lib as lib +import pype.maya.lib as lib from avalon.vendor import requests import avalon.maya @@ -10,7 +10,7 @@ from avalon import api class CreateRenderGlobals(avalon.maya.Creator): label = "Render Globals" - family = "colorbleed.renderglobals" + family = "renderglobals" icon = "gears" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_rig.py b/pype/plugins/maya/create/colorbleed_rig.py index 1212abb6ce..2c8f3d78b9 100644 --- a/pype/plugins/maya/create/colorbleed_rig.py +++ b/pype/plugins/maya/create/colorbleed_rig.py @@ -1,6 +1,6 @@ from maya import cmds -import colorbleed.maya.lib as lib +import pype.maya.lib as lib import avalon.maya @@ -9,7 +9,7 @@ class CreateRig(avalon.maya.Creator): name = "rigDefault" label = "Rig" - family = "colorbleed.rig" + family = "rig" icon = "wheelchair" def process(self): diff --git a/pype/plugins/maya/create/colorbleed_setdress.py b/pype/plugins/maya/create/colorbleed_setdress.py index 47089bea21..79f08ca04c 100644 --- a/pype/plugins/maya/create/colorbleed_setdress.py +++ b/pype/plugins/maya/create/colorbleed_setdress.py @@ -6,5 +6,5 @@ class CreateSetDress(avalon.maya.Creator): name = "setdress" label = "Set Dress" - family = "colorbleed.setdress" + family = "setdress" icon = "cubes" \ No newline at end of file diff --git a/pype/plugins/maya/create/colorbleed_vrayproxy.py b/pype/plugins/maya/create/colorbleed_vrayproxy.py index e866a1d092..704e72f04c 100644 --- a/pype/plugins/maya/create/colorbleed_vrayproxy.py +++ b/pype/plugins/maya/create/colorbleed_vrayproxy.py @@ -6,7 +6,7 @@ class CreateVrayProxy(avalon.maya.Creator): name = "vrayproxy" label = "VRay Proxy" - family = "colorbleed.vrayproxy" + family = "vrayproxy" icon = "gears" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_vrayscene.py b/pype/plugins/maya/create/colorbleed_vrayscene.py index 82f1b11682..df1c232858 100644 --- a/pype/plugins/maya/create/colorbleed_vrayscene.py +++ b/pype/plugins/maya/create/colorbleed_vrayscene.py @@ -4,7 +4,7 @@ import avalon.maya class CreateVRayScene(avalon.maya.Creator): label = "VRay Scene" - family = "colorbleed.vrayscene" + family = "vrayscene" icon = "cubes" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_yeti_cache.py b/pype/plugins/maya/create/colorbleed_yeti_cache.py index 2430821f5f..aaa94ce01d 100644 --- a/pype/plugins/maya/create/colorbleed_yeti_cache.py +++ b/pype/plugins/maya/create/colorbleed_yeti_cache.py @@ -1,7 +1,7 @@ from collections import OrderedDict import avalon.maya -from colorbleed.maya import lib +from pype.maya import lib class CreateYetiCache(avalon.maya.Creator): @@ -9,7 +9,7 @@ class CreateYetiCache(avalon.maya.Creator): name = "yetiDefault" label = "Yeti Cache" - family = "colorbleed.yeticache" + family = "yeticache" icon = "pagelines" def __init__(self, *args, **kwargs): diff --git a/pype/plugins/maya/create/colorbleed_yeti_rig.py b/pype/plugins/maya/create/colorbleed_yeti_rig.py index 55051100ad..c061084dd7 100644 --- a/pype/plugins/maya/create/colorbleed_yeti_rig.py +++ b/pype/plugins/maya/create/colorbleed_yeti_rig.py @@ -1,6 +1,6 @@ from maya import cmds -import colorbleed.maya.lib as lib +import pype.maya.lib as lib import avalon.maya @@ -8,7 +8,7 @@ class CreateYetiRig(avalon.maya.Creator): """Output for procedural plugin nodes ( Yeti / XGen / etc)""" label = "Yeti Rig" - family = "colorbleed.yetiRig" + family = "yetiRig" icon = "usb" def process(self): diff --git a/pype/plugins/maya/load/actions.py b/pype/plugins/maya/load/actions.py index 440fabf124..6db3c6ba34 100644 --- a/pype/plugins/maya/load/actions.py +++ b/pype/plugins/maya/load/actions.py @@ -8,9 +8,9 @@ from avalon import api class SetFrameRangeLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.pointcache"] + families = ["animation", + "camera", + "pointcache"] representations = ["abc"] label = "Set frame range" @@ -42,9 +42,9 @@ class SetFrameRangeLoader(api.Loader): class SetFrameRangeWithHandlesLoader(api.Loader): """Specific loader of Alembic for the avalon.animation family""" - families = ["colorbleed.animation", - "colorbleed.camera", - "colorbleed.pointcache"] + families = ["animation", + "camera", + "pointcache"] representations = ["abc"] label = "Set frame range (with handles)" diff --git a/pype/plugins/maya/load/load_alembic.py b/pype/plugins/maya/load/load_alembic.py index d34530a8d5..9e08702521 100644 --- a/pype/plugins/maya/load/load_alembic.py +++ b/pype/plugins/maya/load/load_alembic.py @@ -1,11 +1,11 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class AbcLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the colorbleed.animation family""" +class AbcLoader(pype.maya.plugin.ReferenceLoader): + """Specific loader of Alembic for the pype.animation family""" - families = ["colorbleed.animation", - "colorbleed.pointcache"] + families = ["animation", + "pointcache"] label = "Reference animation" representations = ["abc"] order = -10 diff --git a/pype/plugins/maya/load/load_camera.py b/pype/plugins/maya/load/load_camera.py index 067c4c0cde..eb75c3a63d 100644 --- a/pype/plugins/maya/load/load_camera.py +++ b/pype/plugins/maya/load/load_camera.py @@ -1,10 +1,10 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class CameraLoader(colorbleed.maya.plugin.ReferenceLoader): - """Specific loader of Alembic for the colorbleed.camera family""" +class CameraLoader(pype.maya.plugin.ReferenceLoader): + """Specific loader of Alembic for the pype.camera family""" - families = ["colorbleed.camera"] + families = ["camera"] label = "Reference camera" representations = ["abc", "ma"] order = -10 diff --git a/pype/plugins/maya/load/load_fbx.py b/pype/plugins/maya/load/load_fbx.py index 0dade5eca2..2ee3e5fdbd 100644 --- a/pype/plugins/maya/load/load_fbx.py +++ b/pype/plugins/maya/load/load_fbx.py @@ -1,10 +1,10 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class FBXLoader(colorbleed.maya.plugin.ReferenceLoader): +class FBXLoader(pype.maya.plugin.ReferenceLoader): """Load the FBX""" - families = ["colorbleed.fbx"] + families = ["fbx"] representations = ["fbx"] label = "Reference FBX" diff --git a/pype/plugins/maya/load/load_look.py b/pype/plugins/maya/load/load_look.py index cc13918ba1..eede195a49 100644 --- a/pype/plugins/maya/load/load_look.py +++ b/pype/plugins/maya/load/load_look.py @@ -1,10 +1,10 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class LookLoader(colorbleed.maya.plugin.ReferenceLoader): +class LookLoader(pype.maya.plugin.ReferenceLoader): """Specific loader for lookdev""" - families = ["colorbleed.look"] + families = ["look"] representations = ["ma"] label = "Reference look" diff --git a/pype/plugins/maya/load/load_mayaascii.py b/pype/plugins/maya/load/load_mayaascii.py index dd86cc97b5..283ecbd3a2 100644 --- a/pype/plugins/maya/load/load_mayaascii.py +++ b/pype/plugins/maya/load/load_mayaascii.py @@ -1,10 +1,10 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class MayaAsciiLoader(colorbleed.maya.plugin.ReferenceLoader): +class MayaAsciiLoader(pype.maya.plugin.ReferenceLoader): """Load the model""" - families = ["colorbleed.mayaAscii"] + families = ["mayaAscii"] representations = ["ma"] label = "Reference Maya Ascii" diff --git a/pype/plugins/maya/load/load_model.py b/pype/plugins/maya/load/load_model.py index 0d08c6ecbd..145a1a3e00 100644 --- a/pype/plugins/maya/load/load_model.py +++ b/pype/plugins/maya/load/load_model.py @@ -1,11 +1,11 @@ from avalon import api -import colorbleed.maya.plugin +import pype.maya.plugin -class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): +class ModelLoader(pype.maya.plugin.ReferenceLoader): """Load the model""" - families = ["colorbleed.model"] + families = ["model"] representations = ["ma"] label = "Reference Model" @@ -37,7 +37,7 @@ class ModelLoader(colorbleed.maya.plugin.ReferenceLoader): class GpuCacheLoader(api.Loader): """Load model Alembic as gpuCache""" - families = ["colorbleed.model"] + families = ["model"] representations = ["abc"] label = "Import Gpu Cache" diff --git a/pype/plugins/maya/load/load_rig.py b/pype/plugins/maya/load/load_rig.py index 1f3e83f60d..aa40ca3cc2 100644 --- a/pype/plugins/maya/load/load_rig.py +++ b/pype/plugins/maya/load/load_rig.py @@ -1,17 +1,17 @@ from maya import cmds -import colorbleed.maya.plugin +import pype.maya.plugin from avalon import api, maya -class RigLoader(colorbleed.maya.plugin.ReferenceLoader): +class RigLoader(pype.maya.plugin.ReferenceLoader): """Specific loader for rigs This automatically creates an instance for animators upon load. """ - families = ["colorbleed.rig"] + families = ["rig"] representations = ["ma"] label = "Reference rig" @@ -62,7 +62,7 @@ class RigLoader(colorbleed.maya.plugin.ReferenceLoader): cmds.select([output, controls] + roots, noExpand=True) api.create(name=namespace, asset=asset, - family="colorbleed.animation", + family="animation", options={"useSelection": True}, data={"dependencies": dependency}) diff --git a/pype/plugins/maya/load/load_setdress.py b/pype/plugins/maya/load/load_setdress.py index 8cc857fc3f..40785613be 100644 --- a/pype/plugins/maya/load/load_setdress.py +++ b/pype/plugins/maya/load/load_setdress.py @@ -3,7 +3,7 @@ from avalon import api class SetDressLoader(api.Loader): - families = ["colorbleed.setdress"] + families = ["setdress"] representations = ["json"] label = "Load Set Dress" @@ -23,7 +23,7 @@ class SetDressLoader(api.Loader): suffix="_", ) - from colorbleed import setdress_api + from pype import setdress_api containers = setdress_api.load_package(filepath=self.fname, name=name, @@ -45,7 +45,7 @@ class SetDressLoader(api.Loader): def update(self, container, representation): - from colorbleed import setdress_api + from pype import setdress_api return setdress_api.update_package(container, representation) @@ -53,7 +53,7 @@ class SetDressLoader(api.Loader): """Remove all sub containers""" from avalon import api - from colorbleed import setdress_api + from pype import setdress_api import maya.cmds as cmds # Remove all members diff --git a/pype/plugins/maya/load/load_vdb_to_redshift.py b/pype/plugins/maya/load/load_vdb_to_redshift.py index bafb529bda..8ff8bc0326 100644 --- a/pype/plugins/maya/load/load_vdb_to_redshift.py +++ b/pype/plugins/maya/load/load_vdb_to_redshift.py @@ -4,7 +4,7 @@ from avalon import api class LoadVDBtoRedShift(api.Loader): """Load OpenVDB in a Redshift Volume Shape""" - families = ["colorbleed.vdbcache"] + families = ["vdbcache"] representations = ["vdb"] label = "Load VDB to RedShift" diff --git a/pype/plugins/maya/load/load_vdb_to_vray.py b/pype/plugins/maya/load/load_vdb_to_vray.py index 5d868174ce..ac20b0eb43 100644 --- a/pype/plugins/maya/load/load_vdb_to_vray.py +++ b/pype/plugins/maya/load/load_vdb_to_vray.py @@ -3,7 +3,7 @@ from avalon import api class LoadVDBtoVRay(api.Loader): - families = ["colorbleed.vdbcache"] + families = ["vdbcache"] representations = ["vdb"] label = "Load VDB to VRay" diff --git a/pype/plugins/maya/load/load_vrayproxy.py b/pype/plugins/maya/load/load_vrayproxy.py index 6bf9bdb484..9396e124ce 100644 --- a/pype/plugins/maya/load/load_vrayproxy.py +++ b/pype/plugins/maya/load/load_vrayproxy.py @@ -7,7 +7,7 @@ import maya.cmds as cmds class VRayProxyLoader(api.Loader): """Load VRayMesh proxy""" - families = ["colorbleed.vrayproxy"] + families = ["vrayproxy"] representations = ["vrmesh"] label = "Import VRay Proxy" @@ -18,7 +18,7 @@ class VRayProxyLoader(api.Loader): def load(self, context, name, namespace, data): from avalon.maya.pipeline import containerise - from colorbleed.maya.lib import namespaced + from pype.maya.lib import namespaced asset_name = context['asset']["name"] namespace = namespace or lib.unique_namespace( diff --git a/pype/plugins/maya/load/load_yeti_cache.py b/pype/plugins/maya/load/load_yeti_cache.py index 5a1e404975..2160924047 100644 --- a/pype/plugins/maya/load/load_yeti_cache.py +++ b/pype/plugins/maya/load/load_yeti_cache.py @@ -8,12 +8,12 @@ from maya import cmds from avalon import api from avalon.maya import lib as avalon_lib, pipeline -from colorbleed.maya import lib +from pype.maya import lib class YetiCacheLoader(api.Loader): - families = ["colorbleed.yeticache", "colorbleed.yetiRig"] + families = ["yeticache", "yetiRig"] representations = ["fur"] label = "Load Yeti Cache" diff --git a/pype/plugins/maya/load/load_yeti_rig.py b/pype/plugins/maya/load/load_yeti_rig.py index 5043b1cbe0..096b936b41 100644 --- a/pype/plugins/maya/load/load_yeti_rig.py +++ b/pype/plugins/maya/load/load_yeti_rig.py @@ -1,9 +1,9 @@ -import colorbleed.maya.plugin +import pype.maya.plugin -class YetiRigLoader(colorbleed.maya.plugin.ReferenceLoader): +class YetiRigLoader(pype.maya.plugin.ReferenceLoader): - families = ["colorbleed.yetiRig"] + families = ["yetiRig"] representations = ["ma"] label = "Load Yeti Rig" diff --git a/pype/plugins/maya/publish/collect_animation.py b/pype/plugins/maya/publish/collect_animation.py index c724759180..349fa8e649 100644 --- a/pype/plugins/maya/publish/collect_animation.py +++ b/pype/plugins/maya/publish/collect_animation.py @@ -16,7 +16,7 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.animation"] + families = ["animation"] label = "Collect Animation Output Geometry" hosts = ["maya"] diff --git a/pype/plugins/maya/publish/collect_history.py b/pype/plugins/maya/publish/collect_history.py index 840e916133..16c8e4342e 100644 --- a/pype/plugins/maya/publish/collect_history.py +++ b/pype/plugins/maya/publish/collect_history.py @@ -17,7 +17,7 @@ class CollectMayaHistory(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.1 hosts = ["maya"] label = "Maya History" - families = ["colorbleed.rig"] + families = ["rig"] verbose = False def process(self, instance): diff --git a/pype/plugins/maya/publish/collect_instances.py b/pype/plugins/maya/publish/collect_instances.py index 6ca3f26da7..48f4889a51 100644 --- a/pype/plugins/maya/publish/collect_instances.py +++ b/pype/plugins/maya/publish/collect_instances.py @@ -19,7 +19,7 @@ class CollectInstances(pyblish.api.ContextPlugin): be able to achieve and the amount of data available to validators. An additional collector could also append this input data into the instance, as we do - for `colorbleed.rig` with collect_history. + for `pype.rig` with collect_history. """ diff --git a/pype/plugins/maya/publish/collect_look.py b/pype/plugins/maya/publish/collect_look.py index 1decca6e2e..cbfecdc0ad 100644 --- a/pype/plugins/maya/publish/collect_look.py +++ b/pype/plugins/maya/publish/collect_look.py @@ -4,7 +4,7 @@ import glob from maya import cmds import pyblish.api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib SHAPE_ATTRS = ["castsShadows", "receiveShadows", @@ -203,7 +203,7 @@ class CollectLook(pyblish.api.InstancePlugin): """ order = pyblish.api.CollectorOrder + 0.4 - families = ["colorbleed.look"] + families = ["look"] label = "Collect Look" hosts = ["maya"] diff --git a/pype/plugins/maya/publish/collect_model.py b/pype/plugins/maya/publish/collect_model.py index cd780acc7f..403500ff97 100644 --- a/pype/plugins/maya/publish/collect_model.py +++ b/pype/plugins/maya/publish/collect_model.py @@ -9,7 +9,7 @@ class CollectModelData(pyblish.api.InstancePlugin): Ensures always only a single frame is extracted (current frame). Note: - This is a workaround so that the `colorbleed.model` family can use the + This is a workaround so that the `pype.model` family can use the same pointcache extractor implementation as animation and pointcaches. This always enforces the "current" frame to be published. @@ -17,7 +17,7 @@ class CollectModelData(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.499 label = 'Collect Model Data' - families = ["colorbleed.model"] + families = ["model"] def process(self, instance): # Extract only current frame (override) diff --git a/pype/plugins/maya/publish/collect_render_layer_aovs.py b/pype/plugins/maya/publish/collect_render_layer_aovs.py index e2153e0ee0..d8d2995515 100644 --- a/pype/plugins/maya/publish/collect_render_layer_aovs.py +++ b/pype/plugins/maya/publish/collect_render_layer_aovs.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): @@ -27,7 +27,7 @@ class CollectRenderLayerAOVS(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Render Elements / AOVs" hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, instance): diff --git a/pype/plugins/maya/publish/collect_renderable_camera.py b/pype/plugins/maya/publish/collect_renderable_camera.py index 8cf33bd73a..9bfc010204 100644 --- a/pype/plugins/maya/publish/collect_renderable_camera.py +++ b/pype/plugins/maya/publish/collect_renderable_camera.py @@ -2,7 +2,7 @@ import pyblish.api from maya import cmds -from colorbleed.maya import lib +from pype.maya import lib class CollectRenderableCamera(pyblish.api.InstancePlugin): @@ -11,8 +11,8 @@ class CollectRenderableCamera(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.01 label = "Collect Renderable Camera(s)" hosts = ["maya"] - families = ["colorbleed.vrayscene", - "colorbleed.renderlayer"] + families = ["vrayscene", + "renderlayer"] def process(self, instance): layer = instance.data["setMembers"] diff --git a/pype/plugins/maya/publish/collect_renderlayers.py b/pype/plugins/maya/publish/collect_renderlayers.py index 369b21de02..c926baf803 100644 --- a/pype/plugins/maya/publish/collect_renderlayers.py +++ b/pype/plugins/maya/publish/collect_renderlayers.py @@ -3,7 +3,7 @@ from maya import cmds import pyblish.api from avalon import maya, api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib class CollectMayaRenderlayers(pyblish.api.ContextPlugin): @@ -72,7 +72,7 @@ class CollectMayaRenderlayers(pyblish.api.ContextPlugin): # instance subset "family": "Render Layers", - "families": ["colorbleed.renderlayer"], + "families": ["renderlayer"], "asset": asset, "time": api.time(), "author": context.data["user"], diff --git a/pype/plugins/maya/publish/collect_setdress.py b/pype/plugins/maya/publish/collect_setdress.py index e5bddac887..bb56163293 100644 --- a/pype/plugins/maya/publish/collect_setdress.py +++ b/pype/plugins/maya/publish/collect_setdress.py @@ -3,7 +3,7 @@ import pyblish.api from maya import cmds, mel from avalon import maya as avalon -from colorbleed.maya import lib +from pype.maya import lib # TODO : Publish of setdress: -unique namespace for all assets, VALIDATOR! @@ -25,7 +25,7 @@ class CollectSetDress(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.49 label = "Set Dress" - families = ["colorbleed.setdress"] + families = ["setdress"] def process(self, instance): diff --git a/pype/plugins/maya/publish/collect_vray_scene.py b/pype/plugins/maya/publish/collect_vray_scene.py index e4a9c42364..89c0aa8670 100644 --- a/pype/plugins/maya/publish/collect_vray_scene.py +++ b/pype/plugins/maya/publish/collect_vray_scene.py @@ -27,10 +27,10 @@ class CollectVRayScene(pyblish.api.ContextPlugin): work_dir = context.data["workspaceDir"] # Get VRay Scene instance - vray_scenes = host.lsattr("family", "colorbleed.vrayscene") + vray_scenes = host.lsattr("family", "vrayscene") if not vray_scenes: self.log.info("Skipping vrayScene collection, no " - "colorbleed.vrayscene instance found..") + "vrayscene instance found..") return assert len(vray_scenes) == 1, "Multiple vrayscene instances found!" @@ -90,7 +90,7 @@ class CollectVRayScene(pyblish.api.ContextPlugin): # instance subset "family": "VRay Scene", - "families": ["colorbleed.vrayscene"], + "families": ["vrayscene"], "asset": asset, "time": api.time(), "author": context.data["user"], diff --git a/pype/plugins/maya/publish/collect_yeti_cache.py b/pype/plugins/maya/publish/collect_yeti_cache.py index 550c780797..2365162c05 100644 --- a/pype/plugins/maya/publish/collect_yeti_cache.py +++ b/pype/plugins/maya/publish/collect_yeti_cache.py @@ -2,7 +2,7 @@ from maya import cmds import pyblish.api -from colorbleed.maya import lib +from pype.maya import lib SETTINGS = {"renderDensity", "renderWidth", @@ -28,7 +28,7 @@ class CollectYetiCache(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.45 label = "Collect Yeti Cache" - families = ["colorbleed.yetiRig", "colorbleed.yeticache"] + families = ["yetiRig", "yeticache"] hosts = ["maya"] tasks = ["animation", "fx"] diff --git a/pype/plugins/maya/publish/collect_yeti_rig.py b/pype/plugins/maya/publish/collect_yeti_rig.py index e6d4ab803e..469651a891 100644 --- a/pype/plugins/maya/publish/collect_yeti_rig.py +++ b/pype/plugins/maya/publish/collect_yeti_rig.py @@ -5,8 +5,8 @@ from maya import cmds import pyblish.api -from colorbleed.maya import lib -from colorbleed.lib import pairwise +from pype.maya import lib +from pype.lib import pairwise SETTINGS = {"renderDensity", @@ -22,7 +22,7 @@ class CollectYetiRig(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder + 0.4 label = "Collect Yeti Rig" - families = ["colorbleed.yetiRig"] + families = ["yetiRig"] hosts = ["maya"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_animation.py b/pype/plugins/maya/publish/extract_animation.py index be56c6c937..abec8b8b49 100644 --- a/pype/plugins/maya/publish/extract_animation.py +++ b/pype/plugins/maya/publish/extract_animation.py @@ -3,11 +3,11 @@ import os from maya import cmds import avalon.maya -import colorbleed.api -from colorbleed.maya.lib import extract_alembic +import pype.api +from pype.maya.lib import extract_alembic -class ExtractColorbleedAnimation(colorbleed.api.Extractor): +class ExtractColorbleedAnimation(pype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, @@ -17,7 +17,7 @@ class ExtractColorbleedAnimation(colorbleed.api.Extractor): label = "Extract Animation" hosts = ["maya"] - families = ["colorbleed.animation"] + families = ["animation"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_camera_alembic.py b/pype/plugins/maya/publish/extract_camera_alembic.py index 7ed65e0d44..90f4954823 100644 --- a/pype/plugins/maya/publish/extract_camera_alembic.py +++ b/pype/plugins/maya/publish/extract_camera_alembic.py @@ -3,12 +3,12 @@ import os from maya import cmds import avalon.maya -import colorbleed.api +import pype.api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib -class ExtractCameraAlembic(colorbleed.api.Extractor): +class ExtractCameraAlembic(pype.api.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's @@ -18,7 +18,7 @@ class ExtractCameraAlembic(colorbleed.api.Extractor): label = "Camera (Alembic)" hosts = ["maya"] - families = ["colorbleed.camera"] + families = ["camera"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_camera_mayaAscii.py b/pype/plugins/maya/publish/extract_camera_mayaAscii.py index e8a67d4f19..4a21e7bf58 100644 --- a/pype/plugins/maya/publish/extract_camera_mayaAscii.py +++ b/pype/plugins/maya/publish/extract_camera_mayaAscii.py @@ -3,9 +3,9 @@ import os from maya import cmds import avalon.maya -import colorbleed.api -from colorbleed.lib import grouper -from colorbleed.maya import lib +import pype.api +from pype.lib import grouper +from pype.maya import lib def massage_ma_file(path): @@ -65,7 +65,7 @@ def unlock(plug): cmds.disconnectAttr(source, destination) -class ExtractCameraMayaAscii(colorbleed.api.Extractor): +class ExtractCameraMayaAscii(pype.api.Extractor): """Extract a Camera as Maya Ascii. This will create a duplicate of the camera that will be baked *with* @@ -83,7 +83,7 @@ class ExtractCameraMayaAscii(colorbleed.api.Extractor): label = "Camera (Maya Ascii)" hosts = ["maya"] - families = ["colorbleed.camera"] + families = ["camera"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_fbx.py b/pype/plugins/maya/publish/extract_fbx.py index 139c203672..48dd5a135c 100644 --- a/pype/plugins/maya/publish/extract_fbx.py +++ b/pype/plugins/maya/publish/extract_fbx.py @@ -6,10 +6,10 @@ import maya.mel as mel import pyblish.api import avalon.maya -import colorbleed.api +import pype.api -class ExtractFBX(colorbleed.api.Extractor): +class ExtractFBX(pype.api.Extractor): """Extract FBX from Maya. This extracts reproducible FBX exports ignoring any of the settings set @@ -31,7 +31,7 @@ class ExtractFBX(colorbleed.api.Extractor): order = pyblish.api.ExtractorOrder label = "Extract FBX" - families = ["colorbleed.fbx"] + families = ["fbx"] @property def options(self): diff --git a/pype/plugins/maya/publish/extract_look.py b/pype/plugins/maya/publish/extract_look.py index 04db69a3bd..a30b1fe7d5 100644 --- a/pype/plugins/maya/publish/extract_look.py +++ b/pype/plugins/maya/publish/extract_look.py @@ -9,8 +9,8 @@ from maya import cmds import pyblish.api import avalon.maya -import colorbleed.api -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.lib as lib @contextlib.contextmanager @@ -45,7 +45,7 @@ def no_workspace_dir(): os.rmdir(fake_workspace_dir) -class ExtractLook(colorbleed.api.Extractor): +class ExtractLook(pype.api.Extractor): """Extract Look (Maya Ascii + JSON) Only extracts the sets (shadingEngines and alike) alongside a .json file @@ -56,7 +56,7 @@ class ExtractLook(colorbleed.api.Extractor): label = "Extract Look (Maya ASCII + JSON)" hosts = ["maya"] - families = ["colorbleed.look"] + families = ["look"] order = pyblish.api.ExtractorOrder + 0.2 def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_maya_ascii_raw.py b/pype/plugins/maya/publish/extract_maya_ascii_raw.py index bcbd39fc3d..ba5d25cc88 100644 --- a/pype/plugins/maya/publish/extract_maya_ascii_raw.py +++ b/pype/plugins/maya/publish/extract_maya_ascii_raw.py @@ -3,10 +3,10 @@ import os from maya import cmds import avalon.maya -import colorbleed.api +import pype.api -class ExtractMayaAsciiRaw(colorbleed.api.Extractor): +class ExtractMayaAsciiRaw(pype.api.Extractor): """Extract as Maya Ascii (raw) This will preserve all references, construction history, etc. @@ -15,7 +15,7 @@ class ExtractMayaAsciiRaw(colorbleed.api.Extractor): label = "Maya ASCII (Raw)" hosts = ["maya"] - families = ["colorbleed.mayaAscii"] + families = ["mayaAscii"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_model.py b/pype/plugins/maya/publish/extract_model.py index 466ee73174..302a899793 100644 --- a/pype/plugins/maya/publish/extract_model.py +++ b/pype/plugins/maya/publish/extract_model.py @@ -3,11 +3,11 @@ import os from maya import cmds import avalon.maya -import colorbleed.api -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.lib as lib -class ExtractModel(colorbleed.api.Extractor): +class ExtractModel(pype.api.Extractor): """Extract as Model (Maya Ascii) Only extracts contents based on the original "setMembers" data to ensure @@ -24,7 +24,7 @@ class ExtractModel(colorbleed.api.Extractor): label = "Model (Maya ASCII)" hosts = ["maya"] - families = ["colorbleed.model"] + families = ["model"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_pointcache.py b/pype/plugins/maya/publish/extract_pointcache.py index 3fcdf3cb58..e703089287 100644 --- a/pype/plugins/maya/publish/extract_pointcache.py +++ b/pype/plugins/maya/publish/extract_pointcache.py @@ -3,11 +3,11 @@ import os from maya import cmds import avalon.maya -import colorbleed.api -from colorbleed.maya.lib import extract_alembic +import pype.api +from pype.maya.lib import extract_alembic -class ExtractColorbleedAlembic(colorbleed.api.Extractor): +class ExtractColorbleedAlembic(pype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, @@ -17,8 +17,8 @@ class ExtractColorbleedAlembic(colorbleed.api.Extractor): label = "Extract Pointcache (Alembic)" hosts = ["maya"] - families = ["colorbleed.pointcache", - "colorbleed.model"] + families = ["pointcache", + "model"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_rig.py b/pype/plugins/maya/publish/extract_rig.py index 7db6adfe43..6a92111bc7 100644 --- a/pype/plugins/maya/publish/extract_rig.py +++ b/pype/plugins/maya/publish/extract_rig.py @@ -3,15 +3,15 @@ import os from maya import cmds import avalon.maya -import colorbleed.api +import pype.api -class ExtractColorbleedRig(colorbleed.api.Extractor): +class ExtractColorbleedRig(pype.api.Extractor): """Extract rig as Maya Ascii""" label = "Extract Rig (Maya ASCII)" hosts = ["maya"] - families = ["colorbleed.rig"] + families = ["rig"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_setdress.py b/pype/plugins/maya/publish/extract_setdress.py index 444e8f3d02..c4d613dc61 100644 --- a/pype/plugins/maya/publish/extract_setdress.py +++ b/pype/plugins/maya/publish/extract_setdress.py @@ -2,13 +2,13 @@ import json import os -import colorbleed.api -from colorbleed.maya.lib import extract_alembic +import pype.api +from pype.maya.lib import extract_alembic from maya import cmds -class ExtractSetDress(colorbleed.api.Extractor): +class ExtractSetDress(pype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -18,7 +18,7 @@ class ExtractSetDress(colorbleed.api.Extractor): label = "Extract Set Dress" hosts = ["maya"] - families = ["colorbleed.setdress"] + families = ["setdress"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_vrayproxy.py b/pype/plugins/maya/publish/extract_vrayproxy.py index 265828b20d..5c68f3ca81 100644 --- a/pype/plugins/maya/publish/extract_vrayproxy.py +++ b/pype/plugins/maya/publish/extract_vrayproxy.py @@ -1,12 +1,12 @@ import os import avalon.maya -import colorbleed.api +import pype.api from maya import cmds -class ExtractVRayProxy(colorbleed.api.Extractor): +class ExtractVRayProxy(pype.api.Extractor): """Extract the content of the instance to a vrmesh file Things to pay attention to: @@ -16,7 +16,7 @@ class ExtractVRayProxy(colorbleed.api.Extractor): label = "VRay Proxy (.vrmesh)" hosts = ["maya"] - families = ["colorbleed.vrayproxy"] + families = ["vrayproxy"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_yeti_cache.py b/pype/plugins/maya/publish/extract_yeti_cache.py index 481586374a..e8cbb02b25 100644 --- a/pype/plugins/maya/publish/extract_yeti_cache.py +++ b/pype/plugins/maya/publish/extract_yeti_cache.py @@ -3,10 +3,10 @@ import json from maya import cmds -import colorbleed.api +import pype.api -class ExtractYetiCache(colorbleed.api.Extractor): +class ExtractYetiCache(pype.api.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, @@ -16,7 +16,7 @@ class ExtractYetiCache(colorbleed.api.Extractor): label = "Extract Yeti Cache" hosts = ["maya"] - families = ["colorbleed.yetiRig", "colorbleed.yeticache"] + families = ["yetiRig", "yeticache"] def process(self, instance): diff --git a/pype/plugins/maya/publish/extract_yeti_rig.py b/pype/plugins/maya/publish/extract_yeti_rig.py index 68fe9b118d..904ec2314e 100644 --- a/pype/plugins/maya/publish/extract_yeti_rig.py +++ b/pype/plugins/maya/publish/extract_yeti_rig.py @@ -5,8 +5,8 @@ import contextlib from maya import cmds import avalon.maya.lib as lib -import colorbleed.api -import colorbleed.maya.lib as maya +import pype.api +import pype.maya.lib as maya @contextlib.contextmanager @@ -88,12 +88,12 @@ def yetigraph_attribute_values(assumed_destination, resources): pass -class ExtractYetiRig(colorbleed.api.Extractor): +class ExtractYetiRig(pype.api.Extractor): """Extract the Yeti rig to a MayaAscii and write the Yeti rig data""" label = "Extract Yeti Rig" hosts = ["maya"] - families = ["colorbleed.yetiRig"] + families = ["yetiRig"] def process(self, instance): diff --git a/pype/plugins/maya/publish/increment_current_file_deadline.py b/pype/plugins/maya/publish/increment_current_file_deadline.py index 40a7634fb7..527f3d781d 100644 --- a/pype/plugins/maya/publish/increment_current_file_deadline.py +++ b/pype/plugins/maya/publish/increment_current_file_deadline.py @@ -11,16 +11,16 @@ class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["maya"] - families = ["colorbleed.renderlayer", - "colorbleed.vrayscene"] + families = ["renderlayer", + "vrayscene"] optional = True def process(self, context): import os from maya import cmds - from colorbleed.lib import version_up - from colorbleed.action import get_errored_plugins_from_data + from pype.lib import version_up + from pype.action import get_errored_plugins_from_data errored_plugins = get_errored_plugins_from_data(context) if any(plugin.__name__ == "MayaSubmitDeadline" diff --git a/pype/plugins/maya/publish/save_scene.py b/pype/plugins/maya/publish/save_scene.py index bf80bc9699..d3e1597e78 100644 --- a/pype/plugins/maya/publish/save_scene.py +++ b/pype/plugins/maya/publish/save_scene.py @@ -9,7 +9,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): label = "Save current file" order = pyblish.api.IntegratorOrder - 0.49 hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, context): import maya.cmds as cmds diff --git a/pype/plugins/maya/publish/submit_maya_deadline.py b/pype/plugins/maya/publish/submit_maya_deadline.py index 430f121779..6a6cabdf93 100644 --- a/pype/plugins/maya/publish/submit_maya_deadline.py +++ b/pype/plugins/maya/publish/submit_maya_deadline.py @@ -9,7 +9,7 @@ from avalon.vendor import requests import pyblish.api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib def get_renderer_variables(renderlayer=None): @@ -99,7 +99,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, instance): diff --git a/pype/plugins/maya/publish/submit_vray_deadline.py b/pype/plugins/maya/publish/submit_vray_deadline.py index 7036a9f1eb..d43e92c6d8 100644 --- a/pype/plugins/maya/publish/submit_vray_deadline.py +++ b/pype/plugins/maya/publish/submit_vray_deadline.py @@ -24,7 +24,7 @@ class VraySubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline ( vrscene )" order = pyblish.api.IntegratorOrder hosts = ["maya"] - families = ["colorbleed.vrayscene"] + families = ["vrayscene"] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_animation_content.py b/pype/plugins/maya/publish/validate_animation_content.py index b595f14d4a..4dd56e9c09 100644 --- a/pype/plugins/maya/publish/validate_animation_content.py +++ b/pype/plugins/maya/publish/validate_animation_content.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateAnimationContent(pyblish.api.InstancePlugin): @@ -11,11 +11,11 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ["maya"] - families = ["colorbleed.animation"] + families = ["animation"] label = "Animation Content" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py b/pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py index 0c05d211ca..d41b13252f 100644 --- a/pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py +++ b/pype/plugins/maya/publish/validate_animation_out_set_related_node_ids.py @@ -1,9 +1,9 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): @@ -16,11 +16,11 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.animation', "colorbleed.pointcache"] + order = pype.api.ValidateContentsOrder + families = ['animation', "pointcache"] hosts = ['maya'] label = 'Animation Out Set Related Node Ids' - actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, pype.api.RepairAction] def process(self, instance): """Process all meshes""" diff --git a/pype/plugins/maya/publish/validate_camera_attributes.py b/pype/plugins/maya/publish/validate_camera_attributes.py index 9c43e9e6ed..8223d87c5d 100644 --- a/pype/plugins/maya/publish/validate_camera_attributes.py +++ b/pype/plugins/maya/publish/validate_camera_attributes.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateCameraAttributes(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.camera'] + order = pype.api.ValidateContentsOrder + families = ['camera'] hosts = ['maya'] label = 'Camera Attributes' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] DEFAULTS = [ ("filmFitOffset", 0.0), diff --git a/pype/plugins/maya/publish/validate_camera_contents.py b/pype/plugins/maya/publish/validate_camera_contents.py index 0b7ba9735d..f3e2a6c121 100644 --- a/pype/plugins/maya/publish/validate_camera_contents.py +++ b/pype/plugins/maya/publish/validate_camera_contents.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateCameraContents(pyblish.api.InstancePlugin): @@ -15,11 +15,11 @@ class ValidateCameraContents(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.camera'] + order = pype.api.ValidateContentsOrder + families = ['camera'] hosts = ['maya'] label = 'Camera Contents' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/plugins/maya/publish/validate_current_renderlayer_renderable.py b/pype/plugins/maya/publish/validate_current_renderlayer_renderable.py index 8f09941f06..aa6ffa555c 100644 --- a/pype/plugins/maya/publish/validate_current_renderlayer_renderable.py +++ b/pype/plugins/maya/publish/validate_current_renderlayer_renderable.py @@ -1,7 +1,7 @@ import pyblish.api from maya import cmds -from colorbleed.plugin import contextplugin_should_run +from pype.plugin import contextplugin_should_run class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): @@ -19,7 +19,7 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin): label = "Current Render Layer Has Renderable Camera" order = pyblish.api.ValidatorOrder hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, context): diff --git a/pype/plugins/maya/publish/validate_deadline_connection.py b/pype/plugins/maya/publish/validate_deadline_connection.py index 0a962e7afc..afd8564c07 100644 --- a/pype/plugins/maya/publish/validate_deadline_connection.py +++ b/pype/plugins/maya/publish/validate_deadline_connection.py @@ -2,7 +2,7 @@ import pyblish.api import avalon.api as api from avalon.vendor import requests -from colorbleed.plugin import contextplugin_should_run +from pype.plugin import contextplugin_should_run class ValidateDeadlineConnection(pyblish.api.ContextPlugin): @@ -11,7 +11,7 @@ class ValidateDeadlineConnection(pyblish.api.ContextPlugin): label = "Validate Deadline Web Service" order = pyblish.api.ValidatorOrder hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, context): diff --git a/pype/plugins/maya/publish/validate_frame_range.py b/pype/plugins/maya/publish/validate_frame_range.py index 05b64ccf17..78bce85b96 100644 --- a/pype/plugins/maya/publish/validate_frame_range.py +++ b/pype/plugins/maya/publish/validate_frame_range.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateFrameRange(pyblish.api.InstancePlugin): @@ -16,11 +16,11 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): """ label = "Validate Frame Range" - order = colorbleed.api.ValidateContentsOrder - families = ["colorbleed.animation", - "colorbleed.pointcache", - "colorbleed.camera", - "colorbleed.renderlayer", + order = pype.api.ValidateContentsOrder + families = ["animation", + "pointcache", + "camera", + "renderlayer", "oolorbleed.vrayproxy"] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_instance_has_members.py b/pype/plugins/maya/publish/validate_instance_has_members.py index e0ea93b85c..76c93b4c85 100644 --- a/pype/plugins/maya/publish/validate_instance_has_members.py +++ b/pype/plugins/maya/publish/validate_instance_has_members.py @@ -1,15 +1,15 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateInstanceHasMembers(pyblish.api.InstancePlugin): """Validates instance objectSet has *any* members.""" - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ["maya"] label = 'Instance has members' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/plugins/maya/publish/validate_instance_subset.py b/pype/plugins/maya/publish/validate_instance_subset.py index 6a26d09594..19c60e4f64 100644 --- a/pype/plugins/maya/publish/validate_instance_subset.py +++ b/pype/plugins/maya/publish/validate_instance_subset.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api import string # Allow only characters, numbers and underscore @@ -16,7 +16,7 @@ def validate_name(subset): class ValidateSubsetName(pyblish.api.InstancePlugin): """Validates subset name has only valid characters""" - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder families = ["*"] label = "Subset Name" diff --git a/pype/plugins/maya/publish/validate_instancer_content.py b/pype/plugins/maya/publish/validate_instancer_content.py index 91b7b15044..68ee17bca6 100644 --- a/pype/plugins/maya/publish/validate_instancer_content.py +++ b/pype/plugins/maya/publish/validate_instancer_content.py @@ -1,7 +1,7 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.maya.lib as lib +import pype.maya.lib as lib class ValidateInstancerContent(pyblish.api.InstancePlugin): @@ -12,7 +12,7 @@ class ValidateInstancerContent(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder label = 'Instancer Content' - families = ['colorbleed.instancer'] + families = ['instancer'] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_instancer_frame_ranges.py b/pype/plugins/maya/publish/validate_instancer_frame_ranges.py index c7307c5a2a..f31014e6e9 100644 --- a/pype/plugins/maya/publish/validate_instancer_frame_ranges.py +++ b/pype/plugins/maya/publish/validate_instancer_frame_ranges.py @@ -44,7 +44,7 @@ class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin): """ order = pyblish.api.ValidatorOrder label = 'Instancer Cache Frame Ranges' - families = ['colorbleed.instancer'] + families = ['instancer'] @classmethod def get_invalid(cls, instance): diff --git a/pype/plugins/maya/publish/validate_joints_hidden.py b/pype/plugins/maya/publish/validate_joints_hidden.py index 1c8e44e843..4b20060602 100644 --- a/pype/plugins/maya/publish/validate_joints_hidden.py +++ b/pype/plugins/maya/publish/validate_joints_hidden.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateJointsHidden(pyblish.api.InstancePlugin): @@ -17,14 +17,14 @@ class ValidateJointsHidden(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.rig'] + families = ['rig'] category = 'rig' version = (0, 1, 0) label = "Joints Hidden" - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_look_contents.py b/pype/plugins/maya/publish/validate_look_contents.py index 2f515e970c..72fc884bfb 100644 --- a/pype/plugins/maya/publish/validate_look_contents.py +++ b/pype/plugins/maya/publish/validate_look_contents.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateLookContents(pyblish.api.InstancePlugin): @@ -17,11 +17,11 @@ class ValidateLookContents(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Look Data Contents' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/pype/plugins/maya/publish/validate_look_default_shaders_connections.py b/pype/plugins/maya/publish/validate_look_default_shaders_connections.py index dc8e7446c9..af355e178b 100644 --- a/pype/plugins/maya/publish/validate_look_default_shaders_connections.py +++ b/pype/plugins/maya/publish/validate_look_default_shaders_connections.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import colorbleed.api +import pype.api class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): @@ -16,8 +16,8 @@ class ValidateLookDefaultShadersConnections(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Look Default Shader Connections' diff --git a/pype/plugins/maya/publish/validate_look_id_reference_edits.py b/pype/plugins/maya/publish/validate_look_id_reference_edits.py index 454320b00c..6a28daa7dc 100644 --- a/pype/plugins/maya/publish/validate_look_id_reference_edits.py +++ b/pype/plugins/maya/publish/validate_look_id_reference_edits.py @@ -2,8 +2,8 @@ from collections import defaultdict from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): @@ -16,12 +16,12 @@ class ValidateLookIdReferenceEdits(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Look Id Reference Edits' - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/plugins/maya/publish/validate_look_members_unique.py b/pype/plugins/maya/publish/validate_look_members_unique.py index b611bdbb33..e9fb20d8d6 100644 --- a/pype/plugins/maya/publish/validate_look_members_unique.py +++ b/pype/plugins/maya/publish/validate_look_members_unique.py @@ -1,8 +1,8 @@ from collections import defaultdict import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): @@ -20,13 +20,13 @@ class ValidateUniqueRelationshipMembers(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder label = 'Look members unique' hosts = ['maya'] - families = ['colorbleed.look'] + families = ['look'] - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.maya.action.GenerateUUIDsOnInvalidAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/plugins/maya/publish/validate_look_no_default_shaders.py b/pype/plugins/maya/publish/validate_look_no_default_shaders.py index 7485fc3cc1..dbef871a16 100644 --- a/pype/plugins/maya/publish/validate_look_no_default_shaders.py +++ b/pype/plugins/maya/publish/validate_look_no_default_shaders.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): @@ -23,11 +23,11 @@ class ValidateLookNoDefaultShaders(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + 0.01 - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + 0.01 + families = ['look'] hosts = ['maya'] label = 'Look No Default Shaders' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] DEFAULT_SHADERS = {"lambert1", "initialShadingGroup", "initialParticleSE", "particleCloud1"} diff --git a/pype/plugins/maya/publish/validate_look_sets.py b/pype/plugins/maya/publish/validate_look_sets.py index 3f4bc99059..f3952b53a4 100644 --- a/pype/plugins/maya/publish/validate_look_sets.py +++ b/pype/plugins/maya/publish/validate_look_sets.py @@ -1,8 +1,8 @@ -import colorbleed.maya.action -from colorbleed.maya import lib +import pype.maya.action +from pype.maya import lib import pyblish.api -import colorbleed.api +import pype.api class ValidateLookSets(pyblish.api.InstancePlugin): @@ -38,11 +38,11 @@ class ValidateLookSets(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Look Sets' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/pype/plugins/maya/publish/validate_look_single_shader.py b/pype/plugins/maya/publish/validate_look_single_shader.py index 687dd2b84f..1b9ebffced 100644 --- a/pype/plugins/maya/publish/validate_look_single_shader.py +++ b/pype/plugins/maya/publish/validate_look_single_shader.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateSingleShader(pyblish.api.InstancePlugin): @@ -12,11 +12,11 @@ class ValidateSingleShader(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Look Single Shader Per Shape' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] # The default connections to check def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_maya_units.py b/pype/plugins/maya/publish/validate_maya_units.py index ba38fbe512..8610c4dd25 100644 --- a/pype/plugins/maya/publish/validate_maya_units.py +++ b/pype/plugins/maya/publish/validate_maya_units.py @@ -1,18 +1,18 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.api -from colorbleed import lib -import colorbleed.maya.lib as mayalib +import pype.api +from pype import lib +import pype.maya.lib as mayalib class ValidateMayaUnits(pyblish.api.ContextPlugin): """Check if the Maya units are set correct""" - order = colorbleed.api.ValidateSceneOrder + order = pype.api.ValidateSceneOrder label = "Maya Units" hosts = ['maya'] - actions = [colorbleed.api.RepairContextAction] + actions = [pype.api.RepairContextAction] def process(self, context): diff --git a/pype/plugins/maya/publish/validate_mesh_has_uv.py b/pype/plugins/maya/publish/validate_mesh_has_uv.py index 4233674ddd..0eb235db0f 100644 --- a/pype/plugins/maya/publish/validate_mesh_has_uv.py +++ b/pype/plugins/maya/publish/validate_mesh_has_uv.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def len_flattened(components): @@ -45,12 +45,12 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): UVs for every face. """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'geometry' label = 'Mesh Has UVs' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] optional = True @classmethod diff --git a/pype/plugins/maya/publish/validate_mesh_lamina_faces.py b/pype/plugins/maya/publish/validate_mesh_lamina_faces.py index ca54648108..9e6e0f40b7 100644 --- a/pype/plugins/maya/publish/validate_mesh_lamina_faces.py +++ b/pype/plugins/maya/publish/validate_mesh_lamina_faces.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): @@ -12,13 +12,13 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Lamina Faces' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_mesh_no_negative_scale.py b/pype/plugins/maya/publish/validate_mesh_no_negative_scale.py index de60300931..ed2ded652b 100644 --- a/pype/plugins/maya/publish/validate_mesh_no_negative_scale.py +++ b/pype/plugins/maya/publish/validate_mesh_no_negative_scale.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateMeshNoNegativeScale(pyblish.api.Validator): @@ -17,11 +17,11 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] label = 'Mesh No Negative Scale' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_mesh_non_manifold.py b/pype/plugins/maya/publish/validate_mesh_non_manifold.py index 1c5ce731fa..961e6e42ef 100644 --- a/pype/plugins/maya/publish/validate_mesh_non_manifold.py +++ b/pype/plugins/maya/publish/validate_mesh_non_manifold.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateMeshNonManifold(pyblish.api.Validator): @@ -13,11 +13,11 @@ class ValidateMeshNonManifold(pyblish.api.Validator): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] label = 'Mesh Non-Manifold Vertices/Edges' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py b/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py index 6f35f512a0..6ade81764b 100644 --- a/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py +++ b/pype/plugins/maya/publish/validate_mesh_non_zero_edge.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @@ -16,13 +16,13 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder - families = ['colorbleed.model'] + order = pype.api.ValidateMeshOrder + families = ['model'] hosts = ['maya'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Edge Length Non Zero' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] __tolerance = 1e-5 diff --git a/pype/plugins/maya/publish/validate_mesh_normals_unlocked.py b/pype/plugins/maya/publish/validate_mesh_normals_unlocked.py index 09fe2b4e4b..faa1c8e248 100644 --- a/pype/plugins/maya/publish/validate_mesh_normals_unlocked.py +++ b/pype/plugins/maya/publish/validate_mesh_normals_unlocked.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateMeshNormalsUnlocked(pyblish.api.Validator): @@ -13,14 +13,14 @@ class ValidateMeshNormalsUnlocked(pyblish.api.Validator): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'geometry' version = (0, 1, 0) label = 'Mesh Normals Unlocked' - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] optional = True @staticmethod diff --git a/pype/plugins/maya/publish/validate_mesh_shader_connections.py b/pype/plugins/maya/publish/validate_mesh_shader_connections.py index 723ff2d3e6..048b146886 100644 --- a/pype/plugins/maya/publish/validate_mesh_shader_connections.py +++ b/pype/plugins/maya/publish/validate_mesh_shader_connections.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def pairs(iterable): @@ -73,12 +73,12 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] label = "Mesh Shader Connections" - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] def process(self, instance): """Process all the nodes in the instance 'objectSet'""" diff --git a/pype/plugins/maya/publish/validate_mesh_single_uv_set.py b/pype/plugins/maya/publish/validate_mesh_single_uv_set.py index 774c7899d2..eeddeb3c9c 100644 --- a/pype/plugins/maya/publish/validate_mesh_single_uv_set.py +++ b/pype/plugins/maya/publish/validate_mesh_single_uv_set.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): @@ -15,15 +15,15 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model', 'colorbleed.pointcache'] + families = ['model', 'pointcache'] category = 'uv' optional = True version = (0, 1, 0) label = "Mesh Single UV Set" - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_mesh_uv_set_map1.py b/pype/plugins/maya/publish/validate_mesh_uv_set_map1.py index 00c9d800cf..8794fa9de1 100644 --- a/pype/plugins/maya/publish/validate_mesh_uv_set_map1.py +++ b/pype/plugins/maya/publish/validate_mesh_uv_set_map1.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): @@ -15,13 +15,13 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] optional = True label = "Mesh has map1 UV Set" - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py b/pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py index 41efd27f89..2aaf11f6c0 100644 --- a/pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py +++ b/pype/plugins/maya/publish/validate_mesh_vertices_have_edges.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def len_flattened(components): @@ -57,13 +57,13 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'geometry' label = 'Mesh Vertices Have Edges' - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @classmethod def repair(cls, instance): diff --git a/pype/plugins/maya/publish/validate_model_content.py b/pype/plugins/maya/publish/validate_model_content.py index c9bbb2c23e..e76e102672 100644 --- a/pype/plugins/maya/publish/validate_model_content.py +++ b/pype/plugins/maya/publish/validate_model_content.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateModelContent(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateModelContent(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ["maya"] - families = ["colorbleed.model"] + families = ["model"] label = "Model Content" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @classmethod def get_invalid(cls, instance): diff --git a/pype/plugins/maya/publish/validate_no_animation.py b/pype/plugins/maya/publish/validate_no_animation.py index bde5775fe7..21749de7cb 100644 --- a/pype/plugins/maya/publish/validate_no_animation.py +++ b/pype/plugins/maya/publish/validate_no_animation.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateNoAnimation(pyblish.api.Validator): @@ -14,12 +14,12 @@ class ValidateNoAnimation(pyblish.api.Validator): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "No Animation" hosts = ["maya"] - families = ["colorbleed.model"] + families = ["model"] optional = True - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_no_default_camera.py b/pype/plugins/maya/publish/validate_no_default_camera.py index e41d2eea99..3d523e8974 100644 --- a/pype/plugins/maya/publish/validate_no_default_camera.py +++ b/pype/plugins/maya/publish/validate_no_default_camera.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): @@ -13,12 +13,12 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin): settings when being loaded and sometimes being skipped. """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.camera'] + families = ['camera'] version = (0, 1, 0) label = "No Default Cameras" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_no_namespace.py b/pype/plugins/maya/publish/validate_no_namespace.py index 46f1dbe49c..626dbcd4b8 100644 --- a/pype/plugins/maya/publish/validate_no_namespace.py +++ b/pype/plugins/maya/publish/validate_no_namespace.py @@ -2,8 +2,8 @@ import pymel.core as pm import maya.cmds as cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def get_namespace(node_name): @@ -16,14 +16,14 @@ def get_namespace(node_name): class ValidateNoNamespace(pyblish.api.InstancePlugin): """Ensure the nodes don't have a namespace""" - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'cleanup' version = (0, 1, 0) label = 'No Namespaces' - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_no_null_transforms.py b/pype/plugins/maya/publish/validate_no_null_transforms.py index d9dff290b8..156e0528a1 100644 --- a/pype/plugins/maya/publish/validate_no_null_transforms.py +++ b/pype/plugins/maya/publish/validate_no_null_transforms.py @@ -1,8 +1,8 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def has_shape_children(node): @@ -37,14 +37,14 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'cleanup' version = (0, 1, 0) label = 'No Empty/Null Transforms' - actions = [colorbleed.api.RepairAction, - colorbleed.maya.action.SelectInvalidAction] + actions = [pype.api.RepairAction, + pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_no_unknown_nodes.py b/pype/plugins/maya/publish/validate_no_unknown_nodes.py index 3fdd087b3d..cc2b9fc009 100644 --- a/pype/plugins/maya/publish/validate_no_unknown_nodes.py +++ b/pype/plugins/maya/publish/validate_no_unknown_nodes.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): @@ -16,12 +16,12 @@ class ValidateNoUnknownNodes(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model', 'colorbleed.rig'] + families = ['model', 'rig'] optional = True label = "Unknown Nodes" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_no_vraymesh.py b/pype/plugins/maya/publish/validate_no_vraymesh.py index 575ad7e549..27e5e6a006 100644 --- a/pype/plugins/maya/publish/validate_no_vraymesh.py +++ b/pype/plugins/maya/publish/validate_no_vraymesh.py @@ -7,7 +7,7 @@ class ValidateNoVRayMesh(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = 'No V-Ray Proxies (VRayMesh)' - families = ["colorbleed.pointcache"] + families = ["pointcache"] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_node_ids.py b/pype/plugins/maya/publish/validate_node_ids.py index f98f1a06f7..c3b989f282 100644 --- a/pype/plugins/maya/publish/validate_node_ids.py +++ b/pype/plugins/maya/publish/validate_node_ids.py @@ -1,8 +1,8 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action -from colorbleed.maya import lib +from pype.maya import lib class ValidateNodeIDs(pyblish.api.InstancePlugin): @@ -14,19 +14,19 @@ class ValidateNodeIDs(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder label = 'Instance Nodes Have ID' hosts = ['maya'] - families = ["colorbleed.model", - "colorbleed.look", - "colorbleed.rig", - "colorbleed.pointcache", - "colorbleed.animation", - "colorbleed.setdress", - "colorbleed.yetiRig"] + families = ["model", + "look", + "rig", + "pointcache", + "animation", + "setdress", + "yetiRig"] - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.maya.action.GenerateUUIDsOnInvalidAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py b/pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py index 99df6a1b63..75ec162a70 100644 --- a/pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py +++ b/pype/plugins/maya/publish/validate_node_ids_deformed_shapes.py @@ -1,9 +1,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): @@ -16,11 +16,11 @@ class ValidateNodeIdsDeformedShape(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ['colorbleed.look'] + order = pype.api.ValidateContentsOrder + families = ['look'] hosts = ['maya'] label = 'Deformed shape ids' - actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, pype.api.RepairAction] def process(self, instance): """Process all the nodes in the instance""" diff --git a/pype/plugins/maya/publish/validate_node_ids_in_database.py b/pype/plugins/maya/publish/validate_node_ids_in_database.py index d808152245..7347ce2ab2 100644 --- a/pype/plugins/maya/publish/validate_node_ids_in_database.py +++ b/pype/plugins/maya/publish/validate_node_ids_in_database.py @@ -2,9 +2,9 @@ import pyblish.api import avalon.io as io -import colorbleed.api -import colorbleed.maya.action -from colorbleed.maya import lib +import pype.api +import pype.maya.action +from pype.maya import lib class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): @@ -18,13 +18,13 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder label = 'Node Ids in Database' hosts = ['maya'] families = ["*"] - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.maya.action.GenerateUUIDsOnInvalidAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/plugins/maya/publish/validate_node_ids_related.py b/pype/plugins/maya/publish/validate_node_ids_related.py index c9185b4e02..aab6cb8003 100644 --- a/pype/plugins/maya/publish/validate_node_ids_related.py +++ b/pype/plugins/maya/publish/validate_node_ids_related.py @@ -1,10 +1,10 @@ import pyblish.api -import colorbleed.api +import pype.api import avalon.io as io -import colorbleed.maya.action +import pype.maya.action -from colorbleed.maya import lib +from pype.maya import lib class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): @@ -12,16 +12,16 @@ class ValidateNodeIDsRelated(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder label = 'Node Ids Related (ID)' hosts = ['maya'] - families = ["colorbleed.model", - "colorbleed.look", - "colorbleed.rig"] + families = ["model", + "look", + "rig"] optional = True - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.maya.action.GenerateUUIDsOnInvalidAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all nodes in instance (including hierarchy)""" diff --git a/pype/plugins/maya/publish/validate_node_ids_unique.py b/pype/plugins/maya/publish/validate_node_ids_unique.py index b32e5c0081..31e5e673de 100644 --- a/pype/plugins/maya/publish/validate_node_ids_unique.py +++ b/pype/plugins/maya/publish/validate_node_ids_unique.py @@ -1,9 +1,9 @@ from collections import defaultdict import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): @@ -12,16 +12,16 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin): Here we ensure that what has been added to the instance is unique """ - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder label = 'Non Duplicate Instance Members (ID)' hosts = ['maya'] - families = ["colorbleed.model", - "colorbleed.look", - "colorbleed.rig", - "colorbleed.yetiRig"] + families = ["model", + "look", + "rig", + "yetiRig"] - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.maya.action.GenerateUUIDsOnInvalidAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.maya.action.GenerateUUIDsOnInvalidAction] def process(self, instance): """Process all meshes""" diff --git a/pype/plugins/maya/publish/validate_node_no_ghosting.py b/pype/plugins/maya/publish/validate_node_no_ghosting.py index 360c3cdce8..8f40628c61 100644 --- a/pype/plugins/maya/publish/validate_node_no_ghosting.py +++ b/pype/plugins/maya/publish/validate_node_no_ghosting.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): @@ -17,11 +17,11 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model', 'colorbleed.rig'] + families = ['model', 'rig'] label = "No Ghosting" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] _attributes = {'ghosting': 0} diff --git a/pype/plugins/maya/publish/validate_render_image_rule.py b/pype/plugins/maya/publish/validate_render_image_rule.py index c16b5ecc3a..762cf963f1 100644 --- a/pype/plugins/maya/publish/validate_render_image_rule.py +++ b/pype/plugins/maya/publish/validate_render_image_rule.py @@ -1,7 +1,7 @@ import maya.mel as mel import pyblish.api -import colorbleed.api +import pype.api def get_file_rule(rule): @@ -14,10 +14,10 @@ class ValidateRenderImageRule(pyblish.api.ContextPlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Images File Rule (Workspace)" hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] def process(self, context): diff --git a/pype/plugins/maya/publish/validate_render_no_default_cameras.py b/pype/plugins/maya/publish/validate_render_no_default_cameras.py index fbfb50bd4e..439cfb69d3 100644 --- a/pype/plugins/maya/publish/validate_render_no_default_cameras.py +++ b/pype/plugins/maya/publish/validate_render_no_default_cameras.py @@ -1,18 +1,18 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin): """Ensure no default (startup) cameras are to be rendered.""" - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.renderlayer'] + families = ['renderlayer'] label = "No Default Cameras Renderable" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def get_invalid(instance): diff --git a/pype/plugins/maya/publish/validate_render_single_camera.py b/pype/plugins/maya/publish/validate_render_single_camera.py index 447f52beec..4321884815 100644 --- a/pype/plugins/maya/publish/validate_render_single_camera.py +++ b/pype/plugins/maya/publish/validate_render_single_camera.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): @@ -14,12 +14,12 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Render Single Camera" hosts = ['maya'] - families = ["colorbleed.renderlayer", - "colorbleed.vrayscene"] - actions = [colorbleed.maya.action.SelectInvalidAction] + families = ["renderlayer", + "vrayscene"] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): """Process all the cameras in the instance""" diff --git a/pype/plugins/maya/publish/validate_renderlayer_aovs.py b/pype/plugins/maya/publish/validate_renderlayer_aovs.py index 06e0c68184..e14c92a8b4 100644 --- a/pype/plugins/maya/publish/validate_renderlayer_aovs.py +++ b/pype/plugins/maya/publish/validate_renderlayer_aovs.py @@ -1,8 +1,8 @@ import pyblish.api -import colorbleed.maya.action +import pype.maya.action from avalon import io -import colorbleed.api +import pype.api class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): @@ -24,8 +24,8 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder + 0.1 label = "Render Passes / AOVs Are Registered" hosts = ["maya"] - families = ["colorbleed.renderlayer"] - actions = [colorbleed.maya.action.SelectInvalidAction] + families = ["renderlayer"] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/plugins/maya/publish/validate_rendersettings.py b/pype/plugins/maya/publish/validate_rendersettings.py index 8f8c86e51e..3510826eca 100644 --- a/pype/plugins/maya/publish/validate_rendersettings.py +++ b/pype/plugins/maya/publish/validate_rendersettings.py @@ -1,8 +1,8 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.lib as lib class ValidateRenderSettings(pyblish.api.InstancePlugin): @@ -27,11 +27,11 @@ class ValidateRenderSettings(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Render Settings" hosts = ["maya"] - families = ["colorbleed.renderlayer"] - actions = [colorbleed.api.RepairAction] + families = ["renderlayer"] + actions = [pype.api.RepairAction] DEFAULT_PADDING = 4 RENDERER_PREFIX = {"vray": "/_/"} diff --git a/pype/plugins/maya/publish/validate_rig_contents.py b/pype/plugins/maya/publish/validate_rig_contents.py index 7530936a66..0a9616ba1f 100644 --- a/pype/plugins/maya/publish/validate_rig_contents.py +++ b/pype/plugins/maya/publish/validate_rig_contents.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import colorbleed.api +import pype.api class ValidateRigContents(pyblish.api.InstancePlugin): @@ -13,10 +13,10 @@ class ValidateRigContents(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Rig Contents" hosts = ["maya"] - families = ["colorbleed.rig"] + families = ["rig"] accepted_output = ["mesh", "transform"] accepted_controllers = ["transform"] diff --git a/pype/plugins/maya/publish/validate_rig_controllers.py b/pype/plugins/maya/publish/validate_rig_controllers.py index ecb3b687d1..06746f2dbb 100644 --- a/pype/plugins/maya/publish/validate_rig_controllers.py +++ b/pype/plugins/maya/publish/validate_rig_controllers.py @@ -2,9 +2,9 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -from colorbleed.maya.lib import undo_chunk +import pype.api +import pype.maya.action +from pype.maya.lib import undo_chunk class ValidateRigControllers(pyblish.api.InstancePlugin): @@ -25,12 +25,12 @@ class ValidateRigControllers(pyblish.api.InstancePlugin): - Break all incoming connections to keyable attributes """ - order = colorbleed.api.ValidateContentsOrder + 0.05 + order = pype.api.ValidateContentsOrder + 0.05 label = "Rig Controllers" hosts = ["maya"] - families = ["colorbleed.rig"] - actions = [colorbleed.api.RepairAction, - colorbleed.maya.action.SelectInvalidAction] + families = ["rig"] + actions = [pype.api.RepairAction, + pype.maya.action.SelectInvalidAction] # Default controller values CONTROLLER_DEFAULTS = { diff --git a/pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py b/pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py index 43bfde918f..525a15a4e5 100644 --- a/pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py +++ b/pype/plugins/maya/publish/validate_rig_controllers_arnold_attributes.py @@ -1,10 +1,10 @@ from maya import cmds import pyblish.api -import colorbleed.api +import pype.api -import colorbleed.maya.lib as lib -import colorbleed.maya.action +import pype.maya.lib as lib +import pype.maya.action class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): @@ -26,12 +26,12 @@ class ValidateRigControllersArnoldAttributes(pyblish.api.InstancePlugin): This validator will ensure they are hidden or unkeyable attributes. """ - order = colorbleed.api.ValidateContentsOrder + 0.05 + order = pype.api.ValidateContentsOrder + 0.05 label = "Rig Controllers (Arnold Attributes)" hosts = ["maya"] - families = ["colorbleed.rig"] - actions = [colorbleed.api.RepairAction, - colorbleed.maya.action.SelectInvalidAction] + families = ["rig"] + actions = [pype.api.RepairAction, + pype.maya.action.SelectInvalidAction] attributes = [ "rcurve", diff --git a/pype/plugins/maya/publish/validate_rig_out_set_node_ids.py b/pype/plugins/maya/publish/validate_rig_out_set_node_ids.py index 72e0db311c..3dde6bc83d 100644 --- a/pype/plugins/maya/publish/validate_rig_out_set_node_ids.py +++ b/pype/plugins/maya/publish/validate_rig_out_set_node_ids.py @@ -1,9 +1,9 @@ import maya.cmds as cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.action +import pype.maya.lib as lib class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): @@ -16,11 +16,11 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder - families = ["colorbleed.rig"] + order = pype.api.ValidateContentsOrder + families = ["rig"] hosts = ['maya'] label = 'Rig Out Set Node Ids' - actions = [colorbleed.maya.action.SelectInvalidAction, colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, pype.api.RepairAction] def process(self, instance): """Process all meshes""" diff --git a/pype/plugins/maya/publish/validate_scene_set_workspace.py b/pype/plugins/maya/publish/validate_scene_set_workspace.py index ed37457164..778c7eae86 100644 --- a/pype/plugins/maya/publish/validate_scene_set_workspace.py +++ b/pype/plugins/maya/publish/validate_scene_set_workspace.py @@ -3,7 +3,7 @@ import os import maya.cmds as cmds import pyblish.api -import colorbleed.api +import pype.api def is_subdir(path, root_dir): @@ -28,7 +28,7 @@ def is_subdir(path, root_dir): class ValidateSceneSetWorkspace(pyblish.api.ContextPlugin): """Validate the scene is inside the currently set Maya workspace""" - order = colorbleed.api.ValidatePipelineOrder + order = pype.api.ValidatePipelineOrder hosts = ['maya'] category = 'scene' version = (0, 1, 0) diff --git a/pype/plugins/maya/publish/validate_setdress_namespaces.py b/pype/plugins/maya/publish/validate_setdress_namespaces.py index a8a28a10ca..0d3b75ca37 100644 --- a/pype/plugins/maya/publish/validate_setdress_namespaces.py +++ b/pype/plugins/maya/publish/validate_setdress_namespaces.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): @@ -17,8 +17,8 @@ class ValidateSetdressNamespaces(pyblish.api.InstancePlugin): label = "Validate Setdress Namespaces" order = pyblish.api.ValidatorOrder - families = ["colorbleed.setdress"] - actions = [colorbleed.maya.action.SelectInvalidAction] + families = ["setdress"] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_setdress_transforms.py b/pype/plugins/maya/publish/validate_setdress_transforms.py index 8ed4a81b5f..918f4670be 100644 --- a/pype/plugins/maya/publish/validate_setdress_transforms.py +++ b/pype/plugins/maya/publish/validate_setdress_transforms.py @@ -1,9 +1,9 @@ import pyblish.api -import colorbleed.api +import pype.api from maya import cmds -import colorbleed.maya.action +import pype.maya.action class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): @@ -27,9 +27,9 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder + 0.49 label = "Setdress Model Transforms" - families = ["colorbleed.setdress"] - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + families = ["setdress"] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] prompt_message = ("You are about to reset the matrix to the default values." " This can alter the look of your scene. " @@ -44,7 +44,7 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - import colorbleed.maya.lib as lib + import pype.maya.lib as lib # Get all transforms in the loaded containers container_roots = cmds.listRelatives(instance.data["hierarchy"], @@ -89,7 +89,7 @@ class ValidateSetDressModelTransforms(pyblish.api.InstancePlugin): """ - import colorbleed.maya.lib as lib + import pype.maya.lib as lib from avalon.vendor.Qt import QtWidgets # Store namespace in variable, cosmetics thingy diff --git a/pype/plugins/maya/publish/validate_shape_default_names.py b/pype/plugins/maya/publish/validate_shape_default_names.py index b4249d69aa..9beb77872a 100644 --- a/pype/plugins/maya/publish/validate_shape_default_names.py +++ b/pype/plugins/maya/publish/validate_shape_default_names.py @@ -3,8 +3,8 @@ import re from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action def short_name(node): @@ -31,15 +31,15 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'cleanup' optional = True version = (0, 1, 0) label = "Shape Default Naming" - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] @staticmethod def _define_default_name(shape): diff --git a/pype/plugins/maya/publish/validate_shape_render_stats.py b/pype/plugins/maya/publish/validate_shape_render_stats.py index 544470c3a7..b803cd366b 100644 --- a/pype/plugins/maya/publish/validate_shape_render_stats.py +++ b/pype/plugins/maya/publish/validate_shape_render_stats.py @@ -1,20 +1,20 @@ import pyblish.api -import colorbleed.api +import pype.api from maya import cmds -import colorbleed.maya.action +import pype.maya.action class ValidateShapeRenderStats(pyblish.api.Validator): """Ensure all render stats are set to the default values.""" - order = colorbleed.api.ValidateMeshOrder + order = pype.api.ValidateMeshOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] label = 'Shape Default Render Stats' - actions = [colorbleed.maya.action.SelectInvalidAction, - colorbleed.api.RepairAction] + actions = [pype.maya.action.SelectInvalidAction, + pype.api.RepairAction] defaults = {'castsShadows': 1, 'receiveShadows': 1, diff --git a/pype/plugins/maya/publish/validate_single_assembly.py b/pype/plugins/maya/publish/validate_single_assembly.py index 886e88ada3..b2cf6abbb4 100644 --- a/pype/plugins/maya/publish/validate_single_assembly.py +++ b/pype/plugins/maya/publish/validate_single_assembly.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api class ValidateSingleAssembly(pyblish.api.InstancePlugin): @@ -17,9 +17,9 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.rig', 'colorbleed.animation'] + families = ['rig', 'animation'] label = 'Single Assembly' def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_skinCluster_deformer_set.py b/pype/plugins/maya/publish/validate_skinCluster_deformer_set.py index 61ccb8f29a..71f7eea31b 100644 --- a/pype/plugins/maya/publish/validate_skinCluster_deformer_set.py +++ b/pype/plugins/maya/publish/validate_skinCluster_deformer_set.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): @@ -14,11 +14,11 @@ class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.fbx'] + families = ['fbx'] label = "Skincluster Deformer Relationships" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): """Process all the transform nodes in the instance""" diff --git a/pype/plugins/maya/publish/validate_step_size.py b/pype/plugins/maya/publish/validate_step_size.py index 07d61ec933..20207a050f 100644 --- a/pype/plugins/maya/publish/validate_step_size.py +++ b/pype/plugins/maya/publish/validate_step_size.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateStepSize(pyblish.api.InstancePlugin): @@ -10,12 +10,12 @@ class ValidateStepSize(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = 'Step size' - families = ['colorbleed.camera', - 'colorbleed.pointcache', - 'colorbleed.animation'] - actions = [colorbleed.maya.action.SelectInvalidAction] + families = ['camera', + 'pointcache', + 'animation'] + actions = [pype.maya.action.SelectInvalidAction] MIN = 0.01 MAX = 1.0 diff --git a/pype/plugins/maya/publish/validate_transfers.py b/pype/plugins/maya/publish/validate_transfers.py index d1ea7d362e..3234b2240e 100644 --- a/pype/plugins/maya/publish/validate_transfers.py +++ b/pype/plugins/maya/publish/validate_transfers.py @@ -1,5 +1,5 @@ import pyblish.api -import colorbleed.api +import pype.api import os from collections import defaultdict @@ -13,7 +13,7 @@ class ValidateTransfers(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Transfers" def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_transform_naming_suffix.py b/pype/plugins/maya/publish/validate_transform_naming_suffix.py index 0c100126ce..0adba9b656 100644 --- a/pype/plugins/maya/publish/validate_transform_naming_suffix.py +++ b/pype/plugins/maya/publish/validate_transform_naming_suffix.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action SUFFIX_NAMING_TABLE = {'mesh': ["_GEO", "_GES", "_GEP", "_OSD"], 'nurbsCurve': ["_CRV"], @@ -31,14 +31,14 @@ class ValidateTransformNamingSuffix(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ['maya'] - families = ['colorbleed.model'] + families = ['model'] category = 'cleanup' optional = True version = (0, 1, 0) label = 'Suffix Naming Conventions' - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] @staticmethod def is_valid_name(node_name, shape_type): diff --git a/pype/plugins/maya/publish/validate_transform_zero.py b/pype/plugins/maya/publish/validate_transform_zero.py index 895248933c..0d2c3beade 100644 --- a/pype/plugins/maya/publish/validate_transform_zero.py +++ b/pype/plugins/maya/publish/validate_transform_zero.py @@ -1,8 +1,8 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateTransformZero(pyblish.api.Validator): @@ -14,13 +14,13 @@ class ValidateTransformZero(pyblish.api.Validator): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ["maya"] - families = ["colorbleed.model"] + families = ["model"] category = "geometry" version = (0, 1, 0) label = "Transform Zero (Freeze)" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] _identity = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/pype/plugins/maya/publish/validate_vray_distributed_rendering.py b/pype/plugins/maya/publish/validate_vray_distributed_rendering.py index 632fd2964e..cccf966ffd 100644 --- a/pype/plugins/maya/publish/validate_vray_distributed_rendering.py +++ b/pype/plugins/maya/publish/validate_vray_distributed_rendering.py @@ -1,6 +1,6 @@ import pyblish.api -import colorbleed.api -import colorbleed.maya.lib as lib +import pype.api +import pype.maya.lib as lib from maya import cmds @@ -15,10 +15,10 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "VRay Distributed Rendering" - families = ["colorbleed.renderlayer"] - actions = [colorbleed.api.RepairAction] + families = ["renderlayer"] + actions = [pype.api.RepairAction] # V-Ray attribute names enabled_attr = "vraySettings.sys_distributed_rendering_on" diff --git a/pype/plugins/maya/publish/validate_vray_translator_settings.py b/pype/plugins/maya/publish/validate_vray_translator_settings.py index 0de41c9563..493febf49a 100644 --- a/pype/plugins/maya/publish/validate_vray_translator_settings.py +++ b/pype/plugins/maya/publish/validate_vray_translator_settings.py @@ -1,16 +1,16 @@ import pyblish.api -import colorbleed.api -from colorbleed.plugin import contextplugin_should_run +import pype.api +from pype.plugin import contextplugin_should_run from maya import cmds class ValidateVRayTranslatorEnabled(pyblish.api.ContextPlugin): - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "VRay Translator Settings" - families = ["colorbleed.vrayscene"] - actions = [colorbleed.api.RepairContextAction] + families = ["vrayscene"] + actions = [pype.api.RepairContextAction] def process(self, context): diff --git a/pype/plugins/maya/publish/validate_vrayproxy_members.py b/pype/plugins/maya/publish/validate_vrayproxy_members.py index 7d9ecdaac5..9cc18e2199 100644 --- a/pype/plugins/maya/publish/validate_vrayproxy_members.py +++ b/pype/plugins/maya/publish/validate_vrayproxy_members.py @@ -1,9 +1,9 @@ import pyblish.api -import colorbleed.api +import pype.api from maya import cmds -import colorbleed.maya.action +import pype.maya.action class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): @@ -12,8 +12,8 @@ class ValidateVrayProxyMembers(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = 'VRay Proxy Members' hosts = ['maya'] - families = ['colorbleed.vrayproxy'] - actions = [colorbleed.maya.action.SelectInvalidAction] + families = ['vrayproxy'] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py b/pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py index 8864637da2..cd9b0754b3 100644 --- a/pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py +++ b/pype/plugins/maya/publish/validate_yeti_renderscript_callbacks.py @@ -1,7 +1,7 @@ from maya import cmds import pyblish.api -import colorbleed.api +import pype.api class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): @@ -20,10 +20,10 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin): """ - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder label = "Yeti Render Script Callbacks" hosts = ["maya"] - families = ["colorbleed.renderlayer"] + families = ["renderlayer"] # Settings per renderer callbacks = { diff --git a/pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py b/pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py index 423cc18360..51872d04e1 100644 --- a/pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py +++ b/pype/plugins/maya/publish/validate_yeti_rig_input_in_instance.py @@ -1,18 +1,18 @@ from maya import cmds import pyblish.api -import colorbleed.api -import colorbleed.maya.action +import pype.api +import pype.maya.action class ValidateYetiRigInputShapesInInstance(pyblish.api.Validator): """Validate if all input nodes are part of the instance's hierarchy""" - order = colorbleed.api.ValidateContentsOrder + order = pype.api.ValidateContentsOrder hosts = ["maya"] - families = ["colorbleed.yetiRig"] + families = ["yetiRig"] label = "Yeti Rig Input Shapes In Instance" - actions = [colorbleed.maya.action.SelectInvalidAction] + actions = [pype.maya.action.SelectInvalidAction] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_yeti_rig_settings.py b/pype/plugins/maya/publish/validate_yeti_rig_settings.py index 90ddfafa45..9914277721 100644 --- a/pype/plugins/maya/publish/validate_yeti_rig_settings.py +++ b/pype/plugins/maya/publish/validate_yeti_rig_settings.py @@ -12,7 +12,7 @@ class ValidateYetiRigSettings(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Yeti Rig Settings" - families = ["colorbleed.yetiRig"] + families = ["yetiRig"] def process(self, instance): diff --git a/pype/plugins/maya/publish/validate_yetirig_cache_state.py b/pype/plugins/maya/publish/validate_yetirig_cache_state.py index 94a46d2821..405aeb3fd7 100644 --- a/pype/plugins/maya/publish/validate_yetirig_cache_state.py +++ b/pype/plugins/maya/publish/validate_yetirig_cache_state.py @@ -1,10 +1,10 @@ import pyblish.api -import colorbleed.action +import pype.action import maya.cmds as cmds -import colorbleed.maya.action +import pype.maya.action class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): @@ -19,9 +19,9 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder label = "Yeti Rig Cache State" hosts = ["maya"] - families = ["colorbleed.yetiRig"] - actions = [colorbleed.action.RepairAction, - colorbleed.maya.action.SelectInvalidAction] + families = ["yetiRig"] + actions = [pype.action.RepairAction, + pype.maya.action.SelectInvalidAction] def process(self, instance): invalid = self.get_invalid(instance) diff --git a/pype/scripts/fusion_switch_shot.py b/pype/scripts/fusion_switch_shot.py index 4254d6c4ce..92271a3b7c 100644 --- a/pype/scripts/fusion_switch_shot.py +++ b/pype/scripts/fusion_switch_shot.py @@ -8,8 +8,8 @@ from avalon import api, io, pipeline import avalon.fusion # Config imports -import colorbleed.lib as colorbleed -import colorbleed.fusion.lib as fusion_lib +import pype.lib as pype +import pype.fusion.lib as fusion_lib log = logging.getLogger("Update Slap Comp") @@ -87,7 +87,7 @@ def _format_filepath(session): # Create new unqiue filepath if os.path.exists(new_filepath): - new_filepath = colorbleed.version_up(new_filepath) + new_filepath = pype.version_up(new_filepath) return new_filepath @@ -189,7 +189,7 @@ def switch(asset_name, filepath=None, new=True): representations = [] for container in containers: try: - representation = colorbleed.switch_item(container, + representation = pype.switch_item(container, asset_name=asset_name) representations.append(representation) except Exception as e: @@ -209,7 +209,7 @@ def switch(asset_name, filepath=None, new=True): # Update savers output based on new session _update_savers(current_comp, switch_to_session) else: - comp_path = colorbleed.version_up(filepath) + comp_path = pype.version_up(filepath) current_comp.Print(comp_path) diff --git a/pype/setdress_api.py b/pype/setdress_api.py index bc6c67aacc..c6de0a4f74 100644 --- a/pype/setdress_api.py +++ b/pype/setdress_api.py @@ -10,7 +10,7 @@ from maya import cmds from avalon import api import avalon.io as io from avalon.maya.lib import unique_namespace -from colorbleed.maya.lib import matrix_equals +from pype.maya.lib import matrix_equals log = logging.getLogger("PackageLoader") @@ -137,7 +137,7 @@ def load_package(filepath, name, namespace=None): # manager # for container in containers: # cmds.setAttr("%s.id" % container, - # "colorbleed.setdress.container", + # "setdress.container", # type="string") # TODO: Lock all loaded nodes @@ -162,7 +162,7 @@ def _add(instance, representation_id, loaders, namespace, root="|"): """ - from colorbleed.maya.lib import get_container_transforms + from pype.maya.lib import get_container_transforms # Process within the namespace with namespaced(namespace, new=False) as namespace: @@ -359,7 +359,7 @@ def update_scene(set_container, containers, current_data, new_data, new_file): """ - from colorbleed.maya.lib import DEFAULT_MATRIX, get_container_transforms + from pype.maya.lib import DEFAULT_MATRIX, get_container_transforms set_namespace = set_container['namespace']