Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For #46368: Customizable export UI, plus additional hook methods for Shot/Cut updating. #42

Merged
merged 16 commits into from Mar 14, 2018

Conversation

thebeeland
Copy link
Contributor

@thebeeland thebeeland commented Feb 20, 2018

This is implementing the idea outlined in #41 from CBSD. It's providing hook methods for building custom widgets for each processor we provide, plus allows for custom preset properties to be created, and those properties then being used to set/initialize the custom widget.

In addition, to meet the workflow requirements outlined by CBSD, additional hook methods have been added to allow for customization of if/how Shots, Cuts, and CutItems are created/updated during the export process.

NOTE: There are likely going to be use cases for custom UI elements/properties that we then don't have hooks for in the right places to control certain export functionality via those custom properties. The hiero_update_shot and hiero_update_cuts hooks added here provide the flexibility necessary to implement CBSD's custom shot processor behavior, but doesn't go beyond that.

Hooks added:

  • hiero_customize_export_ui:
    • create_shot_processor_widget
    • get_shot_processor_ui_properties
    • set_shot_processor_ui_properties
    • create_transcode_exporter_widget
    • get_transcode_exporter_ui_properties
    • set_transcode_exporter_ui_properties
    • create_audio_exporter_widget
    • get_audio_exporter_ui_properties
    • set_audio_exporter_ui_properties
    • create_nuke_shot_exporter_widget
    • get_nuke_shot_exporter_ui_properties
    • set_nuke_shot_exporter_ui_properties
  • hiero_update_shot:
    • create_filesystem_structure
    • update_shotgun_shot_entity
  • hiero_update_cuts:
    • allow_cut_updates
    • create_cut_item
    • get_cut_thumbnail

Example custom UI based on CBSD's example:

image

@dashielb1010
Copy link

Wow this is great!
So cool that these hooks are becoming a reality. I'm very happy you've taken the time to incorporate them. Looking forward to testing them out when my schedule frees up! -Cheers!

info.yml Outdated

hook_update_cuts:
type: hook
description: ""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops. Missed this one. I'll get a description in!

@manneohrstrom
Copy link
Contributor

Adding josh as well - his input will be very valuable here given that he has been working extensively both with the publish2 UI extensions and the hiero exporter.

Copy link
Contributor

@manneohrstrom manneohrstrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added what i hope are some helpful comments! I think it would be great to be able to look at the sphinx docs for this as a next step. Having a full, practical (ideally cut n paste) example for the custom UI hook would be super awesome.

built by the associated get properties hook method.
"""
return

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whitespace :)

HookBaseClass = sgtk.get_hook_baseclass()


class HieroCustomizeExportUI(HookBaseClass):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a great opportunity to build these hooks like we did the shotgun write node and the publisher, with sphinx docs. See:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass done. See the comment below for more detail on a few caveats.

"""
return None

def get_shot_processor_ui_properties(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should follow the same patterns as we do in the publisher:
http://developer.shotgunsoftware.com/tk-multi-publish2/plugin.html#tk_multi_publish2.base_hooks.PublishPlugin.get_ui_settings

So how about

create_shot_processor_widget
get_shot_processor_ui_settings
set_shot_processor_ui_settings

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...I was just doing this, and it feels odd. Everything related to this on the Hiero/NS end of things revolves around "properties" and not "settings." It's feeling really strange mixing the two nomenclatures interchangeably. Having the getter return "settings dictionaries" feels okay, generally speaking, but then having the setter provide a list of property objects from Hiero's API and calling those "settings" doesn't feel right.

Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say stick with Hiero/NS terms

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning that way, as well.

label="Create Cut:",
key="sgCreateCut",
value=True,
type=bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to pass type? This feels wrong to me. When is this not type(value)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah...I just tried to think of an example of where it might be different than type(value), and I can't come up with anything reasonable. I'll ditch it!


Example Return Data:

.. code-block:: python
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need both a syntax example and a real example. Maybe we can skip this one and keep the actual example below?

return [
dict(
label="Create Cut:",
key="sgCreateCut",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this sgCreateCut - what does it signify?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the kwarg name from Hiero's ui property constructor, but maybe we can make it a little more obvious by calling it "property_name" or "name" instead. Label is the human-readable name that's displayed in the UI, and key is the "system" name of the property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to "name"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll also change the example to use a more verbose name

:rtype: dict or None
"""
cut_item = self.parent.sgtk.shotgun.create("CutItem", cut_item_data)
self.parent.logger.info("Created CutItem in Shotgun: %s" % cut_item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will the fact that we don't batch these guys really slow things down?
Could the hook take a list of cut items and do a batch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can look at the underlying logic and see what might be possible. Is it too slow now? This isn't changing the logic flow other than running it from a hook.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this new or old code? I am not sure about current performance. But if you are running say a 200 shot conform, running create 200 times will definitely be slower than running a batch. But like you say, it depends on other factors, workflow ux etc. If this is old code and we are merely updating the docs, probably better to leave things the way they are!

try:
# See if we can find a poster frame for the sequence and
# turn that into a usable thumbnail.
thumbnail = hiero_sequence.thumbnail(hiero_sequence.posterFrame())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we provide a link to a hiero API reference here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not do it as part of the sphinx docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do!

Handles creating the filesystem structure for the shot that
was exported. The preset properties dictionary is provided to
allow for the lookup of any custom properties that might have
been defined in other hooks, like can be achieved when using
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a practical example to illustrate here would be great.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

self.parent.logger.debug(
"Creating file system structure for %s %s..." % (entity_type, entity_id)
)
self.parent.sgtk.create_filesystem_structure(entity_type, [entity_id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep in mind that the self.parent.logger in hooks will require a fairly modern core.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tk-nuke already requires v0.18.45

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And looking deeper, tk-hiero-export requires v0.17.21 of tk-core, which looks like it contains the parent property for Hook.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're doing the hook docs and base hooks, it's going to need a really new core anyway. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the minimum required for that? I assume it's newer than 0.17.21, which means I should bump up the requirement in info.yml.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe 2 releases ago v0.18.124

Jeff Beeland added 3 commits February 26, 2018 18:04
NOTES:
- Old-style hooks with execute methods must use a pass instead of raising a NotImplementedError in the base_hooks module.
- The base_hooks module can't live under the tk_hiero_export module, because importing that module requires access to the hiero API, which isn't available when generating docs.
@thebeeland
Copy link
Contributor Author

I've added a first pass of the sphinx docs setup. As noted in the commit message, there are a couple caveats when compared to publish2 and tk-nuke-quickreview:

  • The execute methods in old-style hooks have to pass instead of raising NotImplementedError.
  • The base_hooks can't live under the tk_hiero_export module, because importing from tk_hiero_export requires access to the hiero api, which isn't available at docs build time.
  • The docs for most of the older are VERY minimal, so it doesn't look great. We should probably include them anyway, though, and queue up a ticket for a future sprint to do a docs pass on those older hooks.

Copy link
Contributor

@josh-t josh-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looks good. Biggest comment is to consider a simple, 3 method API for the custom UI hook, then connect instances of that to whatever types you want to customize via the config.

@@ -34,6 +36,84 @@ def setApp(cls, app):
def app(self):
return self._app

def _get_custom_properties(self, get_method):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docstring.

return self._custom_property_definitions[get_method]

def _get_custom_widget(self, parent, create_method, get_method, set_method, properties=None):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

self._displayName = "Shotgun Audio Export"
self._taskType = ShotgunAudioExporter

def populateUI(self, widget, exportTemplate):
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing docstring.


- Status of associated Shot entities can be updated
- Nuke scripts can be written into the project's filesystem structure for each Shot that's processed
- Sequence and Shot entity data can be updated in Shotgun
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth pointing out that other parent entity types can be used as well (Episodes for example)

"""
def create_shot_processor_widget(self, parent_widget):
"""
Builds and returns a custom widget to be embedded in the parent exporter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth mentioning how it will fit into the layout?

"""
return

def create_audio_exporter_widget(self, parent):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about a type argument as well, but I think you'd just end up with a big conditional that called out to other methods in the hook.

One idea that could simplify things a bunch would be to define a base hook that only has 3 methods (create widget and get/set properties). This could be used for any of the types, but you associate the type/hook at the config level. So if you need a custom audio and transcode hooks, you'd configure something like this:

custom_export_ui_plugins:
  - type: audio
    hook: "{config}/hooks/tk-hiero-export/custom_audio.py"
  - type: transcode
    hook: "{config}/hooks/tk-hiero-export/custom_transcode.py"

Now you can cut down on the boilerplate and just define the API that's used for all the various types. Would that work?

been defined in other hooks, like can be achieved when using
the hiero_customize_export_ui hook.

:param str entity_type: The entity type that was created or
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're breaking this out into a hook, I'm wondering if we should deemphasize "Shot" to some extent. Maybe something as simple as renaming the hook/class/methods to not include the word "shot".

Copy link
Contributor Author

@thebeeland thebeeland Mar 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning towards not changing this from "shot", simply because it's more or less paired with the existing hiero_get_shot hook. We can't easily rename that hook without potentially breaking client setups that make use of it, so I'm inclined to keep get/update shot hooks sharing nomenclature.

self.parent.logger.debug(
"Creating file system structure for %s %s..." % (entity_type, entity_id)
)
self.parent.sgtk.create_filesystem_structure(entity_type, [entity_id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're doing the hook docs and base hooks, it's going to need a really new core anyway. :)

@@ -254,41 +260,61 @@ def taskStep(self):
sg_shot['task_template'] = template

# commit the changes and update the thumbnail
self.app.log_debug("Updating info for %s %s: %s" % (shot_type, shot_id, str(sg_shot)))
self.app.tank.shotgun.update(shot_type, shot_id, sg_shot)
self.app.execute_hook_method(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


# create the directory structure
self.app.log_debug("Creating file system structure for %s %s..." % (shot_type, shot_id))
self.app.tank.create_filesystem_structure(shot_type, [shot_id])
self.app.execute_hook_method(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@josh-t
Copy link
Contributor

josh-t commented Feb 27, 2018

One more thing: You might consider creating hook instances throughout the app and keeping them persistent rather than the old way. That way you can instantiate them with the new base classes to get any default implementation, allowing clients to only override methods they're customizing.

You should inject the base classes when calling execute_hook_method.

@josh-t
Copy link
Contributor

josh-t commented Feb 27, 2018

Another though... sorry...

Would be nice to have some sample/test implementation (looks like you've already written) that we could use for QA. The publish2 app does something similar (see here).

@manneohrstrom
Copy link
Contributor

Thanks for the sphinx docs pass 1! Awesome!!! Josh made some great points about the overall flow, so I'll hold off further changes for now until you have reviewed that feedback or i might confuse things :)

About the old methods vs sphinx - definitely huge value to get them into sphinx even if they count have fully featured docs like the new ones - i think it would be great if we could at least get basic params and descriptions in as part of this PR - hopefully that won't take a huge amount of time?

This is very exciting!

@manneohrstrom
Copy link
Contributor

One more thought - for the next round of review would be great if you could zip up and attach a sphinx doc build to the PR! Just in case it's tricky to build locally. (e.g. code imports hiero stuff etc).

@thebeeland
Copy link
Contributor Author

After discussing it with Josh and Rob, we're not going to go the full publish2-style setup for how the hooks are configured. Instead, we're keeping the single hook for the custom export ui, but cleaning it up a bit. I've gone through and set base classes pointing back to the base_hooks (even for the old hooks), and I've put the no-op implementation of each for the create/get/set hook methods for custom ui into the base hook. I've then emptied out the hooks/hiero_customize_export_ui.py file, and linked to the (eventual) sphinx docs page in a comment. @manneohrstrom -- If you have any better suggestions around that last bit, please let me know!

I've also added some basic docs to the older hooks, and things look much better in sphinx docs as a result.

Back to you guys! @josh-t @manneohrstrom

Copy link
Contributor

@josh-t josh-t left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Looks awesome. I think you will need to bump the core requirement to v0.18.124.

self.parent.logger.debug(
"Creating file system structure for %s %s..." % (entity_type, entity_id)
)
self.parent.sgtk.create_filesystem_structure(entity_type, [entity_id])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe 2 releases ago v0.18.124

@manneohrstrom
Copy link
Contributor

For reference, here are the built docs:

docs.zip

Copy link
Contributor

@manneohrstrom manneohrstrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments. Looks great! Where it is easy to add, it would be great with some simple doc example of any complex data structure (e.g. dicts). Also, where we are referring to hiero API stuff, it would be great with links to the hiero api if possible!

docs/index.rst Outdated

.. py:currentmodule:: base_hooks

Creating custom UI elements
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better that we order the hooks according to frequency - start with the ones that are most likely to be used by users, and the most advanced ones at the end!

docs/index.rst Outdated

During the export process a number things can happen:

- Status of associated Shot entities can be updated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i would ❤️ if there was a flow chart diagram showing the process and included all the hooks. I think that would be a perfect overview for TDs to understand what is possible. happy to help putting this together if you need help.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, we can chat offline about this.

@@ -0,0 +1,106 @@
Shotgun Hiero Export API reference, |release|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something weird about the doc build - not sure where to put the comment so adding it here - the TOC seems to contain the same items twice. I just realised that we have the same issue for the nuke review docs but not for the publish. Could you investigate and potentially update the nuke review docs too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll ping you offline about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an internal ticket to look at this more closely. It's not immediately obvious why the ToC is wonky.

- Nuke scripts can be written into the project's filesystem structure for each Shot that's processed
- Sequence and Shot entity data can be updated in Shotgun
- Cuts can be update to include new CutItems built from the exported sequences

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there good hiero API documentation? Perhaps it's worth including a link here in the intro?

app.py Outdated
@@ -50,6 +50,9 @@
ShotgunAudioExporterUI,
ShotgunHieroObjectBase,
)

import base_hooks
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the absolute import? What if this app runs next another app which also imports base hooks?

We typically very very rarely do absolute imports and if we do, we name things explicitly (e.g. the import tk_hiero_export above is slightly better but still unconventional!)

See the nuke review app for another way to do this: https://github.com/shotgunsoftware/tk-nuke-quickreview/blob/master/app.py#L30


:param processor: Processor The is being used, in case distinguishing between
differnt exports is needed.
:param processor: The processor object that is about to be started.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is a processor? Can we link to hiero docs?

:rtype: dict or None
"""
cut_item = self.parent.sgtk.shotgun.create("CutItem", cut_item_data)
self.parent.logger.info("Created CutItem in Shotgun: %s" % cut_item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this new or old code? I am not sure about current performance. But if you are running say a 200 shot conform, running create 200 times will definitely be slower than running a batch. But like you say, it depends on other factors, workflow ux etc. If this is old code and we are merely updating the docs, probably better to leave things the way they are!

try:
# See if we can find a poster frame for the sequence and
# turn that into a usable thumbnail.
thumbnail = hiero_sequence.thumbnail(hiero_sequence.posterFrame())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not do it as part of the sphinx docs?

# not expressly granted therein are reserved by Shotgun Software Inc.

# UI Hook
# ===========================
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: maybe drop this header. We don't normally have these kinds of things.


class HieroCustomizeExportUI(HookBaseClass):
"""
This class defines methods that can be used to customize the UI of the various
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we include a screenshot to illustrate as part of the docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Also added some overview sphinx docs for customizing UI. Basic stuff, but it's a good, brief intro with an example screenshot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants