From ca92c42cde633913d1a2d35f592bc2f647986a41 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Apr 2019 11:31:29 +0200 Subject: [PATCH 01/41] initialization of nukestudio integration --- README.md | 25 +- pype/nukestudio/__init__.py | 181 +++++++++ pype/nukestudio/inventory.py | 347 ++++++++++++++++++ pype/nukestudio/lib.py | 242 ++++++++++++ pype/nukestudio/menu.py | 7 + pype/plugins/nukestudio/publish/collect.py | 188 ++++++++++ .../publish/collect_active_project.py | 13 + .../nukestudio/publish/collect_colorspace.py | 27 ++ .../publish/collect_current_file.py | 14 + .../nukestudio/publish/collect_host.py | 13 + .../publish/collect_host_version.py | 12 + .../nukestudio/publish/collect_selection.py | 15 + .../nukestudio/publish/collect_submission.py | 14 + .../nukestudio/publish/extract_review.py | 102 +++++ .../nukestudio/publish/extract_tasks.py | 125 +++++++ .../nukestudio/publish/validate_names.py | 43 +++ .../publish/validate_projectroot.py | 53 +++ .../publish/validate_resolved_paths.py | 29 ++ .../nukestudio/publish/validate_task.py | 58 +++ .../nukestudio/publish/validate_track_item.py | 59 +++ .../nukestudio/publish/validate_viewer_lut.py | 22 ++ .../Python/Startup/pyblish_startup.py | 14 + .../Python/Startup/selection_tracker.py | 9 + 23 files changed, 1598 insertions(+), 14 deletions(-) create mode 100644 pype/nukestudio/__init__.py create mode 100644 pype/nukestudio/inventory.py create mode 100644 pype/nukestudio/lib.py create mode 100644 pype/nukestudio/menu.py create mode 100644 pype/plugins/nukestudio/publish/collect.py create mode 100644 pype/plugins/nukestudio/publish/collect_active_project.py create mode 100644 pype/plugins/nukestudio/publish/collect_colorspace.py create mode 100644 pype/plugins/nukestudio/publish/collect_current_file.py create mode 100644 pype/plugins/nukestudio/publish/collect_host.py create mode 100644 pype/plugins/nukestudio/publish/collect_host_version.py create mode 100644 pype/plugins/nukestudio/publish/collect_selection.py create mode 100644 pype/plugins/nukestudio/publish/collect_submission.py create mode 100644 pype/plugins/nukestudio/publish/extract_review.py create mode 100644 pype/plugins/nukestudio/publish/extract_tasks.py create mode 100644 pype/plugins/nukestudio/publish/validate_names.py create mode 100644 pype/plugins/nukestudio/publish/validate_projectroot.py create mode 100644 pype/plugins/nukestudio/publish/validate_resolved_paths.py create mode 100644 pype/plugins/nukestudio/publish/validate_task.py create mode 100644 pype/plugins/nukestudio/publish/validate_track_item.py create mode 100644 pype/plugins/nukestudio/publish/validate_viewer_lut.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py diff --git a/README.md b/README.md index 7cf8c4c0b6..fe0ad70a36 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,20 @@ -The base studio *config* for [Avalon](https://getavalon.github.io/) +he base studio _config_ for [Avalon](https://getavalon.github.io/) Currently this config is dependent on our customised avalon instalation so it won't work with vanilla avalon core. We're working on open sourcing all of the necessary code though. You can still get inspiration or take our individual validators and scripts which should work just fine in other pipelines. - _This configuration acts as a starting point for all pype club clients wth avalon deployment._ - - ### 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. +- **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. diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py new file mode 100644 index 0000000000..a1ee6dedf7 --- /dev/null +++ b/pype/nukestudio/__init__.py @@ -0,0 +1,181 @@ +import os +import sys +from avalon import api as avalon +from pyblish import api as pyblish + +from .. import api + +from pype.nukestudio import menu + +from .lib import ( + show, + setup, + register_plugins, + add_to_filemenu +) + +import nuke + +from pypeapp import Logger + + +# #removing logger handler created in avalon_core +# for name, handler in [(handler.get_name(), handler) +# for handler in Logger.logging.root.handlers[:]]: +# if "pype" not in str(name).lower(): +# Logger.logging.root.removeHandler(handler) + + +log = Logger().get_logger(__name__, "nuke") + +# log = api.Logger.getLogger(__name__, "nuke") + +AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") + +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, "nuke", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory") + +self = sys.modules[__name__] +self.nLogger = None + +if os.getenv("PYBLISH_GUI", None): + pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) + + +# class NukeHandler(Logger.logging.Handler): +# ''' +# Nuke Handler - emits logs into nuke's script editor. +# warning will emit nuke.warning() +# critical and fatal would popup msg dialog to alert of the error. +# ''' +# +# def __init__(self): +# api.Logger.logging.Handler.__init__(self) +# self.set_name("Pype_Nuke_Handler") +# +# def emit(self, record): +# # Formated message: +# msg = self.format(record) +# +# if record.levelname.lower() in [ +# # "warning", +# "critical", +# "fatal", +# "error" +# ]: +# nuke.message(msg) + +# +# '''Adding Nuke Logging Handler''' +# nuke_handler = NukeHandler() +# if nuke_handler.get_name() \ +# not in [handler.get_name() +# for handler in Logger.logging.root.handlers[:]]: +# api.Logger.logging.getLogger().addHandler(nuke_handler) +# api.Logger.logging.getLogger().setLevel(Logger.logging.INFO) +# +# if not self.nLogger: +# self.nLogger = Logger + + +def reload_config(): + """Attempt to reload pipeline at run-time. + + CAUTION: This is primarily for development and debugging purposes. + + """ + + import importlib + + for module in ( + "app", + "app.api", + "{}.api".format(AVALON_CONFIG), + "{}.templates".format(AVALON_CONFIG), + "{}.nuke.actions".format(AVALON_CONFIG), + "{}.nuke.templates".format(AVALON_CONFIG), + "{}.nuke.menu".format(AVALON_CONFIG), + "{}.nuke.lib".format(AVALON_CONFIG), + ): + log.info("Reloading module: {}...".format(module)) + try: + module = importlib.import_module(module) + reload(module) + except Exception as e: + log.warning("Cannot reload module: {}".format(e)) + importlib.reload(module) + + +def install(): + + # api.set_avalon_workdir() + # reload_config() + + import sys + + for path in sys.path: + if path.startswith("C:\\Users\\Public"): + sys.path.remove(path) + + log.info("Registering Nuke plug-ins..") + 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) + + # Disable all families except for the ones we explicitly want to see + family_states = [ + "write", + "review" + ] + + avalon.data["familiesStateDefault"] = False + avalon.data["familiesStateToggled"] = family_states + + menu.install() + + # load data from templates + # api.load_data_from_templates() + + +def uninstall(): + log.info("Deregistering Nuke 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) + + # reset data from templates + api.reset_data_from_templates() + + +def on_pyblish_instance_toggled(instance, old_value, new_value): + """Toggle node passthrough states on instance toggles.""" + self.log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + instance, old_value, new_value)) + + from avalon.nuke import ( + viewer_update_and_undo_stop, + add_publish_knob + ) + + # Whether instances should be passthrough based on new value + + with viewer_update_and_undo_stop(): + n = instance[0] + try: + n["publish"].value() + except ValueError: + n = add_publish_knob(n) + log.info(" `Publish` knob was added to write node..") + + n["publish"].setValue(new_value) diff --git a/pype/nukestudio/inventory.py b/pype/nukestudio/inventory.py new file mode 100644 index 0000000000..0d030c64ad --- /dev/null +++ b/pype/nukestudio/inventory.py @@ -0,0 +1,347 @@ +import os + +from pyblish import api + + +# Collection +collect_json_CollectJSON = api.CollectorOrder + 0.1 +collect_source_CollectScene = api.CollectorOrder + 0.1 +collect_scene_version_CollectSceneVersion = api.CollectorOrder + 0.1 +collect_existing_files_CollectExistingFiles = api.CollectorOrder + 0.25 +collect_reviews_CollectReviews = api.CollectorOrder + 0.3 +collect_sorting_CollectSorting = api.CollectorOrder + 0.49 + +# Validation +persist_publish_state_PersistPublishState = api.ValidatorOrder +validate_executables_ValidateFFmpeg = api.ValidatorOrder +validate_processing_ValidateProcessing = api.ValidatorOrder +validate_scene_version_ValidateSceneVersion = api.ValidatorOrder +validate_review_ValidateReview = api.ValidatorOrder + +# Extraction +extract_scene_save_ExtractSceneSave = api.ExtractorOrder - 0.49 +extract_review_ExtractReview = api.ExtractorOrder +extract_review_ExtractReviewTranscode = api.ExtractorOrder + 0.02 +extract_review_ExtractReviewTranscodeNukeStudio = ( + api.ExtractorOrder + 0.02 +) + +# Integration +extract_json_ExtractJSON = api.IntegratorOrder + 1 +copy_to_clipboard_action_Report = api.IntegratorOrder + 1 + +# AfterEffects +aftereffects_collect_render_items_CollectRenderItems = api.CollectorOrder +aftereffects_collect_scene_CollectScene = api.CollectorOrder + +aftereffects_validate_output_path_ValidateOutputPath = api.ValidatorOrder +aftereffects_validate_scene_path_ValidateScenePath = api.ValidatorOrder +aftereffects_validate_unique_comp_renders_ValidateUniqueCompRenders = ( + api.ValidatorOrder +) + +aftereffects_append_deadline_data_AppendDeadlineData = api.ExtractorOrder +aftereffects_append_ftrack_audio_AppendFtrackAudio = api.ExtractorOrder +aftereffects_extract_local_ExtractLocal = api.ExtractorOrder + +# CelAction +celaction_collect_scene_CollectScene = api.CollectorOrder +celaction_collect_render_CollectRender = api.CollectorOrder + 0.1 +celaction_bait_append_ftrack_data_AppendFtrackData = ( + api.CollectorOrder + 0.1 +) +celaction_bait_append_ftrack_asset_name_AppendFtrackAssetName = ( + api.CollectorOrder + 0.1 +) + +celaction_bait_validate_scene_path_ValidateScenePath = ( + api.ValidatorOrder +) + +celaction_bait_append_ftrack_data_AppendFtrackAudio = ( + api.ExtractorOrder +) +celaction_extract_deadline_ExtractDeadline = api.ExtractorOrder +celaction_extract_render_images_ExtractRenderImages = api.ExtractorOrder +celaction_extract_render_images_ExtractRenderMovie = api.ExtractorOrder + 0.1 +celaction_extract_deadline_movie_ExtractDeadlineMovie = ( + api.ExtractorOrder + 0.4 +) + +celaction_bait_integrate_local_render_IntegrateLocal = ( + api.IntegratorOrder +) + +# Deadline +deadline_OnJobFinished_collect_output_CollectOutput = api.CollectorOrder +deadline_OnJobSubmitted_collect_movie_CollectMovie = api.CollectorOrder +deadline_OnJobSubmitted_collect_render_CollectRender = api.CollectorOrder +deadline_collect_family_CollectFamily = api.CollectorOrder + 0.1 +deadline_collect_houdini_parameters_CollectHoudiniParameters = ( + deadline_collect_family_CollectFamily + 0.01 +) +deadline_collect_maya_parameters_CollectMayaParameters = ( + deadline_collect_family_CollectFamily + 0.01 +) +deadline_collect_nuke_parameters_CollectNukeParameters = ( + deadline_collect_family_CollectFamily + 0.01 +) +deadline_collect_houdini_render_CollectHoudiniRender = api.CollectorOrder + 0.4 + +deadline_validate_houdini_parameters_ValidateHoudiniParameters = ( + api.ValidatorOrder +) +deadline_validate_maya_parameters_ValidateMayaParameters = api.ValidatorOrder +deadline_validate_nuke_parameters_ValidateNukeParameters = api.ValidatorOrder + +deadline_extract_ftrack_path_ExtractFtrackPath = api.ExtractorOrder +deadline_extract_houdini_ExtractHoudini = api.ExtractorOrder +deadline_extract_job_name_ExtractJobName = api.ExtractorOrder +deadline_extract_maya_ExtractMaya = api.ExtractorOrder +deadline_extract_nuke_ExtractNuke = api.ExtractorOrder +deadline_extract_suspended_ExtractSuspended = api.ExtractorOrder + +deadline_integrate_collection_IntegrateCollection = api.IntegratorOrder - 0.1 +deadline_bait_integrate_ftrack_thumbnail_IntegrateFtrackThumbnail = ( + api.IntegratorOrder +) +deadline_bait_update_ftrack_status_UpdateFtrackStatus = ( + api.IntegratorOrder + 0.4 +) + + +# Ftrack +ftrack_collect_nukestudio_CollectNukeStudioEntities = api.CollectorOrder + 0.1 +ftrack_collect_nukestudio_CollectNukeStudioProjectData = ( + api.CollectorOrder + 0.1 +) +ftrack_collect_version_CollectVersion = api.CollectorOrder + 0.2 +ftrack_collect_family_CollectFamily = api.CollectorOrder + 0.4 + +ftrack_validate_assets_ValidateAssets = api.ValidatorOrder +ftrack_validate_nuke_settings_ValidateNukeSettings = api.ValidatorOrder +ftrack_validate_nukestudio_ValidateNukeStudioProjectData = api.ValidatorOrder +ftrack_validate_nukestudio_tasks_ValidateNukeStudioTasks = api.ValidatorOrder + +ftrack_extract_components_ExtractCache = api.ExtractorOrder +ftrack_extract_components_ExtractCamera = api.ExtractorOrder +ftrack_extract_components_ExtractGeometry = api.ExtractorOrder +ftrack_extract_components_ExtractGizmo = api.ExtractorOrder +ftrack_extract_components_ExtractImg = api.ExtractorOrder +ftrack_extract_components_ExtractLUT = api.ExtractorOrder +ftrack_extract_components_ExtractMovie = api.ExtractorOrder +ftrack_extract_components_ExtractAudio = api.ExtractorOrder +ftrack_extract_components_ExtractReview = api.ExtractorOrder +ftrack_extract_components_ExtractScene = api.ExtractorOrder +ftrack_extract_entities_ExtractProject = api.ExtractorOrder +ftrack_extract_entities_ExtractEpisode = ( + ftrack_extract_entities_ExtractProject + 0.01 +) +ftrack_extract_entities_ExtractSequence = ( + ftrack_extract_entities_ExtractEpisode + 0.01 +) +ftrack_extract_entities_ExtractShot = ( + ftrack_extract_entities_ExtractSequence + 0.01 +) +ftrack_extract_entities_ExtractLinkAssetbuilds = ( + ftrack_extract_entities_ExtractShot + 0.01 +) +ftrack_extract_entities_ExtractAssetDataNukeStudio = ( + ftrack_extract_entities_ExtractShot + 0.01 +) +ftrack_extract_entities_ExtractTasks = ( + ftrack_extract_entities_ExtractShot + 0.01 +) +ftrack_extract_entities_ExtractCommit = ( + ftrack_extract_entities_ExtractTasks + 0.01 +) +ftrack_extract_entities_ExtractNukeStudio = ( + ftrack_extract_entities_ExtractTasks + 0.01 +) +ftrack_extract_thumbnail_ExtractThumbnailImg = api.ExtractorOrder + 0.1 +ftrack_extract_review_ExtractReview = api.ExtractorOrder + 0.2 +ftrack_extract_components_ExtractComponents = api.ExtractorOrder + 0.4 + +ftrack_integrate_status_IntegrateStatus = api.IntegratorOrder + +ftrack_other_link_source_OtherLinkSource = api.IntegratorOrder + 1 + +# Hiero +hiero_collect_items_CollectItems = api.CollectorOrder + +hiero_validate_names_ValidateNames = api.ValidatorOrder + +hiero_extract_transcode_BumpyboxExtractTranscodeH264 = api.ExtractorOrder - 0.1 +hiero_extract_transcode_BumpyboxExtractTranscodeJPEG = api.ExtractorOrder - 0.1 +hiero_extract_audio_ExtractAudio = api.ExtractorOrder +hiero_extract_ftrack_shot_ExtractFtrackShot = api.ExtractorOrder +hiero_extract_nuke_script_ExtractNukeScript = api.ExtractorOrder +hiero_extract_transcode_ExtractTranscode = api.ExtractorOrder +hiero_extract_ftrack_components_ExtractFtrackComponents = ( + api.ExtractorOrder + 0.1 +) +hiero_extract_ftrack_tasks_ExtractFtrackTasks = api.ExtractorOrder + 0.1 +hiero_extract_ftrack_thumbnail_ExtractFtrackThumbnail = ( + api.ExtractorOrder + 0.1 +) + +# Houdini +houdini_collect_Collect = api.CollectorOrder + +houdini_validate_alembic_ValidateAlembic = api.ValidatorOrder +houdini_validate_dynamics_ValidateDynamics = api.ValidatorOrder +houdini_validate_geometry_ValidateGeometry = api.ValidatorOrder +houdini_validate_mantra_camera_ValidateMantraCamera = api.ValidatorOrder +houdini_validate_mantra_settings_ValidateMantraSettings = api.ValidatorOrder +houdini_validate_output_path_ValidateOutputPath = api.ValidatorOrder + +houdini_extract_scene_save_ExtractSceneSave = api.ExtractorOrder - 0.1 +houdini_extract_local_ExtractLocal = api.ExtractorOrder + +# Maya +maya_collect_framerate_CollectFramerate = api.CollectorOrder - 0.5 +maya_collect_files_CollectFiles = api.CollectorOrder +maya_collect_render_setups_CollectRenderSetups = api.CollectorOrder +maya_collect_sets_CollectSets = api.CollectorOrder +maya_collect_sets_CollectSetsProcess = maya_collect_sets_CollectSets + 0.01 +maya_collect_sets_CollectSetsPublish = maya_collect_sets_CollectSets + 0.01 +maya_collect_playblasts_CollectPlayblasts = api.CollectorOrder +maya_collect_playblasts_CollectPlayblastsProcess = ( + maya_collect_playblasts_CollectPlayblasts + 0.01 +) +maya_collect_playblasts_CollectPlayblastsPublish = ( + maya_collect_playblasts_CollectPlayblasts + 0.01 +) + +maya_modeling_validate_intermediate_shapes_ValidateIntermediateShapes = ( + api.ValidatorOrder +) +maya_modeling_validate_points_ValidatePoints = ( + api.ValidatorOrder +) +maya_modeling_validate_hierarchy_ValidateHierarchy = ( + api.ValidatorOrder +) +maya_modeling_validate_shape_name_ValidateShapeName = ( + api.ValidatorOrder +) +maya_modeling_validate_transforms_ValidateTransforms = ( + api.ValidatorOrder +) +maya_modeling_validate_display_layer_ValidateDisplayLayer = ( + api.ValidatorOrder +) +maya_modeling_validate_smooth_display_ValidateSmoothDisplay = ( + api.ValidatorOrder +) +maya_validate_arnold_setings_ValidateArnoldSettings = api.ValidatorOrder +maya_validate_name_ValidateName = api.ValidatorOrder +maya_validate_render_camera_ValidateRenderCamera = api.ValidatorOrder +maya_validate_render_layer_settings_ValidateRenderLayerSettings = ( + api.ValidatorOrder +) +maya_validate_vray_settings_ValidateVraySettings = api.ValidatorOrder + +maya_validate_scene_modified_ValidateSceneModified = api.ExtractorOrder - 0.49 +maya_extract_alembic_ExtractAlembic = api.ExtractorOrder +maya_extract_formats_ExtractFormats = api.ExtractorOrder +maya_lookdev_extract_construction_history_ExtractConstructionHistory = ( + maya_extract_formats_ExtractFormats - 0.01 +) +maya_modeling_extract_construction_history_ExtractConstructionHistory = ( + maya_extract_formats_ExtractFormats - 0.01 +) +maya_rigging_extract_disconnect_animation_ExtractDisconnectAnimation = ( + maya_extract_formats_ExtractFormats - 0.01 +) +maya_extract_playblast_ExtractPlayblast = api.ExtractorOrder +maya_extract_render_layer_ExtractRenderLayer = api.ExtractorOrder + +# Nuke +nuke_collect_selection_CollectSelection = api.CollectorOrder - 0.1 +nuke_collect_backdrops_CollectBackdrops = api.CollectorOrder + 0.1 +nuke_collect_framerate_CollectFramerate = api.CollectorOrder +nuke_collect_reads_CollectReads = api.CollectorOrder +nuke_collect_write_geo_CollectWriteGeo = api.CollectorOrder +nuke_collect_writes_CollectWrites = api.CollectorOrder +nuke_collect_write_geo_CollectCacheProcess = api.CollectorOrder + 0.01 +nuke_collect_write_geo_CollectCachePublish = api.CollectorOrder + 0.01 +nuke_collect_writes_CollectWritesProcess = api.CollectorOrder + 0.01 +nuke_collect_writes_CollectWritesPublish = api.CollectorOrder + 0.01 +nuke_collect_groups_CollectGroups = api.CollectorOrder + 0.1 + +nuke_validate_datatype_ValidateDatatype = api.ValidatorOrder +nuke_validate_frame_rate_ValidateFrameRate = api.ValidatorOrder +nuke_validate_group_node_ValidateGroupNode = api.ValidatorOrder +nuke_validate_proxy_mode_ValidateProxyMode = api.ValidatorOrder +nuke_validate_read_node_ValidateReadNode = api.ValidatorOrder +nuke_validate_write_node_ValidateWriteNode = api.ValidatorOrder +nuke_validate_write_node_ValidateReviewNodeDuplicate = api.ValidatorOrder +nuke_validate_writegeo_node_ValidateWriteGeoNode = api.ValidatorOrder + +nuke_extract_output_directory_ExtractOutputDirectory = api.ExtractorOrder - 0.1 +nuke_extract_backdrop_ExtractBackdrop = api.ExtractorOrder +nuke_extract_group_ExtractGroup = api.ExtractorOrder +nuke_extract_write_Extract = api.ExtractorOrder +nuke_extract_write_ExtractCache = api.ExtractorOrder +nuke_extract_write_ExtractCamera = api.ExtractorOrder +nuke_extract_write_ExtractGeometry = api.ExtractorOrder +nuke_extract_write_ExtractWrite = api.ExtractorOrder +nuke_extract_review_ExtractReview = api.ExtractorOrder + 0.01 + +# NukeStudio +nukestudio_collect_CollectFramerate = api.CollectorOrder +nukestudio_collect_CollectTrackItems = api.CollectorOrder +nukestudio_collect_CollectTasks = api.CollectorOrder + 0.01 + +nukestudio_validate_names_ValidateNames = api.ValidatorOrder +nukestudio_validate_names_ValidateNamesFtrack = api.ValidatorOrder +nukestudio_validate_projectroot_ValidateProjectRoot = api.ValidatorOrder +nukestudio_validate_resolved_paths_ValidateResolvedPaths = api.ValidatorOrder +nukestudio_validate_task_ValidateImageSequence = api.ValidatorOrder +nukestudio_validate_task_ValidateOutputRange = api.ValidatorOrder +nukestudio_validate_track_item_ValidateTrackItem = api.ValidatorOrder +nukestudio_validate_track_item_ValidateTrackItemFtrack = api.ValidatorOrder +nukestudio_validate_viewer_lut_ValidateViewerLut = api.ValidatorOrder + +nukestudio_extract_review_ExtractReview = api.ExtractorOrder +nukestudio_extract_tasks_ExtractTasks = api.ExtractorOrder + +# RoyalRender +royalrender_collect_CollectMayaSets = api.CollectorOrder + 0.1 +royalrender_collect_CollectNukeWrites = api.CollectorOrder + 0.1 + +royalrender_extract_maya_ExtractMaya = api.ExtractorOrder +royalrender_extract_maya_alembic_ExtractMovie = api.ExtractorOrder +royalrender_extract_nuke_ExtractNuke = api.ExtractorOrder + +# TVPaint +tvpaint_extract_deadline_ExtractDeadline = api.ExtractorOrder - 0.1 +tvpaint_collect_scene_arg_CollectSceneArg = api.CollectorOrder - 0.05 +tvpaint_collect_render_CollectRender = api.CollectorOrder + 0.1 + +tvpaint_validate_scene_path_ValidateScenePath = api.ValidatorOrder + +tvpaint_extract_hobsoft_scene_ExtractHobsoftScene = api.ExtractorOrder + + +def get_order(module, name): + path = get_variable_name(module, name) + + if path not in globals().keys(): + raise KeyError("\"{0}\" could not be found in inventory.".format(path)) + + return globals()[path] + + +def get_variable_name(module, name): + plugins_directory = os.path.abspath( + os.path.join(__file__, "..", "plugins") + ) + + module = os.path.relpath(module, plugins_directory) + path = "{0}{1}".format(module, name) + path = path.replace(".py", "_") + path = path.replace(os.sep, "_") + + return path diff --git a/pype/nukestudio/lib.py b/pype/nukestudio/lib.py new file mode 100644 index 0000000000..e2a11dea08 --- /dev/null +++ b/pype/nukestudio/lib.py @@ -0,0 +1,242 @@ +# Standard library +import os +import sys + +# Pyblish libraries +import pyblish.api + +# Host libraries +import hiero + +from PySide2 import (QtWidgets, QtGui) + +# Local libraries +import plugins + +cached_process = None + + +self = sys.modules[__name__] +self._has_been_setup = False +self._has_menu = False +self._registered_gui = None + + +def setup(console=False, port=None, menu=True): + """Setup integration + + Registers Pyblish for Hiero plug-ins and appends an item to the File-menu + + Arguments: + console (bool): Display console with GUI + port (int, optional): Port from which to start looking for an + available port to connect with Pyblish QML, default + provided by Pyblish Integration. + menu (bool, optional): Display file menu in Hiero. + """ + + if self._has_been_setup: + teardown() + + # register bumpybox plugins + pyblish.api.register_plugin_path(r"C:\Users\hubert\CODE\github\pyblish-bumpybox\pyblish_bumpybox\plugins\nukestudio") + + register_plugins() + register_host() + add_submission() + + if menu: + add_to_filemenu() + self._has_menu = True + + self._has_been_setup = True + print("pyblish: Loaded successfully.") + + +def show(): + """Try showing the most desirable GUI + This function cycles through the currently registered + graphical user interfaces, if any, and presents it to + the user. + """ + + return (_discover_gui() or _show_no_gui)() + + +def _discover_gui(): + """Return the most desirable of the currently registered GUIs""" + + # Prefer last registered + guis = reversed(pyblish.api.registered_guis()) + + for gui in list(guis) + ["pyblish_lite"]: + try: + gui = __import__(gui).show + except (ImportError, AttributeError): + continue + else: + return gui + + +def teardown(): + """Remove integration""" + if not self._has_been_setup: + return + + deregister_plugins() + deregister_host() + + if self._has_menu: + remove_from_filemenu() + self._has_menu = False + + self._has_been_setup = False + print("pyblish: Integration torn down successfully") + + +def remove_from_filemenu(): + raise NotImplementedError("Implement me please.") + + +def deregister_plugins(): + # De-register accompanying plugins + plugin_path = os.path.dirname(plugins.__file__) + pyblish.api.deregister_plugin_path(plugin_path) + print("pyblish: Deregistered %s" % plugin_path) + + +def register_host(): + """Register supported hosts""" + pyblish.api.register_host("nukestudio") + + +def deregister_host(): + """De-register supported hosts""" + pyblish.api.deregister_host("nukestudio") + + +def register_plugins(): + # Register accompanying plugins + plugin_path = os.path.dirname(plugins.__file__) + pyblish.api.register_plugin_path(plugin_path) + + +def add_to_filemenu(): + PublishAction() + + +class PyblishSubmission(hiero.exporters.FnSubmission.Submission): + + def __init__(self): + hiero.exporters.FnSubmission.Submission.__init__(self) + + def addToQueue(self): + # Add submission to Hiero module for retrieval in plugins. + hiero.submission = self + show() + + +def add_submission(): + registry = hiero.core.taskRegistry + registry.addSubmission("Pyblish", PyblishSubmission) + + +class PublishAction(QtWidgets.QAction): + def __init__(self): + QtWidgets.QAction.__init__(self, "Publish", None) + self.triggered.connect(self.publish) + + for interest in ["kShowContextMenu/kTimeline", + "kShowContextMenukBin", + "kShowContextMenu/kSpreadsheet"]: + hiero.core.events.registerInterest(interest, self.eventHandler) + + self.setShortcut("Ctrl+Alt+P") + + def publish(self): + import pyblish_nukestudio + + # Removing "submission" attribute from hiero module, to prevent tasks + # from getting picked up when not using the "Export" dialog. + if hasattr(hiero, "submission"): + del hiero.submission + + pyblish_nukestudio.show() + + def eventHandler(self, event): + + # Add the Menu to the right-click menu + event.menu.addAction(self) + + +def _show_no_gui(): + """Popup with information about how to register a new GUI + In the event of no GUI being registered or available, + this information dialog will appear to guide the user + through how to get set up with one. + """ + + messagebox = QtWidgets.QMessageBox() + messagebox.setIcon(messagebox.Warning) + messagebox.setWindowIcon(QtGui.QIcon(os.path.join( + os.path.dirname(pyblish.__file__), + "icons", + "logo-32x32.svg")) + ) + + spacer = QtWidgets.QWidget() + spacer.setMinimumSize(400, 0) + spacer.setSizePolicy(QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.Expanding) + + layout = messagebox.layout() + layout.addWidget(spacer, layout.rowCount(), 0, 1, layout.columnCount()) + + messagebox.setWindowTitle("Uh oh") + messagebox.setText("No registered GUI found.") + + if not pyblish.api.registered_guis(): + messagebox.setInformativeText( + "In order to show you a GUI, one must first be registered. " + "Press \"Show details...\" below for information on how to " + "do that.") + + messagebox.setDetailedText( + "Pyblish supports one or more graphical user interfaces " + "to be registered at once, the next acting as a fallback to " + "the previous." + "\n" + "\n" + "For example, to use Pyblish Lite, first install it:" + "\n" + "\n" + "$ pip install pyblish-lite" + "\n" + "\n" + "Then register it, like so:" + "\n" + "\n" + ">>> import pyblish.api\n" + ">>> pyblish.api.register_gui(\"pyblish_lite\")" + "\n" + "\n" + "The next time you try running this, Lite will appear." + "\n" + "See http://api.pyblish.com/register_gui.html for " + "more information.") + + else: + messagebox.setInformativeText( + "None of the registered graphical user interfaces " + "could be found." + "\n" + "\n" + "Press \"Show details\" for more information.") + + messagebox.setDetailedText( + "These interfaces are currently registered." + "\n" + "%s" % "\n".join(pyblish.api.registered_guis())) + + messagebox.setStandardButtons(messagebox.Ok) + messagebox.exec_() diff --git a/pype/nukestudio/menu.py b/pype/nukestudio/menu.py new file mode 100644 index 0000000000..9180c924ba --- /dev/null +++ b/pype/nukestudio/menu.py @@ -0,0 +1,7 @@ +import nuke +from avalon.api import Session + +from pype.nuke import lib + + +def install(): diff --git a/pype/plugins/nukestudio/publish/collect.py b/pype/plugins/nukestudio/publish/collect.py new file mode 100644 index 0000000000..c2eeb25235 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect.py @@ -0,0 +1,188 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class CollectFramerate(api.ContextPlugin): + """Collect framerate from selected sequence.""" + + order = inventory.get_order(__file__, "CollectFramerate") + label = "Framerate" + hosts = ["nukestudio"] + + def process(self, context): + for item in context.data.get("selection", []): + context.data["framerate"] = item.sequence().framerate().toFloat() + return + + +class CollectTrackItems(api.ContextPlugin): + """Collect all tasks from submission.""" + + order = inventory.get_order(__file__, "CollectTrackItems") + label = "Track Items" + hosts = ["nukestudio"] + + def process(self, context): + import os + + submission = context.data.get("submission", None) + data = {} + + # Set handles + handles = 0 + if submission: + for task in submission.getLeafTasks(): + + if task._cutHandles: + handles = task._cutHandles + + # Skip audio track items + media_type = "core.Hiero.Python.TrackItem.MediaType.kAudio" + if str(task._item.mediaType()) == media_type: + continue + + item = task._item + if item.name() not in data: + data[item.name()] = {"item": item, "tasks": [task]} + else: + data[item.name()]["tasks"].append(task) + + data[item.name()]["startFrame"] = task.outputRange()[0] + data[item.name()]["endFrame"] = task.outputRange()[1] + else: + for item in context.data.get("selection", []): + # Skip audio track items + # Try/Except is to handle items types, like EffectTrackItem + try: + media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" + if str(item.mediaType()) != media_type: + continue + except: + continue + + data[item.name()] = { + "item": item, + "tasks": [], + "startFrame": item.timelineIn(), + "endFrame": item.timelineOut() + } + + for key, value in data.iteritems(): + + context.create_instance( + name=key, + item=value["item"], + family="trackItem", + tasks=value["tasks"], + startFrame=value["startFrame"] + handles, + endFrame=value["endFrame"] - handles, + handles=handles + ) + context.create_instance( + name=key + "_review", + item=value["item"], + family="review", + families=["output"], + handles=handles, + output_path=os.path.abspath( + os.path.join( + context.data["activeProject"].path(), + "..", + "workspace", + key + ".mov" + ) + ) + ) + + +class CollectTasks(api.ContextPlugin): + """Collect all tasks from submission.""" + + order = inventory.get_order(__file__, "CollectTasks") + label = "Tasks" + hosts = ["nukestudio"] + + def process(self, context): + import os + import re + + import hiero.exporters as he + import clique + + for parent in context: + if "trackItem" != parent.data["family"]: + continue + + for task in parent.data["tasks"]: + asset_type = None + + hiero_cls = he.FnSymLinkExporter.SymLinkExporter + if isinstance(task, hiero_cls): + asset_type = "img" + movie_formats = [".mov", ".R3D"] + ext = os.path.splitext(task.resolvedExportPath())[1] + if ext in movie_formats: + asset_type = "mov" + + hiero_cls = he.FnTranscodeExporter.TranscodeExporter + if isinstance(task, hiero_cls): + asset_type = "img" + if task.resolvedExportPath().endswith(".mov"): + asset_type = "mov" + + hiero_cls = he.FnNukeShotExporter.NukeShotExporter + if isinstance(task, hiero_cls): + asset_type = "scene" + + hiero_cls = he.FnAudioExportTask.AudioExportTask + if isinstance(task, hiero_cls): + asset_type = "audio" + + # Skip all non supported export types + if not asset_type: + continue + + resolved_path = task.resolvedExportPath() + + # Formatting the basename to not include frame padding or + # extension. + name = os.path.splitext(os.path.basename(resolved_path))[0] + name = name.replace(".", "") + name = name.replace("#", "") + name = re.sub(r"%.*d", "", name) + instance = context.create_instance(name=name, parent=parent) + + instance.data["task"] = task + instance.data["item"] = parent.data["item"] + + instance.data["family"] = "trackItem.task" + instance.data["families"] = [asset_type, "local", "task"] + + label = "{1}/{0} - {2} - local".format( + name, parent, asset_type + ) + instance.data["label"] = label + + instance.data["handles"] = parent.data["handles"] + + # Add collection or output + if asset_type == "img": + collection = None + + if "#" in resolved_path: + head = resolved_path.split("#")[0] + padding = resolved_path.count("#") + tail = resolved_path.split("#")[-1] + + collection = clique.Collection( + head=head, padding=padding, tail=tail + ) + + if "%" in resolved_path: + collection = clique.parse( + resolved_path, pattern="{head}{padding}{tail}" + ) + + instance.data["collection"] = collection + else: + instance.data["output_path"] = resolved_path diff --git a/pype/plugins/nukestudio/publish/collect_active_project.py b/pype/plugins/nukestudio/publish/collect_active_project.py new file mode 100644 index 0000000000..0ac6192e4a --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_active_project.py @@ -0,0 +1,13 @@ +import pyblish.api + + +class CollectActiveProject(pyblish.api.ContextPlugin): + """Inject the active project into context""" + + order = pyblish.api.CollectorOrder - 0.2 + + def process(self, context): + import hiero + + context.data["activeProject"] = hiero.ui.activeSequence().project() + self.log.info("activeProject: {}".format(context.data["activeProject"])) diff --git a/pype/plugins/nukestudio/publish/collect_colorspace.py b/pype/plugins/nukestudio/publish/collect_colorspace.py new file mode 100644 index 0000000000..2b629ba1f7 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_colorspace.py @@ -0,0 +1,27 @@ +import pyblish.api + + +class CollectProjectColorspace(pyblish.api.ContextPlugin): + """get active project color settings""" + + order = pyblish.api.CollectorOrder + 0.1 + label = "Project's color settings" + def process(self, context): + import hiero + + project = context.data["activeProject"] + colorspace = {} + colorspace["useOCIOEnvironmentOverride"] = project.useOCIOEnvironmentOverride() + colorspace["lutSetting16Bit"] = project.lutSetting16Bit() + colorspace["lutSetting8Bit"] = project.lutSetting8Bit() + colorspace["lutSettingFloat"] = project.lutSettingFloat() + colorspace["lutSettingLog"] = project.lutSettingLog() + colorspace["lutSettingViewer"] = project.lutSettingViewer() + colorspace["lutSettingWorkingSpace"] = project.lutSettingWorkingSpace() + colorspace["lutUseOCIOForExport"] = project.lutUseOCIOForExport() + colorspace["ocioConfigName"] = project.ocioConfigName() + colorspace["ocioConfigPath"] = project.ocioConfigPath() + + context.data["colorspace"] = colorspace + + self.log.info("context.data[colorspace]: {}".format(context.data["colorspace"])) diff --git a/pype/plugins/nukestudio/publish/collect_current_file.py b/pype/plugins/nukestudio/publish/collect_current_file.py new file mode 100644 index 0000000000..010d4e15ab --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_current_file.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectCurrentFile(pyblish.api.ContextPlugin): + """Inject the current working file into context""" + + order = pyblish.api.CollectorOrder + + def process(self, context): + """Todo, inject the current working file""" + + project = context.data('activeProject') + context.set_data('currentFile', value=project.path()) + self.log.info("currentFile: {}".format(context.data["currentFile"])) diff --git a/pype/plugins/nukestudio/publish/collect_host.py b/pype/plugins/nukestudio/publish/collect_host.py new file mode 100644 index 0000000000..caad4d344a --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_host.py @@ -0,0 +1,13 @@ +import pyblish.api + + +class CollectHost(pyblish.api.ContextPlugin): + """Inject the host into context""" + + order = pyblish.api.CollectorOrder + + def process(self, context): + import pyblish.api + + context.set_data("host", pyblish.api.current_host()) + self.log.info("current host: {}".format(pyblish.api.current_host())) diff --git a/pype/plugins/nukestudio/publish/collect_host_version.py b/pype/plugins/nukestudio/publish/collect_host_version.py new file mode 100644 index 0000000000..267d035f4d --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_host_version.py @@ -0,0 +1,12 @@ +import pyblish.api + + +class CollectHostVersion(pyblish.api.ContextPlugin): + """Inject the hosts version into context""" + + order = pyblish.api.CollectorOrder + + def process(self, context): + import nuke + + context.set_data('hostVersion', value=nuke.NUKE_VERSION_STRING) diff --git a/pype/plugins/nukestudio/publish/collect_selection.py b/pype/plugins/nukestudio/publish/collect_selection.py new file mode 100644 index 0000000000..e22ea79a05 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_selection.py @@ -0,0 +1,15 @@ +import pyblish.api + +import hiero + +class CollectSelection(pyblish.api.ContextPlugin): + """Inject the selection in the context.""" + + order = pyblish.api.CollectorOrder - 0.1 + label = "Selection" + + def process(self, context): + selection = getattr(hiero, "selection") + + self.log.debug("selection: {}".format(selection)) + context.data["selection"] = hiero.selection diff --git a/pype/plugins/nukestudio/publish/collect_submission.py b/pype/plugins/nukestudio/publish/collect_submission.py new file mode 100644 index 0000000000..cd2b855524 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_submission.py @@ -0,0 +1,14 @@ +import pyblish.api + + +class CollectSubmission(pyblish.api.ContextPlugin): + """Collect submisson children.""" + + order = pyblish.api.CollectorOrder - 0.1 + + def process(self, context): + import hiero + + if hasattr(hiero, "submission"): + context.data["submission"] = hiero.submission + self.log.debug("__ submission: {}".format(context.data["submission"])) diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py new file mode 100644 index 0000000000..2b688cb53c --- /dev/null +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -0,0 +1,102 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ExtractReview(api.InstancePlugin): + """Extracts movie for review""" + + order = inventory.get_order(__file__, "ExtractReview") + label = "NukeStudio Review" + optional = True + hosts = ["nukestudio"] + families = ["review"] + + def process(self, instance): + import os + import time + + import hiero.core + from hiero.exporters.FnExportUtil import writeSequenceAudioWithHandles + + nukeWriter = hiero.core.nuke.ScriptWriter() + + item = instance.data["item"] + + handles = instance.data["handles"] + + sequence = item.parent().parent() + + output_path = os.path.abspath( + os.path.join( + instance.context.data["currentFile"], "..", "workspace" + ) + ) + + # Generate audio + audio_file = os.path.join( + output_path, "{0}.wav".format(instance.data["name"]) + ) + + writeSequenceAudioWithHandles( + audio_file, + sequence, + item.timelineIn(), + item.timelineOut(), + handles, + handles + ) + + # Generate Nuke script + root_node = hiero.core.nuke.RootNode( + item.timelineIn() - handles, + item.timelineOut() + handles, + fps=sequence.framerate() + ) + + root_node.addProjectSettings(instance.context.data["colorspace"]) + + nukeWriter.addNode(root_node) + + item.addToNukeScript( + script=nukeWriter, + includeRetimes=True, + retimeMethod="Frame", + startHandle=handles, + endHandle=handles + ) + + movie_path = os.path.join( + output_path, "{0}.mov".format(instance.data["name"]) + ) + write_node = hiero.core.nuke.WriteNode(movie_path.replace("\\", "/")) + self.log.info("__ write_node: {0}".format(write_node)) + write_node.setKnob("file_type", "mov") + write_node.setKnob("colorspace", instance.context.data["colorspace"]["lutSettingFloat"]) + write_node.setKnob("meta_codec", "ap4h") + write_node.setKnob("mov64_codec", "ap4h") + write_node.setKnob("mov64_bitrate", 400000) + write_node.setKnob("mov64_bitrate_tolerance", 40000000) + write_node.setKnob("mov64_quality_min", 2) + write_node.setKnob("mov64_quality_max", 31) + write_node.setKnob("mov64_gop_size", 12) + write_node.setKnob("mov64_b_frames", 0) + write_node.setKnob("raw", True ) + write_node.setKnob("mov64_audiofile", audio_file.replace("\\", "/")) + write_node.setKnob("mov32_fps", sequence.framerate()) + nukeWriter.addNode(write_node) + + nukescript_path = movie_path.replace(".mov", ".nk") + nukeWriter.writeToDisk(nukescript_path) + + process = hiero.core.nuke.executeNukeScript( + nukescript_path, + open(movie_path.replace(".mov", ".log"), "w") + ) + + while process.poll() is None: + time.sleep(0.5) + + assert os.path.exists(movie_path), "Creating review failed." + + instance.data["output_path"] = movie_path + instance.data["review_family"] = "mov" diff --git a/pype/plugins/nukestudio/publish/extract_tasks.py b/pype/plugins/nukestudio/publish/extract_tasks.py new file mode 100644 index 0000000000..c841b604f1 --- /dev/null +++ b/pype/plugins/nukestudio/publish/extract_tasks.py @@ -0,0 +1,125 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ExtractTasks(api.InstancePlugin): + """Extract tasks.""" + + order = inventory.get_order(__file__, "ExtractTasks") + label = "Tasks" + hosts = ["nukestudio"] + families = ["trackItem.task"] + optional = True + + def filelink(self, src, dst): + import filecmp + import os + import shutil + + import filelink + + # Compare files to check whether they are the same. + if os.path.exists(dst) and filecmp.cmp(src, dst): + return + + # Remove existing destination file. + if os.path.exists(dst): + os.remove(dst) + + try: + filelink.create(src, dst, filelink.HARDLINK) + self.log.debug("Linking: \"{0}\" to \"{1}\"".format(src, dst)) + except WindowsError as e: + if e.winerror == 17: + self.log.warning( + "File linking failed due to: \"{0}\". " + "Resorting to copying instead.".format(e) + ) + shutil.copy(src, dst) + else: + raise e + + def process(self, instance): + import time + import os + + import hiero.core.nuke as nuke + import hiero.exporters as he + import clique + + task = instance.data["task"] + + hiero_cls = he.FnSymLinkExporter.SymLinkExporter + if isinstance(task, hiero_cls): + src = os.path.join( + task.filepath(), + task.fileName() + ) + # Filelink each image file + if "img" in instance.data["families"]: + collection = clique.parse(src + " []") + for f in os.listdir(os.path.dirname(src)): + f = os.path.join(os.path.dirname(src), f) + + frame_offset = task.outputRange()[0] - task.inputRange()[0] + input_range = ( + int(task.inputRange()[0]), int(task.inputRange()[1]) + 1 + ) + for index in range(*input_range): + dst = task.resolvedExportPath() % (index + frame_offset) + self.filelink(src % index, dst) + # Filelink movie file + if "mov" in instance.data["families"]: + dst = task.resolvedExportPath() + self.filelink(src, dst) + + hiero_cls = he.FnTranscodeExporter.TranscodeExporter + if isinstance(task, hiero_cls): + task.startTask() + while task.taskStep(): + time.sleep(1) + + script_path = task._scriptfile + log_path = script_path.replace(".nk", ".log") + log_file = open(log_path, "w") + process = nuke.executeNukeScript(script_path, log_file, True) + + self.poll(process) + + log_file.close() + + if not task._preset.properties()["keepNukeScript"]: + os.remove(script_path) + os.remove(log_path) + + hiero_cls = he.FnNukeShotExporter.NukeShotExporter + if isinstance(task, hiero_cls): + task.startTask() + while task.taskStep(): + time.sleep(1) + + hiero_cls = he.FnAudioExportTask.AudioExportTask + if isinstance(task, hiero_cls): + task.startTask() + while task.taskStep(): + time.sleep(1) + + # Fill collection with output + if "img" in instance.data["families"]: + collection = instance.data["collection"] + path = os.path.dirname(collection.format()) + for f in os.listdir(path): + file_path = os.path.join(path, f).replace("\\", "/") + if collection.match(file_path): + collection.add(file_path) + + def poll(self, process): + import time + + returnCode = process.poll() + + # if the return code hasn't been set, Nuke is still running + if returnCode is None: + time.sleep(1) + + self.poll(process) diff --git a/pype/plugins/nukestudio/publish/validate_names.py b/pype/plugins/nukestudio/publish/validate_names.py new file mode 100644 index 0000000000..571359a3b7 --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_names.py @@ -0,0 +1,43 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ValidateNames(api.InstancePlugin): + """Validate sequence, video track and track item names. + + When creating output directories with the name of an item, ending with a + whitespace will fail the extraction. + Exact matching to optimize processing. + """ + + order = inventory.get_order(__file__, "ValidateNames") + families = ["trackItem"] + match = api.Exact + label = "Names" + hosts = ["nukestudio"] + + def process(self, instance): + + item = instance.data["item"] + + msg = "Track item \"{0}\" ends with a whitespace." + assert not item.name().endswith(" "), msg.format(item.name()) + + msg = "Video track \"{0}\" ends with a whitespace." + msg = msg.format(item.parent().name()) + assert not item.parent().name().endswith(" "), msg + + msg = "Sequence \"{0}\" ends with a whitespace." + msg = msg.format(item.parent().parent().name()) + assert not item.parent().parent().name().endswith(" "), msg + + +class ValidateNamesFtrack(ValidateNames): + """Validate sequence, video track and track item names. + + Because we are matching the families exactly, we need this plugin to + accommodate for the ftrack family addition. + """ + + order = inventory.get_order(__file__, "ValidateNamesFtrack") + families = ["trackItem", "ftrack"] diff --git a/pype/plugins/nukestudio/publish/validate_projectroot.py b/pype/plugins/nukestudio/publish/validate_projectroot.py new file mode 100644 index 0000000000..459b487bd2 --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_projectroot.py @@ -0,0 +1,53 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class RepairProjectRoot(api.Action): + + label = "Repair" + icon = "wrench" + on = "failed" + + def process(self, context, plugin): + import os + + workspace = os.path.join( + os.path.dirname(context.data["currentFile"]), + "workspace" + ).replace("\\", "/") + + if not os.path.exists(workspace): + os.makedirs(workspace) + + context.data["activeProject"].setProjectRoot(workspace) + + # Need to manually fix the tasks "_projectRoot" attribute, because + # setting the project root is not enough. + submission = context.data.get("submission", None) + if submission: + for task in submission.getLeafTasks(): + task._projectRoot = workspace + + +class ValidateProjectRoot(api.ContextPlugin): + """Validate the project root to the workspace directory.""" + + order = inventory.get_order(__file__, "ValidateProjectRoot") + label = "Project Root" + hosts = ["nukestudio"] + actions = [RepairProjectRoot] + + def process(self, context): + import os + + workspace = os.path.join( + os.path.dirname(context.data["currentFile"]), + "workspace" + ).replace("\\", "/") + project_root = context.data["activeProject"].projectRoot() + + failure_message = ( + 'The project root needs to be "{0}", its currently: "{1}"' + ).format(workspace, project_root) + + assert project_root == workspace, failure_message diff --git a/pype/plugins/nukestudio/publish/validate_resolved_paths.py b/pype/plugins/nukestudio/publish/validate_resolved_paths.py new file mode 100644 index 0000000000..110b8772b5 --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_resolved_paths.py @@ -0,0 +1,29 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ValidateResolvedPaths(api.ContextPlugin): + """Validate there are no overlapping resolved paths.""" + + order = inventory.get_order(__file__, "ValidateResolvedPaths") + label = "Resolved Paths" + hosts = ["nukestudio"] + + def process(self, context): + import os + import collections + + paths = [] + for instance in context: + if "trackItem.task" == instance.data["family"]: + paths.append( + os.path.abspath(instance.data["task"].resolvedExportPath()) + ) + + duplicates = [] + for item, count in collections.Counter(paths).items(): + if count > 1: + duplicates.append(item) + + msg = "Duplicate output paths found: {0}".format(duplicates) + assert not duplicates, msg diff --git a/pype/plugins/nukestudio/publish/validate_task.py b/pype/plugins/nukestudio/publish/validate_task.py new file mode 100644 index 0000000000..a48ae115d8 --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_task.py @@ -0,0 +1,58 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ValidateOutputRange(api.InstancePlugin): + """Validate the output range of the task. + + This compares the output range and clip associated with the task, so see + whether there is a difference. This difference indicates that the user has + selected to export the clip length for the task which is very uncommon to + do. + """ + + order = inventory.get_order(__file__, "ValidateOutputRange") + families = ["trackItem.task"] + label = "Output Range" + hosts = ["nukestudio"] + optional = True + + def process(self, instance): + + task = instance.data["task"] + item = instance.data["parent"] + + output_range = task.outputRange() + first_frame = int(item.data["item"].source().sourceIn()) + last_frame = int(item.data["item"].source().sourceOut()) + clip_duration = last_frame - first_frame + 1 + + difference = clip_duration - output_range[1] + failure_message = ( + 'Looks like you are rendering the clip length for the task ' + 'rather than the cut length. If this is intended, just uncheck ' + 'this validator after resetting, else adjust the export range in ' + 'the "Handles" section of the export dialog.' + ) + assert difference, failure_message + + +class ValidateImageSequence(api.InstancePlugin): + """Validate image sequence output path is setup correctly.""" + + order = inventory.get_order(__file__, "ValidateImageSequence") + families = ["trackItem.task", "img"] + match = api.Subset + label = "Image Sequence" + hosts = ["nukestudio"] + optional = True + + def process(self, instance): + + resolved_path = instance.data["task"].resolvedExportPath() + + msg = ( + "Image sequence output is missing a padding. Please add \"####\" " + "or \"%04d\" to the output templates." + ) + assert "#" in resolved_path or "%" in resolved_path, msg diff --git a/pype/plugins/nukestudio/publish/validate_track_item.py b/pype/plugins/nukestudio/publish/validate_track_item.py new file mode 100644 index 0000000000..3c8b3c6cfd --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_track_item.py @@ -0,0 +1,59 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ValidateTrackItem(api.InstancePlugin): + """Validate the track item to the sequence. + + Exact matching to optimize processing. + """ + + order = inventory.get_order(__file__, "ValidateTrackItem") + families = ["trackItem"] + match = api.Exact + label = "Track Item" + hosts = ["nukestudio"] + optional = True + + def process(self, instance): + + item = instance.data["item"] + self.log.info("__ item: {}".format(item)) + media_source = item.source().mediaSource() + self.log.info("__ media_source: {}".format(media_source)) + + msg = ( + 'A setting does not match between track item "{0}" and sequence ' + '"{1}".'.format(item.name(), item.sequence().name()) + + '\n\nSetting: "{0}".''\n\nTrack item: "{1}".\n\nSequence: "{2}".' + ) + + # Validate format settings. + fmt = item.sequence().format() + assert fmt.width() == media_source.width(), msg.format( + "width", fmt.width(), media_source.width() + ) + assert fmt.height() == media_source.height(), msg.format( + "height", fmt.height(), media_source.height() + ) + assert fmt.pixelAspect() == media_source.pixelAspect(), msg.format( + "pixelAspect", fmt.pixelAspect(), media_source.pixelAspect() + ) + + # Validate framerate setting. + sequence = item.sequence() + source_framerate = media_source.metadata()["foundry.source.framerate"] + assert sequence.framerate() == source_framerate, msg.format( + "framerate", source_framerate, sequence.framerate() + ) + +# +# class ValidateTrackItemFtrack(ValidateTrackItem): +# """Validate the track item to the sequence. +# +# Because we are matching the families exactly, we need this plugin to +# accommodate for the ftrack family addition. +# """ +# +# order = inventory.get_order(__file__, "ValidateTrackItemFtrack") +# families = ["trackItem", "ftrack"] diff --git a/pype/plugins/nukestudio/publish/validate_viewer_lut.py b/pype/plugins/nukestudio/publish/validate_viewer_lut.py new file mode 100644 index 0000000000..c9dc87a95b --- /dev/null +++ b/pype/plugins/nukestudio/publish/validate_viewer_lut.py @@ -0,0 +1,22 @@ +from pyblish import api +from pyblish_bumpybox import inventory + + +class ValidateViewerLut(api.ContextPlugin): + """Validate viewer lut in NukeStudio is the same as in Nuke.""" + + order = inventory.get_order(__file__, "ValidateViewerLut") + label = "Viewer LUT" + hosts = ["nukestudio"] + optional = True + + def process(self, context): + import nuke + import hiero + + # nuke_lut = nuke.ViewerProcess.node()["current"].value() + nukestudio_lut = context.data["activeProject"].lutSettingViewer() + self.log.info("__ nukestudio_lut: {}".format(nukestudio_lut)) + + msg = "Viewer LUT can only be RGB" + assert "RGB" in nukestudio_lut, msg diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py new file mode 100644 index 0000000000..4459be6713 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py @@ -0,0 +1,14 @@ +import traceback + +try: + __import__("pype.nukestudio") + __import__("pyblish") + +except ImportError as e: + print traceback.format_exc() + print("pyblish: Could not load integration: %s " % e) + +else: + # Setup integration + import pype.nukestudio.lib + pype.nukestudio.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py new file mode 100644 index 0000000000..b7e05fed7c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/selection_tracker.py @@ -0,0 +1,9 @@ +"""Puts the selection project into 'hiero.selection'""" + +import hiero + + +def selectionChanged(event): + hiero.selection = event.sender.selection() + +hiero.core.events.registerInterest('kSelectionChanged', selectionChanged) From 618822356766720fb5a5df25b39632ea21ed14ab Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Apr 2019 12:24:55 +0200 Subject: [PATCH 02/41] adding additional integration modules --- pype/nukestudio/__init__.py | 53 +- pype/nukestudio/menu.py | 80 +- .../HieroPlayer/PlayerPresets.hrox | 1108 +++++++++++++++++ .../Python/Startup/SpreadsheetExport.py | 140 +++ .../Python/Startup/setFrameRate.py | 164 +++ .../Python/Startup/version_everywhere.py | 352 ++++++ .../Python/StartupUI/PimpMySpreadsheet.py | 844 +++++++++++++ .../Python/StartupUI/Purge.py | 142 +++ .../StartupUI/nukeStyleKeyboardShortcuts.py | 36 + .../Python/StartupUI/setPosterFrame.py | 45 + .../Startup_old/pyblish_startup.py | 14 + .../Startup_old/selection_tracker.py | 9 + .../pipeline.xml | 198 +++ .../pipeline.xml | 198 +++ .../hiero_plugin_path/Templates/vfx_aces.hrox | 38 + .../Templates/vfx_linear.hrox | 38 + .../Templates/vfx_rec709.hrox | 38 + 17 files changed, 3446 insertions(+), 51 deletions(-) create mode 100644 setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py create mode 100644 setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py create mode 100644 setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py create mode 100644 setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml create mode 100644 setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index a1ee6dedf7..f3ef69608f 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -19,27 +19,18 @@ import nuke from pypeapp import Logger -# #removing logger handler created in avalon_core -# for name, handler in [(handler.get_name(), handler) -# for handler in Logger.logging.root.handlers[:]]: -# if "pype" not in str(name).lower(): -# Logger.logging.root.removeHandler(handler) - - log = Logger().get_logger(__name__, "nuke") -# log = api.Logger.getLogger(__name__, "nuke") - AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") 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, "nuke", "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "inventory") self = sys.modules[__name__] self.nLogger = None @@ -48,42 +39,6 @@ if os.getenv("PYBLISH_GUI", None): pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) -# class NukeHandler(Logger.logging.Handler): -# ''' -# Nuke Handler - emits logs into nuke's script editor. -# warning will emit nuke.warning() -# critical and fatal would popup msg dialog to alert of the error. -# ''' -# -# def __init__(self): -# api.Logger.logging.Handler.__init__(self) -# self.set_name("Pype_Nuke_Handler") -# -# def emit(self, record): -# # Formated message: -# msg = self.format(record) -# -# if record.levelname.lower() in [ -# # "warning", -# "critical", -# "fatal", -# "error" -# ]: -# nuke.message(msg) - -# -# '''Adding Nuke Logging Handler''' -# nuke_handler = NukeHandler() -# if nuke_handler.get_name() \ -# not in [handler.get_name() -# for handler in Logger.logging.root.handlers[:]]: -# api.Logger.logging.getLogger().addHandler(nuke_handler) -# api.Logger.logging.getLogger().setLevel(Logger.logging.INFO) -# -# if not self.nLogger: -# self.nLogger = Logger - - def reload_config(): """Attempt to reload pipeline at run-time. diff --git a/pype/nukestudio/menu.py b/pype/nukestudio/menu.py index 9180c924ba..b62a20559d 100644 --- a/pype/nukestudio/menu.py +++ b/pype/nukestudio/menu.py @@ -1,7 +1,83 @@ -import nuke from avalon.api import Session -from pype.nuke import lib +from pype.nukestudio import lib +import hiero.core + +try: + from PySide.QtGui import * +except Exception: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + +from hiero.ui import findMenuAction + + +# def install(): + # here is the best place to add menu + from avalon.tools import ( + creator, + publish, + workfiles, + cbloader, + cbsceneinventory, + contextmanager, + libraryloader + ) + + menu_name = os.environ['PYPE_STUDIO_NAME'] + # Grab Hiero's MenuBar + M = hiero.ui.menuBar() + + # Add a Menu to the MenuBar + file_action = None + try: + check_made_menu = findMenuAction(menu_name) + except: + pass + + if not check_made_menu: + menu = M.addMenu(menu_name) + else: + menu = check_made_menu.menu() + + actions = [{ + 'action': QAction(QIcon('icons:Position.png'), 'Set Context', None), + 'function': contextmanager.show + }, + { + 'action': QAction(QIcon('icons:ColorAdd.png'), 'Create...', None), + 'function': creator.show + }, + { + 'action': QAction(QIcon('icons:CopyRectangle.png'), 'Load...', None), + 'function': cbloader.show + }, + { + 'action': QAction(QIcon('icons:Output.png'), 'Publish...', None), + 'function': publish.show + }, + { + 'action': QAction(QIcon('icons:ModifyMetaData.png'), 'Manage...', None), + 'function': cbsceneinventory.show + }, + { + 'action': QAction(QIcon('icons:ColorAdd.png'), 'Library...', None), + 'function': libraryloader.show + }] + + + # Create menu items + for a in actions: + # create action + for k in a.keys(): + if 'action' in k: + action = a[k] + elif 'function' in k: + action.triggered.connect(a[k]) + else: + pass + # add action to menu + menu.addAction(action) diff --git a/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox b/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox new file mode 100644 index 0000000000..ec50e123f0 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/HieroPlayer/PlayerPresets.hrox @@ -0,0 +1,1108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 50 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 17 + + + 126935040 + 70 + -1 + + + 2 + 70 + 2 + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py new file mode 100644 index 0000000000..3adea8051c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/SpreadsheetExport.py @@ -0,0 +1,140 @@ +# This action adds itself to the Spreadsheet View context menu allowing the contents of the Spreadsheet be exported as a CSV file. +# Usage: Right-click in Spreadsheet > "Export as .CSV" +# Note: This only prints the text data that is visible in the active Spreadsheet View. +# If you've filtered text, only the visible text will be printed to the CSV file +# Usage: Copy to ~/.hiero/Python/StartupUI +import hiero.core.events +import hiero.ui +import os, csv +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +### Magic Widget Finding Methods - This stuff crawls all the PySide widgets, looking for an answer +def findWidget(w): + global foundryWidgets + if 'Foundry' in w.metaObject().className(): + foundryWidgets += [w] + + for c in w.children(): + findWidget(c) + return foundryWidgets + + +def getFoundryWidgetsWithClassName(filter=None): + global foundryWidgets + foundryWidgets = [] + widgets = [] + app = QApplication.instance() + for w in app.topLevelWidgets(): + findWidget(w) + + filteredWidgets = foundryWidgets + if filter: + filteredWidgets = [] + for widget in foundryWidgets: + if filter in widget.metaObject().className(): + filteredWidgets += [widget] + return filteredWidgets + + +# When right click, get the Sequence Name +def activeSpreadsheetTreeView(): + """ + Does some PySide widget Magic to detect the Active Spreadsheet TreeView. + """ + spreadsheetViews = getFoundryWidgetsWithClassName( + filter='SpreadsheetTreeView') + for spreadSheet in spreadsheetViews: + if spreadSheet.hasFocus(): + activeSpreadSheet = spreadSheet + return activeSpreadSheet + return None + + +#### Adds "Export .CSV" action to the Spreadsheet Context menu #### +class SpreadsheetExportCSVAction(QAction): + def __init__(self): + QAction.__init__(self, "Export as .CSV", None) + self.triggered.connect(self.exportCSVFromActiveSpreadsheetView) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + self.setIcon(QIcon("icons:FBGridView.png")) + + def eventHandler(self, event): + # Insert the action to the Export CSV menu + event.menu.addAction(self) + + #### The guts!.. Writes a CSV file from a Sequence Object #### + def exportCSVFromActiveSpreadsheetView(self): + + # Get the active QTreeView from the active Spreadsheet + spreadsheetTreeView = activeSpreadsheetTreeView() + + if not spreadsheetTreeView: + return 'Unable to detect the active TreeView.' + seq = hiero.ui.activeView().sequence() + if not seq: + print 'Unable to detect the active Sequence from the activeView.' + return + + # The data model of the QTreeView + model = spreadsheetTreeView.model() + + csvSavePath = os.path.join(QDir.homePath(), 'Desktop', + seq.name() + '.csv') + savePath, filter = QFileDialog.getSaveFileName( + None, + caption="Export Spreadsheet to .CSV as...", + dir=csvSavePath, + filter="*.csv") + print 'Saving To: ' + str(savePath) + + # Saving was cancelled... + if len(savePath) == 0: + return + + # Get the Visible Header Columns from the QTreeView + + #csvHeader = ['Event', 'Status', 'Shot Name', 'Reel', 'Track', 'Speed', 'Src In', 'Src Out','Src Duration', 'Dst In', 'Dst Out', 'Dst Duration', 'Clip', 'Clip Media'] + + # Get a CSV writer object + f = open(savePath, 'w') + csvWriter = csv.writer( + f, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) + + # This is a list of the Column titles + csvHeader = [] + + for col in range(0, model.columnCount()): + if not spreadsheetTreeView.isColumnHidden(col): + csvHeader += [model.headerData(col, Qt.Horizontal)] + + # Write the Header row to the CSV file + csvWriter.writerow(csvHeader) + + # Go through each row/column and print + for row in range(model.rowCount()): + row_data = [] + for col in range(model.columnCount()): + if not spreadsheetTreeView.isColumnHidden(col): + row_data.append( + model.index(row, col, QModelIndex()).data( + Qt.DisplayRole)) + + # Write row to CSV file... + csvWriter.writerow(row_data) + + f.close() + # Conveniently show the CSV file in the native file browser... + QDesktopServices.openUrl( + QUrl('file:///%s' % (os.path.dirname(savePath)))) + + +# Add the action... +csvActions = SpreadsheetExportCSVAction() diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py new file mode 100644 index 0000000000..ceb96a6fce --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/setFrameRate.py @@ -0,0 +1,164 @@ +# setFrameRate - adds a Right-click menu to the Project Bin view, allowing multiple BinItems (Clips/Sequences) to have their frame rates set. +# Install in: ~/.hiero/Python/StartupUI +# Requires 1.5v1 or later + +import hiero.core +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtCore import * + from PySide2.QtWidgets import * + +# Dialog for setting a Custom frame rate. +class SetFrameRateDialog(QDialog): + + def __init__(self,itemSelection=None,parent=None): + super(SetFrameRateDialog, self).__init__(parent) + self.setWindowTitle("Set Custom Frame Rate") + self.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed ) + layout = QFormLayout() + self._itemSelection = itemSelection + + self._frameRateField = QLineEdit() + self._frameRateField.setToolTip('Enter custom frame rate here.') + self._frameRateField.setValidator(QDoubleValidator(1, 99, 3, self)) + self._frameRateField.textChanged.connect(self._textChanged) + layout.addRow("Enter fps: ",self._frameRateField) + + # Standard buttons for Add/Cancel + self._buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self._buttonbox.accepted.connect(self.accept) + self._buttonbox.rejected.connect(self.reject) + self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(False) + layout.addRow("",self._buttonbox) + self.setLayout(layout) + + def _updateOkButtonState(self): + # Cancel is always an option but only enable Ok if there is some text. + currentFramerate = float(self.currentFramerateString()) + enableOk = False + enableOk = ((currentFramerate > 0.0) and (currentFramerate <= 250.0)) + print 'enabledOk',enableOk + self._buttonbox.button(QDialogButtonBox.Ok).setEnabled(enableOk) + + def _textChanged(self, newText): + self._updateOkButtonState() + + # Returns the current frame rate as a string + def currentFramerateString(self): + return str(self._frameRateField.text()) + + # Presents the Dialog and sets the Frame rate from a selection + def showDialogAndSetFrameRateFromSelection(self): + + if self._itemSelection is not None: + if self.exec_(): + # For the Undo loop... + + # Construct an TimeBase object for setting the Frame Rate (fps) + fps = hiero.core.TimeBase().fromString(self.currentFramerateString()) + + + # Set the frame rate for the selected BinItmes + for item in self._itemSelection: + item.setFramerate(fps) + return + +# This is just a convenience method for returning QActions with a title, triggered method and icon. +def makeAction(title, method, icon = None): + action = QAction(title,None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to set the frame rate! + def methodWrapper(): + method(title) + + action.triggered.connect( methodWrapper ) + return action + +# Menu which adds a Set Frame Rate Menu to Project Bin view +class SetFrameRateMenu: + + def __init__(self): + self._frameRateMenu = None + self._frameRatesDialog = None + + + # ant: Could use hiero.core.defaultFrameRates() here but messes up with string matching because we seem to mix decimal points + self.frameRates = ['8','12','12.50','15','23.98','24','25','29.97','30','48','50','59.94','60'] + hiero.core.events.registerInterest("kShowContextMenu/kBin", self.binViewEventHandler) + + self.menuActions = [] + + def createFrameRateMenus(self,selection): + selectedClipFPS = [str(bi.activeItem().framerate()) for bi in selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))] + selectedClipFPS = hiero.core.util.uniquify(selectedClipFPS) + sameFrameRate = len(selectedClipFPS)==1 + self.menuActions = [] + for fps in self.frameRates: + if fps in selectedClipFPS: + if sameFrameRate: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon="icons:Ticked.png")] + else: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon="icons:remove active.png")] + else: + self.menuActions+=[makeAction(fps,self.setFrameRateFromMenuSelection, icon=None)] + + # Now add Custom... menu + self.menuActions+=[makeAction('Custom...',self.setFrameRateFromMenuSelection, icon=None)] + + frameRateMenu = QMenu("Set Frame Rate") + for a in self.menuActions: + frameRateMenu.addAction(a) + + return frameRateMenu + + def setFrameRateFromMenuSelection(self, menuSelectionFPS): + + selectedBinItems = [bi.activeItem() for bi in self._selection if (isinstance(bi,hiero.core.BinItem) and hasattr(bi,'activeItem'))] + currentProject = selectedBinItems[0].project() + + with currentProject.beginUndo("Set Frame Rate"): + if menuSelectionFPS == 'Custom...': + self._frameRatesDialog = SetFrameRateDialog(itemSelection = selectedBinItems ) + self._frameRatesDialog.showDialogAndSetFrameRateFromSelection() + + else: + for b in selectedBinItems: + b.setFramerate(hiero.core.TimeBase().fromString(menuSelectionFPS)) + + return + + # This handles events from the Project Bin View + def binViewEventHandler(self,event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Bin view which gives a selection. + return + + # Reset the selection to None... + self._selection = None + s = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if s == None: + return + # Filter the selection to BinItems + self._selection = [item for item in s if isinstance(item, hiero.core.BinItem)] + if len(self._selection)==0: + return + # Creating the menu based on items selected, to highlight which frame rates are contained + + self._frameRateMenu = self.createFrameRateMenus(self._selection) + + # Insert the Set Frame Rate Button before the Set Media Colour Transform Action + for action in event.menu.actions(): + if str(action.text()) == "Set Media Colour Transform": + event.menu.insertMenu(action, self._frameRateMenu) + break + +# Instantiate the Menu to get it to register itself. +SetFrameRateMenu = SetFrameRateMenu() \ No newline at end of file diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py new file mode 100644 index 0000000000..e85e02bfa5 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/version_everywhere.py @@ -0,0 +1,352 @@ +# version_up_everywhere.py +# Adds action to enable a Clip/Shot to be Min/Max/Next/Prev versioned in all shots used in a Project. +# +# Usage: +# 1) Copy file to /Python/Startup +# 2) Right-click on Clip(s) or Bins containing Clips in in the Bin View, or on Shots in the Timeline/Spreadsheet +# 3) Set Version for all Shots > OPTION to update the version in all shots where the Clip is used in the Project. + +import hiero.core +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +def whereAmI(self, searchType='TrackItem'): + """returns a list of TrackItem or Sequnece objects in the Project which contain this Clip. + By default this will return a list of TrackItems where the Clip is used in its project. + You can also return a list of Sequences by specifying the searchType to be 'Sequence'. + Should consider putting this into hiero.core.Clip by default? + + Example usage: + + shotsForClip = clip.whereAmI('TrackItem') + sequencesForClip = clip.whereAmI('Sequence') + """ + proj = self.project() + + if ('TrackItem' not in searchType) and ('Sequence' not in searchType): + print "searchType argument must be 'TrackItem' or 'Sequence'" + return None + + # If user specifies a TrackItem, then it will return + searches = hiero.core.findItemsInProject(proj, searchType) + + if len(searches) == 0: + print 'Unable to find %s in any items of type: %s' % (str(self), + str(searchType)) + return None + + # Case 1: Looking for Shots (trackItems) + clipUsedIn = [] + if isinstance(searches[0], hiero.core.TrackItem): + for shot in searches: + # We have to wrap this in a try/except because it's possible through the Python API for a Shot to exist without a Clip in the Bin + try: + + # For versioning to work, we must look to the BinItem that a Clip is wrapped in. + if shot.source().binItem() == self.binItem(): + clipUsedIn.append(shot) + + # If we throw an exception here its because the Shot did not have a Source Clip in the Bin. + except RuntimeError: + hiero.core.log.info( + 'Unable to find Parent Clip BinItem for Shot: %s, Source:%s' + % (shot, shot.source())) + pass + + # Case 1: Looking for Shots (trackItems) + elif isinstance(searches[0], hiero.core.Sequence): + for seq in searches: + # Iterate tracks > shots... + tracks = seq.items() + for track in tracks: + shots = track.items() + for shot in shots: + if shot.source().binItem() == self.binItem(): + clipUsedIn.append(seq) + + return clipUsedIn + + +# Add whereAmI method to Clip object +hiero.core.Clip.whereAmI = whereAmI + + +#### MAIN VERSION EVERYWHERE GUBBINS ##### +class VersionAllMenu(object): + + # These are a set of action names we can use for operating on multiple Clip/TrackItems + eMaxVersion = "Max Version" + eMinVersion = "Min Version" + eNextVersion = "Next Version" + ePreviousVersion = "Previous Version" + + # This is the title used for the Version Menu title. It's long isn't it? + actionTitle = "Set Version for all Shots" + + def __init__(self): + self._versionEverywhereMenu = None + self._versionActions = [] + + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.binViewEventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.binViewEventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.binViewEventHandler) + + def showVersionUpdateReportFromShotManifest(self, sequenceShotManifest): + """This just displays an info Message box, based on a Sequence[Shot] manifest dictionary""" + + # Now present an info dialog, explaining where shots were updated + updateReportString = "The following Versions were updated:\n" + for seq in sequenceShotManifest.keys(): + updateReportString += "%s:\n Shots:\n" % (seq.name()) + for shot in sequenceShotManifest[seq]: + updateReportString += ' %s\n (New Version: %s)\n' % ( + shot.name(), shot.currentVersion().name()) + updateReportString += '\n' + + infoBox = QMessageBox(hiero.ui.mainWindow()) + infoBox.setIcon(QMessageBox.Information) + + if len(sequenceShotManifest) <= 0: + infoBox.setText("No Shot Versions were updated") + infoBox.setInformativeText( + "Clip could not be found in any Shots in this Project") + else: + infoBox.setText( + "Versions were updated in %i Sequences of this Project." % + (len(sequenceShotManifest))) + infoBox.setInformativeText("Show Details for more info.") + infoBox.setDetailedText(updateReportString) + + infoBox.exec_() + + def makeVersionActionForSingleClip(self, version): + """This is used to populate the QAction list of Versions when a single Clip is selected in the BinView. + It also triggers the Version Update action based on the version passed to it. + (Not sure if this is good design practice, but it's compact!)""" + action = QAction(version.name(), None) + action.setData(lambda: version) + + def updateAllTrackItems(): + currentClip = version.item() + trackItems = currentClip.whereAmI() + if not trackItems: + return + + proj = currentClip.project() + + # A Sequence-Shot manifest dictionary + sequenceShotManifest = {} + + # Make this all undo-able in a single Group undo + with proj.beginUndo( + "Update All Versions for %s" % currentClip.name()): + for shot in trackItems: + seq = shot.parentSequence() + if seq not in sequenceShotManifest.keys(): + sequenceShotManifest[seq] = [shot] + else: + sequenceShotManifest[seq] += [shot] + shot.setCurrentVersion(version) + + # We also should update the current Version of the selected Clip for completeness... + currentClip.binItem().setActiveVersion(version) + + # Now disaplay a Dialog which informs the user of where and what was changed + self.showVersionUpdateReportFromShotManifest(sequenceShotManifest) + + action.triggered.connect(updateAllTrackItems) + return action + + # This is just a convenience method for returning QActions with a title, triggered method and icon. + def makeAction(self, title, method, icon=None): + action = QAction(title, None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to trigger the version change + def methodWrapper(): + method(title) + + action.triggered.connect(methodWrapper) + return action + + def clipSelectionFromView(self, view): + """Helper method to return a list of Clips in the Active View""" + selection = hiero.ui.activeView().selection() + + if len(selection) == 0: + return None + + if isinstance(view, hiero.ui.BinView): + # We could have a mixture of Bins and Clips selected, so sort of the Clips and Clips inside Bins + clipItems = [ + item.activeItem() for item in selection + if hasattr(item, "activeItem") + and isinstance(item.activeItem(), hiero.core.Clip) + ] + + # We'll also append Bins here, and see if can find Clips inside + bins = [ + item for item in selection if isinstance(item, hiero.core.Bin) + ] + + # We search inside of a Bin for a Clip which is not already in clipBinItems + if len(bins) > 0: + # Grab the Clips inside of a Bin and append them to a list + for bin in bins: + clips = hiero.core.findItemsInBin(bin, 'Clip') + for clip in clips: + if clip not in clipItems: + clipItems.append(clip) + + elif isinstance(view, + (hiero.ui.TimelineEditor, hiero.ui.SpreadsheetView)): + # Here, we have shots. To get to the Clip froma TrackItem, just call source() + clipItems = [ + item.source() for item in selection if hasattr(item, "source") + and isinstance(item, hiero.core.TrackItem) + ] + + return clipItems + + # This generates the Version Up Everywhere menu + def createVersionEveryWhereMenuForView(self, view): + + versionEverywhereMenu = QMenu(self.actionTitle) + self._versionActions = [] + # We look to the activeView for a selection of Clips + clips = self.clipSelectionFromView(view) + + # And bail if nothing is found + if len(clips) == 0: + return versionEverywhereMenu + + # Now, if we have just one Clip selected, we'll form a special menu, which lists all versions + if len(clips) == 1: + + # Get a reversed list of Versions, so that bigger ones appear at top + versions = list(reversed(clips[0].binItem().items())) + for version in versions: + self._versionActions += [ + self.makeVersionActionForSingleClip(version) + ] + + elif len(clips) > 1: + # We will add Max/Min/Prev/Next options, which can be called on a TrackItem, without the need for a Version object + self._versionActions += [ + self.makeAction( + self.eMaxVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.eMinVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.eNextVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + self._versionActions += [ + self.makeAction( + self.ePreviousVersion, + self.setTrackItemVersionForClipSelection, + icon=None) + ] + + for act in self._versionActions: + versionEverywhereMenu.addAction(act) + + return versionEverywhereMenu + + def setTrackItemVersionForClipSelection(self, versionOption): + + view = hiero.ui.activeView() + if not view: + return + + clipSelection = self.clipSelectionFromView(view) + + if len(clipSelection) == 0: + return + + proj = clipSelection[0].project() + + # Create a Sequence-Shot Manifest, to report to users where a Shot was updated + sequenceShotManifest = {} + + with proj.beginUndo("Update multiple Versions"): + for clip in clipSelection: + + # Look to see if it exists in a TrackItem somewhere... + shotUsage = clip.whereAmI('TrackItem') + + # Next, depending on the versionOption, make the appropriate update + # There's probably a more neat/compact way of doing this... + for shot in shotUsage: + + # This step is done for reporting reasons + seq = shot.parentSequence() + if seq not in sequenceShotManifest.keys(): + sequenceShotManifest[seq] = [shot] + else: + sequenceShotManifest[seq] += [shot] + + if versionOption == self.eMaxVersion: + shot.maxVersion() + elif versionOption == self.eMinVersion: + shot.minVersion() + elif versionOption == self.eNextVersion: + shot.nextVersion() + elif versionOption == self.ePreviousVersion: + shot.prevVersion() + + # Finally, for completeness, set the Max/Min version of the Clip too (if chosen) + # Note: It doesn't make sense to do Next/Prev on a Clip here because next/prev means different things for different Shots + if versionOption == self.eMaxVersion: + clip.binItem().maxVersion() + elif versionOption == self.eMinVersion: + clip.binItem().minVersion() + + # Now disaplay a Dialog which informs the user of where and what was changed + self.showVersionUpdateReportFromShotManifest(sequenceShotManifest) + + # This handles events from the Project Bin View + def binViewEventHandler(self, event): + + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Bin view which gives a selection. + return + selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Localise Menu. + if selection == None: + return + + view = hiero.ui.activeView() + # Only add the Menu if Bins or Sequences are selected (this ensures menu isn't added in the Tags Pane) + if len(selection) > 0: + self._versionEverywhereMenu = self.createVersionEveryWhereMenuForView( + view) + hiero.ui.insertMenuAction( + self._versionEverywhereMenu.menuAction(), + event.menu, + after="foundry.menu.version") + return + + +# Instantiate the Menu to get it to register itself. +VersionAllMenu = VersionAllMenu() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py new file mode 100644 index 0000000000..3d40aa0293 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/PimpMySpreadsheet.py @@ -0,0 +1,844 @@ +# PimpMySpreadsheet 1.0, Antony Nasce, 23/05/13. +# Adds custom spreadsheet columns and right-click menu for setting the Shot Status, and Artist Shot Assignement. +# gStatusTags is a global dictionary of key(status)-value(icon) pairs, which can be overridden with custom icons if required +# Requires Hiero 1.7v2 or later. +# Install Instructions: Copy to ~/.hiero/Python/StartupUI + +import hiero.core +import hiero.ui + +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + +# Set to True, if you wat 'Set Status' right-click menu, False if not +kAddStatusMenu = True + +# Set to True, if you wat 'Assign Artist' right-click menu, False if not +kAssignArtistMenu = True + +# Global list of Artist Name Dictionaries +# Note: Override this to add different names, icons, department, IDs. +gArtistList = [{ + 'artistName': 'John Smith', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': '3D', + 'artistID': 0 +}, { + 'artistName': 'Savlvador Dali', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Roto', + 'artistID': 1 +}, { + 'artistName': 'Leonardo Da Vinci', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Paint', + 'artistID': 2 +}, { + 'artistName': 'Claude Monet', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Comp', + 'artistID': 3 +}, { + 'artistName': 'Pablo Picasso', + 'artistIcon': 'icons:TagActor.png', + 'artistDepartment': 'Animation', + 'artistID': 4 +}] + +# Global Dictionary of Status Tags. +# Note: This can be overwritten if you want to add a new status cellType or custom icon +# Override the gStatusTags dictionary by adding your own 'Status':'Icon.png' key-value pairs. +# Add new custom keys like so: gStatusTags['For Client'] = 'forClient.png' +gStatusTags = { + 'Approved': 'icons:status/TagApproved.png', + 'Unapproved': 'icons:status/TagUnapproved.png', + 'Ready To Start': 'icons:status/TagReadyToStart.png', + 'Blocked': 'icons:status/TagBlocked.png', + 'On Hold': 'icons:status/TagOnHold.png', + 'In Progress': 'icons:status/TagInProgress.png', + 'Awaiting Approval': 'icons:status/TagAwaitingApproval.png', + 'Omitted': 'icons:status/TagOmitted.png', + 'Final': 'icons:status/TagFinal.png' +} + + +# The Custom Spreadsheet Columns +class CustomSpreadsheetColumns(QObject): + """ + A class defining custom columns for Hiero's spreadsheet view. This has a similar, but + slightly simplified, interface to the QAbstractItemModel and QItemDelegate classes. + """ + global gStatusTags + global gArtistList + + # Ideally, we'd set this list on a Per Item basis, but this is expensive for a large mixed selection + standardColourSpaces = [ + 'linear', 'sRGB', 'rec709', 'Cineon', 'Gamma1.8', 'Gamma2.2', + 'Panalog', 'REDLog', 'ViperLog' + ] + arriColourSpaces = [ + 'Video - Rec709', 'LogC - Camera Native', 'Video - P3', 'ACES', + 'LogC - Film', 'LogC - Wide Gamut' + ] + r3dColourSpaces = [ + 'Linear', 'Rec709', 'REDspace', 'REDlog', 'PDlog685', 'PDlog985', + 'CustomPDlog', 'REDgamma', 'SRGB', 'REDlogFilm', 'REDgamma2', + 'REDgamma3' + ] + gColourSpaces = standardColourSpaces + arriColourSpaces + r3dColourSpaces + + currentView = hiero.ui.activeView() + + # This is the list of Columns available + gCustomColumnList = [ + { + 'name': 'Tags', + 'cellType': 'readonly' + }, + { + 'name': 'Colourspace', + 'cellType': 'dropdown' + }, + { + 'name': 'Notes', + 'cellType': 'readonly' + }, + { + 'name': 'FileType', + 'cellType': 'readonly' + }, + { + 'name': 'Shot Status', + 'cellType': 'dropdown' + }, + { + 'name': 'Thumbnail', + 'cellType': 'readonly' + }, + { + 'name': 'MediaType', + 'cellType': 'readonly' + }, + { + 'name': 'Width', + 'cellType': 'readonly' + }, + { + 'name': 'Height', + 'cellType': 'readonly' + }, + { + 'name': 'Pixel Aspect', + 'cellType': 'readonly' + }, + { + 'name': 'Artist', + 'cellType': 'dropdown' + }, + { + 'name': 'Department', + 'cellType': 'readonly' + }, + ] + + def numColumns(self): + """ + Return the number of custom columns in the spreadsheet view + """ + return len(self.gCustomColumnList) + + def columnName(self, column): + """ + Return the name of a custom column + """ + return self.gCustomColumnList[column]['name'] + + def getTagsString(self, item): + """ + Convenience method for returning all the Notes in a Tag as a string + """ + tagNames = [] + tags = item.tags() + for tag in tags: + tagNames += [tag.name()] + tagNameString = ','.join(tagNames) + return tagNameString + + def getNotes(self, item): + """ + Convenience method for returning all the Notes in a Tag as a string + """ + notes = '' + tags = item.tags() + for tag in tags: + note = tag.note() + if len(note) > 0: + notes += tag.note() + ', ' + return notes[:-2] + + def getData(self, row, column, item): + """ + Return the data in a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + return self.getTagsString(item) + + if currentColumn['name'] == 'Colourspace': + try: + colTransform = item.sourceMediaColourTransform() + except: + colTransform = '--' + return colTransform + + if currentColumn['name'] == 'Notes': + try: + note = self.getNotes(item) + except: + note = '' + return note + + if currentColumn['name'] == 'FileType': + fileType = '--' + M = item.source().mediaSource().metadata() + if M.hasKey('foundry.source.type'): + fileType = M.value('foundry.source.type') + elif M.hasKey('media.input.filereader'): + fileType = M.value('media.input.filereader') + return fileType + + if currentColumn['name'] == 'Shot Status': + status = item.status() + if not status: + status = "--" + return str(status) + + if currentColumn['name'] == 'MediaType': + M = item.mediaType() + return str(M).split('MediaType')[-1].replace('.k', '') + + if currentColumn['name'] == 'Thumbnail': + return str(item.eventNumber()) + + if currentColumn['name'] == 'Width': + return str(item.source().format().width()) + + if currentColumn['name'] == 'Height': + return str(item.source().format().height()) + + if currentColumn['name'] == 'Pixel Aspect': + return str(item.source().format().pixelAspect()) + + if currentColumn['name'] == 'Artist': + if item.artist(): + name = item.artist()['artistName'] + return name + else: + return '--' + + if currentColumn['name'] == 'Department': + if item.artist(): + dep = item.artist()['artistDepartment'] + return dep + else: + return '--' + + return "" + + def setData(self, row, column, item, data): + """ + Set the data in a cell - unused in this example + """ + + return None + + def getTooltip(self, row, column, item): + """ + Return the tooltip for a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + return str([item.name() for item in item.tags()]) + + if currentColumn['name'] == 'Notes': + return str(self.getNotes(item)) + return "" + + def getFont(self, row, column, item): + """ + Return the tooltip for a cell + """ + return None + + def getBackground(self, row, column, item): + """ + Return the background colour for a cell + """ + if not item.source().mediaSource().isMediaPresent(): + return QColor(80, 20, 20) + return None + + def getForeground(self, row, column, item): + """ + Return the text colour for a cell + """ + #if column == 1: + # return QColor(255, 64, 64) + return None + + def getIcon(self, row, column, item): + """ + Return the icon for a cell + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Colourspace': + return QIcon("icons:LUT.png") + + if currentColumn['name'] == 'Shot Status': + status = item.status() + if status: + return QIcon(gStatusTags[status]) + + if currentColumn['name'] == 'MediaType': + mediaType = item.mediaType() + if mediaType == hiero.core.TrackItem.kVideo: + return QIcon("icons:VideoOnly.png") + elif mediaType == hiero.core.TrackItem.kAudio: + return QIcon("icons:AudioOnly.png") + + if currentColumn['name'] == 'Artist': + try: + return QIcon(item.artist()['artistIcon']) + except: + return None + return None + + def getSizeHint(self, row, column, item): + """ + Return the size hint for a cell + """ + currentColumnName = self.gCustomColumnList[column]['name'] + + if currentColumnName == 'Thumbnail': + return QSize(90, 50) + + return QSize(50, 50) + + def paintCell(self, row, column, item, painter, option): + """ + Paint a custom cell. Return True if the cell was painted, or False to continue + with the default cell painting. + """ + currentColumn = self.gCustomColumnList[column] + if currentColumn['name'] == 'Tags': + if option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + iconSize = 20 + r = QRect(option.rect.x(), + option.rect.y() + (option.rect.height() - iconSize) / 2, + iconSize, iconSize) + tags = item.tags() + if len(tags) > 0: + painter.save() + painter.setClipRect(option.rect) + for tag in item.tags(): + M = tag.metadata() + if not (M.hasKey('tag.status') + or M.hasKey('tag.artistID')): + QIcon(tag.icon()).paint(painter, r, Qt.AlignLeft) + r.translate(r.width() + 2, 0) + painter.restore() + return True + + if currentColumn['name'] == 'Thumbnail': + imageView = None + pen = QPen() + r = QRect(option.rect.x() + 2, (option.rect.y() + + (option.rect.height() - 46) / 2), + 85, 46) + if not item.source().mediaSource().isMediaPresent(): + imageView = QImage("icons:Offline.png") + pen.setColor(QColor(Qt.red)) + + if item.mediaType() == hiero.core.TrackItem.MediaType.kAudio: + imageView = QImage("icons:AudioOnly.png") + #pen.setColor(QColor(Qt.green)) + painter.fillRect(r, QColor(45, 59, 45)) + + if option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + + tags = item.tags() + painter.save() + painter.setClipRect(option.rect) + + if not imageView: + try: + imageView = item.thumbnail(item.sourceIn()) + pen.setColor(QColor(20, 20, 20)) + # If we're here, we probably have a TC error, no thumbnail, so get it from the source Clip... + except: + pen.setColor(QColor(Qt.red)) + + if not imageView: + try: + imageView = item.source().thumbnail() + pen.setColor(QColor(Qt.yellow)) + except: + imageView = QImage("icons:Offline.png") + pen.setColor(QColor(Qt.red)) + + QIcon(QPixmap.fromImage(imageView)).paint(painter, r, + Qt.AlignCenter) + painter.setPen(pen) + painter.drawRoundedRect(r, 1, 1) + painter.restore() + return True + + return False + + def createEditor(self, row, column, item, view): + """ + Create an editing widget for a custom cell + """ + self.currentView = view + + currentColumn = self.gCustomColumnList[column] + if currentColumn['cellType'] == 'readonly': + cle = QLabel() + cle.setEnabled(False) + cle.setVisible(False) + return cle + + if currentColumn['name'] == 'Colourspace': + cb = QComboBox() + for colourspace in self.gColourSpaces: + cb.addItem(colourspace) + cb.currentIndexChanged.connect(self.colourspaceChanged) + return cb + + if currentColumn['name'] == 'Shot Status': + cb = QComboBox() + cb.addItem('') + for key in gStatusTags.keys(): + cb.addItem(QIcon(gStatusTags[key]), key) + cb.addItem('--') + cb.currentIndexChanged.connect(self.statusChanged) + + return cb + + if currentColumn['name'] == 'Artist': + cb = QComboBox() + cb.addItem('') + for artist in gArtistList: + cb.addItem(artist['artistName']) + cb.addItem('--') + cb.currentIndexChanged.connect(self.artistNameChanged) + return cb + return None + + def setModelData(self, row, column, item, editor): + return False + + def dropMimeData(self, row, column, item, data, items): + """ + Handle a drag and drop operation - adds a Dragged Tag to the shot + """ + for thing in items: + if isinstance(thing, hiero.core.Tag): + item.addTag(thing) + return None + + def colourspaceChanged(self, index): + """ + This method is called when Colourspace widget changes index. + """ + index = self.sender().currentIndex() + colourspace = self.gColourSpaces[index] + selection = self.currentView.selection() + project = selection[0].project() + with project.beginUndo("Set Colourspace"): + items = [ + item for item in selection + if (item.mediaType() == hiero.core.TrackItem.MediaType.kVideo) + ] + for trackItem in items: + trackItem.setSourceMediaColourTransform(colourspace) + + def statusChanged(self, arg): + """ + This method is called when Shot Status widget changes index. + """ + view = hiero.ui.activeView() + selection = view.selection() + status = self.sender().currentText() + project = selection[0].project() + with project.beginUndo("Set Status"): + # A string of '--' characters denotes clear the status + if status != '--': + for trackItem in selection: + trackItem.setStatus(status) + else: + for trackItem in selection: + tTags = trackItem.tags() + for tag in tTags: + if tag.metadata().hasKey('tag.status'): + trackItem.removeTag(tag) + break + + def artistNameChanged(self, arg): + """ + This method is called when Artist widget changes index. + """ + view = hiero.ui.activeView() + selection = view.selection() + name = self.sender().currentText() + project = selection[0].project() + with project.beginUndo("Assign Artist"): + # A string of '--' denotes clear the assignee... + if name != '--': + for trackItem in selection: + trackItem.setArtistByName(name) + else: + for trackItem in selection: + tTags = trackItem.tags() + for tag in tTags: + if tag.metadata().hasKey('tag.artistID'): + trackItem.removeTag(tag) + break + + +def _getArtistFromID(self, artistID): + """ getArtistFromID -> returns an artist dictionary, by their given ID""" + global gArtistList + artist = [ + element for element in gArtistList + if element['artistID'] == int(artistID) + ] + if not artist: + return None + return artist[0] + + +def _getArtistFromName(self, artistName): + """ getArtistFromID -> returns an artist dictionary, by their given ID """ + global gArtistList + artist = [ + element for element in gArtistList + if element['artistName'] == artistName + ] + if not artist: + return None + return artist[0] + + +def _artist(self): + """_artist -> Returns the artist dictionary assigned to this shot""" + artist = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.artistID'): + artistID = tag.metadata().value('tag.artistID') + artist = self.getArtistFromID(artistID) + return artist + + +def _updateArtistTag(self, artistDict): + # A shot will only have one artist assigned. Check if one exists and set accordingly + + artistTag = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.artistID'): + artistTag = tag + break + + if not artistTag: + artistTag = hiero.core.Tag('Artist') + artistTag.setIcon(artistDict['artistIcon']) + artistTag.metadata().setValue('tag.artistID', + str(artistDict['artistID'])) + artistTag.metadata().setValue('tag.artistName', + str(artistDict['artistName'])) + artistTag.metadata().setValue('tag.artistDepartment', + str(artistDict['artistDepartment'])) + self.sequence().editFinished() + self.addTag(artistTag) + self.sequence().editFinished() + return + + artistTag.setIcon(artistDict['artistIcon']) + artistTag.metadata().setValue('tag.artistID', str(artistDict['artistID'])) + artistTag.metadata().setValue('tag.artistName', + str(artistDict['artistName'])) + artistTag.metadata().setValue('tag.artistDepartment', + str(artistDict['artistDepartment'])) + self.sequence().editFinished() + return + + +def _setArtistByName(self, artistName): + """ setArtistByName(artistName) -> sets the artist tag on a TrackItem by a given artistName string""" + global gArtistList + + artist = self.getArtistFromName(artistName) + if not artist: + print 'Artist name: %s was not found in the gArtistList.' % str( + artistName) + return + + # Do the update. + self.updateArtistTag(artist) + + +def _setArtistByID(self, artistID): + """ setArtistByID(artistID) -> sets the artist tag on a TrackItem by a given artistID integer""" + global gArtistList + + artist = self.getArtistFromID(artistID) + if not artist: + print 'Artist name: %s was not found in the gArtistList.' % str( + artistID) + return + + # Do the update. + self.updateArtistTag(artist) + + +# Inject status getter and setter methods into hiero.core.TrackItem +hiero.core.TrackItem.artist = _artist +hiero.core.TrackItem.setArtistByName = _setArtistByName +hiero.core.TrackItem.setArtistByID = _setArtistByID +hiero.core.TrackItem.getArtistFromName = _getArtistFromName +hiero.core.TrackItem.getArtistFromID = _getArtistFromID +hiero.core.TrackItem.updateArtistTag = _updateArtistTag + + +def _status(self): + """status -> Returns the Shot status. None if no Status is set.""" + + status = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.status'): + status = tag.metadata().value('tag.status') + return status + + +def _setStatus(self, status): + """setShotStatus(status) -> Method to set the Status of a Shot. + Adds a special kind of status Tag to a TrackItem + Example: myTrackItem.setStatus('Final') + + @param status - a string, corresponding to the Status name + """ + global gStatusTags + + # Get a valid Tag object from the Global list of statuses + if not status in gStatusTags.keys(): + print 'Status requested was not a valid Status string.' + return + + # A shot should only have one status. Check if one exists and set accordingly + statusTag = None + tags = self.tags() + for tag in tags: + if tag.metadata().hasKey('tag.status'): + statusTag = tag + break + + if not statusTag: + statusTag = hiero.core.Tag('Status') + statusTag.setIcon(gStatusTags[status]) + statusTag.metadata().setValue('tag.status', status) + self.addTag(statusTag) + + statusTag.setIcon(gStatusTags[status]) + statusTag.metadata().setValue('tag.status', status) + + self.sequence().editFinished() + return + + +# Inject status getter and setter methods into hiero.core.TrackItem +hiero.core.TrackItem.setStatus = _setStatus +hiero.core.TrackItem.status = _status + + +# This is a convenience method for returning QActions with a triggered method based on the title string +def titleStringTriggeredAction(title, method, icon=None): + action = QAction(title, None) + action.setIcon(QIcon(icon)) + + # We do this magic, so that the title string from the action is used to set the status + def methodWrapper(): + method(title) + + action.triggered.connect(methodWrapper) + return action + + +# Menu which adds a Set Status Menu to Timeline and Spreadsheet Views +class SetStatusMenu(QMenu): + def __init__(self): + QMenu.__init__(self, "Set Status", None) + + global gStatusTags + self.statuses = gStatusTags + self._statusActions = self.createStatusMenuActions() + + # Add the Actions to the Menu. + for act in self.menuActions: + self.addAction(act) + + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.eventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + + def createStatusMenuActions(self): + self.menuActions = [] + for status in self.statuses: + self.menuActions += [ + titleStringTriggeredAction( + status, + self.setStatusFromMenuSelection, + icon=gStatusTags[status]) + ] + + def setStatusFromMenuSelection(self, menuSelectionStatus): + selectedShots = [ + item for item in self._selection + if (isinstance(item, hiero.core.TrackItem)) + ] + selectedTracks = [ + item for item in self._selection + if (isinstance(item, (hiero.core.VideoTrack, + hiero.core.AudioTrack))) + ] + + # If we have a Track Header Selection, no shots could be selected, so create shotSelection list + if len(selectedTracks) >= 1: + for track in selectedTracks: + selectedShots += [ + item for item in track.items() + if (isinstance(item, hiero.core.TrackItem)) + ] + + # It's possible no shots exist on the Track, in which case nothing is required + if len(selectedShots) == 0: + return + + currentProject = selectedShots[0].project() + + with currentProject.beginUndo("Set Status"): + # Shots selected + for shot in selectedShots: + shot.setStatus(menuSelectionStatus) + + # This handles events from the Project Bin View + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Timeline/Spreadsheet view which gives a selection. + return + + # Set the current selection + self._selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if len(self._selection) == 0: + return + + event.menu.addMenu(self) + + +# Menu which adds a Set Status Menu to Timeline and Spreadsheet Views +class AssignArtistMenu(QMenu): + def __init__(self): + QMenu.__init__(self, "Assign Artist", None) + + global gArtistList + self.artists = gArtistList + self._artistsActions = self.createAssignArtistMenuActions() + + # Add the Actions to the Menu. + for act in self.menuActions: + self.addAction(act) + + hiero.core.events.registerInterest("kShowContextMenu/kTimeline", + self.eventHandler) + hiero.core.events.registerInterest("kShowContextMenu/kSpreadsheet", + self.eventHandler) + + def createAssignArtistMenuActions(self): + self.menuActions = [] + for artist in self.artists: + self.menuActions += [ + titleStringTriggeredAction( + artist['artistName'], + self.setArtistFromMenuSelection, + icon=artist['artistIcon']) + ] + + def setArtistFromMenuSelection(self, menuSelectionArtist): + selectedShots = [ + item for item in self._selection + if (isinstance(item, hiero.core.TrackItem)) + ] + selectedTracks = [ + item for item in self._selection + if (isinstance(item, (hiero.core.VideoTrack, + hiero.core.AudioTrack))) + ] + + # If we have a Track Header Selection, no shots could be selected, so create shotSelection list + if len(selectedTracks) >= 1: + for track in selectedTracks: + selectedShots += [ + item for item in track.items() + if (isinstance(item, hiero.core.TrackItem)) + ] + + # It's possible no shots exist on the Track, in which case nothing is required + if len(selectedShots) == 0: + return + + currentProject = selectedShots[0].project() + + with currentProject.beginUndo("Assign Artist"): + # Shots selected + for shot in selectedShots: + shot.setArtistByName(menuSelectionArtist) + + # This handles events from the Project Bin View + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we should only be here if raised + # by the Timeline/Spreadsheet view which gives a selection. + return + + # Set the current selection + self._selection = event.sender.selection() + + # Return if there's no Selection. We won't add the Menu. + if len(self._selection) == 0: + return + + event.menu.addMenu(self) + + +# Add the 'Set Status' context menu to Timeline and Spreadsheet +if kAddStatusMenu: + setStatusMenu = SetStatusMenu() + +if kAssignArtistMenu: + assignArtistMenu = AssignArtistMenu() + +# Register our custom columns +hiero.ui.customColumn = CustomSpreadsheetColumns() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py new file mode 100644 index 0000000000..4d2ab255ad --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/Purge.py @@ -0,0 +1,142 @@ +# Purge Unused Clips - Removes any unused Clips from a Project +# Usage: Copy to ~/.hiero/Python/StartupUI +# Demonstrates the use of hiero.core.find_items module. +# Usage: Right-click on an item in the Bin View > "Purge Unused Clips" +# Result: Any Clips not used in a Sequence in the active project will be removed +# Requires Hiero 1.5v1 or later. +# Version 1.1 + +import hiero +import hiero.core.find_items +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +class PurgeUnusedAction(QAction): + def __init__(self): + QAction.__init__(self, "Purge Unused Clips", None) + self.triggered.connect(self.PurgeUnused) + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.eventHandler) + self.setIcon(QIcon('icons:TagDelete.png')) + + # Method to return whether a Bin is empty... + def binIsEmpty(self, b): + numBinItems = 0 + bItems = b.items() + empty = False + + if len(bItems) == 0: + empty = True + return empty + else: + for b in bItems: + if isinstance(b, hiero.core.BinItem) or isinstance( + b, hiero.core.Bin): + numBinItems += 1 + if numBinItems == 0: + empty = True + + return empty + + def PurgeUnused(self): + + #Get selected items + item = self.selectedItem + proj = item.project() + + # Build a list of Projects + SEQS = hiero.core.findItems(proj, "Sequences") + + # Build a list of Clips + CLIPSTOREMOVE = hiero.core.findItems(proj, "Clips") + + if len(SEQS) == 0: + # Present Dialog Asking if User wants to remove Clips + msgBox = QMessageBox() + msgBox.setText("Purge Unused Clips") + msgBox.setInformativeText( + "You have no Sequences in this Project. Do you want to remove all Clips (%i) from Project: %s?" + % (len(CLIPSTOREMOVE), proj.name())) + msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Ok) + ret = msgBox.exec_() + if ret == QMessageBox.Cancel: + print 'Not purging anything.' + elif ret == QMessageBox.Ok: + with proj.beginUndo('Purge Unused Clips'): + BINS = [] + for clip in CLIPSTOREMOVE: + BI = clip.binItem() + B = BI.parentBin() + BINS += [B] + print 'Removing:', BI + try: + B.removeItem(BI) + except: + print 'Unable to remove: ' + BI + return + + # For each sequence, iterate through each track Item, see if the Clip is in the CLIPS list. + # Remaining items in CLIPS will be removed + + for seq in SEQS: + + #Loop through selected and make folders + for track in seq: + for trackitem in track: + + if trackitem.source() in CLIPSTOREMOVE: + CLIPSTOREMOVE.remove(trackitem.source()) + + # Present Dialog Asking if User wants to remove Clips + msgBox = QMessageBox() + msgBox.setText("Purge Unused Clips") + msgBox.setInformativeText("Remove %i unused Clips from Project %s?" % + (len(CLIPSTOREMOVE), proj.name())) + msgBox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) + msgBox.setDefaultButton(QMessageBox.Ok) + ret = msgBox.exec_() + + if ret == QMessageBox.Cancel: + print 'Cancel' + return + elif ret == QMessageBox.Ok: + BINS = [] + with proj.beginUndo('Purge Unused Clips'): + # Delete the rest of the Clips + for clip in CLIPSTOREMOVE: + BI = clip.binItem() + B = BI.parentBin() + BINS += [B] + print 'Removing:', BI + try: + B.removeItem(BI) + except: + print 'Unable to remove: ' + BI + + def eventHandler(self, event): + if not hasattr(event.sender, 'selection'): + # Something has gone wrong, we shouldn't only be here if raised + # by the Bin view which will give a selection. + return + + self.selectedItem = None + s = event.sender.selection() + + if len(s) >= 1: + self.selectedItem = s[0] + title = "Purge Unused Clips" + self.setText(title) + event.menu.addAction(self) + + return + + +# Instantiate the action to get it to register itself. +PurgeUnusedAction = PurgeUnusedAction() diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py new file mode 100644 index 0000000000..36a30e3a3c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py @@ -0,0 +1,36 @@ +# nukeStyleKeyboardShortcuts, v1, 30/07/2012, Ant Nasce. +# A few Nuke-Style File menu shortcuts for those whose muscle memory has set in... +# Usage: Copy this file to ~/.hiero/Python/StartupUI/ + +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import Clips...') +# Note: You probably best to make this 'Ctrl+R' - currently conflicts with 'Red' in the Viewer! +a.setShortcut(QKeySequence('R')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import Folder...') +a.setShortcut(QKeySequence('Shift+R')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Import EDL/XML...') +a.setShortcut(QKeySequence('Ctrl+Shift+O')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Show Metadata') +a.setShortcut(QKeySequence('I')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Edit Settings') +a.setShortcut(QKeySequence('S')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('Monitor Controls') +a.setShortcut(QKeySequence('Ctrl+U')) +#---------------------------------------------- +a = hiero.ui.findMenuAction('New Viewer') +a.setShortcut(QKeySequence('Ctrl+I')) +#---------------------------------------------- diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py new file mode 100644 index 0000000000..18398aa119 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/setPosterFrame.py @@ -0,0 +1,45 @@ +import hiero.core +import hiero.ui +try: + from PySide.QtGui import * + from PySide.QtCore import * +except: + from PySide2.QtGui import * + from PySide2.QtWidgets import * + from PySide2.QtCore import * + + +def setPosterFrame(posterFrame=.5): + ''' + Update the poster frame of the given clipItmes + posterFrame = .5 uses the centre frame, a value of 0 uses the first frame, a value of 1 uses the last frame + ''' + view = hiero.ui.activeView() + + selectedBinItems = view.selection() + selectedClipItems = [(item.activeItem() + if hasattr(item, 'activeItem') else item) + for item in selectedBinItems] + + for clip in selectedClipItems: + centreFrame = int(clip.duration() * posterFrame) + clip.setPosterFrame(centreFrame) + + +class SetPosterFrameAction(QAction): + def __init__(self): + QAction.__init__(self, "Set Poster Frame (centre)", None) + self._selection = None + + self.triggered.connect(lambda: setPosterFrame(.5)) + hiero.core.events.registerInterest("kShowContextMenu/kBin", + self.eventHandler) + + def eventHandler(self, event): + view = event.sender + # Add the Menu to the right-click menu + event.menu.addAction(self) + + +# The act of initialising the action adds it to the right-click menu... +SetPosterFrameAction() diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py b/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py new file mode 100644 index 0000000000..4459be6713 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py @@ -0,0 +1,14 @@ +import traceback + +try: + __import__("pype.nukestudio") + __import__("pyblish") + +except ImportError as e: + print traceback.format_exc() + print("pyblish: Could not load integration: %s " % e) + +else: + # Setup integration + import pype.nukestudio.lib + pype.nukestudio.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py b/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py new file mode 100644 index 0000000000..b7e05fed7c --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py @@ -0,0 +1,9 @@ +"""Puts the selection project into 'hiero.selection'""" + +import hiero + + +def selectionChanged(event): + hiero.selection = event.sender.selection() + +hiero.core.events.registerInterest('kSelectionChanged', selectionChanged) diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml new file mode 100644 index 0000000000..e24a4dbe4e --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/TaskPresets/10.5/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -0,0 +1,198 @@ + + 991 + //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + 1 + True + 3 + + + {shot}/editorial_raw.%04d.{fileext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + False + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 32 bit float + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.%04d.{ext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + True + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 16 bit half + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + To Sequence Resolution + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.nk + + + True + default + mov + + rgb + False + + False + False + False + + True + True + + {shot}/editorial_raw.%04d.{fileext} + + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend + False + True + True + + 0 + 40000000 + 12 + 31 + 2 + avc1 H.264 + Auto + mov32 + 20000 + + False + True + True + False + False + {shot}/editorial_raw.%04d.{fileext} + + None + None + None + None + None + None + None + None + None + + + 8 bit + (auto detect) + True + False + + Write_{ext} + False +
+
+
+
+ + + False + Custom + True + 10 +
diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml new file mode 100644 index 0000000000..e24a4dbe4e --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.1/Processors/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -0,0 +1,198 @@ + + 991 + //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + 1 + True + 3 + + + {shot}/editorial_raw.%04d.{fileext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + False + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 32 bit float + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.%04d.{ext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + True + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 16 bit half + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + To Sequence Resolution + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.nk + + + True + default + mov + + rgb + False + + False + False + False + + True + True + + {shot}/editorial_raw.%04d.{fileext} + + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend + False + True + True + + 0 + 40000000 + 12 + 31 + 2 + avc1 H.264 + Auto + mov32 + 20000 + + False + True + True + False + False + {shot}/editorial_raw.%04d.{fileext} + + None + None + None + None + None + None + None + None + None + + + 8 bit + (auto detect) + True + False + + Write_{ext} + False +
+
+
+
+ + + False + Custom + True + 10 +
diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox new file mode 100644 index 0000000000..684cd0d1a2 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox new file mode 100644 index 0000000000..e915a24084 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox new file mode 100644 index 0000000000..42659cf81b --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox @@ -0,0 +1,38 @@ + + + + + + + + + 2 + 70 + + + 2 + 70 + + + 2 + 70 + 13 + + + 2 + 70 + 17 + + + 2 + 70 + 2 + + + + + + + + + From 6b443ae7a40da093c22f8aa500333c92358d8bd0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 24 Apr 2019 13:28:17 +0200 Subject: [PATCH 03/41] fix(nukestudio): adding and altering more initial stuf --- pype/nukestudio/__init__.py | 47 +---- .../{pyblish_startup.py => Startup.py} | 5 + .../Startup_old/pyblish_startup.py | 14 -- .../Startup_old/selection_tracker.py | 9 - .../pipeline.xml | 198 ++++++++++++++++++ 5 files changed, 211 insertions(+), 62 deletions(-) rename setup/nukestudio/hiero_plugin_path/Python/Startup/{pyblish_startup.py => Startup.py} (72%) delete mode 100644 setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py delete mode 100644 setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py create mode 100644 setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index f3ef69608f..7a87009141 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -32,8 +32,6 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nukestudio", "inventory") -self = sys.modules[__name__] -self.nLogger = None if os.getenv("PYBLISH_GUI", None): pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) @@ -49,14 +47,12 @@ def reload_config(): import importlib for module in ( - "app", - "app.api", + "pypeapp", "{}.api".format(AVALON_CONFIG), "{}.templates".format(AVALON_CONFIG), - "{}.nuke.actions".format(AVALON_CONFIG), - "{}.nuke.templates".format(AVALON_CONFIG), - "{}.nuke.menu".format(AVALON_CONFIG), - "{}.nuke.lib".format(AVALON_CONFIG), + "{}.nukestudio.inventory".format(AVALON_CONFIG), + "{}.nukestudio.lib".format(AVALON_CONFIG), + "{}.nukestudio.menu".format(AVALON_CONFIG), ): log.info("Reloading module: {}...".format(module)) try: @@ -74,9 +70,9 @@ def install(): import sys - for path in sys.path: - if path.startswith("C:\\Users\\Public"): - sys.path.remove(path) + # for path in sys.path: + # if path.startswith("C:\\Users\\Public"): + # sys.path.remove(path) log.info("Registering Nuke plug-ins..") pyblish.register_plugin_path(PUBLISH_PATH) @@ -84,8 +80,6 @@ def install(): avalon.register_plugin_path(avalon.Creator, CREATE_PATH) avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) - pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled) - # Disable all families except for the ones we explicitly want to see family_states = [ "write", @@ -98,7 +92,7 @@ def install(): menu.install() # load data from templates - # api.load_data_from_templates() + api.load_data_from_templates() def uninstall(): @@ -107,30 +101,5 @@ def uninstall(): avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) - pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled) - # reset data from templates api.reset_data_from_templates() - - -def on_pyblish_instance_toggled(instance, old_value, new_value): - """Toggle node passthrough states on instance toggles.""" - self.log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( - instance, old_value, new_value)) - - from avalon.nuke import ( - viewer_update_and_undo_stop, - add_publish_knob - ) - - # Whether instances should be passthrough based on new value - - with viewer_update_and_undo_stop(): - n = instance[0] - try: - n["publish"].value() - except ValueError: - n = add_publish_knob(n) - log.info(" `Publish` knob was added to write node..") - - n["publish"].setValue(new_value) diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py similarity index 72% rename from setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py rename to setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py index 4459be6713..bbef6502a9 100644 --- a/setup/nukestudio/hiero_plugin_path/Python/Startup/pyblish_startup.py +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/Startup.py @@ -1,5 +1,10 @@ import traceback +# activate nukestudio from pype +import avalon.api +import pype.nukestudio +avalon.api.install(pype.nukestudio) + try: __import__("pype.nukestudio") __import__("pyblish") diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py b/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py deleted file mode 100644 index 4459be6713..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Startup_old/pyblish_startup.py +++ /dev/null @@ -1,14 +0,0 @@ -import traceback - -try: - __import__("pype.nukestudio") - __import__("pyblish") - -except ImportError as e: - print traceback.format_exc() - print("pyblish: Could not load integration: %s " % e) - -else: - # Setup integration - import pype.nukestudio.lib - pype.nukestudio.lib.setup() diff --git a/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py b/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py deleted file mode 100644 index b7e05fed7c..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Startup_old/selection_tracker.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Puts the selection project into 'hiero.selection'""" - -import hiero - - -def selectionChanged(event): - hiero.selection = event.sender.selection() - -hiero.core.events.registerInterest('kSelectionChanged', selectionChanged) diff --git a/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml new file mode 100644 index 0000000000..e24a4dbe4e --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/TaskPresets/11.2/hiero.exporters.FnShotProcessor.ShotProcessor/pipeline.xml @@ -0,0 +1,198 @@ + + 991 + //10.11.0.184/171001_ftrack/tgbvfx/editorial/nukestudio/workspace/ + 1 + True + 3 + + + {shot}/editorial_raw.%04d.{fileext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + False + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 32 bit float + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.%04d.{ext} + + + default + exr + False + all + False + False + False + False + True + + + 8 bit + (auto detect) + True + False + + True + + None + None + None + None + None + None + None + None + None + + + Zip (16 scanline) + 16 bit half + False + False + False + channels, layers and views + 45.0 + False + all metadata + + Write_{ext} + + Cubic + To Sequence Resolution + 1.0 +
True
+ width +
+ False + Blend +
+
+
+ + {shot}/editorial.nk + + + True + default + mov + + rgb + False + + False + False + False + + True + True + + {shot}/editorial_raw.%04d.{fileext} + + + Cubic + None + 1.0 +
True
+ width +
+ False + Blend + False + True + True + + 0 + 40000000 + 12 + 31 + 2 + avc1 H.264 + Auto + mov32 + 20000 + + False + True + True + False + False + {shot}/editorial_raw.%04d.{fileext} + + None + None + None + None + None + None + None + None + None + + + 8 bit + (auto detect) + True + False + + Write_{ext} + False +
+
+
+
+ + + False + Custom + True + 10 +
From 5c6bf5fd3b36f2c34c64649290d5ed9208df4ef4 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 May 2019 14:02:27 +0200 Subject: [PATCH 04/41] feat(nukestudio): updating wip --- pype/nukestudio/__init__.py | 18 +++++++++--------- pype/nukestudio/lib.py | 25 ------------------------- pype/nukestudio/menu.py | 4 +--- 3 files changed, 10 insertions(+), 37 deletions(-) diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index 7a87009141..bbae957f61 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -5,12 +5,11 @@ from pyblish import api as pyblish from .. import api -from pype.nukestudio import menu +from .menu import install as menu_install from .lib import ( show, setup, - register_plugins, add_to_filemenu ) @@ -19,7 +18,7 @@ import nuke from pypeapp import Logger -log = Logger().get_logger(__name__, "nuke") +log = Logger().get_logger(__name__, "nukestudio") AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") @@ -63,18 +62,18 @@ def reload_config(): importlib.reload(module) -def install(): +def install(config): # api.set_avalon_workdir() # reload_config() - import sys - + # import sys # for path in sys.path: # if path.startswith("C:\\Users\\Public"): # sys.path.remove(path) - log.info("Registering Nuke plug-ins..") + log.info("Registering NukeStudio plug-ins..") + pyblish.register_host("nukestudio") pyblish.register_plugin_path(PUBLISH_PATH) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) avalon.register_plugin_path(avalon.Creator, CREATE_PATH) @@ -89,14 +88,15 @@ def install(): avalon.data["familiesStateDefault"] = False avalon.data["familiesStateToggled"] = family_states - menu.install() + menu_install() # load data from templates api.load_data_from_templates() def uninstall(): - log.info("Deregistering Nuke plug-ins..") + log.info("Deregistering NukeStudio plug-ins..") + pyblish.deregister_host("nukestudio") 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/pype/nukestudio/lib.py b/pype/nukestudio/lib.py index e2a11dea08..ca1dfa3544 100644 --- a/pype/nukestudio/lib.py +++ b/pype/nukestudio/lib.py @@ -10,8 +10,6 @@ import hiero from PySide2 import (QtWidgets, QtGui) -# Local libraries -import plugins cached_process = None @@ -41,8 +39,6 @@ def setup(console=False, port=None, menu=True): # register bumpybox plugins pyblish.api.register_plugin_path(r"C:\Users\hubert\CODE\github\pyblish-bumpybox\pyblish_bumpybox\plugins\nukestudio") - register_plugins() - register_host() add_submission() if menu: @@ -98,27 +94,6 @@ def remove_from_filemenu(): raise NotImplementedError("Implement me please.") -def deregister_plugins(): - # De-register accompanying plugins - plugin_path = os.path.dirname(plugins.__file__) - pyblish.api.deregister_plugin_path(plugin_path) - print("pyblish: Deregistered %s" % plugin_path) - - -def register_host(): - """Register supported hosts""" - pyblish.api.register_host("nukestudio") - - -def deregister_host(): - """De-register supported hosts""" - pyblish.api.deregister_host("nukestudio") - - -def register_plugins(): - # Register accompanying plugins - plugin_path = os.path.dirname(plugins.__file__) - pyblish.api.register_plugin_path(plugin_path) def add_to_filemenu(): diff --git a/pype/nukestudio/menu.py b/pype/nukestudio/menu.py index b62a20559d..5fae4cf2dd 100644 --- a/pype/nukestudio/menu.py +++ b/pype/nukestudio/menu.py @@ -1,8 +1,6 @@ +import os from avalon.api import Session -from pype.nukestudio import lib - - import hiero.core try: From 38c693a7925f02dc07f67ac55b7cd6402f73d132 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 11 May 2019 18:17:39 +0200 Subject: [PATCH 05/41] feat(nukestudio): adding otio plugin --- .../Startup/otioexporter/OTIOExportTask.py | 369 +++++++++++++++ .../Startup/otioexporter/OTIOExportUI.py | 65 +++ .../Python/Startup/otioexporter/__init__.py | 29 ++ .../StartupUI/otioimporter/OTIOImport.py | 435 ++++++++++++++++++ .../Python/StartupUI/otioimporter/__init__.py | 57 +++ 5 files changed, 955 insertions(+) create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py create mode 100644 setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py new file mode 100644 index 0000000000..77dc9c45b3 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportTask.py @@ -0,0 +1,369 @@ +# MIT License +# +# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +import os +import re +import hiero.core +from hiero.core import util + +import opentimelineio as otio + + +marker_color_map = { + "magenta": otio.schema.MarkerColor.MAGENTA, + "red": otio.schema.MarkerColor.RED, + "yellow": otio.schema.MarkerColor.YELLOW, + "green": otio.schema.MarkerColor.GREEN, + "cyan": otio.schema.MarkerColor.CYAN, + "blue": otio.schema.MarkerColor.BLUE, +} + + +class OTIOExportTask(hiero.core.TaskBase): + + def __init__(self, initDict): + """Initialize""" + hiero.core.TaskBase.__init__(self, initDict) + + def name(self): + return str(type(self)) + + def get_rate(self, item): + num, den = item.framerate().toRational() + rate = float(num) / float(den) + + if rate.is_integer(): + return rate + + return round(rate, 2) + + def get_clip_ranges(self, trackitem): + # Is clip an audio file? Use sequence frame rate + if not trackitem.source().mediaSource().hasVideo(): + rate_item = trackitem.sequence() + + else: + rate_item = trackitem.source() + + source_rate = self.get_rate(rate_item) + + # Reversed video/audio + if trackitem.playbackSpeed() < 0: + start = trackitem.sourceOut() + + else: + start = trackitem.sourceIn() + + source_start_time = otio.opentime.RationalTime( + start, + source_rate + ) + source_duration = otio.opentime.RationalTime( + trackitem.duration(), + source_rate + ) + + source_range = otio.opentime.TimeRange( + start_time=source_start_time, + duration=source_duration + ) + + available_range = None + hiero_clip = trackitem.source() + if not hiero_clip.mediaSource().isOffline(): + start_time = otio.opentime.RationalTime( + hiero_clip.mediaSource().startTime(), + source_rate + ) + duration = otio.opentime.RationalTime( + hiero_clip.mediaSource().duration(), + source_rate + ) + available_range = otio.opentime.TimeRange( + start_time=start_time, + duration=duration + ) + + return source_range, available_range + + def add_gap(self, trackitem, otio_track, prev_out): + gap_length = trackitem.timelineIn() - prev_out + if prev_out != 0: + gap_length -= 1 + + rate = self.get_rate(trackitem.sequence()) + gap = otio.opentime.TimeRange( + duration=otio.opentime.RationalTime( + gap_length, + rate + ) + ) + otio_gap = otio.schema.Gap(source_range=gap) + otio_track.append(otio_gap) + + def get_marker_color(self, tag): + icon = tag.icon() + pat = 'icons:Tag(?P\w+)\.\w+' + + res = re.search(pat, icon) + if res: + color = res.groupdict().get('color') + if color.lower() in marker_color_map: + return marker_color_map[color.lower()] + + return otio.schema.MarkerColor.RED + + def add_markers(self, hiero_item, otio_item): + for tag in hiero_item.tags(): + if not tag.visible(): + continue + + if tag.name() == 'Copy': + # Hiero adds this tag to a lot of clips + continue + + frame_rate = self.get_rate(hiero_item) + + marked_range = otio.opentime.TimeRange( + start_time=otio.opentime.RationalTime( + tag.inTime(), + frame_rate + ), + duration=otio.opentime.RationalTime( + int(tag.metadata().dict().get('tag.length', '0')), + frame_rate + ) + ) + + marker = otio.schema.Marker( + name=tag.name(), + color=self.get_marker_color(tag), + marked_range=marked_range, + metadata={ + 'Hiero': tag.metadata().dict() + } + ) + + otio_item.markers.append(marker) + + def add_clip(self, trackitem, otio_track, itemindex): + hiero_clip = trackitem.source() + + # Add Gap if needed + prev_item = ( + itemindex and trackitem.parent().items()[itemindex - 1] or + trackitem + ) + + if prev_item == trackitem and trackitem.timelineIn() > 0: + self.add_gap(trackitem, otio_track, 0) + + elif ( + prev_item != trackitem and + prev_item.timelineOut() != trackitem.timelineIn() + ): + self.add_gap(trackitem, otio_track, prev_item.timelineOut()) + + # Create Clip + source_range, available_range = self.get_clip_ranges(trackitem) + + otio_clip = otio.schema.Clip() + otio_clip.name = trackitem.name() + otio_clip.source_range = source_range + + # Add media reference + media_reference = otio.schema.MissingReference() + if not hiero_clip.mediaSource().isOffline(): + source = hiero_clip.mediaSource() + media_reference = otio.schema.ExternalReference() + media_reference.available_range = available_range + + path, name = os.path.split(source.fileinfos()[0].filename()) + media_reference.target_url = os.path.join(path, name) + media_reference.name = name + + otio_clip.media_reference = media_reference + + # Add Time Effects + playbackspeed = trackitem.playbackSpeed() + if playbackspeed != 1: + if playbackspeed == 0: + time_effect = otio.schema.FreezeFrame() + + else: + time_effect = otio.schema.LinearTimeWarp( + time_scalar=playbackspeed + ) + otio_clip.effects.append(time_effect) + + # Add tags as markers + if self._preset.properties()["includeTags"]: + self.add_markers(trackitem.source(), otio_clip) + + otio_track.append(otio_clip) + + # Add Transition if needed + if trackitem.inTransition() or trackitem.outTransition(): + self.add_transition(trackitem, otio_track) + + def add_transition(self, trackitem, otio_track): + transitions = [] + + if trackitem.inTransition(): + if trackitem.inTransition().alignment().name == 'kFadeIn': + transitions.append(trackitem.inTransition()) + + if trackitem.outTransition(): + transitions.append(trackitem.outTransition()) + + for transition in transitions: + alignment = transition.alignment().name + + if alignment == 'kFadeIn': + in_offset_frames = 0 + out_offset_frames = ( + transition.timelineOut() - transition.timelineIn() + ) + 1 + + elif alignment == 'kFadeOut': + in_offset_frames = ( + trackitem.timelineOut() - transition.timelineIn() + ) + 1 + out_offset_frames = 0 + + elif alignment == 'kDissolve': + in_offset_frames = ( + transition.inTrackItem().timelineOut() - + transition.timelineIn() + ) + out_offset_frames = ( + transition.timelineOut() - + transition.outTrackItem().timelineIn() + ) + + else: + # kUnknown transition is ignored + continue + + rate = trackitem.source().framerate().toFloat() + in_time = otio.opentime.RationalTime(in_offset_frames, rate) + out_time = otio.opentime.RationalTime(out_offset_frames, rate) + + otio_transition = otio.schema.Transition( + name=alignment, # Consider placing Hiero name in metadata + transition_type=otio.schema.TransitionTypes.SMPTE_Dissolve, + in_offset=in_time, + out_offset=out_time, + metadata={} + ) + + if alignment == 'kFadeIn': + otio_track.insert(-2, otio_transition) + + else: + otio_track.append(otio_transition) + + def add_tracks(self): + for track in self._sequence.items(): + if isinstance(track, hiero.core.AudioTrack): + kind = otio.schema.TrackKind.Audio + + else: + kind = otio.schema.TrackKind.Video + + otio_track = otio.schema.Track(kind=kind) + otio_track.name = track.name() + + for itemindex, trackitem in enumerate(track): + if isinstance(trackitem.source(), hiero.core.Clip): + self.add_clip(trackitem, otio_track, itemindex) + + self.otio_timeline.tracks.append(otio_track) + + # Add tags as markers + if self._preset.properties()["includeTags"]: + self.add_markers(self._sequence, self.otio_timeline.tracks) + + def create_OTIO(self): + self.otio_timeline = otio.schema.Timeline() + self.otio_timeline.name = self._sequence.name() + + self.add_tracks() + + def startTask(self): + self.create_OTIO() + + def taskStep(self): + return False + + def finishTask(self): + try: + exportPath = self.resolvedExportPath() + + # Check file extension + if not exportPath.lower().endswith(".otio"): + exportPath += ".otio" + + # check export root exists + dirname = os.path.dirname(exportPath) + util.filesystem.makeDirs(dirname) + + # write otio file + otio.adapters.write_to_file(self.otio_timeline, exportPath) + + # Catch all exceptions and log error + except Exception as e: + self.setError("failed to write file {f}\n{e}".format( + f=exportPath, + e=e) + ) + + hiero.core.TaskBase.finishTask(self) + + def forcedAbort(self): + pass + + +class OTIOExportPreset(hiero.core.TaskPresetBase): + def __init__(self, name, properties): + """Initialise presets to default values""" + hiero.core.TaskPresetBase.__init__(self, OTIOExportTask, name) + + self.properties()["includeTags"] = True + self.properties().update(properties) + + def supportedItems(self): + return hiero.core.TaskPresetBase.kSequence + + def addCustomResolveEntries(self, resolver): + resolver.addResolver( + "{ext}", + "Extension of the file to be output", + lambda keyword, task: "otio" + ) + + def supportsAudio(self): + return True + + +hiero.core.taskRegistry.registerTask(OTIOExportPreset, OTIOExportTask) diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py new file mode 100644 index 0000000000..887ff05ec8 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/OTIOExportUI.py @@ -0,0 +1,65 @@ +import hiero.ui +import OTIOExportTask + +try: + # Hiero >= 11.x + from PySide2 import QtCore + from PySide2.QtWidgets import QCheckBox + from hiero.ui.FnTaskUIFormLayout import TaskUIFormLayout as FormLayout + +except ImportError: + # Hiero <= 10.x + from PySide import QtCore # lint:ok + from PySide.QtGui import QCheckBox, QFormLayout # lint:ok + + FormLayout = QFormLayout # lint:ok + + +class OTIOExportUI(hiero.ui.TaskUIBase): + def __init__(self, preset): + """Initialize""" + hiero.ui.TaskUIBase.__init__( + self, + OTIOExportTask.OTIOExportTask, + preset, + "OTIO Exporter" + ) + + def includeMarkersCheckboxChanged(self, state): + # Slot to handle change of checkbox state + self._preset.properties()["includeTags"] = state == QtCore.Qt.Checked + + def populateUI(self, widget, exportTemplate): + layout = widget.layout() + formLayout = FormLayout() + + # Hiero ~= 10.0v4 + if layout is None: + layout = formLayout + widget.setLayout(layout) + + else: + layout.addLayout(formLayout) + + # Checkboxes for whether the OTIO should contain markers or not + self.includeMarkersCheckbox = QCheckBox() + self.includeMarkersCheckbox.setToolTip( + "Enable to include Tags as markers in the exported OTIO file." + ) + self.includeMarkersCheckbox.setCheckState(QtCore.Qt.Unchecked) + + if self._preset.properties()["includeTags"]: + self.includeMarkersCheckbox.setCheckState(QtCore.Qt.Checked) + + self.includeMarkersCheckbox.stateChanged.connect( + self.includeMarkersCheckboxChanged + ) + + # Add Checkbox to layout + formLayout.addRow("Include Tags:", self.includeMarkersCheckbox) + + +hiero.ui.taskUIRegistry.registerTaskUI( + OTIOExportTask.OTIOExportPreset, + OTIOExportUI +) diff --git a/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py new file mode 100644 index 0000000000..67e6e78d35 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/Startup/otioexporter/__init__.py @@ -0,0 +1,29 @@ +# MIT License +# +# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from OTIOExportTask import OTIOExportTask +from OTIOExportUI import OTIOExportUI + +__all__ = [ + 'OTIOExportTask', + 'OTIOExportUI' +] diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py new file mode 100644 index 0000000000..f506333a67 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/OTIOImport.py @@ -0,0 +1,435 @@ +# MIT License +# +# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import sys +import hiero.core +import hiero.ui + +try: + from urllib import unquote + +except ImportError: + from urllib.parse import unquote # lint:ok + +import opentimelineio as otio + + +def get_transition_type(otio_item, otio_track): + _in, _out = otio_track.neighbors_of(otio_item) + + if isinstance(_in, otio.schema.Gap): + _in = None + + if isinstance(_out, otio.schema.Gap): + _out = None + + if _in and _out: + return 'dissolve' + + elif _in and not _out: + return 'fade_out' + + elif not _in and _out: + return 'fade_in' + + else: + return 'unknown' + + +def find_trackitem(name, hiero_track): + for item in hiero_track.items(): + if item.name() == name: + return item + + return None + + +def get_neighboring_trackitems(otio_item, otio_track, hiero_track): + _in, _out = otio_track.neighbors_of(otio_item) + trackitem_in = None + trackitem_out = None + + if _in: + trackitem_in = find_trackitem(_in.name, hiero_track) + + if _out: + trackitem_out = find_trackitem(_out.name, hiero_track) + + return trackitem_in, trackitem_out + + +def apply_transition(otio_track, otio_item, track): + # Figure out type of transition + transition_type = get_transition_type(otio_item, otio_track) + + # Figure out track kind for getattr below + if isinstance(track, hiero.core.VideoTrack): + kind = '' + + else: + kind = 'Audio' + + try: + # Gather TrackItems involved in trasition + item_in, item_out = get_neighboring_trackitems( + otio_item, + otio_track, + track + ) + + # Create transition object + if transition_type == 'dissolve': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}DissolveTransition'.format(kind=kind) + ) + + transition = transition_func( + item_in, + item_out, + otio_item.in_offset.value, + otio_item.out_offset.value + ) + + elif transition_type == 'fade_in': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}FadeInTransition'.format(kind=kind) + ) + transition = transition_func( + item_out, + otio_item.out_offset.value + ) + + elif transition_type == 'fade_out': + transition_func = getattr( + hiero.core.Transition, + 'create{kind}FadeOutTransition'.format(kind=kind) + ) + transition = transition_func( + item_in, + otio_item.in_offset.value + ) + + else: + # Unknown transition + return + + # Apply transition to track + track.addTransition(transition) + + except Exception, e: + sys.stderr.write( + 'Unable to apply transition "{t}": "{e}"\n'.format( + t=otio_item, + e=e + ) + ) + + +def prep_url(url_in): + url = unquote(url_in) + + if url.startswith('file://localhost/'): + return url.replace('file://localhost/', '') + + url = '{url}'.format( + sep=url.startswith(os.sep) and '' or os.sep, + url=url.startswith(os.sep) and url[1:] or url + ) + + return url + + +def create_offline_mediasource(otio_clip, path=None): + hiero_rate = hiero.core.TimeBase( + otio_clip.source_range.start_time.rate + ) + + if isinstance(otio_clip.media_reference, otio.schema.ExternalReference): + source_range = otio_clip.available_range() + + else: + source_range = otio_clip.source_range + + if path is None: + path = otio_clip.name + + media = hiero.core.MediaSource.createOfflineVideoMediaSource( + prep_url(path), + source_range.start_time.value, + source_range.duration.value, + hiero_rate, + source_range.start_time.value + ) + + return media + + +def load_otio(otio_file): + otio_timeline = otio.adapters.read_from_file(otio_file) + build_sequence(otio_timeline) + + +marker_color_map = { + "PINK": "Magenta", + "RED": "Red", + "ORANGE": "Yellow", + "YELLOW": "Yellow", + "GREEN": "Green", + "CYAN": "Cyan", + "BLUE": "Blue", + "PURPLE": "Magenta", + "MAGENTA": "Magenta", + "BLACK": "Blue", + "WHITE": "Green" +} + + +def get_tag(tagname, tagsbin): + for tag in tagsbin.items(): + if tag.name() == tagname: + return tag + + if isinstance(tag, hiero.core.Bin): + tag = get_tag(tagname, tag) + + if tag is not None: + return tag + + return None + + +def add_metadata(metadata, hiero_item): + for key, value in metadata.items(): + if isinstance(value, dict): + add_metadata(value, hiero_item) + continue + + if value is not None: + if not key.startswith('tag.'): + key = 'tag.' + key + + hiero_item.metadata().setValue(key, str(value)) + + +def add_markers(otio_item, hiero_item, tagsbin): + if isinstance(otio_item, (otio.schema.Stack, otio.schema.Clip)): + markers = otio_item.markers + + elif isinstance(otio_item, otio.schema.Timeline): + markers = otio_item.tracks.markers + + else: + markers = [] + + for marker in markers: + marker_color = marker.color + + _tag = get_tag(marker.name, tagsbin) + if _tag is None: + _tag = get_tag(marker_color_map[marker_color], tagsbin) + + if _tag is None: + _tag = hiero.core.Tag(marker_color_map[marker.color]) + + start = marker.marked_range.start_time.value + end = ( + marker.marked_range.start_time.value + + marker.marked_range.duration.value + ) + + tag = hiero_item.addTagToRange(_tag, start, end) + tag.setName(marker.name or marker_color_map[marker_color]) + + # Add metadata + add_metadata(marker.metadata, tag) + + +def create_track(otio_track, tracknum, track_kind): + # Add track kind when dealing with nested stacks + if isinstance(otio_track, otio.schema.Stack): + otio_track.kind = track_kind + + # Create a Track + if otio_track.kind == otio.schema.TrackKind.Video: + track = hiero.core.VideoTrack( + otio_track.name or 'Video{n}'.format(n=tracknum) + ) + + else: + track = hiero.core.AudioTrack( + otio_track.name or 'Audio{n}'.format(n=tracknum) + ) + + return track + + +def create_clip(otio_clip, tagsbin): + # Create MediaSource + otio_media = otio_clip.media_reference + if isinstance(otio_media, otio.schema.ExternalReference): + url = prep_url(otio_media.target_url) + media = hiero.core.MediaSource(url) + if media.isOffline(): + media = create_offline_mediasource(otio_clip, url) + + else: + media = create_offline_mediasource(otio_clip) + + # Create Clip + clip = hiero.core.Clip(media) + + # Add markers + add_markers(otio_clip, clip, tagsbin) + + return clip + + +def create_trackitem(playhead, track, otio_clip, clip): + source_range = otio_clip.source_range + + trackitem = track.createTrackItem(otio_clip.name) + trackitem.setPlaybackSpeed(source_range.start_time.rate) + trackitem.setSource(clip) + + # Check for speed effects and adjust playback speed accordingly + for effect in otio_clip.effects: + if isinstance(effect, otio.schema.LinearTimeWarp): + trackitem.setPlaybackSpeed( + trackitem.playbackSpeed() * + effect.time_scalar + ) + + # If reverse playback speed swap source in and out + if trackitem.playbackSpeed() < 0: + source_out = source_range.start_time.value + source_in = ( + source_range.start_time.value + + source_range.duration.value + ) - 1 + timeline_in = playhead + source_out + timeline_out = ( + timeline_in + + source_range.duration.value + ) - 1 + else: + # Normal playback speed + source_in = source_range.start_time.value + source_out = ( + source_range.start_time.value + + source_range.duration.value + ) - 1 + timeline_in = playhead + timeline_out = ( + timeline_in + + source_range.duration.value + ) - 1 + + # Set source and timeline in/out points + trackitem.setSourceIn(source_in) + trackitem.setSourceOut(source_out) + trackitem.setTimelineIn(timeline_in) + trackitem.setTimelineOut(timeline_out) + + return trackitem + + +def build_sequence(otio_timeline, project=None, track_kind=None): + if project is None: + # TODO: Find a proper way for active project + project = hiero.core.projects(hiero.core.Project.kUserProjects)[-1] + + # Create a Sequence + sequence = hiero.core.Sequence(otio_timeline.name or 'OTIOSequence') + + # Create a Bin to hold clips + projectbin = project.clipsBin() + projectbin.addItem(hiero.core.BinItem(sequence)) + sequencebin = hiero.core.Bin(sequence.name()) + projectbin.addItem(sequencebin) + + # Get tagsBin + tagsbin = hiero.core.project("Tag Presets").tagsBin() + + # Add timeline markers + add_markers(otio_timeline, sequence, tagsbin) + + # TODO: Set sequence settings from otio timeline if available + if isinstance(otio_timeline, otio.schema.Timeline): + tracks = otio_timeline.tracks + + else: + # otio.schema.Stack + tracks = otio_timeline + + for tracknum, otio_track in enumerate(tracks): + playhead = 0 + _transitions = [] + + # Add track to sequence + track = create_track(otio_track, tracknum, track_kind) + sequence.addTrack(track) + + # iterate over items in track + for itemnum, otio_clip in enumerate(otio_track): + if isinstance(otio_clip, otio.schema.Stack): + bar = hiero.ui.mainWindow().statusBar() + bar.showMessage( + 'Nested sequences are created separately.', + timeout=3000 + ) + build_sequence(otio_clip, project, otio_track.kind) + + elif isinstance(otio_clip, otio.schema.Clip): + # Create a Clip + clip = create_clip(otio_clip, tagsbin) + + # Add Clip to a Bin + sequencebin.addItem(hiero.core.BinItem(clip)) + + # Create TrackItem + trackitem = create_trackitem( + playhead, + track, + otio_clip, + clip + ) + + # Add trackitem to track + track.addTrackItem(trackitem) + + # Update playhead + playhead = trackitem.timelineOut() + 1 + + elif isinstance(otio_clip, otio.schema.Transition): + # Store transitions for when all clips in the track are created + _transitions.append((otio_track, otio_clip)) + + elif isinstance(otio_clip, otio.schema.Gap): + # Hiero has no fillers, slugs or blanks at the moment + playhead += otio_clip.source_range.duration.value + + # Apply transitions we stored earlier now that all clips are present + for otio_track, otio_item in _transitions: + apply_transition(otio_track, otio_item, track) diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py new file mode 100644 index 0000000000..1503a9e9ac --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/otioimporter/__init__.py @@ -0,0 +1,57 @@ +# MIT License +# +# Copyright (c) 2018 Daniel Flehner Heen (Storm Studios) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import hiero.ui +import hiero.core + +from otioimporter.OTIOImport import load_otio + + +def OTIO_menu_action(event): + otio_action = hiero.ui.createMenuAction( + 'Import OTIO', + open_otio_file, + icon=None + ) + hiero.ui.registerAction(otio_action) + for action in event.menu.actions(): + if action.text() == 'Import': + action.menu().addAction(otio_action) + break + + +def open_otio_file(): + files = hiero.ui.openFileBrowser( + caption='Please select an OTIO file of choice', + pattern='*.otio', + requiredExtension='.otio' + ) + for otio_file in files: + load_otio(otio_file) + + +# HieroPlayer is quite limited and can't create transitions etc. +if not hiero.core.isHieroPlayer(): + hiero.core.events.registerInterest( + "kShowContextMenu/kBin", + OTIO_menu_action + ) From 866ec2e9d2ddb6e5e430f7d815237eca02f74ac0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 11 May 2019 18:18:22 +0200 Subject: [PATCH 06/41] feat(nukestudio): adding basic menu itegration of avalon --- pype/nukestudio/__init__.py | 14 +- pype/nukestudio/inventory.py | 347 ----------------------------------- pype/nukestudio/lib.py | 14 +- pype/nukestudio/menu.py | 41 +++-- 4 files changed, 40 insertions(+), 376 deletions(-) delete mode 100644 pype/nukestudio/inventory.py diff --git a/pype/nukestudio/__init__.py b/pype/nukestudio/__init__.py index bbae957f61..36f3453cf7 100644 --- a/pype/nukestudio/__init__.py +++ b/pype/nukestudio/__init__.py @@ -13,7 +13,6 @@ from .lib import ( add_to_filemenu ) -import nuke from pypeapp import Logger @@ -103,3 +102,16 @@ def uninstall(): # reset data from templates api.reset_data_from_templates() + + +def ls(): + """List available containers. + + This function is used by the Container Manager in Nuke. You'll + need to implement a for-loop that then *yields* one Container at + a time. + + See the `container.json` schema for details on how it should look, + and the Maya equivalent, which is in `avalon.maya.pipeline` + """ + return diff --git a/pype/nukestudio/inventory.py b/pype/nukestudio/inventory.py deleted file mode 100644 index 0d030c64ad..0000000000 --- a/pype/nukestudio/inventory.py +++ /dev/null @@ -1,347 +0,0 @@ -import os - -from pyblish import api - - -# Collection -collect_json_CollectJSON = api.CollectorOrder + 0.1 -collect_source_CollectScene = api.CollectorOrder + 0.1 -collect_scene_version_CollectSceneVersion = api.CollectorOrder + 0.1 -collect_existing_files_CollectExistingFiles = api.CollectorOrder + 0.25 -collect_reviews_CollectReviews = api.CollectorOrder + 0.3 -collect_sorting_CollectSorting = api.CollectorOrder + 0.49 - -# Validation -persist_publish_state_PersistPublishState = api.ValidatorOrder -validate_executables_ValidateFFmpeg = api.ValidatorOrder -validate_processing_ValidateProcessing = api.ValidatorOrder -validate_scene_version_ValidateSceneVersion = api.ValidatorOrder -validate_review_ValidateReview = api.ValidatorOrder - -# Extraction -extract_scene_save_ExtractSceneSave = api.ExtractorOrder - 0.49 -extract_review_ExtractReview = api.ExtractorOrder -extract_review_ExtractReviewTranscode = api.ExtractorOrder + 0.02 -extract_review_ExtractReviewTranscodeNukeStudio = ( - api.ExtractorOrder + 0.02 -) - -# Integration -extract_json_ExtractJSON = api.IntegratorOrder + 1 -copy_to_clipboard_action_Report = api.IntegratorOrder + 1 - -# AfterEffects -aftereffects_collect_render_items_CollectRenderItems = api.CollectorOrder -aftereffects_collect_scene_CollectScene = api.CollectorOrder - -aftereffects_validate_output_path_ValidateOutputPath = api.ValidatorOrder -aftereffects_validate_scene_path_ValidateScenePath = api.ValidatorOrder -aftereffects_validate_unique_comp_renders_ValidateUniqueCompRenders = ( - api.ValidatorOrder -) - -aftereffects_append_deadline_data_AppendDeadlineData = api.ExtractorOrder -aftereffects_append_ftrack_audio_AppendFtrackAudio = api.ExtractorOrder -aftereffects_extract_local_ExtractLocal = api.ExtractorOrder - -# CelAction -celaction_collect_scene_CollectScene = api.CollectorOrder -celaction_collect_render_CollectRender = api.CollectorOrder + 0.1 -celaction_bait_append_ftrack_data_AppendFtrackData = ( - api.CollectorOrder + 0.1 -) -celaction_bait_append_ftrack_asset_name_AppendFtrackAssetName = ( - api.CollectorOrder + 0.1 -) - -celaction_bait_validate_scene_path_ValidateScenePath = ( - api.ValidatorOrder -) - -celaction_bait_append_ftrack_data_AppendFtrackAudio = ( - api.ExtractorOrder -) -celaction_extract_deadline_ExtractDeadline = api.ExtractorOrder -celaction_extract_render_images_ExtractRenderImages = api.ExtractorOrder -celaction_extract_render_images_ExtractRenderMovie = api.ExtractorOrder + 0.1 -celaction_extract_deadline_movie_ExtractDeadlineMovie = ( - api.ExtractorOrder + 0.4 -) - -celaction_bait_integrate_local_render_IntegrateLocal = ( - api.IntegratorOrder -) - -# Deadline -deadline_OnJobFinished_collect_output_CollectOutput = api.CollectorOrder -deadline_OnJobSubmitted_collect_movie_CollectMovie = api.CollectorOrder -deadline_OnJobSubmitted_collect_render_CollectRender = api.CollectorOrder -deadline_collect_family_CollectFamily = api.CollectorOrder + 0.1 -deadline_collect_houdini_parameters_CollectHoudiniParameters = ( - deadline_collect_family_CollectFamily + 0.01 -) -deadline_collect_maya_parameters_CollectMayaParameters = ( - deadline_collect_family_CollectFamily + 0.01 -) -deadline_collect_nuke_parameters_CollectNukeParameters = ( - deadline_collect_family_CollectFamily + 0.01 -) -deadline_collect_houdini_render_CollectHoudiniRender = api.CollectorOrder + 0.4 - -deadline_validate_houdini_parameters_ValidateHoudiniParameters = ( - api.ValidatorOrder -) -deadline_validate_maya_parameters_ValidateMayaParameters = api.ValidatorOrder -deadline_validate_nuke_parameters_ValidateNukeParameters = api.ValidatorOrder - -deadline_extract_ftrack_path_ExtractFtrackPath = api.ExtractorOrder -deadline_extract_houdini_ExtractHoudini = api.ExtractorOrder -deadline_extract_job_name_ExtractJobName = api.ExtractorOrder -deadline_extract_maya_ExtractMaya = api.ExtractorOrder -deadline_extract_nuke_ExtractNuke = api.ExtractorOrder -deadline_extract_suspended_ExtractSuspended = api.ExtractorOrder - -deadline_integrate_collection_IntegrateCollection = api.IntegratorOrder - 0.1 -deadline_bait_integrate_ftrack_thumbnail_IntegrateFtrackThumbnail = ( - api.IntegratorOrder -) -deadline_bait_update_ftrack_status_UpdateFtrackStatus = ( - api.IntegratorOrder + 0.4 -) - - -# Ftrack -ftrack_collect_nukestudio_CollectNukeStudioEntities = api.CollectorOrder + 0.1 -ftrack_collect_nukestudio_CollectNukeStudioProjectData = ( - api.CollectorOrder + 0.1 -) -ftrack_collect_version_CollectVersion = api.CollectorOrder + 0.2 -ftrack_collect_family_CollectFamily = api.CollectorOrder + 0.4 - -ftrack_validate_assets_ValidateAssets = api.ValidatorOrder -ftrack_validate_nuke_settings_ValidateNukeSettings = api.ValidatorOrder -ftrack_validate_nukestudio_ValidateNukeStudioProjectData = api.ValidatorOrder -ftrack_validate_nukestudio_tasks_ValidateNukeStudioTasks = api.ValidatorOrder - -ftrack_extract_components_ExtractCache = api.ExtractorOrder -ftrack_extract_components_ExtractCamera = api.ExtractorOrder -ftrack_extract_components_ExtractGeometry = api.ExtractorOrder -ftrack_extract_components_ExtractGizmo = api.ExtractorOrder -ftrack_extract_components_ExtractImg = api.ExtractorOrder -ftrack_extract_components_ExtractLUT = api.ExtractorOrder -ftrack_extract_components_ExtractMovie = api.ExtractorOrder -ftrack_extract_components_ExtractAudio = api.ExtractorOrder -ftrack_extract_components_ExtractReview = api.ExtractorOrder -ftrack_extract_components_ExtractScene = api.ExtractorOrder -ftrack_extract_entities_ExtractProject = api.ExtractorOrder -ftrack_extract_entities_ExtractEpisode = ( - ftrack_extract_entities_ExtractProject + 0.01 -) -ftrack_extract_entities_ExtractSequence = ( - ftrack_extract_entities_ExtractEpisode + 0.01 -) -ftrack_extract_entities_ExtractShot = ( - ftrack_extract_entities_ExtractSequence + 0.01 -) -ftrack_extract_entities_ExtractLinkAssetbuilds = ( - ftrack_extract_entities_ExtractShot + 0.01 -) -ftrack_extract_entities_ExtractAssetDataNukeStudio = ( - ftrack_extract_entities_ExtractShot + 0.01 -) -ftrack_extract_entities_ExtractTasks = ( - ftrack_extract_entities_ExtractShot + 0.01 -) -ftrack_extract_entities_ExtractCommit = ( - ftrack_extract_entities_ExtractTasks + 0.01 -) -ftrack_extract_entities_ExtractNukeStudio = ( - ftrack_extract_entities_ExtractTasks + 0.01 -) -ftrack_extract_thumbnail_ExtractThumbnailImg = api.ExtractorOrder + 0.1 -ftrack_extract_review_ExtractReview = api.ExtractorOrder + 0.2 -ftrack_extract_components_ExtractComponents = api.ExtractorOrder + 0.4 - -ftrack_integrate_status_IntegrateStatus = api.IntegratorOrder - -ftrack_other_link_source_OtherLinkSource = api.IntegratorOrder + 1 - -# Hiero -hiero_collect_items_CollectItems = api.CollectorOrder - -hiero_validate_names_ValidateNames = api.ValidatorOrder - -hiero_extract_transcode_BumpyboxExtractTranscodeH264 = api.ExtractorOrder - 0.1 -hiero_extract_transcode_BumpyboxExtractTranscodeJPEG = api.ExtractorOrder - 0.1 -hiero_extract_audio_ExtractAudio = api.ExtractorOrder -hiero_extract_ftrack_shot_ExtractFtrackShot = api.ExtractorOrder -hiero_extract_nuke_script_ExtractNukeScript = api.ExtractorOrder -hiero_extract_transcode_ExtractTranscode = api.ExtractorOrder -hiero_extract_ftrack_components_ExtractFtrackComponents = ( - api.ExtractorOrder + 0.1 -) -hiero_extract_ftrack_tasks_ExtractFtrackTasks = api.ExtractorOrder + 0.1 -hiero_extract_ftrack_thumbnail_ExtractFtrackThumbnail = ( - api.ExtractorOrder + 0.1 -) - -# Houdini -houdini_collect_Collect = api.CollectorOrder - -houdini_validate_alembic_ValidateAlembic = api.ValidatorOrder -houdini_validate_dynamics_ValidateDynamics = api.ValidatorOrder -houdini_validate_geometry_ValidateGeometry = api.ValidatorOrder -houdini_validate_mantra_camera_ValidateMantraCamera = api.ValidatorOrder -houdini_validate_mantra_settings_ValidateMantraSettings = api.ValidatorOrder -houdini_validate_output_path_ValidateOutputPath = api.ValidatorOrder - -houdini_extract_scene_save_ExtractSceneSave = api.ExtractorOrder - 0.1 -houdini_extract_local_ExtractLocal = api.ExtractorOrder - -# Maya -maya_collect_framerate_CollectFramerate = api.CollectorOrder - 0.5 -maya_collect_files_CollectFiles = api.CollectorOrder -maya_collect_render_setups_CollectRenderSetups = api.CollectorOrder -maya_collect_sets_CollectSets = api.CollectorOrder -maya_collect_sets_CollectSetsProcess = maya_collect_sets_CollectSets + 0.01 -maya_collect_sets_CollectSetsPublish = maya_collect_sets_CollectSets + 0.01 -maya_collect_playblasts_CollectPlayblasts = api.CollectorOrder -maya_collect_playblasts_CollectPlayblastsProcess = ( - maya_collect_playblasts_CollectPlayblasts + 0.01 -) -maya_collect_playblasts_CollectPlayblastsPublish = ( - maya_collect_playblasts_CollectPlayblasts + 0.01 -) - -maya_modeling_validate_intermediate_shapes_ValidateIntermediateShapes = ( - api.ValidatorOrder -) -maya_modeling_validate_points_ValidatePoints = ( - api.ValidatorOrder -) -maya_modeling_validate_hierarchy_ValidateHierarchy = ( - api.ValidatorOrder -) -maya_modeling_validate_shape_name_ValidateShapeName = ( - api.ValidatorOrder -) -maya_modeling_validate_transforms_ValidateTransforms = ( - api.ValidatorOrder -) -maya_modeling_validate_display_layer_ValidateDisplayLayer = ( - api.ValidatorOrder -) -maya_modeling_validate_smooth_display_ValidateSmoothDisplay = ( - api.ValidatorOrder -) -maya_validate_arnold_setings_ValidateArnoldSettings = api.ValidatorOrder -maya_validate_name_ValidateName = api.ValidatorOrder -maya_validate_render_camera_ValidateRenderCamera = api.ValidatorOrder -maya_validate_render_layer_settings_ValidateRenderLayerSettings = ( - api.ValidatorOrder -) -maya_validate_vray_settings_ValidateVraySettings = api.ValidatorOrder - -maya_validate_scene_modified_ValidateSceneModified = api.ExtractorOrder - 0.49 -maya_extract_alembic_ExtractAlembic = api.ExtractorOrder -maya_extract_formats_ExtractFormats = api.ExtractorOrder -maya_lookdev_extract_construction_history_ExtractConstructionHistory = ( - maya_extract_formats_ExtractFormats - 0.01 -) -maya_modeling_extract_construction_history_ExtractConstructionHistory = ( - maya_extract_formats_ExtractFormats - 0.01 -) -maya_rigging_extract_disconnect_animation_ExtractDisconnectAnimation = ( - maya_extract_formats_ExtractFormats - 0.01 -) -maya_extract_playblast_ExtractPlayblast = api.ExtractorOrder -maya_extract_render_layer_ExtractRenderLayer = api.ExtractorOrder - -# Nuke -nuke_collect_selection_CollectSelection = api.CollectorOrder - 0.1 -nuke_collect_backdrops_CollectBackdrops = api.CollectorOrder + 0.1 -nuke_collect_framerate_CollectFramerate = api.CollectorOrder -nuke_collect_reads_CollectReads = api.CollectorOrder -nuke_collect_write_geo_CollectWriteGeo = api.CollectorOrder -nuke_collect_writes_CollectWrites = api.CollectorOrder -nuke_collect_write_geo_CollectCacheProcess = api.CollectorOrder + 0.01 -nuke_collect_write_geo_CollectCachePublish = api.CollectorOrder + 0.01 -nuke_collect_writes_CollectWritesProcess = api.CollectorOrder + 0.01 -nuke_collect_writes_CollectWritesPublish = api.CollectorOrder + 0.01 -nuke_collect_groups_CollectGroups = api.CollectorOrder + 0.1 - -nuke_validate_datatype_ValidateDatatype = api.ValidatorOrder -nuke_validate_frame_rate_ValidateFrameRate = api.ValidatorOrder -nuke_validate_group_node_ValidateGroupNode = api.ValidatorOrder -nuke_validate_proxy_mode_ValidateProxyMode = api.ValidatorOrder -nuke_validate_read_node_ValidateReadNode = api.ValidatorOrder -nuke_validate_write_node_ValidateWriteNode = api.ValidatorOrder -nuke_validate_write_node_ValidateReviewNodeDuplicate = api.ValidatorOrder -nuke_validate_writegeo_node_ValidateWriteGeoNode = api.ValidatorOrder - -nuke_extract_output_directory_ExtractOutputDirectory = api.ExtractorOrder - 0.1 -nuke_extract_backdrop_ExtractBackdrop = api.ExtractorOrder -nuke_extract_group_ExtractGroup = api.ExtractorOrder -nuke_extract_write_Extract = api.ExtractorOrder -nuke_extract_write_ExtractCache = api.ExtractorOrder -nuke_extract_write_ExtractCamera = api.ExtractorOrder -nuke_extract_write_ExtractGeometry = api.ExtractorOrder -nuke_extract_write_ExtractWrite = api.ExtractorOrder -nuke_extract_review_ExtractReview = api.ExtractorOrder + 0.01 - -# NukeStudio -nukestudio_collect_CollectFramerate = api.CollectorOrder -nukestudio_collect_CollectTrackItems = api.CollectorOrder -nukestudio_collect_CollectTasks = api.CollectorOrder + 0.01 - -nukestudio_validate_names_ValidateNames = api.ValidatorOrder -nukestudio_validate_names_ValidateNamesFtrack = api.ValidatorOrder -nukestudio_validate_projectroot_ValidateProjectRoot = api.ValidatorOrder -nukestudio_validate_resolved_paths_ValidateResolvedPaths = api.ValidatorOrder -nukestudio_validate_task_ValidateImageSequence = api.ValidatorOrder -nukestudio_validate_task_ValidateOutputRange = api.ValidatorOrder -nukestudio_validate_track_item_ValidateTrackItem = api.ValidatorOrder -nukestudio_validate_track_item_ValidateTrackItemFtrack = api.ValidatorOrder -nukestudio_validate_viewer_lut_ValidateViewerLut = api.ValidatorOrder - -nukestudio_extract_review_ExtractReview = api.ExtractorOrder -nukestudio_extract_tasks_ExtractTasks = api.ExtractorOrder - -# RoyalRender -royalrender_collect_CollectMayaSets = api.CollectorOrder + 0.1 -royalrender_collect_CollectNukeWrites = api.CollectorOrder + 0.1 - -royalrender_extract_maya_ExtractMaya = api.ExtractorOrder -royalrender_extract_maya_alembic_ExtractMovie = api.ExtractorOrder -royalrender_extract_nuke_ExtractNuke = api.ExtractorOrder - -# TVPaint -tvpaint_extract_deadline_ExtractDeadline = api.ExtractorOrder - 0.1 -tvpaint_collect_scene_arg_CollectSceneArg = api.CollectorOrder - 0.05 -tvpaint_collect_render_CollectRender = api.CollectorOrder + 0.1 - -tvpaint_validate_scene_path_ValidateScenePath = api.ValidatorOrder - -tvpaint_extract_hobsoft_scene_ExtractHobsoftScene = api.ExtractorOrder - - -def get_order(module, name): - path = get_variable_name(module, name) - - if path not in globals().keys(): - raise KeyError("\"{0}\" could not be found in inventory.".format(path)) - - return globals()[path] - - -def get_variable_name(module, name): - plugins_directory = os.path.abspath( - os.path.join(__file__, "..", "plugins") - ) - - module = os.path.relpath(module, plugins_directory) - path = "{0}{1}".format(module, name) - path = path.replace(".py", "_") - path = path.replace(os.sep, "_") - - return path diff --git a/pype/nukestudio/lib.py b/pype/nukestudio/lib.py index ca1dfa3544..fba8572235 100644 --- a/pype/nukestudio/lib.py +++ b/pype/nukestudio/lib.py @@ -36,9 +36,6 @@ def setup(console=False, port=None, menu=True): if self._has_been_setup: teardown() - # register bumpybox plugins - pyblish.api.register_plugin_path(r"C:\Users\hubert\CODE\github\pyblish-bumpybox\pyblish_bumpybox\plugins\nukestudio") - add_submission() if menu: @@ -79,9 +76,6 @@ def teardown(): if not self._has_been_setup: return - deregister_plugins() - deregister_host() - if self._has_menu: remove_from_filemenu() self._has_menu = False @@ -94,8 +88,6 @@ def remove_from_filemenu(): raise NotImplementedError("Implement me please.") - - def add_to_filemenu(): PublishAction() @@ -129,17 +121,13 @@ class PublishAction(QtWidgets.QAction): self.setShortcut("Ctrl+Alt+P") def publish(self): - import pyblish_nukestudio - # Removing "submission" attribute from hiero module, to prevent tasks # from getting picked up when not using the "Export" dialog. if hasattr(hiero, "submission"): del hiero.submission - - pyblish_nukestudio.show() + show() def eventHandler(self, event): - # Add the Menu to the right-click menu event.menu.addAction(self) diff --git a/pype/nukestudio/menu.py b/pype/nukestudio/menu.py index 5fae4cf2dd..b6e17aeab2 100644 --- a/pype/nukestudio/menu.py +++ b/pype/nukestudio/menu.py @@ -1,5 +1,6 @@ import os from avalon.api import Session +from pprint import pprint import hiero.core @@ -31,9 +32,10 @@ def install(): # Add a Menu to the MenuBar file_action = None + try: check_made_menu = findMenuAction(menu_name) - except: + except Exception: pass if not check_made_menu: @@ -42,40 +44,49 @@ def install(): menu = check_made_menu.menu() actions = [{ - 'action': QAction(QIcon('icons:Position.png'), 'Set Context', None), - 'function': contextmanager.show + 'action': QAction('Set Context', None), + 'function': contextmanager.show, + 'icon': QIcon('icons:Position.png') }, { - 'action': QAction(QIcon('icons:ColorAdd.png'), 'Create...', None), - 'function': creator.show + 'action': QAction('Create...', None), + 'function': creator.show, + 'icon': QIcon('icons:ColorAdd.png') }, { - 'action': QAction(QIcon('icons:CopyRectangle.png'), 'Load...', None), - 'function': cbloader.show + 'action': QAction('Load...', None), + 'function': cbloader.show, + 'icon': QIcon('icons:CopyRectangle.png') }, { - 'action': QAction(QIcon('icons:Output.png'), 'Publish...', None), - 'function': publish.show + 'action': QAction('Publish...', None), + 'function': publish.show, + 'icon': QIcon('icons:Output.png') }, { - 'action': QAction(QIcon('icons:ModifyMetaData.png'), 'Manage...', None), - 'function': cbsceneinventory.show + 'action': QAction('Manage...', None), + 'function': cbsceneinventory.show, + 'icon': QIcon('icons:ModifyMetaData.png') }, { - 'action': QAction(QIcon('icons:ColorAdd.png'), 'Library...', None), - 'function': libraryloader.show + 'action': QAction('Library...', None), + 'function': libraryloader.show, + 'icon': QIcon('icons:ColorAdd.png') }] # Create menu items for a in actions: + pprint(a) # create action for k in a.keys(): if 'action' in k: action = a[k] elif 'function' in k: action.triggered.connect(a[k]) - else: - pass + elif 'icon' in k: + action.setIcon(a[k]) + # add action to menu menu.addAction(action) + hiero.ui.registerAction(action) From ec491113026402da351faaf6775d2813eace819d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 11 May 2019 18:19:15 +0200 Subject: [PATCH 07/41] feat(nukestudio): adding sharedTags.hrox for unified tags to use with pype --- .../Templates/SharedTags.hrox | 468 ++++++++++++++++++ .../hiero_plugin_path/Templates/fusion.png | Bin 0 -> 194142 bytes .../hiero_plugin_path/Templates/houdini.png | Bin 0 -> 11586 bytes .../hiero_plugin_path/Templates/maya.png | Bin 0 -> 13120 bytes .../hiero_plugin_path/Templates/nuke.png | Bin 0 -> 65305 bytes .../hiero_plugin_path/Templates/vfx_aces.hrox | 38 -- .../Templates/vfx_linear.hrox | 38 -- .../Templates/vfx_rec709.hrox | 38 -- .../hiero_plugin_path/Templates/volume.png | Bin 0 -> 19442 bytes 9 files changed, 468 insertions(+), 114 deletions(-) create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/fusion.png create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/houdini.png create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/maya.png create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/nuke.png delete mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox delete mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox delete mode 100644 setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox create mode 100644 setup/nukestudio/hiero_plugin_path/Templates/volume.png diff --git a/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox b/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox new file mode 100644 index 0000000000..4045ea3335 --- /dev/null +++ b/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox @@ -0,0 +1,468 @@ + + + + + + + 2 + 70 + 0 + 0 + 13 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 70 + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 70 + 0 + 0 + + + 2 + 70 + 0 + 0 + 17 + + + 2 + 70 + 0 + 0 + 2 + + + + + + + + + diff --git a/setup/nukestudio/hiero_plugin_path/Templates/fusion.png b/setup/nukestudio/hiero_plugin_path/Templates/fusion.png new file mode 100644 index 0000000000000000000000000000000000000000..208c1279cf461400cf4a03262a5bca30ea4b90c8 GIT binary patch literal 194142 zcmaI7WmuG5)HY0mv;xwtbV&+`gp|^tfOLa&r!cgDA|fEtUBZC0Ce{V_AZ!R)=(zE+&;Tx-W_YpReC(h*`{V3534Rn*16z}~%o5#RxT(I}YO z1U@jmbX63vC3-dVFfdp!UMtGK_0K$9woiWYA%OE1ect{su|asyXa2Zy-_gx(AtTL_ z*cXq6oP`w;{^Xld1)u(-^6BtZ)t%jtX82*^@$vN9v02@&KsnX#&_AI;o4T_s=*qy6%?99==a`mo=UBeorI^nwhyjFz`i!O<05tbt5H5z#2)eaR26`a%qNn z{|lv@A`V+9fg(;Q1|dpCjf@9jv%SjMVS#rC5fbS*gr)?2x&H=T6plCW@A-dY>hu>p zl1ZbIeHkV+Rk?dlVO|Wn4d7&3UYB_k#3duSD3E@B+%4a2S zba*lpF{p6&Hl!pGZ8zwC)fkv~GygcTLGH~>3KRzq+eW{Voqsk_5UPU9(@h%^)dFfZ zN3~$p;@VjwLXUA-We(^@t}&MfZ!wo`gq?M1H*9}kH@_~L*C<*(Tx!k8%J34Y6>+t5 z<;Y0O@t8apaSe1F(9qIq+3V+)5RtZS^-iBZpYs=}J>XB>X?}Eeub1tkG2o+)vFH`+ zV62}5XZG(yLlbF6M*UKD*Bi^0K~ng|LSnY7^u+SU^Q`4HZNJ8?C(`wdTHhL69E^Ew zopc+z3n{t5@&w@ah(_$EEf1Bi>sXS^0Y)-4K9+gHe54M<~R~ksc}@8wzGRj?A4`Y-Z9f+ ztgoRaUtSh)1hJ%pdojkEDqr&IELr^G>V8_%lJXQhzzL1*{IV5F?@a-h6XMkb%Mbgl zm?jf7Rgm+Mpk%(7To22*)pRn;oW!X+e#?^nF# zf)~C+j$vo0EME=L*o5V736gu>ea3thS)nV0Bh-@JKQh`fpC*}g# zAqHM{0+P9RLC%+Z+43jhF8QrNUTEVck|vI;FD7ziJ!RHd6u2aNn6}>JV-cPacDLoc z`)L>nG|^{`NvS!>yzw&DUotLN?dFMYdxPe`WTa=RkD9im1DZ<8_+7qnJRT^z_#1za zHnpiJE+a`hHKi3ee{-`ajeQ?#;?=O40eg;C7^q41Re1A>p(yLy){{r$UE7K7y*i$p{`NivkLj?oiR@`;`J)I#kZ_#KbgqX+lAXw#u((_8AN;G zS#-F&NgLl{rWoLSzxTw?FPboy|^{IV8D@!ZIjH5GN8<$ zhsi(&px9h3t@=%&OwOjLS7|@ssv)m#Qp)KLOR$EoQgf=;*MxXLWjIz?`26Iai>m?i z&KxpgDgEl2c6vz^^A}S#cBtFSi^DCovb6IZ-CAmaLc7r%KM@fTl$_t*ms8;nWdYQH zVXOXU*rn`jgL}K~`E9|Pg^jbZ7gtg15wFHa_^lI1635*=ZTKjU4<@DPSm(YnFZ}ze z|JADaUHvq{-vZ{o;K6!`S{Nh41!iOFZKz<23>UrU+Wv)$w1|A_=V{P7?MbuK9N8ZXYlcDW-gZ+oHMwy9k zrx5b~#mJN+OwUo#vBO(1g+B7=3TcVy{=1dg^9~DxAKL3ICxxv$ze#@hc&Ey&pKqx(QDd1-OK_=m&CbrgXa zt`oj>_-qnp5g)l|(0tU}K#z}08d_iMAG?`p4V$+~Nd#=)+?3S{4r$4Nq-x#In?0zh z<1;jOlnVr{Qh`86e!zC0sT7qSesBT&J<|ay_gq|vCXS4xx49Aa#oKk6>1d_>-`A@v z9xBqs%}72*QhDz9qOlJhn-az&N=jC^-A#4}7&?_}0hN2Vp5P{C5iha1fX>v^f|>+N z0=3}W)B?woJKfKYS5EWs1IEX?uMSc!D{yF1qVl#7%4%DcuA5AXIwR&K(`zpzbTgsq zpnz|GR_CAMUM#`}-M(g{pY|SMnnqai2X|KQA4F17>W#Tr+{`x>ZKt$u>-tYqbEBFw zidK+@gu8>m=tSl*gF1`8%S*DrJH6w<$I)!K8{AyBxFE64A0ikVv`Me&lQdh7t$6B% z#M*@dt}fO$(?p1pN;HeM1Im001n$|K~G?)Bu(MyDPq5`%SU8 zOn$J`EVEMU=T_mh_*J#HSVObUjfL`n`FEzx_n03O{CBU*x+une$qy2uT7K@VXyk2; z|H|L|xxMEu0MW zG=#mMB$Sh^txs3l;9V_#$6vjjnZ|uZiz`$W_uOMt!N*Jdii4KoJ^p)j9(te6d_zJk zua<6ZV)Zz8II@xR>laP6W%Glnk9R6f`hhI+EW-+){GYRkQhlHIt%2>~;GQb5 zi!nu85ZuojwnTrxKu^>i{V8*I^_#6553P0DhyxzM_Kd_ZS~Ignhp$f zYYi%gw6bKRWdqLA(oRdVGjpWw!hy7OwbZQyq+wx}EFdm_^rU(kNzS$_u`)^V+IfZH z{smjEttzmGwT&N}-eqdrH(IOY|DYnJcrOLI@HTtKMbCn1S|^Cd4dRitCa}N_C&48V z2T&TduB)#rYex3ZpE)0|_V!M5WW*#+hbB=MZq$M{)SRxbLB4<&{I{J;bh7OSUb<;b zQ6>#n+Jm{@Xt^5nSf0C;Hy;DERg!_3;R!FLo!zEgn!fx~=8Ppd+hlrsMgS3EAVx!} zzX+Aek6}0o@q%zZVl*{fz1Zl~6ZrDFNceoVvbaRh)G+g+WA{r^Z5@Qa#mY za_2TtS@E~p*fd|3+N{zmrbUc*LQ-9K^4oM=3vY_%an6+MMwx?|h+hrn2%X{ZNBQ9EoLIzY=h>7yOReZuhO9*9k(Or;yoeh7Q+$j$QXnfW{bxp-e~8B|V!p{q z7I`{!nS*e!T)*&gwX;)poQopQzw9eY29PF(?gF9G-R-}u0%PlCX}_m<3Csy@b0Drq z(C5yJ)jwzQ2*P516iZBoR##Jw^<(#iu3~LssaY%#c{9!3QHmV%#|+^2y%V#{}_iMG+AHE%G9#pj3Egg>9}&6lUzyCF7v^$Z|zWK@~n zD8D-7L?LkP*5|>&|Dr?1Nmd|rRDccnCaOL3JXbpsNAh05m6}!jqA9Gc*RDJ8!e^W@ z!ttuHs?Cix4<$_D)Ja6dhL@8S&!uTFICiBo%m;UN9^ktFHYsJXQB|cra7^tDJPFVs?(5rCOrw`LC^pZQ2gkFEfZlMsrMmz6f?a+lQll zwgHn$PH*!b9~2l0a^-ni&_%?m==I-|)KJ*de*T?-Hs042`eV+0Q+1~!mg4kSCJ3y} zm**l~Ec&4vhlC^{Ubcgt+dmsKne@kZ`mSLdv8~Qkx2E}tAIkP6MJ%HhzlG&TjVPAF zi*Uot0o26sac`@jXp{oi{CgQo)Q)S@HlO@Icx&--6KKbA`hgwyZ)@&ccCJzEf7tZ1 zUdtD)8QD2$Ek65cO$S;Z-Od&gLh7DhBF>3Kc1CFhpCV_0hyCC_Gsi@4&VC0VG+R0i z?Gar&tWy!aR&aW3+QJ@Nt>eNe%|&AsXBqxRn1WbM$-+)s`=Dn??ik8@+p|5JTjgAt z4PuT+E!G<(+ZiM>p^}PpFy??k8~rf*VUKHGDCH%%(T7&Yynl37ENO!$N63(z_nJK; zJIgdv66ET-zS+NbAbY#EnF0$5^4La5LePOmj`xybzP^_XFTZXhaw9{$x5e*B^MYAw zNenUeue#DjnxTZftinA~wO@NrA?CXfz4g*menwh$cFqg0+nFsG;{4*0Gy7-G3m;*z zpuO-@;k3Z20w9>?+ym}X_9ACv=~MqF?H+nH=-t@kHGJkc_~AUo(NeR9bmDW0Bq-9@ zvxz)km}Y42aA*m!c|JO-l^x)_eFpjHw7*Z*o|@j~KR!4zOq(=36T}EeYxFl0t zZEe++N77Pvq>RT*At!m8(d?i2OUjKU$DecSR=mK-mTeAPVR7^O=Tv9-^cQTceJqg5 zsKj#?1l~!l9Wwg0bzpGl#1=POngf~siJ|q(7Q>beN_5wt$w*Dfi68X8@?IIP<#e*E+|u&>Ym>`Rb1vvz&b;}5S~Hhl$$6gpjZ zekuUM#=iCo%%e!IpZRH!1yb}M3GM6f*_p^r%Sy}eU%S|hVh-`zKO=*P$a?JCI3Axp zwHqym`vO3U*Ret*np{CtjLk(Q-}g_s^gQTV6@H>-J~Qu!aH#O=CFM*$WV3@PXPOAn z{u#JFr>`aKu_jXv9N~}mDv+q6YkI%LLH&h}pWwlGDFHDdht?2sL}GOu7Ig71b`W}f3s_l5vWAAH|9m%Y*?YiX2fp#rvPN=IB+lf0&0BMT zjRc$0!$5`h8?O=+DWn^unoC}>TTxI@(hv&7JeE(6))5dk>Wsk3jj*UZ-+n%#(Vv{r ztX>%mQw03In_Y-9obZH7@9D2rc8!zwRwQ;rEZ7{>EYwVoYg~N;eM7bni6~Or?}p1h zqtAw$PIt03N6p&!#DIXQ!_@Tsp+bz$$z8Ia@6+33nW_4{PJ<_(5|*gg56LMCwAOw4Q9lutzF5sYt~CKuS7`3q_+|p1>{7A85wQBox+%`<4eb z_zJCDXhDpvt<3^i{F*lUCv85hDZ^e<)AV+yYPlH!b{p1u0av#>{qY4)Fp^y9`~B1U zgRT3+cK-CM>TCn!at3pN61@16869k)Ug8n$u7P@CE^<&0n;bKHAiO^cQ!XS+5(6s< zq|-75O?nXba4PdoOqbZKGEX7I!d}u4+U+Tazd6OxmG$hVZ4mRie$9gA`q4N2`We=S z4(Euomj*a^Kl4(so`sQgSoquIy2Lw+ld!qlt=(SU#3qd{HD6@8ElHu{tN7KkB#-QN z?B@Tt9yRC_u%>@D=?1JE^UWuP@y)r0kxq8bKozsAUz;7VcNOk_2@QWAVm;i#b=fp+ z_S81creAQ8G};MoX68UPbJd$zmB!D@*+@Whj$ItDZ;td)nF?^7{t>Y)b5gY$lUL617E^Un@%@Qk@ofwT4ew~|8e6+ zFsi{1l){b0l_6;J-cis|x!!EZNn6xj?I^uqmVgsG0OWD&hq?6#SdYJ`?rHbMUbyZu zJoo+h?}veG_8}DXWTq*gWrFq7ype~ziRK?8Ew95F=fBGc{QK zo=UlkW6BT)A(dW+acsjN@^@g5`-kX~0f7qg=9ZSnK z#npXmV&`w8&f|kzxEKaf)t{4k5S=W1y2@s|%Ivh}{QS`jAVBed0$_&r#a>-?2f`V4 zm(PCn-bOQnY`LVQtRxdlYjSDuSjaFUJjG@@Ex&g@8spW(3Y4UKcJW5e?Ksp7bztBu zXYU}co&N0^o-!sACR1%#Ki`m+*>hQ+(+srtL3h>109QnRj@@I|m2bJgAyOZC_E0L+ z@v=?m^F7x1;<`7Fh*Jq_Xhl@3!bXsq-mj{JpLLKYpXDZF))L`FRNv?kzh}`$As{Ca zEv3o2;lyL|wE8bi)D zucHKm0}u!aNX_%$HKoz49JjuQkAQ@#a9{KoQ9P7f%U-boptwwi`RQDAVlUHut&>a@ zAD%2*J*KPYOQzW4HZg*c8{Ra)4_fg&!}#OS_IWSR2}b8V9#=>f_&W7R7lw&YfSjxD z8-11r`5vt5=#rXDu}Px6rVN9g(O~4@1~qDLP~<+OtMAYJ!SMl72@qW(9at3?zAFAZ zjn4>dd~v&Q9G~!FLDK!&ooiybJG!sxVI#(jg|LtmE*o{1-G^gyE38-8W2>v%=Fi{A zRlRtyn?J28C+K=d^%K0x-Gly4iASj}|GFX~S5pYbKMu*w)0z2NOH&i-zaDG6(|(n5 zdTx%g3Oqhj5^VX+qOlr94psm%K>pLYypc+OST?e)pz33*{a`uM=cm2O{Jf8Gc|m6$ zV-sBv98kL_$EcfQlef+;T+;rUI*J%e&$azrzx8LO#Q%sPp36=>hGcg+aiidS3YQL} zk$(5;osf6}0h6@MsZr z?smKj&?Pn253rIw9>j?zV-W{g(2(DVZDZU~?OBArBOenBCvNwa8=9`XqmJYtI0sy% z>ROMSigt|m;f6ugJAMcqSDngcd90o8fbF}}D!!D9-9R4kOc6Pzn0dFNE}m=8DX8YF z!CpsOO1=UNE1@P-XjT`CE(R~xC>AYI^n9U*mz~}I=3Yh#j2SAib?;Wk|D12}SxcUQWgk&x29~~A zW+CFQ*S@)A5-FoQjnqbnY8}j@rYCp60-t9MkB#)qQ@pJBuhT1a^qYQN8ohfZHc-{W zS(FY#Bj6Y!638i^11P#YC(Yc+4FRGXHT~J{@47xA3qsz`qi=5Z4zv&*8l&nWK`YIl zsT5-9J{%Xckmqkb0lhskz;ATJETr0Pu%K#hw+;TSA^&vR@)yHpyPTksm#7=qOzziq z|F56$xBS?F)my1*SbpZuStmz#0I@!$feq>`Stpa zo>!`1;PJZEgK~gj5F1}o@ncmS)RAKN>Ia*jmAG1%y>tma8U^achA?m`SV66~e5pGf z=b~rw5R;KhJHmAH+;bFs(X4+^pBicw@M!F_!C>eydK|;zYAGrzs*E}Z5`Y3B|6NkI zUnkTyW(P0)DSP{%97uQXrvKYZjYW5W@ToLK+uaq}QQ(2}_XxP`qa%jpbvgrXHTh>Z z5ly(C`0VgRUX`8Ux$?rX+E&C$yUprmbf>X&6E$XMYmrJ~p9O2A<(sO|=?A(|v+w># z^XkPM>6IPcDJon;-dw%kXisMLXiMtcy4S;|A*b6nu#ofpy-{Rp6Tt3xPfqGhAWR8# z+=hd_sTD+JIF}|DCEYK&R<9&li-INb^{_XeXLOOsaGon54 zN~PlZg$OLSWxg`JT@BzvpF(B)7fK*rk%X zKeiObFD{)wUT?ix1fjtFsDHg#s`d%8fyDvfh^Cr|y%8Su{|>->BjmwuqpUtw z;P{rBoS04*5w0F7G1@F`SQxyPF>i6#;biZoQsaR6sUaFOat1qvxrJl*xqPMD8v9(%+ zy4=^oV&YTNv(j5`YI972j$toFJWdy(>ceKuW9iWE0B@+n%XA(^PNMvw_d&OW>po*J z!UtP=ZLi`%qO(DC-X zAle!zoRl_fh2I(autpkA#Pxn;Rg7TKm70)j06T4NRqaD>uKc=Nd|@>{e6Au7AFe)( zi0u>#k@aG^E<uU>8Hwep%2gqRpXM6aHx1{X-Q~zNh3q5~ zZTCQQD&^3be_8xD`Bi-NSvbfEh6q_mMrmm5xLlrR(B`NOw_Y!8*`pWjT}=_U2Gk|y z{@L^XR8Sn&NI9qb1M(>b;=IO}uYkuQ;)54K$JF`DEmz+OCDSQaANFCqc}PqvMAK;g zYs*nGd^<+y9qp4!O5D584XN0GCc zmovcuspJZM?pf%Saf3EHhaKY;Du6mLH6C|pn^jA$TB(oEmlO?>qt2(vZu$NDdGOWH zR#FafX!zV5Iq`Suyd+JgOov+GZDb~M6uGTl&`{je|IPwPQN}${?!9ejKix%39@lx6El@3pVC7wOW{p_2V@o1C=8-N=^vdF(_yd^L1 z8aH+zw?*A$)FVO}1EVB7kq$(jA(RTq>fDQ@e$GsL%t0F&cz&&0{Nu-2cYRi$l6S}^ ztT)Fd9D(Jmfc$>+mj-*V#v+`-b*@93N5t!3b8vRR5K-iPDIm&;??)(MWv-W>KJ9=cRrA2kcOazjsoiO?4*Q$yY7!U=$H8{RLzL^gm!dRvt-Eh>@;gm!WTth6U|y2g3s0YS1S-gU9NR zVhB3D#WOR4#CH=)p8u2rUjjKx%L!I}^#s#&>J>YVH&*AFXbh%Sz@WsROdtmfE+OX0 z1ks0RR7SrV6s2tEfv{WFe@ifavWeh|{KNQg9>9Q`9jEx8;O!f2G8tdnyL_#(P)nQQ z9pW)A*N`1jc07!T6m;#f=wDnbF@TBcb|?uQ!8PJ*JluhdECx27(XU;rVI*6E1boP8 z2%{;9am|gAC+r6|-%=@z?J_ zHzyGsZwwY3@a+2tL+mjNW%4oF3KJR|4LeK3_Vo=2HITCN@;t}BO7;|c)YPQ(AdZNY z?u}FS>qzpallo8Sj#irN{b7P_jeZyPRS#6eU2AAq1c|^dHkLQkG&UIi7Q!qvC%STQ zbk!fz=$LasJTK~K@XC@YRx?*L&V?QH*Wdj%@bIHCB%UqtdaiO7Qkq1h1BgD~!dc0b z`Pth1FV}~u+fhZlqlhEukXB~kj2HEjQUIl)fMdUBaK<-(fPt8NF%!lJIcZR}axT|p z&5*+oiJ@f)fA2WJgmcF7v#Q;hjYlvyb#bA}h}vQLj~p1@FjIrDf}3+6yp5l2{;}0K zc>s;sqOO^d61ximFAr8t2k{#aZlO03kR*^GtxyfBXQr!KuO5vOaWZXmxW)d7*J2AlrcFplVYK1fzWJ>Ayq-Obkbv&9mG z?}9!zM1M=4W8~dgRvXO-cgYK>Utl zNO?mN8q{+^S36Hn#NXNWk6WAe=*g*iW>k5qsUe0<7tmn{o602`$BU`^3O`(EIbDGx zX8IDLNWj6XlUgP8C-jd}Ul z#ZkskCR4?BwfG(%FI@MjfwleoIDF0n0ocO-U{9g0y+?YTh?OT=^+5ZhMXB3j#>$J| z2S>12AGc)}ZWnIs4)WZ$^dp8qFGpcY&(&yx<)@cW)Q1&lETXp7vcr1T^k>NNW=c}d z4aD^YT2?kSyTw=OwuETBxe(7Z=CQ3LW9;!bJ~N}_X(xKD@thwP)72|(%%Jl_=l7Q| z4l-g~*N${r)^v>m_46l@4tTsy!?3re#T{dDDHIx4oS#USsbSLYa~h)>_Kz#gQ&51w z98KTJ^SiltAMNcf2g6p|(AyHyDop-9&u1FtJ!l5v6*9>Au(^CO2iVGKmA_&sBx~HG zBhj0q*)tA1GZ=sl1rVjhHm+Ple|Cj))VdLt)R#mm(n-}~0U zP7=reLC*6Mv0-;v4zn{^p|=0n4Q;}5G4LoK64O-B5R<8AqropqaoGd8ji=&fg`8YK zysl2xJTD7U@67lw5P^`9klns+?EI}eM}v@R9*8DyBfTLoTQG;3kwH=3y*IuIgrY+J zObY+$2*!!V%f%38`NKjJ`oNiT&cR`@Cci&Vqd%{J?9&E&M$PAqS&`2YuA%YVu8Y7d zP4Q*raFheKqS2>~fAshbkW0o_Ei0F5Yn$88bhFR2Ryz9`22U5OmN%B3zCwL>Y*`o6 zK3-ZHb_E}~b-TEPY(v0;f+Ip}0-W&_0>yO9pEYE2RUWWbE-0L4?*`2=t)5v(fG%EE9S7Cul9%aq_%r5 zRwsaJK#PLy!?Bkn4Co-8D*=41gyW*0sGZZ;O>p}J6aI&y(U9BOroHue_z|N;oohgS;#{1*|0mVhZhX+ zFT^K)i9OG!dM#J=_rE<~%enE#Q;p5`*sT7=<{ganj*s_o3B0yl`>MfSHh;bY7zgE? zstd5b-2EmUz{nl3D`;@Bk?Cebc2Vedu0X$#cdz;Y{+gZQLgV~KW6AiOt&9d77{+Ck zh;zYHAx|=Bl!0?GGr@cv0(+A(Xg9(=L`&U2`s+a(B-(BLp)DZ(Od3X9$;UDBCvu(1A8_!1 zgsvw0AVd#tHx`3gu#BHpUZngH=~TYL?%J3?0NTS8oxH1g2GuLVTe4!}buIZKOBl}z zX=-5w$w}hffDc~y>WNT~ng(8>5ec)|=sSqosA;ny%;&Mfl4klJqH&8lM%eDc#n@o) zF}gsOc?H?vr}8%#NvN&d(n-lXZz1#?oqV8GWQX}1gNskX)#+orVfkXr!PGAs^TxX? zgI5PW^jsR&m7pe#uI}ZB7{hdN@61vqqOtJ7M>L9W=JP6#H&0^m{~=q*?5H_=Yl$Q( zYy10HJQYNNW|)E~p)Su-^#>b&bQQ?ryc%>@tq7~cek}RbmXDnGw`YVlDLDltr9;`w z5;M&R+DW?>U<=(JDf3hg1`Ii4jM2O2rz)*iBge#)tv^$jAP^_sAX%_4fK3mvOz_J z@3R=CG{&Nkq6MMj@cW24LRWdH7uHW2b%rn7Bb_F@@l_A;U3rVDtlMVm=f&0!%#Dxg z!1cN-FI`abkLkT+J|in6(5NcGSD`vUukY!A+pZ=CMObX^PYZgIZafw(>DaJg#($hl zb(uE8BA^>9aWP4qDi?AEpn%AC<>Q>6m%iPfb&)=Wy_|*|UCP2DrN1kNASLkgGGi^d zBz7~PjZ3npXS!VWR_-SfkQ?bY`{CK3HNDs{fBgh2<9)84Ez(DCKg&mZ>Zzo(3>lHB z0u9x+nGJoY`EI@G=-r1lJr645kg~zb>Cm@+M&Ko$O{Szpm77K@0K^cLX=RM743fvQCo(0ySnA|KKAc&fiD5=)?369c&UCxusqeHam;iG#p9ck zT#NFP_FX#4Y?$#)4b(ImJ$2lZ43Pb?KlngxmLOpC49&h_fborE@OW4Cf>Ck>Xi)wH zW@cXl!;@C6bzd$76dCUip(iFej-%0exfP8K5(->xL<`N-`=qiJrA4f41gvGIPFxKb zhddw6@=pC5(0`yepB}4l&XRhL@Eoj)ev_heV3c=BJe3Sed_Ux5TqbBUbGKwAm_4gI zIGzt{)r=}RFglH4bA0M_xz!c;H~6WeHmo6d5L7jsY;_O}1=kNF4k@iN5 z`;si9a~Y5K-)9D#;!22!JPn?VD&@a7^DofZkNA}h};@Wlv?9+-qKC5n4ZqD>{D!L9eWA-EbRfIE9zj9FS zcJRLOl1D;YVj;C@i$QB?0cd<>(suN}#7d18KK%6e|Cn2gLZ0NNYGU0k3_2Bunz_Y4 z8uMEFD6^HKt0^W5@J?{Ilz#j7z!S;oKAZDp-MgPjai; zfYw1b@6)b|#=(xP0im7>hiv1$+5E=~lDYT%=Q;j`RSQ9~(aIT2abbU7^#Zc#?`eTv zbKdU(Xx932VYg=T>8F^cF~3*|yYZq`j)P%$jZ8>nLU)iKjJ=2t((ne;Mt)i{b7$&~ z&9~n34)*RB^RAI2;~4KWWy{>}RP-MN(dhJO)mxfMZ?FqD^}=*W)za%~pqBCE@v#Io zdL9=iW3J5vE-i;!iM0qXxgfo^r~^&PFx$p_lWWlfNcAb<+*vE+>_TsM$b+~0x74*m z_1WgT&zu;cv*+E1XAsHVtjc0#d==tc z2zM$ws9EuuCFZ)qPdV@F5G%Xr+Jc23*ONd<17LLGjvp9wD*`u>ue;u36+$nzw%`b9 z=v8*LHS-Oek)D)^m8u3Ql%0{agxyE-SCR#2@#+L$w*P$W$62Lw&O622{3y8aAaA@y zYqQ+0{AqWn6&~)az?e-`l5x*>BwWVg%TAHKVC!xZ(Rop2ExDD;eVirNZI~Pnf`tK_ zAUDTW{7^<*;zGH7x)vDGa)rn;t-gqGe70y=Ig^dd%_xU=@!rq)36o|^eJB?hixa1L zbE`^X1I4hVs-aXb9z`#{djbfv3rojHh^u_n8~QXXQm8;$P10%5?|3(WifJZjghqm~5dj@~ajhy13z3YJeMXq(5V+qYsET zG7xERxFpzh@_A5t(VTqd&k9?)J5k^oIInc0>8@Y5Bt~qMOqdj6nu;;{+?D& zGe#Jo8}f-2->4*6FG|4BS!4+es1E!B(~KTUr=q?q<_;|o&%F6vNcETvZ%Mv85Ph-!^Q(!@6HdCh-NQHPv!$P z30fA-nEkS@3(NfGO);YnZDOVIZ0Jkpp4ltc1Ct{c9$R!uv%rC=-@q~ri?CWe>iIQ0 ze(H;XIOX-_yW@Dbxkg!FNKZEqHSp9P#-lw>&wjUdCiT3A^+I4MQz2Oe ztJ&craR)}6o>^OmQT{#rA@kgM~eAQ$a_O4jlJOeYfqE4wR6+y1uf`x&6G7 zIQyb!;X#sTY#&Lx`b-Gg69~lwtULe@?tnLlz2?Za6?w1l5#5Wx7cKq=`hmv-PpyT8 zLo>;h0Fu7T^r2+pO1sA03LM4%>E*?^`_4$#m{IjWcpq-!&Zx)8V*)xBtfUM6`w4Ga zfLz>QhvWnbNs~m#6{b+_(SPw}ZiV`?jVx`J?R*gppkA*8I$<98g?zm8L8~V-1e*=>2lT9``D_cqlj;uT!jUqE zPFWFj0_!zxC5yh(r!qS*8pbvQpB*4kf>*&h$9E+^V6w8A_!4ZP7`lp@b<$}x&pp|` z(=7hTZgCf=^*_g(`&pwe7xNgnE)*l?0My6J7jqknT8R0Qq*t6w`VFMdnnJiZsY5}o ztoi|ZCEP-^pg(%7XQF!UPIi|oqn84ZWidyG@}9f=6bXFDnoJLB3da85G zNSYBCEAk;>k_K+wc<{^kZ9M`A=XG8lMGAnI#KDkGKBQYYKZeIM+^*(CN;e)&7=5rK zYbe6B^Jl`g*&5DKHhek!8>A499~!pw;I-qpQue*p9G5Gz|2^+@RF%D)TXD$c@Y|3k zBf;DqKiHrt?)U`s2l{BX0vo5ur6{vKY;zR}kFb1PtSMvP`u5j4FRx+}p!xz`qHgtw zi?m7tqBF;N0loqyRSLQjy)o^l5}o71pkrag@xY_#=n?|XJgom3gpGMfMrrWzrkm^$ zdt@o!4dMZ-;!|)%=ISbB=};VXNfDOUp)KSb*2g-yw|96b>jB)BVNxA#b??#8(!lT( zdVB=jvvDxKcQepty9?(_)E-GeLYt=X`0g(ii_89P0X4q0Y9J0ok{|WC1*pnr_+h>J zuo&y(x|ngeT<>Oi+g-aCMQ>okBR&pz?B|BUBg2pGi=zF zOE?4*vr$J{*f9vR2><3=BgN+_f{0LP9 zWspX+Fh2m=3SfJc28H1d8ExiS z*$X;brN2Xu^}k9PNfrITj~txtc{uLUh=5d?RIM|$=sR!~;r*tok1FD;9c%c}qPgV? zD%u?2^8wB5i@i^nhIJdY8^pcam8qWrn?(K4c_TSogLG;G{krVN6`{V&9tNpDJJ8(? z&M==b>djG@Styx)j#_ca(s$D;8iW9W?PKwPjKDibx!%iGlv4_*WNUW)X4(j?kW)Xv ztoOiTkl8Sm3emwdRmgkBIi!QbwfNO9;b?!CZV~7@da{c7-8|};5Hzj3pGDbvuc?yn zcCg{b6trp$dIgJ*~TE?OZlk4i6}s88wHcjwo5< z+R>x-+w>E4s3#<9j8JK@(~IN{8u+hRXY>jPHJj{ z84}A?@+B65;@yOCF1m?^ejiV=H?#|PI5lNmmZq#cq8^NY1T6lCV&Pj+$%f#=6MWg1 zt#DJwnqrcS5KFgsj2IWqx5%=Z;V`d(Cif51p=NKqCLEK^Nm596qJF#=93~$-U?k@m zcmBgrf2;mZp$I!L#Dd=jTuB@MThQ~DPJ|iQcKWPVUzHv2!Z$FaXwVWvxyeEp;~8Rp zhB5}ReT>lwNkT68PK1gp0@pi%D$)Om@QdF*~38nk_BqP$qnbVaZGFO?? zYArXKpsmrwIAu#VZH_}S6@~X)HkejIo}3Y$gC^#~%Fn1}z>mfe=ES zv81!+rsnHgHm~GBJO_U%GtXIlwd_W;X?tBNB(zM@Fx?E6_MikC^=oLlKR(kLK7;A& z10xvuz_FHD|9*Q5ELDU+^sam${$6vtm=u;rEN7-|`RL-+C!^a&^UtI-SgpAYS%#f? zKPMkTYfB2OcSf4_cCzn*3NUvME`C;t0|4WqEbY;4gErsI4p@ljO3U513B?{gZJWDZ zw=)b+B>o+H)Z_Z4mTAJOMvi(EW|3WYg3UcOvpKd20Swi`Cb_^aLdM za90Jzx6tGwk*IG$m3e}&d;2oQkJqermgO;k3$WuHG`d477m!GEsI^hOa>FRHqW84E z&f1YTTW{Yq#0!SjZH@t(>Q~?8(k+_*rIxISD4RK(jW~Dh+&FVpOVB2sVd0#S{kALZ zKQug~E~uV!A49o=*drYP-bRUt1(-4qW{0DCn4MS9D6(j)SM)kjB3e#(7tN0a6{$AO z2bSY;F5(2Mr!viiz(_`w%FRMtk5nK`hK3OCJ*<`Wa<90AgSWw*bsxYw4n`;o_aHK7 z(CP+g9D*cq0`m9{KM9 zFryKxde8*`9k(X*tDHB)%t73>4PsHb7B~1uJQrZs?a=ls3r#U@vlu!a@!Ub53_x;qH#O^g3i^L%0p|bA z6H;?1zPndZX>ojRbwr}g{-O^S{9>Ugn70~CfYZ$|RP+Fi1_!Qy|7<_tikfOEE~XOO zf3eb?3Vn&$hNTAnE39;CgPt{Lf!*)Ag)PN}TS|<<#=EqbH0OR&nrdod`18d%Uq(fw z_qgc&Ltg{Naa`JW+8`pHn{CY11*Q>kjBFI);&eFCY}jZFk|}i8BwspbE~PFkbS~~m zt^~(}U9jpED7gL(RRzB?X#c46o{NM0lbmznk+-ie2@}Medoexd1+rpCV920VoAuYd zAz%TJ9OOP321OTiF2k;;wV%uv$+J+8RpiZuTakofZiU5a?=$Qil?PzGVZ9`c0jpvK zT7M5+iTnR`u^ogP>ltbH;K@fAzN*G5bAK|^wj;;2FfDd}%d72Pp=&^)9_2SmKsS$0 z^0qf;ck%Mp&yknuQ#CR|h?tTFheR$cwqp}d>uo8zzl2s)OOCs61+$t;3?zqdVnH|?3GZPfyQ%fv(eBB1RGG-A-J~9cuz#k5%_Yj4u$qId$jUV6 z>d1d}BPR~d9F#VpNuZ9Bry&lfRF0^k!#8WX>{5}<+J)>0VbhiWZMwhZ2z26E^W9qkW*D;5u|5>#RW`4- z^R)1@GCQWJC}q*Bwaz2Az{R<0C|a>J=-jL*_lwThz>i~v|Gt`UGC=4@>V3iRa^QG< z3Nf{(H5EGJIRe9IWG?X00?E5jEZe@&T=M8=IJ9Ky_d6>jf6DEP zrdSzU9uHb|7nc#Me;&LoDPB30B0vZ94#&y)O4yq`M(;e&J z>cz}qU|k*|au%yg-_P>ngMhm*syoC8#3;#-hfS6a<8AxlTT)V#ae+6<|E+?()(*-D znuWBl_`$d~E0m3Qe4RUsy!_Jc{;40$5w0w}zK4%0rLZXP3@-{@Mj$ke0g)A?)dgtNXNTxwYBew4k4m-X77e3~0q z)0_`-k$*&9Y8<(dL2&z z$d}sU@-c`16)#`rQ^$?tXRmiYfz27-VX5SQMBVc5-qGj9nN+Z!S*rWd8^oUq`z`Gs zPpd&d`qcB^S6ihJBFS|tcXvmV=B^I$zSR{ANHD2kP~e?U!g0+#ye<;ZJ-GN_@-zEg zL6yRjT3UGTxfBNGv)=PM;TVBl^qQXuOs!|nSrl5vR|EGdodZ3o4Y!{$%6&mPaX}IY za`%lk5QG#3-ER((esRX&z+UqM3}yHdv+llY$VPGK-mMFm;qd$(@A=)tL+>yA31)uy z$;=U&rUSV=u32pba@l}DmFIQio?Q+@yxJ#xH7b2|d25K>w(X$c_(^$y{m|rlBUc1v zq|%|1=eD5;pLmCe$KKM|*0__8vBb1K(z<>y>WHlcK{jjd5X0s-&K-zSxpa(4Ob=|W zb~j!QLz-+>IzFBnG@@1LXBL$1gD50EY7V_wI0J*yKVW$OIc;`{cc)*+K6cL$qn!mG z!p!k;4!#KP*!_5pts*+T+2Fr;8{$P$oEcp4W*|^Mc-6 zGNoy|tyZmtL#RW~=6X<8ccdV$ES(HwDl5p+kGa!-xrODB$3>l{PiuwhW5=eUGV-;_ zRyYSYOeo#;kxy!RC{2+?-(r37+`f7zoEc^YZXz98^XQEk&-C&?3F&9_kr&jEkAsLO z;4zZIEQ#sB`$!-AOb3_Q>kcFB19vtf9oM`721|Qq-bw7CuAvHayo?gegVKHT^YON> zG+ppOj}4@Ty!3ywN^ocu?x@N24Mb$fq6S*V-)H^69KhdLvhdVvYXW-xbXtTX8NvDE zW9Fde?z=ozUp^t}WL$Eo)FKbb4coMj-iqs+#d0bnq|rEXxH?9k8sQkI2DfyYau}M) zUmSgZ5Pwr)17&Hrf5EJEy3{wjF?;mo0j+)Tc=R&0(O(~dRj=KiG^hCMjku9HeMwhX zf&Vcs-ut%BS9dTekH#)S;eytqc^ogTHT4W7@@wO^1ln}%>mai7i9BRceUBF8hNMOr z>7Yb(($lELFQyi-B;f|HscvbspJ!8ORm*6}G~IGa(>%2n{&0pmq3^tNd^I)71Y$E^Nr-0-0RvvX~C(HQIoY?dsSUnpFB6w!7oMIK{lXN@ml=6E$9B9IvL#xBF9 z^iKE^szt=hO06zCk?GF;jjcJv$~rO2$u-*HTS)mSJ^$%qwhoa3$~Ft$&{iy(P^=K0!yWJ5Tk6?+ zGd8m1)19y-`3JRID^j}fF5822NFL0SErEEFDI`}Zr@3Z$HF&vB5H7tAW-VkExKk&W zR^#Q-$&&Q;cf5cPF4O||bLMk+;B@{oXx$9h*V1l;^Gl^jS$C);Z}w;7>}X=N^a=`l zQZ-#Pi7La(DDazb>WLZR$EwywOVGpCXv3x5;)*b=rf=hHC#4Y2&>wE&g~O#mX>?PA ziGXIdUo{BBYryGex+$=1$X7%353)a>e=@{rAjaHc_j+X6pA@fexLj!!(k~#$zn0d- zZswm7EYcPTIvAz}z;-|nRl_hAT2k4(#9rcl6SJYCLLc!+qL2Ehq~F`gx?e9lT`gw} z!bp_5TcoOA`e>Pd|85wuWXmgiXaVj`YFx)KV)3YX1@C`}5YJc-O6})P{q>snids66-rdy0K3Ar!JEMle;RMVkD zjgz5XJEFrAU#l~PE*OArq~JTPb)f4dgk#TiTlo$as;E4W7PUAbdW+P_b>z)jGNLe(yr$xLW&6O7Z%yt$Q zq|h(l6lHc5`9G$zb~p7g!El~f+BFYN^gfkH>?&++Mkeb>b(&*26oeh&`dcLvPDvqu ztJCgMinyJ*^3zPbQu#J>{WP<)1yx+A6@R>09?uA+xK$=t;gBiBLhit<2u<;Tqh=6- zeD;yn|DAl}CwEDDutLp{5S#ZNT6dX!n8{CAs5?&%E+(e@!2y;+Qn#7w-NTii0_19h zDXs0TpS$PT)0gI|b!gzt-iyh*6(tKzLge=PD3_bFgt&Lo0G&<3e~<zb}wO+0$z0edvPd*_j~*fr5TJ%~Foh zzZ+rneT0?tIs@p{`|?gAXyzEGVL|0Sgb>TGey_37QGd(pesz;V)jEKStkJVF`vkwA zwGO;6w7EU`l9SN`9eDV|K_0;(F~yBk$+E2#dSvM0BE7jm2LCCv?muE8g?GDk_Kz@nH4g%G)G%qjYs)f>o^}ITi|9Zub(#;{n$%J) zzPnWBwQeRof4z#s8kn%Jdsm5)V%lf-HM?&U5uTd|RO_`z418K>ebPFFiS$|fD-0!r zU<2<=i+6UCTGa9xprTX1chOrkNGZY?4hD6iF~b#ysP(My%aioW67#ZiMMFJzXELPL zD@;3Qyncr@Fe1k=Wt*f1_&mj|!ws-Bx}bUNyN1DE4L`@)n0eKFw4%VVec5aCtDE=V z5KR9&1mQuLG#w#pc&PuBOA~@)PfL}TmOfnH7Qf)DO4Fl3T`=rL{LxL7gfI;SN4PY> z3QE$<&djuK9im&4CpUQZk=mn3E}CbT=Ca1&)JWFgPxaSmo7v!4RpIaaw5$n5qvq4M z-~q8{X>s;R(tol{7ZRYZ3Lrj|8c8i#rLyLn*WwBD0k?v6ME%eIl=4XBEf~06i=%`! z(6fu7gl)d5q!wv=no^YN@xOM_tW*k5f@M(u@ujISXeTnrms*t(m8-3q{~Q0YL!} zQP;`(gOzUlg1X^kDD)ZWcTvZK&iu8tVXUHqMCIvQz<0e5kMvWD-Cj~EkZUuFyZz&I z?U4f4<8_gR^`cj$m;6ex6B$LP1hMeoS;Z)sTUy9UsEXvpsB|#=u^LoOEKl-+oMy&9 z(DUY!yx=ze=BAdIm_qfR(iBqFCTRjTWNAe&d+T6v74Nf0&|5h~&W(ZSwymTtAd*Nv zghHP7rL=PaA78ZtE^_A#vo`w#Wp#5;`F%Wikjm~)YrneNUOW=63qDnqtkH{8RkNVL zL6vZSL8-1r-H!N8YZ`h$3mi*R^~zKfDsLcI_x@WX#Vikoe6}0|d079w^8PzZj*YGm zF_n%>D^u8N`}aYLp-|f-+eGAwooL;#{d#^f$8GL~s+Pq8HmXE@OotA%x3U1S`!Vrp zp?oXmFeJYtntVqUd$}d+x0|38CqqY zN)$JybruoQdt$ij4MZI*GHH815j4M#e6gUIGt@r097JG|=* zTO-Oh9?0S^x7S~Q-j_Ut6fYkbX2vbaP{KJ3Y?;Y4r0PRq;K}!HPa8oNG9?fXf!=a!AS#@X5gWVP9#ir)>KsX3Zs#H`nUTJB;?~(kxvThSVP>I-jYai zCU!1Pjj_B?i|q%$f+a)P<{e;ZMUXcXJ*+2#XnGj>NGpDmEd{;%7rIvDi)_OKq6qhm z%S)r@WGGKsTCM2_k$Zg=8I9-&|MF)t>=9O4W)DP?=qAU6JdWxPPS%<|T+ZGcrKuw4 zX^Na@Q?%MxUZ?wBGw?pcOVJFbAZL>Ooc)a)@pyjZJHo@F?lgWdSCh!n8U@2iz4TSqOqixlDC z7v9E`uAMZ$g)-uwTSVh*Xvs6%641n{yui}u4&4Nm-{e%?@o zVVhxIko%YW$;q-!k$t%IP9o?9su$IL*jwVfDgv!YYYj^CXq8g(m2|3NB&0Bid339OC@+2vv z1fln>_<4vgKw_3OHu^_5KWi}2KR1iiCN_HDV7h8GRCt;-J5$1EqI%QK1U@snM|Fcr zLP5bRQwX6qd7mZXeYP*@zBRpm|K21&b1G zSQOpN9X|4(L~^;t;&5Gvjq!{_oI{c;qZ;BPUr{3kGI^t|7bN zb?l_oJ51zXQIN+I(d!3Vsf!LE3^Lj@TF%Ea!C>E3JiIF4;N7^4&rb!zjiKGqzlPdV@cdJdLy{}rKsU-lGboq?Yk+n)(|8aj3 z`3hcv;yE%sL~IM~KWQ#3DtcJTqNC-~F5d;%9?l)@)&ak_gdUa_rMx`gkts|RWJo*Q zF4yR~O#zBFJssps&BY{YoBx4gCk5wVy*dqR%fma+5~v94r71mlF!%}kx4>VqWG^I$ zK9PC}Dwo~TvT4!)T0AdQ3tZ;(YhUL5lty5qhI~~XdBmpyCbKY#d28C{ZDVmKQix%D+xx|K{dGE%VSx6(9pu?AK&E5#Zc zS{AzGb_Z(~b8Gc_l$pL&%=-pBZ8BK?+3P=}0d;|!*c%B)8LH_v#>D320)h$%het|z zp7Nl9F1$E6xX4S>Afb=41ETV-3vo~-`4m$jAWEzJiqdAzO75so@Ea-B?!Z2n;$_%c zthUNAuZ}m|#|daQ&MG^cLbdNbHRre#3U4?NL{>QnBbxb83kxa!efBC`QksFY2GuAiAQ721gmsf zEEN2)X;p9%p6q9x8s^8VlBU>wT95bObgH4)@mh0&z9f<>DHiZ~Gy&Hk@w?F7^}-`$ zoobi;V^E%D1wuZfS)X(jzN!lFX+4f>F&BVkrwOS{20DGn0dT!yX4~jPcW?8z^0eKA zV+rdQAl8;|AmStpor&bgi4igUAbD{o6?i#c@Zgu?Wp_p*CnrF@?-2UTJ``8KFj4GO za74 zakn{~>jZv;aOdaeWdZ_j|LpRF)V72|cj;yZqmVj?9vK^M8Z^JU0A)%&Ez|1elBFeR zJJJEZ9}qZCw?rm&Uq5aCm7%q^NoBP3V^24xwEYi-v9<;U|? zb=Rx`Vc|HaA!^ad?O#$7flr!bnom2Oaz%!$Y6Xe!(Ux%JffjKsf?rQc_h|?50|^g) zJ2+9CLVW%-z#(rQCpJWgQd>3h~I*`fb4 zC!I;sVMp`kt0_~0BgwCP8;e0i~mQQSWc%1cqpmqLaZxCEYc#n z;oP`Rh;s7DCGqz!r&*Qv63i=V%%3(N((K?U)rRzh2eEWhiWEdX{846iA6d-qVnCeZ zX8>*yB6Ed$@xuQ5kG8D>o&X>|((kPPUF&n835w~4KZW&H@E-Z_U(aKH`c$Q+mbT9= z-sW**Y+hR{iHIN+aP>y2;&WCez-v9m0Qkh8@(2M^Gw8)NrtM<{O=68tC`bxC{I7@n zEqhRPLxrN9WN#Jf8R!80Q(7l(y6(TLGhO+7g$ik?tjfE+-%~V$w`Q*fLr==}Mw0G{ z!x?nOF&@?en!iu~ubZYYduLt)vZktMyMNDYhffoqJP^V`j(~$o<8!Ly%ekgRgA@ni ziICAp%ySbB5sWxAfAWbh8!-$gq%WzM8@g3m0n`coo7IMyeY21UPcv;042l+}ZApFP zh@4>ob~K?{zr60C#^cWx->bAbcsDe#=HMrW*hff38=s6oEPj6ABoA%Syjv(c_h}Bf z6|ekxv;d*gQ0TIz?48*3KPcOs;kpXIo@$MZr88$x$?n>p6+pWk;k z!+rW=Vl|`-L*(Au-AY|{FcO3weAKqNeWs7W1krkBOT)N=KP&2IK#t$~Yo`HJH~Ei}2v{CeOYz_kA+$U3MjTxPlwE>eZa!);=}6nY$j(S}zjZ z<}~uR+vfdDJH0&=Yqw*${s6u$SHDSjX>!)V(oso+qA8vsZW3$EgKl$#xkKQf9Auzp zM<;l|=Hm-GdKzYtgp%_=(uQy?5dw^gH`{&Y+EY}d9#Fd%e=ZE>1rGrZH(Pn89zTLk zJKt6`Ha=dF8|lZmQn)4*MEensXeu0bv!bLjd}Yt4*6{%uf*CbAm~ zw7a!msGU$yO;=4v8o7T%l7pHg7<}X?>EcgJP~P*_Y_6)0)kHX3)J7^t%pK*d?W-NJ zn~~P3uut_?#BILRVJX>C9^l2=E2Q`75;)Ix1&Euqglf$1ql$))bLFAWDOG;V4tM(G zgnYpw<=Q;oVz0oPmzqMjbaeO)6iF0s6s9=o$!z#h#_0tT#Ua-GfJbRR)R1qtoh?5{ zd;J=&tMblZy~e-;*_>EgelLM>yNU{?G}*_|wB3)w+`vu9(L6^+kgLEHInvb<6(L(8 z|7E0wZ4Z2O3yrd$`<+D~FFf^Iu@$wU336X3)@{0;c0pPZ(X@W;y8=$v-)5VND?jL~ zF1iBNfgz_7Ir-Lo^zD9H^=jW#V;vYtK*@MN6e}6uE?wtEpm}7nR{B#W&wQh-)aR?Q zni-N<%UU#ck7lyKNmt19D^Uo=##<*nixg(Oaqun4pDPzQx_N@M`0Pjpxs|jxtp(%8 zzfBhlK2qx52oa*QV93N?``V#dx$VH}pL8P4r!7}x;%^7E!VkvbM2Vv(-^&_YL1VK| zcUz*9lM*iuQY?hLek{JOIf{~wa(1svBB&NEl&fs)O6}K0Chy=U>-Rr9rjdz|e3G)Z z#An>unc~54aw+ePIRH@Ew9>>o@K4^t>>q{FJ_1}0z}+W2y+?KPf9<6pu&p zu3q&_{dU9kQ{pFL!&jWYg8H1YaV{&eV@RvDF#azW;C)=7*;4K$SBqenhJ1s-fQ~GN zcohXzG^RK)uyqVx3878Wh`n|@h;13e*ZIhS+*=7&HM?EFqG*=aI)tVTCqtL>Hmh=t! zzg6huOTpL7#e!kR>%p5jh+{;q(1I&jMvF86JxuaCdW^e!a~x{|QJmQ3@Aw0#>w^0G zm+O{G9s2vF<4I7)fui-+>Oy_gr58Y6F_L>Z6f)e;5AARm5)|CX;i$cW?nX|36Wd}k ze&H(E`;pZmlf0U2Q(eoeO##=|-+Zw8`CK%kW;+v;&3BW8%xpEoOb$L#B7SW)JU>Rv&-)1dV57SE{DE>aL`dyB_dB@@Nigb2m2Hvftn-Lu&#Skb0HH7b$p}u1 z$jBZ|gP*ld7)(r4ikAC&M$<#hDYDFd$0k%)E;5gS2pZyT~Fb@X6nA-zTyZQUz@xm}tq&d<3Y@c0T|JLLV;mYpFj z)mRD`c{4bd`q86d?D~CwScLA_1k>19YGw`xAApKac#};uQFow{uyTv3K)TM@ihAe| zk+o`t3qho#(H38CoOZFs(d&5c=ZVI1H+~E=Qgl z)YW;x6e5~^EQ@OeX3*nV_^kRx$JlZ>%~Sx%W8n*! zm|u20USIxwZzauWOmCIo|Iph|oCx2eby*2Mdqk#mV52f61jt(u0iUuB;`?uc-~+#r zD?1vDx?+Y7)UY)YG4ilpf4+R5WzGRk8!rw4ZO-+#<74h+_C2~okpW}kB{a%Z6{QvH zbRIrsES4TUzrLe+otSer`~>}aG8y|7-q^;PN=%=SI%K2!SH90dg_9`T?=-u|9wl35 z-Yv0_3mod&Ljt~VbD-g(>9uj1BFtjA>k zOU7c`{RkyF&-%t5<$K`~3Y;}@Vm1-pi?*R@x`7;eeBQ(#8?)j^-PQrG?#rMm6;Kb%pz@W=@D@_gX!f)Vx&!O^hG-1o(xP4jIneH&z!7@)~}k+tg=1{ zF*imZLqRKBTsPK$!k2!w2HWESJ>0(Hz?KWg?evl%{qUAk z7H(*uU;FM#UgGqL--=KsOMUxzl|cE7Q1K|o1e?ZJisIZ}(s1&R4h5cP2Tbwa;g0iv zE1r|{ob7|xjkg~s>&{mQ76UFy1@#wIot@?E#ZZ#AY|x0_sOye>6-l_TRNa@}AXCfs zkpdIZlN>HxCXG#J;EjutYjWoX9?359gdDywD0l?I39jP*xZdIZ%OA-`4b5$#CDH(Q zgn=Ld5Bo}O)tFq_H*q*r*k4KHv0~VlLoI&d?3RbzPT=Cp%vqW#QL(Lwr|a{9-ig+} z1#K9Ki4Vk-8qolN-x#sj$0pcFNC*cbXE8OJ#;4zg)de6+?~RvxLJB3x`x$bG{_1_L zu<3Cv7+HI2A19~VFjaANzUny1vo-D)l$F}KSZ#uBTsa&Vsdc#UVKIa$ohzf2rO7t& zl_yLzbV!bJEK01y*Tg1a?rCnoI|iPf;G{SMDy7CmfpOG{$`0p_pwS(_EmPV-5$v#T zRj(kyQ6brS5%L4fqNQc;%TM1>FXmQeE*TM9cTQD2PZl?q8It4+#RTNvZ7AO+U-qDt zn?05zX18KmyQ$(o3P$Ty2s&1bD*g%_X(J6g%}Wez=4(8i1S}o&hk`Ca=8pi#Q-Cnw zbP1%}e`~x*(K1rLdeVwG$&>$@K?a1JK_RJS;Mu8>P+O;_^{!(HH{il)-}mL>)s#5+ zAHPej)l#2Uu2*34L;2R1tA9bpfVT0G+N2u*@A~#-hQr!QgtO=4tPwwI=Ok~!peCdN8;F+;;)gTiLbv(^HWBHJt+E8#wz%w^ChDcZSPm{CrU*%c@4C=*L zDe!+!Kcl1(I#Q6hF)>v}+8bWa!u!NBfezL2QR526uN}C10v4W;nRvoR2e+CbVOk^ zeB4LQobDHM*GZ3|u+{|e>q*H{<@Yj_6>LY_&Ov0#5dV-SUBl~#Y7KFvyt3wbvl)g) zwgX>yXnB0z;(AHu&}oAVqxb+eam)YdzeSF;Cg!EDa&k5>#e18iRQAR>HvF5;bnw~ecUAy|0&RX7fB z!vo-i$E(Rz;&XZsKWwGjCFugBHhIXM`S_9n@g=*f@Hdg*#3P z(SZZLr`$H_nI77~+{#WhV0o@!ghl~~$`}Bd{tJ%Pn&vSE_MWo-m_3@=pOf{*+7LARFiEF8@9!Ok6` zTL4_zgH4-qiladomY`2Fc}rj(!MH#(WAY<3={QOWG6!G9{Ph5Px_e7#a9wB$I#3j~ z_nGt<2X(8pIE&q-x_0C@f$X+cg$A1LjDC6&4VP8x4z^eSRz2#|p=`!fW}*h1w7Z9f zVL<@nA?1u@<<$Hy-^BD*Zq_7VXJRE~8b^Teul#yael`cyuH5R*W*mgH=&e|b7U{%h z7LX^%2A{&3 z={oH$%6piN>sT9>SZqJDFWPi1y4=2YL7RxkMuI00N5(5o<>yv8{A21U z;}JiUzxrdX)h~<1A8{~E>m_gnm?1i3pRRK<{C_*ywnN_k>JuGQ8;CB|VDfoS$r|~* zLc}_w@;E3fz3Iy*YSbE5voZAY1a*Gl?@TQ+HxE8SLP!26bAOXGFda7yIG*LoFY zHTDk{TFkm*kw3XBK3Gq7e@?ZS1g?V2u0L7h4|u3`(HpCq0cdKz{WmA=!)40{s^ zo|z82Ab|$QJ*~sJ^MU=7c)<<*0XrIc>ets1qtU+^thq+@zS=|T^UdKfGQf>P#()%$^8P{xf z%!Y^=QH8Lac^9~@>sAq{vgk%Y(hE-EkKM7iz6BuNu%0~EOXMSzCFkz4R#B$#E>7df zvCgI0N-M7ofb_8Yu9rXT!?-acH!*a^_o}nxGMO^pNTa6Ht!?Va9tlCCW35xKHR!*wMTmg|SNK1rY1~WfQtYqWX~T zjw0-acPHWvRs4WmQBOlrztBiDEcR5>_@g8CszrTm>hMuAJja&R9P%Ls@2QTtJGxQ&m+4 z=-$uvFb~Pacu=_)B8`sjSo*Emb9jA>*aEVt(*kg?OX%3571gyMgtYs84vhz2|4VA( zGM@U@N1^DQr_=s`qT9XkYaAQ9(XVx}LA4=~6sP53$bbJy z;7z~*5aSQ-g;CzfZOXH_#u_ZVn3N?bGKs&N7N?20Q@B~?kZ19!L-O@x+Wb9_hGY?2 zDRG0qEz=*w!XIO7!;~+5G@+7JM)J>Tqy7hMRM7pT{8{C5w#)~4N)G$LFH4u|G(qlC zn%!4SQjfPVaqo5XNAlE|ztIEx+C5D^M>#$2f>R2i2rfPiFSk>1x>eh+S#;B2Y2LUhpSU@-y({$sCgthp=IHOZbNNMFlnk{`xbg zT0q8LZ|r0_DC&-sg7LEN*Pe5MTJ8(>Z)JibvyTKgM@YE#%x!GZX@J?+4Jj5%^q=8h zF|8oC!|%Y$7#(rJ;_-?iXdp`i>kHgP6Y|oV6U`Sy=Km?~fJRo{bHAbpvqZ3@;oT08 z$CFlGLBQ3k&^@lGc6TvLj|tWRHb;k^mSAJjTU$DJjOo4i2V4+taLN6GT%fG`O2_zz z!Z+!=Ci#8!qSZ+R!7E#OEK)2v1$4>8PAaA7FTD6zGDbO13+Rga1`KNJd$h44nAnSV zW<%tgY`$b$Wpi<*P9dIueHlQfrc6|66QL*ao}tT)P@XcwHlbj(?c$f$@gfN3gLLa* zBuL5BxK|KE7Y2yiat<%m^4vtC0!^-A#+DaEN+N*Qscu74F`E#YpUhBEm8zFd+m6$eTS&pu*XX?&X;}ezB_cGtgnpda zmo`wX7%bY%{f!yo3H1g~pR^nfs%WO@KSrd$i=_@P2;E0OV@(c7$6s|IP%Y~io@mo#r!DpOOMMBqda7JTiptT2WZUPJTxNkzk`xZ_1DY7 z>>5pp_d=HeXG?P_ClQP}#vz{*1OB5AbS9~gox5jg| zCCMf`&57z)O>Tfci9N1iAEOxAar*HY^m5z83k`iMyrQi`J;0P!>zCCK-07YWcHc8q zyUz>91X$op)ekSWPb1zM-Le-EF)Y1BFV~9wGTgX*0FU+>WX@z`nOk%+39w01Kx6VO zq|roUd=q1Jjt^<#y3-=yHYVXAi+BS~09cAzS^TtbF&jsb|Gl3>MZutAvIJUBBO@~~ zLpJ!rF|NZ)r<=lx?kMp-KhdQ%?0}F1f46&zUPU)?ohx5K#%~;{O5o0J==; zU*h2PF1Ac`H+8qxO`QYJ#Q`RcY{fGzT+cy9Mfbg*VGdMu5)s;d!7Dy zF05~GQK`kQZdo}~fyUgXbgnaazI1J&+PLw78Ji|aAQ3=l=4x@@u9s`)sU>uyC}|)s zw>``_=tNa#V$%6=!xLGvh^dHFqpbDqd5W9Z?SUKO*m|oM*&I$xeH!P*KU#qWF5w)* zoH)Bv^xTl#hp!bo7bcV?c=W4(E!OAs%&vw6AOV98G?G#STd5y^pcByuj^0f!)V!Gp z<9jDfU8Ioe&%B@ddlV-sz3i_cXqbcCKzWg|Uz2zkom;1CrGJ!CJ9p#w<$2nPPzOo} zeu<%B3n2x+fJ*Yrbxf1fVh?owD4sUB_2ByCB{DHWSQ-X8u$s^tNxF3b2FXz)h#lYNxKAZe;uEeH4B52~0-%WC8xHPjpR$C{r>B06{ zE2sT&Lg_@YO8;pw)g>HfaJix7X$qY7{=F`^}}gt4Yn4Zj?b{oAW1@9Bx{> z+11jdx@~ob$Y;1;Y@T>KLk@qjy09(bF%Z5XY0?fj>m5-A-Yq)7_Tr0Xf083<2+xo{ zJn*o`95&R`1bNW&D5V%KYoS`wr~A(L9~M|>%voT2av{Q;R6oeOCrkz8I-lZoaC&Sq`X3Z5ZM~95c(mf{dE@oLWN0T%Oa3rZ>lfz z3zXPBh1?vD;^Sr!tHrD?K{+oYB>j)^8^%hSBGGR~bYRQIUN+nx_OYa;ClJYN5jKBe zTz8X&wC&zsGgRv>Pv?s8UOihfq9`SF|1rv%#cCw66S=r;jOumzO~uaGYosP>v6jpt zsugvD(UeqOM^ECK-P;rpf3Ub+dRk>kLJFoztk?J>_QLyJhJgEw^E4WO9HGlZvU`Pc z&v@#_7;BmZRO#23N0!W7^)}z)k!Ay-dWA24)1)2g^yDz;{{*AXkRJ4GM7_Wb?`D|N z>l5w!hUBH*&MnWog}8+50wziGYCTx6uX`Af*W1XjS-_soc6Z4<~i(fZt*MPZk*pJYrB-T zwlVNEUEiC?Va({kG5WnLrN3dlncsK@UXsXFC96%mzIFKc@n$9iz7Q;-<^v%?9=vgg z=Kq0YcS%u_Ps0`B5_Yx~*2W(8JvdRB$DvgpMFd$UxdDgal>b5+o2|pkmeRS&o@MeZMy@PWq!xbFKu^wA>sFyYla_Av+ zxxQkre=S=w?Va^3Hs+JR-0`z$w&dqT-{kTuaRxVKand|v($c6*x_u?C^5a-uJW=hw zL6RPN?=6<}F|4oTbl&%;m1!0jnFnkFju#7HfeYuDf$D?@Aw}XL*Vhd!@ZAEvy$qfT zFROHy#iC>e+ohf#WJ&SMw=K?5_@ND?s}8ga!Ou}8u&+Hn&K)~W-*yg+31NtR($p<6 zd(%SIwU_iN^b|G-O&YZOD}{y=S^QZkW)6)=ebM2{6+2R;MY#TN%SYWVtUp-A?fNTP zU7r*$D-cEnoKzjd56+H}hVhjSmWPJ@hc9%iYX$ueOZASLkq8>UyJeBQ#TYLU?tpSE zKT6z`ec;Ee^E(w3O2sQPiKU zCi#TQO&f4p1N;A1hMj;!UcKn4NzvFS+s3R|{DV4s<1Z?6uTRMK%-H*y%wwq%4I=#+ zL^1qsTAcnfECZS#_c#I;X-&!>`}AO!8PO`2hx7 zLzkR4KeoT@8&v3}iFiW08y{^wUf?pV?nMu~*kaT~vk;(*{bF+R!zdVkYNl}CVvkd0 zaIDIJ!`(tYtg;HFZ6jlugrfB(vQ3`Hb>I5EKazkOE1wfo=~`=_aw2PC$UKGig%4LI z+n1)8MudYm_iH-Ei?8ef_Wt91K*L3{k-*Bw9$0p4jR&~o!< zv1&*&z0+%o1~XKAB9VDRa5Rtm@Uy)j+u69J>lFJd0n{^+^Bv+g=BzLvOPaKQH;Rt3 zxWAt%b&gM}G#6lM>HDL9B+bbL6#UvG+yMu|*t=O1>G*mm<0u;~u9tK)DE#gTTXqV# zG>WW2u?X`M9(6FY!oW@R`mtNd$g)ryvY&NxybceX#QVoEg|IQCWu@#RL*cMgL8gR< zjMnh&xU+td?Z7Y)KTPg_y|z`Xe_u(YaxD2UKE$I)V8KANOKaa$$|to?^o~9{aI9OCeSnTRMwpJWNY{I-!yy!%g7d97o}>r7tqQs3 zvS|Irtd!+v4m+c4s9{7!@?nDf@AbAWx9G2K;;Axv?w1JXyZz9KmN?{mETLQw@h4={ zsl%e-rTRCc_EV>lbH%Pw^?ukkT)eEl>CLr^M|OS~f;u<;)X0z1yHc`#<6X8U6`V?3}LPoikP{VGAMw-42GCw0sOc7(i>ndeZ9@~1?pR`NNu~>Vxb!E`t z9or_L{#!?FHh2lz(mhk!dH71c^)P5PbQ<>>>eudKt7+=s2)+IjPb=Q(3qz7OwO50X zc!)%icysX6{l|j)v7N2MM|rvacXk%<**d~_kU$88aO6Yb;z zg52<;|L-dD`?R~)``^9zyh<@ARQGmQawkHaf)k}T*3PZ@)>>aUf}cW4j7%b!i1+Lm zHK@%?a>p*m_%jTv%Iuvtbfo{rs?HVMkjQ6Tb-RM>G1;c%KMSa9>&@vgr^JL{{Z}-U zyWnbatjTaohV<>v$Tc;1R`0?Rx%2%Wc?yZy0>O<%tL*Akbe1&)*cbVPk7*9^6LX4MJH(0Thf4_rDmihByhYph$Vfz^U;{6tOV zn6gCbcV=(m=M8!;y)%#2B#xh&4W~XDb*}11r*GI+E<2S3zw5c^3XGrG19|=o@1;x=3D;V-6kKtH_r)|;K zq4jc|ba&k0kl~%F4kc=+I_tdK`K>VZh5_)5jyxfwqN0$wFnp0zPRm0k6Du&Tt6>T~|cn>u*~N7`K;texN#@VExt5m1>^tk7(nco)YthZHHn(s>N|fJsEs!;n>o z53f*9wY`nFI&!a_Fk`)HU(r*1dRWG^0YwzLh*hJ@}jEE@c1lOx>cOED>N<4f@A zNO>fKJw;fTHVr7CnQq?&Et5NQ5OI_w5SA*Ncj#pYk*iX|D6LoIFu+xP~4X5{R$H4uv&I({ikq^|V z)x^i33=oLuYysW50{xbQ;0clG%kWSEKjF#G@zD%L75tR(c*(4Pjs%0Z#oU6n`9gOk z21VPcbz@N|D=6NzD1?j_I>+8f7w$Vh_x{UnKmKd)3ZyJlEc?>^8~cxUxxE+L{9_a2 zS1W$p4W8qK!pgDVvuiy30=)hD2$j8z@tx>*{K_9+*9NZDdanT6kBy)%oer~JA5Zg_ zT=TuBfHW`}r}k4VhHsduMOhUB50M$m@{u49F-Ys7 zZ9;YwEkr23&^i$oX3f~zO5M7chz~9&yZh^~t+W4!$2&ugZlR;8-9Tv?OF-PeB5r_}|J>+D?l1JCK61J9AU0hW64-R)CnPlROZ zVapZB{MK&{VYcgFZ1YZwI&x4+=I#HX>6@eD?!K?v#6A5D;_n8TNP~7*((VJSYH3eln-?YjvRa%`xfh2u&IwP0&YH zQYK*@>qZ3M>OX_%L&L!Tv9(ZYI8WXDGmNza*b8>Y2Nm9#ZMg0HLz3c)ug>UxapLi8 zzG*QT}<8(-a*V z9>AB{F8>VMP`)INTgHhTk4p79F8K)qpr-vzTVDpL{GkiFoZqFxZLj!03+xP*kS;W>2-i zsuX=_>$`+(n#I&u?-QFWh4()GHskWOhMGEx7G=J#-~VVGWMY{qJo1ZIlt7vVZTu** zeJBk?y6$@4|5;%xduSp39r!nTS7x-uxzh5XNMi8GK;AQDItS!#>mT8YaqgAb5Xf`N z_&b$2;z0g*qZiS)&=XWaBp}}3+?xv|Ij8;&2O=y)ZUqmz=l)Vu z#77C5J+lY1X9hhH`>NdrR?!zCa33IW4d%eAw-A^zpeCgcKu*>~1KA(kNy*Dmu2A0q z*7)S_m8jw%R7Zd-@WbBs8uG`0h-PUEc*id@zeWmNkBp0L>rE9 z?))H=4Bwfd2Am92<2d435OVeOgsAtil4ovf_TO8w_Kq5Km^5P0t?#8&V0HL%TZ1x7Lj(J@idnmG|N__9p}YWfN5#hkr4CRe-9wX(KN zze67dLOnuZNVNI}3o|VSbdIq@X%25dk`l^HEIKTwXBzIEMiiMwtLqIj zhRs+ItsVJvXq^dn%2T5eoD7nMolzkb#E#Jqd^zh?wB@S7%KSS=0)5Yk61y*U5^$7z z#dkApq>4qA{!K~6Zy7psLW-M!lJH;~;kFadn9r1;Z6Hjd!d0O*IX$qo6LbF~eU%|#ir+r{2`e?e68Da+%K2msr+9mHp>Kw_w4wL?Hxo*Gt7HVC2fakuMd)9@XH|b zRtWP~6CHzRfyn*!bNf!%7=1K+fj@I@^1Je=rH&~eZMQ2i(s^IHf2PYm(Ur=y$|p*^ zB68P8(?hl$hlyVNtBFP()2pOJANt7<(sSrV}y%OrKNo_H(A=ha0f9hTO50q z-!%+9Gc>FkX<(3yJOVzn5J^7V86>ZL^a%*kK3CI>5a7%{ZGoUey$ z{K00fCaTbT+lssK_;@!s4x|Hg8;;`tp4x)qCl!RlD+LUzxnjEiIi!g*wG~zxr%zYdqktisx zVuuLYe$(^F`9%mfNVw{lz5)RibrCHL)cbtuVdIGT(M|BSvEa;i3F{`cuI%@LiX^oA z-0n$mbe|Xrz@EmZ4KCly4)SXuqY}iw#4u#W5?_9dOf3%SW?rjlPd(6~K3ud+|mS^wTZh1U_SR z@K?r0R1dV%jqtiVAx9O8wO<~%Dh9Sr_p7`_&&_Q7ZZ(rBv>6@${HB7deWtETK3;1; zlu^nJSb}!&S;c#?oUFAgQaE%?b~h4Bz+CH z3`4HVo?OG+8<;^ONaq0qc2sbPsc(HCGQWX4b0K#}5tpT+81;keqyI6mw)g_6t7Hnv zxR`qe4W7#J+TNQ)sS$5C5f%m+taLPkdJFxyebGuv+I47 z35l@ku~pwk%O}k(FMBkv8>o^G@r?3FPdiCZ_NDjj3cg;|j88fRzV?x^Hg;6{HYjUk zr|NuKN7vA?^^>voZ{_wr^#JFgc3zcg2p|p|B-m#igX)*o`)_JL8;=f*J_)*tE^<4r z(Ph(Yl#lUJdoM3JxjNk&xqY}+t(JS|>U}bvDqCawIa^H>1)b=QpXJ&m(O-R5?pLZ8uggpTltx6aEB`XAt zJD+!yGfF%D=L$@_u_Mj}5*=93JenAD;DtDfnbOCj*?p}p$9P27uhA1(-d1r@6$#2Q z6y@xeE?&Lat#JHH5{P-H4Cuk3&s#v zSk@FnBU&p&dk8nei(&k&B;O~&T7*V_(^2outK=t||5RM8b%*l?X9k@^4Fq0SV92F~ z>(a$Yq(ZE<%R$TBW_#D?QJd!DM`pU3{qwql-)Hy8N9xnBLuekW{7&T*cyWSAS=~%M zx9aGV8oyW19hIp&1^otr|DK76*<((`Z^e&4f!ehU3;2gPnONaorOCro^SRG(D#8KjyG=%0=6L1t;ej&T)0AQkln+3 zE%2h~ zTwT)uS~51s>qwZr9^y!CakvBi^^-&s5uaWbX-hrF0yTPb2s1$|#=N+CmVxn4=g&PU zqMJRcIN6^+%>L4)4JN#CS|!333^n|A_F6#nT@fVXZe9J_tYNWN8`((->zYI4^!7`P zbMCf|*&Gz^S*r&XUl0y9D38%Eu)aBjhr7jI#JZvns+_yxB$MlynH=3x?Yv1|tVaMH z_TVKmtnzliGdU?W37;(kxts52)iiCFPgECuaoi>XIQT4xfqxaBGJ<-V3G6N=NHc%V zt_04GSmIsnZTw|!svkgpSn>xdQe##@u!E^G?^-9W!*UNDVR5@_4LkauQNLl)7Ez|8 zund`Vwe84}QR1E?&RKTHQZC}+*zfFKJ_#O;r(!NIt0YG#kg&K9QW5i{QQElh!xVg2 zRI;gm%qV6pShSt>zhMq%Do734KdrXVfPFOLza0bXQ-_Nb=$fN}{&(?9t(mzdCuBc$}rOcYN-!pEgwrZK8>d)evh-lv|H^ zYnPGzYZ9f6LQlYb9Nd8@EZDYb8(L)5>d$*SU#Ke%3Cp2boTjS!IpK#$*1V;!iIlP1AW(YP>;OQDP zF|OS#+v?6%xfCU&iF+d7XAoqOVfP6%;9QgY(Q@%luFN?60-`)@G0ed0G3$4S^7p^R z=cP@R7)z0ch=vuh3ibLu3B80=^*DOZPOZg?BfYvuYV!rYPwiw1zODl!i&8^lCe06P z5Qc%{+d#>siBGW~5f&P53rvKojE({Hi3&^hz0Ob@uMMLXIQI`k_cv!XyN+jqpE3G@ zlX~80;lt5_;)GQJFazdg>-dBFq2j3Qj%qA_yl>Vp4E?R+{Ly|-9%A!%r&93}WlNi+ z+0HGxU)@WakJ3mq`&{{xN2=rIl`SgUYEzb!zMRd}I9=^iFk|@TN0B#)X^=Eavt;k6 z2izh*2z4%nAnGBw0v+`)9`YPFo;zibUOnR8NFn9J$q(*o+^-Jka{a!T3bFhJ=7fzR zr0vgF1?Z4IwZ6oP>-}b5X_t$4T?ac9jy!f0$CLQ7=lKd(&m3Ndz&4Ji>*3&=1g7$8 zzw+XRj2%u-B!fuKE8m8Ug&~;oN(e&t@!6TO5AMexjb8Uf;;v@O0H%XjKjcKnD{Zs> zP)gGD(}w=)MS0mIL!lO(;{zZJ0b$LysE4U-_?L9UTI9Sgq#TpqWb1Z|V^Ofg_u0YV zX?gnSdOt#j^4@Z{?D8CcmVU(9hV*=HRJ8+4gWxlzi*7j+%>2WjtCUCV9*mMGQU5a~fk^FZ>foVozc~;8Xs4+d`W`V; z#aXD^$2p7E?N4xpKkKI&_L?$p*2+o7i%b4;nS>m*Y7O9NS^M8L!2`@FQn30lRe$0cP!(6*S_RS=u2XV>G> zAi2yw$&HBJK-B*JCh`B4qc=aiM_;BJQ!894Q;>VTdQ#GUGEIBHF|-?vlL3KB-E4R* zkcqk6@jbRLAQ46EI|o60y9X*S?0^A60c3Q(jeuMujAgFSU{EpoN>{Kg%yY~&{EKmx zZ47G^JRHCZu4{jZvQf(D>-_nBz!Gw+VoMzV0oIOJu-)%V=nFXflpQeX#WtVLZzTAmZiq2Y5Ge8R*jhB(pSO+Pr5>iV zJz`Hqpo9n4qmD+5vh+XSb!?Lct?CPyF6&4Jn`5NOQKSB8jN4uB&Rsn$x-XSMK+qkY zqhK^0Sr;kfyuQQI!NFM1w!o5Vl}M!lfu z*xY^I!)?lV#?PWQ6y0vWfn2LLl}Q}syhOclAP&%PBN1t)L)LYe<%!r<&{%;{)Bn1&G%eLUQeVj) zcn}Kwec3o$Y3|K8RMBC770`p1{Z-ePz2eFDv;-7z7b`B)v%_mHv%5n8RJT4)dY<1A zEq3>4t)BJRbjH>GFhoyf-Q2{qzooXH#V0fQ=k!C8RogvgjFyR?W%!R_#?PHlDhABn zCCEb2LANWqvlq_+F;?1aJ#tmjt=c1hN9ryM)E*j+KnaF$aN9i1=EV!N_fx7|r-0_D z63kTii;AN4ANcE*Mgkz;O=eK4SDyAXps>+9^m5=)?;yVwG6EzW0u7!k{sB>Cy*O`f z^FAM8^o6$4<6u6*-Zf&nhll5D7A;(P|Hn9tOnzs+TYyj#yzd*C!M-I< zl(++1pE5O--HN-YDX*!oTUSIDVnJit1Kx= ze4Ve$DF;`E&y2`<>9EeY<(K!uw>m%Aj6yGvFIed%ezy$*=JD_(M=B=t3Jlb{(*=qDwPR^GS9T>|3|DELtk&@#BW|I(#RQkB>!7L!}kJX^Qb-p$hFE_6+5p$zmMIu9{F`%j4UcNcGTs`Lt%)cs7yZhBn; z6Gt8L)D4jth7d3^VQm)N9!G7B?BxkMeP`|Q$$dS_r^uIH$Nj%v0{PmVj=fF%YX&R^ z6FmA4I@Cshp1m8+?>&0WBLvHO-OF6Qfid8x@!1^#H=ptUC|g`|;eyN1(WoP2UK!hK>fP zNr(4inFmnG^8x-gTk(mLeyD{1kc%`HL0?SP@p|rKA@niE)zzA70b(Cl0G`9=!Jjt{KA7o#kU=8xw_s!6?s@uI~M>;3jB_wWOdNC304E>R(c0 zH`IpsX_%1%u=0x3Pg^U-Rj@Uw2iYTOH9>@0{iK~p2*tcNdGja$IMX74N(2d2rTjFI z?-8#zSKZu@c~ABjI7fYQhZ(c%HTaSvf|;lsWcVCk^?5)i0T z%g2W0vGV9p+8Xb^3IQ~gXlo5sKqMd7PupyZf)lm_p)Id$nN^;!(I1RshJ3@eRa=WQ ze92MIlCQfwel~^PzBDh-mf;X~Q5}Ga3*;Gv+xyxQ_Hay)WEe4&tMhU&*|Ym5#%$d~ zlCH(wf`RMZCr0q_1;jmQ1gNSNvh@%w2XBO4rlxyymJ?>eWGLT_u3 z+m47`-}OujXYM_67Q#ogF5eDz4i^i14;=T>*9pnGzfK(a9{FfflFK$a73YC{{CSa; zNmj7KdkSa~rAxX8v-{Xa0XN7`_$=zGNJ*rW(7HHuk@?&#aHc*)zof};E2+iS>$4lw zjRBT4_mxpWe)h3MA6}`pm$2;uNKw1~2x1tO!6I<^JQA@RPW4sr@x1{Xi3{hhojRS5 zym$s7nVOpGLG4$|Z4RXLKVhXJ=T+fl+h(d2Ds_IEp6^5LyerlCAKiY#^VeGwgX|dH z+|%mq0LB@;8Q3<^rYZMwGCc(cVu3n7n_-}5@ zsO65(%KnMI;a(WtQ>nM=l-QrSv9*l9dlqxIpzL3SZP$VJeeWVmoKnLzIOgq>KnO$; z8BEchCjS3efCLNofR~=@g;pVwa%^IeBHc3cPG#77_E6=0*|lxcjgyX67!m3Dv9;rK zC`Y50dteXW>EkBjV@#MXj-DWU;Qd1_#o4O4@&J2Ght&VY!_}q$eOJetxt0|t$e0=9 zOWM*na{jj$*hV&8@crTUTWND&8u&BA1ARbJL|YwoNu6U&e_{MIvu5WHhUjP7?IU5d zRQ}NBXtmk_*|8GeixXa6o%jGP(lrt-hBg$vznnoIYw34Wj)tGc zrf$auU+p|k6txK%9K<-JWt-xIaK&TI<5L#SNswd-4UmN!y=ki}$0UvC%J_!&u+W)b zyB%wf%kF!WQTAO#+31?A4$?l`a8vzj4jc zd9jY~Qzecv@1m@qJ72=l&+z03HICk94XtvvH#zMCQdpu~Kc&q}+xFIAdt6n}`t`eh zq1lwXjId1Pi@AeHymBIFqUkNBg!(#^A2y~lO*pQK2 z7RF-nc)W%)QXIjIE7d`6JIks8&DWDQ$Bqp5CU0193o)F4`Fb5??eo6gW?5Fw34B$& z;?ptfI!DpVU^6aRJIr{mU3My1#PgyymbEhKl z@Nkd^Xz<>aCgrTo&(DkbSiSV7Qkru1=1NtZ&WAOi>?iuGN7abmSRVE~ggcy?_NO#* zHX~hw|0rdPiuvNqm(NX$iomhq(Xd=K8Qmm7=+v^KFBD9_N(lj{$}DV!#6{aw+z{$=;{Yc*@CXnU7Q{~kh%DZE4>m& zM*k^{DZ{b(s> zKEP8~JQ=aJ9CJ+%8$oYRO6nRK5hZz=z~{qusUD0hJzCI(tUR)w4Js$Fumy_zne4w` zfB8pk1=|B_qoPZ_avi`R-gOdZaffd?jSuR{BC>E;$P~vGM`0lBHD8RU4v$kOjtbLM zIeb5NpD^&Wpr6!hsO%d>X7HGl7!7&~8)fH)Co*fF}t}ug&pSk+#$0M~kRBe%( z7OssW;^%Zdg)?H2sTMqK`Cst()X*w$D#EWJQ1%#5Oh9*f*|Be;v(KpA}B0Lo$ z`bGiO-b@1qFiXZ27AYQ%_Z=wQowUx$wh*3L467;7q=qVkd?Rt?qc0x~4<d8+BaMaIzqkZBwP6&tD)2si_IxK`!-J0mgs z`53WJu>JUk+E{KiX7+nvYkOR>l1J2R%1cebL(qRZHaLuxAo`xHOOSSA*!D$2?G&hl z@F`8IO!4nS*fQ**u-sU0q3mmH!K5ft6s<%XY93a|<9AxR+(cmw6|LoMv}ebtOA8k} z?_Pf{d_DA*v11}#4YD*r;S-Vj13@W<=6g&VIz}Bg4P^T8x!J}Yi z`&^)Y$=9V4n~ZyBm>jUenLd@!1*;I<#CW@Z2j%^ECWBd;M+<`)oj_dF#|1> zJ>Qp^DDU?fkszto`%Rbn5Dto|aiGsPeu+ufW0z3UOT{nC{QIdKpRoIpr-u`DA5q|S z0GEvpm*F;FDci@LsX1(c*z;^rT<_*oSG^@D*)NP$KepO0HnV6Mx9J#*f<^`X-x-S9 zMAZOG%G(ZZZOQ40^u)+mBoPjaF%&%jm!DF)-Y?8G6r3zZ{%3fy{%r({&|7kl zGQ{D|gBcalM>l(mEAW1)mO@8U>p|>j2wQ{mmKLG0)tc%In30lfO0BOIxjrqav|S27 z*W4UCXIAs|?a#RCfgy>hMF0((w*o)jZfeqr@JW!1S&7k8Cx^;$dolMYNNJ_vMHD_w+x{kDgBdVHO{ zo)z-CaK?^Vbh3?;oIgGrdAgeg!x+A>hgT3+U8E@UqJ}u~2|X&10NDc}Ix;>pm}`WEF0D3C$Y3;k5!Va|L5qR9uPR=5uq%7V_dq5Ri07s z_5JyN3`!ksk=qpUbvKIC=VM`}`4$!4jYbnGmJ*voqE;=kYt2gGfz`l zV51BJ+33dqFMAu2rN2WO-MvX;3?VW#ui)%%mxcb3iF6BbL62DETdjmOJV@s!8l0&I z<@(&&Fq$V|<%_`32ARcp9oxF#uHg);^?_kPjf?hy@;V z6!6qsp^RRtm3H151OI?x1{b}IRx$Y2AjaXCsqLrL=YQSt9tZCG=NjhdERny@VF9w+ zG(0HqZbLZndE|<)@lMQ!0+it2WX__Py5>bymSvP{FZ#Qpw=l!2`wm8j4mb{5f}o~U z^hdw?^10Oo_7Q@IBG0hjRegFmykPwI9z32+>;DpGe8XG4_Gpe9)c&4vf7Ish(c2NCb9Ud?969wA`C%++7!&;1d1@q z4-)(alZ?cj{2mLD36?vnqt50hycIx4Zfs@ypeNaV`~4OMkziWd4kr+;KmnxV69rFp zl!u*jGN_A4q;FgmkfT(?jzaI^Z>&eoI!K*Rl3uQu?c-*5d~b#&_`DDpjaZAnuZ(vp z!z;+)`CPmsUvba~8+I7L)y9R2gCV*ej<9#WKc|?c#5^ws?YT#?jD8Rj8IC1F&40Wv zyl#wdyZZ_{6If$C5q8X-+s_>P9cw+H0C)Alsr1+7L@7r@Txf3HMa*{HDOHj~sg#aSOn&cB%!gR|RG%Z7d z(4zi%$e-jOWT5FzE=(v3*AKB8<&ag{gJevY1X}yclX05unz02MZxx)mW5vTGnRPLc z?3I|cDNr7nG}gF`4i4m2l#P(0PZLL>=UnXV>`gt6N9lUK|I4fd91ZxnPC5NG!`Fpt zm3F*+T=w!UB?jmV{-x~uk+RyPx3zzrKodqgkx(zh96%<}W-D;u8!`0gb zw1*z4n=Qd<;f;|ceQQ_z&z#Sg&zPckbh)avI+Vj`#}=%|7IgHJ3e0@T4Q;j4|KzjF zqf&Hj#(oXL%9zU7LF2Sa_=<}PzLd<=vakEP><)NGOwl!M;4qw6`;6YfEbHb>XzjgI z9Q^_6?}fx`zU&s#`Q6|AoX&N0Im)oChN!@7#*nV`qPLmf)n?Bdj+ph%#e6yCk&eUU zIU~8Rjl2WeYn#uomI+$0rF0-x3_dDnK`T^2eqyT*h zB<4fg>qCTajy~k5YY+DzEPEaOE=HxY5=bvE__Z>R?V?F9B0q3LMsVV#^1Nfk=+;gh=lJD=OnH9qQ>tU1{8eF_Uh9eI6K3>`i|IM+i*(_#rqb03Yjx)iu+%h2ZmQ z%k*;j`C+vN@Unor@RpE>D@E%!8r}Aj`JXdVM6F&YAGTd@pBmf#Kv-Z`>v%hyR-4;Y zuzj9Pa0%)a4zL{k=eBy%EN=ZTxInFVo3aP}ie)L%PJ$D-*htYGk)>~#`lGZfj~K&^ zoJ<0qNsUbM4eUmOJ{a)UB#>D1B28(1QbBFf=8@E@YJelhB4pX>pCGBhRQkInS&|HH zlkJ6=?RTER=<+mkyHD4dMM|Zt4xg*Ho$ZqB9nJ80kUi>Vu77RXu04+@#(5>9Z!;=H zYax7=M2ok;owesm^oMT9Dzj(-k?l#=MVHJZP{68}=+rA^3zrXfKUpv>M%H@S$L7Qu zL0guFk5#dRxdf{|pE8)HoK@_V<-^4R_~RkfoUOXlZY5UUQ2q-m9(eFV2X+l-;Dx^P z(gXjCDc4a9!Z~%n&18;PfxoA&QX}*QX78`y#MVfuw!Nb3OU3Q9FON{5Z)|R_Yr0XF zZ~Bu^&*Y1oeOGw`1!#J~L|0hHX?{Q#O-*Acvp2rgT8&kixCc`#YE_~ z<~!|=gwmPKF)X7rvz;n34wFsvB;x(m@&M&XF2vhV5PIWy_6mz3#hTdMQ$uZF-ZWKm z%jvR#tQkh^^g34D0pJG7Eah%@2Fe>;YpV_4Z+5&}TX;`f?0@I8&o&Ov2c3yA`Hk(t z7fDDfbv{@AWD8cz)DgfPo=u(UuQN){Y!1|3_qp(ZwHl=wqML4(js@$L-R_Y!P4W5N z(8N75K8_Mcn6o?E7iuPj&2%c_O7kO7Nu5PV+Te62X#VFW3@7&GCnLP?BRxC&hGN=wo1(?CmC<-6DO}4aW!iMH#*CqX~9j3o617YxXTFt9_U0?-x5`S;;)f0|C=E3|oBO zMS8zHSmo_6{~)||6wqg^Bm_FdB$Unf_{8bDM3qkCA9@Cu3CZ2bF0r$adog)In1y(V zc|DFp)bI63CXVJ5{6j6`1N>HUq6jONm{@d+RjRRj*LQUPY)XG}EK%w)_;|`Yi4PI{ zv{;RrF`sx+iu4q}@hY^i`C>DAJv{k(4baKLPN@}F{wn%Y zN5vYg3Qo;yp>W^D-0k{jUL^P&SJ%FO+;L^o$IpgCTqvYP?f|!RG^kQ;XRnN*A=-y< z)oM`+R|0{wxPQq=!K!^B`qJGV6%8p@uEP8U!Ak|5t94(O)M_TwwozX;vLcyA7*?gf zxVEyK7&5hBXRSR|M7l?8PB&FA%1X!0HPZ0pbkZ@eIt%+tb7O9%-_+h8^vvGxv2t&t zTbZ2p<~bJSe^rxFiq%EdOOQ1KT>;{d0O)Bt8&L=dAinLCZ6x2!ffD{vDifixH6rb? zJ~q=Bz$(!c9|MZK?#erUjx2F>RvCU|9jCJ&t8n}|jakqT1`c@CMidnTT~Hi^NXwHN zb!4^xmE(Kx(fjr1>!2=GACDJIpmLf?fl=tD!BbT!3EcT7cNMxGHC!_sRwk)$sH#CX z{2L>~4xYo9>0fl{?V1Am^nxJ){cvqKLx|dNen+!+TFnghB+iI9Md$Z83dL=nvAqG9 z1vQxDa+4}k>8gVD;Z;^Yey{9eXnG*^0NfUW5kKF$S$w!hn63q0R%q3ivny8R+a&{$ z&e?dg2u%uS2ZzOYmlF)e=t_6^-R^-XiJ8NH?cEd?QrT+_I`ZyNq$^7 zj3)Qs3^1Zok%IO1YWfn+CB_;wq6Ri2Ic?PLnGLE znpHDXqatb|Csh=_V>VDxyQdKGZrQ=B*{O@t`v_oaT(WIQzt}-)^#Ljd1{{4(bQ}sx zX)369$R6!+nT%H(S3>{+@c)dz9Wx@eAAUP`-kx(&sH$NVGUN6(2Xw|Oxb6rvPRxE0#9+^}BM4QFc3-Tq(AGn5W0C!Fd(xP9{L`9_ z{E#^Zv~6|$)Cu^~dG!A9^)U5Yjq?9}vN@C1o;(7K2#5y`k+dPxoHJ;$;bgXb_1&~f z=-2D^BJdGYuo5tr{DolO~B)wM@Jds z#ZdA)uqf9)vdmV{Xx7PDX3Mm3Q&ZZ{a6R#Bu2&1yx;{jvJwIPbh2H0&x*nDzs;Z}U zN5{?~nPfMrO^N`fAuF5Q;}E@rRE8VHG{_CHv`lFMh@AVkFn;s!M8$o3ipCc7*cBaCteA z6*Y*XkjLF9=q7}^hU}a@`>6Ou0^8L8zL8d^U8KQEW#x-ec;bCE2G;dC6hhBLX})aI zH6$BsgU7@Guh9|h4Qh})pOq+-yJ1B(GtXADOis%yR~r^BJ-UUZPB>XcO8S^v&E-+* zzYeSO0}AVb`K80hSH6e+zJZT{NG(AhZ0wL7EIL1ZK$jKy+2Q1WD4xw2`-K_3S3zd99v{ZtJ-GKvZg9$^Y-a2PbQb+L~ld_*>oH2VKzBa^tmi_(VAw@ zuvxYFIa>^tIYl5G7$XJtti*ONZ~t2buFV5##OZ{3X@IF!&4D+ zkLgM`)?{MiO1ztF9C>-*!c5QQG;fL32%&ijiEcF@XAR!m1!H1ckLd%D)HAa!82eTA zzbLJfXmyW76Gp*vsd9_>tqvZH)h~^ur-vAM9sJjoMti6}&eK~%5;MzNp#;tATYYLj z86-b*K<0=PcL;}-5Nz+0n^&9blzyh?FAE+y4(`D8zC+c_#MgFkI9mryetU+9ihE*#tS8OmUCca9hYN*LT$;DeS~oNBuzh{8UTtOi%Ms&8PR^6n*LOaXh`fqoH*IxV2o;NP7mkGz-i4CBBNw+49`x2eb^JFZ-`0Wqm zMTu&w=NQ?cPlMq)q2z(>&sdZ}?ZuE4{>|8$V3p`sjxI-$Pm&0v>6;@g#p|%r;k2~i z#kPLe9P}sIN7$zSj|Fh&0_sag(usq>(FNHou?4Wv=(^NuXyLLHDK^b+1cnO|&amI0<^md15vD z_rP0aR!?VNYtMt=&f5jj^HGGBPN!y{rcI>!HFnI@^>)G=z9T*+LOw8rXo6Jimk)_n zL>fK{v+95qn00B}GDH{O%p4hy(aSBrrK07eVoM($s)Fd*O2I!>WEy(Z`$07~+7Q8R zP}AlzMaJCCGgYF$^Kt_KC{=zlmFG)`tu%Ih=UaQJQ2XvJ_Vf2I+FR$ez4Z`UBk+Myf4-I zs`uoZWv-s-PZqO+!~|TDW=3_#Xy;?7Ut{^t@!ZdcSQwS#jA)vj(Y3$?Jxq z`QRgln7W0`J1361OV^F7;15$jWA@lz+pb8rPr@nf4BauwyUZAbN1bcXW7fKZFK`UQ zG3j#2E!7L6GlQZr0RmsWvuwK1&CTn#LFxxx>BbE<4?T0GUAYs?l$R3Yra}nyOm4UT zp?`!vN4_qM5bM(Y>n{-Q$urY(HopGh>2Pwgun%33`r?Y95RDh%py#%#cw*QG_KSbu zB5exIxGLrD4-z1NK)mHQ*eZi`!*I$pp5P_-DNi*no>Xgnb}?lCEG3#EIZstqcdl(@ z%Ofj^^RozSShf>Dxmm@5=mF!v{5*fDqT;pI&KpGF%^#tI!aLihI~aM^I0ZFkz)Bai zzY<&_4Qe|NZ33SJi}v@amA~1AhK@IfWN$CG7kBsq3A1IK_3FPu2Ps2mk@Dzov@J&5 zzM{oR$z*W|mhsf6DzxDqe1iASnrENl{d+&(8s2vS>g-(~Z0R|3v(Tk;VxjAW_Zzd1Zkun7|s5sbRl0Gv^TRV?EvfU$q zA=swnn%z5Fd3OHND}ldvO4UxlYz)j}R@x|};%;giv*=(?l9kPSz?4iPnEf(Q#zlr| z02%7Z_D9~29@{}_6xiYmq3Qi%0Nwqw!svRTI&^c%eGi_Eb=Z-Pi3RD=G#ZuHy{pvo zuqWe<<<2t>7Kk?3PD`!>P=Md|RrhmN+Ik1;{EU0UpmLw^mw+77)c6izM!Snp(+pYF z(6)Lw#ii(a+$Zy>1gC+%FpaF%M<%_my#A~256@{Brs!Wtr2o2J47>xvIPNQEd2r;z z%C(k}ZW_uCG(GrK+ll$YAqQX(_4yf!EX|A$Sl}E7Y*a)bOtAOszSPdAnujy|SPkc_ zf@-xAz0jwPZnnXF549Y}sieV(g=XI4`1#>fjZE*!mrl*u1F-$`r203q3JW(&WOyC3 z7~)}{@&fo`0V;Z@0;@>p+2{kR_XQXt6JF2tqyhhHI|d>H1-nfGc6`~0bQ-s3=wQ-A ze9`^;#mi5n0iS@loYkqh`skJDtFeb#8s>`~@cl&AWZ}NZGw_KOwPMzE)&QmP&thCp zLWP$~V9x+o?;GbI%}Osm8Qt;_>w@!Z^RiYEXn9w0e$4nerLMJ6Ne^6C%jn&BhGQDh1c^v_tREqE=cZ7?ei51=<2>?lFWh} zq8%^WXd74wKIA}T(-{3Mny3z|H-r8}ej)gz!cbXLA%+{Lg?rM6j1-JMWW;MT_Wx+Q z3bweGWt$KP?hpv>?(XjH1P|^qxVyW%yAy(Y2u^Sf4ucHtZu91x^X?DWU-$0rvQ?|9 zcEq(g;ZQ0VS?o2ChBdB)N_tkM)-^3E1T2Y!`n7wT2giNWqN@Al@I&C2NA3+v&fiuf zBcHL7Mh!*ycU7fvF(ST)Fq1GMU!Q~L>a5jHix=v?_cS)8)NPWQU+oYGljTTy=VJCg zkZaG^5L5qiIFs*J;Mam>rnggK2S>ygOC0<|vJ`pc9<{0l0xe~gp-s2#pb_kw*$E z}O1hR&Z%RL;h-wADQoOocmM+OK3GD0|Gb0>NhURkP~YfQdq*%)G8`3WWM~ z?s#=A|8PC?hIzVWSyh&(Rw`2&xO!e>G6EsBIRvAV%66gz%a}DJai*gv*9$z2ua*_` zwdf73d(;KxYm^_9YY_#|PEsA$pUGfgg7T84C$x~=#qnc+Jp=gtF~Egc2Y4x)y|XWm z^b^yRxgCx{%ze(8VI6dsZsK&`n>nTsajKZb0c0gokv2>DLc*zvqPVwm-7o_Vw8au6$GF{&_#M*sM z89W4wArud?vmck1x}MlGctCazk6Hu%k-gBJEOuD0t1F#zKCKc%^lJETV%Z8`RU7v? z9w1bMIT`eK`p?M}QHJ}Y`9l!4`QErGv)^+91oRrq!4V>I_2Dw{z&j*UE9iJ}iOcFD z#;C1S&Go1p%`NYBU`eB2e>8uB6cis2%pBa4qZJ|9ReJo&UM{FK`Fw!E$jw}wksZ$OA;nV8zZKwLA|LSa=_=|u1(i*HpWW!xNcm_7(Vb@g!#T^&Lt8;s?d?vIf79r^1(ddD-jfq zLqgSSA%2F8);06>wt2d-Z1BD95q3GV)oulo=$UFmxK+dNRq ztL07At*aDFT3Hi;6-x4QakVPuhi$J7u;s<}iNo~pG!*uUj8Kki@+oDsGoN zQ#C+iOmBWzxT_4Iqa}%SD1A$JSv)6mh@1*$;o8`=upa`r{Q2pmC!l5a!*0Kc?jq^L zJ_(;ru!=OP8p53Lzuz4uep6m%dVPE;cq-%{gp7bQH#xdtHg5^rS1}xQ4W)2Qi8M1y zIsKgQ;9gvfi>Z;+!(842rPTiz`X^&ry=B?+@Rq0V>SUw?K2ROe8a5GrxjUDl z5dr>iF@hoda0iV^vrG~0Xq9x}IibpZ6lo77^sZGT08KJ)gr@x! zubBy81&Gb)bD}N?pxFIoBF1Q~#%W(l`5j}S^dy07W1K3_*+->xD^@9hAWEV&Cci_5 zfP%~iS2e~hUCtihW%4v3|IZ1)p0aUY*0aydA5Zj-?Xz1ds@23Os~%;u1I8PDz79=o z`fRZ-vBr0INNw7%CK>EMRcP_N{D$(_NK#W?qcB06bP9YRaW_S^Ae|L!t9I|Mk&eT6 zU63a~ex@Oz+$)dyF5B-z2W+e=`8mz@_Q>N=(4OKL03v_CWq!{u=zAfmsSB;;3R(R; zDNw3{Yp3!?wJ^JBAUXUGwSn3wlA-kV6f_goe6|(M9Jf$ICMM0!4 zA<9z|YNgR}-h2G6Q9kbalJSqgD6c42+U)p7hDe$@lG&hX_fxby4_l= zKtcqrUEV9^pF1Jk>G&6V_6Vg<7-C{Kzwy7W#OYrM5@S1dJ`i2_Q7ivx(Y3lEi+EV} zx*yx^eg`)_94I@RE%$7J)^oC2?&2L7;>=loegFK+E;o*yJc*q=x|HjfaR;Y)H;nM! z@jZpW7;F|nxRYqEql_TD^0U@bFsf8WHFcDGofqGd_*8_s#cXqz*ug^6tpsmA&agV; zMik4tX1qHs&Fd!DXX+_`i?{!GYzd@hQ%&r$k=*pP$| zt0GaBniwF>u z7_psCO3)4BYs~X|Wzp#;onwOs*aIwdOUkkc)ua}>EBeL+q7s5a3p5&~Tt4?J&+B2# zw?f@_885V8CnMfNChO*M<$xoPl;=}l8V)*3`Hx5@pN8K{oW~&@$=ZwnWtZu4M^c(e zGSDCl4C-4xdYKC*93{G~*X4w(ITuWVf-t;oG34Nwnps&Al~lKVZ1LqzltJLL&JQgX z@5gWKJ+O%?!$J`t7HtH_5y?@WX&4n^Jx3U6wQ=VP<9&W#BZrvL-U<^G$+Vfq_X~90uWlX_8(UJCzm?731@{asrX1Ff1Jju<)#`|g( zCB?c>t)u~sO}n^JQXP0J-15yE6UTiCarSKcBOB6gpnCcF;dcP;ZIDCc)7h}7dDBWa zU0DxPek~Wbu;26Imcje&$QHuWaW8tznl_5}5>GD=CpfJ^RRImIQZL?JE>ddSeZEqf zHx5>ivn%=%g&k!l_HJz@Zntw@sZYHhr?J9|Dd;y2bOju(8bzt+b(#;w`u?AWi5up= z*3o(8DYY8-D688#KLsnL=XiGv$^OqJL{DdaLU91k#3IL1EChY+lf9@G>(9@Hi; zM3-%*ooxlv6>SOm{BXv~>y=}XLD$qEjK+6f7q@;$lPb`{2`t20-1o?yGz91LiG~2f zp_4z;p=;829a~0_&r(R`!$HHpwZNm}G+8=r6ZCssf4~r!)AXGsU&L0LI1m7zl6POO z6DSLBn0WtY&4TacBZeNd+^<3temFiwPb%t;aT_UO>$O*6MKVQ(!n(3W!%Ao;JJ8vW zw#o`&Z*JF~EP%NUiBgTZevc|^m~BcS-~66WTz1-n&F|813XU5VwlP|3v2J^LUv@NQ z-(9g5J(&DC|9d{n6F7b{Rk5eXHf7=OxDe%nEiz8l`h|VvHHAR0lXm z=MXJJP9}$ADlM$n=;Msd6OAAVFwx#pv#rxLUo8CJ62yzD?jU(%u3{NR6Ebn3(G>+Up zk!X@f`lbH0#q|k<4&IZgBqc4|av;n?UPPHJ6qrIMs%gpT6_(C8c5LqJ1K{aqEf!ya!_A*{259PUCu?svcFjP#NDJY4NpCrk2br+OH zQlW*4|E=F-i3N}?2AE4}m~;Orw4#PKZ!wT?!-zs1`h7TzUfeLIVx0%GoZa;-=5lb| z^icmcu=NvFtMooBU_f461J&dHaHS`)V8WI1OmV3ZNb;*?2ON=V*ArXX<`DMFUw->UcN@q{av4n&IQ#&EfW1n1#(#LV zf>eHue!4vR!&pE7WfXnvBc(L~KX1q7ob4Y7ce+Q#b#<3$qt|IBW%cy;Ef9Z8WKa^2 zk2a}7n6Gc={Fi|%$zeh_>pY(6&fYp3^a^6AoT3eC9Uav?cUO`vg+V4DHy%J(&>To<|J6#q__M zV${-N%e6)Ssp~4V8>(l+YK0{_ANU}qiY0SNVdiH{1R}r_v;L4y*WYz&B{b39YWSg| z(?Ro3_Xk>b=+sJj*5Rb#{&R0Z&&Z+ib8_ zHO1hP73tvg_{(PifH_e8GA%K`1;B!2${o4SGnWvXvEeQ;ioGf*>NW$Aay!|V9oX=Z zdV6IdpnP##zi?w7%xn7z?kQu?#KetUjn2_~J?4hUUj^9IW548Kgoo#Q0ncHb>2$Y# zf$ts$26AS5r1p|16x~oZie`ADW;pTN z-LfrQ(M4qpT+uPLQ0fN}v}m!JpTd3(DJf5(Rh>UP9pYU;c>3Klz&TS+BAr_WF2Ohk zQnzT06(bCo%v}7vAN0X|Mq^WVyL#cqxcqX$1cpU#5|x27lWF^D-OWuSXEa}jd1xKq z@m{LTQY@KU4)VQL2wZ=4)N(aFu5$aXX4jK}R9c#%%)K?4ZvpuJ!~K?joia#f!iwME zafs317m0<2>6sPSgh?MWSEAcR_teh2qE?=)Vqxo`m6llb@tDlLy$=wQ6|Btiqjd93 zF`e$jQuG`ZZc^Gl3BT+sZhF7?3BBK*7yl=OV2JO96rsZ^7_l>2C(W*Og%KbDwv7Mt7w+CR>rdgQ4;bCjIS1GiTYfV=PPmu3Zf8Bt)Ze&d0>m-At?h4xP0RTy^E$~w?bl- z_k%!plyHj*|-uB?{(a28+xBb~Y8HjlI&fkv~!SS6WNk72@0 z*FcAQP{3V;@>N&`RhBip&gg9SuPACXl`xWC0s22bo~4w$-5gfqrv3uN`Zl{SR__?A zIpHs%qu!W8ziAVm)Knq-6)kDX_bfX{Y))s{ss%XH?N-Gop4TH>_{~80tr?!69_6RY z-IH$c7)F~G+p7Ig7g7=uh%FQvU0bSdLq%{ogzETi>crx!n{ixZGP${d*HNW*OB$4< zp$Zmnb#w;P-6|a-H31~|<6?2eV~JDYwPzQjPHn5uLQp3qCWxtqbGcVG*Ej+LTN-jN zUJpB0GDAeh8JhYt&|;(gPwKP3fM7or66Bd7hk5pWkf05Ae%Pe@Lr*x!OJh%VbP}1F zWKV!y?Xn>aYk&Y{@N+CBgX*Bbugv9QqoC%?Q@a$m7VJh@-B?vhgTH}(_8m3)cc+>0 zhE-Jg*eq5=yx6?SUhq}CrcCh;1Fr^Wc^OHldhv`pBp*tx{=No2u+Hyc8*7w}K!uso zEJhEZDJ8LjD?7gaw7KU07#S-*MKe3$1{6|@9=!{w@w4phL;;dni7he?z*D#t={yjq z^Y|5uHdHpwWtGoQdR>t3Z=i-M3+WuJ-udWvxhLRrYqk4a5cRC1b9|IFf%GVDWRQrm zrr9?UtMzb=yeY#Zl)GDK8QU7TFunXDh&PVyKv#zmx1w1Mja>P~6M!a0d!CNDc&_3L^yS{ zoZTX@RF|eoDCIq+nlG!UuPz|o7s4DztPxY}Zb}X~aW|j{AEP-V(n!6x~MPo#N8w?4ZR*S%3=CL} z>PhhnbuXQ7<7R(vR93$^Q zAO{R`bG+c4js5ld?o%9hUsqg^V+?@rkj?9Ozk#YH%d{Xs1a(~2>=agmKN54$x+DEG zaU0F8cD-w|F^g#Hbvl1Hj(L)Wdso(6rHzj5_m9tru3ujQ%q8EK1L^z?-3T6_!*u2vp= z&HVDW*TLNg- zFp;TrIW0f}BL^7eRD|;>Vvqz5ybiFEGTMU|$Vb5=VQb#nUkH7$6Mv=kCCQ!)5*xSS z8eZ2*K=vIcsdBzNyfkDH05C#ECl+X~5XY8;gBQp=B z`#y)-^NpJ-D~GO_tt-}mewO7P**10RrRoZ0bo{V;0I`&I0h737mkc`0@Z%6~S%!GU zG6gfjwLl_{9Cd_J#rGD2AFJmxhKz?Tf}|dMl0+v1;v`s->mEnB71naeqIDm27>f+$ zt1R=qH)d<4Ed{TLE(kZ_g;Z_6dTLt?hX#tHPs;knN=71-=;k7~8M7k|@I=Pu_UL!C zL&|_1*GW-tC52F-N_6)f%=3Gv{Zp^>7IF;Da34ex&?Hs2a|fPuO|Kz^{BCD;;J3bM z{vFjaqfTvQ%h(bNwTM|hl5968)1ex*%#h6?out-Qz!gmkFd5@L!yf8$KuZ~l{ z*>#@~z#fktJ0$ASt5xvL^Zkw@j~`v#aYB;Y9V6m1u^x%F+@?cHCs#waQ*1+kTedo) zoD?dtn$vlawi)s+i<$wdZ9NRBTt6!R9-rBBm|AS(cq!5#xYRDLDq3r*HPG`TbJ=D{ ze56Ign~pr7>rOm4(1yl#B;uHua!_l}Jgrz`O~mADI|ANqZ;y9CfTPy{OKeDpINV#= zcqE3#QI%;+$$H0Hj9U5&Np!c4R9Of9u+lEX%?o_U_da=y8rx;=?)ANyya+Fz-#lrA z{Kvz}-sx>rMjzc{5Xv;sj}EM(Nk`MR+>Y6`4Uu+JXnaMJlQy53+*#G#9^o_DlFTTo z_8Br)M1B7dZ9~DKzPAJ3sa)p;eN-bewBm1zAIGoRVht?!y0@YD7K9YTk@{MUV47XW zjpN`Hw*rVfC(7uasfIQDT(%YtOrPJfsj);3AJSu%JytxG%x2tVevQq1Lmps35fIZ4 ztIf7oE9ay2TE5ze_|aU-1Cmnm>oVVUSbit!aO#GU>QTCTe_rirj%jOdxXUhCyENeX zyO-az=wi|!gwGAPKDIEX-RpgJ2Hp*Tt63J>Ovyt{Fm3RFo5}8a^))^Cjs3e_8FiP1 z#W#sQdQN*4Q!eo1)XQZFL4j6FM#x1?gu!*VF@f%q!jf$Wj#vG9`%=;T*X%FIro@d~ z)eJFa>ZKXa@j#o=7M`g{0 z>g#0>OGSU2EHpRR7kRWgyN^&iDk_+x)nxLc%`QDLY+Awc`R7FsJnQgG1?9Q-Qx~jD zibwM_`NqDD@nAn(oD@$k@uAv(~>|NMN|QTqY`697CjF9&UbbhWkjkEcyt zB=DQsF&W{;0+Fk?S=4y!pn_w@fP4Q%OAF?|>aZJ>EeCs7%-u;(SzpI6O*Bm@g*XO3 zbIrds-ZQevp_wjlaj$|qr_C?aSzDLc0c#_U@)sU1({4HIW@vCc&U;h7gQDD zlxVS35?mdxPZzEIFWQ)Xvg82`BFj5?W^_+oVz)=-Q#Le!NV_*lNn2142InOeg{nyx zb>~LsFO!EA^#S#<2(9>#+ouz&8a`UcIO~9tQ{txZbrpX)EgvXT-_}-5R$Fk1EP4XO zO|~Q6pP#SHp!RLVjc^=<^pxR}pJujY6rCsL@htB)9Yn;y**zbTGt zXSA^-+Ba6@%O)#MD&~3aAXmUEl_{jvrUs$@HqDKF>2bUR{>^(s^`^iPL2drUE3F!c zDc$9gGf$1s`xK$<^pqs4hB}2Ed6AaX0$mi8SQ~TrHT+71$d#Rt>1OkG*!_<>kybJO zYQmyEUO1pq6^gXeK$lVOfF{VD2tuZZ_{=NfvgkPBc7<;aV27{}v-$X!$AlNbxR<6? z06R3tY$oAs zi-+H?PBfoi4`(P}hgs%uoFvMj6d_~XYX8xQtQnr7D1pQs5W^^OSV#a5>*+1{cv+&? z%ukq$BbF$ru*4Y~5)(;o&tPabDR>7oJolI;a7DkU_+t1Z2RxuM?F7WjvEXd{#Zv0G z6*(+|gW*JAa}G@m*-K>J*rqoU`g7s;CXm7?ec+tiiq6<>^m9RN4Wbeu>oLe=(P)@g zK=DB3muc<~pq{H)iFA{t)>$AAc(PPeSi3kM49Xj=$a6jjcJa**U$NJ9SQgXnL{oc}Dwg2Jk5 zO#MMnL^af5r^BEAa(0A1(qpgaQ}%bRBx7G8JS_BrB}=BhR4%35m72Hnq!6aL%udfT zA;3ETUiOF~NbXd}FLlLH5PVNB7jL7wsZso^FjDLiYlF*W9tL9M&FckL3y%Vv{#@5E?SzfZ;Y7~?{)l!y>qQEd&=4-Az*y74?;&amsZPJtK+?SHR6bHZ-02;oF^ zHm&SsjpRVwid4GhuBm*U!>Q%tx+hR?su^ZeV@5ri1YB|C;QK+2*QvCIo1T*U-E+jo zlzYOwyHTHk`OD;maB2+i*XqL6keRZ>)}rKG@C9(iN%e%6a&fL@2fWKe_UO~NCU~Xl%f~v;_x^g4NPdgvMw3q>_jlW5 z(}Q?M$h>RBdVR!e{>6jHS1zcKlmySIh-8*aSzu`!)juFedWugCzR{=&g_c)4r(4;2 z)tJ`szrB5iMstfas~r6PccbhV|HY=50TdG#_W@dJX8f8zXX}XQ)g2HRB<`fZ(0$vf zOl^5@(&qy#T(%$z=wx--flnexLMcY!h~qqz78u(2OAtooDlV2tve>QvDmBHY^kZ~hSpLb_yCi-0O= z&cWn<@$FLl;d9J4m?21ir3VSqgSmO1#$`dL8Odyj5)n^c!YT_P>M4i*>wD`fTwi?( z8!rcKAgfv>Iwx5we;Dk#vyVf(|ECX-JSG)H4oeVO%tS2{se@NsiC9Ks{PFOY5BGoa zc$xZ^7g8u1B+|CL3+j8!%xU9;{>V5VxsS#J+HEz8I|FpnSW{NO)|_OPQ>PjjUcINbTM*1*%kpc6{sPQtDrEWkG>gU6U10{cUg#?SFUA z*(v#D7b7%Nv>p4!5^h>A+&zJN!eUX^b1PxRqUOV^3I2kAF{n z*9UI!bCM4+fMKyk6v~jjv}PgMy!Sax+Uvk(Tml)xei(*0BP5XA{H-hH9sINY%fGjW zw7c!pS63Q&ucGD@@4oZ2J&)4&e1{NbM8gi5z@bht$gnhtXG=a=hJ3nNC*Z+vx9q=h z|1J%!+Gf&6Oeyswr^njQ35^z2{*suvZb*_nXe;HzGij}m?_Dh9b#PTSRcG7Pv1Jff z7$ZNoG_iZ$ya`RNqa)B( z2~FAyc{{`opFZ@Mz6H-264_D~?$JoD9Pqkf-Z1wbBp(bqoO@^sX4L|Kr-P>{%vP0q z&3-x_OtV>h&}7)dL`5jU;y2j!`uPWCICu(6h}||T7wm>&FeoiF9slY>X`~@^xZ7xm zT;Dw|u6g`ggya=D!V*(;wujmyY;bcda#8p&M7%{%=pv~&EDQWdZFBj$a=JD&Vf{?b zv$T`#+)>PFhCF#D|7zvf!8oKqi~0yEF)et$wIEJRQ<>G{#88>=N!if{=V|_@O*l=W zjD9uFF+R@bEI;1C(o=hzEY5Y0tpjIMlgc+2=926Bk;^y~9@3;UY{*B{uBol)x z0(+nbb&GU9c-~iig*11{_Acf@C~PawsBjRy9sK8}k#>I+!gZ9c>ma}GX>)h9C8JRs zojmgBuW;IcSLI6V#$i{+QP+ySy6#W3P9N${Z5O!O=90a+Un>A@14JzeXZ}liUXiJ& z1jX<|)`|*50Y{tw>sGxBuVsSUTCZz;zvJJFH0nem9Ifu(j6#G=f^n;Weu25SkeXV6 zIxlGRV92aL#_w4t=Yf?Z$7t7X8`hK`!HwxCxeu<^HDU=C4-GfQ=4_JG#+s8Xj}$YFRdL zo{>COPcR&#nik@6Djfe*!z2VmJ&^nQu#-FBqpXS*igtL{$q_yFKW(-RoIkx)9v5Au()HQr>CcJ2lyu7dq4?_ql^nX{X`C6 zO;O=VRj^p;&U>gY+;jwHw{05=EL*dZW$oP1oJ5cZgZ4y%)LH-E7LZF9Ii2ZFxhGyi4p82R##!&zNm%v6d zb9V6hNL_GszxI$=8efXWk&YaFJ082l7Tinhvd+EzUHW_5u&Q?h?B_Z*rBVeqW+$Q1 z!B>}tu2Rkx{H}Fu*q$YQ0iy!Xy(=1NWxFF1O};KI?88&jxM;s2(Jm2g{-sI^JQWd* zNPAvEQuJt!nR%k3<=sJrf7r^u8awc+vH6vJKq@s`rs3M)-}c0J z3&?Bdr#$OhS$1shd-L3J7_=D1qfluNL_{Gj_We#8pO3XyPoi41H0JfL7DErhwE4<| zE-~l1B%}F6Dw1UkM45{I@vqhy1{J6MaQ>jruwRp`E(ndD&NrU0iSYWk3?m7Z<$8JS zl>H$;*rLj#jE7Og!wA-<=Ke;C%zDkO`@STl8~h&Me2#MA3KEY8HhH_8k7eZ6a05qJHC#|w_Ud!!IkWHR6<{0R zR<&>!V(De#s|!1%V}4CEUzTlkPu!Zd9{6_jO|4JS{O7RJ^I0W)5%=15P9@N6ci{#R z_kwBL&RwrIN7luvYRioAWzm0+3dZ7}1e=`-hr4KxpU9XVM7y*|*`ANfcd177?n0Q(8&KB9*xX?3t;#ND(|I!YD&W`I`~zPOQy&IXG{1{KN*HXGjes!V-ik}|!+ z;lCA23pP>SX!6xHy!U84w4HljPzx@vM*esfNX8M>5x9Qa)gOeq?+HK+2h1e(yY1=M z%1&ojOf6GQ3AE0iEx9j9_rY!9#IXqN0Zi))$PI(DTZzP5gQ6R8Vz`3sG z@^kj9XPj{0N2gquoBXqGIDjJQJ%c2uaE6g!+2QW7KS9X6B_+vR@z<^<!wUw)OSP z-`t;Yos9usb=T>bB?~yHbJ#PC){i(I^4w-pq-r=HZt1IslpNeoPwyxI8dQ`YyG0am zg7i(zQ>m#_AY9vLjuA^iPNNSnQ*{RIJQHqD|C%FQ%|Rt6nJqhQG-*;d2WEIk#gx7O z)s%rRo9yCZIgOY#H7!}R%)&*~+TWa?{Sl3pH6)A1%SNln@zG!w_i-6-N{tP^`&)Vl z-_G3`hu=HQA}+-;M;#~sHa)@4?h7)UT+v4m8x4kc9_1HhIAx=(;xu@*Js!+J2>b~# zKcnc8iVM|-6Xu96cY8&{)CK$huwzQbICz9_`3Sv5`oJA6Wt_1Q5?|d6ILn!YvN)A; zH^v0d4zX8rd!CCQ?Z)+KOW7CdkKV!F-_|XPt7v@B@iHR-LzYc%FT>?}ReF#>js?crH zHd-I+Im`SfJGj(nX{0#kO<#Zebr@7c!s!W zR@D?URY`OUgd(D(vh!SF(0fz&aCCtAs2ffpKHYF^F8P%QbWn&tJiuymMb8x2QMzJ7 z=yRZnUU0gw={_N)fekCb~wH)(L+qLOQT zN?dJw@NpUqbdmxpkU1GqI$bdIn3z|l{=&ReuB4xs7)*D}<+GpQ1LH`~zTgn+#(f{) zw3$+^qB0myLZfuywEvqUX8x-iEn1vh8L(exGW9<;)N}<#68|?S-XBnZ>Ix!f3M>`1;~V|6!Us=7pQS4DB$4nE1>K&@Y$IlA8xFIKYAL`X_!!wip=D8 z=y)EC!4OgX5ik{Y{JCF!$>=?)vlmj?91y*_cZ7G#&QLYO$vzVLL>Vno(IAx<;b~F z?m+@BDk+&Z$tW0js7WJAH}nj4G^Gf<4>IWBsmp2XPVWC`Isf-<4?GHlBGIZ1(o>8( z^Y8RLq!GW~xbcO3Do6qG!XpzLwMW?EN@MgtExo2Nca?6gN1dI;W)!rRO<$%cYPzR-&^qap!RG4v4)T}U@yiUcf z2M`(=#01ZZ(@L(VE#_ap5pNvuZ}r7f-E6a87f0BLP>`e#+2ERzS25?-E9Q1}W3H{D z!}jp{+znCKw-DqP!Whe=Jo8Ye)Sdcp4|hD-JN%$&KRb)R@v9P3A;sF}aIz8Fj~cOQ z#v`rns<~}q&Fwh+S78y5YM5%-pr;zjJY7O&oxKNu*$Xz$fi}okXl^{{sVLQgT$Uk( z6w>b`af^RZ@XbYH#U##sPWRuXlPv1AT<*u?Z^gIVqq}Z&dU#!)yrg?XAyznj>(vKq z*y}3pSwK2Kq>FP3G9TkMs_km5IqfVSv2C>Y z_Yg>>HDBVT%(rL@qd*7A*Tj83d2O3UQuEfAyh^{4w&5aAijP${YA0P)2)54Fs@7b( zQ7RtS-1BBPfbW2_v#YHs0Qwl-JSOQ z+&~QwTjEpX@BvS_aV!k7j6Lu+tXZh9eZ$^|nff_O4JCP8K^XpWpw#n=v+63wc~ ziD74a7jZ#7!U_WhIBq%k61U|^{-3#s2s8%i2kGyuZa{`wYF|KFZv=VU1~L4(q@$?j znG}T*WuJjYw7?zqbmBS2j7c!VtRUKWZ*2{VRQ^YlC|7QCX$qlCW?f8gut*be=yL_x z|6~pE-3^jw54*rK`nYAwHqA_{h}*3eS*ApJ7#`MIC&AA~4S*((>ue4?>vnc(6L ztJda865{}S;ebJOCt2HiF2KQ@`%97IA5jn4o|W95*JKikb9)v_Cz=Uy!f&yGwIZP*{_sAj>HdId{5=vZRoyC+`b8ws{1oEqPt*o zwH^gLizcfqZ?unucCS1N{Lfj$g>YJcG#@BNW(Ta1^sx}~NPL|uM!?t9;3#6S34DQ4 z3I(s1Dv1&toFEpZs3w~I{Kc4iC2rfBV79SnU;i|VO>Nh#i%-}4Gz6j}#uchZ<;yP- zq6eKn(6*AHaqCC3J?2qwTTSWvE>Xrsok0X7J;czK(Qq`|#d5bY5(;~5Wn27*ktW>8 z!1lM(gk3h@##r10It-|r8@DgYoR!PIPuVt{-=0C5-8%w2WRCxOG>sRkLE9!E@~Hbb zt2nEZJ!k?C?X*7d2<7|Jqz^RJB~Zi#16H%urny$X7B05F7a;MsKCe&sHT0@+{-RG; z${8qgJTkSU8-wFrvn=fW_spK2VkNWV76z0v**}A1ig|{PW%BMVBA$~nV#y;^kVLhp zIQ(Bwi&g@K#eCJJo&ng& zR8u|@X=uOkq&Ijv?Ut8_`x;mb-78K-0Av)hHM6;1z^5Eww~c4zLV84eA@#~cW3Agk z@O*X#9V74O2V(b>VWyvLDPC#GLfyah7RyoJq!6;>egVg5*)B-1WENox!EUJGw*TFM zc4V>lD8$C#b~svLe7g!ZF~2s>2p&3-4vUtBohVsdu6?@Yjf@{*-TDf6}s! z9mki;<N)iK0dq-Op;C#=~ylM5&y zt1rkdBdu6OV93uUktZ<3I%+$nZ(`%v3@>>&s+3- zFAB~twF7R@1+#uHBP{IyBC+XxKXGWncYn5c;FXKCWxf}-VJP_~93FxGbxqdYB~7z$Rd4tIz_E}xva1~uoH7Hm?zgFv=U zkt)7))a1(!dbu-h1o7~!1vn2gT84(+RS;-ogTfBf;dBvYMx{~TSWq4I6L_|!5W?uzIYAt!BXO`8c|KhJ5@^H#jrRkd^x19v= zUdC&{mKU+Ll6t8i2{mHAM$MOI>69ye!|+DlQaKN8QOQbWBk$oS-Cg6>I~5cN5?+qa zj#Cg#%UzGRc!@@A(KJk2uDgM!bSsk*8ulcM!5sNnq9ldMkJp300wOeI4;e^=NGQNe zWK-%6@T)eIC_h&9dM6>Iex~lSZL4?8*^qhx>n?hpuNgZ7M_i{`I^_+Ak@No3%Xuyu zHS)Tl-W^Q5Jvld6G1ujMKyq*AE7T z(0l4~0R$!EaYzdQ=)Y{R>i6MR>K?xOj{CILHzdxhJ!-yCN>|1t$hBL01{Q~^F<7?i zMWCS}d{eL9+ZEq`KQ4P2W_g9C&tpe{Qhii>0r9~}cB2dic}u_o)kM|KYkvhicA-8R zfgdy0^<=FlmF7_B&18e!p))*}+y{BrPkRF!uSkd}#@n|3CV*rXzQ?@B&gW9tPAyVR z>7Um5zMgBfK~CS>zdlA{q|_#u)ZGk}7W88S zQT>H#Z(9Iu9NQF1DYP5T4B6P7joJl94DZ6|15fpm-))}j2_%y{+>U#n$^G1UzoIhf zGrJNS|6}mQl(!7VL~GVb6H+TeL=!DSGUBED3q6>;vIK6Qp(==Mkar{Eu=HeAbkG2S z2YqnpN#`R7<^q~}FaqC@1umqM{Oc<2{+mp*uLJLi_OL<6cQkN*35^=ll$2$_Mq_fn z%JMtl@a5BY7CfnD7kz!2l)39!0w*3ftIAqZV_J{yr<$(U6{ELMRENFCXXv8kk^{my zMzm0UH6YabRBF<_JkrbV+TP1*>08I_O{=N)IjkjM;bk>XXmwXpVaxHwe+GBh&v5-h zT~u`wBoT$unPOr33~Z7wcpCtBEuJ}Uh~j|oAhE1#uNLYe!o!#N*~{Yz|HqjJ@)D}4 z5Z78M4nf>k#bQ=?7|c36_IWpqi)Uv2m({x@I!Mw6Kw_>@%a$C0LmEN$I~8D3_2o;A z@RG3)9T1f`Zo|oLFZ6auV+@}B7MyczQM~06pUyysn zh+LO^Mb7*Y_8ts0vek@#;VN&$Lq3J9%zP>X@`MshIhDHZWUQ1yA?MK?Eg})ERQL@U_VgJaDu!e3xfoY6Ybv?*3I|A-3#Lp>(>!{sZeQ zTnQhna~Vrxw$T(tPDDs(_F&qfwW5Oqo_7r1LBh|ZecnD%wf=Y+Eu)mwGfZtbpah$E z78GIFjdyN3V0HVWZV%VpVM?Tn)n7HbiF86prxU*P)-TM^Lk$I7&p}gh;ay{!-ov7O zB!8O3sx`(UZDKdENXvaECz>LgSH5 zNS*4T&Ywsrps}yTj`m>k1ZEhaJTlU3xe@BOj-74}*yKGz%hurKwbdL}u#Q+y;p8R;jz?c0 z$+ai$Cm?$Ghq{C6t+-r3k(4U!3C;^uy2v5fZDxM0`gCbQNBjLNOT;=$?3IxpzxA;Cej-Lq<(gjD#^Wff7Kg0b;&bavmR7jnrcR?q>rxmo z$7Hji_a&5!I+Q4nsnSz42KU@kxsH+pFFxw~^n_XNYdbbIHWja{NpfQhWBg>vM3cbd zf=V6#T*r?8-{%!8Hzu1!2z$@y=1Qtc|8nY5W58soN1(5Ryj(XM+|b;4UTUD_fF3Q&P&D{(?VoPxYuXYR7Ez zAip_B?eu$@wHJD~%S;fma(!1!U-wMYGPs=kDwY^a;tu&B(xDil^3Xb!ViG0cMx~#8 zDpzxGtRU0flpb4q#!Z_iz2q!RFL7Qag;f%XQC(1g7X1n}=JFGwI*ISe7N9_a#Xa&- zCa-|tIu95=)+NZ#KUj#eCh|7RYP2MnpU;z;OfnbSgNmY;Q6?x|(L40AS|cV0dcyLI zJ%F`l`Cno-F=K)9pZFw_%46Bo;q*jXPZA?yQR^OGJi#DAQ2+KOm2DoW44k4eBh3in z!`SNJ-~`5_Z5VB=_@rnnIDxN}8}nM@OeL&|wKsz+K3}{0ew^6BTvyj+W{}c)iO*lu zuUH-St6B``&zn7o8Zl2?hRjC)?Vn-0s>%%h`&BEj;Wq$!6aWWh(A(UnQZcy=^kN6a zhGZ%X>D5i6npKVuggp)V#km0Fl#(`}p@&Rxl~pXuzMrM8>H>o>9J~EPP=y`t`=sSI zx=P(T5H}6}E(+|yBw>9&=Se4_YimGR*Bm=7GLQb~P}9_YuOX31+6gJE41#_`f9jbh zk{fbXlp`*RUFD7V74OG^I+k@mkUsoLS&7KSp$5)@>G)$0xQ?~U7llJbb;dBkH!Et77$Y6b9e|v@(I?eg{IB@-pUC9 zd6Sv;n{GwlEO}i1D>k{mZpyfqdRJD@=?qld7z25Vvj(=Ry)0YmT~8y~G2)z6O%F=f z%NRPA*8IR3psWb*dM;o@YNUkUY}Y9ka=*16L?pB2H&Ow97#(l$K&f$mqs7JMNxCCI z590VnMXjo*ZOi!=iK(AuC?*n3EK({_LR>reSG+xH6SSf!&}-zxP4LoV=c+r{Q`r7K z#?#?;t4?@-(^L3xD2|ES@liPwW*I8WDtWI#L?#{0@L_RN{Tm`H#+B@LzA{{Hl6$Ec z-!FCH$h0b$FPcm~D1x5A1-=i}Q^%F(+=JcaXqvryTzgH)#UF_US%VMyVd}_kKK~|B zeY=3W3T%OKKfW&WdRGtaClKw=75@-;vies6E8U%zOc77PxIPR!n3@FIO>^~6VvP5} zvDz;nDKU?LM!=vcDfq!7)LHW}n%~_Y__2gh)vetSg_UZa=%-8xt!$AQIRwkVe~r9P zqQZB2XyVbueXN=<1a5*eq9sbiRLAiLI_T^EU_qwNX z0u^+MhuNGnn8G=gpS^P{N8SD~fJCiS_Ftbz2YCv?9nsd;DQ}75KWX7BLyus~>&e~& zo?8Cx>F%UV^+kFOs2vBJW(wwp3jpzYqnE&OcibHdR zH=LgoB50rL>*5S-MkC(?A}7|A<7%a@Mf|*?Pb$Z{*~s^Xx}SX|lPi)}u4Qu6lC_}f z7eq8e+4DVKkBJQqe1+RYMt^V|<7ar#>Br0%x+c0_MVcelT%TlcbqRWRpmJp+IxF=q z3Q6EfRPL6)-z@$68c0)XrCQ6N&{?4XIMLppx%GQG#W{PXdc1g_5oYx*aDZZAi7!s{5V665@0SZvW>}XbsK@=P&#>OUw3{C_WTs0?eBtujTG~`I1SR$*aN@|!0w_~(utI%v|n)mjL$YBmO z<4Y8B;yX7~?nv=}RoJk5XIZ2DaIYb1CN8Lcx(c`>Grr@05(itlb)?Tf|6~@8t)_D{ z2*+?IX(!WG+{!4uNMi^~?!;Ms+4;w4;B6U&Np10RripUjTt~2fZkTy^kQA$-!BQ+G zwNQz;=q2{!J)Z~dA%;pdOF3(Qt{G(;JZla*9Bw^%!8BtOy)$QULwA+I#S7P!e`RU% zL6Vc-Gs|9)T6DmO8UPusgb`A&HDvBHo5hI_k5A>E&pVj^fXJaKF<8!Z*j<8`6gmKj?ry_mbk*GI{G*hYHfX{&q zRS)GnJ7GU3o=6nILZk3mkd8ev?$5EJ&qly&81*?uUD@q7Xm1lt>Dva@A;}B;?zU;x z5|C0Jpbup@8n22``Shif*|fad9icXYlfHu69!)G{U2uBxo|4$lEiBy-l!KFJeG?}v zFp&?uMeKQiZzM37(9Y0os1|}DZ+r|`zU?v1imb+JPW4hFx@txu{fTD~I&XP>KP}ud z_8jPhQdefh$U)n#1kg}nOmY#zI{{*)YCVdg$@}?THCGcY&eFk0Ntl3*fp!g~giNd) zwv${(1~0015s1`a1KCzbSu$?tye-1N zGe}b$ls^^)cZ&Q`WDqrp*XU>Ew@roj54q!2dYXo)i2NRkq#J`JNZ)S%3)4m_wACVYMbc|&l`ECc zPmrJFkrgl1D^*R!-&un^RD?x5H<5SnPx*+$09t^Go`%uvq~b8VVqnKVYMi>8b8d&5 zxtM#ud!@YHgdVP8*Xe^MJl?O8B*n-?5_k{l4-iF|`Eq$IcBegb0Tzl2Zj*la#D+Jg zWU@fATN3xAL9gwH@?jngNpGzaQaZQ6S*Eyg?&X(=>wpRRx~<>CP&&ARoe4Id=o6KZ zyvOJ%kSci88AY8Q$*co_WXO`&AnM7v+v!Cc&V*VB@>B2k#|dy3`jyraN$ zK5*AKe%gFun&ylnyi1cTK&4TGlMHtH?!={S*H@Q&6?P;298%t%a z?kAOq?@}axj>49=JNrF*o{8#uJP2LOMzR@$>EAKMW#dCDFLp=n`7A;+Zx9v}G2tVD zHdfR`uoZ8kKdsFRiqY_B8sm;O?hUb3EU~F#24YzsQ?8@K`UfYXjuR0(K%E0O3_pOf z+|QuBzP_WA>mR<_Ij-ta&6|7;5@fe8TE4W-n=< z)8&}-FYk^zJzpo!ab90GzIa~Ij{JzkysMT=>+I?9&9ejwJnjOie@nq^@~gl~ksM5gBF5&{@ie z54Z%`ztCsGzxf3nT=r`^hfBuoU) z_=g{V2u>6{h>gUeEPNaAL6R0JpFa>)qQKx6 zLo%7Fa%$w^$3=jlr*(Gga<*(gxMbH4cppOE^0_C_tb0P>N;oBBn!eG&(1UN1qe6j< zI{%*cmUG!hP`DmVdR_?wCG=8`TA+e9K9DrcICtgVZmGZZazaGx?-BDX+%=u=XP*SV zTQ2juGV3eMj1A{g!2}=NH~)_A>U?O_<4&% zH+xD1$KCt>g`Fu|-|t|5%Y2T+#xc>krckIEP4#2CqQCUzp9ERGz z_PJ6(m0(-X!eK6|!(H|=^ZnX+YrC&fF=+;KK^7^~Pd8Qp*}uLbuwTQYh5qzTHV|Tt z<6piTl?3wUOrUic_mS613`!4r(f0BB0YunnWOKY0bliOZWjiw8_jR_uEHX3Q9&Zcu zZGS9&^}GG5fQ?g7N(m_ZBJDBD(^~FP1l1?o#5|>WTuhCD+D8%H_n9nQWv^6W_$yA` z7w|i=@a}PM&t#2n!K#3^YnsFKh)cg8sw1VWw)ZZlB0-jTx$G=eo)(IfZf*uqaUF^r z2DbvWYky+SVrO>cQ7@Y%D)GC&)s2 zfY=1~tfj}1P>bz#?NNewr&g_*ML+*&=F*~V?a`srXWkmI6DWq#;@{F22rQq z8HYSy2BaBZF7f2;2ivwIcWqUfdmW1>83t(R5E1H8y1qvi6~PR&Llor9Qd!c=XRIbo z56O<1pW9r3eAj_p6WqP-zj{IEm|bpkruC;I5+j6>-hQi;)2>Co^*8b&sgmXf++_Sf z{!R}*;pVc#IC@=kyS~C*LrS1as@7|6Yo@6nFP|k*7n+)owx4@ZyWDu=mFkaE#>#`n zz3df0nf>!iqfKQyO3t><9j7%E>e^s@@jN;5x7dT;e zw}@C0<`R+<`Bi&3JYX|(X&V2vR)s=w1#7uQh@9)Vcl+>IZeYdh}{B&^>Va#iR z;wF)iU+122+krc;?}1(k&(j;l3sj8}U%IT-d5fy4ax4FMAjzw^sw;%>m?FO=Sp)CC-MCGB+RUkp=>1p(DD8 zV3AL!Msq_a1t#iRZg5ye-p%Rob&+kf1*9ZQd%iB^=k0-C9I3tEw5G(Wv;lJJ zE5?G~bNo=EtXT$?Wn=L@J3np?*SkI%JN+II6Q}2M++ps~IzrY*t1&{Ml_}b6_|eoi zcE5MLKAGya6Y1K6pL~G85sLtDE)RIK7i@z#_j4gtLW1@v57b~&e7HL$rJFGZxgR2w z%mxchbpNyO|8nMvaqq6! zY!a#La}ox_j&lZ0B$AI_~BSdupm zv~)GegKsAqMjD#w2qg6&)&bEcH{jaVQ=ot0R6t5M@^B-0897>X-%yPrQ{!wR)gXcH-@0N6;NCXg{C$XeB9F zipU-4I$McF91_EY;UN(AS$K={s#SQlPgS z!=1-hfZwCQo(s{YT{ZrQ-iiLe2>0TX&0+* z1B7$nHPJFZUVp8eoD^-H3?%E}x1u&1p`;e_bmV@Q=CxlL^s#dXc3X7s=Ref1wk7GO zH?t--oM8%L6Oh4L2&xg9Ph)anv&us}7<>J9DijTW!vcF2vv|%CxmXO*iILI7g61O( zN~v}J+RZNzEmz8bk1_2jeI2UAc`L>p4su+X)a_U^pw6kvcit=M0z95&?5pyhDGCVA z`DU^9zf2F=oVmRE5_ zT5(RQ2|v$0R%obS6xfTrQgM5%(E`X{{C=)%rfc2+{FC|QI9I<^m0;()clPLC ze+yc+<>xZ3lGh7_zerbjG)&>@yt+kw?94`P+#D!E?fkueYCNCU8|#VXUVt`vwqJLJ=7HP7a6DP&Ep`v|o%(_8Vp!E1MNx3tWM2Afgx_RVB8 z8Jf)ylG}Jhchn?yn3u=h2N_#n`)i{pkDDTJP#OqO#vj%~#!Xax9N=(!y>16g*Sw>Q z;?Oh*WR>=5x;Ix5jwz$EZ|IE%M4fnR^=qNNMhsq{&^->e)+-GQt~}KgAtDiYFQ)bEv%o@yf5SM{d{)$@^jw51it6z?aEDaYGu%lsEtI1&==B8N=5Qw z{%RfohlmA1v9Yy~;62jXd0(I4{|gCXO+%In(7>0&7N1i<6r~4qy%;?VI6HFQ40Voo z-JOqrJfwbQc(xeLib!G*m!R=AU#vx<>fQUnOBD7Qm9Ld*;_1S+#q^^){c=(9GLS;g z6!oc>c=b%M8}n(~pcuGHkSbNo`hI~^rPUf?P>qc2mOeRwQ&Xl`F`g3^;va?4DY*$) z47u#M$D80vC-Mm>UT05|5$IW2`P5u@V@l)e0}oV z-c6ZanvwdtY?agwS1GHVmg=>UlG^`Vl5}(X}LvE!I+9={vz=xJh2F4AwTC5oyY*jh{*0q$9#&q7 zs{(~ba<%h;#1hZPLK?E0CIR13^q?s}denUha#kKexFfbC1Cm5KmV8lAPs>pXykL@7 zFd)%#foGwvSH@hR8{!? zfCK}X`m0d%nD$@%h0^tj!=K`2X{m}kHP6d0H!0jN{dE&}+u(J%k5yvgqXSm+dRkln zwhP72C)XeJO5WRRDLq)3H;GMS1GO`C{00iC2>qg{sW$}!XfH;x(*&()P{jT%6VKpB zq+aiR6z*%TnGf1%z@SzfAFO!Ap1bz&e+fA2mGairZHO&{RHx7395t%0gE+LHBT)dI zBN3kkHd@8Fgh0+CUhQZw)I7I~u02|->X8w9bt}j~Nk)Fn6Jb5u69BDoj)ty`KVeiQ z(QUpkd-E%c`a z+|-DcnY(#->(;5$<(OsH`xGbW%F*zk%Fxc)O~u+w>~|`FNR0BQ4G;@SOzp&j?S;nO z+Bb~!SGl*z>VVAzAIAg>lvKh`r{0NnpYi*#DmlocyL29ZhfKu`NnL>UQru7vJR+d- zws!3#FK%+Bczapbo9d}D4j*U1XNApoP_ZeHpL~Q{(6@VX^MwBCp9S95@zM1u@{jS+ zXz}5@q1`erzW4};;5pNH)^MJb-Vfpsoj>nE-CT1Ks}*pri3o)mi}mJ(cwM-WbBtk! zsZ697y`rvUNh`=U)307kmQhwDXeU{pAZg;^#v#f!sLT_hZOH88XdYkWjx`EZq>m*o zC)tIOW2})XwN7n+nKHo<@_Z@&hfZCrMAc+i88PFr(ln8-deszotVXlsl@M6OuR+Ql^{w7)j?y?-d4)K1=_( zGRR*QU$xyANb<_@Isn;xDJ$-fVs|7A)EO|h=+Sd*kHA0C;fT&GGc00K&dTeDo^;?Y z(LnvnX!KIqk3=HYigm?I4GlR}E*0zWfD>6d2+9EvWD`(W;``^DUwyNg&U`bUuQ&7$ z5sw{N%!62uKiWGCTQ#*`mT(bs>d(!4;zfti;tnm^W>I*S$y~S{Y)*kT<|)zfoBjTh zZB>pS`1AJ1kuzvKFd#fVkC~?-gI(9`fUevF{ak&WJ_w0Yj_%4g3UO(2kFg;9fU>lD z7ZJvG7LnKU+I!2_GgZ?N9BbI+9?vhwJ4HyHp9_0Qx+Zl}M4I;aqmh*KFGTlyg-vxr z;6B-Eiv>Ia=cB=4q12smh+i9zn&tY2!K30rF6~RxJb!mgP<9!#RvjC4KSk zH2aiF23<-qtuhrgu*f{pzAR6KLGqz#m8e>Gc)}d`veyIyV&h z7bOmmJ|YBocfnWXSbXhSvt^rk{^Bel`stfM{2boKH7@rx-nGA1?lK7Y#j(db4!>2z zQi*hGMhsmWPIdsP9@`{1liVsQYTKwTN`j14aByOtIr<2;r@LW`4c}*`acMNYyuTrZ z(Y;``O1VU5zQwTwdedRFyEOGbASh1@iIfEom4O6y)4<4@@n+m`b1y&eh4pwUc!Hqc zJlL7|+u#QSh6QWXEU$SF6cr7?W>mz-`bE9arCYRVKMj2GIhhu6$gSu)Bdm zxkz;nPb`o7Ps3m{KcPd{+EMQF_OViLKyvSEjOEMG@|Qe5of$e87tJWbvw~i^i_a!I zYrnh_0atZ+&l7+$3UzDe%-Ef^&C{0Af&R2w?b_svfwp|%W$mLm-lh@#UOXXP+|*7 z{^P!FEdFd<(-qH))yGR&@T;`&46U@z812T&0*fyp44eD9`tEL;`L2_D>VEDX8Fa^N zu!nERKP1OI(ZIvZ;X;E&v9JhTNnUBwi2Ans0{lBI{Ni^Y@CN21LdtF})CBe{x#M25 z59de0lWTHW2(U>Gf%F{>8>Y5nqiIJ^i2E%y0_pk<)o5AW@lAihu=@P1;XJ(RCh7%l ze%lIo=5p+EP3;`#75s9SDVgIPOpi{F{v#Ms@lICy@7o%zvm2XUk$qN+xuayTr|z*8 z^S8_Al;1zdSdI3NNo@&yWE)pu+uYqZ&52rqy4>$zC$ktS(*X5J4crMh{iv^s+*}C_ zz(-kF`_?bMlNz-_YVM{ z6lLTFPZdRLBFVXxPFgrNV4(7>`PkdZ8(9}^yZ9(knd2&aU>f;sQVtm^P+d1i$WB4v zxF4L~Y9v}usQEv$VAoEF+a&CB1{4SZwRe%s&o+YN&oJ|bXm-y_I?C>N?u zOFX*c(5 z-=ED;S8Y*0EO6U}t6Vt9qHWagBupxjOW*CU7-7fuWew6r6}h~~oc|=v3fHLGT$gox za@Xp#e(>?No=5!G{x#^@@0v{b<(BvA`Iq>x#VJ{+%gV2E50d889{`FcWml40i64*o z`zRYp33kADX})sZ7&t-zDNSGNiTB~eu$-UE8)v{B`S5b@8&cpybN|O_e$L*@e@F+i z4x`iA=${N!Vyr>+79)SXbKC`gU$5kA4cwB5*!QgAbbPRHqe=9>6!>j>wJlZfG_i8J z#L&R`A@OVw-cr*a1M2rCZf2Qm>xA|x59DnW0~Zb_TZN8u*)7|>9#Jmi_cBQ#c@Kwf zhS?&8IDq_#N-9)nC9;Hz65>+0&l-v{cyB-Y)4bb)gj~-gDd-21g~{qU&k)1W#LTPd ztLUp37y&{&MRprEpT0+g-&e%QQ=lNJ`mXIY>iV)%OWj7JmB=l}PCMd)AAr*@v%OnJ zHJ+iF-X~92Asp!Am41&4n3PYS5J?CN21d{)owIg*74Eu%#^aryujuMP?--6C5MEuE zb1rk2Tdbr1J#WvANlL#fphho~5yld2Jdjm6lQ@be9kdNZv;};bB_>#ysSF8l*(7qk zXvfzYm>2lLRun1R^d8k&UwADIy(ImNGw?`FEhV-B&|hGfp_kLUVi`Yu_yvKG?j0gL zg-#%j$nmD{bX~7^_V>KIl?T%Cm*}qV%hJ@M2!awt!M$pgfyP}QNk_Y zj_Xul&&1`o1gu6g$**NG2RcT$rFE)@6gvZZ1Ci>lhod}@VvBE>h2)X|$S&20y`EEl zS!B7IH@h`H2d&JebT?-Ywbnml1%AQ%fm9--d;NZ+t^1#jXZR~XGQuYAsoNn+V(%yG zNEo5O9NY`3bO$*}lidE9!n81G{`lj(ce99oxIkqOkFbk7r`hD~Pc}hO4oonHuJPGB zH5MA1+GoxQ+2B-XHd&-H?T5&H<;=k_?*Zcdt8NI_{?z%X-?q!;fX?u3kBqrz<{$y| zg2gP9?QCaYZ6=$?uNJQ0@FrFD5Vy8_^RU*3mN@skxL0^;L}joM ze`ovS#lYrLz1UlX#PG4kK`b$dtCL%yhDkbeiPzn5uoZngQKm2SaRW!3+dkRuI=<0? zENCkdkTYcWeT2_yNyic{Cpke+ zRbAgq9@o5{y&|HL!RCK6j>8Ur1NnPhLjZ1PIHGP4$tF^X^dF`S^br;nK7*GBmu&E3 z@6=r}v3}_!X#?zu4Lmu@?>Hj5)dRO#Qq}ivFpo^{sMIiCRg{@C@A>D?vkh!e;v5U= z#WNXA#iTT_VH|&3Z~mrcSNVCoemT?NdApN_Xj<;r8VXDi3k1e82j21w$74@$re;!c zxI(J!9pzpOG-Q*wsccxMM6ko~)0yadW@)kE{n_ewu~eo~KHm--rkYQ_ei*X~bfLtw zuE8sAzs_G*){NEFJ(G(es9Mh;?XgG-jNp9=f^wAYEUR`mJ`LE}av6=VymGqTQxN)i zab?mwbvj?7BgHxxBva);A4S7yW(Xx1gQWH)aghnk0;*AqSwWd09cEc^N)`fm^Bd>; zri&pDe$~SPyb+fI@skd=oppT%hV!{=U<0M!7fYbB?f^I|p}>_x*9dOM0-PA>rVQaZ zoJrigzX}vBp@o$^jBVaklRBCR1ATGw1d+?5R@NQ-Nh@F^!7_59ho90n$@7e1_?~=z z@14i5t%mx={YZn=#lO^Z=&)_-j6TuQTke8zk*M-SO5o2#p~#3ZiThiSZOE4+&BX}q z2RfW{t3!CsTf}KvhN*9aDaM z?E0yI72T#MCcO>NukI4vdVd`sv)<- z1n1W4&W|kb(L!FxSRYj9j}xS6LD!rzC8EPHVXJztHu0*=lw&{~+0h?MJll5i&oyH| zGrzkFs15Toz;Ty!QMqrut+b;A>XY+byk9AO((2-ICe{d4HBl`}#^f-_p9^(B?JzIU z#mjXVodf<|oZkHxwyxLVmn)}vb@Q4^dBp&`W*%Z-wR6Wyk=@Vvw<(5?bcZzt^Ak1_ zyJV#;Gu)Q?DF+a9Zp_p`sYLB^RvFum4Dya#Iupc8cK-anK!OK|eaDYcGDAgql*#HD z2l}F2dFA8;9kwp^{J*kY25u)FDc5u9G^%*&R`Q)XgaKby7LI;u#CFUx<-;q#6xi)VH@dtsoCr;or1 zwWvL)G|NgckB;8qP;?19>qAe?E>Nzw6uo*pgxKf}vT{3aL(I%HB`#=b)?u5gIqtm~c3y05AK zA47pKCUOssc6gV#AhpzFZl+(W7waezMksMuE=0_-s@avc&GoOu)r?;MJ>1r&-W1Xm-# zqrX0V(DQh{^>@SE4as6bKCUkm9B83-GN26xwOKQi6e+`A1vc{WzKBl>T0?EEEfcAY zkRIHN#8dSwSca)@3=?0ZQE+m2_{QtWA%)e3zxCve1cGD}(}?Ws0Hz;$*yCBzUn(I` z%eNeaOr(Jm8?~w^&KTL_J`?3GX+$_u+&*|Qmab>5pA8R9>y!{;?N+kWrBt?VYg(JP zZ>Fy2a+`;1>iV+}10z1YZ8$1Gln|@?#j2G}?;A2&oPWPL0NVscB)*{3Se>)*XCcPh zEMkVrI0YbD%i!?e@-B#V>~H0FEEajOW;;A)2xVnc5@z9>_ZE+f*ngl5fuTi1P_4EK zL}Iu~l+O59hs4kW@~T02o}%@?YyrE;!wma3{ zA$;;wMs6sm$;Yd~Lq4=>vyA*^ zB_vHFn5*n^AFy+zn$VcfynjHX7-vClY4N-718HK)jDcyQ{twIe#s|`*g<^4L5)Oc! zop^ z=X4D%-SdW}@OOGjdPqTJj%5@ft*|yKiL_l@B}dFj#Y$<$Ym-4D=sUX_LBXsvMKjJe z!d9iN8sc;ovVuQS*Pcb8Pw}Fs`s=U`Sx*+yilhBs#f{#CH4t)LE9U-oO~WeC(M^cI z8yxai<9$rJn`#}ne&I>Cs(7~)Whr_8@8bc^Cyz5hi13{l2VN>W64nQU=}OeJ%X#gv zJE7-W(Yr(yjLo5|h7r8pb7|$vl z&y@EN(Qsjsiv^VAYi6p^x5u-&Vqo`jKuqNnVy%$o8$- zN|UT!Ka%tdx5MUfqg+*&(;k|)+?#RWEhoY!NFCRAL~XLSAZ@pMST3O;UXEJ;vi?2O z%z^$1U0zvTEMn7`elq_q_(k*?O*6W=!k>J=37Gps_CVbjwdNtocm3Z;zFH+yzIC>( zA=p2EEn=`f=ni%0sYp&5bK$@5>j1mi#$YShW-6yCf4+>33-e4_&$D4f)g)fk0i|ug z)TY=odn2?GssCK<-81+7ks$su0vRcX3?!|adStGBh z6sMF!R=*6y4o(oWvVke|8N9h;L)gMfA!rJ+3c~68`eM^w@Ywsft*jI24ruyB%i
5B1`Z+kyd14CwS0zK7trii8jHz*> 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 z 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 literal 0 HcmV?d00001 diff --git a/setup/nukestudio/hiero_plugin_path/Templates/houdini.png b/setup/nukestudio/hiero_plugin_path/Templates/houdini.png new file mode 100644 index 0000000000000000000000000000000000000000..128eac262af7069b9c6dd226b570c5599c9e325e GIT binary patch literal 11586 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 xvm1w#tEIVV-5fy!X*eGB_{kh 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 literal 0 HcmV?d00001 diff --git a/setup/nukestudio/hiero_plugin_path/Templates/nuke.png b/setup/nukestudio/hiero_plugin_path/Templates/nuke.png new file mode 100644 index 0000000000000000000000000000000000000000..9d9dc4104c9b2b8a27dd8b17846f54e10b857584 GIT binary patch literal 65305 zcmc$FV|!d-xA4Svnud*S+qT`Pv29Fj+h`iAaid8Z+qP{xZ+g!2oWJmXm}_Rwp1pK0 zuQd@$3X(|hxbOe~07+U(Oa%Y{|NICJfQ9~iyY-lT1^^(wS&E7(NsEdSDLL7jS=yKa z03Ki=Y8%!wN|^7`YW$M6;W$HCxAYjyal%rLu}`zi0N7o%#KS zS&Fjf>2tq=5qN?@Tne)HPDJX*GwfLs#oV|C)5jO3eYKE}3dNG4kQ73hsq?(3zd`=iZ{u=qzftjSJeQe* zh<1rj1WIP_CE3!>xBNcqg0VRtagoE!-aRJgtW^RDM`+GvZyc}{9YdOG#>Os`3D*tN zC&DB9M~Exka`OghaXbKXmy)6^ks*q&LR^J$&qTZq@&1y~Iq2HpcWLrbR@DJ&jBO^4 z!jwVRca~I3>Gtn)d37Fu{_1PVR=Th1gno zssBi01FsnJwL7hIc5r;X3T?#Skxrl@S9Pralzdul`o!c9k`J8>W4OAsqaz@XVa+^A#)G-Gm7 z$gFeHHj~i$v(_s)ZiVumKeikLr>+)M>!$%r+wjxaN~YdL5dcAMtB`gBm2`m?jNlVq zO&<|g@k29Skrv)Q15rN>QMyKIiQzYI+!;iZ>b%~*rGYc|4UNMQ-doQJ1E)H)K2Fz^ zGsD+>R@Ys)-#+?uCtp?w`Er~66L?pQtN3k=TB8IsDpp(=biaYMp;XM}O_a~A9qa-T zJPKc-%Shqpy9*3hJ`D!WK}y>h06@U__W}cCW@7^YL;z_qVKoo1(+-$ye94TDHy6d# z_TlO3X&Q6c)LpUzsuibxuoY~|O~iByE~h^qj`*t`4lpq81C9<9ct&0QTF6}6HY2HY z+*H~c0#eq@Pvkr<-=hSh?k z11}2k*VSpZzL@{NZ?M3`yI25j1pn@0fkpX?=R>0XMihcmXzeily!y|h&|TI4?)~po zG7)$x7}a0t=4PA!fW-nE0W7lr7YSAXy4WX7a;~HMRQmq~`uQLZpbh#zD7r|L0%5hF z^C2nM*6aR@0+J5GaN)lwv>;_rKVc?s{#wy(`X9>w(Pj!b#ro7$Q3-#-R0>S8vtRl@c2mKATmOfrPln0EKVix^OrO!~{+IoK!a(}} zFN~j9;Q!8|1l0jQgRvSBbs#$5RZff~W(rGCn_$eu3SUqqCCW>^`e2p^q`A{Fy>V5P z$Efh7M*X$C|A6U2oaU)*^nqO3T>3f6xtDACh2tyN&kvSYnzN{eDP_D1-M%!6jVnBY zfhAH_|5s@1IUM!kZwXx(d(^iEeW!Q|8EZ`pz(1-@v#Pp9?}ycK#?- zQR)*8B!N)x{9?KAL%TFZDQKT8aU=sp;o4Zv%R6Qkqz1fw=jQbSH`Yh~eq4~vbA#qZ zKjs%~1`_t!`@jk}IUP}AvVS8$8-0}3P6GlK&v?e~D@j*INS5@_Hxd(V ziv<%#jF9P!0nts`klp`Odu!X;n~@rl$lf3fptz0AK9fRlvSWKB)mh4QmwPfx+=n8< z^X;(hoe1^~FfNh#03%fY0-l8KnX=eO?YJNs@$?>szRCp0x8`2q8mnl~C_IRyywJ(J z&dUVnhcUoF2o1n*>bbND(O%qtS?6eg*Zsb*k$(4fH?)6>jKj`HZclf+tNvVz3(+^r z5hY~!srN)oRJM{uAwn>0tIwso|=IwLAeO@Fsrkg6$r@;&ZAJ9FII$ia(_gQ&3r z_%ge^MfY!T{Df(0s>MPmb3|0m`88H{i(|DR#;nUjRk1sE!8=QvPj%~#P8B;-gIHII zV=7H7=3}t=CRyV7c>gR7Z*wUx6&)P)Rg!qssw6EZB3TVF{<~cR?Ksu!5-HkZm@y0? zxHujpjm)LGX=i67D4yIsR7Xi3CrF`KXbj=l{r8%?DQj^s{b;Y(g!6plNsSX9(3EfA z&X-no^Tgh^{?kU+p*}6P9YN^+1YLadB$oEZOu}TQSWA^pGhme)giAY`;;lh2>03f<)A1tf|bvZIMKo?}JWiX$-ZaWI| zKsP$1NiT}Jp1hbFh4YCC^X7D^7A9OMEV$FwqOyfV2P18D(k<{pKD0P)PvkD%O?{`L z1EkuDa{1g;VMmlN7a&1(8j7|EkMa8(wCxwHQ1b0RH6{eWAXC`Zfa*8Pnt#gD;bwIf zLx2kg3xfNLhm$nlRPI8-1Nr1j7kN;&{dnBZnYe)xnosqY@!)qNKM|&~wQ1!rs?E6} zY-%CB)~?4m3wB_PX_Xi`qE=pls(PWJXhEcf{+SZQKYs`oXxT>N0Le4=tgD$;^ji1V z=9TpKnqse;Ab|*T?$k}jOx{EV!mGmRB{E@vhSs06(HjcCXNrLlF+(~>r}M0<>`vjj z#+d74T5W3vE+4})#>lV#Hu->fU~!*^|{iU`$;r+~a3#J&rq&`TV$$U-g9xGO_m+gq;2UJII(G zaSMHZSY;HdMw+deCWE{?-^o}<;f@{m@2BS1i2ReU6_4zrnOX;Mc+kVzx)?ZmgX`m1 zB6L}@g~)-VCgF6uGb!iwX}QQD00!{7Br@@nODNB!hwQ#|_|(WeU(zM46&rSlu)uLY zO`Csuy=XQX`(O`CHI>K4y_~qXBDnu7>Uf{IA0en3Auxa~Up6wN(fGmHd)n3`D%684 zZFE|LHJ~j5zmrCisGEG3!#*VSgym^x+W(A*|1JOiwEG{CA?$>1YtP zcm>D412?hcbLYyaZ;r3{k^(2S8(*-`xWi%qqyBV3v>r>YyEs=J59-q?lLI-n2_JJsNf%Aii6Zg3w7tqS8T>Ue}gM7c+uxRc6 z&rn^U$h{Tb(b5IUbITX`6KC6yVs{lprv)Q~OB-sSTy5%MF^Ry(^+y4;$|!t6p~u8^ zSe;n*5$)7zH8WHYD|FycVV|)R-cClwM+j(BIz~2)w9P%UD0T2-EHB8Vh{o^pCw;+P z66pcvWze34(OunI%f6}%+BF5CpA^DF|I^%2wd8xD%nMs=e0LU7{td}#_wj)O#kp7Z zqL|2z7kz&r(ud!e68HKG58p-}6F7(Wt2Fn8-e5mz`>#}G-?n3C+!WWC0-yTZK+ed# z+fYP5n|!Ov%|AUKCviR`kiIxT&@&d;nKuGTtX!@ee}=m)re*+n*CGmCjcH%SVZZq4 zI(c{CMG(Xgv7uO6FsO8fUM$V)L3St8I(j!(y}&YwulQdRJ0ZLdxp#y_NVr$sVH^rn zH-+^`e(FkCT*g~p0?BU&4Z23x69>+NF=FXBnU@0xH3CcbqDx-U2^5i8UQ6b`3B|6U zZALK0Img4OC(sR<^Krq)iaY)F#!<%e254d#SU!B>yU_@A*`z+pb6Sd}b1gR67JOeA6DxIreT zZ{J+b&r9?1*5OeRyONv{(kNf>EpHsA5eRbDxD?mCJllMzz`0lNGz4{64EJ_2qZ9bL z+(QhQWr95L3BLP-EN({4eMT!Tt_u9dTlQaKokt^H&EI$1V!!bUGVCqTrSm}$bq|zR`o}@!g>2%#{V!2NWkf8TE~eN`R&oTI4$L;?bQ)`{CU~Mnfw`D5A=r zZ=>22aE<68{oRgm%U$8XkI^_!Fk)d^YI#Qb5jovLliU-zD2~C?gaDRuwl3(f@q-G% z$c8jZjux`5N*={PN5^S;IXh`>Ecet3YiZ_m*bwR@gv#C3!2C?BKFiX@O zUlr#_Gev_Ld&=@&UNrkGRlE=W<>I%;27{5v*VfRH$k!aR|GOIfX~bo=Vh&S3a=n3mk=>e`@j?|prl-uE4rOtnV4gU0c$9|SNJSt|Ds-IagaS~=&N@#CS!&DNgJ5@ zcBz9EChX5#>VLSP(T&=LeOu#dDwP+Gcr;>dtM+{BSd=(H^HdEFU66Q_v}wxpGq`XH zx3nJ+E1m&CxG#hQ@iy$U;JNeceqp!Dj|+AMgrRjaXfN_=`3}OopZp9sO#f^I8($Wb z*5rL6A?EX-M%GNLyq60}l+uBYX@T|;-K10GStK0HjQi4F(U0`YjC2Xlaz4z1w6R%e zkoeG>=v=q?z+`Q{vVEx5FN`tE&8yDWyAB%Rx6u-CrYGlalV!*F+Je9`IsFB*G4rkvME zFJ(m*nS*tGvFQuGy^G~~Z$|a8t5j``D~;5F11+C%Q^VA5ATe*m^84M~S5}4$cGkiL zb~01aa+sK$afJICw+(~pC#Lo2!hi?ZiiYq$J(!4LVA%X*G{z?+EM5+3q4VOVgZ|;n z^tXQ}h*I-`?DeH(LO6lQ^?wEEg$MS$QAG3EVRq3>A?vgsBCZh@616|Ip=*ouvs&yK zsB8c-1GTh^MT(zt-I;k7WWkKTGeb;EXA=`j40FbMp?coga6)ci-_6y3Qx|{);<-IH zX4PN!d%?avSJJJ-r=(=DlwMB(8oVTM>CO1yJx?ErWN;`_g&)zQ1L4w@YNElG1E0g$ zE+4x5s%N*8)4NK#*i2y!pTQ$^yD}W}9{wy-ekPb4Blo^7P&I%7q8{1nRH4;(Yhg;}ErAcsz0c`= zgd%wl;t`b7d?00dfJksO^b zHNKLgKB_ft8Fb*8cgUAs8)#=GEG!6}5J&+y{|WQi*;GR#=oCLaA9-e zOXLes>`3;S91oTv_LSsfh31QlK`p~lE17i#;V==0AM@rT1Zn)r48Y_Ymj%&wy7B>q zeb)O;R}=Iq&s42GfH0p@yhQF=j1%5uEVNcBYg%e=3nXKAUNQ{*g2C_-4noGot&w(P zONbF2%$(B09^l8nzw9>%TJyUaZfJ*|GH6OQHl7dOZWJc7p;ds~-X)k+>a7+1p5d=y zczXFq&f1?D8!PGk5p@>T(y+8cf%zs|-Aw51>@Lr5{>n9B?G5?-sHT+be30u(l6G9T zqh+w)J}gV}ld5>h-%Hz2y%z8A0^PD#38vB?er9)Mu&I0| zV(XEgS=@T08>j-XBNqEp>A@Y{cP#sa^hlEUzFGs1sH3V0t8U+R zIg}nKFM(AhH6%;xxE^%a86djxt?sm}%Ut(}NGar~BzG0UI84H1`S1@uKLtY))0bH^ zTcsG%0web+cq-QTy-6r=dnFqkuvJ24gm6IvpNHz+c2`;%^U$*#xBpE|i&VG{rb11M z$-Oc0)*5ZFb;|6;3O1U~2SS~0B$7^BGOaXhpH1;6-_$A?b=`NP>48^u$$U3DxaI;P zx8V~TYd`$-4x23o(qJIjtd01eVET8&xU79disOQCupwCeKhtcSEOrTw{%k8~oeY`fBSI~SRh)M3oM`<_g-3v-SXNs zq+M-_yJQtx3sdjAD38n|W2PxOBj*4vW#M=p-9_^~$jD%IBrq>MG$m;)d^(^4)JYy_ zQ8?CYHX51%`9PfX_~fDXiwScbL}^2CA2}&)A1FDf-q`WTUPlM+o*YK~-GwWJc@Vt0 zam%JCeJJ|l_-9LvY3RZuO6qN?|Fkyu#9+dV>8lgZBf$i{Kch>VN?=XR@#I%lu{=d8 z(FF-%{JV9ao3;)XgYYttoxbAHd!!m)E@15d-taln4)(3$3Wo@8ig{;;#oOw8YzXmW zHpJd977pT&Vc0HIixkm%CRazdc3Yrk{0Ap<`G9An-?y~GHx73p;c?}4Xj2^jc40lO zuT~xAS{)~kX&8vENNs6mx3r5*f^V_tttHGa5@RSBlVZ#{efB1M5#qMjqa)Owp6uNB za7#0h|EiQgA)zEetF=IC(ru^LnB9_BJLS4O>b&#P3N$7gACt^thXF z$)*YEMx|FIKDlgYC`Enx%60DjummY0&SVURGWA)-L+DJdy%||_%6+f)&tLBfs=0cD zT60lUkJuolY%c=^1vf@k`k-_qr%GZq4RtT5)+OS@lq*)hB-h`6Fl->bg=K2$!EWlxs6>%V2b)LkAcOE%8wfj ze8$PW@QH`(#D;6lw|xt}-0?XpEzj%aw=(zL8>?LX@X6vs+yYvvOEe|c{^OLGD1Rt4 z7WYe%YOKs)OMMcH^LM!)xDh`=U`*gvxK!X2>Nk+96H*_nQ*~Epx^{n1H8dWLKa0#q z?1$4Y4!5$L<_to|AFu^Zl+4&t)QY@MXyp7yev>*P;d8kN)__^v(6p2v00f zYJ#A^8ja%iRHun+DE=!Hrx!0#R2MG^6OP0t7q-8wg52;`62^2|ICzp9wyl3A^nD0w z-8w?mm6n~c9MHk{qtp0?qg~5lr+xUZKlhb+Bv8%oOCV@-zUCY9^7|00fZOp7sr03I(Gf;kNA4$?bcO6!&bdsFfNOD4 z$QUI~b|8X^h?W6_{f&NxEhU#F4DL3Dtg}GM+dX>qYu7$c8Chs_d#gUn=nMSkUQCNy zdB$JD?#4`ZS(;LH7uC#M?#!Qf6m1Jy7sQEQDbyJQr*`qLPOn_%$0K5WZe@N|G@dA>a<87osgL2DsQ*_T}zg` zwI`1XzD73k5vn%piZiU?$|c9(0RK?bZ>$2;X?d{{fRexOZ=N(v_MX3r=f8tn9nlC$ z7;;7rttpJZ#VtZv=b=D+T``{^`pmy_|MFi}n0l zEKOe)%3h#^{E_xO@<5`+G}8!rdE@@}184D`(h>Dj+C}@{T70M_5LY4z{&gH)5X;km zs`o7ATRzdcQ_+3|x{@gCT4C?{XIW@fqmq8EWj|ho)}HXo-`l=fC&0iR=*n0a(7Gvo zHZzLC{hvEx3DL-Xn5ffo6O%ED)1*~$pt20X4I%4-Q8P<2;mSub({L;g@nn$=# z+^~l8Pq|_dKM~{O*g;)Y?0e%u54?`XAnsGdDu*ypk=}X#Ix(GzOs#cPzkfg!DG zWP}ev@W)x|*7Dmi&H0e}{>RQ#*xv%iQS9@eH^U82o(PM+B4+HqB^0!N=gVyLBXK{?6w+VAgnu~Kl$d@^jIsTkqjasHm6D9zA>u?pI32h~;TUpa2kp5qkRT5S8Kqi=IYKB zGZZ?19PUx#PmYpi-hh6s@~O(d226xb3YeEibRZ+qzafl9HF3+fTriP=_IK&07@8p#6-N`FN z4u1e*z@lR<9m=7+k9dL!T(5PCNpbtioJnwOKl_8B)8VY2iP7{0`9D6)g1>Q9&`F9d z=>;|SozpR)pg^w_fgo$t^fXo30;K$qbi&s`Nr}=iN#Xt~-tnSZ7ZL3z!8;l9eoBoM z;6oy0Wn+oGI~*>Ic7(E{6O>fWQ`v+b)CqCqMH%dh!0~{@TQ^;-;ug^GE(Vh=1h@SV zFSz-NF;c|13^vsc=Iw|nhE^j028GJ<2ZJ%hz%%yat=ID;?JKN{8+Mq`zfKvT1QjH+ zrYX2S)4DldsN1Z0|!@T02NEc;iuV?@_5eH z=7j-P4jZ>`Mu3_!$=`gJDI6#+mI$}7IK!%~m>FOCa6UZrY|H(bT=bQsHg{Gp>0pOY z{p54%xrF(%`G=+Dden*A2zLs6ckYti-9Hq$Tmu;-lsEOwM(vBsxf|a>8;7M5%DBi^ zMq3HYdBq``lYmLwnYYBd9{<*AM)qsN-?*L(W%IGpC3jBoMLM)HVSj#p;i`s(ROF+Gl_ zpSDD2W!6m$dii?yjLTK*!{w~Mj)66655=IZ3l@Ev1no!~Ku4WIz zP;pE8ZAYhH{?hKbK?k|{an3{U<0(5m_-A(;z57@I=-eommyoQL%HGbopg^TwHK%Ml zto+7pK)n@h5&DgPTL5w+;f$B%nnk?)n=dhW9KFJjwYAf9c)hhEhbub1*2(bd5?ssu zDLl8OnaZ}<=A#w55UHV^>_ym(S7{koh)m#k^L7x-o3t4y-!jIAmg!%GYluN&;J!LL z+Iq7TFEB7UJ41L6g2{vzwGd!;2g<<-HofQQ);WEs+B&@int7sA2&otUYZ>DNU{LqH zROwpp{o$5&B#y;7eE~t&5Cn5&&W!@gVL$7`5V-Kf5fLeiA|KHm27}Z}sS(nNlHG!k zs_Sb!?WLLJ`e>oVH6s@3;A(+-5eD8uIf+dsDkgevADo84coqPEDUuSILBOC7<^1t` zzXm2r7}=#f|^X z?00{0_LZ;aZi_}sp79h7c0g*bG32erY=Pr>W$-Fjjfeu7i4wb8OAY~C@U5FGCHE}< zjcd4V$_4L+Vngx!LMU8l_%)i#r_PfC=#%s4u(Jktm#alNp4d>1xvCRB22x@sP=O59 z$6_!;of*5N4lp`u?7APhYBf&D3vjTrNn3%TqF!Tp^`<_z|ZcN zvP4%3Bx0169jnd#ShO>W{#sVk!S0wmmvfce5Un+99dcN;8kna$;dAU+^P2Ex;!e_a zmEvteZQ;(g@GI-$K#rSV)5+Q~>tCa95`O}y?%lr@&uB{IlT(gpCSD#}nnLRA7I8Y& zHN)TpkVP4YLfi9oiZF1use~d5;{hPDkD`Z_I4V2MLN5rFax=fiW3%oH$lBNYi|vA)KU_ay_1E7L719yl|1#KO`;MOFL#@5 z3DQaCQrKvrU=mG%*hhJb^wb)uA&7}zyA0_8%(FonObO)3O-LlC@_% zgglU=IRMaud}PD0w?aF|E7%*rKHm>-7WYsO^_tGJdj$+x%k8 zHe2>9UWDK?l6||ngjETwv5a3Oiws}Y1M5QEt%h8D#itY~3WOGX$kjkjX+`u2{J5o^v(MHjodUP^TMO12~R(HVK8 zVJ{H4v62V*&n<#O-u!MOX){=+ZAx)|rIqbAiyEP+rDs)lyZRfeF=8k(h;mk_73pD|A+5&P_cM?M?e z-&-xyJUj~Z*O_-FLu}as%^#YgSMMycUN0YF>O2}; zgnS1|r$plpZOYMX-BSg0T^&AeFF|az@Z&kY7oD!&owU8PdYeuDs|_v0+LkxodY(nT z3-*1xCeEGtR<>V~^)(YFq9RQ&)}>}=>$UL7druREePn*^Y}!TBp0f0Y!e~Lz8tZ97 zQDJ!Y%CXGkJA?k%H|>c!GHc}_1Iu@TA1EGrRK7MO!kczQQV7H`id$1}V=E7X zq*2SuFJ~&|_rF9WH!(^(J6()pZiC9yCxGlQ)W$e6h4y+$%NaG!8 zna}gl_YL%I$p0!$SY**$#&Dk;{_;6Krs3W?{3C`f5qhA@ZXOB;i;yN7v^I!*gD1HC zrs{NF%!%S#{>DKENn-G_ywH8Ta{reed+wmn$ zjCs1A#IU`-BRpgFe05KPB;T;L0K)t$M|4sFrYa0$sId{xLqVI^Hqg6y|vLE{_l$1!GBE|Rp=(Y9!$sc>>Q(L>#3T39W4GU>0n zbuyAD6%=BwP1&YB91cv3vH(V zald`|xZ>u1W`19p|8JbFB;{5_IQx#H2^!pSV=Y;5*`o}|?FmuW#ObpfB6qP{%dC7o znYbTx-{&OBQ2LyNP8jos4x0f#)~%ivi@vaUtEiM4%J|SbboXb})Rr;|N3Yq#Dns`e zg={mjygk1)F&BLAx&qxJEC&OW%aGm>~MgoD{ zJmXg2%akO7AG**)f?QElR})VLWW*A2s2}56W{nTLduX1q<-P{b7i-reVY-*s6O4O= z^Nb|T-<62-+NyQNMO02Cm9gT)__@{d_T8yMnLMPznbit=920TjF)nGg;<9S-Ry49< zZ;fLrAdbkkKS(R73&9TKN4<|@5lOpvq9lFF@Gw($iYZLsW2-YbCem}STh8Yo3qsnk zss@Jv!2z`vlt0M0uZ@yjvJ0MM+F2ia?*={ar=hzc>6+xacp<*ra;Ad8;!XwKJq&~{ zH=Ot)AGJw_cgnnFj?pP-2+mz@+$cr)>;WGJdwP2hRQGE7f*HsdU()PKQ?2(khNWiD z(O^5N4HNCVfn9vm3E`|DBTNk(IllmMHSX2efd<>YurAd37Arvij^uXTrgIL>`KxTR zJ%nN-n3cEY%mTK^m$g+y~>Ug_rRx#X?I->l}Z;7S10A9%JC9|dNd{fne> zfcSifhD?7Sdhlvv9RJ^cLEb{@JS2GsEd0hd2S#qhHno1rG})floxvTxJ72x!53{HV zaqr*UZv~N#o}(_R5*79JNF^2Jq`2{v=D8mBECFGyaxg|p>W<)y=g{~NFc`l15kYR- z6*jt##7YZ-G|q9n$h{i!GC2xfAr6$SKS$Fq1&?$wQp%Fho#@aQj~RbEL6A45 z$WDl=R^@A4`S@w;;2H@O$I3~4k!$J6of7o}vqHu``djXHfsH;A|XHUoU0h{Ce#d?~jRx!Ok}jj<+C`^0nB)bG-6pYdy_g z6zAMvOG&bo)Hic85!PK;mar4;#yN0k9&G!RVk?ZL*ox!=MgxdM5A3myp> zLN=W_$I>T-@`U3|cV?z~3H{{1^>L}>jKL1aVBw0k2rw@0FESF6<4COb+F^jsDVZ1f zHTIM9(w7+B%tucS8vP5#Uv8xVGaZjQQMk{a|Ag9}PeAG_~ZO9_$@_gud+Yuamc z>_F`@{c+LKa-z@SJWO~jK0*bJ3M5+v(DXB?)d`Me;)2g4k9Q_W!6H3nlLM@o_>WfL z*c@CW0t&hOzoySwojjz8bpgVRx+IUd2Yxo%M^R(!Xd*MfPxEzE!DZwy>Us z%4@f?^ZeEc|JHH-)dByCrWd}w;43ISk@3T#`E_fELzRf~_QW{=OpT%~Bakc)2#m>g z6A77(C#GMJLUK!aR?nmo+TMtWG6FZupJo1y4^uY9m8c%iEuP_@s-So@o^)m{Op;G* zchCixMN&FY*-v~rH93cO?Be05>e;H_A5@FFA7Kjj$1!oZyxEHgD&Zcmq73pB9c>ERPWTQB-N9=4{> zvP-Vfn?CB{;;5DQ@Em0>Z|HOY&awTpo@)ACqmR#|rlt8vHC4=6+|w z7@#1F;E;PWzHRf_h9&!A$jWgb?wqi<;VIqki_0HkEBtTamuTu&jmu0N;|}!`y8VFT z@6el>+pSrTb*?hSl|Q>V4~>I@uO4xHVMureg_F8SZ=t zSdAZ`7*cx+NQbso;tCv%3;-K$<{)`scmt}ukN_zq_@AW2lOOQwALi)LT>f-vaH0gKAfAync{H${$HP2R0Mz&LM@WBZ(WEw=c6>5i3 zFT7K4n#;=prr8Hv@(Jo6En@wCbii@6r?!D_ZHb?WYOsLo#qXBI*Us)8-Wa=^<~^ZJ z(o$q&DZVkKkCm(2kNt`JVcNx;Zac4}8Qh=OOSI*CsUtKoy{LP2+9HRuGup~bDx&X` zu;-~b^+w=-;$&nh6=qwnZ33;32N!LrxUwP6E&EG@jR4OgZ0f$YY{q>J?=EWvu@isW z|M0D_GqI>rhqH$qR3e4Va>C+_O7a-F`ss=J#%ug^E#5>?jj{J<>E$<6DKm|TzH2`n zAKX@COc^p536jFN{7E-?&L*5DPqkiF&poky zzw_c(;>HE8>CE{hwOZn+@4=YNApKYM!3az|=|nlQ5*5L$>Ox~$LtVi_?}y_0!CZ6n zp?C>bBD|}rU>2*8Ucy(9^m2FDm68+CWVlw zsa~WZx#J;!PVy3|qeJ%eB#EH*B$;*7LA2O<)k*tETyufrqX2lHe(uRibyez?mr`hQMdb)#dtB-~1_iRD`*mI^PR}8QUutN&}XKa)6vOMUa7x+TG?JTcMrpo2Ks{+AsiU}lm!3Pq|x1+VC%joPC>DM5TZ35)PDFf!H zpylT(JYQZCcMSI*)1!&9HaXCrDkmT?@+oT4v`=_ZnJBgwScROS;-v@07?wRsRK}L<+b%jKRRFgLF3!YZ6m_s2IP+=p#F6Hi=pXDS ztk*SU4^K6M>_hrC=SCdt?4!Apxji2cU)6?Dw?lnLk+orenth#-m(82iU*O3Akmczl z(#TUN3Xx7vjr>9yH^*Jj53`2lbotzL$mU_StUJr#0X$1#Uj3Xkm$CnA@l)w<1&7d> zvZ|37d(=J+-vfIz<7gU8F+m!*L(vzHwm zi>G7nsMA!*Q+hIBwe-OhvDK8i`;(=pRE9oZu1oJ!-HfsE_3}=KkJAc`1v_R~z$68> zQo*=XPWM0C4w5->mvTPg=0Z7YBKDz!T@eFHw(z|`9IF=K`;q?Bl7Wtc48p~fc})Tj zKH>**%7=M%Rl<=6d7Bje@!`S|s>y6h0jUz;D*$0ft^e(T05wJ0Bio+TMkc9LOI~PV zLJ=HdHRG`mKTKO@h9?W#jR5Or9xrnW`m&VPcqczobD({RFHWb|3S-}YEFG#_Kdff! zd%L5tg2WbzQkb(LW*DKX)+i~4zjTuw{D3o_M%w9tuDn-@;oa!oZ_6IFH5%`G7yW&K5-Wrw!PWRNk(-Tq2f;=G z;BQQ00_boG-y+d~6QSQ3I7c~x$CR~so^z)G2{yu@L6x#W1sj7hc$LtNZ9PX$0 zAS*_`di%g1eFhBA8w36Lspb$HNabB`gL=fSt??rt%0##0{eqh}62-0bp0}rjZ2NHd zS&fKJ$sd_Tm=}*}dFQDHeCT$eqmhuN>gK6KU)K%D({h!GnaRrO)-Sa=aurf`6IK;# z9>)YIy3zuqL@-J#szVyHi1EUeUTVz^h8Sz zg0bM|`D>G%PaUW(7%gVdYSLf;W_WQa3}MFuAfa$5x?rK6{n^lzJ&}HUh~xZ0k9$@gvh4(fIDpzYx{=6F=*?xU+%m#)byzQ^u5iMbS!wbCrE- z$6xa)gJ(5Y!t(l2*L!xMX3>MDlkSx%MAFa~F{vr&&w-_9{4MrTSM$#F<>@d1=kMa>fI_dZV^p6xvvJZ*bclrXoMbm}sV4902+VWjvO$}Lk<_m53gme-C zA9M1LW>I?!gb&Lqh_Pjk`De8$L)N|;#d50q1d5ZZgO(1)V{7~=z=W*r}rjJ9fsZN=Fs9Z>=YbxE|gF3B&3oO z6Jv~SO&Hu^l5fX;-{GSo8jhi^=D*5YQ%Q%3BLV&6g6obHUtRA>x_KfXT@3CbBLCjt z%1r0|Xux~#E7xnt+)sy-dPlP7>ldqC-`)pFYS~osD@pUCb$t-f4M8Ia>BI6!`-_*v zCXSw>S-+`uZNfQ`u5Pnev!DPzWN4R9j<-!lbO-YZmC<)uL$DKiTw4GFLs-88 z`DFqbQf6q<`Zp(y%6?}-sBcD%>RBrU5R}`PyuqS{nAhoaQra;IcEKz9H$mp6vjgl5 zf1BL@EgD~d$8TQcjVJ4M_uOeK5IozJz+88r1EX2dXaYN=2V>ywynmCki6n;eIPFIV z`@Hq*WO(DkK`)ZA{6cTKfA7hb$BMA?K-;E}Bn?3BH@b0kg0%VT$kJ~6z%;NpqDZLV z7kU#Xi+$lzP?tMjNc9EUaAUR(d5&$*T7~J?r7-ExS0T%deJleFyZoP~B{6;*D1qLE zQ((NXQ90f0FiB)`2l=dOTz@LsjTMgrnP^TtW2RC*S_ZL@y(F{meG@35#y}@u*0OA@ z*J@AK^9(nQZ`dDUv)g2wYVNWIFCT9Buj^rSM4f3WdOJ7kl7@Fz2+v6=^Wrh{qWy4w zLAjPR(tPyfIj(ukJVL-mceT%q?2d>j$@S40*7Et##qECm5)X*^SkS=?J^CTw)%N^h z$aQ2}K`MszGjCu0y5v>{fEKJuLpVL7SaR;W(P*)R=`^n2uGCZjvwJk7>W zk}@l%!e6xRQ;~a7aGM|r=SE%DFFj^j%=?q|Hu8Xr;0gY_J-^;3E(3!J;h~(%EOg-5 z^+Gtz*+c2!Lr`1hFEm|!>?*Qc1w8%X|Zt zZ?Fwpl7wUt{LhBuRxsWS-IZ=pp`@*BUpW?7IUyx_is|A>3J*Ip?D z@=S7|4{vK-;Wiv2Xn&^1jt?l~FnFMO;wODD4*tedFC}OF}5Du?MhoX@ho37d|eVeF?f@omeA16s})r8TIy5;?m#qUV(e+eYVzvAt-@4Ob@W`2n5uVhgI$mo z#T+r!kC?=%qC@qDx?Hei(oorxnc1zWDU*xr9Viw($=mn|BsF1~lGQvlBA}}RoeTk) z`s2_U+$oy(aGD~)XWyiTx{2OWqaQkc>Ha$NU1LWsx8F}t94?<45xclJ*=SdsplOx0K&SmWuarkD`4$+(7o0AOase;1OwQ4^V2^2#tf0jnI^YZsW z4edvG6WDf*o3Q*l#VDCdHLNaiV}4@!3aR zEBMt{kC9v3ysh|kZ$)h zZK6{3Tvtd}+3>P~4=2+AXU8EQoj9HZ-#5an_ktrLCfUG7q{VI(I~PeO_*Q5X*rms3 z)shImAtem7uH*S`;qi@BEQX$ zzTbNWX9I~OyRH}Nwtql76OUzH^PyA`KHUCQ>#J71cBuN<5^M@^DSu~bAZ)uqI>m9m z{#?|flHpPy{r2(ko+a@_+VsJ}XBK2ve*Zzje7NvkDL}pF?^ay2i&Vk$X4suSj4w(@ zwV?lmlJ?X}O49qA@pZ(5#>h);j|OV0sqO=+(oiS$S1b27$;I4CczjM$1 z2i?89YgbpV^^`6jQ%VGXY8_2y7$Sm+a32hY#s?u3Q>dqB;)|DfBv*nT+aZs=a~}ru zgta-EyAEk_0UUKTx=cz)2y}-pCObFNdV3QEv&0g2_N6Bf8lFsj>cFE^& zO&m$iqS4$?W{!h}ZQK0|Q=6t{Y%n6{qt4;x?N8)GYnxGEq1|oVpU`;52e$>Ck>rw; z&sdb!EAbQ^y5OZY4Rp}EL}yrM2%FtQiZ4?J8|6af%rQP^ zL%LYw3pYoW#aI@}dTs3PKUe>o4)>5O8|ge>uis=1eewkVPYW=Fvv0|9>%4F!8fX7*bvwT){1#@H1=Z4Wh3PYCf7LoY-@Dn)+sJG0+9J>8 z`XS8LYkflD&5aRy8ND3W44@dE8kj|pER*QLgfWa57K`+Kb`HBD6@1sV}7jc?p z`b(mShtyxYT)S+BSA*7;LT|sGYm77F!7q9R%!UEHx?NRhB^j|M+7!Ho`Ye~Qd%n$9 zN?D!BFEH=|rDyI7TC@P}(urb0YeU5jFUe>_IkjRhzVBaEsn`bnxnOIFX~j6#>>Qw- z`Y=V7+9fW5WRrhS?&MK6_ZTGv*Pb*Adf86s<$O@qR zt6fnIx{(@-)n2t_IO|}~U?#OF&}8Hr9t!C9*0FU(2IR(=3+QE`l0lqWlZ(iAo>uqG z>CTi@3#;9haMN`TKwnGctk97YV@c6SsafF7i$BD-tPW3{+|VNr6=^ zKmT=5zEInbuf_jjcnXGt5qqJNac!QbIjJ5tg&Jn^cW%e}?XG}!-wlk8bT0UWw2!tB z&i_+O=iAG=_2W`dRq!+1$o6~>|Azb5BHstX>E{GP+BUc$lL%4u!ZjF}?5Qrnna3$^ z^E3b>hrN3jjzn^&t47smc5ZzLIL@OvGEv6sqj=oLY1#f8@wk z`X+z-9n;~&x0vtdId=P!t#pUXJODaCmT8qb{{y{NOI_@c@pv2@$PD&-lBEta2^czc ziU33C!&Z{bK)STb!8ldJu%<-W9KsYP{lw0HZ(0x&kvw7B3p{Uu;SPxSD&+}i39p*A)tP%{V>rt9WI?IUSF-jS_!d9MQzG5>?un3q zD=MMGKuBY5LgCG9{0pBS5iJBdYnd2;sG0*wuTSi=To`6Za?)ySatxCZ%ciq9wjHUq zP5=<7qQ4S7pT08?W??NSVKwJuc4ro}N3^6WlBm33*P6^JU&mi$ysuh$c9gD{XG`D1>wdG%$(Bu!@) z1wx2qtG{@>!|qhr^)%02wj^1?^R^LDEfU1#qhv+Bo)2mdAyy~G_Kfc4h*I{KdMnV^(!F(7#qxk|b8jbQoTkH9Hi zWy)qg(l63frvLuhyDy4L+ts_pCi#it^NS?d_{tS%0o7Cj6a%b4%KV5bU+~+Pc6tn& zyjy?8vE>tT%mH9uN<)N>w-1fEfyX*Y1-@P|(m^6&w41?0w#5?oZ!pL!yWld?MFec( zqm`cv6z(NrCd!_5vw;l1V8zWwO%OikKN}VEI+37bf2R?i^XzQ)JQHX7C(XTtK44;E zn@G04#@v<}Nb`mrmLP#!v#K$)c6v@Rg)Su#(fJW`!`t}BX=XD!a2F}c^JkWuxnhz2crB;^cM#o-iRseh=Yg$7}u4ou<2hll>4 zG1vPbmhEnY^Xm@?--v`w-(Kv$$m%pC5B>U5NMc_IqkI$sPJxGE2b0ge{`pKEM;FQ; zHe6)yy@7znT(Mxa*H-#wa=<;v(@-PXViIEL`W23n5?cE)vqyxc-ZDt4zV`W(B0$A9 zbPzCIl)ikLQ`FSEyOn~-w>Zjh8GHUg>FCSO2T7`|2^4Ixb)+pmyy6>+Bs{WZQ7>9k zl8jL3i}0g5Zc$&Jt-fM~^%Z__#oA>Fe+*dqdJX17Xpr&Ntv(0^vQs#m=tlmQani|H z$)MWSIo}+E8M^R8;H~6c5e0)B)cCxBrR;U9_wh>P>batvb3}5RVXL`Mw9@YM(*K?V zn34a{_)7RdXV6hcYxp5AT&AfV4yXiMcneU}_$XG2wPV0)qz<+nw8ybMt2=6gDE|7MRO*D%*TIxkV8>5tcmB zEld4H5S)4U?#0Z0^G%ry>}(pIICDzTgnWFP-LhVHk$)WAn^S*Z$8Qxjz9?LFp^oiD zKr7JVSBAqR>|2+%ryhmJ-94>$PvJ#^4hNMU>44p6QVP?48~w|#%v%JvWGmqm?7bVz z`R$@Ksu2*Yb1hg8en z3CHSFNrt05!19BXvvGY3hqV=OHac?ke)={gQ=^a8AnBE_B5Jhlj0|<`j3&wwl`2oR z8Pr$B%honJ^0zn3#gv%NX@o4Cqy{hq5SFk)o$g2 zG;<91`)Em90KwhIoMQVpUiDe@PrRMkbeQ^p&&F&0zq*Iw zhrV-_bez=KI#Rk=1mkgiMcYm2D?#25oew5}s#3y3__p)VyY{l~tnboI+`?`fbnQ1v z=T-oOg9{qo)R0efm8~;MGWA;hn+n3~tFK;>tB<-B4e+H;3&c=f6IBW~m4Y8MJew$b ztx;>DmG6iOTrSXa8sJ5=yp;K|`(69e%MOH@6iIfKoU>sB%Al~}xKBDVP(8EWqp|RS z`<)F`sz&sb9ag+@=)}zz@z`*P>a+~%{ zKP$%90`dvM>y742Ba)i~>oxJX0)6nGzwxOiO3iL%Uq;0nsYe^E*M2{}D9ER5-F&!Q z817Jj{la7f%XKU{BW%;`J^kSbno?1|C&?c1+EDBh(S-CfR-f2;rD8}txsrfflu0~q ze^S-bL$PE3ACh{aj+|Zzr?ptYqQcNW6OmbFl~N1t6O-%8(ixBN=)P4~GsbKYkv@kT zuZ4EE0rHyqoNG37qtKEbvmiAa{N^00=r-TS+dy3JyE)C=rm~rsA0tYs+Jl?FOUrOl zrRz=Yv+p13e!(2t!Mc+i`V1z(ma}Osi^!O##F`ir==V4M?AY*Z^JgjFdJ@?Mb|H!h z_oF&)J~puU!U|-AgcX5)3`D*o{mJhYw#~7F&kL5(Znsa3dJ-@z1t19?xF`=_XhzDj zEE*zN|IbzlHcJ4)bL3u(v|^_FL@6(@PD~>mWHn01fUl>#mJFR|(dQC!9%^Lfjd;6Q3 z(8f|uQK?Gk5nXCT&|juVpRwK52CT8R38jc^vorwluTQoh`;h|IUMsDu*yfvfw-{v% z7F7Y$C`xKqjOmg*gk1oFp0J@n7wO;Zr(sy~QrqcrjZ(AAJmm61r!MQVsx)!%t7@c> zY#@0wQn)D+f(CLI3o3clZe!Wi78M%Ik9__{PY%HQV}4qWaDcpEDglDkGbbXr!7UtI z&OzrXA{IV8-LMO4;5SCv>P&zo`pA*$v%2`&N|AV4W1EspV!MuE>PU|rr{My-iaJHtqIR(Y$2PkQU=Yi1_+FTy2J47z!zmjjD7w09Bk}<`tLm zZ@tLRGpI5%>H@)Luea$*FK54Sm)3|^W89dNU%yiR`WqLJbWihl-o~Q2uhUc5JzzuW z19onI$+RVkI|6pL_jS*-r&em3k6lB_7*3K=DW(W#aH>p z^u~{Gfq_l;_cPtFr8k-8r@P;*tW0xC|D+-$IIZe4ndF9cMPLkdWN5VGaj$hL53F*; z!|E~m`(S*icst)hv{tVRhC>>ddSLfE%vYFl-X~wlsN#RTHPPQ|T$nyZ3rMr{I1THE z;oBxVIG}X@GL`8*UqRYOZjiWXmtl}wdUIQa& z#@qkL_|Rn>o{r0-N_R;$XY`rci2RT&q^)iigIoB> z_PMYsEM7l&WOv@~pcI~?#`kadiPOH@3v$vr*W?e{j4|0}Q;4~;AaS3U&g=!9m=ASy z2mpmpMGfV%Txo{2Q^{4Tno@?w3d- z)#|5VGc2=DnJ-uxrXb~<>_n>yjjNs$J#RZ{1#PF%As=6aZ`=$96|!4Rm@t$$Qsqg= zD$*9AhpBT|jRKZ5LQNmxL8Bqn?fAu`@((FSLdGl7jd3tJd+nq6uLsxU5I z9?PYP)4$LpbdeO;*d~&^?X+dZb+VdU4ebZryLVFXovIuMoWNTP;8 zW}J&fzU!xrQ!|pUG)1?eB&3Whm6ddVoau77HR7s1DoGHPf2;D$vku~tRk+5;ZBz%R zp=|5m%1BaS)j5;sb10{yx1YT!1}oUfsL$jb<%{N~A3!E3sV(RJ`M-L-5yHz#Rha+u z^i3z`=4X|^f;wyyD2Z9nKw$vPNup-M?q|X;z$aDqQ+Rc4(KcqsfA`|3F2W4I4n$We z-7XvnqOF+}63C6f6jg}Rb;CvJSF4}~0CLdpu%E{zEd+qu7tdqmTR-S%Ens$x3Eo9p zcqecmLy5LrQOvqhKS9?QiF@BmD9vrLj%&DU{}J7R*0J*kL;%sAvk#B*>!{MKzGneD z9LB6Y7NTZix1+9~VllS*!e9Pls<==NIjWYT>7S*ijc^Q1a8nC$mD(K>_@R)_F@B$F zIy=?`wJOO0re(c)x)aW#&d)QQTS_;buNhciQn(#1?U=reFk&bbytEbN@{jmlO7%!u ze-$Bq7-AYifVM`LgTuEW)+&2Ku^E~RYP5i1Sl`fFOp0lm z=CIeK2}#Ui;&h~E-;PxcXvXM@7_<3hQ@%Ao{d-tUA0QVF%-EX&N`50LjP>Aqc@3?_ zPW};SuVdCJbYB?kVyaLduxyPvNCXPKYBEux;o9I+{Z)0@`YBIYim94k9jKuikRoR9 z&b#L1PBNlpGJ1ZSEV^9u`ptU&W$Zx2>89NtW|<$r&2-^iqF@U3=aCkxD~G z+@ysy-4rNSEkqo(Oq@6SGiS{Gf%yUmhYjYPE`?IIC&d%MDUo4G6+bY%6rwBJ2`-fW ziBP*ZF&nR@!Bz0P$I;VNV2~Dn-fzjD1@uf++nH1tysRdn#DgbUGxmE|bZ+K)`&-tR zG}u#E>+U`*{PBR!TVKi6+t2z3=lAWrIp0o2uML#7erApt3jYx{zPIu_dFgOyU#j=P z02;9k*V-Lz0@FZW3xjWGMwTt7i0nHNi&-d#H)FyKtdQQA_xQBiW>pd6<+J`hpDkT= zZ7%Hn8yo9h6@UBApbt;p=)J=J6Rh-TfQj_w^f{Z4d0q$M`T5nN%1ibG)$4%RYXP42 zkW^N>?>{X@@&BCUu&{SF@a{sx82D(5e~#N?cuB~Z0-Q>EPjk-itA1XlSU=5U3m|$P zoEVJzoRC?=9PkcCP|i}P$Nm^20k_GGehoStih(*oVK+cstm zZ@aD?Ki!8EW`%s?{;|&(8ttbM&U9L zBWy1guzr4?3U&UC-npsMnkhIR?FxvO3< zjh!0qnJ!&ICW~!R(4R2hTP=4KvCrm<5Do=CZJBfJ!p zE;efDnPFUA+Eyb)*FHJw{Xg|Tnhv&+y|&wiy?O*~pO=M{$lW|Ob_MCpdagaG1PjALFi4Myhp3-?3 ziNO+6af50@>+(Xi-#J(Q7SuI+XZAih90t(8^<$glUNTdVx9Ym5c6yRsi=CJfw(cw1 zz$t3xB&ak~+}WtpFF3}nF42R|C7!+J>h1*oLf(Y~04}AIFUuPCi$&aA-N> z?k^=Z8HIB*zHGlaA+|2zC2y|)X9P|9HHWh7Ni`^lbs zc_DXMK)o4yIb^-mz)gjQL)G#Vokt>Tx?QffyMtkOqNksFP|#$uX4$QspFNT@=->Pe z+@(%_<+SK8?<-0~FsX6y0a%QqutYI-D189)Zb0I`>W1v$W(n<`92~GMEmkk_Hyl-X z=ddt~lc}Xv%vEpb8i&*;sWPZAk{51majVzf1BX(e;_*y8sOPsB{wppjOq^?iBym{U zXAbFW(11k1q-&>AAwfGdoyXfliJhSQNk@9OxK-;@sVD&yfo3+%?%mw~KW6aeJ4h0n z9U^>Hr!ib;rs|A#>(IQnTTQoODX4Gs@l2v&`efYSxFL_v#x7ypo{1KgD873fk>}X zmOTy>C6U<$*GYfh&uhdSvdw{n27bOhn3wBN_}O(=V5}5oF%2LtA7j*D+Ac_U0~+?1 zpPoD1uslWhCfm*r6~&$9=~t(-l0V2j)pL;_nIyg$%7#ZSH`d|w)HXrcttam6{@ay; zph2^Q-mTwGRaB`th7{PL@Fj4%LAaZ-Dvd+c!u-C#m#(zPAX8FLUuaf?4#x}m#>ZXR zkO@HC!+^_+%PvS14Hkx^hE*VWIQk~GV0}87zsQYqvrOLuMPp2j6W$~6VpDNzsUYn8A*WDy`i3-4{GiHKKsn# zX7P;fbI~OFza^cF#8Azz!~}BNDzg35piK--6r#y(`tc9qS+M3zb@+@2 zPODw7MCAw3k;$pSyA%2Uo`XEhi^-(lQm~4d#Iiy9w1{gtFt+b!@7*0M6e$YX{D2{T zg)EN@Ia}z~86%-F;VlXW;rn?R;qbf{e_cl}UkK!m>jOh`HJ)OVV1Rq{IeLYSWr%o) zn-WwXr1$C)84J;ig57{2Sn%x3Doa_ruVYk(leW4aL4SVpP~D4U zOHf6U0Vp;?AK-p%b&^b7RzD1kRBmxQLCb4J7;NEl`F~ZRhjiKGsS{vOe|athH*&<^ zI^Bk53H@PbF}Uxdg`XTRTs@zXgR)leay(bBChU8lXaJCarED(BJpQUhEsnaNc0-h9}Juzeiu=$eeyp8QUpmxw{ zdLxz%If|OGfZeX?AIdRGNI8(h#|&1my@{i@V|gdF6!vEvEdO^lHmeZaKW!f4f1m6X z;c^n7*PAS4&S+iSV`;HZy440u(>q!W~$G{?KT z`N#^Rs+~G0?Q0IT(w_vBKbf3JeNJauLBRCh?$_4)ygW$0JmmFC_O8Uitj_bU{vWt_ zxxg`L@ua@wM>9S@FV-G$%LA?IeZ~TkjyZMcg05pdhd2UC5XLyXV@Z)Jrm5d9Mh=y& zN=cr)u!W2S7SImJ)pNpe_2*A(M1ME{Uu~T->z12%39RfU?Y1EiqBo%RuC*JEK(nr& z>=W(Q3eV-tc1XT4VnOTf?3a5}Ve4nbfy{EaifN7uHyJ+N_ks%Zf`K^qp4-905Z#+o!2jo(;l0Ow|WqL6gGZ9A33AHLIA6&0iq z%9E}=#TrtLo&{H+{M|oNj%5&g&)r zOMJ2|>{Bk>p}~=y5?QKqZTO9}q-sDht4wLN^UpB7vEB&ek!<-2N3vh*tu3>z)k8Dp zdzk!4EL{w;G`rkc51#}s*S=)FvuV^CIu5oi`E11$4JEpSPkA@{rDAt&uKncNU>UdVn=Yk5|gzw|=Whj8bq0 zMj{5Trssey*a>qG86>2v@2?dBMv6U4mRevo`dKXGpl|F=MMd;&ca$@=M=GV8^K+4~ z74Ek4I0tj_M_YJ!@<((s;`qy6^H1JVhJ>{+=mhx59qb5dRb-#%`EK`l5-2;_zUWl^ z$z4BTHQ!HZZ=HaIXYmU%pko!kK82pGXnAZv5~}~77N8mYb{q9)&_^hh`=fMkQYa>B z2GBJ5F6?vmT8hUx#q{dCJJcXACef~f4DnJlg&-gUenDU^o)DB^2IIY-9K~!z5_Jlm zUrZzB<^K9bt5y3O_4}`mCLBULuij`MN_X!O{u-pxoDR#(cIoegd{nE|_#D&nuO@=Z zk!iw)d79Rb(TIS9p0a+Rf3J@LF`bKtmFk0R(GLvcWK$vPe&*O;-W{zSj_m+owk%o! zTxrxG^WFSTNk5Dk04JN5XMeYbt!S^1K)x(E&z*fKw86i4`kC(&%-1-LSJQAM$c%v( zA>a?~>y>cE?RhESyuO|38>tqw;?!m5sZyw(n#RD>Ge@^#`D(Uc*wU6{8(E{0QRzPr zPpFvSrKcqu$_30j-671A*UZ5Y8?{p)p_s@uDiqLg%kZDbHCylBn^a+7cHAk4h) zn^HS6iM#_u2U*9D9N|_Fhg!kLz+MGDMcaEsO?=RYbih!#GH%VGvwTTxG-&JWZZgaF zL)xg>bF$vEO+%hTs*sTfHO~uLv_u}&Id;uJRy0)soM}B4fbx4y0X?vQ1_!%bt-H!( zak;$XXYJzMkOhs0BM*NI5s`doF?ZiCY|9K1HC9b)`)l9#_4pfiPaCN5n33|}8aL3~HC zO4hOcy#@u4_|s}U@c}d{{_Hl|qDzhf3A}vL4;HI~Q6?ZuG4bzDyah`46MzqY* zV+@t!UQ1|cT;nA5n_8^Zf9oFzQLwj9-Dp@zJ>Qt3H+f!K?}~tS*RF=Mb;sgzjWlko zvzWZ)tX(}^JJt~~u$C7Ct!{4l8z#29bD1(G+#S8s9B7ggyKmLfCFM3Dj3Owxjue+G zZnnTCEF{(p0#ycO{F8o{lFxman&EJPNRhxaE`B6r9^mU*A7KB!%1G?QD4ND9L8o#g z0h7^6GucV_=mMI-t~m#FDXY%6VDcswf!yIqM?=0&a_~(2<%u_8wM{sGtCp|pMtVE{ zG4YKHRrGhWboJZ5^5enE{qVMyS7f53h%#5e(rnwrLFWnnKST z`M~06B*x}(=)}4wKu%lBF6W-n^9)g#-v&OE9VS6DPzne~0^47C@>||>&1iwUw)*7k z_jAPwe0^yy0z9k~MjSOk=qL(%z!1N?^2hb(&T|jE(I{a~k~6>C@+acbvSXzCwdb|x zGyZ!i6YVbk+eZ&Q_7Ao24NhS%b}>85@l7rSf~V-6bGC?ph;c>~IAv^5#ZUfx{V*PJ zADRQRXT4V3JTUwp`PN4kgSZ4~H#5l|8r=zfc}#p3go_SyyE!3Oj}541^UK88sZeQh zR1+`WF4Lsg%Mti2*<=k48`c-ooR4LCWMz1$+LDUI2+v;$8E`tzEEUWwUFwlRRQ*mh zg>_b`k)qaR%D>zCqtJW}F~PU}-bov`CJ}QMT|ygxxs>qfu5ESGL`D{n`y`rdfMAwg z23Csj@{=R{9mb^tNnU?{|_ZUV?7 z-@Y5_Gh@Ljoe*s~3#0D?uxF9RK(7FvPn_f#4c73cG6p95L$9GqD^#!)0-!GG&lVl$ z6FrUs4#5^r1vwhKNV9^q3pHzpL!1@Y3DTjV@zWf&&t#qK6n0tcUQg)y@98d10Fo@k zVJ#%>=a}~uz0fQ8nh&O|K$651cjI@JVah$&4DJux;iZLx%T5)EJ$jG^ndu~%w)2GA#!kjJ}7zHc*=%N>g5QQNZ zD=00)OxP6J>^|yMlHZdz_<`RVaUZ*tNTx?eU2j=Oaa)qstF2mV8dK(&25{Mll|_1m#xEB3%63F8gOy zXyXTXxrSZ47m!cX8?I#_J!XTw( z1edrG-=kU-`X4x-INVy>B2L&$avbS9M9i7hU~-H0x@*h>Q#vk`O-j#VdjDJy&pDd zEGsMr&7vp@aS*#Qo3peiSdW!vD=oeR`Hz3@};O2}4nHu0;)!Hjd4nQr($Y$}_vHt!;P& z2T9zgeM#%YNFga$kJtR@;>@}Y{!Y%(Q9B=XFFFUM+rAOSydBm{z{!i`nU82HLy*D) zGW)HY7!AHHOmnzK>odR)Kb<#=BRnjetXTGHqd&vmAqjr>X9(4qT0m(sOatoDTA&WGd258hK%SF@ntRi7^bKs0U1E@>&|C2ascK_!4edq zs&}?CZud^?75Z=a&p(T_0R+4G!j{qWbg4C0I%j-D{n==X;L_uX5j0?>_q;=iQM(~S zZ1jN+`rRP3Esq3KqLKolWOBY|t3tOQrkHNFFnYlU_k0=b!wC}O{RHZSmAy0)JX#2} z1qj;Q5OVn`st9q=B8jEy0-Uofo$Q6WM379=JO;Wc^Xy~IUsG5@a{o~|J;Y;isw$;J zk^_o$*Onw&2IbmH--Eb>WbW#t6>ijfZssxA{Pj4ACyNo=8K1|L1_c$W=A9>o02#-h zKn<@Zi(Ex3KBP(eq0@uc^`Qo#2dKpDYbwaOjg^BL;wTHtumFnRyC+Blz(D0V^;8{{778q7D29j&bn_m%K%Vh2T=Z>a~h6wV{?m z5nU{@sR-(N(I2*CxrhZ80oWHYRDDfVVO;{BP*$ zzcLGz1h21pLWM>LcB#IN8XjKaY_KH>vDB*l6)cn&doppsKtDoA1Q1Z!@4=`Tm(mIf z#IIu>Lr+=uVId@?{OiC@P+lmoP#a*OQG%L`2>wEj|Z!fiqGJyAHMI z#YkTiGK~lr4f`jk3e7}?A`>Ti(Fg3#g9OJ3-@S?K^b{QkL(bTzlCPOTGQhNUriR9F zD%DjK7vxz<7c>f6*NbeKue+tK#nwSR}XV({C>hHA7;>KY#4L zE6-E5B@SskU7uWC?OwMh1fh#n5-w1r8-e{Ck2`;eNnvij?wUD`iXr^u=fD(_ZK&uy zn|IQ=89YV*CeM~89#ssuMloIVBbpQKGafNo7;BkcKN^~0qj>Lp)M%!U+VS2YLkt5A zfkhRn>~zI=0drwU6@m`%f>OA04()LZeV}Fz24=K9*HqtlJg23=#_CHT#Y~ z{=1MFp@e^S#sF;&cKA`Aum>of+h%xWn+Pqk2*(MDy303axqi|AD~XLE8SSD;gd_o5 zMi~-czVV`(grInD3AtP3Cjv38pa$J_#zecAqO=r6t2rN_O-^ic<0t4->lT3sH&6O& zlEyewnjUm_l)^!LXmEO2-!Jap+;n)T_K6P-gHclcHCOm+2KG&LGe6h=e z-&p~xUHLCUoiqSAE32`2>Gn^MZ(bxZ0Smt<@Cr`xR}L(wHT!}bFSwuezoaf~K4-bH z4}k^(;d)me1H>TB3@B-ZaiDYGK;;& zDq`r5#>+wi5xxArdplMTlsY`u%ms*SoVQcd$Tt}!eYtnAS!8=Y_vRVV@Qe!&WAw`E z`EEMzx7Av?>SfSo=;m3%MFr}jt&&9}xQeqHpoVe7J3+@UE<6{kPecE}z-{^kAxD7{ zaAS#OVK|T;u|+sW^|zm>S7J!q@0yVq;E9g{6`Y-|Tr!TUc`euY>Q`uhD-PfzBerL7?|XqWSFW?8UbP~Q z!&hM>ef|z1QStKjfl}Ot6N@JBw4*q54wJ#((B@_#j6y<9BEEP$cmkC z_77EyHaU=uIVJk_o5|sys#mqYruXzM;r^Z7!E8;458L*_t^|s55U41@gtq}+;^KIx zQn3&KG1!qx=_u#J{C+F#5oR}yn2Q@OobFw3on{Uqed36d)Ele1k;%nS2xK|E&Y%yrE9sO$BB_u$vazh;_N)dII3v+aEM#a`>x11 zT@aPVFxPA}05fgVTe1hyA$q|wkTVr7vc*kuxrCsuZA5mO5Gg-o^KGD{1{)V=0T(&X z<(2GW>15;h8DKJaAdFD@40e84Xca;;w9TpaA8bP3=mX~LJD(a?ma#qEf^x8?uIG3yjxV$PJ#V`Z)yzT~`>?)nTI8(`|3+ z9cPtT`9m9vdZlAt<51f1a?I@7-^g zQnP zk`^q+TP9o%){Kq%p>HyfQV zffsXquZ>{h#cG!p>Ikh-fg$WyHyOrWWd~O1b#s@RikvtEr3rel^O6;9sCm0|%yPFR z>|^JjZ*VhNSDI(OJJ#l>{t|zmT`nl$#Kw9v%i?936(zxa_I;Nq7yi>yhs_TK@SdP$ zEzfVc+|RUsA{lr4Y)!YPG_duHi{;verV!pt8MH+aHWM3rOYBT|vTE%mHx(A(S1AeC z&YK;V0@rz-k{a(^2(}u^; zY3v=W-f=0dPF-x5h-SoUcxjMOgGeQY9{WW84#pLo=SOqY!En6a*wHSuzJ&}&4248b z!~j3sIc}MNJF;)zmScF%2K6GKyz_dFa+m-R7hY9R$>nd7HP`cc;kw;`uihgO4E)ep zM!i66S;9cbhVdaMsw!ITpmV4o#XoGx9Ydj*pNR=0$|HlF>x&|*`;^au=n3Og7cecm zl?{qSMA#*G$KO7;v;5p0dJ$giJqruE)N!&+6m+@b1p^wn!jbG!_H!C10Sy5)Q2}vG z!n%i9UT*i4$wJuJh)x28Yotx2VR-2An}EZ=8SI1nE%dt#lA3?8FR2sEGhwA@3_Ws^NkN(#{r=?6uy8WDCQ0B$jWD6hB! zK=BKC>p(T)1}6*XIoS~2TDxC4)Q%Dh`yw;=at^$P_S_rnedEJpx<&_Uso@(SYm69M z!lUf35$}Ng*e?n~&L>d#K%t|tvS-bgPvr2A!`H%I!bpaUDA-zn+?08dXHmyr3O~eJ zDZ0V~WwovL-g_bpmO#Jfyu5gZdxjEzf>-%&orw~zNU0#j0%&~00HrW14kkO5ArZ3_ z%t~YXEj9775t*%gc>-5d2oEWGsQ7WB}s`aQzIM{}mQ)74(49Yr-Z>MODI@ zucBdvITm??bl&I2v6VK3X^vcA-5tWw(#X%&`KLzXOmds3Xqr;2(D# zqs<0;GMc3)G8cZtF}C7MCkTkZzU1}kQ8cfuBZAf|MOvpC=H;l@B9gr9?mU>NWt_Zz z2r<2Kn&I(Ks+%|mp~k6$V6QABB4(a~xR&60TlIoTdHy0(=pl8}_^q$vO$Bl(tPhwFAfC6XcA$7@U5HZSFj6DnLyI8+MAjW@5QrS+0L)BAp!_M zB{|CA$_P-T&Xn7qhc$KKdNOb7uLNP0VPt;aqyLz7xHy=Uh2k2f%9{xsVuS{K(o5zr zhJ;QM6OY9`!2f!!;IWb%wbWOa8`<3MsFbA!Xu!-Uv8B<~w}%3W2!JH%j3y2H#sa3X zk~o0UVz$`=Jg)a!A*(@e0Zcj6v2^+$xih;oIEEp4!4})Ue{J??4{mCCmLwsy}VmRRu=F>@yr#K1-60S*=0G5Qt zj%?-#U!<+Op%3zHFAqAW;w=teBwn~F$O+W}yO<9kcHXXjr{TdIW5b{)WK0;Bw6&o? zre^VvidjlIb5F}|h1ecj52`=VKyBSRcck1Adu zfH|=ddJNh&Q75eseNbE$Nm`+Gx!wON>84EiMsL^>T`Epz!8!tCz{@T=NQqPL_nFD~ zK7vrc!5!nu@xI+&r<#;Kr_tc=4FoV2FOu_v07_q1 z?sh@4ZdXLe!GXWfxB!vS83VDiDuR*(hvhpqwB&n=y|Ph-Tnb59-_1Vk6}Qy%+RiMe zQDW%9@@OEmR4aY`Q)J{*D^8PELaGM(i=2Y;0qD$$L48v$llJw9J;*XPBuUK_=4Ac0+TXq=tttG2|w(CBzJ47s}yaHcaODk%ayAEEH z5TNpajSL(_%>J+FA2WIYYz!gHH?a*(SVr^!9nZZz(M@dUnbT~bg@Ocv>A0)*5fiuC z!aym2j2|_|ASns# zI~=-4T@eiOU|!n&E_CufImr7s zbVucTgAxb<^$V5`al>uS!;8^eUGGm9wo;}v$GzakW$o6}uhht(dlgC#c5jWLUYKVB zOEhi)j4fKc9KDaWp#4<4b_hxjD^XlY4i+vlEJ2YN;Bdye9AJv0M-_b<7^^2M8oM=0 z)X`j9n#BRPVaGMSB+g@=LO^zvkM`*n*Ph_SYp@38I;bN}(L|pDz|~v^U{}PywfP9? zayxOmDk5thIzKzdAv3-o@+oBPSGMvhAQVuy{BkIZrW_zot?oU*KX>(iXu1x6IG?Zk z>{_h8qW4vzi{9HJYSidLNC=__(aY*3YD9~+L<E7Sj{1<(Sarj!x-610I_d0k<#bg zzK13}gfJ@+%!S@Qc_nvhlq@w4<}fG`MBO=}a?4F98Cwg{Q3~ih-{rj+?4#dkFR@RL zkSP6(w~?inm6Kv|Uo?P!Vv_8At#hHg6P1~FbPRooBkOpCh3Jpe!rE7=VF_M!HdUlW zh(4{NdR~5YglvzL-=dFiZyuZ=#UF$$Ba9y2i*_o_TkO?}N3y}-2WTSm3&ueL#S8vO zqzui=%@-Z9U|ZIAJs;k>s;t0;CPZ~LhfHtl9xHf;s8z5t-u3Hlj~p(@i~Pw#iaeP5 zwP+ju_($V7v-J@|MJEm$BB+PA8Dy$5F z@IQ*9)3oK{)5m3WQP?aK_n1yVFJ!TpKWk_qD?zo9zfiN1nEAN!K0JQZ-Q#IUqTOMJ z$!ypWEFM@cwvkh5WVf6z2Vk?yfT3ir=)BB3)DJnB<;?@~STMMO3*)BR9P~GEq%Od! z`in<*(zl=e2YceVwRj1XZTg^6Bx8eLY+YJ2yD3^yVGdpp;P650BxZr6%+;_{^RDH^ zL6o=%3qiuH9>eK{O>>`O*mDRuP%$hf9%%pL`5#^lGJl=Fi#!TBt;8lsLqk}zu8oA` z8ed26G_#p&-c#ayVP>7FwtN@iwHgDlq*ma1I&& zaMO5I-f%!UxorQU`>;(;8n5YeECi){yI;+){yI%`%lY@^**79lPu6(4lJOXDR{e)b zMRg4>#I$%VtKA#YuEADM-{i?mH|B}@sFw(7p@;>hM634=e=AyXMwF!NO)V*};Eflv=D^tg&TD+rZYtUP0W^bjn6HQQ*|WPBp{lZTn;5ad!Xzvux0q$J>djsEYL zeFVswwTRrb6M>q~Mh!Pw2qdX?v%&;ZZaIc&#W#(M{h83ULso!H4;KHwUBQ7(Eudu@YZ%R*KTY7f(0>SyVFM`JmE~6%5^@;)>M7x2jgyQWujuOHIkbzUp#j)+n0! zWbSi^ER_aNi)}RnJ|`fH82XV_q950~GJ*vWOgk){;EXT^Qy`W_pVJDu+^LdYOd>`y zc|eRFLMX$!SjXci{*aJ);}c_EvACrSP%vqFBUc_mV-qOHC8-zJuueUj$q&(RkK^6Q z;hc<=`jvFd#6MwGlWD$LdKwp2Prz`ekvN$#I|i&u!~bAVwXAfbSe5R5HQSN8U#;t= z^{x;H^Cj(Z_n=Y%Ny!##N)%!~Njqcs`!YIp8vK>@S{${EGB0RV@fM>50Xv_*Nf|k& zxE^l0#AuZg6(axTO(8!BH{^Qvb`P;}J;vgiQp#z%+CQ$oNx{PkNXj&Vn6udHp(0lf zgsM@rX^OVWYKwkBS`z!CL(v~_&FgF7g03S2pFWt+wM?e{oUM}W`qKKMQ&%IISgDA5 z0KY8Z)zG&|ThHCxH;m2PJP+)@K9j>H#|Xv{nr+S8h}}@yB|&SJ_6~Xw*pf^@%Htsb zGm36NiM(s$)86bZm)@boW)&%gTwp}yYYs>9&kCj|bQd+n-aCdxH2RQk6%@txpPl^( zY51SPUrUd;*0@?$*;K5XwkTk%x<-^*+{b}JL@m4Qi&bGlHBi+kYHhv2?7piyG6P7P z&BQFeE5H{i(aiRMJ2x_7RerYFcJ^djpZy6j8*49|!&A#vn{(wQ{bQ}EZIKc8lzsX3 z=i_|oc5dx*Pk03z~Cwln{vt9X88xTFG~wE@`!0F2G*Xgj_lc9 z;s!Abg$60Y6JnGB7O@t*(S>tekd)8|DK*#IhKbPU!MvdmEUa55Lgz#@8}2{ioe>nkX=CRVLLdnI^p0~_G)dVLLXgki zVyenW$P1iOr|GgdWH<0If_;QsN-qD~8ArApRb^1h0r?GKkVvr9&%e!n4@;L~l5PVb z5SdRaIA}+sk9?3nUN^KHT2zgWR5A8fl<{~#dnZUmm=}OB+#wXHal|wHuNtEJVU9fm zn{V6T5H!y3{UvLzG#J~~K291$<%FFdLaA=GU;};vxL=A9DRV=mLjJ1UYO?iUd-gYv zPlG>Vc)>9U6u$Fz<5L~G+n_8f#CNk?@tBtOZ_O_r#V8xARd?rPl01D!2mxI5{|jo* ze?bk~L{{H4So!S#Sb~+7ur*Kn?RTOPFCoobzN1JNWJu$MJ*MTW zJ_OKcpal348yeKZ1bcF z2{Z^D*Z)ZRe!eg(YyfO%TMvEM=oADpr`1s1AS}|g6g4S*t1ik zVTZqMTf;khM3A<&B{l3}CPO`#!=NipYT6(WYRbz*{BwI3TpmQ6pfU&3*Wc zGQUZ01q!TYF%b)qxxvb*{Vq*OkT+39rnGrbp+CPdSOLXtwQMo|-;+4CEF8b1_gK5S zlGziU=>XYF`qCx3cVqlH&#Rc;V(t0Zin8PYB+b1XnLa>?_5L@Fvr0Cm50pA?U-`72 zHj#1+D1|X|j1&^HsfxGoT(1zhaL9WGORwtaY<*32TB@O*^8_gU7D56pY?j(ed4`-k&8op z@0$z|k>_@k&n6TE$r5ni*sQw#p*O#NmduKg_;N|vjngAtN!+=#aWJ$@j1xrR0d`U5 zJP(-S)sN={C&kGzd$m{)g!_K&1ZxC;@66ZY+R~ur&6Rih%$>vCWTg}Y^@mvvGYyg(-%b>_ zpUqX*A#_ciTqJXvEa^jBSkc(r`(Zb|GIW+#uzi7#G|Tu->QDs%EogPNr~FBwlb7Y^ zh}U19b&4kg2_omuqYfCYo;|CXZg>@sn1Z3^{)GJs+$ac{<#d)rK!HguR9!3~s3(W# zFr*;h*|qf&ER9E_YuFLEtBU3X^8&N?O@Ej`uy3L9ce{sPLBv2a4MUqBa9`|L#d)c- zN*dD3fW_V+YAPyT*qu(bh#Bpi%6Y?1XjGICrnCCv6Z^d2c&xu^9JARJ)3)(o9I}fj zw{b}r+U}|#kOE5BG34vUa5q>*g^g~u;@x``sot8JH4Pq4yO5n;CtEG>Tt$pADM%neeI-hH;c~L|oH!ZQna; zWWrX;bKTB%J2&Py*+2FKyaL zc7MZ;v>R6>xdxDFF>*(m9Og1kx|W;T6Z%Lc9NGyQ^NMV8`gD{Zc9R@fDfPl+=kHX| zzH}#ezOnO+GE50x^Q@Z78sndHO=}GCqMS&b<(w5bQIls2h%5&bfr*BJ7t$ea*vEe$ z(1X}P#q@>*6>=bD?bA^2x&e4?BvtuR`>ra2JeCy9;EUC|&NL5_V1-#8g#<_~S(Mhi zyg=6<(HVY5K879J!`{1^*^=h2xfv*Cllmzsu0sE&${d1Y`=0G3{Se9|U|Fc;>w!`p z0|B$9P$>2Izx)^G$Xes3&RWjkF=S=+y3Z}U>g5#IGStvWERzx?n)t+B)=lYQ#;Bg7 z|1$eB^=4lf8Q`buvD?5oj`tLG@+68Kh{dVQPy%m-`3l=3(^74~uK#^5BN&T$hB7}o zW5uN7JpNO}Hgr$Qto@QL9oQ#W-$f=l`I77+$8}qHL=vg{ zWX7I(vaN>V(-@9LuyCfAXkd^nM*+ z2gH3GN*g>4B}bW?*@RqABdtaNr-$mY?ZrWk(VfgF4(!8(F#DylrQDZ<*nAdPDrO7f z5em({&5p3tB-e)&y+WrsdmgG%k_DXCy79ojx`Iz#slcR))=`gZGOM^I9eOubCq+e= zPC#$8k`Z?%VUhW1?}S|@<&@5B_s82qeX$FN`Y8Ev6k1H6LCvGBhHaMJ{4%oU1E=0! zUdgOK9GhwAkEfDfol){||1vUEPnx&*h7^G+$L;rpuM=A!MfEfXT8Bh}8>vapOK-^~ zz{k=L?)J-cA^91dzj^p#3g<4zmC3PTXbu9lAdw>x+gaY9&ZA$pAA|p4R4`=E|D$Qg zaRjyhp1Be1eiIr2MRQhcA|ntR8PPxI%BWEzX_{%Po)@ij<{}T|^U0KXj}-^5f1~Jx zLXWCqWN;53@TGd;M^I$|fb?`t)%}4=YoNclhpxA4`S;SlA-o}bes!iD*Ec5S7eRUj zFr*wvNrB8QSgV>9ef+$?kj=l?oVvxJpq*d9G}r3xNxyQLkqkscV}i1b4Dg4sY?i%y z5ELUfDHGK#y~6h`JQc=kODPs@h5eI?$obv`P$dZuID;Hs(7BPyd~t^Dcc+^Md}Si? zLXb4E1)<&|ak|JNfnBc)6YGjf%hu~_Cx3=(P*$8SR<<&QSj$D4N87!5e?k%y62+9X zmgZnlh6ND`{Vjn0{pbo>-PnKNdoT!@;xP1boWOK zg?`2-vgy_a!T?`nSq+-DJDN+_$g3(Y++LRb9`<-6XTilRG5Z zRcTuJ`~=N7xcxV%fF4)_1CQ1d4XmiroGR@8Raw+7%rU0y>P%mB`SQ_xN!w7GR5uPz zV*UlwyWN_(&Yp#xAdkES1sjN(_jgtC!!y(A0izMJ8bxgrYc6t-K2c~o{318~uN|2#RB-Up_ zXQr}cRYGcZzv#UQzl3QWa<>iZACq-_P`f=nw8$1yJ5!X6WtqjkNFY89zmZGYvdp2j z18945o3b7$=^|^h)Uo%mPDR(;s8~7yN2V0$Fd}2jL&zXWSf+vGa$vJh|EtOrcHqog z&ZB|1*60-%a-4gz#RDe^FkRn z8<10+BeIHPD#KfVG}1}gJ949<8q0Nf!7FUzB>2tNg^uI$$!Bl@q~JZqAlEJP9;Wn? z$`u$SxOUxw=wXD)xb^2swcA}+;|VC2_+4435i5bdRSoBrHV|DMKIg3u9(4V<{ipV+ zgkno)VHKry0Nsxkd>ToHWt^54X**QLa~n4Df0>y(5c2qEex~)p5)XsK1{{!Dt((R- zTGYaU1FXom04Jf7qxS{{wux3?X(aG+n?f8gV-KxlW<}D;feYVUClCDS=LV-IwBi+`avX{@i^|4jXLK{t9O5P-R{*3!y&j}Y^L)7%= z5%b>bMuwv>0QTjBuqVe4mM0lb@3-~L+m=jEHlQ$C zBK09+Ts|_gqn%WLp`6cl9VERHK3u@9Pg zM}{;fggjC#PY~}=;C$KG#b94u_~GBw4#52IqQdh-38?Qc%nM6hRZwH`2PoG}j(Y}I z2YARgW=)$p7F=v{Pa2mB(lhrbdA#fJMk+B4vo1{PDF{504d=F!J{>qu_xbV)gd%g$ zM7(|RbuMvdO;_SUS2R*pk#yLa2{iJw5<6Onn$6^1xZJTzbptDY2hoqp_;dAqawl-4 zG7yjNe-$LXz4a#OwCBWguP)(IlZmcFCFC9N#^4vcO)NllPbW`ip zQAjxm7a1K+vg|zp)6n9_qrLTn1`**0$sJQ>3~84ECKcdteGr5e^Q~F!7|+t06^PU* ze4#yd766*HPZ)9GhNs_!0Tpl{A;!D37>eb;qJSfCd%FVuiQ+Yo#VjR;``mx+{4EU8 zH1RkkO;>y1J4;2HD2iMkdu*UcbtruMI|Bz~v(>5CEhwj9-^RLRQVOKRX-cZKki%)I zfOu4CDv`}mDg-NQJFUdhuUj)uRYxD+g#x2{Z`LME?}-uan`|#mzgpLPBqUN#4^+7fKHymg zXUW~WNAj}pNg2&Q*qZ{IB}_hLYO8sPsQzOkY9fk;h4ty`g}&#%gG&IZI2s%5|Fvk% z)i*M6j_!gFRJ;;JTUWreR_272{LSXuia&JN3f%~w$8me|;Qo|tC zEta^(;3xMVjMq{|(doJ{_->ZOG=kn(lY~0e%D)cVKpAv}@Ml_?31ANLWwK=rl;j!9 zbvyt@911U(vJyUUz?260<43b;o0%Z_^Kd1Q4ogc%#qg@%xev}VE~*h9))dgiq^&?5 z{oF{CQ6UxBXqzT?jQA5E>k0NQGOK_el`(9OJ`46Mv_b~5>u^(o57B8ZnE2-J{bj6> zVa93&P|RECJcfZ5gr*1VTTMMQm;U&;(?*jHD++k#MQWeU zq0MV*Wx`kRsmP>s87T3FfrUoLBBP7L$N?eAAF%+^VTiQPw<(eigf@sC5^1j@m7hC0mwLRsitIrsLJc62Qg?2k~VKO8e?U z-aUcxL@dDkl^CVRI?^k%SW%UA{{iz`ByX|B?bb%)p(YfZ z$lXyq6@w6{$-=7M-{hSk({ag+jM~B@+A}3~SR`W!h)n@RjNBVsyvSZ$prDCv7B8o_ zH#}Y^g0?M0^&iB5hCSXQ3EaX#PythQ)Do*A$~~5**^i}R;8VYcX)iX$K9;9-wpZb8 zd{=QG+r)cTQ=-WYS`h0P6I=bJzSQpWCWZ_x@w|epY7Vb0`zHG_IC>%Xf?nM4$}h|1rE8{hl`vF{PaEt zUB9+?La^{nx1RH^9kynA-lF%vTwgkqdak3K+-A*U_yG9VC#(faJi5zJ4f8?97xv5Z z77<@%eIHtGA5{I9>mmTdnw+ITKKz2zq)2w79J^}EN?AszllDQ&)_{G{rRUfJ26t9_Pg4fVy;J6cy z=!Xii$f`_-TV(~7T%?)xv&V03Ao*N>Uih%g8QWs{@ae(8v5E}Zo^gav^J`;)- z>)iA~uy-_3k|J}vuL6S9RgQDhP}4QlKlTFx)m(_%(Cu3G(!Qtrzo|D8#t z!Q4Ifc;48(ya3C-u-W4~5>7WfYfTOq|46+|>v#N3^I{@q2P-axhkD>r^kA}Zkd0jW z{iD4a^<7FW?2B0EN>_t9JqRhD98rYOJ+hIJp56CIW%~lFoJyu)YBcJDI}13;6pO*DajBF|cka|u*T-VF-hgS76d!?~FwAFsb9di^EW(kCSG`X+GOE+F*x z!fRiTET;163fKEk3>5H%9j9fOJyr}ykShoUKsPp3AX(Gnek!JaG>IX6p#6QIO zES<38#(k60Z~|?$DE)Q?6XFPDd+>cUosZvZVfL8z{*1 zi*B;34S0ON^OSVFnf&wD3G^|JBbrUlURSwy2CKKQC@TU9d7;ucIFCx%ZIs(ua@Ax0q}KI@u=0>#=gTOYz;)r%Z}mhW1&4 zv1jvl@y?1Gz-Z6EjMX(D-YbEKoxSA_s&kM{+Ccz0X%-SdGEOV0m37@%VlAWGU~(0> z5a>W3kML=oF?0)uT^aKbYQs4lRLRw7a~=Ar-9+K0Dw(W{$kQ6ZzO!TyKTH0@doYII4%o7JY=MZiVZR0xLi|5mU;J0KPx2f9Z|7P=wftJ8^DKlKPJ(N~ z&|mJS!X_ht8tS_mWmpZlhBBa02h*>yHJ{7qVSf1aaWdDYoM$cN+h95}<4fzu27D}? zE%g43MFNe+tiQ($MAs%Dox+=`PyXN>tv49l6!4s14L8dlQ$G*{LBqweY--sn$p>0!8cxm1S?IZe1HBeq4>-2*cPi`H48Vkvr7lr8d6aNUYme- zdsq)Ko|qCCP=I_6{d${o_I<$E!8Gi>njzHYq!U*(ZB_Y)QNO>*6o&S~K9;6>aH6bg zizfOLCw4$o-~0Q5x|mTzzvqj@Xh%Q_N1}_k#syRa+J4u19xe0YG8il0V?}Er`Jt4% zgY8i#68DmS_sLP!V5281YCE9dM*l3lk@kRY&3~ze3akkQ{?fYJ%N=wc zVnnhPbSZ}=!#GhRnbQ3Ohf!oxL!Ol6@e_=?DsxNEh}I@zw{Tg&4^o^5gYbQE5`W!y znlBOw#NL|_?t$=SKM47bCsakM@ixg385N5cTgNomd)}y{idrDo=mH6;ksvR0S{Dj= zl5cGn!K?t!67{I=pki>j?qW!QvE8KtKIdvw{o1NcBGpCI-%;s0lGU$HVM{rGn_}i{ z#2`R44(BHc#9jdXnvexk%Htj-39d+pE+a#xVUn(vq00Se`C-k;0v_-?0?=WQ{b7QE zzdA0WKu+;3ygaKmpq$xki@(1c3%V671%EM<&0O6v6n}b)RrkGlo`$jyzg)6CFYs5Q z5Ey#Z+Fs8}xDu+`|E`B1$NnYz`D=UN-3obiHt18ZW(kut9)-LY*!$r>9$cRqDs-(l z4p1HeZ&a%r({R{ej$pJMD}+&;FNrW*T;ua2i~8NE7sBSBxOq;vnH>ynl2ID|tZ6T? zHz?qL@q@Z!Z#QZA^FDlSz@q>0PV_jYD>(gCl09Je{;j+`4;P#Z5x;%^Khi}G>@m1U z0bGg$VeB^RfghOUD8X`Xs8A4J@}kc!nrgR)4><_6(vzqJlvfJAmecQyn}X;GAJCTy z1aE|<-YO^?$xM;qVSV6nK|zf>QhW!gb1(vasboT0DV(Iro@}$;?-iucx)?0y zNf#z1u}SIauMa{Q(Lwd0{kECay}6Hr0@Uuwyca74rg^34$}l`>a4Y4L{ckeK9p)&S zpw61osqYw4X43|@_m(edT!h97AHIGe3h0sGV#Dl>U>_T!2PhYmjy<8kFQH%7KH7OG z6BGMVuK#fv|At=#4$SJymwOSUeNVBHFx#R>a%7a2C6j`htE7~EG*pP<(IK|P^#~Rl z*6}$R(h6X4w^o1T=urMz`%^~l1hKJIQX$-7)sA12G-W0CC_bz(u+&SGLyZ7%6(p+* zBeg)7lmIzLw#PWkyo zx57vo>LGK~|4A}LTYFRv)M1|Wt0vZdIW42On1CM2OdP?RW^!-8`i+@OUhWFR#&b@O z_zVC|{_?wz^4AzUg1VkBz?u8Iv5UwDqv~P^a2(5>;6~L`%Qo>ew3R(d3n?*Eg2 zSTHitu2cJn?kB8BcKzFoXtCeNXm)19!%wUlK)pfk*74fIhlUa2-+!2%$6R+wNo%~= z58Nr*ys>6weg)^Gvt;~X!qdbxWt{QP z0-lQ))u4Ag0LqtV=126Sn-}e3gv^=LEX=$L!pCcV>kuibIJk=jvL)f)|0KY|T8K)~ z-bgWk8^gl-_S>~|S$d$yotQ&!gCDlJim>JE&Y_XVHwFn#s-B z6Lk{+HMQP$NNn$r{`=xdVT_!S;>4c;NF*}1mR~@WmaypGaX(cBPUi@W3oJCt@mS2) zuuJY4%&=|^vF?~Z(-J$&kDJ8sONBk~RU+)ab%>L=N;=X%iL4D2Xml^0+ad&pZo)IY ztnunYDA~njAINDV2@h_Ihki3GlYDY{jHQP`yqC#rFagMG48`FvB{7@kL|-iKxGS#~yvx2Ii{1b?qJ7F~UVl{);&=ZuX+GB)8)x!vZS{fhkL0hb_A zL=c2*{gsHi<JfngqPWaDau_67w6LAC znzinM`ud(sfk2x~GO7AG)`{k2G0Ge17f)m~MBM)#k0H|4zy5tpRoo;gOL+7_)e=A> zy%^?MD}{Mds2tD;B4iXzj8}h-Ze##dg6+QwL<$ZgLxdK8Jj84bzZ%Ft7dmoTG)-xI zL=-H2p(ByhWY0cz)om#9FzT@L;qi5|*R->b<*z;OU#9}mF*YTwTSS02-tZ9LIH5c- zBw-Pgi@^?}tsmYF^sz~QnLB`e(3&%+GqKlaFoy6lf~aHT3~^u=M@CV4ktHjv^F#*b zY}GEkYMtNP@!@)yGWgV$kt4vduQ{6khXs0}hc_C!{3v5tWZvZ%7a*2Qb6tqL1jwEc z30dpWCF0!z<6yg#$kp96!#clcPCSuMgAKx}Hd#=Qh#~@bG({1bVK&^yFH3&1AX^y@ zItew+OkY7({@{P+EHIvg(d>G0Z?Xiw-(o2dELq9Y;0k3Vms0Qivg zuogdtI3xOE7hb1`$Et;fvw!z$-*wOUwyc(m5j6zQE6vsScgCDi8)XYqi z{b5I%Q{mI+zx+XXxU3~L#;`dmcT*sxc-LJfBQ7-P4Re!BX{K$y9B1aPTXO(aL}F$P zfCO=QU5uD9oUN9m^AuB~0QU%sVtj=&5;}7s<3<2)CR?HeN=yWDTdu-^5#<8LP+IC( z@>_CoS$U5y0momN&1*TGEqin1I5{(yj};5aX$ILO3*`A}^F7ri$d_#P?`%H0#{fdy zo0}xx@xr_RT(u`MIBEcXc#Mm#ZFF~sCLIs$6PRy*W zCdcR5PEz>7tzYts??E1yG1fEYeNWhb-MR_xu*_`gj{V)Ql5y|xakepcYzcw@{K@%t zPsBN2TL4F-)Mju?j>kD%Cn?lpiX~c`MRvvi_R$+skjO2iE+z%XW#OcRVV1j7MEvH3 zHt(1vncNS7GX>{{^#{m3N+aqZ23?HdTiD#QnbYJQY2SE5zs7~q#smnE`gS@mFjxu) zY{!Sdw*c0056Hm zihisL`-Rcl-+j&86KzG9#5M9yn#;P!60Vki^-12ZyrYHv<~&K~^xl)QXJ{j}FDZ!B zUpCd9eOlG;aRIgq6Fnye%hvNmx@<+&M!=+IT8#=TNE&wlqnUN7kYAaCU{>F%X_etp zH$7F81f`f9e3^~gU*uWtS*6LZhMvuKmZ`kj>8TnD5~Tk9Sn=LihRSL|gfO=tHs_5^ zxy=1OAN{%fU6zbFA+_^49x|-!pM=3S&GU937K(m~wMbKj9gxSetGm#XE(p^Ub!q`C zddS~=;pB!{N2$&m?~Okn_AI@m*V~8To62IJ-Wa`~ZYtyZ_t^c#@8j;qgHofwwSYeSqqsS zC5D@<{eWb=!fj3=!q&t?3INa#m?-{ZrU?UbB_8<+7uF}M222QRwr&{}r`+#?|I1BK z8jwtUNp>~`#_(m8Q7P18?*KIuO8V&c&*am79kP1nHuEC%`R3i{(nJQ9?acBR9&fXv zT))xkG+f_^lG6{8kMb>Df5+$U%OpRI{_`A!#NK-J{`E(GMOpTt%}Q$YrPCA~JhxPT)zlkIeCozzXLhX$J6_fj%+nMXz@!g=Bl&a$NhN^4f-d}wjo z2Y|=A`8svG^}YG|XFilGjVAeE;6PJc+%^2q25I|l1K1Q3vWC4uk$XKgq%TKw0{OM_ zU(sD*)hEVT7uFRAR#x1OwU&>x*Cn2hYGQzdhQG?~IT0mas`9zy6Vhr8=c4tB+*%rnZandqaX z`GCLHKG)qpH-avIalEMpkfUXsI)Q)}-V*SD=U8ptk-yV*P zKoD+YPP4grq-J#VspZ+T;3BCy(ILr}u^ud%}vTB#s#Dt;ic3>+XNsUU&>y zG~P^l{ryF-?~f(O1k^cg`})dj&(_GONL*J1*xx4 zd3QHb(1~_0d_xBQewO83Io8V^v{umGY!5Fv{HETW~#?4FeQPJi1nk%tHyPIRh1)w;)GdDW|4rI=00&PNRz2xHGr%h6J_9C~j2MvY>*!--o(uraiPv{E) zLT2h##k*Myyj`c(xv%13KsheA)Zz{88+mJhe+%qsdesl}n<}Ez27MQC&6UBSq z{X16d!c!0~&D7w!h!2S-&r9O!!+Euf1>1Ae+<#86GWcZwx*aBa!FzaPoy6g4487t* zCCH}Vr}kZYy!7Iu-t&j1HPdM|YUbXTVNeuT%?JVj6Zv}on0;J-(BsBv zF4)jZrTP12%LC(pWr&Gd?~E~J!Zd0!nkuXi4vQ!ih>!uQ@KM4@6{UOE`*>yU8jdf7 zFpKS6ik)Q-+=5UK4ko0Gy9C!ilurcQzBdk&kRa<*WO?CFt%qT1L7!|`{N4AU&^OQB zaaxKH-!gy}HrtMGdg&Ay%xAXD5^0dWQ9g_t8vBre#Wq9_^szi`JW?8BIg?3=3pfFO zljb&@n*p+ekJyf?yXC^9m(K222VF@r)zPr}m&RS~*In?mZs*d|NfIQ5e&|2P4;E~! zKkaWVgD>X&H*$+v)E^I;rqemqM`u#FF>@eOi#9waFze&`;>j13O7>{=UK+g#`yqJI{r#tPldCcMDRl~l(dP>MORv@B6 zXuEt!pTC@0xca}1h!-Z66lkpf;j1gJ?}+!ysV3z%#cYkL#q{8-!$Vi8o1aGsOrp~4 z%A>Nv0yLfE^rnu{?L}(^UtMYFaNWl=SINMCJn!mysuBX81A9Ik&Mmk*s|3zzcEZ(njx;wr8 z0j7U8i^w4=7`S)%Io1bz@z6^6o1m>Cr)+rw_kU}OjwMH(h0^QF$I+;ck08yX&|c&0GVzSLP_=wmC`phSUsg4)KA95mk%x|L(9b+z0&kr0-_%|RX5w7m67%Pb9D{J$5wj33jfCX*bf;C?$P;M{p)5=3}*L#Wl~-COMV9Ax+v@ zXQRm|GJ6LE(a96y;$aDJPSh^QhsNL3OjYajDO!HQRf>}2;5Zr%`B~ndNhJXE(}gJf;$-m% z`b9dNpI(Fb7*E>(R3VHJ;ixFD)k6pO>Qw)J9c;XDgL~AGoD!ViRr~$aRVXOY%~DeY>C} z;8`-}@r($_`n*jfxyvu56u9G+gSKcyBiOzMr>Z-`(=yx1NW(G_u>Zs{E37F< z5;zG3U<`y2Ax8|C4n9d$giw{=`j>-5JB|wPT@>VG{aQ-;S%A*>G15}-eBLP5Rr_gt zA#+bM7Frt z{G?3l^iywS4AiFg31zH8UHX=j)bT(r4;y+S*j^9NMc$jxNhAdUhAB=lDyX#uWTvz0 zC^QNQ7lZM{>fV2h)a!U>!ihFl`&V?LqY-!9#4HH?9JA)mB@CB^_}E2nK7W$nvTp5A zxjlGlau{aOdrjp`u6m+0dm|`UuVtCr;}u(Xm`Xj*dqgwW=M?&Pdn#>Ho$@EOdLn>G z*PZa)kAitnl)n2h|3+UKx1v>7@2Q6MC#?MX0$zc~+2~Ru-ru+~cpq8Pl;#aCKC@oN zHOpR0I4lQmCKqYHkXrjQZ=QlVH_y2~9dM((uAOuqJ-u62`jji&{(oA44V?X|bYiCu z>u2E?wtK%}PT3**fvrDyKg=(s3NgrofmFYpUi-cyw@CI^-`7cx@ef#;CRtFQrqh#c zg$vXLP5z;AS~0LU_yLLvzuU zFdpDSIJ-LFayat(Pn+|4`Ez=DM+z_3wmO&%z0F(Dn{IXFeDFbtWP-avdp+bjv0@eb>aSOcv~Hm?*Hg65)5JxuR? zcX(jB9sDpvsf{s^z(^3i+$fj&`-)?xg>uf9h)RsL9aqQqe<3 zW|@K1H&twiEdN8_0}Y9%)!mqxn^MCL5Xi>&m3-eh@#dx8qJ@CFB8^Yz{iVtOYwxSt z+U$aMAp{6kT#CE9w76^0;%P=TqzXL(Ut#nC@qP1Ak=S z(|*6f{CsGoV|xEZme}uIGrNnM%I_mrJA?c4HP{PDSy*9P_}&hefjjW^E9LqHpH$IF zH|V?^8wyzJAAGoe7Jkd%Z&cxLN-~ZGNbilT2&5qu>M`5UpebV(RjT>VdK;>=AO!6@OpFjqhdrD@>{?x zi5g&G#1D$5kwne2A&;V$+Y^$T_XneP>B76r{*VG#4&=dDFx%F(qA@=BdE3>D&(HKy zgsKsg%K?WUEpUJSZ50j~q^L~91_JE-wg}4Kt>T%^du>}^_?T|6jKMZD;!k*QT;#Yu zS$xaXa^3$j-`3SQ?o;6Y+`79)_6PIiqaWkg*VWC_f0LQlI4QSiGcDCt*xgqY)uL6+fQLMR)aGi*+*`%2D)m}! z3Z{%XaNY?7p>?L%BF|FC2SA-b-)BHcekDARLXDO6%@Wo!C>w;my-c0-X_vV06SU$w zCgL4Wyx&%MOv-qI;i!J*!7Br%WoK#D)MBjE?58={Gp z3f1Nb3{Sk|IL{8i*LsH?a7`!hp{K~}yZ~x)6UWQa- zk>V{CQ*rFxpk7v`VJ2YxvkRFe!2kz4-NuutY8n2x#`Gb$l|2g}?n^?1PERDQQ)Vx;nZ&lJEyW$}u}_VxP5uSowI7 za@?56_hJf(?H^NAc_IHkdet5>KF$!>p661y96yccFh8^t5*uZ|ArSvwekF3~rn57! z`!F0rN<}|DcbPI#Gx+6HcFMCEz_Ya-B>3x=M%_#>EUREBi9_+*h)_qn!T~v1C}aXK z6W#Fst2)5>JKVxygPMg2Xe+-rvMs^^3P)k6`(3~qylIm*q(7n6{M3(5tNPxARw}1<|DPchb>z4?<R$-e;DFPF}LnB_aG#J}SX!P?0Z5<4Y2@EbR33+(Mc%Rh_qsK4M zMZPFcgEJsrWY#$Hf>kLv!HgD#2s-yILy3-!icG-6pv&t=``cLDO5j~ruBp;-`*UsM z%*|Jg`B83h-u=gXj`3@f8P`w-frIeJZnZS)l`qbFl9Mc=nXxy(dj(U89(u zpz`njXsoW*1~zA?xp@hkvW1X5qX-1e{(+SZ1%u*m`cuuJ9#9AAXh|Y}i&%j$NyS*L z?lB12q;cuo`jpv^1?k7imF0C+>i?7rJAgI~7P#_5bRwI^e~R~|m~MVwj<41Do%S6X zGPRKAZn@gX9q3JDpyL)?m-&p>k=xLquOmXeeek}Q;&LN6H~a~sFugSp!YnRM8~}7Q zro;mXAK1M@8dW$$r$lx_#KZ|JTz8Xnms`1LJ_3P4n@=8D)@xcIi7dyF5w)8PNN2=b ztHZStRE@ByxAZtk|0W7Ye@jx%eK&f#W0?P1`CBC-lvJkc9=x>c*EK|f_mAzW*VsE3 zio74{H-9V;rLj0MG49IZX^$!!E9VF;fx0%uglOSnr`TrN$ZEbQo^9p^knn+%)UVH7 zL*Ju8L-6(A&*uv}K2yG#^H337M)@%_Ax#D;jxq%0T#f~y0iH95?>k>pFpFP&rY%Dj zS?iH^@#&$E;Y=AzlV{&y&Lo5e7yC!ZVxsmPN<`aPoXiSM4&Yyc{d=7?x{E-`d;q$Y z+HH{Z0XlbV%Bw%)V_vD)fr508^Kb{)&%Inl@V&d{rqRo@7?$nPXv#xY<>onVZgl2H z;!a;d#;Zp7I0@*<&9KH>g?atg*XV2YJ}y-#yfa4q`j(ol*N+fbqD&Q1qMwem3CAs7 zzwB+l3EpmddVDNPZ?v5H+@U>2jBVXjJ z^L^;Ro1*ZS*|>#~h@q0dy}2o;$cJqb_V&#~XeUq2dn&8yxSO+w6cqV@o&+|}bAscj z-!`s@7aH3}FT+-LE8e{@XdzH@qYp-(G+AlL{2mKDTTkS$0oHxBHfh9=Ivw?ASYbg7 zW)`~lE%@c2o<0=60(ZD^zifsYFQl7)1S|U|B+WnsUq>>n9v0oy#7Uo~?+W~1DA>-_ zOu)!ipRVsd5_h=`;@s@KR1JR-QF^~{k@o1wSxkYRe0MAkD>h1!J}_mf39cG{z`f5J z`6jgf^5LG^BU7UxRK)owLd9ebNT)m3Ho1jBaP;WVKcSK3p!O&w$o%CbDkdp0%HJ9S zzU~A=rF^t${JQyP;5IzzyGg~To$$&`S#TU^TL0`s z_I-CmWlhU)$jRMT)p0c_JbGhBeCKW_samHScXL3Of8HdXC8pTB;1A1V30Zkz0N|pp zRR%K8L|~uP?zk?HBW9z}T?Z1!hpv6(l!N-{?!1L+ENZ)XtMIIFVrX)lFwqjnL|K9h z2EtjhKDeh|b8jtAKAhE>ovk;}yxi8F_Y$uLb?V#0{rHiH4oX0$&L9Pb=leUh%#EHr zInIL>T!f}U)3nar8khI21{xZjk&j@zd4Uh7i`K(I>!(w`6FNM!fUCFwMH6vxnunya zT$#~(zoYoSlV-(H@jHLNom@S}Oj5iZlE3lElE1Cep0XG>yw&aN5AgZwghUyOjs<^uX)`B=*7yG!qS9ba#=F#c{{hF0>7 zx8`D`jFvqT!P(ivONO@kPz2hqkTV!tZBf&>k5uBb(-5#`b7KJ`q?7&eX=p4YGPy{Og zl5m6dvJqU&BroW!r~&=x6c^Mp7^Btd@jdMAE!TGW4}}I~ak55s$)}P2J)a@U0V@57 z%y*)d5^_Yp7)JZL-Dja{{iR=2<7&7M9iO4O6Y-7z$bi8PAIF{v)L7S5f7Ip^)-DWd zQLxC)KONm8qzee(0N1AkXAK0VnDiuWUIfxRR(L{twn_6FP9d$@ai~yk!ly))aQ^uk zILoF9hTn>&af z{Fi-sR~%2i*5Fh%r6ObO4}1OnDT5!tEl^eYN#R0Fh6HB!kh2bjc?t85Yf$X*sx;JP zHyqN-3Riz|8$G#@&Kj9l6hH6~MAkHh63{}HF9^j8Ff5nqR;@AUzNZcFSwpWqqbj~w z=YC#ymnwVvT-NP<-S&E%=&+Y`s6Mv&L4^*+i`gqbh{M=XQWWvJ`;AZ7L}H1gb-{Dt z67l>m*zD`JU$Hc5TRra#a_EQWW}LxE_3%0$n9sw3;96QBiN8MuUZ`S(DXTExia&#M zV}G|gEfilAXNKlaj8RZ3a)6^asXd_BbVj@5 zz2-Xtvm~Knp*mWa@86F^J=nY5Z(7{hKEA#lHXsYK9Uy!I1Zu8w17jp*pH{0*O*L~E zg#Og%E#T5UJnC>SrIOk2f&Q>Up#bN^pxk>?#!eQCWkbsPXdh@XpT+SY1M+Db{ixLY zp{sm*3AnR~Og9}Md6ZgjBeK~f1AT^~A+*&z<}gNqtG~G53D1?b%OOjh3)XERdy)Wj z|A2mF45Tjt@ml;10XkrUF~ZaxPAz^aQfTvc6c_a8vwau#XkG6|xAU77xF*cCGk{#X!wI8$o@jUl;>fLap-mgaSjmi_$Iv{hr zjjG+>JV#lT)b=J2_6gxdC}peHTv{!hy@KzrZ4XACfem ziX)TeMAOgjDAo5z-x{lmBiuv&L$Udr_?hUkNUFF~3>A6Efq~3Wx)bvVN}XHt@WLU= z64YIb!lzQh2geVZEmSluOvkAoEq_$LSeIQ%C`3M2IN|NaU3FiL(*xqj-ZGZTu=+^M zUAcGO$cbHiqpvO**|EfS98_CMprwRiU|mo@K5tH-{&`%0?*8yvQ;P5mja&G^*+Un6 z)Nvu(zn^rtxI_9xjJskaMhXO#qbZa4_oS#B93U$2?mGbUa=!5L68q9q8Gs+xZ|)6R zQpBIEu1cc9Pd|cKzKr%YT2Y2J2xKwFT$PQw-QurhN9_C3;@UD#aGqhm<#y^!j{~l3 zhY62;b41-os`E@JwPf#VSw!F@NsgINk3G9`bqCX=vsfB46rMib79~Fm^(EvnU!>|i zAtnb-lPH7F_sF7zqt)pG;JvTUe*O*9mQfIwKTUS(FU@|l{9g9Pes#qnZ=eSw2(RIj zuMNIn!8e&urOzi&^viz#HqgB?^=3D6kaK#1(`3uydybq#gAa{2-j>@G&T9qr4eQSP7SFYs_N{JA|5VwuA4DX5IhUMYOo5#Hk2>`f#Xn)S!v2&twA*G{3;6 z-j22d?H|nJ;Pa6g?_kT2bU{cZNRe}t3&}FbP>5#*p)WF--sStD+w=2TOC@g8mo$1z zvEx=rsL1cDX%|P~ET5liqNflH02uj<23!?#@Dl+pvAW=G?F?RCa!6%Y9%t7ydo*=; zN(1@q{IhA8uv|Y>5!s_VI7I;;1!y0zbqJ$cBh+6%*nG-vW|#5n&&l0=SDB}(A%F*XwGVkw7y`FC+3?dj z4lXU@SaNS{oNeC+1SjRgP{(6XZkW zsFDxeO`xs&bq-X>pgg~i!`RioxjMp6m_Ji z%*PzgX%<`U#+05IOe3~c55I?=bFT?DaAF5v`JAOhJCmBrrl|gjqtQMxW3ElIsw+>r zF4B7o;vYE%8e7`a$q!Dwy$gBP+VynM~UM+}Q2{(uP8Pj%!Ts@o@4U6`Qx zbQZ66ACK#drZ-2Kw6$(VCO-705;?X&dLkhv`j6d8xYn}{%ZiZvz2i$%YAn&GG+K+i z52D=Nk0c9lNd+_oR7p>#Fu<{}@IiYew$#{6jDRr?^h1jpCj#B)Ojj;2X;5naMQ3&Y zAgs-VMh_O^ON}Xdtmx)J)4>YBe02Z5H~h*in(j}5LXleQX3MhGZP?~7-TMS>hZV)v z%R1V2SihVWNH_{Cez)OccUjb^!1b0v^NHc`!NsZLB%Qk#iR5<0ww=R~rw5 zR#b=Ho#p3IR8?bq8d7;+0lww7;=^2((6<5kJauQ5fEM+R%+yt4+0jBXIU6CK?l0lQ2Q(80r8}(a`EAe}l9OLW1HYDrp4?gg4;xg$TzWu>MToA%=n{G4o_LVG@VXAh&g=hc9u z?UqW5`mm%AjD|{nQTvnM92Fk0Sa!Q^G31a8lwgbW z+0JEtzK2;4rj{t8>*Q)Ynp%R1it-;-xEGOu2a@s+sQi0lJ5LRNO%(IISk6h?w_^YG zlwsS7n%I#`=JPkV=}?%fDDTGn%~Q}-A;$ZYRCjAU6xZ|NQ>{SPrSMgn^waHt=56f*mj-zHVt^$_slkH=+` zw?b5|5uQ%`EaF=sz}jy6%Kq_UpKZ89&!spmq(0Z>-&Ob3z))n> zOr3CqRNC!wch*_yPl^a53^QuI{FR$H#CzFxBSNht@JrZUg403-)q z$%HZ+!Jr@`a}$yqxDiT$J#GJ%it2T>ipC1K`ojI~2?is-5%jZdkb@BpV`~^;Ptr>? zDpOT#>I-XDsjML?+cKoH?5=;FJ?Clb>pG;~DwLWSGp?M98j#Ug-m}yn87fR5S`{TR zw^J-CYcSkf+RrZ*WP1Eca(52DBKUfhdpT&`%&BWx;_1|SM^z|n~5KXE#&8T zeK;d`r?%jGf;P`BknnveW9VDKZ9uZ}>19nPSL~%X%Z1n1G1!{*vqmfo$a@biCJ(e` zjWX2D)|r*1{yqKl&hi$iXy!M4N^8!VPaKSxk6s|_kJdLtloM|!7d|TEqp06nGI1_ogG0m;k^h(#l;FZ)T5aXsrXQoJ|JJ}5*D1zpiF6~lT15# z*{M!FP&E0Y&p+HF_1oybqq_e7xj)~`<8iWI%^$Jwaq<#~V=^yQF55MSlmzq2+M~{M} zJ6}?P7aG+sTrSr(f>0eZCaV~XM5W*?JbaF41*pfq$ZO*GNB~d3>q6Hh+f8622o60f!=FeXH1m-HT9~6KeTUF!U(|1;H=H%@C8D64+w^3$_RP7R! zm*VCF5cT6TIc@oaYLKT{@37vUYYs0OMz?(SNWS?Bqs=)?^-f$KCHH;r(=+3@_)Fm{FRQQK88I~!aBs?}af;WzSrOnM znr%rc>U7=uOq&`%1_3N*&CI2Q6e8o#yz3VHhEo%I=~w`pth^GQD3-eFG4of5`RiAjfowmN`C#3{yNExPSaf({-nv`{LL;z zcnlxQphY%IzwhO$Uo*`K!hV*qjK-LP1dxOpEh&B3<7jd3`^*XDH+1NS-i~miwSpx_ z-xnVOT2W6X3+hWOBDvo_RJnN#ON!1>7{TFWh4_N}X)^&#mC#m^P<$xy>+gUb6ha z=**<9KWaoE237lmrtoH*y(FQa2;^nNMO38_((nt=F|%Rawq<-C^Aa5wch_$QtSl^w z$Qn&Rg!BOXjfcLZs=aqj;9y+Lu>Ih`z!Q{mG~?w~&PoFV3xST)%}Z%^M;|D#8Cem< zAq@;2Rk!h8MwAjCM|xMs-J(s+>QiSp0xCTZ=>a-JO1gXWt-VmM?jCUjMDE8jW;KLf*?WJob)?5~;X zh#E;{!Vfdd5mW~fkgqq%Tzj{{;n0r8pgQsMnC`~A!@rWO-&h~l4+82pS$DsC+B$%NOdy9K5u#iK>L#12(-n*NpA2Now#y?^sV-Cq_Q6^Ah+) z14YOK9fA$^w?3~#jd`Y#5aYpK^9RP1&t>z8uJ+Iu$Ko}OFwZgH>02`k+GRN>^TW6% zk+VM@K!!7?On-E^kfv?FVPA&7=~fdM4N9(zJ!=h$5+>{wa?w?tp!T2KKb#xULh~P_ zrW9E5#ewhk3G}mXbQpF!j9MGXAKm2GIUIZ{$ui*CBUpkhkmvykn^zsMa{M+?CqJdB z6FsH2kIx47f1+eK6l=!`~l2k$4Z-Ku^gHtkHkUZJA^kX3SyjV6%h z2m0YB-DS&@=7pHevlp|F70opo{26E_3T{0siFO9IuC1@_kT=sgw;kua*oxr6b&(R>INZP6Kv7k*!u z;y4yx=-c3&&C?)x5+S-YTAJa*KzMe@R`*epCC|PO!soAJl{%6eN)k> zV-CBqa0foJG?yF|LAPdF_)z~zIaIh&r^L+#LJ~RKBg;|n2d76i(V#aR1za-7TwG+( z{dAAfYbsKo+7I7TsHR%Xr7l5{P);%*x@TVspvOB~(9!e%TNM2=SJS_AST$>2cL3;aIp#UI$ zM5xDFa(Ol%3)x|=G7}9kQ5rh01A_)^ek#!=wU_2UyqsKjE!n5YPOA5fe++N{#*o;=Df7Ld1=o9xN z`=7N&O~l5+cQ@p=(t0c^u{@g1@Buz(ctI><9U1gbReOw~cmJ}m)9{;r8l7y?Jn;x! zorJB1a-LHgpnBG>Y0fX-{L!r-a!#cug5;km`u#p*ZkNHO)qm2wV*OCgr(Su)8L<0` z!O)d-2_v+609z(;s3_rFybC*9>RLXP7G@tdCPhpwdzi+7j67k`X?-1kLxrJGJ7S4G*z%kkUQv3ll z`Z6P<|C~{$x>vlkXroLrApGi*!jffiZu{0R_mjd`d5+Ga)ErHUWrR3{>H}G#smRu#U!dlCp6$iz8>gbxPxiTD-=pJ zr5CTLu#e=pF*JdkZftEp-5jv=2-N3GQuonF6fKX0&!CU-o4%83Ya>pPFdh_o-BdWJ z0D94zI0Ez(%=FYpIpBxP(BD0u+2c*SKL666l~~!FBld!Y+LkTJ7dIXP{S1#VO6oTs zIH1v*h^Qo?YuybuRozng$ZuQ|z9&mG<7NqFH>=rQ25+!|c}VvwXl0!y4{Yqur|!(R z<+^QRH1`dh;{PFV;tXAO4fFVDG2BS@P;K}&IzBL>Jj%kigyD`hcFnz}4&VC%F9NvX zGg$OgymTO&Fek8GOWdR$8*o7sTS;t!X4Xm}%gYP(_Wj^pF$*}l#x z+4ib(u_bnXd-kPdq2`F%U2zcx|K2=Yk{G04uaJD1uB#aRwsu>Yy6PpEpi)D*Au&hj z6Hbv0S3b1^^{3qdyeJLzIVA}jBFVj6 zS&hGp)g-~lpWTBhjK4ziRfRsZ(|ZWEVx*Zc7#BT!CWFp%pH9!?pf2}QE^Z{4y2(ef z2mFEKghyNxS%lw*FMCXE&tM>gxE1Ggd;}}xy3@Z zAI8&1&zufUWo39$mf<6o}~Z z$-{f+-J3i0K~=qAh&c1`8ih-l-+L4+z360GIcNTg`thrxrSRt*Ncj6Ub7PnQe-T@b zOimuTH`?yx+}EEi^jXzi!w+1=meW z)O%8V3LcmHV^fDafCmB!^I)&t;FBsz=*5|2u2=j(d%-Mp~xzdV>V5J!0Uxf7Kg zM-}|^iJ2KXd+3rpR{>PWg!pF2_-xD*nLIlWnP#jD`^B^Ask6X6_qqGrS+a{xc03MjC1T&&*4 zN_}G~KI!Y1eG?QDLliyOf6gRle;xA=H4$k5Y@AP$oX-v1*Yal8;Ijxep3E+9>^ICo zNs}jOjgyqO3MGw#IFC&g`EJrX7a_UC_Izz0)=2+AZqO(AD9A1p z|Alf(1LA@y5zx*$2#oSiAU4?Q_t8{ec6C2*JyGG*paTW4q-r0JvKCz^E}_@HvwR$% zMC_=Y-N{k)r(F0*`SjhP6C<(MQPpQ!*?ii;v%}+A7W0A6u3}G1m>94X4s<;+zOy#6xRcrymJ4jho3W@Nq@)t0)4;iQp zv{WFT4?&yWdlXnR^IKF`SfZV!F*llvf~H}{Md~eN6~5G0n`*5mrX3Jzh2p*ZD(g>! z5KVq#W<=o!aA{gg&NYzE&0F~$JcW<bYfO+ zaK3suf7dL@C2H^)>0RGG=NzAB>tyDBh7s2re20K;m#BN zh+DcMI-YECne6Ft45OW15yAn;<#gQjpUkVFvFX1#X(CJ*9Y@pTj9-G?^^v=g8u-wlhBlj=$6G{NLCh8D@A8_|Z3dPB!Z~E3f#iT@$ z;)3oUBdctz_l1B?%Sm#+D(S`NmJ5o1X z+SboTWx6L-LT$wlXxyk5Zg(?10|md8n{Rb|^+;yeoqCXBud(&?-@$h?$a%yP z9khEftP}B2?O&8uq`!i3=8;fo<+-`vd-4B>SZ7h(S@uh#nI6Jmv0fql=$LHZ_s3mQ z5X%>W{Exe^SRy`YAR&#*=ARMtO#$556RCzeUw>-6AJ(uXfl39^^ct$=+#s?rFpM4p z9jHmE84aio5Jmgpc`?}75ykqK*R1!GBz6-=s>^iF*$VS`8n|h2CIpY2o|+1@I{(f?OO^FaL>QFN*#c$@98h3CVOjM zO~0k$o>zuvw-L~@S+fT<=KqOr4M9Xnwl`N_xgJ-lUt!{O@9k4H+&5}xK1}jNw2(LR zZZCmScdnsDwv8?bOTxYQ5<#p+7vc-`%XwBP$35lgwE64FAB~{~`iTl-N=fVmtiJD$ z@ic!`=iPxFWk0=v-Iw=Jc;=6=<2+0~YKtiuzmi|*4F3>BKHIO!S0_AMj9DLn-)|e)7iouG&vo@SOl_8LZhRh(z3$oznxuJ%Ron4cLm**k`a<$XX!K## z%Zv+dH0Wy=)~@xxOA(M0PYZsEOHkdReX>`N$uZGJaA!>4?we+Kke?1I*|k?{tDw zOKE?^YoNAg_K)wD-y=kVH$7`>_1NncA5AjC%#D(@@;l+<_6!VvYsBQ5-Cvc_X;&_hV?RcqWkwsE?uw`is#!d%F&SU!1H_|k)9*ZcUWSi z1Hc~RN>30=tGeR&!WHMcN!9R~Qlivnl;HyCbc4)>bQJ5J1tpR;2>CFV0Gk7NO+&qS1oJ z`{?MoT?|D0e_b>#JK-z@wLIMO6xhcFY26}Y@AMWq>wJ~&tn%shdU)VbTKYaLyxmfR z8r-ArDv0v0jpWz8IAf;H4-xNAz@&2mMu=fS#`)#C=_V!Y1H`aN$_aO4uRMd5PC>Q5 zxpV98i6z8>Tm?T~UyHrnzhT3*k9Q%WfrX^jY zyYq9&e}O;Ubc@*|Y0q81UbBi66{XPXEq@^5vnDV*iHCcJ6w(Jp81Myj;pojqTo(6% zFzK{eZn4y%H!;s00@1@6azkK#)6?qgEBRF&a5IOmnT+6{0$KV5KmNbQV;8|3l~`l8 zekX%!YrZp3HAT?mcHlGc@~W{Rx?M!yV);kHYoET27=s_gF{}HeDO#kBe=E+OaEK)l zSQ&XW+qwt)j+gPmY=_^_1@AkDlLn_XFX;0%pmN0@j*@W7zuXGGg%F=TWG z+j5As4s#Ku{f}l~yjr(R?07 z?yWI>u8z)E#~os%MS=zTMsI8gXs0CHw?HNZ)oD30?#@0duo5%!J@(QVChcL0vdKF-8a^jiIb; z?L&;1%~y?6G$Hhhe7b0OLdup7@}`fuz8=6fFKiMa>_lW}#xA=99a($V&6*1;F7@Ls z>7z%{ER0=kua};ubvZJ^hs8orBC(2{o85!;$V7f(!_jQ`jj3o@>0fdWm9I| zZqG-vfZCU4r|#cRlO&55v1=c^Ow%s_R?br&^HlA21VR5G!=G~G=MvN}JZY>08xFCj z+0tnNFUWGhbR$MfB37xsQBPUCD3)`yZuRiZENd&x4cwfLMxNY99m=oOV)}keX2fqP zYRBrCV2IRB2?@G38Kd_ETzv`lULQBt`0b;~h;ODYYS+e#UWh^*6`wx%z#X|ctlV!r z5xjrPOu9-nG90fU8Dd?;{I$L_^L}jZ{G{X1rUqk~&P9jav34rYF4lWrr8#vr0HUTn z!i5%(^gn>4Kw0#goZa1#a1L^MFzx27q1c224s9emlIbO8X!{x`-FZZ4K$DmV4mLDYbeYu1n9$dPGpwh`_imH zoMe4_R}uC5&-z0*TNVCG+q5?KvfYxDo=TT_ee^6o6 zt&r3O`q7*=8xBQ#T27?lOgj8-&21ORQoJmeA6gA6@Cc|MQt5xW3K&l$vxl%7fa$S|}Um7on_VRVLZ0i48b}w)^_xq5sT`SI5*F3c#BR+2T0qNkOP4_){qi z=X}=-j@oD@OoE%IW5nWNT|L!)ENHOu6Zqswp2aWqWxD3qvP*|eF6Q!SYin$!cZd97 z>s3v#z`h%)VQWLLGIJ2JQ-LV}dG8Z|v%G5LY9fsE67-b5$K z{O#Bx7J?x$GeR=6A(>`ncYl8E9{Q2#Jjex zMXcYiwJzFI5)BC&k7p)7{@DrvXZN_ zP7rFwGDO%X-vAiDzX0KoAa)=qquq}s!YH^3^ZvWPZ0K~BPCF!{FAO3`P-QWSzXFtj3 ziwghYoSK_mqZ#2)hJ3xx&vp@PZ)}H0VdhSfx9b_WzSeF^3O;&vor(0g?NQ9j2xZ=# z{EDb{rfF1OOiwxadMogL_l@uqlOjA)S&kV94*(wqF|I-VBNOaEWN5J-G*%!zVnCow zLm{GR2;2YugdFg{>OKcK@IU9ur6D4xYSOG-@P8ls|7ZR)hySnc2Q2j5kd}~yw?6{~ O`N&8ph}Vi52L2zL*R3M} literal 0 HcmV?d00001 diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox deleted file mode 100644 index 684cd0d1a2..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Templates/vfx_aces.hrox +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - 2 - 70 - - - 2 - 70 - - - 2 - 70 - 13 - - - 2 - 70 - 17 - - - 2 - 70 - 2 - - - - - - - - - diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox deleted file mode 100644 index e915a24084..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Templates/vfx_linear.hrox +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - 2 - 70 - - - 2 - 70 - - - 2 - 70 - 13 - - - 2 - 70 - 17 - - - 2 - 70 - 2 - - - - - - - - - diff --git a/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox b/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox deleted file mode 100644 index 42659cf81b..0000000000 --- a/setup/nukestudio/hiero_plugin_path/Templates/vfx_rec709.hrox +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - 2 - 70 - - - 2 - 70 - - - 2 - 70 - 13 - - - 2 - 70 - 17 - - - 2 - 70 - 2 - - - - - - - - - diff --git a/setup/nukestudio/hiero_plugin_path/Templates/volume.png b/setup/nukestudio/hiero_plugin_path/Templates/volume.png new file mode 100644 index 0000000000000000000000000000000000000000..47119dc98b5935b2df0168541708a0336cf2cbcd GIT binary patch literal 19442 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 literal 0 HcmV?d00001 From b586383a0cc1c268f06455d43197671c4480a51c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 11 May 2019 18:19:47 +0200 Subject: [PATCH 08/41] fix(nukestudio): fixing to new hiero api --- .../Python/StartupUI/nukeStyleKeyboardShortcuts.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py index 36a30e3a3c..41c192ab15 100644 --- a/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py +++ b/setup/nukestudio/hiero_plugin_path/Python/StartupUI/nukeStyleKeyboardShortcuts.py @@ -12,25 +12,22 @@ except: from PySide2.QtCore import * #---------------------------------------------- -a = hiero.ui.findMenuAction('Import Clips...') +a = hiero.ui.findMenuAction('Import File(s)...') # Note: You probably best to make this 'Ctrl+R' - currently conflicts with 'Red' in the Viewer! a.setShortcut(QKeySequence('R')) #---------------------------------------------- -a = hiero.ui.findMenuAction('Import Folder...') +a = hiero.ui.findMenuAction('Import Folder(s)...') a.setShortcut(QKeySequence('Shift+R')) #---------------------------------------------- -a = hiero.ui.findMenuAction('Import EDL/XML...') +a = hiero.ui.findMenuAction('Import EDL/XML/AAF...') a.setShortcut(QKeySequence('Ctrl+Shift+O')) #---------------------------------------------- -a = hiero.ui.findMenuAction('Show Metadata') +a = hiero.ui.findMenuAction('Metadata View') a.setShortcut(QKeySequence('I')) #---------------------------------------------- a = hiero.ui.findMenuAction('Edit Settings') a.setShortcut(QKeySequence('S')) #---------------------------------------------- -a = hiero.ui.findMenuAction('Monitor Controls') +a = hiero.ui.findMenuAction('Monitor Output') a.setShortcut(QKeySequence('Ctrl+U')) #---------------------------------------------- -a = hiero.ui.findMenuAction('New Viewer') -a.setShortcut(QKeySequence('Ctrl+I')) -#---------------------------------------------- From 2cf60c1fbb4ac249597010d9153f9612ac5dd052 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Sat, 11 May 2019 18:20:14 +0200 Subject: [PATCH 09/41] feat(nukestudio): work on plugins --- .../publish/collect_assumed_destination.py | 2 +- pype/plugins/nukestudio/_unused/collect.py | 191 ++++++++++++++++++ .../collect_submission.py | 0 .../{publish => _unused}/extract_tasks.py | 3 +- pype/plugins/nukestudio/publish/collect.py | 183 +++-------------- .../plugins/nukestudio/publish/collectTags.py | 14 ++ .../nukestudio/publish/extract_review.py | 3 +- .../nukestudio/publish/validate_names.py | 5 +- .../publish/validate_projectroot.py | 3 +- .../publish/validate_resolved_paths.py | 4 +- .../nukestudio/publish/validate_task.py | 5 +- .../nukestudio/publish/validate_track_item.py | 10 +- .../nukestudio/publish/validate_viewer_lut.py | 3 +- 13 files changed, 245 insertions(+), 181 deletions(-) create mode 100644 pype/plugins/nukestudio/_unused/collect.py rename pype/plugins/nukestudio/{publish => _unused}/collect_submission.py (100%) rename pype/plugins/nukestudio/{publish => _unused}/extract_tasks.py (97%) create mode 100644 pype/plugins/nukestudio/publish/collectTags.py diff --git a/pype/plugins/global/publish/collect_assumed_destination.py b/pype/plugins/global/publish/collect_assumed_destination.py index fa6a3d9423..4f9d180843 100644 --- a/pype/plugins/global/publish/collect_assumed_destination.py +++ b/pype/plugins/global/publish/collect_assumed_destination.py @@ -9,7 +9,7 @@ class CollectAssumedDestination(pyblish.api.ContextPlugin): label = "Collect Assumed Destination" order = pyblish.api.CollectorOrder + 0.498 - exclude_families = ["clip"] + exclude_families = ["clip", "trackItem"] def process(self, context): for instance in context: diff --git a/pype/plugins/nukestudio/_unused/collect.py b/pype/plugins/nukestudio/_unused/collect.py new file mode 100644 index 0000000000..4e20202fe0 --- /dev/null +++ b/pype/plugins/nukestudio/_unused/collect.py @@ -0,0 +1,191 @@ +from pyblish import api + +class CollectFramerate(api.ContextPlugin): + """Collect framerate from selected sequence.""" + + order = api.CollectorOrder + label = "Collect Framerate" + hosts = ["nukestudio"] + + def process(self, context): + for item in context.data.get("selection", []): + context.data["framerate"] = item.sequence().framerate().toFloat() + return + + +class CollectTrackItems(api.ContextPlugin): + """Collect all tasks from submission.""" + + order = api.CollectorOrder + label = "Collect Track Items" + hosts = ["nukestudio"] + + def process(self, context): + import os + + submission = context.data.get("submission", None) + data = {} + + # Set handles + handles = 0 + if submission: + for task in submission.getLeafTasks(): + + if task._cutHandles: + handles = task._cutHandles + self.log.info("__ handles: '{}'".format(handles)) + + # Skip audio track items + media_type = "core.Hiero.Python.TrackItem.MediaType.kAudio" + if str(task._item.mediaType()) == media_type: + continue + + item = task._item + if item.name() not in data: + data[item.name()] = {"item": item, "tasks": [task]} + else: + data[item.name()]["tasks"].append(task) + + data[item.name()]["startFrame"] = task.outputRange()[0] + data[item.name()]["endFrame"] = task.outputRange()[1] + else: + for item in context.data.get("selection", []): + # Skip audio track items + # Try/Except is to handle items types, like EffectTrackItem + try: + media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" + if str(item.mediaType()) != media_type: + continue + except: + continue + + data[item.name()] = { + "item": item, + "tasks": [], + "startFrame": item.timelineIn(), + "endFrame": item.timelineOut() + } + + for key, value in data.items(): + + context.create_instance( + name=key, + subset="trackItem", + asset=value["item"].name(), + item=value["item"], + family="trackItem", + tasks=value["tasks"], + startFrame=value["startFrame"] + handles, + endFrame=value["endFrame"] - handles, + handles=handles + ) + context.create_instance( + name=key + "_review", + subset="reviewItem", + asset=value["item"].name(), + item=value["item"], + family="trackItem_review", + families=["output"], + handles=handles, + output_path=os.path.abspath( + os.path.join( + context.data["activeProject"].path(), + "..", + "workspace", + key + ".mov" + ) + ) + ) + + +class CollectTasks(api.ContextPlugin): + """Collect all tasks from submission.""" + + order = api.CollectorOrder + 0.01 + label = "Collect Tasks" + hosts = ["nukestudio"] + + def process(self, context): + import os + import re + + import hiero.exporters as he + import clique + + for parent in context: + if "trackItem" != parent.data["family"]: + continue + + for task in parent.data["tasks"]: + asset_type = None + + hiero_cls = he.FnSymLinkExporter.SymLinkExporter + if isinstance(task, hiero_cls): + asset_type = "img" + movie_formats = [".mov", ".R3D"] + ext = os.path.splitext(task.resolvedExportPath())[1] + if ext in movie_formats: + asset_type = "mov" + + hiero_cls = he.FnTranscodeExporter.TranscodeExporter + if isinstance(task, hiero_cls): + asset_type = "img" + if task.resolvedExportPath().endswith(".mov"): + asset_type = "mov" + + hiero_cls = he.FnNukeShotExporter.NukeShotExporter + if isinstance(task, hiero_cls): + asset_type = "scene" + + hiero_cls = he.FnAudioExportTask.AudioExportTask + if isinstance(task, hiero_cls): + asset_type = "audio" + + # Skip all non supported export types + if not asset_type: + continue + + resolved_path = task.resolvedExportPath() + + # Formatting the basename to not include frame padding or + # extension. + name = os.path.splitext(os.path.basename(resolved_path))[0] + name = name.replace(".", "") + name = name.replace("#", "") + name = re.sub(r"%.*d", "", name) + instance = context.create_instance(name=name, parent=parent) + + instance.data["task"] = task + instance.data["item"] = parent.data["item"] + + instance.data["family"] = "trackItem.task" + instance.data["families"] = [asset_type, "local", "task"] + + label = "{1}/{0} - {2} - local".format( + name, parent, asset_type + ) + instance.data["label"] = label + + instance.data["handles"] = parent.data["handles"] + + # Add collection or output + if asset_type == "img": + collection = None + + if "#" in resolved_path: + head = resolved_path.split("#")[0] + padding = resolved_path.count("#") + tail = resolved_path.split("#")[-1] + + collection = clique.Collection( + head=head, padding=padding, tail=tail + ) + + if "%" in resolved_path: + collection = clique.parse( + resolved_path, pattern="{head}{padding}{tail}" + ) + + instance.data["collection"] = collection + else: + instance.data["output_path"] = resolved_path diff --git a/pype/plugins/nukestudio/publish/collect_submission.py b/pype/plugins/nukestudio/_unused/collect_submission.py similarity index 100% rename from pype/plugins/nukestudio/publish/collect_submission.py rename to pype/plugins/nukestudio/_unused/collect_submission.py diff --git a/pype/plugins/nukestudio/publish/extract_tasks.py b/pype/plugins/nukestudio/_unused/extract_tasks.py similarity index 97% rename from pype/plugins/nukestudio/publish/extract_tasks.py rename to pype/plugins/nukestudio/_unused/extract_tasks.py index c841b604f1..29c1350cc9 100644 --- a/pype/plugins/nukestudio/publish/extract_tasks.py +++ b/pype/plugins/nukestudio/_unused/extract_tasks.py @@ -1,11 +1,10 @@ from pyblish import api -from pyblish_bumpybox import inventory class ExtractTasks(api.InstancePlugin): """Extract tasks.""" - order = inventory.get_order(__file__, "ExtractTasks") + order = api.ExtractorOrder label = "Tasks" hosts = ["nukestudio"] families = ["trackItem.task"] diff --git a/pype/plugins/nukestudio/publish/collect.py b/pype/plugins/nukestudio/publish/collect.py index c2eeb25235..de6b2b3fca 100644 --- a/pype/plugins/nukestudio/publish/collect.py +++ b/pype/plugins/nukestudio/publish/collect.py @@ -1,12 +1,10 @@ from pyblish import api -from pyblish_bumpybox import inventory - class CollectFramerate(api.ContextPlugin): """Collect framerate from selected sequence.""" - order = inventory.get_order(__file__, "CollectFramerate") - label = "Framerate" + order = api.CollectorOrder + label = "Collect Framerate" hosts = ["nukestudio"] def process(self, context): @@ -16,173 +14,44 @@ class CollectFramerate(api.ContextPlugin): class CollectTrackItems(api.ContextPlugin): - """Collect all tasks from submission.""" + """Collect all Track items selection.""" - order = inventory.get_order(__file__, "CollectTrackItems") - label = "Track Items" + order = api.CollectorOrder + label = "Collect Track Items" hosts = ["nukestudio"] def process(self, context): import os - submission = context.data.get("submission", None) data = {} - - # Set handles - handles = 0 - if submission: - for task in submission.getLeafTasks(): - - if task._cutHandles: - handles = task._cutHandles - - # Skip audio track items - media_type = "core.Hiero.Python.TrackItem.MediaType.kAudio" - if str(task._item.mediaType()) == media_type: + for item in context.data.get("selection", []): + self.log.info("__ item: {}".format(item)) + # Skip audio track items + # Try/Except is to handle items types, like EffectTrackItem + try: + media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" + if str(item.mediaType()) != media_type: continue + except: + continue - item = task._item - if item.name() not in data: - data[item.name()] = {"item": item, "tasks": [task]} - else: - data[item.name()]["tasks"].append(task) + data[item.name()] = { + "item": item, + "tasks": [], + "startFrame": item.timelineIn(), + "endFrame": item.timelineOut() + } - data[item.name()]["startFrame"] = task.outputRange()[0] - data[item.name()]["endFrame"] = task.outputRange()[1] - else: - for item in context.data.get("selection", []): - # Skip audio track items - # Try/Except is to handle items types, like EffectTrackItem - try: - media_type = "core.Hiero.Python.TrackItem.MediaType.kVideo" - if str(item.mediaType()) != media_type: - continue - except: - continue - - data[item.name()] = { - "item": item, - "tasks": [], - "startFrame": item.timelineIn(), - "endFrame": item.timelineOut() - } - - for key, value in data.iteritems(): + for key, value in data.items(): context.create_instance( name=key, + subset="trackItem", + asset=value["item"].name(), item=value["item"], family="trackItem", tasks=value["tasks"], - startFrame=value["startFrame"] + handles, - endFrame=value["endFrame"] - handles, - handles=handles + startFrame=value["startFrame"], + endFrame=value["endFrame"], + handles=0 ) - context.create_instance( - name=key + "_review", - item=value["item"], - family="review", - families=["output"], - handles=handles, - output_path=os.path.abspath( - os.path.join( - context.data["activeProject"].path(), - "..", - "workspace", - key + ".mov" - ) - ) - ) - - -class CollectTasks(api.ContextPlugin): - """Collect all tasks from submission.""" - - order = inventory.get_order(__file__, "CollectTasks") - label = "Tasks" - hosts = ["nukestudio"] - - def process(self, context): - import os - import re - - import hiero.exporters as he - import clique - - for parent in context: - if "trackItem" != parent.data["family"]: - continue - - for task in parent.data["tasks"]: - asset_type = None - - hiero_cls = he.FnSymLinkExporter.SymLinkExporter - if isinstance(task, hiero_cls): - asset_type = "img" - movie_formats = [".mov", ".R3D"] - ext = os.path.splitext(task.resolvedExportPath())[1] - if ext in movie_formats: - asset_type = "mov" - - hiero_cls = he.FnTranscodeExporter.TranscodeExporter - if isinstance(task, hiero_cls): - asset_type = "img" - if task.resolvedExportPath().endswith(".mov"): - asset_type = "mov" - - hiero_cls = he.FnNukeShotExporter.NukeShotExporter - if isinstance(task, hiero_cls): - asset_type = "scene" - - hiero_cls = he.FnAudioExportTask.AudioExportTask - if isinstance(task, hiero_cls): - asset_type = "audio" - - # Skip all non supported export types - if not asset_type: - continue - - resolved_path = task.resolvedExportPath() - - # Formatting the basename to not include frame padding or - # extension. - name = os.path.splitext(os.path.basename(resolved_path))[0] - name = name.replace(".", "") - name = name.replace("#", "") - name = re.sub(r"%.*d", "", name) - instance = context.create_instance(name=name, parent=parent) - - instance.data["task"] = task - instance.data["item"] = parent.data["item"] - - instance.data["family"] = "trackItem.task" - instance.data["families"] = [asset_type, "local", "task"] - - label = "{1}/{0} - {2} - local".format( - name, parent, asset_type - ) - instance.data["label"] = label - - instance.data["handles"] = parent.data["handles"] - - # Add collection or output - if asset_type == "img": - collection = None - - if "#" in resolved_path: - head = resolved_path.split("#")[0] - padding = resolved_path.count("#") - tail = resolved_path.split("#")[-1] - - collection = clique.Collection( - head=head, padding=padding, tail=tail - ) - - if "%" in resolved_path: - collection = clique.parse( - resolved_path, pattern="{head}{padding}{tail}" - ) - - instance.data["collection"] = collection - else: - instance.data["output_path"] = resolved_path diff --git a/pype/plugins/nukestudio/publish/collectTags.py b/pype/plugins/nukestudio/publish/collectTags.py new file mode 100644 index 0000000000..5137a9f22f --- /dev/null +++ b/pype/plugins/nukestudio/publish/collectTags.py @@ -0,0 +1,14 @@ +from pyblish import api + + +class CollectTrackItemTags(api.InstancePlugin): + """Collect Tags from selected track items.""" + + order = api.CollectorOrder + label = "Collect Tags" + hosts = ["nukestudio"] + + def process(self, instance): + instance.data["tags"] = instance.data["item"].tags() + self.log.info(instance.data["tags"]) + return diff --git a/pype/plugins/nukestudio/publish/extract_review.py b/pype/plugins/nukestudio/publish/extract_review.py index 2b688cb53c..537988e0ad 100644 --- a/pype/plugins/nukestudio/publish/extract_review.py +++ b/pype/plugins/nukestudio/publish/extract_review.py @@ -1,11 +1,10 @@ from pyblish import api -from pyblish_bumpybox import inventory class ExtractReview(api.InstancePlugin): """Extracts movie for review""" - order = inventory.get_order(__file__, "ExtractReview") + order = api.ExtractorOrder label = "NukeStudio Review" optional = True hosts = ["nukestudio"] diff --git a/pype/plugins/nukestudio/publish/validate_names.py b/pype/plugins/nukestudio/publish/validate_names.py index 571359a3b7..169febd764 100644 --- a/pype/plugins/nukestudio/publish/validate_names.py +++ b/pype/plugins/nukestudio/publish/validate_names.py @@ -1,5 +1,4 @@ from pyblish import api -from pyblish_bumpybox import inventory class ValidateNames(api.InstancePlugin): @@ -10,7 +9,7 @@ class ValidateNames(api.InstancePlugin): Exact matching to optimize processing. """ - order = inventory.get_order(__file__, "ValidateNames") + order = api.ValidatorOrder families = ["trackItem"] match = api.Exact label = "Names" @@ -39,5 +38,5 @@ class ValidateNamesFtrack(ValidateNames): accommodate for the ftrack family addition. """ - order = inventory.get_order(__file__, "ValidateNamesFtrack") + order = api.ValidatorOrder families = ["trackItem", "ftrack"] diff --git a/pype/plugins/nukestudio/publish/validate_projectroot.py b/pype/plugins/nukestudio/publish/validate_projectroot.py index 459b487bd2..b9b851e0d1 100644 --- a/pype/plugins/nukestudio/publish/validate_projectroot.py +++ b/pype/plugins/nukestudio/publish/validate_projectroot.py @@ -1,5 +1,4 @@ from pyblish import api -from pyblish_bumpybox import inventory class RepairProjectRoot(api.Action): @@ -32,7 +31,7 @@ class RepairProjectRoot(api.Action): class ValidateProjectRoot(api.ContextPlugin): """Validate the project root to the workspace directory.""" - order = inventory.get_order(__file__, "ValidateProjectRoot") + order = api.ValidatorOrder label = "Project Root" hosts = ["nukestudio"] actions = [RepairProjectRoot] diff --git a/pype/plugins/nukestudio/publish/validate_resolved_paths.py b/pype/plugins/nukestudio/publish/validate_resolved_paths.py index 110b8772b5..f1f0b7bbc8 100644 --- a/pype/plugins/nukestudio/publish/validate_resolved_paths.py +++ b/pype/plugins/nukestudio/publish/validate_resolved_paths.py @@ -1,11 +1,9 @@ from pyblish import api -from pyblish_bumpybox import inventory - class ValidateResolvedPaths(api.ContextPlugin): """Validate there are no overlapping resolved paths.""" - order = inventory.get_order(__file__, "ValidateResolvedPaths") + order = api.ValidatorOrder label = "Resolved Paths" hosts = ["nukestudio"] diff --git a/pype/plugins/nukestudio/publish/validate_task.py b/pype/plugins/nukestudio/publish/validate_task.py index a48ae115d8..ff8fa6b6e1 100644 --- a/pype/plugins/nukestudio/publish/validate_task.py +++ b/pype/plugins/nukestudio/publish/validate_task.py @@ -1,5 +1,4 @@ from pyblish import api -from pyblish_bumpybox import inventory class ValidateOutputRange(api.InstancePlugin): @@ -11,7 +10,7 @@ class ValidateOutputRange(api.InstancePlugin): do. """ - order = inventory.get_order(__file__, "ValidateOutputRange") + order = api.ValidatorOrder families = ["trackItem.task"] label = "Output Range" hosts = ["nukestudio"] @@ -40,7 +39,7 @@ class ValidateOutputRange(api.InstancePlugin): class ValidateImageSequence(api.InstancePlugin): """Validate image sequence output path is setup correctly.""" - order = inventory.get_order(__file__, "ValidateImageSequence") + order = api.ValidatorOrder families = ["trackItem.task", "img"] match = api.Subset label = "Image Sequence" diff --git a/pype/plugins/nukestudio/publish/validate_track_item.py b/pype/plugins/nukestudio/publish/validate_track_item.py index 3c8b3c6cfd..3fe7a739ce 100644 --- a/pype/plugins/nukestudio/publish/validate_track_item.py +++ b/pype/plugins/nukestudio/publish/validate_track_item.py @@ -1,6 +1,4 @@ from pyblish import api -from pyblish_bumpybox import inventory - class ValidateTrackItem(api.InstancePlugin): """Validate the track item to the sequence. @@ -8,10 +6,10 @@ class ValidateTrackItem(api.InstancePlugin): Exact matching to optimize processing. """ - order = inventory.get_order(__file__, "ValidateTrackItem") + order = api.ValidatorOrder families = ["trackItem"] match = api.Exact - label = "Track Item" + label = "Validate Track Item" hosts = ["nukestudio"] optional = True @@ -21,7 +19,7 @@ class ValidateTrackItem(api.InstancePlugin): self.log.info("__ item: {}".format(item)) media_source = item.source().mediaSource() self.log.info("__ media_source: {}".format(media_source)) - + msg = ( 'A setting does not match between track item "{0}" and sequence ' '"{1}".'.format(item.name(), item.sequence().name()) + @@ -55,5 +53,5 @@ class ValidateTrackItem(api.InstancePlugin): # accommodate for the ftrack family addition. # """ # -# order = inventory.get_order(__file__, "ValidateTrackItemFtrack") +# order = api.ValidatorOrder # families = ["trackItem", "ftrack"] diff --git a/pype/plugins/nukestudio/publish/validate_viewer_lut.py b/pype/plugins/nukestudio/publish/validate_viewer_lut.py index c9dc87a95b..08c084880d 100644 --- a/pype/plugins/nukestudio/publish/validate_viewer_lut.py +++ b/pype/plugins/nukestudio/publish/validate_viewer_lut.py @@ -1,11 +1,10 @@ from pyblish import api -from pyblish_bumpybox import inventory class ValidateViewerLut(api.ContextPlugin): """Validate viewer lut in NukeStudio is the same as in Nuke.""" - order = inventory.get_order(__file__, "ValidateViewerLut") + order = api.ValidatorOrder label = "Viewer LUT" hosts = ["nukestudio"] optional = True From c8395f43dda60a79b51ab56643d6dee9b22c5e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Sun, 12 May 2019 22:41:40 +0000 Subject: [PATCH 10/41] fix(ftrack): check if asset is in avalon db before trying to delete it, throw sanitized error otherwise --- pype/ftrack/actions/action_delete_asset.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pype/ftrack/actions/action_delete_asset.py b/pype/ftrack/actions/action_delete_asset.py index eabadecee6..96087f4c8e 100644 --- a/pype/ftrack/actions/action_delete_asset.py +++ b/pype/ftrack/actions/action_delete_asset.py @@ -85,6 +85,12 @@ class DeleteAsset(BaseAction): 'type': 'asset', 'name': entity['name'] }) + + if av_entity is None: + return { + 'success': False, + 'message': 'Didn\'t found assets in avalon' + } asset_label = { 'type': 'label', From a48af95ffe2f7f3377d5899049fff31fa39d4348 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 16:32:39 +0200 Subject: [PATCH 11/41] root for launcher in avalon_apps is not parsed from arguments --- pype/avalon_apps/avalon_app.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/pype/avalon_apps/avalon_app.py b/pype/avalon_apps/avalon_app.py index 0b2553c4d9..547ecd2299 100644 --- a/pype/avalon_apps/avalon_app.py +++ b/pype/avalon_apps/avalon_app.py @@ -40,15 +40,7 @@ class AvalonApps: def show_launcher(self): # if app_launcher don't exist create it/otherwise only show main window if self.app_launcher is None: - parser = argparse.ArgumentParser() - parser.add_argument("--demo", action="store_true") - parser.add_argument( - "--root", default=os.environ["AVALON_PROJECTS"] - ) - kwargs = parser.parse_args() - - root = kwargs.root - root = os.path.realpath(root) + root = os.path.realpath(os.environ["AVALON_PROJECTS"]) io.install() APP_PATH = launcher_lib.resource("qml", "main.qml") self.app_launcher = launcher_widget.Launcher(root, APP_PATH) From 1b33c88ca58156d276fb6bda1f628453540c500a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:04:32 +0200 Subject: [PATCH 12/41] component also returns start/end frame and frame rate (fps) if have these info in input data --- .../widgets/widget_component_item.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 2e0df9a00c..14f6a8312d 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -10,11 +10,16 @@ class ComponentItem(QtWidgets.QFrame): C_HOVER = '#ffffff' C_ACTIVE = '#4BB543' C_ACTIVE_HOVER = '#4BF543' + signal_remove = QtCore.Signal(object) signal_thumbnail = QtCore.Signal(object) signal_preview = QtCore.Signal(object) signal_repre_change = QtCore.Signal(object, object) + startFrame = None + endFrame = None + frameRate = None + def __init__(self, parent, main_parent): super().__init__() self.has_valid_repre = True @@ -291,4 +296,12 @@ class ComponentItem(QtWidgets.QFrame): 'thumbnail': self.is_thumbnail(), 'preview': self.is_preview() } + + if ('startFrame' in self.in_data and 'endFrame' in self.in_data): + data['startFrame'] = self.in_data['startFrame'] + data['endFrame'] = self.in_data['endFrame'] + + if 'frameRate' in self.in_data: + data['frameRate'] = self.in_data['frameRate'] + return data From 9000ca0b83eb94c20ccc390e6a8b730b59df9833 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:05:41 +0200 Subject: [PATCH 13/41] removed get_ranges (death code) --- .../widgets/widget_drop_frame.py | 28 +------------------ 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index cffe673152..de2bbe19a3 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -182,34 +182,8 @@ class DropDataFrame(QtWidgets.QFrame): 'is_sequence': True, 'actions': actions } - self._process_data(data) - def _get_ranges(self, indexes): - if len(indexes) == 1: - return str(indexes[0]) - ranges = [] - first = None - last = None - for index in indexes: - if first is None: - first = index - last = index - elif (last+1) == index: - last = index - else: - if first == last: - range = str(first) - else: - range = '{}-{}'.format(first, last) - ranges.append(range) - first = index - last = index - if first == last: - range = str(first) - else: - range = '{}-{}'.format(first, last) - ranges.append(range) - return ', '.join(ranges) + self._process_data(data) def _process_remainder(self, remainder): filename = os.path.basename(remainder) From 56d1b8d1e48f2d3d9c82ec6adede46cf196b7f06 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:06:14 +0200 Subject: [PATCH 14/41] pasted path from clipboard is normpathed --- pype/standalonepublish/widgets/widget_drop_frame.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index de2bbe19a3..6be69584d0 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -49,7 +49,7 @@ class DropDataFrame(QtWidgets.QFrame): else: # If path is in clipboard as string try: - path = ent.text() + path = os.path.normpath(ent.text()) if os.path.exists(path): paths.append(path) else: From da376847404bc61d0fc9d5b37be60e5ca52fa5f0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:06:40 +0200 Subject: [PATCH 15/41] collections add start and end frame into data --- pype/standalonepublish/widgets/widget_drop_frame.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 6be69584d0..2fd14c26c7 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -170,6 +170,13 @@ class DropDataFrame(QtWidgets.QFrame): repr_name = file_ext.replace('.', '') range = collection.format('{ranges}') + # TODO: ranges must not be with missing frames!!! + # - this is goal implementation: + # startFrame, endFrame = range.split('-') + rngs = range.split(',') + startFrame = rngs[0].split('-')[0] + endFrame = rngs[-1].split('-')[-1] + actions = [] data = { @@ -177,6 +184,8 @@ class DropDataFrame(QtWidgets.QFrame): 'name': file_base, 'ext': file_ext, 'file_info': range, + 'startFrame': startFrame, + 'endFrame': endFrame, 'representation': repr_name, 'folder_path': folder_path, 'is_sequence': True, From 9c0c3a346559d1c2e9b495ca4b7c75959c441549 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:07:45 +0200 Subject: [PATCH 16/41] added method for enhanced getting data from ffprobe --- .../widgets/widget_drop_frame.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 2fd14c26c7..1fe2777826 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -1,5 +1,6 @@ import os import re +import json import clique import subprocess from pypeapp import config @@ -244,6 +245,25 @@ class DropDataFrame(QtWidgets.QFrame): break except Exception as e: pass + def load_data_with_probe(self, filepath): + args = [ + 'ffprobe', + '-v', 'quiet', + '-print_format', 'json', + '-show_format', + '-show_streams', filepath + ] + ffprobe_p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + shell=True + ) + ffprobe_output = ffprobe_p.communicate()[0] + if ffprobe_p.returncode != 0: + raise RuntimeError( + 'Failed on ffprobe: check if ffprobe path is set in PATH env' + ) + return json.loads(ffprobe_output)['streams'][0] return output def _process_data(self, data): From ec38c4b048307a4b97afe29f7dcb2b4018aa6eb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:08:44 +0200 Subject: [PATCH 17/41] get file_info replaced with get_file_data which collect more information --- .../widgets/widget_drop_frame.py | 69 ++++++++++++------- 1 file changed, 43 insertions(+), 26 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_drop_frame.py b/pype/standalonepublish/widgets/widget_drop_frame.py index 1fe2777826..4e99f697cb 100644 --- a/pype/standalonepublish/widgets/widget_drop_frame.py +++ b/pype/standalonepublish/widgets/widget_drop_frame.py @@ -216,35 +216,9 @@ class DropDataFrame(QtWidgets.QFrame): 'is_sequence': False, 'actions': actions } - data['file_info'] = self.get_file_info(data) self._process_data(data) - def get_file_info(self, data): - output = None - if data['ext'] == '.mov': - try: - # ffProbe must be in PATH - filepath = data['files'][0] - args = ['ffprobe', '-show_streams', filepath] - p = subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=True - ) - datalines=[] - for line in iter(p.stdout.readline, b''): - line = line.decode("utf-8").replace('\r\n', '') - datalines.append(line) - - find_value = 'codec_name' - for line in datalines: - if line.startswith(find_value): - output = line.replace(find_value + '=', '') - break - except Exception as e: - pass def load_data_with_probe(self, filepath): args = [ 'ffprobe', @@ -264,10 +238,53 @@ class DropDataFrame(QtWidgets.QFrame): 'Failed on ffprobe: check if ffprobe path is set in PATH env' ) return json.loads(ffprobe_output)['streams'][0] + + def get_file_data(self, data): + filepath = data['files'][0] + ext = data['ext'] + output = {} + probe_data = self.load_data_with_probe(filepath) + + if ( + ext in self.presets['extensions']['image_file'] or + ext in self.presets['extensions']['video_file'] + ): + if 'frameRate' not in data: + # default value + frameRate = 25 + frameRate_string = probe_data.get('r_frame_rate') + if frameRate_string: + frameRate = int(frameRate_string.split('/')[0]) + + output['frameRate'] = frameRate + + if 'startFrame' not in data or 'endFrame' not in data: + startFrame = endFrame = 1 + endFrame_string = probe_data.get('nb_frames') + + if endFrame_string: + endFrame = int(endFrame_string) + + output['startFrame'] = startFrame + output['endFrame'] = endFrame + + file_info = None + if 'file_info' in data: + file_info = data['file_info'] + elif ext in ['.mov']: + file_info = probe_data.get('codec_name') + + output['file_info'] = file_info + return output def _process_data(self, data): ext = data['ext'] + # load file data info + file_data = self.get_file_data(data) + for key, value in file_data.items(): + data[key] = value + icon = 'default' for ico, exts in self.presets['extensions'].items(): if ext in exts: From 13d551ba72f5d3b663ff804ac9033f33d6454ceb Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 13 May 2019 19:10:25 +0200 Subject: [PATCH 18/41] removed not used code --- pype/standalonepublish/widgets/widget_component_item.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 14f6a8312d..43aa54a955 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -16,10 +16,6 @@ class ComponentItem(QtWidgets.QFrame): signal_preview = QtCore.Signal(object) signal_repre_change = QtCore.Signal(object, object) - startFrame = None - endFrame = None - frameRate = None - def __init__(self, parent, main_parent): super().__init__() self.has_valid_repre = True From 08712b4cb5e228227e8e81a70ef0e3a021acd714 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 14:49:22 +0200 Subject: [PATCH 19/41] version spinbox added to middle widget so user can choose version --- pype/standalonepublish/widgets/__init__.py | 5 +- .../widgets/widget_family.py | 62 ++++++++++++++++++- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/pype/standalonepublish/widgets/__init__.py b/pype/standalonepublish/widgets/__init__.py index cd99e15bed..4c6a0e85a5 100644 --- a/pype/standalonepublish/widgets/__init__.py +++ b/pype/standalonepublish/widgets/__init__.py @@ -20,15 +20,14 @@ from .model_tree_view_deselectable import DeselectableTreeView from .widget_asset_view import AssetView from .widget_asset import AssetWidget + from .widget_family_desc import FamilyDescriptionWidget from .widget_family import FamilyWidget from .widget_drop_empty import DropEmpty from .widget_component_item import ComponentItem from .widget_components_list import ComponentsList - from .widget_drop_frame import DropDataFrame - from .widget_components import ComponentsWidget -from.widget_shadow import ShadowWidget +from .widget_shadow import ShadowWidget diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 7259ecdb64..a2276bf7f9 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -51,6 +51,19 @@ class FamilyWidget(QtWidgets.QWidget): name_layout.addWidget(btn_subset) name_layout.setContentsMargins(0, 0, 0, 0) + # version + version_spinbox = QtWidgets.QSpinBox() + version_spinbox.setMinimum(1) + version_spinbox.setMaximum(9999) + version_spinbox.setEnabled(False) + + version_checkbox = QtWidgets.QCheckBox("Next Available Version") + version_checkbox.setCheckState(QtCore.Qt.CheckState(2)) + + version_layout = QtWidgets.QHBoxLayout() + version_layout.addWidget(version_spinbox) + version_layout.addWidget(version_checkbox) + layout = QtWidgets.QVBoxLayout(container) header = FamilyDescriptionWidget(self) @@ -63,6 +76,8 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(QtWidgets.QLabel("Subset")) layout.addLayout(name_layout) layout.addWidget(input_result) + layout.addWidget(QtWidgets.QLabel("Version")) + layout.addLayout(version_layout) layout.setContentsMargins(0, 0, 0, 0) options = QtWidgets.QWidget() @@ -86,6 +101,7 @@ class FamilyWidget(QtWidgets.QWidget): input_asset.textChanged.connect(self.on_data_changed) list_families.currentItemChanged.connect(self.on_selection_changed) list_families.currentItemChanged.connect(header.set_item) + version_checkbox.stateChanged.connect(self.on_version_refresh) self.stateChanged.connect(self._on_state_changed) @@ -95,6 +111,8 @@ class FamilyWidget(QtWidgets.QWidget): self.list_families = list_families self.input_asset = input_asset self.input_result = input_result + self.version_checkbox = version_checkbox + self.version_spinbox = version_spinbox self.refresh() @@ -103,7 +121,8 @@ class FamilyWidget(QtWidgets.QWidget): family = plugin.family.rsplit(".", 1)[-1] data = { 'family': family, - 'subset': self.input_subset.text() + 'subset': self.input_subset.text(), + 'version': self.version_spinbox.value() } return data @@ -204,6 +223,8 @@ class FamilyWidget(QtWidgets.QWidget): if asset_name != self.parent_widget.NOT_SELECTED: self.echo("'%s' not found .." % asset_name) + self.on_version_refresh() + # Update the valid state valid = ( subset_name.strip() != "" and @@ -213,6 +234,45 @@ class FamilyWidget(QtWidgets.QWidget): ) self.stateChanged.emit(valid) + def on_version_refresh(self): + auto_version = self.version_checkbox.isChecked() + self.version_spinbox.setEnabled(not auto_version) + if not auto_version: + return + + version = 1 + + asset_name = self.input_asset.text() + subset_name = self.input_result.text() + if ( + ( + asset_name.strip() != '' or + asset_name == self.parent_widget.NOT_SELECTED + ) and subset_name.strip() != '' + ): + asset = self.db.find_one({ + 'type': 'asset', + 'name': asset_name + }) + subset = self.db.find_one({ + 'type': 'subset', + 'parent': asset['_id'], + 'name': subset_name + }) + if subset: + versions = self.db.find({ + 'type': 'version', + 'parent': subset['_id'] + }) + if versions: + versions = sorted( + [v for v in versions], + key=lambda ver: ver['name'] + ) + version = int(versions[-1]['name']) + 1 + + self.version_spinbox.setValue(version) + def on_data_changed(self, *args): # Set invalid state until it's reconfirmed to be valid by the From 58d941d76df7326887d97043dab7605abc7a2c24 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 15:16:20 +0200 Subject: [PATCH 20/41] fixed asset not selected bug --- pype/standalonepublish/widgets/widget_family.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index a2276bf7f9..9a347cbeab 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -245,10 +245,8 @@ class FamilyWidget(QtWidgets.QWidget): asset_name = self.input_asset.text() subset_name = self.input_result.text() if ( - ( - asset_name.strip() != '' or - asset_name == self.parent_widget.NOT_SELECTED - ) and subset_name.strip() != '' + asset_name != self.parent_widget.NOT_SELECTED and + subset_name.strip() != '' ): asset = self.db.find_one({ 'type': 'asset', From f472285f657f7e83cb7736c0d28d849ea6e29de2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 15:16:51 +0200 Subject: [PATCH 21/41] changed font color of not enabled inputs so its readable --- pype/standalonepublish/widgets/widget_family.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 9a347cbeab..102705f98b 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -35,7 +35,7 @@ class FamilyWidget(QtWidgets.QWidget): input_asset.setStyleSheet("color: #BBBBBB;") input_subset = QtWidgets.QLineEdit() input_result = QtWidgets.QLineEdit() - input_result.setStyleSheet("color: gray;") + input_result.setStyleSheet("color: #BBBBBB;") input_result.setEnabled(False) # region Menu for default subset names @@ -56,6 +56,7 @@ class FamilyWidget(QtWidgets.QWidget): version_spinbox.setMinimum(1) version_spinbox.setMaximum(9999) version_spinbox.setEnabled(False) + version_spinbox.setStyleSheet("color: #BBBBBB;") version_checkbox = QtWidgets.QCheckBox("Next Available Version") version_checkbox.setCheckState(QtCore.Qt.CheckState(2)) From 35e5e7319f8efe5db41998e52e90ab3c43a7e417 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 May 2019 15:43:58 +0200 Subject: [PATCH 22/41] feat(nukestudio): plugins work and basic Tags integration --- .../publish/collect_assumed_destination.py | 2 +- .../plugins/global/publish/collect_presets.py | 14 ++ .../validate_resolved_paths.py | 0 .../{publish => _unused}/validate_task.py | 0 .../plugins/nukestudio/publish/collectTags.py | 14 -- .../publish/{collect.py => collect_clips.py} | 24 +-- .../nukestudio/publish/collect_framerate.py | 13 ++ .../publish/collect_hierarchy_context.py | 72 ++++++++ .../nukestudio/publish/collect_metadata.py | 30 ++++ .../nukestudio/publish/collect_subsets.py | 45 +++++ .../nukestudio/publish/collect_tags.py | 30 ++++ .../publish/integrate_assumed_destination.py | 132 +++++++++++++++ .../integrate_ftrack_component_overwrite.py | 21 +++ .../publish/integrate_hierarchy_avalon.py | 140 ++++++++++++++++ .../publish/integrate_hierarchy_ftrack.py | 155 ++++++++++++++++++ .../nukestudio/publish/validate_names.py | 4 +- .../nukestudio/publish/validate_track_item.py | 15 +- .../Templates/SharedTags.hrox | 152 ++++++++--------- 18 files changed, 740 insertions(+), 123 deletions(-) create mode 100644 pype/plugins/global/publish/collect_presets.py rename pype/plugins/nukestudio/{publish => _unused}/validate_resolved_paths.py (100%) rename pype/plugins/nukestudio/{publish => _unused}/validate_task.py (100%) delete mode 100644 pype/plugins/nukestudio/publish/collectTags.py rename pype/plugins/nukestudio/publish/{collect.py => collect_clips.py} (68%) create mode 100644 pype/plugins/nukestudio/publish/collect_framerate.py create mode 100644 pype/plugins/nukestudio/publish/collect_hierarchy_context.py create mode 100644 pype/plugins/nukestudio/publish/collect_metadata.py create mode 100644 pype/plugins/nukestudio/publish/collect_subsets.py create mode 100644 pype/plugins/nukestudio/publish/collect_tags.py create mode 100644 pype/plugins/nukestudio/publish/integrate_assumed_destination.py create mode 100644 pype/plugins/nukestudio/publish/integrate_ftrack_component_overwrite.py create mode 100644 pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py create mode 100644 pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py diff --git a/pype/plugins/global/publish/collect_assumed_destination.py b/pype/plugins/global/publish/collect_assumed_destination.py index 4f9d180843..fa6a3d9423 100644 --- a/pype/plugins/global/publish/collect_assumed_destination.py +++ b/pype/plugins/global/publish/collect_assumed_destination.py @@ -9,7 +9,7 @@ class CollectAssumedDestination(pyblish.api.ContextPlugin): label = "Collect Assumed Destination" order = pyblish.api.CollectorOrder + 0.498 - exclude_families = ["clip", "trackItem"] + exclude_families = ["clip"] def process(self, context): for instance in context: diff --git a/pype/plugins/global/publish/collect_presets.py b/pype/plugins/global/publish/collect_presets.py new file mode 100644 index 0000000000..8edf9797de --- /dev/null +++ b/pype/plugins/global/publish/collect_presets.py @@ -0,0 +1,14 @@ +from pyblish import api +from pypeapp import config + + +class CollectPresets(api.ContextPlugin): + """Collect Presets.""" + + order = api.CollectorOrder + label = "Collect Presets" + + def process(self, context): + context.data["presets"] = config.get_presets() + self.log.info(context.data["presets"]) + return diff --git a/pype/plugins/nukestudio/publish/validate_resolved_paths.py b/pype/plugins/nukestudio/_unused/validate_resolved_paths.py similarity index 100% rename from pype/plugins/nukestudio/publish/validate_resolved_paths.py rename to pype/plugins/nukestudio/_unused/validate_resolved_paths.py diff --git a/pype/plugins/nukestudio/publish/validate_task.py b/pype/plugins/nukestudio/_unused/validate_task.py similarity index 100% rename from pype/plugins/nukestudio/publish/validate_task.py rename to pype/plugins/nukestudio/_unused/validate_task.py diff --git a/pype/plugins/nukestudio/publish/collectTags.py b/pype/plugins/nukestudio/publish/collectTags.py deleted file mode 100644 index 5137a9f22f..0000000000 --- a/pype/plugins/nukestudio/publish/collectTags.py +++ /dev/null @@ -1,14 +0,0 @@ -from pyblish import api - - -class CollectTrackItemTags(api.InstancePlugin): - """Collect Tags from selected track items.""" - - order = api.CollectorOrder - label = "Collect Tags" - hosts = ["nukestudio"] - - def process(self, instance): - instance.data["tags"] = instance.data["item"].tags() - self.log.info(instance.data["tags"]) - return diff --git a/pype/plugins/nukestudio/publish/collect.py b/pype/plugins/nukestudio/publish/collect_clips.py similarity index 68% rename from pype/plugins/nukestudio/publish/collect.py rename to pype/plugins/nukestudio/publish/collect_clips.py index de6b2b3fca..69ec4814e9 100644 --- a/pype/plugins/nukestudio/publish/collect.py +++ b/pype/plugins/nukestudio/publish/collect_clips.py @@ -1,28 +1,14 @@ from pyblish import api -class CollectFramerate(api.ContextPlugin): - """Collect framerate from selected sequence.""" - order = api.CollectorOrder - label = "Collect Framerate" - hosts = ["nukestudio"] - - def process(self, context): - for item in context.data.get("selection", []): - context.data["framerate"] = item.sequence().framerate().toFloat() - return - - -class CollectTrackItems(api.ContextPlugin): +class CollectClips(api.ContextPlugin): """Collect all Track items selection.""" order = api.CollectorOrder - label = "Collect Track Items" + label = "Collect Clips" hosts = ["nukestudio"] def process(self, context): - import os - data = {} for item in context.data.get("selection", []): self.log.info("__ item: {}".format(item)) @@ -43,13 +29,13 @@ class CollectTrackItems(api.ContextPlugin): } for key, value in data.items(): - + family = "clip" context.create_instance( name=key, - subset="trackItem", + subset="{0}{1}".format(family, 'Default'), asset=value["item"].name(), item=value["item"], - family="trackItem", + family=family, tasks=value["tasks"], startFrame=value["startFrame"], endFrame=value["endFrame"], diff --git a/pype/plugins/nukestudio/publish/collect_framerate.py b/pype/plugins/nukestudio/publish/collect_framerate.py new file mode 100644 index 0000000000..822be8fb9b --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_framerate.py @@ -0,0 +1,13 @@ +from pyblish import api + +class CollectFramerate(api.ContextPlugin): + """Collect framerate from selected sequence.""" + + order = api.CollectorOrder + label = "Collect Framerate" + hosts = ["nukestudio"] + + def process(self, context): + for item in context.data.get("selection", []): + context.data["framerate"] = item.sequence().framerate().toFloat() + return diff --git a/pype/plugins/nukestudio/publish/collect_hierarchy_context.py b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py new file mode 100644 index 0000000000..b421d31f79 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_hierarchy_context.py @@ -0,0 +1,72 @@ +import pyblish.api +from avalon import api + + +class CollectHierarchyContext(pyblish.api.ContextPlugin): + """Collecting hierarchy context from `parents` and `hierarchy` data + present in `clip` family instances coming from the request json data file + + It will add `hierarchical_context` into each instance for integrate + plugins to be able to create needed parents for the context if they + don't exist yet + """ + + label = "Collect Hierarchy Context" + order = pyblish.api.CollectorOrder + 0.1 + + def update_dict(self, ex_dict, new_dict): + for key in ex_dict: + if key in new_dict and isinstance(ex_dict[key], dict): + new_dict[key] = self.update_dict(ex_dict[key], new_dict[key]) + else: + new_dict[key] = ex_dict[key] + return new_dict + + def process(self, context): + json_data = context.data.get("jsonData", None) + temp_context = {} + for instance in json_data['instances']: + if instance['family'] in 'projectfile': + continue + + in_info = {} + name = instance['name'] + # suppose that all instances are Shots + in_info['entity_type'] = 'Shot' + + instance_pyblish = [ + i for i in context.data["instances"] if i.data['asset'] in name][0] + in_info['custom_attributes'] = { + 'fend': instance_pyblish.data['endFrame'], + 'fstart': instance_pyblish.data['startFrame'], + 'fps': instance_pyblish.data['fps'] + } + + in_info['tasks'] = instance['tasks'] + + parents = instance.get('parents', []) + + actual = {name: in_info} + + for parent in reversed(parents): + next_dict = {} + parent_name = parent["entityName"] + next_dict[parent_name] = {} + next_dict[parent_name]["entity_type"] = parent["entityType"] + next_dict[parent_name]["childs"] = actual + actual = next_dict + + temp_context = self.update_dict(temp_context, actual) + self.log.debug(temp_context) + + # TODO: 100% sure way of get project! Will be Name or Code? + project_name = api.Session["AVALON_PROJECT"] + final_context = {} + final_context[project_name] = {} + final_context[project_name]['entity_type'] = 'Project' + final_context[project_name]['childs'] = temp_context + + # adding hierarchy context to instance + context.data["hierarchyContext"] = final_context + self.log.debug("context.data[hierarchyContext] is: {}".format( + context.data["hierarchyContext"])) diff --git a/pype/plugins/nukestudio/publish/collect_metadata.py b/pype/plugins/nukestudio/publish/collect_metadata.py new file mode 100644 index 0000000000..23d36ba4a2 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_metadata.py @@ -0,0 +1,30 @@ +from pyblish import api + + +class CollectClipMetadata(api.InstancePlugin): + """Collect Metadata from selected track items.""" + + order = api.CollectorOrder + 0.01 + label = "Collect Metadata" + hosts = ["nukestudio"] + + def process(self, instance): + item = instance.data["item"] + ti_metadata = self.metadata_to_string(dict(item.metadata())) + ms_metadata = self.metadata_to_string( + dict(item.source().mediaSource().metadata())) + + instance.data["clipMetadata"] = ti_metadata + instance.data["mediaSourceMetadata"] = ms_metadata + + self.log.info(instance.data["clipMetadata"]) + self.log.info(instance.data["mediaSourceMetadata"]) + return + + def metadata_to_string(self, metadata): + data = dict() + for k, v in metadata.items(): + if v not in ["-", ""]: + data[str(k)] = v + + return data diff --git a/pype/plugins/nukestudio/publish/collect_subsets.py b/pype/plugins/nukestudio/publish/collect_subsets.py new file mode 100644 index 0000000000..b27a718f49 --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_subsets.py @@ -0,0 +1,45 @@ +from pyblish import api + + +class CollectClipSubsets(api.InstancePlugin): + """Collect Subsets from selected Clips, Tags, Preset.""" + + order = api.CollectorOrder + 0.01 + label = "Collect Subsets" + hosts = ["nukestudio"] + families = ['clip'] + + def process(self, instance): + tags = instance.data.get('tags', None) + presets = instance.context.data['presets'][ + instance.context.data['host']] + if tags: + self.log.info(tags) + + if presets: + self.log.info(presets) + + # get presets and tags + # iterate tags and get task family + # iterate tags and get host family + # iterate tags and get handles family + + instance = instance.context.create_instance(instance_name) + + instance.data.update({ + "subset": subset_name, + "stagingDir": staging_dir, + "task": task, + "representation": ext[1:], + "host": host, + "asset": asset_name, + "label": label, + "name": name, + # "hierarchy": hierarchy, + # "parents": parents, + "family": family, + "families": [families, 'ftrack'], + "publish": True, + # "files": files_list + }) + instances.append(instance) diff --git a/pype/plugins/nukestudio/publish/collect_tags.py b/pype/plugins/nukestudio/publish/collect_tags.py new file mode 100644 index 0000000000..9ae34d415f --- /dev/null +++ b/pype/plugins/nukestudio/publish/collect_tags.py @@ -0,0 +1,30 @@ +from pyblish import api + + +class CollectClipTags(api.InstancePlugin): + """Collect Tags from selected track items.""" + + order = api.CollectorOrder + label = "Collect Tags" + hosts = ["nukestudio"] + families = ['clip'] + + def process(self, instance): + tags = instance.data["item"].tags() + + tags_d = [] + if tags: + for t in tags: + tag_data = { + "name": t.name(), + "object": t, + "metadata": t.metadata(), + "inTime": t.inTime(), + "outTime": t.outTime(), + } + tags_d.append(tag_data) + + instance.data["tags"] = tags_d + + self.log.info(instance.data["tags"]) + return diff --git a/pype/plugins/nukestudio/publish/integrate_assumed_destination.py b/pype/plugins/nukestudio/publish/integrate_assumed_destination.py new file mode 100644 index 0000000000..c1936994e4 --- /dev/null +++ b/pype/plugins/nukestudio/publish/integrate_assumed_destination.py @@ -0,0 +1,132 @@ +import pyblish.api +import os + +from avalon import io, api + + +class IntegrateAssumedDestination(pyblish.api.InstancePlugin): + """Generate the assumed destination path where the file will be stored""" + + label = "Integrate Assumed Destination" + order = pyblish.api.IntegratorOrder - 0.05 + families = ["clip", "projectfile"] + + def process(self, instance): + + self.create_destination_template(instance) + + template_data = instance.data["assumedTemplateData"] + # template = instance.data["template"] + + anatomy = instance.context.data['anatomy'] + # template = anatomy.publish.path + anatomy_filled = anatomy.format(template_data) + mock_template = anatomy_filled.publish.path + + # 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) + + # 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 + # 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"] + self.log.info(subset_name) + asset_name = instance.data["asset"] + project_name = api.Session["AVALON_PROJECT"] + + project = io.find_one({"type": "project", + "name": project_name}, + projection={"config": True, "data": True}) + + template = project["config"]["template"]["publish"] + # anatomy = instance.context.data['anatomy'] + + asset = io.find_one({"type": "asset", + "name": asset_name, + "parent": project["_id"]}) + + 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, + "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 version is not None: + version_number += version["name"] + + if instance.data.get('version'): + version_number = int(instance.data.get('version')) + + hierarchy = asset['data']['parents'] + if hierarchy: + # hierarchy = os.path.sep.join(hierarchy) + hierarchy = os.path.join(*hierarchy) + + template_data = {"root": api.Session["AVALON_PROJECTS"], + "project": {"name": project_name, + "code": project['data']['code']}, + "silo": silo, + "family": instance.data['family'], + "asset": asset_name, + "subset": subset_name, + "version": version_number, + "hierarchy": hierarchy, + "representation": "TEMP"} + + instance.data["assumedTemplateData"] = template_data + self.log.info(template_data) + instance.data["template"] = template diff --git a/pype/plugins/nukestudio/publish/integrate_ftrack_component_overwrite.py b/pype/plugins/nukestudio/publish/integrate_ftrack_component_overwrite.py new file mode 100644 index 0000000000..047fd8462c --- /dev/null +++ b/pype/plugins/nukestudio/publish/integrate_ftrack_component_overwrite.py @@ -0,0 +1,21 @@ +import pyblish.api + + +class IntegrateFtrackComponentOverwrite(pyblish.api.InstancePlugin): + """ + Set `component_overwrite` to True on all instances `ftrackComponentsList` + """ + + order = pyblish.api.IntegratorOrder + 0.49 + label = 'Overwrite ftrack created versions' + families = ["clip"] + optional = True + active = False + + def process(self, instance): + component_list = instance.data['ftrackComponentsList'] + + for cl in component_list: + cl['component_overwrite'] = True + self.log.debug('Component {} overwriting'.format( + cl['component_data']['name'])) diff --git a/pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py b/pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py new file mode 100644 index 0000000000..0f7fdb20d3 --- /dev/null +++ b/pype/plugins/nukestudio/publish/integrate_hierarchy_avalon.py @@ -0,0 +1,140 @@ +import pyblish.api +from avalon import io + + +class IntegrateHierarchyToAvalon(pyblish.api.ContextPlugin): + """ + Create entities in ftrack based on collected data from premiere + + """ + + order = pyblish.api.IntegratorOrder - 0.1 + label = 'Integrate Hierarchy To Avalon' + families = ['clip'] + + def process(self, context): + if "hierarchyContext" not in context.data: + return + + self.db = io + if not self.db.Session: + self.db.install() + + input_data = context.data["hierarchyContext"] + self.import_to_avalon(input_data) + + def import_to_avalon(self, input_data, parent=None): + + for name in input_data: + self.log.info('input_data[name]: {}'.format(input_data[name])) + entity_data = input_data[name] + entity_type = entity_data['entity_type'] + + data = {} + # Process project + if entity_type.lower() == 'project': + entity = self.db.find_one({'type': 'project'}) + # TODO: should be in validator? + assert (entity is not None), "Didn't find project in DB" + + # get data from already existing project + for key, value in entity.get('data', {}).items(): + data[key] = value + + self.av_project = entity + # Raise error if project or parent are not set + elif self.av_project is None or parent is None: + raise AssertionError( + "Collected items are not in right order!" + ) + # Else process assset + else: + entity = self.db.find_one({'type': 'asset', 'name': name}) + # Create entity if doesn't exist + if entity is None: + if self.av_project['_id'] == parent['_id']: + silo = None + elif parent['silo'] is None: + silo = parent['name'] + else: + silo = parent['silo'] + entity = self.create_avalon_asset(name, silo) + self.log.info('entity: {}'.format(entity)) + self.log.info('data: {}'.format(entity.get('data', {}))) + self.log.info('____1____') + data['entityType'] = entity_type + # TASKS + tasks = entity_data.get('tasks', []) + if tasks is not None or len(tasks) > 0: + data['tasks'] = tasks + parents = [] + visualParent = None + data = input_data[name] + if self.av_project['_id'] != parent['_id']: + visualParent = parent['_id'] + parents.extend(parent.get('data', {}).get('parents', [])) + parents.append(parent['name']) + data['visualParent'] = visualParent + data['parents'] = parents + + self.db.update_many( + {'_id': entity['_id']}, + {'$set': { + 'data': data, + }}) + + entity = self.db.find_one({'type': 'asset', 'name': name}) + self.log.info('entity: {}'.format(entity)) + self.log.info('data: {}'.format(entity.get('data', {}))) + self.log.info('____2____') + + # Else get data from already existing + else: + self.log.info('entity: {}'.format(entity)) + self.log.info('data: {}'.format(entity.get('data', {}))) + self.log.info('________') + for key, value in entity.get('data', {}).items(): + data[key] = value + + data['entityType'] = entity_type + # TASKS + tasks = entity_data.get('tasks', []) + if tasks is not None or len(tasks) > 0: + data['tasks'] = tasks + parents = [] + visualParent = None + # do not store project's id as visualParent (silo asset) + + if self.av_project['_id'] != parent['_id']: + visualParent = parent['_id'] + parents.extend(parent.get('data', {}).get('parents', [])) + parents.append(parent['name']) + data['visualParent'] = visualParent + data['parents'] = parents + + # CUSTOM ATTRIBUTES + for k, val in entity_data.get('custom_attributes', {}).items(): + data[k] = val + + # Update entity data with input data + self.db.update_many( + {'_id': entity['_id']}, + {'$set': { + 'data': data, + }}) + + if 'childs' in entity_data: + self.import_to_avalon(entity_data['childs'], entity) + + def create_avalon_asset(self, name, silo): + item = { + 'schema': 'avalon-core:asset-2.0', + 'name': name, + 'silo': silo, + 'parent': self.av_project['_id'], + 'type': 'asset', + 'data': {} + } + entity_id = self.db.insert_one(item).inserted_id + + return self.db.find_one({'_id': entity_id}) diff --git a/pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py b/pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py new file mode 100644 index 0000000000..d6d03e9722 --- /dev/null +++ b/pype/plugins/nukestudio/publish/integrate_hierarchy_ftrack.py @@ -0,0 +1,155 @@ +import pyblish.api + + +class IntegrateHierarchyToFtrack(pyblish.api.ContextPlugin): + """ + Create entities in ftrack based on collected data from premiere + Example of entry data: + { + "ProjectXS": { + "entity_type": "Project", + "custom_attributes": { + "fps": 24,... + }, + "tasks": [ + "Compositing", + "Lighting",... *task must exist as task type in project schema* + ], + "childs": { + "sq01": { + "entity_type": "Sequence", + ... + } + } + } + } + """ + + order = pyblish.api.IntegratorOrder + label = 'Integrate Hierarchy To Ftrack' + families = ["clip"] + optional = False + + def process(self, context): + self.context = context + if "hierarchyContext" not in context.data: + return + + self.ft_project = None + self.session = context.data["ftrackSession"] + + input_data = context.data["hierarchyContext"] + + # adding ftrack types from presets + ftrack_types = context.data['ftrackTypes'] + + self.import_to_ftrack(input_data, ftrack_types) + + def import_to_ftrack(self, input_data, ftrack_types, parent=None): + for entity_name in input_data: + entity_data = input_data[entity_name] + entity_type = entity_data['entity_type'].capitalize() + + if entity_type.lower() == 'project': + query = 'Project where full_name is "{}"'.format(entity_name) + entity = self.session.query(query).one() + self.ft_project = entity + self.task_types = self.get_all_task_types(entity) + + elif self.ft_project is None or parent is None: + raise AssertionError( + "Collected items are not in right order!" + ) + + # try to find if entity already exists + else: + query = '{} where name is "{}" and parent_id is "{}"'.format( + entity_type, entity_name, parent['id'] + ) + try: + entity = self.session.query(query).one() + except Exception: + entity = None + + # Create entity if not exists + if entity is None: + entity = self.create_entity( + name=entity_name, + type=entity_type, + parent=parent + ) + # self.log.info('entity: {}'.format(dict(entity))) + # CUSTOM ATTRIBUTES + custom_attributes = entity_data.get('custom_attributes', []) + instances = [ + i for i in self.context.data["instances"] if i.data['asset'] in entity['name']] + for key in custom_attributes: + assert (key in entity['custom_attributes']), ( + 'Missing custom attribute') + + entity['custom_attributes'][key] = custom_attributes[key] + for instance in instances: + instance.data['ftrackShotId'] = entity['id'] + + self.session.commit() + + # TASKS + tasks = entity_data.get('tasks', []) + existing_tasks = [] + tasks_to_create = [] + for child in entity['children']: + if child.entity_type.lower() == 'task': + existing_tasks.append(child['name']) + # existing_tasks.append(child['type']['name']) + + for task in tasks: + if task in existing_tasks: + print("Task {} already exists".format(task)) + continue + tasks_to_create.append(task) + + for task in tasks_to_create: + self.create_task( + name=task, + task_type=ftrack_types[task], + parent=entity + ) + self.session.commit() + + if 'childs' in entity_data: + self.import_to_ftrack( + entity_data['childs'], ftrack_types, entity) + + def get_all_task_types(self, project): + tasks = {} + proj_template = project['project_schema'] + temp_task_types = proj_template['_task_type_schema']['types'] + + for type in temp_task_types: + if type['name'] not in tasks: + tasks[type['name']] = type + + return tasks + + def create_task(self, name, task_type, parent): + task = self.session.create('Task', { + 'name': name, + 'parent': parent + }) + # TODO not secured!!! - check if task_type exists + self.log.info(task_type) + self.log.info(self.task_types) + task['type'] = self.task_types[task_type] + + self.session.commit() + + return task + + def create_entity(self, name, type, parent): + entity = self.session.create(type, { + 'name': name, + 'parent': parent + }) + self.session.commit() + + return entity diff --git a/pype/plugins/nukestudio/publish/validate_names.py b/pype/plugins/nukestudio/publish/validate_names.py index 169febd764..52382e545d 100644 --- a/pype/plugins/nukestudio/publish/validate_names.py +++ b/pype/plugins/nukestudio/publish/validate_names.py @@ -10,7 +10,7 @@ class ValidateNames(api.InstancePlugin): """ order = api.ValidatorOrder - families = ["trackItem"] + families = ["clip"] match = api.Exact label = "Names" hosts = ["nukestudio"] @@ -39,4 +39,4 @@ class ValidateNamesFtrack(ValidateNames): """ order = api.ValidatorOrder - families = ["trackItem", "ftrack"] + families = ["clip", "ftrack"] diff --git a/pype/plugins/nukestudio/publish/validate_track_item.py b/pype/plugins/nukestudio/publish/validate_track_item.py index 3fe7a739ce..600bf58938 100644 --- a/pype/plugins/nukestudio/publish/validate_track_item.py +++ b/pype/plugins/nukestudio/publish/validate_track_item.py @@ -1,13 +1,13 @@ from pyblish import api -class ValidateTrackItem(api.InstancePlugin): +class ValidateClip(api.InstancePlugin): """Validate the track item to the sequence. Exact matching to optimize processing. """ order = api.ValidatorOrder - families = ["trackItem"] + families = ["clip"] match = api.Exact label = "Validate Track Item" hosts = ["nukestudio"] @@ -44,14 +44,3 @@ class ValidateTrackItem(api.InstancePlugin): assert sequence.framerate() == source_framerate, msg.format( "framerate", source_framerate, sequence.framerate() ) - -# -# class ValidateTrackItemFtrack(ValidateTrackItem): -# """Validate the track item to the sequence. -# -# Because we are matching the families exactly, we need this plugin to -# accommodate for the ftrack family addition. -# """ -# -# order = api.ValidatorOrder -# families = ["trackItem", "ftrack"] diff --git a/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox b/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox index 4045ea3335..128bde5456 100644 --- a/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox +++ b/setup/nukestudio/hiero_plugin_path/Templates/SharedTags.hrox @@ -1,19 +1,19 @@ - - + + - + 2 70 0 0 13 - + - - + + @@ -23,10 +23,10 @@ - + - - + + @@ -37,8 +37,8 @@ - - + + @@ -49,8 +49,8 @@ - - + + @@ -61,8 +61,8 @@ - - + + @@ -73,8 +73,8 @@ - - + + @@ -85,8 +85,8 @@ - - + + @@ -97,8 +97,8 @@ - - + + @@ -109,8 +109,8 @@ - - + + @@ -121,8 +121,8 @@ - - + + @@ -133,8 +133,8 @@ - - + + @@ -145,8 +145,8 @@ - - + + @@ -157,8 +157,8 @@ - - + + @@ -169,8 +169,8 @@ - - + + @@ -181,8 +181,8 @@ - - + + @@ -199,10 +199,10 @@ 0 0 - + - - + + @@ -214,8 +214,8 @@ - - + + @@ -227,8 +227,8 @@ - - + + @@ -240,8 +240,8 @@ - - + + @@ -253,8 +253,8 @@ - - + + @@ -268,8 +268,8 @@ - - + + @@ -283,8 +283,8 @@ - - + + @@ -298,8 +298,8 @@ - - + + @@ -313,8 +313,8 @@ - - + + @@ -328,8 +328,8 @@ - - + + @@ -343,8 +343,8 @@ - - + + @@ -358,8 +358,8 @@ - - + + @@ -373,8 +373,8 @@ - - + + @@ -392,47 +392,51 @@ 0 0 - + - - + + + - - + + + - - + + + - - + + + @@ -457,9 +461,9 @@ 0 0 2 - - - + + + From fccb376d9ce3720058ea9de037262126ac38c5bb Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 May 2019 17:58:32 +0200 Subject: [PATCH 23/41] feat(nuke): incrementing script version only if not prerender only published --- .../nuke/_publish_unused/test_instances.py | 24 +++++++++++++++++++ .../nuke/publish/increment_script_version.py | 19 +++++++++++---- 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 pype/plugins/nuke/_publish_unused/test_instances.py diff --git a/pype/plugins/nuke/_publish_unused/test_instances.py b/pype/plugins/nuke/_publish_unused/test_instances.py new file mode 100644 index 0000000000..e3fcc4b8f1 --- /dev/null +++ b/pype/plugins/nuke/_publish_unused/test_instances.py @@ -0,0 +1,24 @@ +import pyblish.api + + +class IncrementTestPlugin(pyblish.api.ContextPlugin): + """Increment current script version.""" + + order = pyblish.api.CollectorOrder + 0.5 + label = "Test Plugin" + hosts = ['nuke'] + + def process(self, context): + instances = context[:] + + prerender_check = list() + families_check = list() + for instance in instances: + if ("prerender" in str(instance)): + prerender_check.append(instance) + if instance.data.get("families", None): + families_check.append(True) + + if len(prerender_check) != len(families_check): + self.log.info(prerender_check) + self.log.info(families_check) diff --git a/pype/plugins/nuke/publish/increment_script_version.py b/pype/plugins/nuke/publish/increment_script_version.py index 77eab30a63..e8071ede93 100644 --- a/pype/plugins/nuke/publish/increment_script_version.py +++ b/pype/plugins/nuke/publish/increment_script_version.py @@ -16,7 +16,18 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): assert all(result["success"] for result in context.data["results"]), ( "Atomicity not held, aborting.") - from pype.lib import version_up - path = context.data["currentFile"] - nuke.scriptSaveAs(version_up(path)) - self.log.info('Incrementing script version') + instances = context[:] + + prerender_check = list() + families_check = list() + for instance in instances: + if ("prerender" in str(instance)): + prerender_check.append(instance) + if instance.data.get("families", None): + families_check.append(True) + + if len(prerender_check) != len(families_check): + from pype.lib import version_up + path = context.data["currentFile"] + nuke.scriptSaveAs(version_up(path)) + self.log.info('Incrementing script version') From 4e74bda9fa40e9aff64d13aa87462dbbcc939dae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:20:43 +0200 Subject: [PATCH 24/41] NOT_SELECTED moved into family widget --- pype/standalonepublish/app.py | 3 +-- pype/standalonepublish/widgets/widget_family.py | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pype/standalonepublish/app.py b/pype/standalonepublish/app.py index 956cdb6300..5d3bfd0505 100644 --- a/pype/standalonepublish/app.py +++ b/pype/standalonepublish/app.py @@ -26,7 +26,6 @@ class Window(QtWidgets.QDialog): initialized = False WIDTH = 1100 HEIGHT = 500 - NOT_SELECTED = '< Nothing is selected >' def __init__(self, parent=None): super(Window, self).__init__(parent=parent) @@ -160,7 +159,7 @@ class Window(QtWidgets.QDialog): self.widget_family.change_asset(asset['name']) else: self.valid_parent = False - self.widget_family.change_asset(self.NOT_SELECTED) + self.widget_family.change_asset(None) self.widget_family.on_data_changed() def keyPressEvent(self, event): diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 102705f98b..117fb30151 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -17,12 +17,14 @@ class FamilyWidget(QtWidgets.QWidget): data = dict() _jobs = dict() Separator = "---separator---" + NOT_SELECTED = '< Nothing is selected >' def __init__(self, parent): super().__init__(parent) # Store internal states in here self.state = {"valid": False} self.parent_widget = parent + self.asset_name = self.NOT_SELECTED body = QtWidgets.QWidget() lists = QtWidgets.QWidget() @@ -132,7 +134,10 @@ class FamilyWidget(QtWidgets.QWidget): return self.parent_widget.db def change_asset(self, name): - self.input_asset.setText(name) + if name is None: + name = self.NOT_SELECTED + self.asset_name = name + self.on_data_changed() def _on_state_changed(self, state): self.state['valid'] = state From 941bf8e44c73d1dda18597a93e280f5805e0e2ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:23:18 +0200 Subject: [PATCH 25/41] input asset removed from widgets and subset result is filled even if asset is not selected --- .../widgets/widget_family.py | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 117fb30151..1029f71593 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -32,9 +32,7 @@ class FamilyWidget(QtWidgets.QWidget): container = QtWidgets.QWidget() list_families = QtWidgets.QListWidget() - input_asset = QtWidgets.QLineEdit() - input_asset.setEnabled(False) - input_asset.setStyleSheet("color: #BBBBBB;") + input_subset = QtWidgets.QLineEdit() input_result = QtWidgets.QLineEdit() input_result.setStyleSheet("color: #BBBBBB;") @@ -74,8 +72,6 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(QtWidgets.QLabel("Family")) layout.addWidget(list_families) - layout.addWidget(QtWidgets.QLabel("Asset")) - layout.addWidget(input_asset) layout.addWidget(QtWidgets.QLabel("Subset")) layout.addLayout(name_layout) layout.addWidget(input_result) @@ -93,6 +89,7 @@ class FamilyWidget(QtWidgets.QWidget): layout.setContentsMargins(0, 0, 0, 0) layout = QtWidgets.QVBoxLayout(body) + layout.addWidget(lists) layout.addWidget(options, 0, QtCore.Qt.AlignLeft) layout.setContentsMargins(0, 0, 0, 0) @@ -101,7 +98,6 @@ class FamilyWidget(QtWidgets.QWidget): layout.addWidget(body) input_subset.textChanged.connect(self.on_data_changed) - input_asset.textChanged.connect(self.on_data_changed) list_families.currentItemChanged.connect(self.on_selection_changed) list_families.currentItemChanged.connect(header.set_item) version_checkbox.stateChanged.connect(self.on_version_refresh) @@ -112,7 +108,6 @@ class FamilyWidget(QtWidgets.QWidget): self.menu_subset = menu_subset self.btn_subset = btn_subset self.list_families = list_families - self.input_asset = input_asset self.input_result = input_result self.version_checkbox = version_checkbox self.version_spinbox = version_spinbox @@ -178,22 +173,37 @@ class FamilyWidget(QtWidgets.QWidget): self.input_subset.setText(action.text()) def _on_data_changed(self): - item = self.list_families.currentItem() + asset_name = self.asset_name subset_name = self.input_subset.text() - asset_name = self.input_asset.text() + item = self.list_families.currentItem() - # Get the assets from the database which match with the name - assets_db = self.db.find(filter={"type": "asset"}, projection={"name": 1}) - assets = [asset for asset in assets_db if asset_name in asset["name"]] if item is None: return - if assets: - # Get plugin and family - plugin = item.data(PluginRole) - if plugin is None: - return - family = plugin.family.rsplit(".", 1)[-1] + assets = None + if asset_name != self.NOT_SELECTED: + # Get the assets from the database which match with the name + assets_db = self.db.find( + filter={"type": "asset"}, + projection={"name": 1} + ) + assets = [ + asset for asset in assets_db if asset_name in asset["name"] + ] + + # Get plugin and family + plugin = item.data(PluginRole) + if plugin is None: + return + + family = plugin.family.rsplit(".", 1)[-1] + + # Update the result + if subset_name: + subset_name = subset_name[0].upper() + subset_name[1:] + self.input_result.setText("{}{}".format(family, subset_name)) + + if assets: # Get all subsets of the current asset asset_ids = [asset["_id"] for asset in assets] subsets = self.db.find(filter={"type": "subset", @@ -216,25 +226,20 @@ class FamilyWidget(QtWidgets.QWidget): self._build_menu(defaults) - # Update the result - if subset_name: - subset_name = subset_name[0].upper() + subset_name[1:] - self.input_result.setText("{}{}".format(family, subset_name)) - item.setData(ExistsRole, True) self.echo("Ready ..") else: self._build_menu([]) item.setData(ExistsRole, False) - if asset_name != self.parent_widget.NOT_SELECTED: + if asset_name != self.NOT_SELECTED: self.echo("'%s' not found .." % asset_name) self.on_version_refresh() # Update the valid state valid = ( + asset_name != self.NOT_SELECTED and subset_name.strip() != "" and - asset_name.strip() != "" and item.data(QtCore.Qt.ItemIsEnabled) and item.data(ExistsRole) ) @@ -246,12 +251,12 @@ class FamilyWidget(QtWidgets.QWidget): if not auto_version: return + asset_name = self.asset_name + subset_name = self.input_result.text() version = 1 - asset_name = self.input_asset.text() - subset_name = self.input_result.text() if ( - asset_name != self.parent_widget.NOT_SELECTED and + asset_name != self.NOT_SELECTED and subset_name.strip() != '' ): asset = self.db.find_one({ From 2604e9178c8f3342be49abd0a58268ec22e2f86d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:23:50 +0200 Subject: [PATCH 26/41] changed icon for tasks nodes --- .../widgets/model_tasks_template.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pype/standalonepublish/widgets/model_tasks_template.py b/pype/standalonepublish/widgets/model_tasks_template.py index 4af3b9eea7..bd1984029c 100644 --- a/pype/standalonepublish/widgets/model_tasks_template.py +++ b/pype/standalonepublish/widgets/model_tasks_template.py @@ -8,13 +8,13 @@ class TasksTemplateModel(TreeModel): COLUMNS = ["Tasks"] - def __init__(self): + def __init__(self, selectable=True): super(TasksTemplateModel, self).__init__() - self.selectable = False - self._icons = { - "__default__": awesome.icon("fa.folder-o", - color=style.colors.default) - } + self.selectable = selectable + self.icon = awesome.icon( + 'fa.calendar-check-o', + color=style.colors.default + ) def set_tasks(self, tasks): """Set assets to track by their database id @@ -32,13 +32,11 @@ class TasksTemplateModel(TreeModel): self.beginResetModel() - icon = self._icons["__default__"] for task in tasks: node = Node({ "Tasks": task, - "icon": icon + "icon": self.icon }) - self.add_child(node) self.endResetModel() From 4123cdfb165d74fa242f9929dd8b8af6decc2ad4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:26:09 +0200 Subject: [PATCH 27/41] added tasks view into asset widget --- .../standalonepublish/widgets/widget_asset.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 45e9757d71..e7d72a9db0 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -2,6 +2,8 @@ import contextlib from . import QtWidgets, QtCore from . import RecursiveSortFilterProxyModel, AssetModel, AssetView from . import awesome, style +from . import TasksTemplateModel, DeselectableTreeView + @contextlib.contextmanager def preserve_expanded_rows(tree_view, @@ -128,7 +130,7 @@ class AssetWidget(QtWidgets.QWidget): self.parent_widget = parent - layout = QtWidgets.QVBoxLayout(self) + layout = QtWidgets.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(4) @@ -163,12 +165,29 @@ class AssetWidget(QtWidgets.QWidget): layout.addLayout(header) layout.addWidget(view) + # tasks + task_view = DeselectableTreeView() + task_view.setIndentation(0) + task_view.setHeaderHidden(True) + + task_model = TasksTemplateModel() + task_view.setModel(task_model) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(4) + main_layout.addLayout(layout, 80) + main_layout.addWidget(task_view, 20) + # Signals/Slots selection = view.selectionModel() selection.selectionChanged.connect(self.selection_changed) selection.currentChanged.connect(self.current_changed) refresh.clicked.connect(self.refresh) + + self.task_view = task_view + self.task_model = task_model self.refreshButton = refresh self.model = model self.proxy = proxy From 9f5d932e0a2eff60246e61791738a1a6d03f9e8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:26:33 +0200 Subject: [PATCH 28/41] tasks widget now loads tasks from selected asset --- pype/standalonepublish/widgets/widget_asset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index e7d72a9db0..92a4d1d88f 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -185,6 +185,7 @@ class AssetWidget(QtWidgets.QWidget): selection.currentChanged.connect(self.current_changed) refresh.clicked.connect(self.refresh) + self.selection_changed.connect(self._refresh_tasks) self.task_view = task_view self.task_model = task_model @@ -242,6 +243,16 @@ class AssetWidget(QtWidgets.QWidget): def refresh(self): self._refresh_model() + def _refresh_tasks(self): + tasks = [] + selected = self.get_selected_assets() + if len(selected) == 1: + asset = self.db.find_one({ + "_id": selected[0], "type": "asset" + }) + tasks = asset['data'].get('tasks', []) + self.task_model.set_tasks(tasks) + def get_active_asset(self): """Return the asset id the current asset.""" current = self.view.currentIndex() From b4115f2867d9564f6074887ab2cdf8f738968916 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:27:29 +0200 Subject: [PATCH 29/41] getting tasks from asset is more secure --- pype/standalonepublish/widgets/widget_asset.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 92a4d1d88f..4b27ba808e 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -250,7 +250,8 @@ class AssetWidget(QtWidgets.QWidget): asset = self.db.find_one({ "_id": selected[0], "type": "asset" }) - tasks = asset['data'].get('tasks', []) + if asset: + tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) def get_active_asset(self): From 113bd52638b20e5211b07984109024fb5d962257 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:27:40 +0200 Subject: [PATCH 30/41] collect data also return task --- pype/standalonepublish/widgets/widget_asset.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 4b27ba808e..41163e13d6 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -201,10 +201,17 @@ class AssetWidget(QtWidgets.QWidget): def collect_data(self): project = self.db.find_one({'type': 'project'}) asset = self.db.find_one({'_id': self.get_active_asset()}) + + try: + index = self.task_view.selectedIndexes()[0] + task = self.task_model.itemData(index)[0] + except Exception: + task = None data = { 'project': project['name'], 'asset': asset['name'], - 'parents': self.get_parents(asset) + 'parents': self.get_parents(asset), + 'task': task } return data From 72b8f24c85056745c589e55f45ba445f43cd5217 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 14 May 2019 18:28:06 +0200 Subject: [PATCH 31/41] fixed subset name in collect_data --- pype/plugins/standalonepublish/publish/collect_context.py | 2 +- pype/standalonepublish/widgets/widget_family.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/plugins/standalonepublish/publish/collect_context.py b/pype/plugins/standalonepublish/publish/collect_context.py index cbe9df1ef6..2f3ca1ca27 100644 --- a/pype/plugins/standalonepublish/publish/collect_context.py +++ b/pype/plugins/standalonepublish/publish/collect_context.py @@ -55,7 +55,7 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): instance = context.create_instance(subset) instance.data.update({ - "subset": family + subset, + "subset": subset, "asset": asset_name, "label": family + subset, "name": family + subset, diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 1029f71593..78388d17d8 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -119,7 +119,7 @@ class FamilyWidget(QtWidgets.QWidget): family = plugin.family.rsplit(".", 1)[-1] data = { 'family': family, - 'subset': self.input_subset.text(), + 'subset': self.input_result.text(), 'version': self.version_spinbox.value() } return data From 65be5efb5c6137957222ae3c158314d806622479 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 May 2019 09:57:58 +0200 Subject: [PATCH 32/41] fix(nuke): increment script version when prerender desabled --- pype/plugins/nuke/publish/increment_script_version.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pype/plugins/nuke/publish/increment_script_version.py b/pype/plugins/nuke/publish/increment_script_version.py index e8071ede93..2e33e65528 100644 --- a/pype/plugins/nuke/publish/increment_script_version.py +++ b/pype/plugins/nuke/publish/increment_script_version.py @@ -21,11 +21,12 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin): prerender_check = list() families_check = list() for instance in instances: - if ("prerender" in str(instance)): + if ("prerender" in str(instance)) and instance.data.get("families", None): prerender_check.append(instance) if instance.data.get("families", None): families_check.append(True) + if len(prerender_check) != len(families_check): from pype.lib import version_up path = context.data["currentFile"] From 346c824f720a35decb2237f0b6d489597f3b091e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 May 2019 10:43:12 +0200 Subject: [PATCH 33/41] feat(nuke): fixing handles for publishing writes --- pype/plugins/nuke/publish/collect_instances.py | 5 ++++- pype/plugins/nuke/publish/collect_writes.py | 3 +++ pype/plugins/nuke/publish/validate_script.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pype/plugins/nuke/publish/collect_instances.py b/pype/plugins/nuke/publish/collect_instances.py index 8a2bb06fff..e9db556a9f 100644 --- a/pype/plugins/nuke/publish/collect_instances.py +++ b/pype/plugins/nuke/publish/collect_instances.py @@ -17,6 +17,10 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): def process(self, context): asset_data = io.find_one({"type": "asset", "name": api.Session["AVALON_ASSET"]}) + + # add handles into context + context.data['handles'] = int(asset_data["data"].get("handles", 0)) + self.log.debug("asset_data: {}".format(asset_data["data"])) instances = [] # creating instances per write node @@ -51,7 +55,6 @@ class CollectNukeInstances(pyblish.api.ContextPlugin): "family": avalon_knob_data["family"], "avalonKnob": avalon_knob_data, "publish": node.knob('publish').value(), - "handles": int(asset_data["data"].get("handles", 0)), "step": 1, "fps": int(nuke.root()['fps'].value()) diff --git a/pype/plugins/nuke/publish/collect_writes.py b/pype/plugins/nuke/publish/collect_writes.py index ce37774ac9..68cd227280 100644 --- a/pype/plugins/nuke/publish/collect_writes.py +++ b/pype/plugins/nuke/publish/collect_writes.py @@ -35,10 +35,12 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): output_type = "mov" # Get frame range + handles = instance.context.data.get('handles', 0) first_frame = int(nuke.root()["first_frame"].getValue()) last_frame = int(nuke.root()["last_frame"].getValue()) if node["use_limit"].getValue(): + handles = 0 first_frame = int(node["first"].getValue()) last_frame = int(node["last"].getValue()) @@ -76,6 +78,7 @@ class CollectNukeWrites(pyblish.api.ContextPlugin): "outputDir": output_dir, "ext": ext, "label": label, + "handles": handles, "startFrame": first_frame, "endFrame": last_frame, "outputType": output_type, diff --git a/pype/plugins/nuke/publish/validate_script.py b/pype/plugins/nuke/publish/validate_script.py index ad4a83b32f..08c91dab31 100644 --- a/pype/plugins/nuke/publish/validate_script.py +++ b/pype/plugins/nuke/publish/validate_script.py @@ -28,7 +28,7 @@ class ValidateScript(pyblish.api.InstancePlugin): ] # Value of these attributes can be found on parents - hierarchical_attributes = ["fps", "resolution_width", "resolution_height", "pixel_aspect"] + hierarchical_attributes = ["fps", "resolution_width", "resolution_height", "pixel_aspect", "handles"] missing_attributes = [] asset_attributes = {} From 39902e50a0e7abeb2b1a760b6b271bf113de1456 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 May 2019 17:51:54 +0200 Subject: [PATCH 34/41] remove echo label under assets --- pype/standalonepublish/app.py | 33 ++----------------- .../widgets/widget_family.py | 8 ++--- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/pype/standalonepublish/app.py b/pype/standalonepublish/app.py index 5d3bfd0505..da5fbbba10 100644 --- a/pype/standalonepublish/app.py +++ b/pype/standalonepublish/app.py @@ -39,19 +39,9 @@ class Window(QtWidgets.QDialog): # Validators self.valid_parent = False - # statusbar - added under asset_widget - label_message = QtWidgets.QLabel() - label_message.setFixedHeight(20) - # assets widget - widget_assets_wrap = QtWidgets.QWidget() - widget_assets_wrap.setContentsMargins(0, 0, 0, 0) widget_assets = AssetWidget(self) - layout_assets = QtWidgets.QVBoxLayout(widget_assets_wrap) - layout_assets.addWidget(widget_assets) - layout_assets.addWidget(label_message) - # family widget widget_family = FamilyWidget(self) @@ -66,10 +56,10 @@ class Window(QtWidgets.QDialog): QtWidgets.QSizePolicy.Expanding ) body.setOrientation(QtCore.Qt.Horizontal) - body.addWidget(widget_assets_wrap) + body.addWidget(widget_assets) body.addWidget(widget_family) body.addWidget(widget_components) - body.setStretchFactor(body.indexOf(widget_assets_wrap), 2) + body.setStretchFactor(body.indexOf(widget_assets), 2) body.setStretchFactor(body.indexOf(widget_family), 3) body.setStretchFactor(body.indexOf(widget_components), 5) @@ -81,13 +71,10 @@ class Window(QtWidgets.QDialog): # signals widget_assets.selection_changed.connect(self.on_asset_changed) - self.label_message = label_message self.widget_assets = widget_assets self.widget_family = widget_family self.widget_components = widget_components - self.echo("Connected to Database") - # on start self.on_start() @@ -130,22 +117,6 @@ class Window(QtWidgets.QDialog): parents.append(parent['name']) return parents - def echo(self, message): - ''' Shows message in label that disappear in 5s - :param message: Message that will be displayed - :type message: str - ''' - self.label_message.setText(str(message)) - def clear_text(): - ''' Helps prevent crash if this Window object - is deleted before 5s passed - ''' - try: - self.label_message.set_text("") - except: - pass - QtCore.QTimer.singleShot(5000, lambda: clear_text()) - def on_asset_changed(self): '''Callback on asset selection changed diff --git a/pype/standalonepublish/widgets/widget_family.py b/pype/standalonepublish/widgets/widget_family.py index 78388d17d8..63776b1df3 100644 --- a/pype/standalonepublish/widgets/widget_family.py +++ b/pype/standalonepublish/widgets/widget_family.py @@ -227,12 +227,12 @@ class FamilyWidget(QtWidgets.QWidget): self._build_menu(defaults) item.setData(ExistsRole, True) - self.echo("Ready ..") else: self._build_menu([]) item.setData(ExistsRole, False) if asset_name != self.NOT_SELECTED: - self.echo("'%s' not found .." % asset_name) + # TODO add logging into standalone_publish + print("'%s' not found .." % asset_name) self.on_version_refresh() @@ -339,10 +339,6 @@ class FamilyWidget(QtWidgets.QWidget): self.list_families.setCurrentItem(self.list_families.item(0)) - def echo(self, message): - if hasattr(self.parent_widget, 'echo'): - self.parent_widget.echo(message) - def schedule(self, func, time, channel="default"): try: self._jobs[channel].stop() From 1f0836b433db7b0e675040297819fd42cb0e6b85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 15 May 2019 17:55:00 +0200 Subject: [PATCH 35/41] tasks widget is shown only if are any tasks available --- pype/standalonepublish/widgets/widget_asset.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pype/standalonepublish/widgets/widget_asset.py b/pype/standalonepublish/widgets/widget_asset.py index 41163e13d6..54b7f7db44 100644 --- a/pype/standalonepublish/widgets/widget_asset.py +++ b/pype/standalonepublish/widgets/widget_asset.py @@ -169,6 +169,7 @@ class AssetWidget(QtWidgets.QWidget): task_view = DeselectableTreeView() task_view.setIndentation(0) task_view.setHeaderHidden(True) + task_view.setVisible(False) task_model = TasksTemplateModel() task_view.setModel(task_model) @@ -260,6 +261,7 @@ class AssetWidget(QtWidgets.QWidget): if asset: tasks = asset.get('data', {}).get('tasks', []) self.task_model.set_tasks(tasks) + self.task_view.setVisible(len(tasks)>0) def get_active_asset(self): """Return the asset id the current asset.""" From 0e48fa33993ae81a8bf2c68807cd3cdb34463c00 Mon Sep 17 00:00:00 2001 From: Milan Kolar Date: Wed, 15 May 2019 16:59:04 +0100 Subject: [PATCH 36/41] fix collector to include the new data --- .../standalonepublish/publish/collect_context.py | 13 ++++++------- .../publish/integrate_ftrack_instances.py | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/pype/plugins/standalonepublish/publish/collect_context.py b/pype/plugins/standalonepublish/publish/collect_context.py index cbe9df1ef6..6ac2dca936 100644 --- a/pype/plugins/standalonepublish/publish/collect_context.py +++ b/pype/plugins/standalonepublish/publish/collect_context.py @@ -55,10 +55,10 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): instance = context.create_instance(subset) instance.data.update({ - "subset": family + subset, + "subset": subset, "asset": asset_name, - "label": family + subset, - "name": family + subset, + "label": subset, + "name": subset, "family": family, "families": [family, 'ftrack'], }) @@ -74,10 +74,9 @@ class CollectContextDataSAPublish(pyblish.api.ContextPlugin): collections, remainder = clique.assemble(component['files']) if collections: self.log.debug(collections) - range = collections[0].format('{range}') - instance.data['startFrame'] = range.split('-')[0] - instance.data['endFrame'] = range.split('-')[1] - + instance.data['startFrame'] = component['startFrame'] + instance.data['endFrame'] = component['endFrame'] + instance.data['frameRate'] = component['frameRate'] instance.data["files"].append(component) instance.data["representations"].append(component) diff --git a/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py b/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py index 8d938bceb0..0dc9bb137c 100644 --- a/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py +++ b/pype/plugins/standalonepublish/publish/integrate_ftrack_instances.py @@ -57,19 +57,19 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): "name": "thumbnail" # Default component name is "main". } elif comp['preview']: - if not instance.data.get('startFrameReview'): - instance.data['startFrameReview'] = instance.data['startFrame'] - if not instance.data.get('endFrameReview'): - instance.data['endFrameReview'] = instance.data['endFrame'] + if not comp.get('startFrameReview'): + comp['startFrameReview'] = comp['startFrame'] + if not comp.get('endFrameReview'): + comp['endFrameReview'] = instance.data['endFrame'] location = ft_session.query( 'Location where name is "ftrack.server"').one() component_data = { # Default component name is "main". "name": "ftrackreview-mp4", "metadata": {'ftr_meta': json.dumps({ - 'frameIn': int(instance.data['startFrameReview']), - 'frameOut': int(instance.data['endFrameReview']), - 'frameRate': 25.0})} + 'frameIn': int(comp['startFrameReview']), + 'frameOut': int(comp['endFrameReview']), + 'frameRate': float(comp['frameRate')]})} } else: component_data = { From 0c5f876137dbba24f2c7953419337108ecdf00a5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 15 May 2019 21:45:52 +0200 Subject: [PATCH 37/41] feat(nuke): adding callback to nuke for change color of Loader nodes regarding to last version --- pype/nuke/lib.py | 47 +++++++++++++++++++++++++ pype/plugins/nuke/load/load_sequence.py | 8 +++-- setup/nuke/nuke_path/menu.py | 8 ++++- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pype/nuke/lib.py b/pype/nuke/lib.py index 46b1d6e4c8..20e7dfb210 100644 --- a/pype/nuke/lib.py +++ b/pype/nuke/lib.py @@ -29,6 +29,53 @@ def onScriptLoad(): nuke.tcl('load movWriter') +def checkInventoryVersions(): + """ + Actiual version idetifier of Loaded containers + + Any time this function is run it will check all nodes and filter only Loader nodes for its version. It will get all versions from database + and check if the node is having actual version. If not then it will color it to red. + + """ + + + # get all Loader nodes by avalon attribute metadata + for each in nuke.allNodes(): + if each.Class() == 'Read': + container = avalon.nuke.parse_container(each) + + if container: + node = container["_tool"] + avalon_knob_data = get_avalon_knob_data(node) + + # get representation from io + representation = io.find_one({ + "type": "representation", + "_id": io.ObjectId(avalon_knob_data["representation"]) + }) + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + # check the available version and do match + # change color of node if not max verion + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0x4ecd25ff", 16)) + + def writes_version_sync(): try: rootVersion = pype.get_version_from_path(nuke.root().name()) diff --git a/pype/plugins/nuke/load/load_sequence.py b/pype/plugins/nuke/load/load_sequence.py index b4e3cfb8b5..f03e0fc97e 100644 --- a/pype/plugins/nuke/load/load_sequence.py +++ b/pype/plugins/nuke/load/load_sequence.py @@ -128,11 +128,15 @@ class LoadSequence(api.Loader): # add additional metadata from the version to imprint to Avalon knob add_keys = ["startFrame", "endFrame", "handles", - "source", "colorspace", "author", "fps"] + "source", "colorspace", "author", "fps", "version"] data_imprint = {} for k in add_keys: - data_imprint.update({k: context["version"]['data'][k]}) + if k is 'version': + data_imprint.update({k: context["version"]['name']}) + else: + data_imprint.update({k: context["version"]['data'][k]}) + data_imprint.update({"objectName": read_name}) r["tile_color"].setValue(int("0x4ecd25ff", 16)) diff --git a/setup/nuke/nuke_path/menu.py b/setup/nuke/nuke_path/menu.py index 9a96a52850..4982513b78 100644 --- a/setup/nuke/nuke_path/menu.py +++ b/setup/nuke/nuke_path/menu.py @@ -1,5 +1,10 @@ -from pype.nuke.lib import writes_version_sync, onScriptLoad +from pype.nuke.lib import ( + writes_version_sync, + onScriptLoad, + checkInventoryVersions +) + import nuke from pypeapp import Logger @@ -8,5 +13,6 @@ log = Logger().get_logger(__name__, "nuke") nuke.addOnScriptSave(writes_version_sync) nuke.addOnScriptSave(onScriptLoad) +nuke.addOnScriptSave(checkInventoryVersions) log.info('Automatic syncing of write file knob to script version') From d4dccba24884520fb44b6406d2e47c8f9a7fab8d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 May 2019 17:32:43 +0200 Subject: [PATCH 38/41] fix(nuke): logging cleaning --- pype/nuke/__init__.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pype/nuke/__init__.py b/pype/nuke/__init__.py index 376e8f95b8..a2b1aeda6e 100644 --- a/pype/nuke/__init__.py +++ b/pype/nuke/__init__.py @@ -25,8 +25,6 @@ from pypeapp import Logger log = Logger().get_logger(__name__, "nuke") -# log = api.Logger.getLogger(__name__, "nuke") - AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype") PARENT_DIR = os.path.dirname(__file__) @@ -38,9 +36,8 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "nuke", "load") CREATE_PATH = os.path.join(PLUGINS_DIR, "nuke", "create") INVENTORY_PATH = os.path.join(PLUGINS_DIR, "nuke", "inventory") -self = sys.modules[__name__] -self.nLogger = None +# registering pyblish gui regarding settings in presets if os.getenv("PYBLISH_GUI", None): pyblish.register_gui(os.getenv("PYBLISH_GUI", None)) @@ -66,6 +63,7 @@ class NukeHandler(logging.Handler): "fatal", "error" ]: + msg = self.format(record) nuke.message(msg) @@ -77,9 +75,6 @@ if nuke_handler.get_name() \ logging.getLogger().addHandler(nuke_handler) logging.getLogger().setLevel(logging.INFO) -if not self.nLogger: - self.nLogger = Logger - def reload_config(): """Attempt to reload pipeline at run-time. @@ -157,7 +152,7 @@ def uninstall(): def on_pyblish_instance_toggled(instance, old_value, new_value): """Toggle node passthrough states on instance toggles.""" - self.log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( + log.info("instance toggle: {}, old_value: {}, new_value:{} ".format( instance, old_value, new_value)) from avalon.nuke import ( From 16182d41eba4cd5859f7a6d645635a9473759346 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 16 May 2019 17:34:33 +0200 Subject: [PATCH 39/41] feat(nuke): Load nkscript as precomp with version --- pype/plugins/nuke/load/load_script_precomp.py | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 pype/plugins/nuke/load/load_script_precomp.py diff --git a/pype/plugins/nuke/load/load_script_precomp.py b/pype/plugins/nuke/load/load_script_precomp.py new file mode 100644 index 0000000000..6fd76edd03 --- /dev/null +++ b/pype/plugins/nuke/load/load_script_precomp.py @@ -0,0 +1,170 @@ +from avalon import api, style, io +from pype.nuke.lib import get_avalon_knob_data +import nuke +import os +from pype.api import Logger +log = Logger().get_logger(__name__, "nuke") + + + +class LinkAsGroup(api.Loader): + """Copy the published file to be pasted at the desired location""" + + representations = ["nk"] + families = ["*"] + + label = "Load Precomp" + order = 10 + icon = "file" + color = style.colors.dark + + def load(self, context, name, namespace, data): + + from avalon.nuke import containerise + # for k, v in context.items(): + # log.info("key: `{}`, value: {}\n".format(k, v)) + version = context['version'] + version_data = version.get("data", {}) + + vname = version.get("name", None) + first = version_data.get("startFrame", None) + last = version_data.get("endFrame", None) + + # Fallback to asset name when namespace is None + if namespace is None: + namespace = context['asset']['name'] + + file = self.fname.replace("\\", "/") + self.log.info("file: {}\n".format(self.fname)) + + precomp_name = context["representation"]["context"]["subset"] + + # Set global in point to start frame (if in version.data) + start = context["version"]["data"].get("startFrame", None) + + # add additional metadata from the version to imprint to Avalon knob + add_keys = ["startFrame", "endFrame", "handles", + "source", "author", "fps"] + + data_imprint = { + "start_frame": start, + "fstart": first, + "fend": last, + "version": vname + } + for k in add_keys: + data_imprint.update({k: context["version"]['data'][k]}) + data_imprint.update({"objectName": precomp_name}) + + # group context is set to precomp, so back up one level. + nuke.endGroup() + + # P = nuke.nodes.LiveGroup("file {}".format(file)) + P = nuke.createNode( + "Precomp", + "file {}".format(file)) + + # Set colorspace defined in version data + colorspace = context["version"]["data"].get("colorspace", None) + self.log.info("colorspace: {}\n".format(colorspace)) + + + # ['version', 'file', 'reading', 'output', 'useOutput'] + + P["name"].setValue("{}_{}".format(name, namespace)) + P["useOutput"].setValue(True) + + with P: + # iterate trough all nodes in group node and find pype writes + writes = [n.name() for n in nuke.allNodes() + if n.Class() == "Write" + if get_avalon_knob_data(n)] + + # create panel for selecting output + panel_choices = " ".join(writes) + panel_label = "Select write node for output" + p = nuke.Panel("Select Write Node") + p.addEnumerationPulldown( + panel_label, panel_choices) + p.show() + P["output"].setValue(p.value(panel_label)) + + P["tile_color"].setValue(0xff0ff0ff) + + return containerise( + node=P, + name=name, + namespace=namespace, + context=context, + loader=self.__class__.__name__, + data=data_imprint) + + def switch(self, container, representation): + self.update(container, representation) + + def update(self, container, representation): + """Update the Loader's path + + Nuke automatically tries to reset some variables when changing + the loader's path to a new file. These automatic changes are to its + inputs: + + """ + + from avalon.nuke import ( + update_container + ) + + node = nuke.toNode(container['objectName']) + + root = api.get_representation_path(representation).replace("\\","/") + + # Get start frame from version data + version = io.find_one({ + "type": "version", + "_id": representation["parent"] + }) + + # get all versions in list + versions = io.find({ + "type": "version", + "parent": version["parent"] + }).distinct('name') + + max_version = max(versions) + + updated_dict = {} + updated_dict.update({ + "representation": str(representation["_id"]), + "endFrame": version["data"].get("endFrame"), + "version": version.get("name"), + "colorspace": version["data"].get("colorspace"), + "source": version["data"].get("source"), + "handles": version["data"].get("handles"), + "fps": version["data"].get("fps"), + "author": version["data"].get("author"), + "outputDir": version["data"].get("outputDir"), + }) + + # Update the imprinted representation + update_container( + node, + updated_dict + ) + + node["file"].setValue(root) + + # change color of node + if version.get("name") not in [max_version]: + node["tile_color"].setValue(int("0xd84f20ff", 16)) + else: + node["tile_color"].setValue(int("0xff0ff0ff", 16)) + + log.info("udated to version: {}".format(version.get("name"))) + + + def remove(self, container): + from avalon.nuke import viewer_update_and_undo_stop + node = nuke.toNode(container['objectName']) + with viewer_update_and_undo_stop(): + nuke.delete(node) From 4f4a45c54b201cd69ec056622eeeee351a94043e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 17 May 2019 11:21:43 +0200 Subject: [PATCH 40/41] fix(premiere): removing message_window from pype.api and adding link to the message into premiere --- pype/api.py | 5 ----- pype/premiere/__init__.py | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pype/api.py b/pype/api.py index fcdcbce82b..0acb80e383 100644 --- a/pype/api.py +++ b/pype/api.py @@ -46,8 +46,6 @@ from .lib import ( get_data_hierarchical_attr ) -from .widgets.message_window import message - __all__ = [ # plugin classes "Extractor", @@ -89,7 +87,4 @@ __all__ = [ "Colorspace", "Dataflow", - # QtWidgets - "message" - ] diff --git a/pype/premiere/__init__.py b/pype/premiere/__init__.py index 74ce106de2..cc5abe115e 100644 --- a/pype/premiere/__init__.py +++ b/pype/premiere/__init__.py @@ -7,6 +7,8 @@ from pyblish import api as pyblish from pypeapp import Logger from .. import api +from ..widgets.message_window import message + import requests log = Logger().get_logger(__name__, "premiere") @@ -42,7 +44,7 @@ def request_aport(url_path, data={}): return req except Exception as e: - api.message(title="Premiere Aport Server", + message(title="Premiere Aport Server", message="Before you can run Premiere, start Aport Server. \n Error: {}".format( e), level="critical") @@ -99,7 +101,7 @@ def install(): # synchronize extensions extensions_sync() - api.message(title="pyblish_paths", message=str(reg_paths), level="info") + message(title="pyblish_paths", message=str(reg_paths), level="info") def uninstall(): From 8c2db60b26074f66578b8f91eec0651bfa2a7992 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 17 May 2019 16:21:47 +0200 Subject: [PATCH 41/41] (hotfix)/ added stagingDir to standalone publisher --- .../widgets/widget_component_item.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pype/standalonepublish/widgets/widget_component_item.py b/pype/standalonepublish/widgets/widget_component_item.py index 43aa54a955..a58a292ec5 100644 --- a/pype/standalonepublish/widgets/widget_component_item.py +++ b/pype/standalonepublish/widgets/widget_component_item.py @@ -284,11 +284,19 @@ class ComponentItem(QtWidgets.QFrame): self.preview.change_checked(hover) def collect_data(self): + in_files = self.in_data['files'] + staging_dir = os.path.dirname(in_files[0]) + + files = [os.path.basename(file) for file in in_files] + if len(files) == 1: + files = files[0] + data = { 'ext': self.in_data['ext'], 'label': self.name.text(), - 'representation': self.input_repre.text(), - 'files': self.in_data['files'], + 'name': self.input_repre.text(), + 'stagingDir': staging_dir, + 'files': files, 'thumbnail': self.is_thumbnail(), 'preview': self.is_preview() }