Source code for tools.project.metadataManager

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

""" Module metadataManager:
This module contains classes for opening a metadata manager to manage the
metadata on the current project in Mari.
"""

# noinspection PyUnresolvedReferences
import mari
import logging
import traceback
import os
from PySide.QtGui import QTableWidgetItem, QHeaderView, QMessageBox, QFileDialog
from PySide import QtCore
from stkMariTools.lib.jsonutils import JSONUtils

from stkMariTools.lib.ui_utils import MariToolsMenuItem, MariWidget
from stkMariTools.tools.project.ui.metadataManagerUi import Ui_Form_metadataManager


[docs]class MetadataManagerMenuItem(MariToolsMenuItem): """ This class adds the Metadata Manager action. """ def __init__( self ): """ The constructor. :return: """ # Call base constructor super( MetadataManagerMenuItem, self ).__init__() # Set the class instance in order for the action command to be # able to be set in the namespace correctly. mari.MetadataManagerMenuItem = self # Action item is to be called in the Menu self.actionIdentifier = 'Metadata Manager' # Python command to be run when action is executed from the Menu self.actionCommand = 'mari.MetadataManagerMenuItem.openUi()' # Path to the action in the Mari Menu self.actionPath = 'MainWindow/&Scripts/&Project' # Icon to use for the action self.actionIcon = 'About' # Register the plugin self.addMariToolsMenuItem()
[docs] def openUi(self): """ This method launches the Metadata Manager UI """ MetadataManager()
[docs]class MetadataManager(MariWidget): """ This class creates a Metadata Manager to manage metadata on Mari projects. """ def __init__(self, title='Metadata Manager'): """ The constructor. :param title: :return: """ # Create class logger self.logger = logging.getLogger(__name__) self.ui_form = None # Define table headers self.headers = ['Name', 'Data'] # Define default metadata keys self.default_keys = [ 'AnimControlFrame', 'ColorEnabled', 'ColorProfile', 'MriImagePickerImageImage', 'MriImportPath', 'MriImportTemplate', 'OcioColorSpace', 'OcioConfigPath', 'OcioDisplay', 'OcioExtrapolate', 'OcioGain', 'OcioGamma', 'OcioLutPath', 'OcioSwizzle', 'OcioView' ] # Store all current metadata as backup self.default_metadata = self.getMetadata() # Call base constructor super( MetadataManager, self ).__init__(title=title)
[docs] def defineUi(self): """ This method should be overloaded to handle defining the actual UI interface. It is run before populateData() and makeConnections(). :return: """ # Get instance of UI form and setup widgets onto that instance self.ui_form = Ui_Form_metadataManager() self.ui_form.setupUi(self.widget_holder) self.ui_form.tableWidget_metadataView.setColumnCount(2) # Create horizontal header horizontal_header = QHeaderView(QtCore.Qt.Orientation.Horizontal) # Set table headers properties self.ui_form.tableWidget_metadataView.setHorizontalHeader( horizontal_header ) self.ui_form.tableWidget_metadataView.setHorizontalHeaderLabels(self.headers) # First re-size the header to the contents, then allow user to resize further horizontal_header.setResizeMode(QHeaderView.ResizeToContents) horizontal_header.setResizeMode(QHeaderView.Interactive) horizontal_header.setStretchLastSection(True)
[docs] def makeConnections(self, *args, **kwargs): """ This method handles connecting UI widgets to callbacks. :param args: :param kwargs: :return: ``None`` """ # Connect Mari scene open/close events to metadata refresh mari.utils.connect(mari.projects.opened, self.refreshMetadata) mari.utils.connect(mari.projects.archived, self.refreshMetadata) mari.utils.connect(mari.projects.closed, self.refreshMetadata) metadataView_model = self.ui_form.tableWidget_metadataView.model() metadataView_model.dataChanged.connect(self.setMetadata) # Connect UI buttons self.ui_form.btn_reset.clicked.connect(self.resetToDefault) self.ui_form.btn_removeCustomMetadata.clicked.connect(self.removeNonStandardMetadata) self.ui_form.btn_refresh.clicked.connect(self.refreshMetadata) self.ui_form.btn_addMetadata.clicked.connect(self.addMetadataEntry) self.ui_form.btn_exportMetadata.clicked.connect(self.exportMetadataToFile) self.ui_form.btn_importMetadata.clicked.connect(self.importMetadataFromFile) # Close button self.ui_form.btn_close.clicked.connect(self.cancelAction)
[docs] def populateData(self, *args, **kwargs): """ This method populates UI with data. :param args: :param kwargs: :return: """ current_project = mari.projects.current() # Refresh the table view self.refreshMetadata(current_project)
[docs] def refreshMetadata(self, project=None): """ This method populates/refreshes the metadata view with data. :param project: :return: """ self.logger.debug('Refresh triggered...') # Temporarily disconnect the metadata view dataChanged() signal so # that updates are not needlessly called on the project metadataView_model = self.ui_form.tableWidget_metadataView.model() # mari.utils.disconnect(metadataView_model.dataChanged, self.setMetadata) if not project: project = mari.projects.current() if not project: self.logger.warning('# No project could be found to grab metadata from!') return # Reset default metadata store if none exists # todo: this may cause opening new project to keep metadata from old session if not self.default_metadata: self.default_metadata = self.getMetadata(project) # Set the current project label self.ui_form.lbl_projectName.setText(project.name()) metadata = {} # Grab metadata values and format dictionary with names/data for key in project.metadataNames(): metadata[key] = project.metadata(key) for i, key in enumerate(sorted(metadata.keys())): self.ui_form.tableWidget_metadataView.setRowCount(len(metadata.keys())) key_item = QTableWidgetItem(key) value_item = QTableWidgetItem(metadata[key]) self.ui_form.tableWidget_metadataView.setItem(i, 0, key_item) self.ui_form.tableWidget_metadataView.setItem(i, 1, value_item) # Resize the columns of the tableview self.ui_form.tableWidget_metadataView.resizeColumnsToContents() # Auto resize window and tablewidget to fit metadata header_width = self.ui_form.tableWidget_metadataView.\ horizontalHeader().length() self.widget_holder.setMinimumWidth(header_width + 80) # Reconnect the metadata view dataChanged() signal metadataView_model.dataChanged.connect(self.setMetadata) self.logger.debug('Successfully refreshed all metadata!')
[docs] def getMetadata(self, project=None): """ This method gets the current metadata on the project and returns it as a ``dict`` object. :param project: :return: ``dict`` containing the current metadata available in the project. :rtype: ``dict`` """ if not project: project = mari.projects.current() if not project: self.logger.error('### No project is currently available to get ' 'metadata from!!!') return # Check that there is at least one metadata key on project if project.metadataNames(): metadata = {} for name in project.metadataNames(): metadata[name] = project.metadata(name) return metadata else: self.logger.warning('# There is no metadata on this object!')
[docs] def resetToDefault(self, project=None, keep_non_standard_entries=True): """ This method resets all metadata to the original state that the project was saved in when first loaded. :param project: ``Project`` instance of Mari ``ProjectManager`` :param keep_non_standard_entries: ``bool`` that determines if added metadata entries should be removed :return: ``None`` """ self.logger.debug('Resetting metadata values...') if not project: project = mari.projects.current() if not project: self.logger.error('### No project is currently available to ' 'remove metadata from!!!') return # Warn user prompt prompt = mari.utils.messageResult( 'This operation is not undo-able! Are you sure you want to proceed?', 'Confirm resetting metadata?', QMessageBox.StandardButton.Ok|QMessageBox.Cancel, QMessageBox.Icon.Warning ) if prompt == QMessageBox.StandardButton.Ok: if self.default_metadata: for key, value in self.default_metadata.items(): # Set default values and keys for metadata self.logger.debug('Setting: {0} to {1}...'.format(key, value)) project.setMetadata(key, value) if not keep_non_standard_entries: self.removeNonStandardMetadata(project) else: self.logger.warning('# There was no default metadata that could be found!') # Refresh UI self.refreshMetadata() self.logger.info('Successfully reset metadata to default values!')
[docs] def removeNonStandardMetadata(self, project=None): """ This method removes all non-standard metadata entries from the given project. :param project: :return: """ if not project: project = mari.projects.current() if not project: self.logger.error('### No project is currently available ' 'to remove metadata from!!!') return if not self.default_metadata: self.logger.error('### There is no default metadata found on project!!!') return # Warn user prompt prompt = mari.utils.messageResult( 'This operation is not undo-able! Are you sure you want to proceed?', 'Confirm removing non-standard metadata?', QMessageBox.StandardButton.Ok|QMessageBox.Cancel, QMessageBox.Icon.Warning ) if prompt == QMessageBox.StandardButton.Ok: # Remove all extra keys for item in project.metadataNames(): if item not in self.default_keys: self.logger.debug('Removing metadata: {0}'.format(item)) project.removeMetadata(item) # Refresh the UI self.refreshMetadata()
[docs] def setMetadata(self): """ This method is called whenever the user changes the data in the metadata view and acts to apply the changes to the actual Mari project metadata. :return: """ if not self.checkForOpenProject(): return # Read in values from the metadata view and apply it to the project tableView_model = self.ui_form.tableWidget_metadataView.model() if not tableView_model or tableView_model.rowCount() == 0: return current_project = mari.projects.current() for i in range(tableView_model.rowCount()): key = tableView_model.data(tableView_model.index(i, 0)) value = tableView_model.data(tableView_model.index(i, 1)) # Convert value to boolean if specific string detected if value == 'true' or value == 'True': value = True elif value == 'false' or value == 'False': value = False # Check for invalid data types if not key or value == None: continue try: current_project.setMetadata(key, value) except ValueError: self.logger.error('### Unable to set: {0} to {1}!!!\n{2}' .format(key, value, traceback.print_exc())) raise ValueError
[docs] def addMetadataEntry(self): """ This method adds a new metadata entry to the metadata view. :return: ``None`` """ self.logger.debug('Adding new metadata entry...') row_count = self.ui_form.tableWidget_metadataView.rowCount() self.ui_form.tableWidget_metadataView.insertRow(row_count)
[docs] def exportMetadataToFile(self): """ This method exports the current project metadata to a serialized file that can be loaded and applied to other projects. :return: """ self.logger.debug('Exporting metadata to file...') if not self.checkForOpenProject(): return # Form default directory for exporting files to default_export_path = os.path.join( os.path.expanduser('~'), 'Mari', 'Exported Metadata' ) if not os.path.isdir(default_export_path): res = QMessageBox.question( self, 'Create default directory?', 'Create a default location to save metadata files to?', QMessageBox.Ok|QMessageBox.Cancel ) if res == QMessageBox.Ok: os.makedirs(default_export_path) # Open file dialog for user to choose where to export file to export_path = QFileDialog.getSaveFileName( self, 'Choose location to save file...', default_export_path, 'JSON Metadata files(*.json)' )[0] if not export_path: return # Read metadata from the project current_project = mari.projects.current() if not current_project: return metadata_items = current_project.metadataNames() if metadata_items: metadata = {} for name in metadata_items: metadata[name] = current_project.metadata(name) # Export the file to the specified location JSONUtils.write(export_path, metadata) self.logger.info('Successfully exported to: {0}'.format(export_path))
[docs] def importMetadataFromFile(self): """ This method imports an existing metadata file and applies the data to the current project. :return: """ self.logger.debug('Importing metadata from file...') # Temporarily disconnect the metadata view dataChanged() signal so # that updates are not needlessly called on the project metadataView_model = self.ui_form.tableWidget_metadataView.model() mari.utils.disconnect(metadataView_model.dataChanged, self.setMetadata) if not self.checkForOpenProject(): return current_project = mari.projects.current() # Form default directory for importing files from default_import_path = os.path.join( os.path.expanduser('~'), 'Mari', 'Exported Metadata' ) # Open file dialog for user to choose where to import file to import_path = QFileDialog.getOpenFileName( self, 'Choose metadata file to iport...', default_import_path, '*.json' )[0] if not import_path: return if not os.path.isfile(import_path): self.logger.error('### {0} does not exist!!!'.format(import_path)) QMessageBox.warning( self, 'File does not exist!', '{0} does not exist!\nPlease select a different file and try again!' .format(import_path) ) return # Read in metadata from the file metadata = JSONUtils.read(import_path) # Apply metadata to the project for key, value in metadata.items(): self.logger.debug('Setting: {0} to {1}'.format(key, value)) current_project.setMetadata(key, value) self.refreshMetadata() # Reconnect the metadata view dataChanged() signal metadataView_model.dataChanged.connect(self.setMetadata) self.logger.info('Successfully imported: {0}!'.format(import_path))
[docs] def checkForOpenProject(self): """ This method checks if a project is currently open in Mari. :return: ``bool`` indicating if project is open in Mari. :rtype: ``bool`` """ # Get latest project currently open current_project = mari.projects.current() if not current_project: self.logger.error('### There is no project currently open!!!') QMessageBox.warning( self, 'No project open!', 'There is no Mari project currently open!' ) return False else: return True