def update_is_available(self, show_dialogs=True): """ Returns whether or not a new Artella DCC plugin version is available to download :return: """ from artella.plugins.updater import utils current_version = dccplugin.DccPlugin().get_version() if not current_version: return True latest_release_info = utils.get_latest_stable_artella_dcc_plugin_info( show_dialogs=show_dialogs) if not latest_release_info: return True latest_version = latest_release_info.get('version', None) if not latest_version: return True current_version_split = current_version.split('.') latest_version_split = latest_version.split('.') if latest_version_split[0] > current_version_split[0]: return True else: if latest_version_split[1] > current_version_split[1]: return True else: if latest_version_split[2] > latest_version_split[2]: return True return False
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)
def is_artella_path(file_path=None): """ Returns whether or not given file path is an Artella file path or not A path is considered to be an Artella path if the path is located inside the Artella project folder in the user machine :param str file_path: path to check. If not given, current DCC scene file path will be used :return: True if the given file path is an Artella path; False otherwise. :rtype: bool """ return dccplugin.DccPlugin().is_artella_path(file_path)
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 async_execute_in_main_thread(fn, *args, **kwargs): """ Executes the given function in the main thread when called from a non-main thread. This call will return immediately and will not wait for the code to be executed in the main thread. :param fn: function to call :param args: :param kwargs: :return: """ return dccplugin.DccPlugin().async_execute_in_main_thread(fn=fn, *args, **kwargs)
def download_file(file_path, show_dialogs=True): """ Downloads a file from Artella server :param str file_path: File we want to download from Artella server :param bool show_dialogs: Whether UI dialogs should appear or not. :return: True if the download operation was successful; False otherwise. :example: >>> self.download_file("C:/Users/artella/artella-files/ProjectA/Assets/test/model/hello.a") True """ return dccplugin.DccPlugin().download_file(file_path=file_path, show_dialogs=show_dialogs)
def show_warning_message(text, title='', duration=None, closable=True): """ Shows a warning message :param text: str, warning text to show :param title: str, title of the warning message :param duration: float or None, if given, message only will appear the specified seconds :param closable: bool, Whether the message can be closed by the user or not (when duration is given) """ return dccplugin.DccPlugin().show_warning_message(text=text, title=title, duration=duration, closable=closable)
def is_client_available(update=False): """ Returns whether or not current client is available :param update: bool, Whether or not remote sessions should be updated :return: True if the client is available and running; False otherwise. :rtype: ArtellaDriveClient """ artella_drive_client = dccplugin.DccPlugin().get_client() if not artella_drive_client or not artella_drive_client.check( update=update): return False return True
def shutdown(dev=False): """ Shutdown Artella Plugin :return: True if Artella shutdown was successful; False otherwise. :rtype: bool """ try: plugins.shutdown(dev=dev) dccplugin.DccPlugin().shutdown(dev=dev) except Exception as exc: pass return True
def convert_path(file_path): """ Converts given path to a path that Artella can understand :param str file_path: File path we want to convert :return: str :rtype: str :example: >>> self.translate_path("C:/Users/Bobby/artella-files/ProjectA/Assets/Characters/A/Model/a.ma") "$$ART_LOCAL_ROOT/ProjectA/Assets/Characters/A/Model/a.ma" >>> self.translate_path("/ProjectA/Assets/Characters/A/Model/a.ma") "$ART_LOCAL_ROOT/ProjectA/Assets/Characters/A/Model/a.ma" """ return dccplugin.DccPlugin().convert_path(file_path)
def is_path_translated(file_path): """ Returns whether or not given path is already translated to a valid Artella path :param file_path: str, path you want to check translation validation :return: True if the given path is an already translated Artella path; False otherwise :rtype: bool :example: >>> self.is_path_translated("C:/Users/Bobby/artella-files/ProjectA/Assets/Characters/A/Model/a.ma") False >>> self.is_path_translated("$ART_LOCAL_ROOT/ProjectA/refs/ref.png") True """ return dccplugin.DccPlugin().is_path_translated(file_path)
def get_client(check=False, update=False): """ Returns current Artella Drive Client being used by Artella DCC plugin :param check: bool, Whether check or not client is available and running :param update: bool, Whether or not remote sessions should be updated :return: Instance of current Artella Drive Client being used :rtype: ArtellaDriveClient """ artella_drive_client = dccplugin.DccPlugin().get_client() if check and not artella_drive_client.check(update=update): return None if not artella_drive_client: return None return artella_drive_client
def make_new_version(file_path=None, comment=None, do_lock=False): """ Uploads a new file/folder or a new version of current opened DCC scene file :param str file_path: Optional path of the file we want to create new version of. If not given, current opened DCC scene file path will be used. :param str comment: Optional comment to add to new version metadata. If not given, a generic message will be used. :param bool do_lock: Whether or not to force the lock of the file to make a new version. With new Artella version this is not mandatory. :return: True if the make new version operation is completed successfully; False otherwise. :rtype: bool """ return dccplugin.DccPlugin().make_new_version(file_path=file_path, comment=comment, do_lock=do_lock)
def update_paths(file_path=None, show_dialogs=True, call_post_function=True, skip_save=True): """ Updates all file paths of the given file path to make sure that they point to valid Artella file paths :param str or list(str) file_path: :param bool show_dialogs: :param bool call_post_function: :param bool skip_save: :return: """ return dccplugin.DccPlugin().update_paths( file_path=file_path, show_dialogs=show_dialogs, call_post_function=call_post_function, skip_save=skip_save)
def translate_path(file_path): """ Converts a file path to a local file path taking into account the available projects. :param str file_path: File path we want to to translate to its user local version :return: User local version of the given path :rtype: str :example: >>> self.translate_path("C:/Users/Bobby/artella-files/ProjectA/Assets/Characters/A/Model/a.ma") "C:/Users/Tomi/artella/data/ProjectA/Assets/Characters/A/Model/a.ma" >>> self.translate_path("/ProjectA/Assets/Characters/A/Model/a.ma") "C:/Users/Tomi/artella/data/ProjectA/Assets/Characters/A/Model/a.ma" """ translated_path = dccplugin.DccPlugin().translate_path(file_path) if isinstance(translated_path, dict): return '' return translated_path
def check_for_updates(self, show_dialogs=True): """ Shows UI informing the user if there is available or not a new version of the DCC plugin to download """ from artella.plugins.updater import utils from artella.plugins.updater.widgets import versioninfo latest_release_info = utils.get_latest_stable_artella_dcc_plugin_info( show_dialogs=show_dialogs) if not latest_release_info: return False current_version = dccplugin.DccPlugin().get_version() about_dialog = versioninfo.VersionInfoDialog( current_version=current_version, latest_release_info=latest_release_info) about_dialog.exec_() return True
def init(dcc_paths=None, init_client=True, plugin_paths=None, extensions=None, dev=False, load_plugins=True, create_menu=True, create_callbacks=True): """ Initializes Artella Plugin :param bool init_client: Whether or not Artella Drive Client should be initialized during initialization. Useful to avoid to connect to Artella client when developing DCC specific functionality. :param list(str) plugin_paths: List of paths where Artella plugins can be located :param list(str) extensions: List of extensions to register :param bool dev: Whether or not initialization should be done in dev mode :param bool load_plugins: Whether or not Artella Plugins should be loaded :param bool create_menu: Whether or not Artella menu should be created :param bool create_callbacks: Whether or not Artella DCC plugin callbacks should be created :return: True if Artella initialization was successful; False otherwise. :rtype: bool """ register_dcc_paths(dcc_paths) plugins_path = plugin_paths if plugin_paths is not None else list() extensions = extensions if extensions is not None else list() artella_logger = logging.getLogger('artella') # Make sure that Artella Drive client and DCC are cached during initialization current_dcc = dcc_core.current_dcc() if not current_dcc: artella_logger.error( 'Impossible to load Artella Plugin because no DCC is available!') return False # Due to the TCP server, Artella plugin freezes some DCCs (such as Maya) if its execute in batch mode (through # console). For now we skip Artella plugin initialization in that scenario. if dcc.is_batch(): return False shutdown(dev=dev) # Specific DCC extensions are managed by the client dcc_extensions = dcc.extensions() or list() extensions.extend(dcc_extensions) # Initialize resources and theme resource.register_resources_path( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources')) # Create Artella Drive Client artella_drive_client = client.ArtellaDriveClient.get( extensions=extensions) if init_client else None # Load Plugins if load_plugins: default_plugins_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'plugins') if default_plugins_path not in plugins_path: plugins_path.append(default_plugins_path) plugins.register_paths(plugin_paths) plugins.load_registered_plugins(dev=dev) # Initialize Artella DCC plugin dccplugin.DccPlugin(artella_drive_client).init( dev=dev, show_dialogs=False, create_menu=create_menu, create_callbacks=create_callbacks, init_client=init_client) if not dev: updater_plugin = plugins.get_plugin_by_id('artella-plugins-updater') if updater_plugin and updater_plugin.update_is_available( show_dialogs=False): updater_plugin.check_for_updates(show_dialogs=False) 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