mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
initialization of nukestudio integration
This commit is contained in:
parent
bb39f87f81
commit
ca92c42cde
23 changed files with 1598 additions and 14 deletions
25
README.md
25
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.
|
||||
|
|
|
|||
181
pype/nukestudio/__init__.py
Normal file
181
pype/nukestudio/__init__.py
Normal file
|
|
@ -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)
|
||||
347
pype/nukestudio/inventory.py
Normal file
347
pype/nukestudio/inventory.py
Normal file
|
|
@ -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
|
||||
242
pype/nukestudio/lib.py
Normal file
242
pype/nukestudio/lib.py
Normal file
|
|
@ -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_()
|
||||
7
pype/nukestudio/menu.py
Normal file
7
pype/nukestudio/menu.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import nuke
|
||||
from avalon.api import Session
|
||||
|
||||
from pype.nuke import lib
|
||||
|
||||
|
||||
def install():
|
||||
188
pype/plugins/nukestudio/publish/collect.py
Normal file
188
pype/plugins/nukestudio/publish/collect.py
Normal file
|
|
@ -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
|
||||
13
pype/plugins/nukestudio/publish/collect_active_project.py
Normal file
13
pype/plugins/nukestudio/publish/collect_active_project.py
Normal file
|
|
@ -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"]))
|
||||
27
pype/plugins/nukestudio/publish/collect_colorspace.py
Normal file
27
pype/plugins/nukestudio/publish/collect_colorspace.py
Normal file
|
|
@ -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"]))
|
||||
14
pype/plugins/nukestudio/publish/collect_current_file.py
Normal file
14
pype/plugins/nukestudio/publish/collect_current_file.py
Normal file
|
|
@ -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"]))
|
||||
13
pype/plugins/nukestudio/publish/collect_host.py
Normal file
13
pype/plugins/nukestudio/publish/collect_host.py
Normal file
|
|
@ -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()))
|
||||
12
pype/plugins/nukestudio/publish/collect_host_version.py
Normal file
12
pype/plugins/nukestudio/publish/collect_host_version.py
Normal file
|
|
@ -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)
|
||||
15
pype/plugins/nukestudio/publish/collect_selection.py
Normal file
15
pype/plugins/nukestudio/publish/collect_selection.py
Normal file
|
|
@ -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
|
||||
14
pype/plugins/nukestudio/publish/collect_submission.py
Normal file
14
pype/plugins/nukestudio/publish/collect_submission.py
Normal file
|
|
@ -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"]))
|
||||
102
pype/plugins/nukestudio/publish/extract_review.py
Normal file
102
pype/plugins/nukestudio/publish/extract_review.py
Normal file
|
|
@ -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"
|
||||
125
pype/plugins/nukestudio/publish/extract_tasks.py
Normal file
125
pype/plugins/nukestudio/publish/extract_tasks.py
Normal file
|
|
@ -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)
|
||||
43
pype/plugins/nukestudio/publish/validate_names.py
Normal file
43
pype/plugins/nukestudio/publish/validate_names.py
Normal file
|
|
@ -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"]
|
||||
53
pype/plugins/nukestudio/publish/validate_projectroot.py
Normal file
53
pype/plugins/nukestudio/publish/validate_projectroot.py
Normal file
|
|
@ -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
|
||||
29
pype/plugins/nukestudio/publish/validate_resolved_paths.py
Normal file
29
pype/plugins/nukestudio/publish/validate_resolved_paths.py
Normal file
|
|
@ -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
|
||||
58
pype/plugins/nukestudio/publish/validate_task.py
Normal file
58
pype/plugins/nukestudio/publish/validate_task.py
Normal file
|
|
@ -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
|
||||
59
pype/plugins/nukestudio/publish/validate_track_item.py
Normal file
59
pype/plugins/nukestudio/publish/validate_track_item.py
Normal file
|
|
@ -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"]
|
||||
22
pype/plugins/nukestudio/publish/validate_viewer_lut.py
Normal file
22
pype/plugins/nukestudio/publish/validate_viewer_lut.py
Normal file
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue