Action Maya Convert 2016

mail@pastecode.io avatar
unknown
python
2 years ago
9.4 kB
3
Indexable
Never
# :coding: utf-8

import logging
import os.path
import threading
import json
import sys

# noinspection DuplicatedCode
PFX_LIB_DIRECTORY = os.path.abspath(os.environ.get('STUDIO_LIBRARY'))
if PFX_LIB_DIRECTORY not in sys.path:
    sys.path.append(PFX_LIB_DIRECTORY)

VENDOR_DIRECTORY = os.path.abspath(os.environ.get('STUDIO_VENDOR'))
if VENDOR_DIRECTORY not in sys.path:
    sys.path.append(VENDOR_DIRECTORY)

import ftrack_api   # noqa: E402
from ftrack_action_handler.action import BaseAction   # noqa: E402
from studio_library.helpers.ftrack_classes.FtrackSendMaya2016Constructor import FtrackSendMaya2016Constructor   # noqa: E402

ENTITY_TYPES = {'AssetVersion': 'version.id',
                'TypedContext': 'version.task_id',
                'Component': 'id'}


def asynchronous(fn):
    """Run *fn* asynchronously."""
    def wrapper(*args, **kwargs):
        thread = threading.Thread(target=fn, args=args, kwargs=kwargs)
        thread.start()
    return wrapper


# noinspection SpellCheckingInspection,DuplicatedCode
def _get_components(session, entity_type, entity_id):
    components = session.query(
        'select id, name, version, version.task, '
        'version.task.name, version.task.parent.name, version.task.parent.parent.name from Component '
        'where {arg} is {id} '
        'and name is scene'.format(
            arg=ENTITY_TYPES.get(entity_type, 'version.task_id'),
            id=entity_id)).all()
    task = components[0]['version']['task'] if components else None
    return task, components


# noinspection PyBroadException
def _update_job(job, description, session, status='running'):
    job['data'] = json.dumps({'description': description})
    job['status'] = status
    try:
        session.commit()
    except Exception:
        session.rollback()
        raise
    else:
        return job


# noinspection PyBroadException
def _add_job_component(job, component_id, session):
    session.create(
        'JobComponent',
        {'component_id': component_id, 'job_id': job['id']}
    )
    try:
        session.commit()
    except Exception:
        session.rollback()


# noinspection PyBroadException
class ConvertToMaya2016Action(BaseAction):
    """Action to write note on multiple entities."""

    identifier = 'convert-to-maya-2016'
    label = 'Convert to Maya 2016'
    description = 'Converting Scenes to Maya 2016'
    icon = 'https://cdn.icon-icons.com/icons2/1508/PNG/512/maya_103816.png'
    output_folder = ''

    def discover(self, session, entities, event):
        """Return true if the action is discoverable.
        Action is discoverable if all entities are Tasks or AssetVersions."""
        if not entities:
            self.logger.info('Selection is not valid for entities: {}'.format(entities))
            return False

        for entity_type, entity_id in entities:
            if entity_type in ('AssetVersion', 'Component'):
                continue
            elif entity_type == 'TypedContext' and session.get(entity_type, entity_id).entity_type == 'Task':
                continue
            else:
                self.logger.info('Selection is not valid. Entity type is {}'.format(entity_type))
                return False
        self.logger.info('Selection is valid')
        return True

    def launch(self, session, entities, event):
        values = event['data']['values']
        self.output_folder = values.pop('output_folder', '')

        components = {comp_id for k, comp_id in values.items()
                      # Get only scene keys and check their boolean state
                      if k.startswith('scene') and values.get('do|{}'.format(k.split('|', 1)[-1]))}

        user_id = event['source']['user']['id']
        self._async_submit(components, user_id)

        return {
            'success': True,
            'message': 'Started submitting scenes'
        }

    @asynchronous
    def _async_submit(self, components, user_id):

        # Create new session as sessions are not guaranteed to be thread-safe.
        session = ftrack_api.Session(auto_connect_event_hub=False, plugin_paths=[])

        # Creating ftrack job
        job = session.create('Job', {
            'user_id': user_id,
            'status': 'running',
        })

        try:
            location = session.query('Location where name is "ftrack.unmanaged"').first()
            i = 0
            for component_id in components:
                component = session.get('Component', component_id)
                input_filepath = location.get_filesystem_path(component).replace('\\', '/')
                # TODO: Add name from ftrack hierarchy not the input_filepath
                _update_job(job,
                            'Uploading {}/{} - {}'.format(i + 1, len(components), os.path.basename(input_filepath)),
                            session)
                try:
                    self._submit_scene(input_filepath)
                except Exception as err:
                    self.logger.exception('Uploading scene {} failed\n{}'.format(input_filepath, err))
                else:
                    i += 1
            if i > 1:
                _update_job(job,
                            '{}/{} scenes submitted to Deadline'.format(i, len(components)), session, status='done')
            else:
                _update_job(job, 'Submission of all {} scenes failed', session, status='failed')
        except BaseException as err:
            self.logger.exception('Action failed\n{}'.format(err))
            session.rollback()
            _update_job(job, 'Action failed. Check logs for further information', session, status='failed')

    def _submit_scene(self, input_filepath):
        output_filepath = os.path.join(self.output_folder, os.path.basename(input_filepath)).replace('\\', '/')

        copier = FtrackSendMaya2016Constructor(input_filepath, output_filepath)
        return copier.normal_submit(job_data=None)

    def interface(self, session, entities, event):
        """Return interface."""
        values = event['data'].get('values', {})
        if values:
            return None

        items = []
        task = None
        for entity_type, entity_id in entities:
            task, components = _get_components(session, entity_type, entity_id)
            if not components:
                continue
            components.sort(key=lambda x: -x['version']['version'])
            components_options = [
                {'label': 'v{:03d}'.format(component['version']['version']), 'value': component['id']}
                for component in components
            ]
            items += [{
                        'label': ' ' * 40 + '{} / {} / {}'.format(
                            task['parent']['parent']['name'], task['parent']['name'], task['name']),
                        'name': 'do|{}'.format(task['id']),
                        'type': 'boolean',
                        'value': True
                    },
                    {
                        'label': 'Scene',
                        'name': 'scene|{}'.format(task['id']),
                        'type': 'enumerator',
                        'value': components_options[0]['value'],
                        'data': components_options
                    },
                    {
                        'value': '---',
                        'type': 'label'
                    }]
        if not items:
            items.append({
                    'value': '- No any **scene** component found!',
                    'type': 'label'
                })
        else:
            from datetime import datetime
            from sc.ftrack_utils import ftrack_paths
            default_path = ftrack_paths.getPath(
                task, template=['pfxWorkBase'], return_format='single', session=session)
            output_folder = os.path.join(
                default_path, 'Data_Out', '{:%Y-%m-%d}'.format(datetime.now())) if default_path else ''
            items += [
                {
                    'label': 'Output Folder: ',
                    'type': 'text',
                    'value': os.path.normpath(output_folder),
                    'name': 'output_folder'
                }, {
                    'type': 'form',
                    'title': 'Select Scenes for Convert',
                    'submit_button_label': 'Start'
                }
            ]
        return items


# noinspection PyUnusedLocal
def register(session, **kw):
    """Register plugin. Called when used as an plugin."""

    # Validate that session is an instance of ftrack_api.Session. If not,
    # assume that register is being called from an old or incompatible API and
    # return without doing anything.
    if not isinstance(session, ftrack_api.session.Session):
        return

    action_handler = ConvertToMaya2016Action(session)
    action_handler.register()


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    s = ftrack_api.Session()
    register(s)

    # Wait for events
    logging.info('Registered actions and listening for events. Use Ctrl-C to abort.')
    s.event_hub.wait()