Aaron Powell Posted May 29, 2020 Report Share Posted May 29, 2020 I built a little Blender launcher action for Connect so I can integrate ftrack with my current studio setup. I have a basic launcher working well, and can open Blender both from Connect and the ftrack interface. How do I go a step further and tell it to open the component file associated with an asset? I've found some documentation pointing me in the right direction, but can't seem to make it to the finish line. Any pointers would be appreciated! I assume I should be grabbing the file location from the asset and pushing it through either the event data's "command" or "options" variables (http://ftrack-connect.rtd.ftrack.com/en/stable/developing/hooks/application_launch.html#developing-hooks-application-launch). Because opening a blend file from the command line isn't a keyword argument (ie, "blender my_blend_file.blend") it should be under the command variable, but I can't figure out how to get it to work. Here is my launch function: def launch(self, event): '''Callback method for Blender action.''' applicationIdentifier = ( event['data']['applicationIdentifier'] ) self.logger.debug( pprint(event['data']) ) context = event['data'].copy() # Tried opening a test file, but this crashes the action. Removing it opens default Blender file. #context['data']['command'].append('C:\\Users\\aaron\\Desktop\\test.blend') return self.launcher.launch( applicationIdentifier, context ) I've spent some time in the Maya action source code, but found little helpful for this particular problem. https://bitbucket.org/ftrack/ftrack-connect-maya/src/394b8d7a065bb67469386391caa9d883165e926a/resource/hook/ftrack_connect_maya_hook.py?at=master#ftrack_connect_maya_hook.py-74,89,117,126,131,134,137:138 Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted May 29, 2020 Report Share Posted May 29, 2020 Hi @Aaron Powell have you tried using the 'launchArguments' rather than data.command ? you can see how it gets used in nuke (in this case from the discovery itself) https://bitbucket.org/ftrack/ftrack-connect-nuke/src/77c27edbab69316fc09a3b37f77852c9b8adadd7/resource/hook/ftrack_connect_nuke_hook.py#lines-276 and how gets interally used:https://bitbucket.org/ftrack/ftrack-connect/src/e9c26b4007b03825f1fcf5b0346a4260c38ad45b/source/ftrack_connect/application.py#lines-416 hope it helps. L. Link to comment Share on other sites More sharing options...
Aaron Powell Posted May 29, 2020 Author Report Share Posted May 29, 2020 I haven't - I'll take a look! Link to comment Share on other sites More sharing options...
Aaron Powell Posted May 29, 2020 Author Report Share Posted May 29, 2020 @Lorenzo Angeli These links are incredibly helpful. Adding the path with launchArguments works perfectly. I can use the context['selection'] data to look up the file name I assume, right? I see I get an entityId and type, so I'll start playing around with the API and reference the links you sent me. Thanks again! Link to comment Share on other sites More sharing options...
Aaron Powell Posted May 30, 2020 Author Report Share Posted May 30, 2020 (edited) Ok, so here's a little update! If I run the action against the component, I can pull the file path with: selection = event['data'].get('selection', []) component = self.session.get('Component', selection[0]['entityId']) if component is not None: # this is an internal function that prints to a file on my desktop for quick debugging # returns 'u'https://ftrack-us-east-1.s3-accelerate.amazonaws.com/68d68ada-9f98-11ea-96a3-42010af0000d/storage/5/5/0/f/f586-a218-11ea-8422-72ed585b6b9d?Signature=iUglFLXGsXDm4pTY7RvA5gFxn%2BQ%3D&Expires=1590812357&AWSAccessKeyId=AKIAYKW36AUCL5BMAVWP' self.__debug(component['component_locations'][0]['url']['value']) Is there a better way to do this or am I on the right track? Edited May 30, 2020 by Aaron Powell Found more information through testing and research Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted June 5, 2020 Report Share Posted June 5, 2020 Hi @Aaron Powell, depending on where you file lives (location type), you can use either : location = session.pick_location() location.get_filesystem_path(component) or location = session.pick_location() location.get_url(component) Hope it helps. L. Link to comment Share on other sites More sharing options...
Aaron Powell Posted June 18, 2020 Author Report Share Posted June 18, 2020 Thanks Lorenzo, that works great. All of a sudden though, ftrack doesn't want to launch Blender with the edited launchArguments parameter. I had it working at one point a couple of weeks ago, and left it alone to take another look at my Perforce issues. Now that I'm revisiting this, it's giving me grief. The expected launchArguments are in the event['data'] object - this is what I get if I print out event['data'] just before passing it to launcher.launch() (file path changed to protect client): {u'actionIdentifier': u'blender-launch-action', u'applicationIdentifier': u'blender_2.82', u'icon': u'http://icons.iconarchive.com/icons/dakirby309/simply-styled/256/Blender-icon.png', u'label': u'Blender 2.82', u'launchArguments': [u'c:\\Users\\aaron\\Perforce\\path\\to\\file.blend'], u'selection': [{u'entityId': u'cce25bb7-b9ee-41a5-8278-db5c47d21940', u'entityType': u'Component'}]} And here are my discover() and launch() functions for reference: def discover(self, event): '''Return action based on *event*.''' launchArguments = [] selection = event['data'].get('selection', []) if self.is_component(selection): # custom helper function component = self.session.get('Component', selection[0]['entityId']) if component is not None: location = self.session.pick_location() url = location.get_filesystem_path(component) launchArguments.append(url) items = [] applications = self.applicationStore.applications applications = sorted( applications, key=lambda application: application['label'] ) for application in applications: applicationIdentifier = application['identifier'] label = application['label'] items.append({ 'actionIdentifier': self.identifier, 'label': label, 'icon': 'http://icons.iconarchive.com/icons/dakirby309/simply-styled/256/Blender-icon.png', 'applicationIdentifier': applicationIdentifier, 'launchArguments': launchArguments }) return { 'items': items } def launch(self, event): '''Callback method for Blender action.''' applicationIdentifier = ( event['data']['applicationIdentifier'] ) context = event['data'].copy() self.__debug(context) # custom debug helper function return self.launcher.launch( applicationIdentifier, context ) Nothing unusual is showing up in the main ftrack log file, but I don't know if it would. Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted June 19, 2020 Report Share Posted June 19, 2020 in the ftrack_connect logs you should have the full command which gets executed by subprocess in a line such as : Quote 2020-06-19 15:34:06,044 - ftrack_connect.application.ApplicationLauncher - DEBUG - Launching ['/usr/autodesk/maya2018/bin/maya'] with options {'close_fds': True, '.........} try to manually run it to see if there's any error. let us know how it goes. L. Link to comment Share on other sites More sharing options...
Aaron Powell Posted June 20, 2020 Author Report Share Posted June 20, 2020 @Lorenzo Angeli When I checked the log file there was no such line. So, I did some testing. First, I removed both my Blender plugin and the Perforce plugin and tried to run Photoshop. That time, I did see a "launch" log line. When I enabled only the Perforce plugin and tried to run Photoshop, I got the same results I did the first time: significantly smaller log file with no "launch" log. Enabling Blender only, I was able to see a launch log line but it doesn't appear to be attaching the launchArguments to the command if I'm reading it right. I hard-coded a path to an existing file to test. 2020-06-20 11:55:34,707 - ftrack_connect.ui.widget.actions.Actions - DEBUG - Before action launched: {'selection': [], 'applicationIdentifier': 'blender_2.82', 'label': 'Blender 2.82', 'actionIdentifier': 'blender-launch-action', 'launchArguments': ['c:\\Users\\aaron\\Desktop\\lookdev_addon.blend'], 'icon': 'http://icons.iconarchive.com/icons/dakirby309/simply-styled/256/Blender-icon.png'} 2020-06-20 11:55:34,786 - ftrack_connect.application.ApplicationLauncher - DEBUG - Launching ['C:\\Program Files\\Blender Foundation\\Blender 2.82\\blender.exe'] with options {'close_fds': True, 'cwd': 'C:\\Program Files\\Blender Foundation\\Blender 2.82', 'env': {#environment variables here#} 2020-06-20 11:55:34,796 - ftrack_connect.application.ApplicationLauncher - DEBUG - blender_2.82 application started. (pid=14384) 2020-06-20 11:55:34,798 - ftrack_connect.ui.widget.action_item.ActionItem - DEBUG - Launched action with result: [{'message': 'Blender 2.82 application started.', 'success': True}] 2020-06-20 11:55:34,826 - ftrack_connect.ui.widget.actions.Actions - DEBUG - Action launched: {'selection': [], 'applicationIdentifier': 'blender_2.82', 'label': 'Blender 2.82', 'actionIdentifier': 'blender-launch-action', 'launchArguments': ['c:\\Users\\aaron\\Desktop\\lookdev_addon.blend'], 'icon': 'http://icons.iconarchive.com/icons/dakirby309/simply-styled/256/Blender-icon.png'} Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted June 22, 2020 Report Share Posted June 22, 2020 Hi @Aaron Powell, extra arguments seems to be passed fine : 'launchArguments': ['c:\\Users\\aaron\\Desktop\\lookdev_addon.blend'], but then does not get populated when running the application 2020-06-20 11:55:34,786 - ftrack_connect.application.ApplicationLauncher - DEBUG - Launching ['C:\\Program Files\\Blender Foundation\\Blender 2.82\\blender.exe'] as by https://bitbucket.org/ftrack/ftrack-connect/src/57ece813d21bff72621c1a6a58feb958cd0557cd/source/ftrack_connect/application.py#lines-364 your Launching command should contain your extra arguments. in order to further debug you can overwrite _getApplicationLaunchCommand to print the command before being returned to the actual popen function. Hope it helps. L. Link to comment Share on other sites More sharing options...
marc mantei Posted June 23, 2020 Report Share Posted June 23, 2020 22 hours ago, Lorenzo Angeli said: 'launchArguments': ['c:\\Users\\aaron\\Desktop\\lookdev_addon.blend'], but then does not get populated when running the application 2020-06-20 11:55:34,786 - ftrack_connect.application.ApplicationLauncher - DEBUG - Launching ['C:\\Program Files\\Blender Foundation\\Blender 2.82\\blender.exe'] as by https://bitbucket.org/ftrack/ftrack-connect/src/57ece813d21bff72621c1a6a58feb958cd0557cd/source/ftrack_connect/application.py#lines-364 your Launching command should contain your extra arguments. in order to further debug you can overwrite _getApplicationLaunchCommand to print the command before being returned to the actual popen function. Hope it helps. L. Hi Lorenzo, i am currently looking for the same solution and my launchArgument gets not populated when running my application too. can you please give more details on how to overwrite the "_getApplicationLaunchCommand" ? this function is not included in my hook, and adding this function from your mentioned site will miss some inputs i cannot work out to handle. this is my first time using ftrack. thanks in advance. M. Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted June 25, 2020 Report Share Posted June 25, 2020 @marc mantei _getApplicationLaunchCommand is a method of the ftrack connect ApplicationLauncher class. Using this as baseclass for your launcher will provide you access to the underlying method. You can see an example in the maya hook hope it helps. L. Link to comment Share on other sites More sharing options...
Aaron Powell Posted June 30, 2020 Author Report Share Posted June 30, 2020 @Lorenzo Angeli _getApplicationLaunchCommand seems to only be returning the base command - ['C:\\Program Files\\Blender Foundation\\Blender 2.82\\blender.exe']. No attached launchArguments. Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted July 1, 2020 Report Share Posted July 1, 2020 @Aaron Powell would you mind either paste here the full launcher or send it over support so I can have a closer look ? Thanks. L. Link to comment Share on other sites More sharing options...
Aaron Powell Posted July 1, 2020 Author Report Share Posted July 1, 2020 import logging import ftrack_api import sys import pprint import ftrack_connect.application class ApplicationStore(ftrack_connect.application.ApplicationStore): '''Store used to find and keep track of available applications.''' def _discoverApplications(self): '''Return a list of applications that can be launched from this host.''' applications = [] if sys.platform == 'darwin': prefix = ['/', 'Applications'] applications.extend(self._searchFilesystem( expression=prefix + [ 'Blender*', 'Blender.app' ], label='Blender {version}', applicationIdentifier='blender_{version}' )) elif sys.platform == 'win32': prefix = ['C:\\', 'Program Files.*'] applications.extend(self._searchFilesystem( expression=( prefix + ['Blender Foundation', 'Blender*', 'blender.exe'] ), label='Blender {version}', applicationIdentifier='blender_{version}' )) self.logger.debug( 'Discovered applications:\n{0}'.format( pprint.pformat(applications) ) ) return applications class BlenderAction(object): '''Launch Blender action.''' # Unique action identifier. identifier = 'blender-launch-action' def __debug(self, message): f = open("C:\\Users\\aaron\\Desktop\\output.txt", "a") f.write(pprint.pformat(message)) f.close() def __init__(self, applicationStore, launcher): '''Initialise action with *applicationStore*.''' super(BlenderAction, self).__init__() self.logger = logging.getLogger( __name__ + '.' + self.__class__.__name__ ) self.applicationStore = applicationStore self.launcher = launcher if self.identifier is None: raise ValueError('The action must be given an identifier.') def register(self, session): '''Register action.''' session.event_hub.subscribe( 'topic=ftrack.action.discover', self.discover ) session.event_hub.subscribe( 'topic=ftrack.action.launch and data.actionIdentifier={0}'.format( self.identifier ), self.launch ) self.session = session def is_component(self, selection): if ( len(selection) != 1 or selection[0]['entityType'] != 'Component' ): return False return True def discover(self, event): '''Return action based on *event*.''' launchArguments = [] selection = event['data'].get('selection', []) if self.is_component(selection): component = self.session.get('Component', selection[0]['entityId']) if component is not None: location = self.session.pick_location() url = location.get_filesystem_path(component) launchArguments.append(url) items = [] applications = self.applicationStore.applications applications = sorted( applications, key=lambda application: application['label'] ) for application in applications: applicationIdentifier = application['identifier'] label = application['label'] items.append({ 'actionIdentifier': self.identifier, 'label': label, 'icon': 'http://icons.iconarchive.com/icons/dakirby309/simply-styled/256/Blender-icon.png', 'applicationIdentifier': applicationIdentifier, 'launchArguments': ['c:\\Users\\aaron\\Desktop\\lookdev_addon.blend'] #launchArguments }) return { 'items': items } def launch(self, event): '''Callback method for Blender action.''' applicationIdentifier = ( event['data']['applicationIdentifier'] ) context = event['data'].copy() return self.launcher.launch( applicationIdentifier, context ) class ApplicationLauncher(ftrack_connect.application.ApplicationLauncher): '''Custom launcher to modify environment before launch.''' def __debug(self, message): f = open("C:\\Users\\aaron\\Desktop\\output.txt", "a") f.write(pprint.pformat(message)) f.close() def _getApplicationLaunchCommand(self, application, context=None): command = ftrack_connect.application.ApplicationLauncher._getApplicationLaunchCommand(self, application, context) self.__debug(command) return command def register(session, **kw): '''Register action in Connect.''' # 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): return applicationStore = ApplicationStore() launcher = ApplicationLauncher( applicationStore ) action = BlenderAction(applicationStore, launcher) action.register(session) Here's the full hook. To be honest, it's not too much different from the Houdini example in the docs at the moment, which is why I'm not sure what's going on. Let me know if something sticks out. Lorenzo Angeli 1 Link to comment Share on other sites More sharing options...
Lorenzo Angeli Posted July 7, 2020 Report Share Posted July 7, 2020 Hi @Aaron Powell using the discovery to populate the available applications does not work as it will look into the application store to decide which one to start. The best option at the time seems to be to override the _getApplicationLauncher as by this example to inject the launchArguments you are after. long story short this is what you can simply do: class ApplicationLauncher(ftrack_connect.application.ApplicationLauncher): '''Custom launcher to modify environment before launch.''' def _getApplicationLaunchCommand(self, application, context=None): command = super(ApplicationLauncher, self)._getApplicationLaunchCommand(application, context) command.extend(context.get('launchArguments')) return command The issue is in the original _getApplicationLauncher, which extracts the extra launchArguments from the applications but does not take in account the current context. good news is that we are in the process of reviewing how applications are launched, hence this will be fixed soon. Cheers. L. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now