def initialize_callbacks(): global ARTELLA_CALLBACKS_CACHE if ARTELLA_CALLBACKS_CACHE: return shutdown_type = None if hasattr(callback.Callbacks(), 'ShutdownCallback'): shutdown_type = getattr(callback.Callbacks(), 'ShutdownCallback') for callback_name in dcc_core.callbacks(): callback_type = getattr(dcc_core.DccCallbacks, callback_name)[1]['type'] if callback_type == 'simple': callback_type = SimpleCallbackWrapper elif callback_type == 'filter': callback_type = FilterCallbackWrapper else: callback_type = SimpleCallbackWrapper callback_class = getattr(callback.Callbacks(), '{}Callback'.format(callback_name), None) if not callback_class: logger.warning( 'Dcc {} does not provides a Callback implementation for {}Callback. Skipping ...'.format( dcc.name(), callback_name)) continue # This should not be necessary. We added just to be sure that no callbacks are registered in scenes # because that could break user DCC session. new_callback = ARTELLA_CALLBACKS_CACHE.get(callback_name, None) if new_callback: new_callback.cleanup() ARTELLA_CALLBACKS_CACHE[callback_name] = callback_type(callback_class, shutdown_type) logger.debug('Creating Callback: {} | {}'.format(callback_name, callback_class))
def handle_message(self, msg): """ Internal function that handles the response received from Artella Drive App :param dict msg: Dictionary containing the response from Artella server """ logger.debug('Handling realtime message: {}'.format(msg)) if not isinstance(msg, dict): logger.warning('Malformed realtime message: {}'.format(msg)) return command_name = msg.get('type') dcc_name = dcc.name() if command_name.startswith(dcc_name): dcc_operation = command_name.split('-')[-1] file_path = msg['data']['ARTELLA_FILE'] if dcc_operation == 'import': self._handle_import_message(file_path) elif dcc_operation == 'reference': self._handle_reference_message(file_path) elif command_name == 'authorization-ok': logger.debug('websocket connection successful.') elif command_name == 'open': file_path = msg['data']['ARTELLA_FILE'] self._handle_open_message(file_path) else: pass
def get_version_variable_name(self): """ Returns the environment variable name used to store current Arqtella DCC plugin version :return: Environment version name :rtype: str """ return 'ARTELLA_{}_PLUGIN'.format(dcc.name())
def pass_message(self, json_data): """ Executes handle message Artella plugin functionality in specific DCC thread to make sure that it does not conflicts with main DCC UI thread :param json_data: """ logger.debug('Passing message to {}: {}'.format(dcc.name(), json_data)) self.execute_in_main_thread(self.handle_message, json_data)
def closeEvent(self, event): if self._plugin_updated: dcc_name = dcc.name() import artella.loader artella.loader._reload() if dcc_name == 'maya': import maya.cmds as cmds cmds.evalDeferred(artella.loader.init) else: artella.loader.init() super(UpdaterWindow, self).closeEvent(event)
def init_ui(self): """ Function that initializes plugin UI related functionality """ main_menu = dccplugin.DccPlugin().get_main_menu() if not main_menu: return plugin_menu = self._config_dict.get('menu') plugin_package = self._config_dict.get('package', 'Artella') plugin_dccs = self._config_dict.get('dcc', list()) can_load_plugin = True if plugin_dccs: can_load_plugin = dcc.name() in plugin_dccs if can_load_plugin: if plugin_menu and 'label' in plugin_menu: menu_parents = plugin_menu.get('parents', None) if menu_parents: current_parent = 'Artella' if dcc.check_menu_exists( 'Artella') else None for menu_parent in menu_parents: if not dcc.check_menu_exists(menu_parent): # TODO: Before More Artella menu addition we force the creation of a # TODO: separator. We should find a way to avoid hardcoded this. icon = '' if menu_parent == 'More Artella': dcc.add_menu_separator('Artella') icon = 'artella.png' dcc.add_sub_menu_item(menu_parent, parent_menu=current_parent, icon=icon) current_parent = menu_parent menu_label = plugin_menu['label'] menu_command = plugin_menu['command'] menu_icon = self._config_dict.get('icon', '') if menu_parents: menu_parent = menu_parents[-1] dcc.add_menu_item(menu_label, menu_command, menu_parent, icon=menu_icon) else: dcc.add_menu_item(menu_label, menu_command, main_menu, icon=menu_icon)
def _init(self): """ Internal function that initializes info for the plugin and its environment """ from artella import dcc self._info.update({ 'name': self._plugin.__class__.__name__, 'module': self._plugin.__class__.__module__, 'filepath': inspect.getfile(self._plugin.__class__), 'id': self._id, 'application': dcc.name() })
def get_latest_stable_artella_dcc_plugin_info(dcc_name=None, platform=None, show_dialogs=False): """ Returns plugin info data from Artella server :param str dcc_name: name of the DCC plugin we want to retrieve to retrieve info from. If not give current DCC will be used :param str platform: name of the OS platform we want to retrieve DCC plugin of (windows, darwin and linux) :return: Dictionary containing plugin info data :rtype: dict """ dcc_plugin_info = dict() message_title = 'Impossible to retrieve version info' dcc_name = dcc_name or dcc.name() current_platform = platform or None if not current_platform: if utils.is_windows(): current_platform = 'windows' elif utils.is_mac(): current_platform = 'darwin' elif utils.is_linux(): current_platform = 'linux' if not current_platform: msg = 'Impossible to retrieve Dcc plugin info from Artella server because ' \ 'current OS platform is not supported: "{}"'.format(sys.platform) logger.warning(msg) if show_dialogs: qtutils.show_warning_message_box(message_title, msg) return dcc_plugin_info artella_url = 'https://updates.artellaapp.com/plugins/{}/versions/stable-{}.json'.format(dcc_name, current_platform) req = Request(artella_url) rsp = None try: rsp = urlopen(req) except Exception: try: ssl._create_default_https_context = ssl._create_unverified_context # To avoid [SSL: CERTIFICATE_VERIFY_FAILED] errors. context = ssl._create_unverified_context() rsp = urlopen(req, context=context) except URLError as exc: if hasattr(exc, 'reason'): msg = 'Failed to retrieve Artella DCC plugin info ¨{}({})" from Artella server ({}): "{}"'.format( dcc_name, current_platform, artella_url, exc.reason) elif hasattr(exc, 'code'): msg = 'Failed to retrieve Artella DCC plugin info ¨{}({})" from Artella server ({}): "{}"'.format( dcc_name, current_platform, artella_url, exc.code) else: msg = exc logger.debug(exc) logger.error(msg) if show_dialogs: qtutils.show_error_message_box(message_title, msg) warning_message = 'Was not possible to retrieve DCC Artella plugin info ¨{}({})" from Artella server'.format( dcc_name, current_platform) if not rsp: if show_dialogs: qtutils.show_warning_message_box(message_title, warning_message) return dcc_plugin_info artella_rsp = rsp.read() if not artella_rsp: if show_dialogs: qtutils.show_warning_message_box(message_title, warning_message) return dcc_plugin_info try: dcc_plugin_data = json.loads(artella_rsp) except Exception as exc: msg = 'Error while reading data from Artella DCC plugin info ¨{}({})": {}'.format( dcc_name, current_platform, exc) logger.error(msg) if show_dialogs: qtutils.show_error_message_box(message_title, msg) return dcc_plugin_info if not dcc_plugin_data: if show_dialogs: qtutils.show_warning_message_box(message_title, warning_message) return dcc_plugin_info dcc_plugin_info['platform'] = dcc_plugin_data.get('platform', '') dcc_plugin_info['version'] = dcc_plugin_data.get('version', '0.0.0') dcc_plugin_info['file_name'] = dcc_plugin_data.get('file_name', '') dcc_plugin_info['url'] = dcc_plugin_data.get('url', '') return dcc_plugin_info
def local_path_to_uri(self, file_path): """ Translates a local file path to its URI format :param str file_path: Absolute local file path we want to translate to URI :param str prefix: :return: path in its URI format if current DCC supports this feature; path without any change otherwise :rtype: str """ if not dcc.supports_uri_scheme(): logger.warning('Current DCC {} does not supports Artella URI scheme!'.format(dcc.name())) return file_path return file_path
def load_registered_plugins(dev=False): """ Loads all the plugins found in the registered plugin paths :return: """ plugin_paths = list( set([ utils.clean_path(plugin_path) for plugin_path in _PLUGIN_PATHS if os.path.isdir(plugin_path) ])) if not plugin_paths: logger.info('No Artella Plugins found to load!') return found_paths = dict() for plugin_path in plugin_paths: for root, dirs, files in os.walk(plugin_path): if consts.ARTELLA_PLUGIN_CONFIG not in files: continue clean_path = utils.clean_path(root) found_paths[clean_path] = os.path.join( root, consts.ARTELLA_PLUGIN_CONFIG) if not found_paths: logger.info('No plugins found in registered plugin paths: {}'.format( _PLUGIN_PATHS)) return for plugin_path in plugin_paths: for plugin_dir in os.listdir(plugin_path): clean_path = utils.clean_path(os.path.join(plugin_path, plugin_dir)) if os.path.isdir(clean_path) and clean_path not in sys.path: sys.path.append(clean_path) for plugin_path, plugin_config in found_paths.items(): # Search sub modules paths sub_modules_found = list() for sub_module in utils.iterate_modules(plugin_path): file_name = os.path.splitext(os.path.basename(sub_module))[0] if file_name.startswith('_') or file_name.startswith( 'test_') or sub_module.endswith('.pyc'): continue if not sub_module or sub_module in sub_modules_found: continue sub_modules_found.append(sub_module) if not sub_modules_found: continue # Find specific DCC plugin implementation dcc_name = dcc.name() sub_module = sub_modules_found[0] max_length = 50 index = 0 artella_module_parts = list() temp_sub_module = sub_module while True: if index > max_length: artella_module_parts = list() break base_name = os.path.basename(temp_sub_module) if not base_name or base_name == 'artella': artella_module_parts.append(base_name) break artella_module_parts.append(base_name) temp_sub_module = os.path.dirname(temp_sub_module) index += 1 if not artella_module_parts: module_path = utils.convert_module_path_to_dotted_path( os.path.normpath(sub_module)) else: module_path = os.path.splitext('.'.join( reversed(artella_module_parts)))[0] module_path_split = module_path.split('.') dcc_module_path = '{}.{}.{}'.format('.'.join(module_path_split[:-1]), dcc_name, module_path_split[-1]) sub_module_obj = utils.import_module(dcc_module_path, skip_exceptions=True) if not sub_module_obj: sub_module_obj = utils.import_module(module_path) if not sub_module_obj: logger.error( 'Error while importing Artella Plugin module: {}'.format( module_path)) continue if dev: plugin_version = 'DEV' else: plugin_version = None module_path_dir = module_path.rsplit('.', 1)[0] version_module_path = '{}.__version__'.format(module_path_dir) version_module_path = version_module_path.replace( '.{}.__'.format(dcc_name), '.__') try: version_module_obj = utils.import_module(version_module_path) except Exception: version_module_obj = None if version_module_obj: try: plugin_version = version_module_obj.get_version() except Exception as exc: logger.warning( 'Impossible to retrieve version for Artella Plugin module: {} | {}' .format(module_path, exc)) for member in utils.iterate_module_members(sub_module_obj, predicate=inspect.isclass): register_plugin(member[1], plugin_config, os.path.dirname(sub_module), plugin_version) if not _PLUGINS: logger.warning('No Artella plugins found to load!') return ordered_plugins_list = list() for plugin_id, plugin_dict in _PLUGINS.items(): plugin_class = plugin_dict['class'] plugin_index = plugin_class.INDEX or -1 index = 0 for i, plugin_item in enumerate(ordered_plugins_list): plugin_item_index = list(plugin_item.values())[0]['index'] if plugin_index < plugin_item_index: index += 1 ordered_plugins_list.insert( index, {plugin_id: { 'index': plugin_index, 'dict': plugin_dict }}) for plugin_item in ordered_plugins_list: plugin_id = list(plugin_item.keys())[0] plugin_dict = list(plugin_item.values())[0]['dict'] plugin_class = plugin_dict['class'] plugin_config_dict = plugin_dict.get('config', dict()) try: plugin_inst = plugin_class(plugin_config_dict) except Exception: logger.error( 'Impossible to instantiate Artella Plugin: "{}"'.format( plugin_id)) logger.error(traceback.format_exc()) continue _PLUGINS[plugin_id]['plugin_instance'] = plugin_inst
def register_plugin(class_obj, config_path, plugin_path, plugin_version=None, plugin_interface=None): """ Registers an Artella plugin instance into the manager :param class_obj: :param config_path: :param plugin_path: :param plugin_version: :param plugin_interface: :return: :rtype: bool """ if not config_path or not os.path.isfile(config_path): logger.warning( 'Impossible to register Artella Plugin: {} because its config file does not exists: {}!' .format(class_obj, config_path)) return False plugin_interface = plugin_interface or plugin.ArtellaPlugin if not issubclass(class_obj, plugin_interface): return True plugin_id = None if hasattr(class_obj, 'ID'): plugin_id = getattr(class_obj, 'ID') if not plugin_id: plugin_id = class_obj.__name__ if plugin_id in _PLUGINS: logger.warning( 'Artella Plugin: "{}" already registered!'.format(plugin_id)) return True try: plugin_config = utils.read_json(config_path) except Exception: logger.warning( 'Artella Plugin "{}" configuration file "{}" has not a proper structure. Skipping plugin ...' .format(plugin_id, config_path)) return False plugin_name = plugin_config.get('name', None) plugin_package = plugin_config.get('package', None) plugin_icon = plugin_config.get('icon', None) plugin_resources = plugin_config.get('resources', None) # Register DCC resources, both DCC specific plugin implementation # resources and generic implementation resources plugin_resources_paths = list() if plugin_resources and plugin_path: plugin_path_base = os.path.basename(plugin_path) if plugin_path_base == dcc.name(): plugin_resources_paths.append( os.path.join(plugin_path, plugin_resources)) plugin_resources_paths.append( os.path.join(os.path.dirname(plugin_path), plugin_resources)) else: plugin_resources_paths.append( os.path.join(plugin_path, plugin_resources)) for plugin_resources_path in plugin_resources_paths: if os.path.isdir(plugin_resources_path): dcc.register_dcc_resource_path(plugin_resources_path) icons_path = os.path.join(plugin_resources_path, 'icons') if os.path.isdir(icons_path): dcc.register_dcc_resource_path(icons_path) _PLUGINS[plugin_id] = { 'name': plugin_name, 'package': plugin_package, 'icon': plugin_icon, 'class': class_obj, 'config': plugin_config, 'version': plugin_version, 'resource_paths': plugin_resources_paths } logger.info( 'Artella Plugin: "{}" registered successfully!'.format(plugin_id)) return True
def uninstall(self, show_dialogs=True): artella_path = artella.__path__[0] if not os.path.isdir(artella_path): msg = 'Artella folder "{}" does not exists!'.format(artella_path) if show_dialogs: api.show_warning_message(text=msg) else: logger.warning(msg) return False res = qtutils.show_question_message_box( 'Artella Uninstaller', 'All plugins will be removed.\n\nArtella plugin will not be accessible by any DCC after uninstall.\n\n' 'Are you sure you want to uninstall Artella Plugin?') if not res: return False do_remove_install_folder = not dccplugin.DccPlugin().dev valid_uninstall = self._uninstall(artella_path) if not valid_uninstall: msg = 'Artella uninstall process was not completed!'.format(artella_path) if show_dialogs: api.show_error_message(text=msg) else: logger.error(msg) return False loader.shutdown(dev=False) if do_remove_install_folder: try: logger.info('Removing Artella Dcc Plugin directory: {}'.format(artella_path)) utils.delete_folder(artella_path) except Exception as exc: logger.warning( 'Impossible to remove Artella Dcc plugin directory: {} | {}'.format(artella_path, exc)) return False if os.path.isdir(artella_path): msg = 'Artella folder was not removed during uninstall process.\n\n{}\n\n Remove it manually if you ' \ 'want to have a complete clean uninstall of Artella plugin.'.format(artella_path) if show_dialogs: dcc.show_info('Artella Uninstaller', msg) else: logger.info(msg) utils.open_folder(os.path.dirname(artella_path)) # Remove specific DCC install folder if exists root_dcc_install_dir = os.path.dirname(os.path.dirname(os.path.dirname(artella_path))) dcc_install_dir = os.path.join(root_dcc_install_dir, dcc.name()) if os.path.isdir(dcc_install_dir): utils.delete_folder(dcc_install_dir) # Cleanup artella directories from artella_dir = os.path.dirname(artella_path) sys_paths = [artella_path, artella_dir, utils.clean_path(artella_path), utils.clean_path(artella_dir)] paths_to_remove = list() for sys_path in sys.path: if sys_path in sys_paths: paths_to_remove.append(sys_path) sys.path.remove(sys_path) elif 'artella-plugins' in sys_path or 'artella-dccs' in sys_path: paths_to_remove.append(sys_path) for path_to_remove in paths_to_remove: if path_to_remove not in sys.path: continue sys.path.remove(path_to_remove) return True
def _fill_data(self): install_path = r'D:\dev\artella\test_download' dcc_plugins = list() dcc_install_package = 'artella-installer-{}'.format(dcc.name()) base_file_name = 'artella_installer_maya' file_name = '{}.tar.gz'.format(base_file_name) file_path = os.path.join(install_path, file_name) dcc_pypi_info = utils.get_pypi_info(dcc_install_package) if dcc_pypi_info: dcc_url = dcc_pypi_info.get('url', '') if dcc_url: valid = utils.download_and_extract_package_from_pypi(dcc_url, file_path, install_path) if valid: config_file = None print('Install Path: {}'.format(install_path)) for root, dirs, files in os.walk(install_path): if config_file: break for file_path in files: if file_path == 'artella-installer.json': config_file = os.path.join(root, file_path) break if config_file and os.path.isfile(config_file): config_data = core_utils.read_json(config_file) config_plugins = config_data.get('plugins', list()) for config_plugin in config_plugins: plugin_id = config_plugin.get('id', '') if not plugin_id: plugin_repo = config_plugin.get('repo', '') if plugin_repo: plugin_id = plugin_repo.split('/')[-1] if plugin_id: dcc_plugins.append(plugin_id) dcc_plugins = list(set(dcc_plugins)) all_plugins = plugins.plugins() for plugin_id, plugin_data in all_plugins.items(): plugin_name = plugin_data['name'] plugin_icon_name = plugin_data.get('icon', None) plugin_package = plugin_data.get('package', None) plugin_version = plugin_data.get('version', None) plugin_resource_paths = plugin_data.get('resource_paths', list()) if plugin_id in dcc_plugins: dcc_plugins.remove(plugin_id) plugin_icon_pixmap = None if plugin_icon_name and plugin_resource_paths: for plugin_resource_path in plugin_resource_paths: plugin_icon_path = os.path.join(plugin_resource_path, plugin_icon_name) if os.path.isfile(plugin_icon_path): plugin_icon_pixmap = QtGui.QPixmap(plugin_icon_path) if plugin_package not in self._plugins: package_layout = self._add_package_tab(plugin_package) self._plugins[plugin_package] = {'layout': package_layout, 'plugins': []} else: package_layout = self._plugins[plugin_package]['layout'] pypi_info = utils.get_pypi_info(plugin_id) if not pypi_info: continue plugin_author = pypi_info.get('author', '') plugin_author_email = pypi_info.get('author_email', '') plugin_summary = pypi_info.get('summary', '') plugin_latest_version = pypi_info.get('version', '') # this is the latest version of the plugin in PyPI plugin_upload_date = pypi_info.get('upload_date', '') plugin_size = pypi_info.get('size', '') plugin_url = pypi_info.get('url', '') new_plugin_widget = plugin.PluginVersionWidget( plugin_id, plugin_name, plugin_package, plugin_version, plugin_author, plugin_author_email, plugin_summary, plugin_latest_version, plugin_upload_date, plugin_size, plugin_url, plugin_icon_pixmap) new_plugin_widget.updated.connect(self._on_updated_plugin) self._plugins[plugin_package]['plugins'].append(new_plugin_widget) package_layout.addWidget(new_plugin_widget) print(dcc_plugins)
def _fill_data(self): current_plugin_version = dccplugin.DccPlugin().get_version() or 'Undefined' self._artella_dcc_plugin_version_label.setText(current_plugin_version) added_packages = dict() # Retrieve Artella plugins versions all_plugins = plugins.plugins() for plugin_id, plugin_data in all_plugins.items(): plugin_package = plugin_data.get('package', 'Not Defined') package_item = added_packages.get(plugin_package, None) if not package_item: package_item = QtWidgets.QTreeWidgetItem([plugin_package]) self._plugins_tree.addTopLevelItem(package_item) added_packages[plugin_package] = package_item plugin_name = plugin_data['name'] plugin_version = plugin_data.get('version', 'Undefined') plugin_item = QtWidgets.QTreeWidgetItem([plugin_name, plugin_version, plugin_id]) package_item.addChild(plugin_item) package_item = added_packages.get('Artella', None) if not package_item: package_item = QtWidgets.QTreeWidgetItem(['Artella']) self._plugins_tree.addTopLevelItem(package_item) # Retrieve DCC plugin version dcc_version = None dcc_module_name = core_dcc.CURRENT_DCC_MODULE if dcc_module_name: try: dcc_module_version = '{}.__version__'.format(dcc_module_name) dcc_version_mod = utils.import_module(dcc_module_version) dcc_version = dcc_version_mod.get_version() except Exception as exc: logger.warning('Impossible to retrieve {} Artella plugin version: {}'.format(dcc.name(), exc)) dcc_version = dcc_version or 'Undefined' dcc_version_item = QtWidgets.QTreeWidgetItem([dcc.nice_name(), dcc_version, dcc_module_name.replace('.', '- ')]) package_item.insertChild(0, dcc_version_item) # Retrieve Artella core version core_version = None core_version_path = 'artella.__version__' try: core_version_mod = utils.import_module(core_version_path) core_version = core_version_mod.get_version() except Exception as exc: logger.warning('Impossible to retrieve Artella Core version: {}'.format(exc)) core_version = core_version or 'Undefined' core_version_item = QtWidgets.QTreeWidgetItem(['Core', core_version, 'artella-plugins-core']) package_item.insertChild(0, core_version_item) self._plugins_tree.expandAll() self._plugins_tree.resizeColumnToContents(0) self._plugins_tree.resizeColumnToContents(1) self._plugins_tree.resizeColumnToContents(2)