-
Notifications
You must be signed in to change notification settings - Fork 1
/
SubTunnel.py
379 lines (269 loc) · 15.3 KB
/
SubTunnel.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
"""
SubTunnel
Kuba Roth: 140801
Info:
A Sublime plugin to send code snippets into running Houdini session
supported nodes:
SOP - vex: attribwrangle, pointwrangle, volumewrangle, popwrangle, VOPSOP (inline)
python: 'python' node (new in H13)
OTLS: code/script tabs in any context (SOP,OBJ,ROP ...)
"""
import sublime, sublime_plugin
import subprocess,sys
import re, json, os
import time
import SubTunnel.SubTunnelPorts as subPorts
class Tunnel():
def __init__(self,window,port):
self.window = window
self.port = port
self.hython_path = self.getConfig('hcommand')
self.hcommand = '%s %s' % (self.hython_path, self.port)
self.hipfile = '%s' % (self.getConfig('hipfile')) # path to current $HIP
self.nodeType = self.getNodeType()
self.nodePath = self.getNodePath()
self.filePath = self.getFilePath() # path to the temporary code file
self.codeAsText = self.getCodeAsText()
#print ("path: ",self.nodePath, "\ntype: ", self.selection, "\n\n")
def getConfig(self, opt):
''' loads the config '''
plugin_path = '%s/SubTunnel' % (sublime.packages_path())
config = '%s/config.json' % plugin_path
if os.path.exists(config)==True:
f=open(config).read()
options = json.loads(f)
return options[opt]
def getNodeType(self):
''' get the selected node type '''
# Note a special treatment of backticks (bash specifics)
# cmd = ''' %s "optype -t opfind -N \"/\"\`opselectrecurse(\\"/\\",0)\`" ''' % self.hcommand
# This is just a regular hscript command you would launch from hscript shell in houdini
hscriptCmd = r'''optype -t opfind -N /`opselectrecurse("/",0)'''
# hscriptCmd = r'''optype -t opfind -N "/"opselectrecurse("/",0)''' # change on WIN
if os.name=='posix':
hscriptCmd = subPorts.escape(hscriptCmd, 1)
else:
hscriptCmd = subPorts.escape(hscriptCmd, 2) # dont escape backticks in a shell
# the command gets wrapped with double-quates - required by bash
# and prepanded with full path hcommand
cmd = r'''%s "%s"''' % (self.hcommand,hscriptCmd)
print ("CMD getNodeType:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
selection = None
selection = cmd_stdout.decode('ascii').strip()
if selection == '':
print ("=== nothing selected ===")
print ("Node type: " ,selection)
return selection
def getNodePath(self):
''' get the node path '''
# cmd = ''' %s "opfind -N \"/\"\`opselectrecurse(\\"/\\",0)\`" ''' % self.hcommand # no space between \"/\"\`o
# cmd = r''' %s "opfind -N "/"\`opselectrecurse(\"/\",0)\`" ''' % self.hcommand # raw works too
hscriptCmd = r'''opfind -N "/"`opselectrecurse("/",0)''' # Hscript command
if os.name=='posix':
hscriptCmd = subPorts.escape(hscriptCmd, 1)
else:
hscriptCmd = subPorts.escape(hscriptCmd, 2)
cmd = r'''%s "%s"''' % (self.hcommand,hscriptCmd)
print ("CMD getNodePath:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
nodePath = cmd_stdout.decode('ascii').strip()
print ("Node path: " ,nodePath)
return nodePath
def getFilePath(self):
view = self.window.active_view()
return view.file_name() # None if the file is not save
def getCodeAsText(self):
''' Introduce escape characters to avoid misinterpretation by the shell '''
view = self.window.active_view()
codeText = view.substr(sublime.Region(0, view.size()))
# Treat \n in the strings differently then new lines at the end of the line
temp = []
textSplited = re.split('((?s)".*?")', codeText)
# textSplited = re.split('^.*$[\n]"', codeText)
for x in textSplited:
if os.name=='posix':
x = subPorts.escape(x)
else:
x = subPorts.escape(x,3) # WIN - code as text
temp.append(x)
codeText = r''.join(temp)
'''
this command works from the bash
hcommand 2223 "opparm /obj/geo1/python1 python \"print \\"______cccB\\" \" "
^ ^ ^ ^
queue single escape quote | |
double escape
'''
return codeText
class SubTunnelCommand(sublime_plugin.WindowCommand):
''' hdaRun is a sub-function to be able to access previously defined Tunnel()
the callback script in show_quick_panel implicitly expects only one arguments - choice index
Also I wasn't able to initialize the SubTunnelCommand class and define the Tunnel() in constructor
Probably because the SubTunnelCommand is a special type of class and Sublime does some stuff under the hood
'''
def getTableAndOpName(self,hcommand, nodePath):
''' get the Table (parent network type) and the HDA type name used by otcontentadd '''
tableAndOpName = '_'
cmd = ''' %s optype -o %s''' %(hcommand, nodePath)
# print ("getTableAndOpName: ",cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
tableAndOpName = cmd_stdout.decode('ascii').strip().split('\n')[0]
return tableAndOpName
def getHdaContent(self,hcommand,tableAndOpName):
''' returns all the available tabs on the HDA which we filter to build menu list
There are 3 main sections to support at the moment PythonModule, PythonCook, VexCode '''
cmd = ''' %s otcontentls %s''' %(hcommand, tableAndOpName)
# print ("getTableAndOpName: ",cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
content = cmd_stdout.decode('ascii').strip().split('\n')
# Let's filter it for now
content = [entry for entry in content if entry in ['PythonModule', 'PythonCook', 'VflCode']]
# print ("Filter Content:", content)
# There may be the case when creting a HDA from a subnet that the Python section is not yet created
# Create one:
if len(content)==0:
content = ['PythonModule']
return content
def buildPowershellCmd(self, h, hscriptCmd):
"""This is method addresses limitation of CMD which has been truncating sent source code.
We We no longer launch CMD from subprocess, instead on Windows we use Powershell.
"""
# NOTE: We cant rely on tempfile.gettempdir() as it returns lower case path.
# Since this is Windows only - access env variable directly.
os_temp_dir = os.getenv("TEMP")
serialized_code_file = '{0}/sublime_houdini_tunnel.txt'.format(os_temp_dir)
with open(serialized_code_file, 'w') as f:
f.write('"{0}"'.format(hscriptCmd)) # wrap output in double quotes
# print("serialize opparm args into: ", serialized_code_file)
# Build path to the Powershell script - same location as this script
this_dir = os.path.split("{0}".format(__file__))[0]
# NOTE: Subsequent arguments after PORT are serialized into a file - as this is the only way to handle
# quotes and escape sequences in the source code.
cmd = '''powershell -File "{0}/tunnel_houdini.ps1" {1} {2}'''.format(
this_dir,
h.hython_path,
h.port
)
return cmd
def hdaRun(self,choice,hdaOptions,tunnel,tableAndOpName):
# Currently there is no support for the vex context in vex HDA SOP
if choice!=-1: # -1 is set when pressed ESC
hscriptCmd = r'''otcontentadd %s %s %s''' % (tableAndOpName, hdaOptions[choice], tunnel.filePath) # Hscript command
hscriptCmd = subPorts.escape(hscriptCmd, 1)
cmd = r'''%s "%s"''' % (tunnel.hcommand,hscriptCmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
def run(self):
port = subPorts.getPort()
h = Tunnel(self.window,port)
# print (h.nodeType)
# print (h.nodePath)
# print (h.filePath) # path to the temporary code file
# print (h.getCodeAsText)
# print ('HIP',h.hipfile)
if h.nodeType in ['attribwrangle', 'pointwrangle', 'volumewrangle', 'popwrangle']: # VEX WRANGLE nodes
hscriptCmd = r'''opparm %s snippet \"%s\"''' % (h.nodePath,h.codeAsText) # Hscript command + \" around already escaped code
# hscriptCmd = subPorts.escape(hscriptCmd, 1) # No espcing here - the code already is escaped
cmd = r'''%s "%s"''' % (h.hcommand,hscriptCmd)
# cmd = ''' %s \"opparm %s snippet \\"%s\\" \"''' %(h.hcommand, h.nodePath,h.codeAsText)
if os.name in ['nt']:
cmd = self.buildPowershellCmd(h, hscriptCmd)
print ("CMD - vexsop:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
elif h.nodeType in ['inline']: # INLINE VEX VOPSOP - same as wrangle nodes but with different param name
# cmd = ''' %s \"opparm %s code \\"%s\\" \"''' %(h.hcommand, h.nodePath,h.codeAsText)
hscriptCmd = r'''opparm %s code \"%s\"''' % (h.nodePath,h.codeAsText) # just and escape around codeAsText
cmd = r'''%s "%s"''' % (h.hcommand,hscriptCmd)
print ("CMD inline:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
elif h.nodeType == "python": # PYTHON SOP
# cmd = ''' %s \"opparm %s python \\"%s\\" \"''' %(h.hcommand, h.nodePath,h.codeAsText) # 2x backslash to have \" in terminal
hscriptCmd = r'''opparm %s python \"%s\"''' % (h.nodePath,h.codeAsText) # just and escape around codeAsText
cmd = r'''%s "%s"''' % (h.hcommand,hscriptCmd)
if os.name in ['nt']:
cmd = self.buildPowershellCmd(h, hscriptCmd)
print ("CMD python sop:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
elif h.nodeType=="": # In case no node is selected
portMesssage = '\tPort %s not opened' % h.port
self.window.show_quick_panel(['\tNo Node selected or...', portMesssage, '\t-> run openport -a in Houdini Textport', '\t-> rerun "Tunnel Sessions" to connect to running Houdini session'], 0 ,sublime.MONOSPACE_FONT)
else: # HDA code / scripts tab
# For hda find its type to determine what network it is suppose to go
tableAndOpName = self.getTableAndOpName(h.hcommand,h.nodePath)
hdaOptions = self.getHdaContent(h.hcommand,tableAndOpName)
hdaLabels = self.getHdaContent(h.hcommand,tableAndOpName)
print ("NTWK type: ", tableAndOpName)
print ("HDA Options:", hdaOptions)
info = ['','INFO:', 'File Path: {:>25s}'.format(h.hipfile), 'Node Path: {:>25s}'.format(h.nodePath), 'Node Type: {:>25s}'.format(tableAndOpName)]
hdaLabels.extend(info)
# TODO there is still a problem with vex otl where the
# otcontentadd Sop/testVex VexCode /home/kuba/temp/bbbbb.vex
# has no effect
self.window.run_command('save') # save the current sublime file
self.window.show_quick_panel(hdaLabels, lambda id: self.hdaRun(id,hdaOptions,h,tableAndOpName) ,sublime.MONOSPACE_FONT)
class FindHoudiniSessionsCommand(sublime_plugin.WindowCommand):
'''
This section allows user to pick the desired Houdini session to connect to
'''
def run(self):
if os.name=='posix':
pidsDict={}
pids = subPorts.getHoudiniPorts()
for pid,port in pids.items():
ports = {'port':-1,'hipfile':''} # pids ports
ports['port']=port
ports['hipfile']=subPorts.getHipName(port)
pidsDict[pid] = ports
else:
pidsDict = {}
import SubTunnel.SubTunnelPortsWin as subWinPorts
from imp import reload
reload(subWinPorts)
pidsDict = subWinPorts.getHPorts()
print ("---", pidsDict)
portName_list = subPorts.buildPortList(pidsDict) # Build list
# pidsDict consist all the collected information
print ("All Pids:", pidsDict)
self.window.show_quick_panel(portName_list, lambda id: subPorts.savePort(id,pidsDict) ,sublime.MONOSPACE_FONT)
print ("Port Set")
pass
class ShelfToolCommand(sublime_plugin.WindowCommand):
'''
Send the code into one of the existing shelf tools
Name the tool has to set before
'''
def on_done(self, shelfToolName):
### Update config with new tool name
configAll = subPorts.getConfig()
configAll['shelftool'] = shelfToolName
# print(configAll)
plugin_path = '%s/SubTunnel' % (sublime.packages_path())
config = '%s/config.json' % plugin_path
f = open(config, 'w')
f.write(json.dumps(configAll))
f.close()
### Send the code
port = subPorts.getPort()
h = Tunnel(self.window,port)
code = h.codeAsText
python = "hou.shelves.tools()['%s'].setData('%s')" % (shelfToolName,code)
hscriptCmd = r'''python -c \"%s\"''' % python
cmd = r'''%s "%s"''' % (h.hcommand,hscriptCmd)
print ("CMD shelf:", cmd)
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = p.communicate()
def run(self):
prevShelfTool = subPorts.getConfig('shelftool')
print (prevShelfTool)
if prevShelfTool == None:
prevShelfTool="_"
self.window.show_input_panel("Shelf Tool Name:",prevShelfTool,self.on_done,None,None)
# self.window.show_quick_panel(portName_list, lambda id: subPorts.savePort(id,pidsDict) ,sublime.MONOSPACE_FONT)
pass