├── .gitignore ├── setup ├── menu.py └── atom_server.py ├── .npmignore ├── keymaps └── nuke.cson ├── styles └── nuke.less ├── lib ├── status-view.coffee ├── send_to_nuke.py └── nuke.coffee ├── menus └── nuke.cson ├── package.json ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /setup/menu.py: -------------------------------------------------------------------------------- 1 | import atom_server 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /keymaps/nuke.cson: -------------------------------------------------------------------------------- 1 | 'atom-text-editor': 2 | 'ctrl-alt-r': 'nuke:run' 3 | 'escape': 'nuke:closeMessage' 4 | -------------------------------------------------------------------------------- /styles/nuke.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .nuke { 8 | } 9 | -------------------------------------------------------------------------------- /lib/status-view.coffee: -------------------------------------------------------------------------------- 1 | {View} = require 'atom' 2 | 3 | module.exports = class StatusView 4 | 5 | constructor: (serializedState) -> 6 | 7 | @element = document.createElement('div') 8 | 9 | serialize: -> 10 | 11 | destroy: -> 12 | @detach() 13 | 14 | update: (text) -> 15 | # Update the message 16 | @element.innerHTML = text 17 | 18 | getElement: -> 19 | @element 20 | -------------------------------------------------------------------------------- /menus/nuke.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/hacking-atom-package-word-count#menus for more details 2 | 'context-menu': 3 | 'atom-text-editor': [ 4 | { 5 | 'label': 'Run in Nuke' 6 | 'command': 'nuke:run' 7 | } 8 | ] 9 | 'menu': [ 10 | { 11 | 'label': 'Packages' 12 | 'submenu': [ 13 | 'label': 'nuke' 14 | 'submenu': [ 15 | { 'label': 'Run', 'command': 'nuke:run' } 16 | ] 17 | ] 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Foundry-Nuke", 3 | "main": "./lib/nuke", 4 | "version": "0.3.0", 5 | "private": true, 6 | "description": "Run python scripts from Atom to Nuke.", 7 | "activationCommands": { 8 | "atom-text-editor": [ 9 | "nuke:run", 10 | "nuke:closeMessage" 11 | ] 12 | }, 13 | "repository": "https://github.com/tokejepsen/atom-foundry-nuke", 14 | "license": "MIT", 15 | "engines": { 16 | "atom": ">0.50.0" 17 | }, 18 | "dependencies": { 19 | "temp": "~0.7.0", 20 | "atom-message-panel": "1.2.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Toke Jepsen https://github.com/tokejepsen/atom-foundry-nuke 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /setup/atom_server.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Simple socket server using threads 3 | ''' 4 | 5 | import socket 6 | import sys 7 | import threading 8 | import StringIO 9 | import contextlib 10 | 11 | import nuke 12 | 13 | HOST = '' 14 | PORT = 8888 15 | 16 | 17 | @contextlib.contextmanager 18 | def stdoutIO(stdout=None): 19 | old = sys.stdout 20 | if stdout is None: 21 | stdout = StringIO.StringIO() 22 | sys.stdout = stdout 23 | yield stdout 24 | sys.stdout = old 25 | 26 | 27 | def _exec(data): 28 | with stdoutIO() as s: 29 | exec(data) 30 | return s.getvalue() 31 | 32 | 33 | def server_start(): 34 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 35 | s.bind((HOST, PORT)) 36 | s.listen(5) 37 | 38 | while 1: 39 | client, address = s.accept() 40 | try: 41 | data = client.recv(4096) 42 | if data: 43 | result = nuke.executeInMainThreadWithResult(_exec, args=(data)) 44 | client.send(result) 45 | except SystemExit: 46 | result = self.encode('SERVER: Shutting down...') 47 | client.send(result) 48 | raise 49 | finally: 50 | client.close() 51 | 52 | t = threading.Thread(None, server_start) 53 | t.setDaemon(True) 54 | t.start() 55 | -------------------------------------------------------------------------------- /lib/send_to_nuke.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import sys 3 | import textwrap 4 | from optparse import OptionParser 5 | 6 | parser = OptionParser() 7 | parser.add_option("-f", "--file", dest="file") 8 | parser.add_option("-a", "--host", dest="host") 9 | parser.add_option("-p", "--port", dest="port") 10 | 11 | (options, args) = parser.parse_args() 12 | 13 | def SendToNuke(options): 14 | 15 | PY_CMD_TEMPLATE = textwrap.dedent(''' 16 | import traceback 17 | import sys 18 | import __main__ 19 | 20 | namespace = __main__.__dict__.get('_atom_plugin_SendToNuke') 21 | if not namespace: 22 | namespace = __main__.__dict__.copy() 23 | __main__.__dict__['_atom_plugin_SendToNuke'] = namespace 24 | 25 | namespace['__file__'] = r\'{0}\' 26 | 27 | try: 28 | execfile(r\'{0}\', namespace, namespace) 29 | except: 30 | sys.stdout.write(traceback.format_exc()) 31 | traceback.print_exc() 32 | ''') 33 | 34 | command_tpl = PY_CMD_TEMPLATE.format(options.file) 35 | 36 | host = options.host.replace('\'', '') 37 | port = int(options.port) 38 | 39 | ADDR = (host, port) 40 | 41 | client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 42 | client.connect(ADDR) 43 | 44 | client.send(command_tpl) 45 | data = client.recv(4096) 46 | 47 | print(data) 48 | 49 | client.close() 50 | 51 | if __name__=='__main__': 52 | if options.file: 53 | SendToNuke(options) 54 | else: 55 | sys.exit("No command given") 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-foundry-nuke 2 | 3 | Run python scripts from Atom to Nuke. 4 | 5 | ## Features: 6 | 7 | * Send an entire file to Nuke 8 | 9 | ## Installation: 10 | 11 | Copy atom_server.py from the setup folder to [USER]\.nuke 12 | 13 | Alternatively make atom_server.py from the following code; 14 | 15 | ```python 16 | import socket 17 | import sys 18 | import threading 19 | import StringIO 20 | import contextlib 21 | 22 | import nuke 23 | 24 | HOST = '' 25 | PORT = 8888 26 | 27 | 28 | @contextlib.contextmanager 29 | def stdoutIO(stdout=None): 30 | old = sys.stdout 31 | if stdout is None: 32 | stdout = StringIO.StringIO() 33 | sys.stdout = stdout 34 | yield stdout 35 | sys.stdout = old 36 | 37 | 38 | def _exec(data): 39 | with stdoutIO() as s: 40 | exec(data) 41 | return s.getvalue() 42 | 43 | 44 | def server_start(): 45 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 46 | s.bind((HOST, PORT)) 47 | s.listen(5) 48 | 49 | while 1: 50 | client, address = s.accept() 51 | try: 52 | data = client.recv(4096) 53 | if data: 54 | result = nuke.executeInMainThreadWithResult(_exec, args=(data)) 55 | client.send(result) 56 | except SystemExit: 57 | result = self.encode('SERVER: Shutting down...') 58 | client.send(result) 59 | raise 60 | finally: 61 | client.close() 62 | 63 | t = threading.Thread(None, server_start) 64 | t.setDaemon(True) 65 | t.start() 66 | ``` 67 | 68 | Copy menu.py from the setup folder to [USER]\.nuke 69 | 70 | Alternatively paste in the following into an existing menu.py; 71 | 72 | ```python 73 | import atom_server 74 | ``` 75 | 76 | ## Usage: 77 | 78 | Open up a python script and press ```ctrl-alt-r``` on the file. 79 | 80 | ## Thanks to: 81 | 82 | David Paul Rosser for the original work; https://github.com/davidpaulrosser/atom-maya 83 | 84 | Hugh MacDonald for a guiding repo; https://github.com/Nvizible/NukeExternalControl 85 | -------------------------------------------------------------------------------- /lib/nuke.coffee: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | sys = require 'sys' 4 | exec = require('child_process').exec 5 | StatusView = require './status-view' 6 | temp = require 'temp' 7 | {CompositeDisposable} = require 'atom' 8 | {MessagePanelView, PlainMessageView} = require 'atom-message-panel' 9 | 10 | module.exports = 11 | 12 | modalTimeout: null 13 | messagepanel: null 14 | 15 | activate: (state) -> 16 | 17 | atom.commands.add 'atom-workspace', "nuke:closeMessage", => @closeMessage() 18 | 19 | # Set defaults 20 | atom.config.setDefaults("nuke", host: '127.0.0.1', port: 8888, save_on_run: true ) 21 | 22 | # Create the status view 23 | @statusView = new StatusView(state.statusViewState) 24 | @modalPanel = atom.workspace.addModalPanel(item: @statusView.getElement(), visible: false) 25 | 26 | # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable 27 | @subscriptions = new CompositeDisposable 28 | 29 | # Listen for run command 30 | @subscriptions.add atom.commands.add 'atom-workspace', 'nuke:run': => @run() 31 | 32 | # Automatically track and cleanup files at exit 33 | temp.track() 34 | 35 | 36 | deactivate: -> 37 | @statusView.destroy() 38 | @subscriptions.dispose() 39 | 40 | serialize: -> 41 | 42 | getActiveEditor: -> 43 | atom.workspace.getActiveTextEditor() 44 | 45 | run: -> 46 | 47 | # Get the current selection 48 | editor = @getActiveEditor() 49 | 50 | if atom.config.get('nuke').save_on_run 51 | editor.save() 52 | 53 | selection = editor.getLastSelection() 54 | 55 | if editor.getLastSelection().isEmpty() 56 | # Get the active pane file path 57 | @send_to_nuke editor.buffer.file.path 58 | else 59 | # console.log('send selection', selection) 60 | # Create a tmp file and save the selection 61 | text = editor.getSelections()[0].getText() 62 | 63 | @get_tmp_file_for_selection text, (file) => 64 | @send_to_nuke file 65 | 66 | return 67 | 68 | send_to_nuke: (file) -> 69 | 70 | # console.log('send to nuke', file) 71 | 72 | if not file.match '.py' 73 | @updateStatusView "Error: Not a python file" 74 | @closeModal() 75 | return 76 | 77 | HOST = atom.config.get('nuke').host 78 | PORT = atom.config.get('nuke').port 79 | 80 | cmd = "python #{__dirname}/send_to_nuke.py" 81 | cmd += " -f \"#{file}\"" 82 | cmd += " -a '#{HOST}'" #h results in a conflict? 83 | cmd += " -p #{PORT}" 84 | 85 | date = new Date() 86 | 87 | @updateStatusView "Executing file: #{file}" 88 | 89 | exec cmd, (error, stdout, stderr) => 90 | 91 | console.log 'stdout', stdout 92 | console.log 'stderr', stderr 93 | 94 | @messagepanel = new MessagePanelView({title: 'Nuke Feedback'}) unless @messagepanel? 95 | 96 | @messagepanel.clear() 97 | @messagepanel.attach() 98 | 99 | String::startsWith ?= (s) -> @slice(0, s.length) == s 100 | 101 | lines = stdout.split('\n') 102 | text_class = 'text-highlight' 103 | for line in lines 104 | text_class = 'text-error' if line.startsWith('Traceback (most recent call last):') 105 | 106 | @messagepanel.add new PlainMessageView 107 | message: line 108 | className: text_class 109 | 110 | ellapsed = (Date.now() - date) / 1000 111 | 112 | if error? 113 | @updateStatusView "Error: #{stderr}" 114 | console.error 'error', error 115 | else 116 | @updateStatusView "Success: Ran in #{ellapsed}s" 117 | 118 | # Cleanup any tmp files created 119 | temp.cleanup() 120 | 121 | @closeModal() 122 | 123 | updateStatusView: (text) -> 124 | 125 | clearTimeout @modalTimeout 126 | 127 | @modalPanel.show() 128 | @statusView.update "[atom-foundry-nuke] #{text}" 129 | 130 | closeMessage: -> 131 | 132 | try @messagepanel.close() 133 | 134 | closeModal: -> 135 | 136 | clearTimeout @modalTimeout 137 | 138 | @modalTimeout = setTimeout => 139 | @modalPanel.hide() 140 | , 2000 141 | 142 | get_tmp_file_for_selection: (selection, callback) -> 143 | 144 | temp.mkdir 'atom-foundry-nuke-selection', (err, dirPath) -> 145 | 146 | inputPath = path.join dirPath, 'command.py' 147 | 148 | fs.writeFile inputPath, selection, (err) -> 149 | 150 | if err? 151 | throw err 152 | else 153 | callback inputPath 154 | --------------------------------------------------------------------------------