diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000000..9de8d23bb2 --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +# ignore = D203 +exclude = + .git, + __pycache__, + docs, + */vendor + +max-complexity = 30 diff --git a/pype/ftrack/ftrackRun.py b/pype/ftrack/ftrackRun.py index 245f7f2b46..e90530b3b2 100644 --- a/pype/ftrack/ftrackRun.py +++ b/pype/ftrack/ftrackRun.py @@ -1,56 +1,201 @@ import sys import os import argparse - -from app.lib.utils import forward +import subprocess +import threading +import time +from app import style +from app.vendor.Qt import QtCore, QtGui, QtWidgets from pype.ftrack import credentials, login_dialog as login_dialog +from app.api import Logger +from FtrackServer import FtrackServer +log = Logger.getLogger(__name__) # Validation if alredy logged into Ftrack -def validate(): - validation = False - cred = credentials._get_credentials() - if 'username' in cred and 'apiKey' in cred: - validation = credentials._check_credentials( - cred['username'], - cred['apiKey'] - ) - if validation is False: - login_dialog.run_login() - else: - login_dialog.run_login() +class FtrackRunner: + def __init__(self, main_parent=None, parent=None): - validation = credentials._check_credentials() - if not validation: - print("We are unable to connect to Ftrack") - sys.exit() + self.parent = parent + self.loginWidget = login_dialog.Login_Dialog_ui(self) + self.actionThread = None + self.actionServer = FtrackServer('action') + self.eventThread = None + self.eventServer = FtrackServer('event') -# Entered arguments -parser = argparse.ArgumentParser() -parser.add_argument("--actionserver", action="store_true", - help="launch action server for ftrack") -parser.add_argument("--eventserver", action="store_true", - help="launch action server for ftrack") -parser.add_argument("--logout", action="store_true", - help="launch action server for ftrack") + self.boolLogged = False + self.boolActionServer = False + self.boolEventServer = False -kwargs, args = parser.parse_known_args() + def showLoginWidget(self): + self.loginWidget.show() -if kwargs.logout: - credentials._clear_credentials() - sys.exit() -else: - validate() + def validate(self): + validation = False + cred = credentials._get_credentials() + try: + if 'username' in cred and 'apiKey' in cred: + validation = credentials._check_credentials( + cred['username'], + cred['apiKey'] + ) + if validation is False: + self.showLoginWidget() + else: + self.showLoginWidget() -if kwargs.eventserver: - fname = os.path.join(os.environ["FTRACK_ACTION_SERVER"], "eventServer.py") - returncode = forward([ - sys.executable, "-u", fname - ]) + except Exception as e: + log.error("We are unable to connect to Ftrack: {0}".format(e)) -else: - fname = os.path.join(os.environ["FTRACK_ACTION_SERVER"], "actionServer.py") - returncode = forward([ - sys.executable, "-u", fname - ]) + validation = credentials._check_credentials() + if validation is True: + log.info("Connected to Ftrack successfully") + self.loginChange() + else: + log.warning("Please sign in to Ftrack") + self.boolLogged = False + self.setMenuVisibility() -sys.exit(returncode) + return validation + + # Necessary - login_dialog works with this method after logging in + def loginChange(self): + self.boolLogged = True + self.setMenuVisibility() + self.runActionServer() + + def logout(self): + credentials._clear_credentials() + self.stopActionServer() + self.stopEventServer() + + log.info("Logged out of Ftrack") + self.boolLogged = False + self.setMenuVisibility() + + # Actions part + def runActionServer(self): + if self.actionThread is None: + self.actionThread = threading.Thread(target=self.setActionServer) + self.actionThread.daemon=True + self.actionThread.start() + + log.info("Ftrack action server launched") + self.boolActionServer = True + self.setMenuVisibility() + + def setActionServer(self): + self.actionServer.run_server() + + def resetActionServer(self): + self.stopActionServer() + self.runActionServer() + + def stopActionServer(self): + try: + self.actionServer.stop_session() + if self.actionThread is not None: + self.actionThread.join() + self.actionThread = None + + log.info("Ftrack action server stopped") + self.boolActionServer = False + self.setMenuVisibility() + except Exception as e: + log.error("During Killing action server: {0}".format(e)) + + # Events part + def runEventServer(self): + if self.eventThread is None: + self.eventThread = threading.Thread(target=self.setEventServer) + self.eventThread.daemon=True + self.eventThread.start() + + log.info("Ftrack event server launched") + self.boolEventServer = True + self.setMenuVisibility() + + def setEventServer(self): + self.eventServer.run_server() + + def resetEventServer(self): + self.stopEventServer() + self.runEventServer() + + def stopEventServer(self): + try: + self.eventServer.stop_session() + if self.eventThread is not None: + self.eventThread.join() + self.eventThread = None + + log.info("Ftrack event server stopped") + self.boolEventServer = False + self.setMenuVisibility() + except Exception as e: + log.error("During Killing Event server: {0}".format(e)) + + # Definition of Tray menu + def trayMenu(self, parent): + # Menu for Tray App + self.menu = QtWidgets.QMenu('Ftrack', parent) + self.menu.setProperty('submenu', 'on') + self.menu.setStyleSheet(style.load_stylesheet()) + + # Actions - server + self.smActionS = self.menu.addMenu("Action server") + self.aRunActionS = QtWidgets.QAction("Run action server", self.smActionS) + self.aRunActionS.triggered.connect(self.runActionServer) + self.aResetActionS = QtWidgets.QAction("Reset action server", self.smActionS) + self.aResetActionS.triggered.connect(self.resetActionServer) + self.aStopActionS = QtWidgets.QAction("Stop action server", self.smActionS) + self.aStopActionS.triggered.connect(self.stopActionServer) + + self.smActionS.addAction(self.aRunActionS) + self.smActionS.addAction(self.aResetActionS) + self.smActionS.addAction(self.aStopActionS) + + # Actions - server + self.smEventS = self.menu.addMenu("Event server") + self.aRunEventS = QtWidgets.QAction("Run event server", self.smEventS) + self.aRunEventS.triggered.connect(self.runEventServer) + self.aResetEventS = QtWidgets.QAction("Reset event server", self.smEventS) + self.aResetEventS.triggered.connect(self.resetEventServer) + self.aStopEventS = QtWidgets.QAction("Stop event server", self.smEventS) + self.aStopEventS.triggered.connect(self.stopEventServer) + + self.smEventS.addAction(self.aRunEventS) + self.smEventS.addAction(self.aResetEventS) + self.smEventS.addAction(self.aStopEventS) + + # Actions - basic + self.aLogin = QtWidgets.QAction("Login",self.menu) + self.aLogin.triggered.connect(self.validate) + self.aLogout = QtWidgets.QAction("Logout",self.menu) + self.aLogout.triggered.connect(self.logout) + + self.menu.addAction(self.aLogin) + self.menu.addAction(self.aLogout) + + self.boolLogged = False + self.setMenuVisibility() + + return self.menu + + # Definition of visibility of each menu actions + def setMenuVisibility(self): + + self.smActionS.menuAction().setVisible(self.boolLogged) + self.smEventS.menuAction().setVisible(self.boolLogged) + self.aLogin.setVisible(not self.boolLogged) + self.aLogout.setVisible(self.boolLogged) + + if self.boolLogged is False: + return + + self.aRunActionS.setVisible(not self.boolActionServer) + self.aResetActionS.setVisible(self.boolActionServer) + self.aStopActionS.setVisible(self.boolActionServer) + + self.aRunEventS.setVisible(not self.boolEventServer) + self.aResetEventS.setVisible(self.boolEventServer) + self.aStopEventS.setVisible(self.boolEventServer) diff --git a/pype/ftrack/login_dialog.py b/pype/ftrack/login_dialog.py index 8cfceb6d91..020f917b9a 100644 --- a/pype/ftrack/login_dialog.py +++ b/pype/ftrack/login_dialog.py @@ -17,9 +17,15 @@ class Login_Dialog_ui(QtWidgets.QWidget): buttons = [] labels = [] - def __init__(self): + def __init__(self, parent=None): - super().__init__() + super(Login_Dialog_ui, self).__init__() + + self.parent = parent + + self.setWindowIcon(self.parent.parent.icon) + self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint) + self.loginSignal.connect(self.loginWithCredentials) self._translate = QtCore.QCoreApplication.translate @@ -32,12 +38,11 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.resize(self.SIZE_W, self.SIZE_H) self.setMinimumSize(QtCore.QSize(self.SIZE_W, self.SIZE_H)) + self.setMaximumSize(QtCore.QSize(self.SIZE_W+100, self.SIZE_H+100)) self.setStyleSheet(style.load_stylesheet()) self.setLayout(self._main()) - self.setWindowTitle('FTrack Login') - - self.show() + self.setWindowTitle('Pype - Ftrack Login') def _main(self): self.main = QtWidgets.QVBoxLayout() @@ -163,15 +168,15 @@ class Login_Dialog_ui(QtWidgets.QWidget): entity.setStyleSheet("border: 1px solid red;") def enter_credentials(self): - user = self.user_input.text().strip() - api = self.api_input.text().strip() + username = self.user_input.text().strip() + apiKey = self.api_input.text().strip() msg = "You didn't enter " missing = [] - if user == "": + if username == "": missing.append("Username") self._invalid_input(self.user_input) - if api == "": + if apiKey == "": missing.append("API Key") self._invalid_input(self.api_input) @@ -179,7 +184,7 @@ class Login_Dialog_ui(QtWidgets.QWidget): self.setError("{0} {1}".format(msg, " and ".join(missing))) return - verification = credentials._check_credentials(user, api) + verification = credentials._check_credentials(username, apiKey) if verification: credentials._save_credentials(username, apiKey) @@ -280,23 +285,12 @@ class Login_Dialog_ui(QtWidgets.QWidget): if verification is True: credentials._save_credentials(username, apiKey) credentials._set_env(username, apiKey) + self.parent.loginChange() self._close_widget() + def closeEvent(self, event): + event.ignore() + self._close_widget() def _close_widget(self): - self.close() - - -class Login_Dialog(Login_Dialog_ui): - def __init__(self): - super(Login_Dialog, self).__init__() - - -def getApp(): - return QtWidgets.QApplication(sys.argv) - -def run_login(): - app = getApp() - ui = Login_Dialog() - ui.show() - app.exec_() + self.hide() diff --git a/pype/launcher_actions.py b/pype/launcher_actions.py index 7d72cb2b38..cf68dfb5c1 100644 --- a/pype/launcher_actions.py +++ b/pype/launcher_actions.py @@ -1,86 +1,30 @@ import os -from avalon import api, lib, pipeline +import sys +from avalon import api, pipeline -class FusionRenderNode(api.Action): - - name = "fusionrendernode9" - label = "F9 Render Node" - icon = "object-group" - order = 997 - - def is_compatible(self, session): - """Return whether the action is compatible with the session""" - if "AVALON_PROJECT" in session: - return False - return True - - def process(self, session, **kwargs): - """Implement the behavior for when the action is triggered - - Args: - session (dict): environment dictionary - - Returns: - Popen instance of newly spawned process - - """ - - # Update environment with session - env = os.environ.copy() - env.update(session) - - # Get executable by name - app = lib.get_application(self.name) - env.update(app["environment"]) - executable = lib.which(app["executable"]) - - return lib.launch(executable=executable, args=[], environment=env) - - -class VrayRenderSlave(api.Action): - - name = "vrayrenderslave" - label = "V-Ray Slave" - icon = "object-group" - order = 996 - - def is_compatible(self, session): - """Return whether the action is compatible with the session""" - if "AVALON_PROJECT" in session: - return False - return True - - def process(self, session, **kwargs): - """Implement the behavior for when the action is triggered - - Args: - session (dict): environment dictionary - - Returns: - Popen instance of newly spawned process - - """ - - # Update environment with session - env = os.environ.copy() - env.update(session) - - # Get executable by name - app = lib.get_application(self.name) - env.update(app["environment"]) - executable = lib.which(app["executable"]) - - # Run as server - arguments = ["-server", "-portNumber=20207"] - - return lib.launch(executable=executable, - args=arguments, - environment=env) +PACKAGE_DIR = os.path.dirname(__file__) +PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins", "launcher") +ACTIONS_DIR = os.path.join(PLUGINS_DIR, "actions") def register_launcher_actions(): """Register specific actions which should be accessible in the launcher""" - pipeline.register_plugin(api.Action, FusionRenderNode) - pipeline.register_plugin(api.Action, VrayRenderSlave) + actions = [] + ext = ".py" + sys.path.append(ACTIONS_DIR) + + for f in os.listdir(ACTIONS_DIR): + file, extention = os.path.splitext(f) + if ext in extention: + module = __import__(file) + klass = getattr(module, file) + actions.append(klass) + + if actions is []: + return + + for action in actions: + print("Using launcher action from config @ '{}'".format(action.name)) + pipeline.register_plugin(api.Action, action) diff --git a/pype/plugins/launcher/actions/FusionRenderNode.py b/pype/plugins/launcher/actions/FusionRenderNode.py new file mode 100644 index 0000000000..d866215fac --- /dev/null +++ b/pype/plugins/launcher/actions/FusionRenderNode.py @@ -0,0 +1,38 @@ +import os +from avalon import api, lib + + +class FusionRenderNode(api.Action): + + name = "fusionrendernode9" + label = "F9 Render Node" + icon = "object-group" + order = 997 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + if "AVALON_PROJECT" in session: + return False + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered + + Args: + session (dict): environment dictionary + + Returns: + Popen instance of newly spawned process + + """ + + # Update environment with session + env = os.environ.copy() + env.update(session) + + # Get executable by name + app = lib.get_application(self.name) + env.update(app["environment"]) + executable = lib.which(app["executable"]) + + return lib.launch(executable=executable, args=[], environment=env) diff --git a/pype/plugins/launcher/actions/VrayRenderSlave.py b/pype/plugins/launcher/actions/VrayRenderSlave.py new file mode 100644 index 0000000000..7461cfc0dd --- /dev/null +++ b/pype/plugins/launcher/actions/VrayRenderSlave.py @@ -0,0 +1,44 @@ +import os + +from avalon import api, lib + + +class VrayRenderSlave(api.Action): + + name = "vrayrenderslave" + label = "V-Ray Slave" + icon = "object-group" + order = 996 + + def is_compatible(self, session): + """Return whether the action is compatible with the session""" + if "AVALON_PROJECT" in session: + return False + return True + + def process(self, session, **kwargs): + """Implement the behavior for when the action is triggered + + Args: + session (dict): environment dictionary + + Returns: + Popen instance of newly spawned process + + """ + + # Update environment with session + env = os.environ.copy() + env.update(session) + + # Get executable by name + app = lib.get_application(self.name) + env.update(app["environment"]) + executable = lib.which(app["executable"]) + + # Run as server + arguments = ["-server", "-portNumber=20207"] + + return lib.launch(executable=executable, + args=arguments, + environment=env)