Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

stamps.py 90KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280
  1. #------------------------------------------------------
  2. # Stamps by Adrian Pueyo and Alexey Kuchinski
  3. # Smart node connection system for Nuke
  4. # adrianpueyo.com, 2018-2019
  5. version= "v1.0"
  6. date = "Sep 27 2019"
  7. #-----------------------------------------------------
  8. # Constants
  9. STAMP_DEFAULTS = { "note_font_size":20, "hide_input":0 }
  10. ANCHOR_DEFAULTS = { "tile_color" : int('%02x%02x%02x%02x' % (255,255,255,1),16),
  11. "autolabel": 'nuke.thisNode().knob("title").value()',
  12. "knobChanged":'stamps.anchorKnobChanged()',
  13. "onCreate":'if nuke.GUI:\n try:\n import stamps; stamps.anchorOnCreate()\n except:\n pass'}
  14. WIRED_DEFAULTS = { "tile_color" : int('%02x%02x%02x%02x' % (1,0,0,1),16),
  15. "autolabel": 'nuke.thisNode().knob("title").value()',
  16. "knobChanged":'import stamps; stamps.wiredKnobChanged()'}
  17. DeepExceptionClasses = ["DeepToImage","DeepHoldout","DeepHoldout2"] # Nodes with "Deep" in their class that don't classify as Deep.
  18. NodeExceptionClasses = ["Viewer"] # Nodes that won't accept stamps
  19. ParticleExceptionClasses = ["ParticleToImage"] # Nodes with "Particle" in class and an input called "particles" that don't classify as particles.
  20. StampClasses = {"2D":"NoOp", "Deep":"DeepExpression"}
  21. AnchorClassesAlt = {"2D":"NoOp", "Deep":"DeepExpression"}
  22. StampClassesAlt = {"2D":"NoOp", "Deep":"DeepExpression", "3D":"LookupGeo", "Camera":"DummyCam", "Axis":"Axis", "Particle":"ParticleExpression"}
  23. InputIgnoreClasses = ["NoOp", "Dot", "Reformat", "DeepReformat", "Crop"]
  24. TitleIgnoreClasses = ["NoOp", "Dot", "Reformat", "DeepReformat", "Crop"]
  25. TagsIgnoreClasses = ["NoOp", "Dot", "Reformat", "DeepReformat", "Crop"]
  26. AnchorClassColors = {"Camera":int('%02x%02x%02x%02x' % (255,255,255,1),16),}
  27. WiredClassColors = {"Camera":int('%02x%02x%02x%02x' % (51,0,0,1),16),}
  28. STAMPS_HELP = "Stamps by Adrian Pueyo and Alexey Kuchinski.\nUpdated "+date
  29. VERSION_TOOLTIP = "Stamps by Adrian Pueyo and Alexey Kuchinski.\nUpdated "+date+"."
  30. STAMPS_SHORTCUT = "F8"
  31. KEEP_ORIGINAL_TAGS = True
  32. if not globals().has_key('Stamps_LastCreated'):
  33. Stamps_LastCreated = None
  34. if not globals().has_key('Stamps_MenusLoaded'):
  35. Stamps_MenusLoaded = False
  36. Stamps_LockCallbacks = False
  37. import nuke
  38. import nukescripts
  39. import re
  40. from functools import partial
  41. # PySide import switch
  42. try:
  43. if nuke.NUKE_VERSION_MAJOR < 11:
  44. from PySide import QtCore, QtGui, QtGui as QtWidgets
  45. from PySide.QtCore import Qt
  46. else:
  47. from PySide2 import QtWidgets, QtGui, QtCore
  48. from PySide2.QtCore import Qt
  49. except ImportError:
  50. from Qt import QtCore, QtGui, QtWidgets
  51. # Import stamps_config
  52. # Optional: place the stamps_config.py file anywhere in your python path (i.e. in your /.nuke folder)
  53. anchor_defaults = STAMP_DEFAULTS.copy()
  54. anchor_defaults.update(ANCHOR_DEFAULTS)
  55. wired_defaults = STAMP_DEFAULTS.copy()
  56. wired_defaults.update(WIRED_DEFAULTS)
  57. try:
  58. from stamps_config import *
  59. if ANCHOR_STYLE:
  60. anchor_defaults.update(ANCHOR_STYLE)
  61. if STAMP_STYLE:
  62. wired_defaults.update(STAMP_STYLE)
  63. except:
  64. pass
  65. #################################
  66. ### FUNCTIONS INSIDE OF BUTTONS
  67. #################################
  68. def wiredShowAnchor():
  69. n = nuke.thisNode()
  70. a_name = n.knob("anchor").value()
  71. if nuke.exists(a_name):
  72. nuke.show(nuke.toNode(a_name))
  73. elif n.inputs():
  74. nuke.show(n.input(0))
  75. def wiredZoomAnchor():
  76. n = nuke.thisNode()
  77. a_name = n.knob("anchor").value()
  78. if nuke.exists(a_name):
  79. a = nuke.toNode(a_name)
  80. #nuke.show(a)
  81. nuke.zoom(nuke.zoom(),[a.xpos()+a.screenWidth()/2,a.ypos()+a.screenHeight()/2])
  82. elif n.inputs():
  83. ni = n.input(0)
  84. nuke.zoom(nuke.zoom(),[ni.xpos()+ni.screenWidth()/2,ni.ypos()+ni.screenHeight()/2])
  85. def wiredZoomThis():
  86. n = nuke.thisNode()
  87. nuke.zoom(nuke.zoom(),[n.xpos(),n.ypos()])
  88. def wiredStyle(n, style = 0):
  89. ''' Change the style of a wired stamp, based on some presets '''
  90. if "note_font_size" in wired_defaults.keys():
  91. size = wired_defaults["note_font_size"]
  92. else:
  93. size = 20
  94. nf = n["note_font"].value().split(" Bold")[0].split(" bold")[0]
  95. if style == 0: # DEFAULT
  96. n["note_font_size"].setValue(size)
  97. n["note_font_color"].setValue(0)
  98. n["note_font"].setValue(nf)
  99. elif style == 1: # BROKEN
  100. n["note_font_size"].setValue(size*2)
  101. n["note_font_color"].setValue(4278190335)
  102. n["note_font"].setValue(nf+" Bold")
  103. def wiredGetStyle(n):
  104. ''' Check connection status of wired and set the style accordingly. '''
  105. if not isWired(n):
  106. return False
  107. if not n.inputs():
  108. wiredStyle(n,1)
  109. elif not isAnchor(n.input(0)):
  110. wiredStyle(n,1)
  111. elif n["anchor"].value() != n.input(0).name():
  112. wiredStyle(n,1)
  113. else:
  114. wiredStyle(n,0)
  115. def wiredTagsAndBackdrops(n, updateSimilar=False):
  116. try:
  117. a = n.input(0)
  118. if not a:
  119. return
  120. a_tags = a["tags"].value().strip().strip(",")
  121. a_bd = backdropTags(a)
  122. an = n.knob("anchor").value()
  123. if updateSimilar:
  124. ns = [i for i in allWireds() if i.knob("anchor").value() == an]
  125. else:
  126. ns = [n]
  127. for n in ns:
  128. try:
  129. tags_knob = n.knob("tags")
  130. bd_knob = n.knob("backdrops")
  131. [i.setVisible(False) for i in [tags_knob, bd_knob]]
  132. if a_tags:
  133. tags_knob.setValue("<i>{}</i>".format(a_tags))
  134. tags_knob.setVisible(True)
  135. if len(a_bd) and a_bd != []:
  136. bd_knob.setValue("<i>{}</i>".format(",".join(a_bd)))
  137. bd_knob.setVisible(True)
  138. except:
  139. pass
  140. except:
  141. try:
  142. [i.setVisible(False) for i in [tags_knob, bd_knob]]
  143. except:
  144. pass
  145. def wiredKnobChanged():
  146. global Stamps_LockCallbacks
  147. k = nuke.thisKnob()
  148. kn = k.name()
  149. if kn in ["xpos","ypos","reconnect_by_selection_this","reconnect_by_selection_similar"]:
  150. return
  151. n = nuke.thisNode()
  152. if Stamps_LockCallbacks == True:
  153. return
  154. ni = n.inputs()
  155. if n.knob("toReconnect") and n.knob("toReconnect").value() and nuke.GUI:
  156. if not ni:
  157. if n.knob("auto_reconnect_by_title") and n.knob("auto_reconnect_by_title").value() and n.knob("title"):
  158. n.knob("auto_reconnect_by_title").setValue(0)
  159. for a in allAnchors():
  160. if a.knob("title") and a["title"].value() == n["title"].value():
  161. n.knob("auto_reconnect_by_title").setValue(False)
  162. nuke.thisNode().setInput(0,a)
  163. n["anchor"].setValue(a.name())
  164. wiredStyle(n)
  165. return
  166. try:
  167. inp = n.knob("anchor").value()
  168. a = nuke.toNode(inp)
  169. if a.knob("title") and n.knob("title") and a["title"].value() == n["title"].value():
  170. nuke.thisNode().setInput(0,a)
  171. wiredStyle(n)
  172. else:
  173. wiredStyle(n,1)
  174. except:
  175. wiredGetStyle(n)
  176. else:
  177. try:
  178. a = n.input(0)
  179. if isAnchor(a):
  180. if a.knob("title") and n.knob("title") and a["title"].value() == n["title"].value():
  181. n.knob("anchor").setValue(a.name())
  182. else:
  183. inp = n.knob("anchor").value()
  184. a = nuke.toNode(inp)
  185. if a.knob("title") and n.knob("title") and a["title"].value() == n["title"].value():
  186. nuke.thisNode().setInput(0,a)
  187. else:
  188. wiredStyle(n,1)
  189. n.setInput(0,None)
  190. except:
  191. pass
  192. n.knob("toReconnect").setValue(False)
  193. elif not ni:
  194. if nodeType(n)=="Particle" and not nuke.env["nukex"]:
  195. return
  196. wiredStyle(n,1)
  197. return
  198. elif kn == "selected": #First time it's this knob, it will activate the first if, then, ignore.
  199. return
  200. elif kn == "inputChange":
  201. wiredGetStyle(n)
  202. elif kn == "title":
  203. kv = k.value()
  204. if titleIsLegal(kv):
  205. if nuke.ask("Do you want to update the linked stamps' title?"):
  206. a = retitleAnchor(n) # Retitle anchor
  207. retitleWired(a) # Retitle wired stamps of anchor a
  208. return
  209. else:
  210. nuke.message("Please set a valid title.")
  211. try:
  212. n["title"].setValue(n["prev_title"].value())
  213. except:
  214. pass
  215. else:
  216. try:
  217. n.knob("toReconnect").setValue(False)
  218. if ni:
  219. if isAnchor(n.input(0)):
  220. if n.knob("title").value() == n.input(0).knob("title").value():
  221. n.knob("anchor").setValue(n.input(0).name())
  222. elif nuke.ask("Do you want to change the anchor for the current stamp?"):
  223. n.knob("anchor").setValue(n.input(0).name())
  224. n.knob("title").setValue(n.input(0).knob("title").value())
  225. n.knob("prev_title").setValue(n.input(0).knob("title").value())
  226. else:
  227. n.setInput(0,None)
  228. try:
  229. n.setInput(0,nuke.toNode(n.knob("anchor").value()))
  230. except:
  231. pass
  232. wiredGetStyle(n)
  233. except:
  234. pass
  235. if kn == "showPanel":
  236. wiredTagsAndBackdrops(n)
  237. def wiredOnCreate():
  238. n = nuke.thisNode()
  239. n.knob("toReconnect").setValue(1)
  240. for k in n.allKnobs():
  241. if k.name() not in ['wired_tab','identifier','lockCallbacks','toReconnect','title','prev_title','tags','backdrops','anchor','line1','anchor_label','show_anchor','zoom_anchor','stamps_label','zoomNext','selectSimilar','space_1','reconnect_label','reconnect_this','reconnect_similar','reconnect_all','space_2','advanced_reconnection','reconnect_by_title_label','reconnect_by_title_this','reconnect_by_title_similar', 'reconnect_by_title_selected', 'reconnect_by_selection_label','reconnect_by_selection_this','reconnect_by_selection_similar','reconnect_by_selection_selected','auto_reconnect_by_title','advanced_reconnection','line2','buttonHelp','version']:
  242. k.setFlag(0x0000000000000400)
  243. def anchorKnobChanged():
  244. k = nuke.thisKnob()
  245. kn = k.name()
  246. if kn in ["xpos","ypos"]:
  247. return
  248. n = nuke.thisNode()
  249. if kn == "title":
  250. kv = k.value()
  251. if titleIsLegal(kv):
  252. if nuke.ask("Do you want to update the linked stamps' title?"):
  253. retitleWired(n) # Retitle wired stamps of anchor a
  254. return
  255. else:
  256. nuke.message("Please set a valid title.")
  257. try:
  258. n["title"].setValue(n["prev_title"].value())
  259. except:
  260. pass
  261. elif kn == "name":
  262. try:
  263. nn = anchor["prev_name"].value()
  264. except:
  265. nn = anchor.name()
  266. children = anchorWireds(n)
  267. for i in children:
  268. i.knob("anchor").setValue(nn)
  269. anchor["prev_name"].setValue(anchor.name())
  270. elif kn == "tags":
  271. for ni in allWireds():
  272. if ni.knob("anchor").value() == n.name():
  273. wiredTagsAndBackdrops(ni, updateSimilar=True)
  274. return
  275. def anchorOnCreate():
  276. n = nuke.thisNode()
  277. for k in n.allKnobs():
  278. if k.name() not in ['anchor_tab','identifier','title','prev_title','prev_name','showing','tags','stamps_label','selectStamps','reconnectStamps','zoomNext','createStamp','buttonHelp','line1','line2','version']:
  279. k.setFlag(0x0000000000000400)
  280. try:
  281. nn = n["prev_name"].value()
  282. except:
  283. nn = n.name()
  284. children = anchorWireds(n)
  285. for i in children:
  286. i.knob("anchor").setValue(nn)
  287. n["prev_name"].setValue(n.name())
  288. return
  289. def retitleAnchor(ref = ""):
  290. '''
  291. Retitle Anchor of current wired stamp to match its title.
  292. returns: anchor node
  293. '''
  294. if ref == "":
  295. ref = nuke.thisNode()
  296. try:
  297. ref_title = ref["title"].value()
  298. if ref_title.strip() != "":
  299. ref_title = ref_title.strip()
  300. ref_anchor = ref["anchor"].value()
  301. na = nuke.toNode(ref_anchor)
  302. for kn in ["title","prev_title"]:
  303. na[kn].setValue(ref_title)
  304. ref["prev_title"].setValue(ref_title)
  305. return na
  306. except:
  307. return None
  308. def retitleWired(anchor = ""):
  309. '''
  310. Retitle wired stamps connected to supplied anchor
  311. '''
  312. if anchor == "":
  313. return
  314. try:
  315. anchor_title = anchor["title"].value()
  316. anchor_name = anchor.name()
  317. for nw in allWireds():
  318. if nw["anchor"].value() == anchor_name:
  319. nw["title"].setValue(anchor_title)
  320. nw["prev_title"].setValue(anchor_title)
  321. return True
  322. except:
  323. pass
  324. def wiredSelectSimilar(anchor_name = ""):
  325. if anchor_name=="":
  326. anchor_name = nuke.thisNode().knob("anchor").value()
  327. for i in allWireds():
  328. if i.knob("anchor").value() == anchor_name:
  329. i.setSelected(True)
  330. def wiredReconnect(n=""):
  331. succeeded=True
  332. if n=="":
  333. n = nuke.thisNode()
  334. try:
  335. anchor = nuke.toNode(n.knob("anchor").value())
  336. if not anchor:
  337. succeeded = False
  338. n.setInput(0,anchor)
  339. except:
  340. succeeded = False
  341. try:
  342. wiredGetStyle(n)
  343. except:
  344. pass
  345. return succeeded
  346. def wiredReconnectSimilar(anchor_name = ""):
  347. if anchor_name=="":
  348. anchor_name = nuke.thisNode().knob("anchor").value()
  349. for i in nuke.allNodes():
  350. if isWired(i) and i.knob("anchor").value() == anchor_name:
  351. reconnectErrors = 0
  352. try:
  353. i.knob("reconnect_this").execute()
  354. except:
  355. reconnectErrors += 1
  356. finally:
  357. if reconnectErrors > 0:
  358. nuke.message("Couldn't reconnect {} nodes".format(str(reconnectErrors)))
  359. wiredGetStyle(i)
  360. def wiredReconnectAll():
  361. for i in nuke.allNodes():
  362. if isWired(i):
  363. reconnectErrors = 0
  364. try:
  365. i.knob("reconnect_this").execute()
  366. except:
  367. reconnectErrors += 1
  368. finally:
  369. if reconnectErrors > 0:
  370. nuke.message("Couldn't reconnect {} nodes".format(str(reconnectErrors)))
  371. def wiredReconnectByTitle(title=""):
  372. #1. Find matching nodes.
  373. n = nuke.thisNode()
  374. if title=="":
  375. title = n.knob("title").value()
  376. matches = []
  377. for i in nuke.allNodes():
  378. if isAnchor(i) and i.knob("title").value() == title:
  379. matches.append(i)
  380. #2. Do what's necessary
  381. num_matches = len(matches)
  382. if num_matches == 1: # One match -> Connect
  383. anchor = matches[0]
  384. n["anchor"].setValue(anchor.name())
  385. n.setInput(0,anchor)
  386. elif num_matches > 1:
  387. # More than one match...
  388. ns = nuke.selectedNodes()
  389. if ns and len(ns) == 1 and isAnchor(ns[0]):
  390. # Exactly one anchor selected and title matches -> connect
  391. if ns[0].knob("title").value() == title:
  392. n["anchor"].setValue(ns[0].name())
  393. n.setInput(0,ns[0])
  394. n.knob("reconnect_this").execute()
  395. else:
  396. # Selection not matching -> Message asking to select a specific anchor.
  397. nuke.message("More than one Anchor Stamp found with the same title. Please select the one you like in the Node Graph and click this button again.")
  398. elif num_matches == 0:
  399. nuke.message("No Anchor Stamps with title '{}' found in the script.".format(title))
  400. def wiredReconnectByTitleSimilar(title=""):
  401. #1. Find matching anchors.
  402. n = nuke.thisNode()
  403. if title=="":
  404. title = n.knob("title").value()
  405. matches = []
  406. for i in allAnchors():
  407. if i.knob("title").value() == title:
  408. matches.append(i)
  409. #2. Do what's necessary
  410. num_matches = len(matches)
  411. if num_matches == 0:
  412. # No matches -> abort
  413. nuke.message("No Anchor Stamps with title '{}' found in the script.")
  414. return
  415. anchor_name = n.knob("anchor").value()
  416. siblings = [i for i in nuke.allNodes() if isWired(i) and i.knob("anchor").value() == anchor_name]
  417. if num_matches == 1: # One match -> Connect
  418. anchor = matches[0]
  419. for s in siblings:
  420. s["anchor"].setValue(anchor.name())
  421. s.setInput(0,anchor)
  422. wiredStyle(s,0)
  423. s.knob("reconnect_this").execute()
  424. elif num_matches > 1:
  425. # More than one match...
  426. ns = nuke.selectedNodes()
  427. if ns and len(ns) == 1 and isAnchor(ns[0]):
  428. # Exactly one anchor selected and title matches -> connect
  429. if ns[0].knob("title").value() == title:
  430. for s in siblings:
  431. s["anchor"].setValue(ns[0].name())
  432. s.setInput(0,ns[0])
  433. wiredStyle(s,0)
  434. s.knob("reconnect_this").execute()
  435. else:
  436. # Selection not matching -> Message asking to select a specific anchor.
  437. nuke.message("More than one Anchor Stamp found with the same title. Please select the one you like in the Node Graph and click this button again.")
  438. def wiredReconnectByTitleSelected():
  439. #1. Find matching anchors.
  440. ns = nuke.selectedNodes()
  441. ns = [i for i in ns if isWired(i)]
  442. for n in ns:
  443. title = n.knob("title").value()
  444. matches = []
  445. for i in allAnchors():
  446. if i.knob("title").value() == title:
  447. matches.append(i)
  448. #2. Do what's necessary only for the one match cases
  449. anchor_name = n.knob("anchor").value()
  450. if len(matches) == 1: # One match -> Connect
  451. anchor = matches[0]
  452. n["anchor"].setValue(anchor.name())
  453. n.setInput(0,anchor)
  454. wiredStyle(n,0)
  455. n.knob("reconnect_this").execute()
  456. def wiredReconnectBySelection():
  457. global Stamps_LockCallbacks
  458. n = nuke.thisNode()
  459. ns = nuke.selectedNodes()
  460. if not len(ns):
  461. nuke.message("Please select an Anchor Stamp first.")
  462. elif len(ns)>1:
  463. nuke.message("Multiple nodes selected, please select only one Anchor Stamp.")
  464. else:
  465. if not isAnchor(ns[0]):
  466. nuke.message("Please select an Anchor Stamp.")
  467. else:
  468. Stamps_LockCallbacks = True
  469. n["anchor"].setValue(ns[0].name())
  470. n["title"].setValue(ns[0]["title"].value())
  471. n.setInput(0,ns[0])
  472. wiredGetStyle(n)
  473. Stamps_LockCallbacks = False
  474. n.knob("reconnect_this").execute()
  475. def wiredReconnectBySelectionSimilar():
  476. global Stamps_LockCallbacks
  477. n = nuke.thisNode()
  478. ns = nuke.selectedNodes()
  479. if not len(ns):
  480. nuke.message("Please select an Anchor Stamp first.")
  481. elif len(ns)>1:
  482. nuke.message("Multiple nodes selected, please select only one Anchor Stamp.")
  483. elif not isAnchor(ns[0]):
  484. nuke.message("Please select an Anchor Stamp.")
  485. else:
  486. anchor_name = n.knob("anchor").value()
  487. siblings = [i for i in nuke.allNodes() if isWired(i) and i.knob("anchor").value() == anchor_name]
  488. for s in siblings:
  489. Stamps_LockCallbacks = True
  490. s["anchor"].setValue(ns[0].name())
  491. s["title"].setValue(ns[0]["title"].value())
  492. s.setInput(0,ns[0])
  493. wiredStyle(s,0)
  494. Stamps_LockCallbacks = False
  495. s.knob("reconnect_this").execute()
  496. def wiredReconnectBySelectionSelected():
  497. global Stamps_LockCallbacks
  498. n = nuke.thisNode()
  499. ns = nuke.selectedNodes()
  500. if not len(ns):
  501. nuke.message("Please select one Anchor plus one or more Stamps first.")
  502. return
  503. anchors = []
  504. stamps = []
  505. for i in ns:
  506. if isAnchor(i):
  507. anchors.append(i)
  508. if isWired(i):
  509. stamps.append(i)
  510. if len(anchors) != 1:
  511. nuke.message("Please select one Anchor, plus one or more Stamps.")
  512. return
  513. else:
  514. anchor = anchors[0]
  515. if not len(stamps):
  516. nuke.message("Please, also select one or more Stamps that you want to reconnect to the selected Anchor.")
  517. for s in stamps:
  518. Stamps_LockCallbacks = True
  519. s["anchor"].setValue(anchor.name())
  520. s["title"].setValue(anchor["title"].value())
  521. s.setInput(0,anchor)
  522. wiredStyle(s,0)
  523. Stamps_LockCallbacks = False
  524. s.knob("reconnect_this").execute()
  525. def anchorReconnectWired(anchor = ""):
  526. if anchor=="":
  527. anchor = nuke.thisNode()
  528. anchor_name = anchor.name()
  529. for i in allWireds():
  530. if i.knob("anchor").value() == anchor_name:
  531. reconnectErrors = 0
  532. try:
  533. i.setInput(0,anchor)
  534. except:
  535. reconnectErrors += 1
  536. finally:
  537. if reconnectErrors > 0:
  538. nuke.message("Couldn't reconnect {} nodes".format(str(reconnectErrors)))
  539. def wiredZoomNext(anchor_name = ""):
  540. if anchor_name=="":
  541. anchor_name = nuke.thisNode().knob("anchor").value()
  542. anchor = nuke.toNode(anchor_name)
  543. showing_knob = anchor.knob("showing")
  544. showing_value = showing_knob.value()
  545. i = 0
  546. for ni in allWireds():
  547. if ni.knob("anchor").value() == anchor_name:
  548. if i == showing_value:
  549. nuke.zoom(1.5,[ni.xpos()+ni.screenWidth()/2,ni.ypos()+ni.screenHeight()/2])
  550. showing_knob.setValue(i+1)
  551. return
  552. i+=1
  553. showing_knob.setValue(0)
  554. nuke.message("Couldn't find any more similar wired stamps.")
  555. def anchorSelectWireds(anchor = ""):
  556. if anchor == "":
  557. try:
  558. anchor = nuke.selectedNode()
  559. except:
  560. pass
  561. if isAnchor(anchor):
  562. anchor.setSelected(False)
  563. wiredSelectSimilar(anchor.name())
  564. def anchorWireds(anchor = ""):
  565. ''' Returns a list of the children stamps to the anchor with the specified name '''
  566. if anchor == "":
  567. try:
  568. anchor = nuke.selectedNode()
  569. except:
  570. pass
  571. if isAnchor(anchor):
  572. try:
  573. nn = anchor["prev_name"].value()
  574. except:
  575. nn = anchor.name()
  576. children = [i for i in allWireds() if i.knob("anchor").value() == nn]
  577. return children
  578. wiredOnCreate_code = """if nuke.GUI:
  579. try:
  580. import stamps; stamps.wiredOnCreate()
  581. except:
  582. pass
  583. """
  584. wiredReconnectToTitle_code = """n = nuke.thisNode()
  585. try:
  586. nt = n.knob("title").value()
  587. for a in nuke.allNodes():
  588. if a.knob("identifier").value() == "anchor" and a.knob("title").value() == nt:
  589. n.setInput(0,a)
  590. break
  591. except:
  592. nuke.message("Unable to reconnect.")
  593. """
  594. wiredReconnect_code = """n = nuke.thisNode()
  595. try:
  596. n.setInput(0,nuke.toNode(n.knob("anchor").value()))
  597. except:
  598. nuke.message("Unable to reconnect.")
  599. try:
  600. import stamps
  601. stamps.wiredGetStyle(n)
  602. except:
  603. pass
  604. """
  605. #################################
  606. ### STAMP, ANCHOR, WIRED
  607. #################################
  608. def anchor(title = "", tags = "", input_node = "", node_type = "2D"):
  609. ''' Anchor Stamp '''
  610. try:
  611. n = nuke.createNode(AnchorClassesAlt[node_type])
  612. except:
  613. try:
  614. n = nuke.createNode(StampClasses[node_type])
  615. except:
  616. n = nuke.createNode("NoOp")
  617. name = getAvailableName("Anchor",rand=True)
  618. n["name"].setValue(name)
  619. # Set default knob values
  620. for i,j in anchor_defaults.items():
  621. try:
  622. n.knob(i).setValue(j)
  623. except:
  624. pass
  625. if node_type in AnchorClassColors:
  626. try:
  627. n["tile_color"].setValue(AnchorClassColors[node_type])
  628. except:
  629. pass
  630. for k in n.allKnobs():
  631. k.setFlag(0x0000000000000400)
  632. # Main knobs
  633. anchorTab_knob = nuke.Tab_Knob('anchor_tab','Anchor Stamp')
  634. identifier_knob = nuke.Text_Knob('identifier','identifier', 'anchor')
  635. identifier_knob.setVisible(False)
  636. title_knob = nuke.String_Knob('title','Title:', title)
  637. title_knob.setTooltip("Displayed name on the Node Graph for this Stamp and its Anchor.\nIMPORTANT: This is only for display purposes, and is different from the real/internal name of the Stamps.")
  638. prev_title_knob = nuke.Text_Knob('prev_title','', title)
  639. prev_title_knob.setVisible(False)
  640. prev_name_knob = nuke.Text_Knob('prev_name','', name)
  641. prev_name_knob.setVisible(False)
  642. showing_knob = nuke.Int_Knob('showing','', 0)
  643. showing_knob.setVisible(False)
  644. tags_knob = nuke.String_Knob('tags','Tags', tags)
  645. tags_knob.setTooltip("Comma-separated tags you can define for each Anchor, that will help you find it when invoking the Stamp Selector by pressing the Stamps shortkey with nothing selected.")
  646. for k in [anchorTab_knob, identifier_knob, title_knob, prev_title_knob, prev_name_knob, showing_knob, tags_knob]:
  647. n.addKnob(k)
  648. n.addKnob(nuke.Text_Knob("line1", "", "")) # Line
  649. stampsLabel_knob = nuke.Text_Knob('stamps_label','Stamps:', " ")
  650. stampsLabel_knob.setFlag(nuke.STARTLINE)
  651. # Buttons
  652. buttonSelectStamps = nuke.PyScript_Knob("selectStamps","select","stamps.wiredSelectSimilar(nuke.thisNode().name())")
  653. buttonSelectStamps.setTooltip("Select all of this Anchor's Stamps.")
  654. buttonReconnectStamps = nuke.PyScript_Knob("reconnectStamps","reconnect","stamps.anchorReconnectWired()")
  655. buttonSelectStamps.setTooltip("Reconnect all of this Anchor's Stamps.")
  656. buttonZoomNext = nuke.PyScript_Knob("zoomNext","zoom next","stamps.wiredZoomNext(nuke.thisNode().name())")
  657. buttonZoomNext.setTooltip("Navigate to this Anchor's next Stamp on the Node Graph.")
  658. buttonCreateStamp = nuke.PyScript_Knob("createStamp","new","stamps.stampCreateWired(nuke.thisNode())")
  659. buttonCreateStamp.setTooltip("Create a new Stamp for this Anchor.")
  660. for k in [stampsLabel_knob, buttonCreateStamp, buttonSelectStamps, buttonReconnectStamps, buttonZoomNext]:
  661. n.addKnob(k)
  662. # Version (for future update checks)
  663. n.addKnob(nuke.Text_Knob("line2", "", "")) # Line
  664. buttonHelp = nuke.PyScript_Knob("buttonHelp","Help","stamps.showHelp()")
  665. version_knob = nuke.Text_Knob('version',' ','<a href="http://www.nukepedia.com/gizmos/other/stamps" style="color:#666;text-decoration: none;"><span style="color:#666"> <big>Stamps {}</big></b></a>'.format(version))
  666. version_knob.setTooltip(VERSION_TOOLTIP)
  667. version_knob.clearFlag(nuke.STARTLINE)
  668. for k in [buttonHelp, version_knob]:
  669. n.addKnob(k)
  670. n["help"].setValue(STAMPS_HELP)
  671. return n
  672. def wired(anchor):
  673. ''' Wired Stamp '''
  674. global Stamps_LastCreated
  675. Stamps_LastCreated = anchor.name()
  676. node_type = nodeType(realInput(anchor))
  677. try:
  678. n = nuke.createNode(StampClassesAlt[node_type])
  679. except:
  680. try:
  681. n = nuke.createNode(StampClasses[node_type])
  682. except:
  683. n = nuke.createNode("NoOp")
  684. n["name"].setValue(getAvailableName("Stamp"))
  685. # Set default knob values
  686. for i,j in wired_defaults.items():
  687. try:
  688. n[i].setValue(j)
  689. except:
  690. pass
  691. for k in n.allKnobs():
  692. k.setFlag(0x0000000000000400)
  693. if node_type in WiredClassColors:
  694. n["tile_color"].setValue(WiredClassColors[node_type])
  695. n["onCreate"].setValue(wiredOnCreate_code)
  696. # Inner functionality knobs
  697. wiredTab_knob = nuke.Tab_Knob('wired_tab','Wired Stamp')
  698. identifier_knob = nuke.Text_Knob('identifier','identifier', 'wired')
  699. identifier_knob.setVisible(False)
  700. lock_knob = nuke.Int_Knob('lockCallbacks','',0) #Lock callbacks...
  701. lock_knob.setVisible(False)
  702. toReconnect_knob = nuke.Boolean_Knob("toReconnect")
  703. toReconnect_knob.setVisible(False)
  704. title_knob = nuke.String_Knob('title','Title:', anchor["title"].value())
  705. title_knob.setTooltip("Displayed name on the Node Graph for this Stamp and its Anchor.\nIMPORTANT: This is only for display purposes, and is different from the real/internal name of the Stamps.")
  706. prev_title_knob = nuke.Text_Knob('prev_title','', anchor["title"].value())
  707. prev_title_knob.setVisible(False)
  708. tags_knob = nuke.Text_Knob('tags','Tags:', " ")
  709. tags_knob.setTooltip("Tags of this stamp's Anchor, for information purpose only.\nClick \"show anchor\" to change them.")
  710. backdrops_knob = nuke.Text_Knob('backdrops','Backdrops:', " ")
  711. backdrops_knob.setTooltip("Labels of backdrop nodes which contain this stamp's Anchor.")
  712. anchor_knob = nuke.String_Knob('anchor','Anchor', anchor.name()) # This goes in the advanced part
  713. for k in [wiredTab_knob, identifier_knob, lock_knob, toReconnect_knob, title_knob, prev_title_knob, tags_knob, backdrops_knob]:
  714. n.addKnob(k)
  715. wiredTab_knob.setFlag(0) # Open the tab
  716. n.addKnob(nuke.Text_Knob("line1", "", "")) # Line
  717. ### Buttons
  718. # Anchor
  719. anchorLabel_knob = nuke.Text_Knob('anchor_label','Anchor:', " ")
  720. anchorLabel_knob.setFlag(nuke.STARTLINE)
  721. buttonShowAnchor = nuke.PyScript_Knob("show_anchor"," show anchor ","stamps.wiredShowAnchor()")
  722. buttonShowAnchor.setTooltip("Show the properties panel for this Stamp's Anchor.")
  723. buttonShowAnchor.clearFlag(nuke.STARTLINE)
  724. buttonZoomAnchor = nuke.PyScript_Knob("zoom_anchor","zoom anchor","stamps.wiredZoomAnchor()")
  725. buttonZoomAnchor.setTooltip("Navigate to this Stamp's Anchor on the Node Graph.")
  726. for k in [anchorLabel_knob, buttonShowAnchor, buttonZoomAnchor]:
  727. n.addKnob(k)
  728. # Stamps
  729. stampsLabel_knob = nuke.Text_Knob('stamps_label','Stamps:', " ")
  730. stampsLabel_knob.setFlag(nuke.STARTLINE)
  731. buttonZoomNext = nuke.PyScript_Knob("zoomNext"," zoom next ","stamps.wiredZoomNext()")
  732. buttonZoomNext.setTooltip("Navigate to this Stamp's next sibling on the Node Graph.")
  733. buttonZoomNext.clearFlag(nuke.STARTLINE)
  734. buttonSelectSimilar = nuke.PyScript_Knob("selectSimilar"," select similar ","stamps.wiredSelectSimilar()")
  735. buttonSelectSimilar.clearFlag(nuke.STARTLINE)
  736. buttonSelectSimilar.setTooltip("Select all similar Stamps to this one on the Node Graph.")
  737. space_1_knob = nuke.Text_Knob("space_1", "", " ")
  738. space_1_knob.setFlag(nuke.STARTLINE)
  739. for k in [stampsLabel_knob, buttonZoomNext, buttonSelectSimilar, space_1_knob]:
  740. n.addKnob(k)
  741. # Reconnect Simple
  742. reconnectLabel_knob = nuke.Text_Knob('reconnect_label','Reconnect:', " ")
  743. reconnectLabel_knob.setTooltip("Reconnect by the stored Anchor name.")
  744. reconnectLabel_knob.setFlag(nuke.STARTLINE)
  745. buttonReconnectThis = nuke.PyScript_Knob("reconnect_this","this",wiredReconnect_code)
  746. buttonReconnectThis.setTooltip("Reconnect this Stamp to its Anchor, by its stored Anchor name.")
  747. buttonReconnectSimilar = nuke.PyScript_Knob("reconnect_similar","similar","stamps.wiredReconnectSimilar()")
  748. buttonReconnectSimilar.setTooltip("Reconnect this Stamp and similar ones to their Anchor, by their stored anchor name.")
  749. buttonReconnectAll = nuke.PyScript_Knob("reconnect_all","all","stamps.wiredReconnectAll()")
  750. buttonReconnectAll.setTooltip("Reconnect all the Stamps to their Anchors, by their stored anchor names.")
  751. space_2_knob = nuke.Text_Knob("space_2", "", " ")
  752. space_2_knob.setFlag(nuke.STARTLINE)
  753. for k in [reconnectLabel_knob, buttonReconnectThis, buttonReconnectSimilar, buttonReconnectAll, space_2_knob]:
  754. n.addKnob(k)
  755. # Advanced Reconnection
  756. advancedReconnection_knob = nuke.Tab_Knob('advanced_reconnection', 'Advanced Reconnection', nuke.TABBEGINCLOSEDGROUP)
  757. n.addKnob(advancedReconnection_knob)
  758. reconnectByTitleLabel_knob = nuke.Text_Knob('reconnect_by_title_label','<font color=gold>By Title:', " ")
  759. reconnectByTitleLabel_knob.setFlag(nuke.STARTLINE)
  760. reconnectByTitleLabel_knob.setTooltip("Reconnect by searching for a matching title.")
  761. buttonReconnectByTitleThis = nuke.PyScript_Knob("reconnect_by_title_this","this","stamps.wiredReconnectByTitle()")
  762. buttonReconnectByTitleThis.setTooltip("Look for an Anchor that shares this Stamp's title, and connect this Stamp to it.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  763. buttonReconnectByTitleSimilar = nuke.PyScript_Knob("reconnect_by_title_similar","similar","stamps.wiredReconnectByTitleSimilar()")
  764. buttonReconnectByTitleSimilar.setTooltip("Look for an Anchor that shares this Stamp's title, and connect this Stamp and similar ones to it.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  765. buttonReconnectByTitleSelected = nuke.PyScript_Knob("reconnect_by_title_selected","selected","stamps.wiredReconnectByTitleSelected()")
  766. buttonReconnectByTitleSelected.setTooltip("For each Stamp selected, look for an Anchor that shares its title, and connect to it.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  767. reconnectBySelectionLabel_knob = nuke.Text_Knob('reconnect_by_selection_label','<font color=orangered>By Selection:', " ")
  768. reconnectBySelectionLabel_knob.setFlag(nuke.STARTLINE)
  769. reconnectBySelectionLabel_knob.setTooltip("Force reconnect to a selected Anchor.")
  770. buttonReconnectBySelectionThis = nuke.PyScript_Knob("reconnect_by_selection_this","this","stamps.wiredReconnectBySelection()")
  771. buttonReconnectBySelectionThis.setTooltip("Force reconnect this Stamp to a selected Anchor, whatever its name or title.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  772. buttonReconnectBySelectionSimilar = nuke.PyScript_Knob("reconnect_by_selection_similar","similar","stamps.wiredReconnectBySelectionSimilar()")
  773. buttonReconnectBySelectionSimilar.setTooltip("Force reconnect this Stamp and similar ones to a selected Anchor, whatever its name or title.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  774. buttonReconnectBySelectionSelected = nuke.PyScript_Knob("reconnect_by_selection_selected","selected","stamps.wiredReconnectBySelectionSelected()")
  775. buttonReconnectBySelectionSelected.setTooltip("Force reconnect all selected Stamps to an also selected Anchor, whatever its name or title.\nIMPORTANT: Use this carefully, and only when the normal reconnection doesn't work.")
  776. checkboxReconnectByTitleOnCreation = nuke.Boolean_Knob("auto_reconnect_by_title","<font color=#ED9977>&nbsp; auto-reconnect by title")
  777. checkboxReconnectByTitleOnCreation.setTooltip("When creating this stamp again (like on copy-paste), auto-reconnect it by title instead of doing it by the saved anchor's name, and auto-turn this off immediately.\nIMPORTANT: Should be off by default. Only use this for setting up templates.")
  778. checkboxReconnectByTitleOnCreation.setFlag(nuke.STARTLINE)
  779. advancedReconnection_knob = nuke.Tab_Knob('advanced_reconnection', 'Advanced Reconnection', -1)
  780. for k in [reconnectByTitleLabel_knob, buttonReconnectByTitleThis, buttonReconnectByTitleSimilar, buttonReconnectByTitleSelected,
  781. reconnectBySelectionLabel_knob, buttonReconnectBySelectionThis, buttonReconnectBySelectionSimilar, buttonReconnectBySelectionSelected,
  782. anchor_knob, checkboxReconnectByTitleOnCreation,advancedReconnection_knob]:
  783. n.addKnob(k)
  784. # Version (for future update checks)
  785. line_knob = nuke.Text_Knob("line2", "", "")
  786. buttonHelp = nuke.PyScript_Knob("buttonHelp","Help","stamps.showHelp()")
  787. version_knob = nuke.Text_Knob('version',' ','<a href="http://www.nukepedia.com/gizmos/other/stamps" style="color:#666;text-decoration: none;"><span style="color:#666"> <big>Stamps {}</big></b></a>'.format(version))
  788. version_knob.clearFlag(nuke.STARTLINE)
  789. version_knob.setTooltip(VERSION_TOOLTIP)
  790. for k in [line_knob, buttonHelp, version_knob]:
  791. n.addKnob(k)
  792. # Hide input while not messing position
  793. x,y = n.xpos(),n.ypos()
  794. nw = n.screenWidth()
  795. aw = anchor.screenWidth()
  796. n.setInput(0,anchor)
  797. n["hide_input"].setValue(True)
  798. n["xpos"].setValue(x-nw/2+aw/2)
  799. n["ypos"].setValue(y)
  800. n["help"].setValue(STAMPS_HELP)
  801. wiredTagsAndBackdrops(n)
  802. return n
  803. Stamps_LastCreated = anchor.name()
  804. def getAvailableName(name = "Untitled", rand=False):
  805. ''' Returns a node name starting with @name followed by a number, that doesn't exist already '''
  806. import random
  807. i = 1
  808. while True:
  809. if not rand:
  810. available_name = name+str(i)
  811. else:
  812. available_name = name+str('_%09x' % random.randrange(9**12))
  813. if not nuke.exists(available_name):
  814. return available_name
  815. i += 1
  816. #################################
  817. ### CLASSES
  818. #################################
  819. class AnchorSelector(QtWidgets.QDialog):
  820. '''
  821. Panel to select an anchor, showing the different anchors on dropdowns based on their tags.
  822. '''
  823. # TODO LATER:
  824. # - Have three columns, distinguished, like an asset loader. Maybe even with border color?
  825. # - Button to activate the "Multiple" mode (off by default) which will not close it on clicking "OK"
  826. # - Ability to show and hide backdrops (would turn off their visibility or "bookmark")
  827. def __init__(self):
  828. super(AnchorSelector, self).__init__()
  829. self.setWindowTitle("Stamps: Select an Anchor.")
  830. self.initUI()
  831. #self.setFixedSize(self.sizeHint())
  832. #self.setFixedWidth(self.sizeHint()[0])
  833. self.custom_anchors_lineEdit.setFocus()
  834. def initUI(self):
  835. # Find all anchors and get all tags
  836. self.findAnchorsAndTags() # Generate a dictionary: {"Camera1":["Camera","New","Custom1"],"Read":["2D","New"]}
  837. self.custom_chosen = False # If clicked OK on the custom lineedit
  838. # Header
  839. self.headerTitle = QtWidgets.QLabel("Anchor Stamp Selector")
  840. self.headerTitle.setStyleSheet("font-weight:bold;color:#CCCCCC;font-size:14px;")
  841. self.headerSubtitle = QtWidgets.QLabel("Select an Anchor to make a Stamp for.")
  842. self.headerSubtitle.setStyleSheet("color:#999")
  843. self.headerLine = QtWidgets.QFrame()
  844. self.headerLine.setFrameShape(QtWidgets.QFrame.HLine)
  845. self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  846. self.headerLine.setLineWidth(0)
  847. self.headerLine.setMidLineWidth(1)
  848. self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  849. # Layouts
  850. self.master_layout = QtWidgets.QVBoxLayout()
  851. self.master_layout.addWidget(self.headerTitle)
  852. self.master_layout.addWidget(self.headerSubtitle)
  853. #self.master_layout.addWidget(self.headerLine)
  854. # Main Scroll area
  855. self.scroll_content = QtWidgets.QWidget()
  856. self.scroll_layout = QtWidgets.QVBoxLayout()
  857. self.scroll_content.setLayout(self.scroll_layout)
  858. # Scroll Area Properties
  859. self.scroll = QtWidgets.QScrollArea()
  860. self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
  861. self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  862. self.scroll.setWidgetResizable(True)
  863. self.scroll.setWidget(self.scroll_content)
  864. self.grid = QtWidgets.QGridLayout()
  865. #self.grid.setSpacing(0)
  866. self.lower_grid = QtWidgets.QGridLayout()
  867. self.scroll_layout.addLayout(self.grid)
  868. self.scroll_layout.addStretch()
  869. self.scroll_layout.setContentsMargins(2,2,2,2)
  870. self.grid.setContentsMargins(2,2,2,2)
  871. self.grid.setSpacing(5)
  872. num_tags = len(self._all_tags)
  873. middleLine = QtWidgets.QFrame()
  874. middleLine.setStyleSheet("margin-top:20px")
  875. middleLine.setFrameShape(QtWidgets.QFrame.HLine)
  876. middleLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  877. middleLine.setLineWidth(0)
  878. middleLine.setMidLineWidth(1)
  879. middleLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  880. if len(filter(None,self._all_tags))>0:
  881. tags_label = QtWidgets.QLabel("<i>Tags")
  882. tags_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
  883. tags_label.setStyleSheet("color:#666;margin:0px;padding:0px;padding-left:3px")
  884. #self.grid.addWidget(middleLine,tag_num*10-6,1,2,3)
  885. self.grid.addWidget(tags_label,0,0,1,3)
  886. for tag_num, tag in enumerate(self._all_tags_and_backdrops):
  887. if tag == "":
  888. continue
  889. if tag_num < num_tags:
  890. tag_label = QtWidgets.QLabel("<b>{}</b>:".format(tag))
  891. mode = "tag"
  892. else:
  893. tag_label = QtWidgets.QLabel("{}:".format(tag))
  894. mode = "backdrop"
  895. if tag_num == num_tags:
  896. backdrops_label = QtWidgets.QLabel(" <i> Backdrops")
  897. backdrops_label.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
  898. backdrops_label.setStyleSheet("color:#666;margin:0px;padding:0px;padding-left:3px")
  899. #self.grid.addWidget(middleLine,tag_num*10-5,0,1,3)
  900. self.grid.addWidget(backdrops_label,tag_num*10-3,0,1,1)
  901. tag_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  902. anchors_dropdown = QtWidgets.QComboBox()
  903. for i, cur_name in enumerate(self._all_anchors_names):
  904. cur_title = self._all_anchors_titles[i]
  905. title_repeated = self.titleRepeatedForTag(cur_title, tag, mode)
  906. if mode == "tag":
  907. tag_dict = self._anchors_and_tags_tags
  908. elif mode == "backdrop":
  909. tag_dict = self._anchors_and_tags_backdrops
  910. else:
  911. tag_dict = self._anchors_and_tags
  912. if cur_name not in tag_dict.keys():
  913. continue
  914. if tag in tag_dict[cur_name]:
  915. if title_repeated:
  916. anchors_dropdown.addItem("{0} ({1})".format(cur_title, cur_name), cur_name)
  917. else:
  918. anchors_dropdown.addItem(cur_title, cur_name)
  919. ok_btn = QtWidgets.QPushButton("OK")
  920. ok_btn.clicked.connect(partial(self.okPressed,dropdown=anchors_dropdown))
  921. self.grid.addWidget(tag_label,tag_num*10+1,0)
  922. self.grid.addWidget(anchors_dropdown,tag_num*10+1,1)
  923. self.grid.addWidget(ok_btn,tag_num*10+1,2)
  924. # ALL
  925. tag_num = len(self._all_tags_and_backdrops)
  926. all_tag_label = QtWidgets.QLabel("<b>all</b>: ")
  927. all_tag_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  928. self.all_anchors_dropdown = QtWidgets.QComboBox()
  929. all_tag_texts = [] # List of all texts of the items (usually equals the "title", or "title (name)")
  930. all_tag_names = [i for i in self._all_anchors_names] # List of all real anchor names of the items.
  931. for i, cur_name in enumerate(self._all_anchors_names):
  932. cur_title = self._all_anchors_titles[i]
  933. title_repeated = self._all_anchors_titles.count(cur_title)
  934. if title_repeated > 1:
  935. all_tag_texts.append("{0} ({1})".format(cur_title, cur_name))
  936. else:
  937. all_tag_texts.append(cur_title)
  938. self.all_tag_sorted = sorted(zip(all_tag_texts,all_tag_names), key=lambda pair: pair[0].lower())
  939. for [text, name] in self.all_tag_sorted:
  940. self.all_anchors_dropdown.addItem(text, name)
  941. all_ok_btn = QtWidgets.QPushButton("OK")
  942. all_ok_btn.clicked.connect(partial(self.okPressed,dropdown=self.all_anchors_dropdown))
  943. self.lower_grid.addWidget(all_tag_label,tag_num,0)
  944. self.lower_grid.addWidget(self.all_anchors_dropdown,tag_num,1)
  945. self.lower_grid.addWidget(all_ok_btn,tag_num,2)
  946. tag_num += 1
  947. # POPULAR
  948. tag_num = len(self._all_tags_and_backdrops)+10
  949. popular_tag_label = QtWidgets.QLabel("<b>popular</b>: ")
  950. popular_tag_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  951. self.popular_anchors_dropdown = QtWidgets.QComboBox()
  952. all_tag_texts = [] # List of all texts of the items (usually equals the "title", or "title (name)")
  953. all_tag_names = [i for i in self._all_anchors_names] # List of all real anchor names of the items.
  954. all_tag_count = [stampCount(i) for i in self._all_anchors_names] # Number of stamps for each anchor!
  955. popular_tag_texts = [] # List of popular texts of the items (usually equals the "title (x2)", or "title (name) (x2)")
  956. popular_anchors_names = [x for _,x in sorted(zip(all_tag_count,self._all_anchors_names),reverse=True)]
  957. popular_anchors_titles = [x for _,x in sorted(zip(all_tag_count,self._all_anchors_titles),reverse=True)]
  958. popular_anchors_count = sorted(all_tag_count,reverse=True)
  959. for i, cur_name in enumerate(popular_anchors_names):
  960. cur_title = popular_anchors_titles[i]
  961. title_repeated = popular_anchors_titles.count(cur_title)
  962. if title_repeated > 1:
  963. popular_tag_texts.append("{0} ({1}) (x{2})".format(cur_title, cur_name,str(popular_anchors_count[i])))
  964. else:
  965. popular_tag_texts.append("{0} (x{1})".format(cur_title, str(popular_anchors_count[i])))
  966. for i, text in enumerate(popular_tag_texts):
  967. self.popular_anchors_dropdown.addItem(text, popular_anchors_names[i])
  968. popular_ok_btn = QtWidgets.QPushButton("OK")
  969. popular_ok_btn.clicked.connect(partial(self.okPressed,dropdown=self.popular_anchors_dropdown))
  970. self.lower_grid.addWidget(popular_tag_label,tag_num,0)
  971. self.lower_grid.addWidget(self.popular_anchors_dropdown,tag_num,1)
  972. self.lower_grid.addWidget(popular_ok_btn,tag_num,2)
  973. tag_num += 1
  974. # LineEdit with completer
  975. custom_tag_label = QtWidgets.QLabel("<b>by title</b>: ")
  976. custom_tag_label.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
  977. self.custom_anchors_lineEdit = QtWidgets.QLineEdit()
  978. self.custom_anchors_completer = QtWidgets.QCompleter([i for i,_ in self.all_tag_sorted], self)
  979. self.custom_anchors_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
  980. self.custom_anchors_completer.setCompletionMode(QtWidgets.QCompleter.InlineCompletion)
  981. self.custom_anchors_lineEdit.setCompleter(self.custom_anchors_completer)
  982. global Stamps_LastCreated
  983. if Stamps_LastCreated is not None:
  984. try:
  985. title = nuke.toNode(Stamps_LastCreated)["title"].value()
  986. self.custom_anchors_lineEdit.setPlaceholderText(title)
  987. except:
  988. pass
  989. custom_ok_btn = QtWidgets.QPushButton("OK")
  990. custom_ok_btn.clicked.connect(partial(self.okCustomPressed,dropdown=self.custom_anchors_lineEdit))
  991. self.lower_grid.addWidget(custom_tag_label,tag_num,0)
  992. self.lower_grid.addWidget(self.custom_anchors_lineEdit,tag_num,1)
  993. self.lower_grid.addWidget(custom_ok_btn,tag_num,2)
  994. # Layout shit
  995. self.grid.setColumnStretch(1,1)
  996. if len(filter(None,self._all_tags_and_backdrops)):
  997. self.master_layout.addWidget(self.scroll)
  998. else:
  999. self.master_layout.addWidget(self.headerLine)
  1000. self.master_layout.addLayout(self.lower_grid)
  1001. self.setLayout(self.master_layout)
  1002. self.resize(self.sizeHint().width(),min(self.sizeHint().height()+10,700))
  1003. self.setFixedWidth(self.sizeHint().width()+40)
  1004. def keyPressEvent(self, e):
  1005. selectorType = type(self.focusWidget()).__name__ #QComboBox or QLineEdit
  1006. if e.key() == 16777220:
  1007. if selectorType == "QLineEdit":
  1008. self.okCustomPressed(dropdown=self.focusWidget())
  1009. else:
  1010. self.okPressed(dropdown=self.focusWidget())
  1011. else:
  1012. return QtWidgets.QDialog.keyPressEvent(self, e)
  1013. def findAnchorsAndTags(self):
  1014. # Lets find anchors
  1015. self._all_anchors_titles = []
  1016. self._all_anchors_names = []
  1017. self._all_tags = set()
  1018. self._all_backdrops = set()
  1019. self._backdrop_item_count = {} # Number of anchors per backdrop
  1020. self._all_tags_and_backdrops = set()
  1021. self._anchors_and_tags = {} # Name:tags. Not title.
  1022. self._anchors_and_tags_tags = {} # Name:tags. Not title.
  1023. self._anchors_and_tags_backdrops = {} # Name:tags. Not title.
  1024. for ni in allAnchors():
  1025. try:
  1026. title_value = ni["title"].value()
  1027. name_value = ni.name()
  1028. tags_value = ni["tags"].value()
  1029. tags = re.split(" *, *",tags_value.strip()) # Remove leading/trailing spaces and separate by commas (with or without spaces)
  1030. #backdrop_tags = ["$b$"+x for x in backdropTags(ni)]
  1031. backdrop_tags = backdropTags(ni)
  1032. for t in backdrop_tags:
  1033. if t not in self._backdrop_item_count:
  1034. self._backdrop_item_count[t] = 0
  1035. self._backdrop_item_count[t] += 1
  1036. tags_and_backdrops = list(set(tags + backdrop_tags))
  1037. self._all_anchors_titles.append(title_value.strip())
  1038. self._all_anchors_names.append(name_value)
  1039. self._all_tags.update(tags)
  1040. self._all_backdrops.update(backdrop_tags)
  1041. self._all_tags_and_backdrops.update(tags_and_backdrops)
  1042. self._anchors_and_tags[name_value] = set(tags_and_backdrops)
  1043. self._anchors_and_tags_tags[name_value] = set(tags)
  1044. self._anchors_and_tags_backdrops[name_value] = set(backdrop_tags)
  1045. except:
  1046. pass
  1047. self._all_backdrops = sorted(list(self._all_backdrops), key = lambda x: -self._backdrop_item_count[x])
  1048. self._all_tags = sorted(list(self._all_tags),key=str.lower)
  1049. self._all_tags_and_backdrops = self._all_tags + self._all_backdrops
  1050. #titles_upper = [x.upper() for x in self._all_anchors_titles]
  1051. titles_and_names = zip(self._all_anchors_titles, self._all_anchors_names)
  1052. titles_and_names.sort(key=lambda tup: tup[0].upper())
  1053. self._all_anchors_titles = [x for x, y in titles_and_names]
  1054. self._all_anchors_names = [y for x, y in titles_and_names]
  1055. return self._anchors_and_tags
  1056. def titleRepeatedForTag(self, title, tag, mode=""):
  1057. if self._all_anchors_titles.count(title) <= 1:
  1058. return False
  1059. # Get list of all names that have that tag, and list of related titles
  1060. names_with_tag = []
  1061. titles_with_tag = []
  1062. for i, name in enumerate(self._all_anchors_names):
  1063. if mode == "tag":
  1064. if tag in self._anchors_and_tags_tags[name]:
  1065. names_with_tag.append(name)
  1066. titles_with_tag.append(self._all_anchors_titles[i])
  1067. elif mode == "backdrop":
  1068. if tag in self._anchors_and_tags_backdrops[name]:
  1069. names_with_tag.append(name)
  1070. titles_with_tag.append(self._all_anchors_titles[i])
  1071. else:
  1072. if tag in self._anchors_and_tags[name]:
  1073. names_with_tag.append(name)
  1074. titles_with_tag.append(self._all_anchors_titles[i])
  1075. # Count titles repetition
  1076. title_repetitions = titles_with_tag.count(title)
  1077. return (title_repetitions > 1)
  1078. def okPressed(self, dropdown):
  1079. ''' Runs after an ok button is pressed '''
  1080. dropdown_value = dropdown.currentText()
  1081. dropdown_index = dropdown.currentIndex()
  1082. dropdown_data = dropdown.itemData(dropdown_index)
  1083. try:
  1084. match_anchor = nuke.toNode(dropdown_data)
  1085. except:
  1086. pass
  1087. self.chosen_value = dropdown_value
  1088. self.chosen_anchor_name = dropdown_data
  1089. if match_anchor is not None:
  1090. self.chosen_anchor = match_anchor
  1091. self.accept()
  1092. else:
  1093. nuke.message("There was a problem selecting a valid anchor.")
  1094. def okCustomPressed(self, dropdown):
  1095. ''' Runs after the custom ok button is pressed '''
  1096. global Stamps_LastCreated
  1097. written_value = dropdown.text() # This means it's been written down on the lineEdit
  1098. written_lower = written_value.lower().strip()
  1099. found_data = None
  1100. if written_value == "" and globals().has_key('Stamps_LastCreated'):
  1101. found_data = Stamps_LastCreated
  1102. else:
  1103. for [text, name] in reversed(self.all_tag_sorted):
  1104. if written_lower == text.lower():
  1105. found_data = name
  1106. break
  1107. elif written_lower in text.lower():
  1108. found_data = name
  1109. try:
  1110. match_anchor = nuke.toNode(found_data)
  1111. except:
  1112. nuke.message("Please write a valid name.")
  1113. return
  1114. self.chosen_value = written_value
  1115. self.chosen_anchor_name = found_data
  1116. if match_anchor is not None:
  1117. self.chosen_anchor = match_anchor
  1118. self.accept()
  1119. else:
  1120. nuke.message("There was a problem selecting a valid anchor.")
  1121. class AnchorTags_LineEdit(QtWidgets.QLineEdit):
  1122. new_text = QtCore.Signal(object, object)
  1123. def __init__(self, *args):
  1124. QtWidgets.QLineEdit.__init__(self, *args)
  1125. self.textChanged.connect(self.text_changed)
  1126. self.completer = None
  1127. def text_changed(self, text):
  1128. all_text = unicode(text)
  1129. text = all_text[:self.cursorPosition()]
  1130. prefix = text.split(',')[-1].strip()
  1131. text_tags = []
  1132. for t in all_text.split(','):
  1133. t1 = unicode(t).strip()
  1134. if t1 != '':
  1135. text_tags.append(t.strip())
  1136. text_tags = list(set(text_tags))
  1137. self.new_text.emit(text_tags, prefix)
  1138. def mouseReleaseEvent(self, e):
  1139. self.text_changed(self.text())
  1140. def complete_text(self, text):
  1141. cursor_pos = self.cursorPosition()
  1142. before_text = unicode(self.text())[:cursor_pos]
  1143. after_text = unicode(self.text())[cursor_pos:]
  1144. prefix_len = len(before_text.split(',')[-1].strip())
  1145. if after_text.strip() == '':
  1146. self.setText('%s%s' % (before_text[:cursor_pos - prefix_len], text))
  1147. else:
  1148. self.setText('%s%s, %s' % (before_text[:cursor_pos - prefix_len], text, after_text))
  1149. self.setCursorPosition(cursor_pos - prefix_len + len(text) + 2)
  1150. class TagsCompleter(QtWidgets.QCompleter):
  1151. insertText = QtCore.Signal(str)
  1152. def __init__(self, all_tags):
  1153. QtWidgets.QCompleter.__init__(self, all_tags)
  1154. self.all_tags = set(all_tags)
  1155. self.activated.connect(self.activated_text)
  1156. def update(self, text_tags, completion_prefix):
  1157. tags = list(self.all_tags-set(text_tags))
  1158. model = QtGui.QStringListModel(tags, self)
  1159. self.setModel(model)
  1160. self.setCompletionPrefix(completion_prefix)
  1161. self.complete()
  1162. def activated_text(self,completion):
  1163. self.insertText.emit(completion)
  1164. class NewAnchorPanel(QtWidgets.QDialog):
  1165. '''
  1166. Panel to create a new anchor on a selected node, where you can choose the name (autocompleted at first) and tags.
  1167. '''
  1168. def __init__(self, windowTitle = "New Stamp", default_title="", all_tags = [], default_tags = "", parent = None):
  1169. super(NewAnchorPanel, self).__init__(parent)
  1170. self.default_title = default_title
  1171. self.all_tags = all_tags
  1172. self.default_tags = default_tags
  1173. self.setWindowTitle(windowTitle)
  1174. self.initUI()
  1175. self.setFixedSize(self.sizeHint())
  1176. def initUI(self):
  1177. self.createWidgets()
  1178. self.createLayouts()
  1179. def createWidgets(self):
  1180. """Create widgets..."""
  1181. self.newAnchorTitle = QtWidgets.QLabel("New Anchor Stamp")
  1182. self.newAnchorTitle.setStyleSheet("font-weight:bold;color:#CCCCCC;font-size:14px;")
  1183. self.newAnchorSubtitle = QtWidgets.QLabel("Set Stamp title and tag/s (comma separated)")
  1184. self.newAnchorSubtitle.setStyleSheet("color:#999")
  1185. self.newAnchorLine = QtWidgets.QFrame()
  1186. self.newAnchorLine.setFrameShape(QtWidgets.QFrame.HLine)
  1187. self.newAnchorLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1188. self.newAnchorLine.setLineWidth(0)
  1189. self.newAnchorLine.setMidLineWidth(1)
  1190. self.newAnchorLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1191. self.anchorTitle_label = QtWidgets.QLabel("Title: ")
  1192. self.anchorTitle_edit = QtWidgets.QLineEdit()
  1193. self.anchorTitle_edit.setFocus()
  1194. self.anchorTitle_edit.setText(self.default_title)
  1195. self.anchorTitle_edit.selectAll()
  1196. self.anchorTags_label = QtWidgets.QLabel("Tags: ")
  1197. self.anchorTags_edit = AnchorTags_LineEdit()
  1198. self.anchorTags_edit.setText(self.default_tags)
  1199. self.anchorTags_completer = TagsCompleter(self.all_tags)
  1200. self.anchorTags_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
  1201. self.anchorTags_completer.insertText.connect(self.anchorTags_edit.complete_text)
  1202. self.anchorTags_edit.new_text.connect(self.anchorTags_completer.update)
  1203. self.anchorTags_completer.setWidget(self.anchorTags_edit)
  1204. self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
  1205. self.buttonBox.accepted.connect(self.clickedOk)
  1206. self.buttonBox.rejected.connect(self.clickedCancel)
  1207. def createLayouts(self):
  1208. """Create layout..."""
  1209. self.titleAndTags_layout = QtWidgets.QGridLayout()
  1210. self.titleAndTags_layout.addWidget(self.anchorTitle_label,0,0)
  1211. self.titleAndTags_layout.addWidget(self.anchorTitle_edit,0,1)
  1212. self.titleAndTags_layout.addWidget(self.anchorTags_label,1,0)
  1213. self.titleAndTags_layout.addWidget(self.anchorTags_edit,1,1)
  1214. self.master_layout = QtWidgets.QVBoxLayout()
  1215. self.master_layout.addWidget(self.newAnchorTitle)
  1216. self.master_layout.addWidget(self.newAnchorSubtitle)
  1217. self.master_layout.addWidget(self.newAnchorLine)
  1218. self.master_layout.addLayout(self.titleAndTags_layout)
  1219. self.master_layout.addWidget(self.buttonBox)
  1220. self.setLayout(self.master_layout)
  1221. def clickedOk(self):
  1222. self.anchorTitle = self.anchorTitle_edit.text().strip()
  1223. if self.anchorTitle == "" and self.anchorTitle_edit.text() != "":
  1224. self.anchorTitle = self.anchorTitle_edit.text()
  1225. self.anchorTags = self.anchorTags_edit.text().strip()
  1226. self.accept()
  1227. return True
  1228. def clickedCancel(self):
  1229. '''Abort mission'''
  1230. self.reject()
  1231. class AddTagsPanel(QtWidgets.QDialog):
  1232. '''
  1233. Panel to add tags to the selected Stamps or nodes.
  1234. '''
  1235. def __init__(self, all_tags = [], default_tags = "", parent = None):
  1236. super(AddTagsPanel, self).__init__(parent)
  1237. self.all_tags = all_tags
  1238. self.allNodes = True
  1239. self.default_tags = default_tags
  1240. self.setWindowTitle("Stamps: Add Tags")
  1241. self.initUI()
  1242. self.setFixedSize(self.sizeHint())
  1243. def initUI(self):
  1244. self.createWidgets()
  1245. self.createLayouts()
  1246. def createWidgets(self):
  1247. self.addTagsTitle = QtWidgets.QLabel("Add tag/s")
  1248. self.addTagsTitle.setStyleSheet("font-weight:bold;color:#CCCCCC;font-size:14px;")
  1249. self.addTagsSubtitle = QtWidgets.QLabel("Add tag/s to the selected nodes (comma separated).")
  1250. self.addTagsSubtitle.setStyleSheet("color:#999")
  1251. self.addTagsLine = QtWidgets.QFrame()
  1252. self.addTagsLine.setFrameShape(QtWidgets.QFrame.HLine)
  1253. self.addTagsLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1254. self.addTagsLine.setLineWidth(0)
  1255. self.addTagsLine.setMidLineWidth(1)
  1256. self.addTagsLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1257. self.tags_label = QtWidgets.QLabel("Tags: ")
  1258. self.tags_edit = AnchorTags_LineEdit()
  1259. self.tags_edit.setText(self.default_tags)
  1260. self.tags_completer = TagsCompleter(self.all_tags)
  1261. self.tags_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
  1262. self.tags_completer.insertText.connect(self.tags_edit.complete_text)
  1263. self.tags_edit.new_text.connect(self.tags_completer.update)
  1264. self.tags_completer.setWidget(self.tags_edit)
  1265. self.addTo_Label = QtWidgets.QLabel("Add to: ")
  1266. self.addTo_Label.setToolTip("Which nodes to add tag/s to.")
  1267. self.addTo_btnA = QtWidgets.QRadioButton("All selected nodes")
  1268. self.addTo_btnA.setChecked(True)
  1269. self.addTo_btnB = QtWidgets.QRadioButton("Selected Stamps")
  1270. addTo_ButtonGroup = QtWidgets.QButtonGroup(self)
  1271. addTo_ButtonGroup.addButton(self.addTo_btnA)
  1272. addTo_ButtonGroup.addButton(self.addTo_btnB)
  1273. self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
  1274. self.buttonBox.accepted.connect(self.clickedOk)
  1275. self.buttonBox.rejected.connect(self.clickedCancel)
  1276. def createLayouts(self):
  1277. self.main_layout = QtWidgets.QGridLayout()
  1278. self.main_layout.addWidget(self.tags_label,0,0)
  1279. self.main_layout.addWidget(self.tags_edit,0,1)
  1280. addTo_Buttons_layout = QtWidgets.QHBoxLayout()
  1281. addTo_Buttons_layout.addWidget(self.addTo_btnA)
  1282. addTo_Buttons_layout.addWidget(self.addTo_btnB)
  1283. self.main_layout.addWidget(self.addTo_Label,1,0)
  1284. self.main_layout.addLayout(addTo_Buttons_layout,1,1)
  1285. self.master_layout = QtWidgets.QVBoxLayout()
  1286. self.master_layout.addWidget(self.addTagsTitle)
  1287. self.master_layout.addWidget(self.addTagsSubtitle)
  1288. self.master_layout.addWidget(self.addTagsLine)
  1289. self.master_layout.addLayout(self.main_layout)
  1290. self.master_layout.addWidget(self.buttonBox)
  1291. self.setLayout(self.master_layout)
  1292. def clickedOk(self):
  1293. '''Check if all is correct and submit'''
  1294. self.tags = self.tags_edit.text().strip()
  1295. self.allNodes = self.addTo_btnA.isChecked
  1296. self.accept()
  1297. return True
  1298. def clickedCancel(self):
  1299. '''Abort mission'''
  1300. self.reject()
  1301. class RenameTagPanel(QtWidgets.QDialog):
  1302. '''
  1303. Panel to rename a tag on the selected (or all) nodes.
  1304. '''
  1305. def __init__(self, all_tags = [], default_tags = "", parent = None):
  1306. super(RenameTagPanel, self).__init__(parent)
  1307. self.all_tags = all_tags
  1308. self.allNodes = True
  1309. self.default_tags = default_tags
  1310. self.setWindowTitle("Stamps: Rename tag")
  1311. self.initUI()
  1312. self.setFixedSize(self.sizeHint())
  1313. def initUI(self):
  1314. self.createWidgets()
  1315. self.createLayouts()
  1316. def createWidgets(self):
  1317. self.headerTitle = QtWidgets.QLabel("Rename tag")
  1318. self.headerTitle.setStyleSheet("font-weight:bold;color:#CCCCCC;font-size:14px;")
  1319. self.headerSubtitle = QtWidgets.QLabel("Rename a tag on the selected (or all) nodes.")
  1320. self.headerSubtitle.setStyleSheet("color:#999")
  1321. self.headerLine = QtWidgets.QFrame()
  1322. self.headerLine.setFrameShape(QtWidgets.QFrame.HLine)
  1323. self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1324. self.headerLine.setLineWidth(0)
  1325. self.headerLine.setMidLineWidth(1)
  1326. self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
  1327. self.tag_label = QtWidgets.QLabel("Rename: ")
  1328. self.tag_edit = QtWidgets.QLineEdit()
  1329. all_tags = allTags()
  1330. self.tag_completer = QtWidgets.QCompleter(all_tags, self)
  1331. self.tag_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
  1332. self.tag_edit.setCompleter(self.tag_completer)
  1333. self.tagReplace_label = QtWidgets.QLabel("To: ")
  1334. self.tagReplace_edit = QtWidgets.QLineEdit()
  1335. self.tagReplace_completer = QtWidgets.QCompleter(all_tags, self)
  1336. self.tagReplace_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
  1337. self.tagReplace_edit.setCompleter(self.tagReplace_completer)
  1338. self.addTo_Label = QtWidgets.QLabel("Apply on: ")
  1339. self.addTo_Label.setToolTip("Which nodes to rename the tag on.")
  1340. self.addTo_btnA = QtWidgets.QRadioButton("Selected nodes")
  1341. self.addTo_btnA.setChecked(True)
  1342. self.addTo_btnB = QtWidgets.QRadioButton("All nodes")
  1343. addTo_ButtonGroup = QtWidgets.QButtonGroup(self)
  1344. addTo_ButtonGroup.addButton(self.addTo_btnA)
  1345. addTo_ButtonGroup.addButton(self.addTo_btnB)
  1346. self.buttonBox = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
  1347. self.buttonBox.accepted.connect(self.clickedOk)
  1348. self.buttonBox.rejected.connect(self.clickedCancel)
  1349. def createLayouts(self):
  1350. self.main_layout = QtWidgets.QGridLayout()
  1351. self.main_layout.addWidget(self.tag_label,0,0)
  1352. self.main_layout.addWidget(self.tag_edit,0,1)
  1353. self.main_layout.addWidget(self.tagReplace_label,1,0)
  1354. self.main_layout.addWidget(self.tagReplace_edit,1,1)
  1355. addTo_Buttons_layout = QtWidgets.QHBoxLayout()
  1356. addTo_Buttons_layout.addWidget(self.addTo_btnA)
  1357. addTo_Buttons_layout.addWidget(self.addTo_btnB)
  1358. self.main_layout.addWidget(self.addTo_Label,2,0)
  1359. self.main_layout.addLayout(addTo_Buttons_layout,2,1)
  1360. self.master_layout = QtWidgets.QVBoxLayout()
  1361. self.master_layout.addWidget(self.headerTitle)
  1362. self.master_layout.addWidget(self.headerSubtitle)
  1363. self.master_layout.addWidget(self.headerLine)
  1364. self.master_layout.addLayout(self.main_layout)
  1365. self.master_layout.addWidget(self.buttonBox)
  1366. self.setLayout(self.master_layout)
  1367. def clickedOk(self):
  1368. '''Check if all is correct and submit'''
  1369. self.tag = self.tag_edit.text().strip()
  1370. self.tagReplace = self.tagReplace_edit.text().strip()
  1371. self.allNodes = self.addTo_btnB.isChecked
  1372. self.accept()
  1373. return True
  1374. def clickedCancel(self):
  1375. '''Abort mission'''
  1376. self.reject()
  1377. #################################
  1378. ### FUNCTIONS
  1379. #################################
  1380. def getDefaultTitle(node = None):
  1381. if node == None:
  1382. return False
  1383. title = str(node.name())
  1384. # Default defaults here
  1385. # cam
  1386. if "Camera" in node.Class() and not any([(i.knob("title") and i["title"].value() == "cam") for i in nuke.allNodes("NoOp")]):
  1387. title = "cam"
  1388. return title
  1389. if node.Class() in ["Dot","NoOp"] and node["label"].value().strip() != "":
  1390. return node["label"].value().strip()
  1391. # Filename
  1392. try:
  1393. file = node['file'].value()
  1394. if not (node.knob("read_from_file") and not node["read_from_file"].value()):
  1395. if file != "":
  1396. rawname = file.rpartition('/')[2].rpartition('.')[0]
  1397. if '.' in rawname:
  1398. rawname = rawname.rpartition('.')[0]
  1399. # 1: beauty?
  1400. m = re.match(r"([\w]+)_v[\d]+_beauty", rawname)
  1401. if m:
  1402. pre_version = m.groups()[0]
  1403. title = "_".join(pre_version.split("_")[3:])
  1404. return title
  1405. # 2: Other
  1406. rawname = str(re.split("_v[0-9]*_",rawname)[-1]).replace("_render","")
  1407. title = rawname
  1408. except:
  1409. pass
  1410. return title
  1411. def backdropTags(node = None):
  1412. ''' Returns the list of words belonging to the backdrop/s label/s'''
  1413. backdrops = findBackdrops(node)
  1414. tags = []
  1415. for b in backdrops:
  1416. if b.knob("visible_for_stamps"):
  1417. if not b["visible_for_stamps"].value():
  1418. continue
  1419. elif not b["bookmark"].value():
  1420. continue
  1421. label = b["label"].value()
  1422. if len(label) and len(label) < 50 and not label.startswith("\\"):
  1423. label = label.split("\n")[0].strip()
  1424. label = re.sub("<[^<>]>","",label)
  1425. label = re.sub("[\s]+"," ",label)
  1426. label = re.sub("\.$","",label)
  1427. label = label.strip()
  1428. tags.append(label)
  1429. return tags
  1430. def stampCreateAnchor(node = None, extra_tags = [], no_default_tag = False):
  1431. '''
  1432. Create a stamp from any nuke node.
  1433. Returns: extra_tags list is success, None if cancelled
  1434. '''
  1435. ns = nuke.selectedNodes()
  1436. for n in ns:
  1437. n.setSelected(False)
  1438. if node is not None:
  1439. node.setSelected(True)
  1440. default_title = getDefaultTitle(realInput(node,stopOnLabel=True,mode="title"))
  1441. default_tags = list(set([nodeType(realInput(node,mode="tags"))]))
  1442. node_type = nodeType(realInput(node))
  1443. window_title = "New Stamp: "+str(node.name())
  1444. else:
  1445. default_title = "Stamp"
  1446. default_tags = ""
  1447. node_type = ""
  1448. window_title = "New Stamp"
  1449. try:
  1450. custom_default_title = defaultTitle(node)
  1451. if custom_default_title:
  1452. default_title = str(custom_default_title)
  1453. except:
  1454. pass
  1455. try:
  1456. custom_default_tags = defaultTags(node)
  1457. if custom_default_tags:
  1458. if KEEP_ORIGINAL_TAGS:
  1459. default_tags += custom_default_tags
  1460. else:
  1461. default_tags = custom_default_tags
  1462. except:
  1463. pass
  1464. default_default_tags = default_tags
  1465. if no_default_tag:
  1466. default_tags = ", ".join(extra_tags + [""])
  1467. else:
  1468. default_tags = filter(None,list(dict.fromkeys(default_tags + extra_tags)))
  1469. default_tags = ", ".join(default_tags + [""])
  1470. global new_anchor_panel
  1471. new_anchor_panel = NewAnchorPanel(window_title, default_title, allTags(), default_tags)
  1472. while True:
  1473. if new_anchor_panel.exec_():
  1474. anchor_title = new_anchor_panel.anchorTitle
  1475. anchor_tags = new_anchor_panel.anchorTags
  1476. if not titleIsLegal(anchor_title):
  1477. nuke.message("Please set a valid title.")
  1478. continue
  1479. elif len(findAnchorsByTitle(anchor_title)):
  1480. if not nuke.ask("There is already a Stamp titled "+anchor_title+".\nDo you still want to use this title?"):
  1481. continue
  1482. na = anchor(title = anchor_title, tags = anchor_tags, input_node = node, node_type = node_type)
  1483. na.setYpos(na.ypos()+20)
  1484. stampCreateWired(na)
  1485. for n in ns:
  1486. n.setSelected(True)
  1487. node.setSelected(False)
  1488. extra_tags = anchor_tags.split(",")
  1489. extra_tags = [t.strip() for t in extra_tags if t.strip() not in default_default_tags]
  1490. break
  1491. else:
  1492. break
  1493. return extra_tags
  1494. def stampSelectAnchor():
  1495. '''
  1496. Panel to select a stamp anchor (if there are any)
  1497. Returns: selected anchor node, or None.
  1498. '''
  1499. # 1.Get position where to make the child...
  1500. nodeForPos = nuke.createNode("NoOp")
  1501. childNodePos = [nodeForPos.xpos(),nodeForPos.ypos()]
  1502. nuke.delete(nodeForPos)
  1503. # 2.Choose the anchor...
  1504. anchorList = [n.name() for n in allAnchors()]
  1505. if not len(anchorList):
  1506. nuke.message("Please create some stamps first...")
  1507. return None
  1508. else:
  1509. global select_anchor_panel
  1510. select_anchor_panel = AnchorSelector()
  1511. if select_anchor_panel.exec_(): # If user clicks OK
  1512. chosen_anchor = select_anchor_panel.chosen_anchor
  1513. if chosen_anchor:
  1514. return chosen_anchor
  1515. return None
  1516. def stampCreateWired(anchor = ""):
  1517. ''' Create a wired stamp from an anchor node. '''
  1518. global Stamps_LastCreated
  1519. if anchor == "":
  1520. anchor = stampSelectAnchor()
  1521. if anchor == None:
  1522. return
  1523. nw = wired(anchor = anchor)
  1524. nw.setInput(0,anchor)
  1525. else:
  1526. ns = nuke.selectedNodes()
  1527. for n in ns:
  1528. n.setSelected(False)
  1529. dot = nuke.nodes.Dot()
  1530. dot.setXYpos(anchor.xpos(),anchor.ypos())
  1531. dot.setInput(0,anchor)
  1532. nw = wired(anchor = anchor)
  1533. code = "dummy = nuke.nodes.{}()".format(nw.Class())
  1534. exec(code)
  1535. nww = dummy.screenWidth()
  1536. nuke.delete(dummy)
  1537. nuke.delete(dot)
  1538. for n in ns:
  1539. n.setSelected(True)
  1540. nw.setXYpos(anchor.xpos()+anchor.screenWidth()/2-nww/2 ,anchor.ypos()+56)
  1541. anchor.setSelected(False)
  1542. return nw
  1543. def stampCreateByTitle(title = ""):
  1544. ''' Create a wired stamp given a title. '''
  1545. global Stamps_LastCreated
  1546. anchor = None
  1547. for a in allAnchors():
  1548. if a.knob("title") and a["title"].value() == title:
  1549. anchor = a
  1550. break
  1551. if anchor == None:
  1552. return
  1553. nw = wired(anchor = anchor)
  1554. nw.setInput(0,anchor)
  1555. return nw
  1556. def stampDuplicateWired(wired = ""):
  1557. ''' Create a duplicate of a wired stamp '''
  1558. ns = nuke.selectedNodes()
  1559. for n in ns:
  1560. n.setSelected(False)
  1561. wired.setSelected(True)
  1562. clipboard = QtWidgets.QApplication.clipboard()
  1563. ctext = clipboard.text()
  1564. nuke.nodeCopy("%clipboard%")
  1565. wired.setSelected(False)
  1566. new_wired = nuke.nodePaste("%clipboard%")
  1567. clipboard.setText(ctext)
  1568. new_wired.setXYpos(wired.xpos()-110,wired.ypos()+55)
  1569. try:
  1570. new_wired.setInput(0,wired.input(0))
  1571. except:
  1572. pass
  1573. for n in ns:
  1574. n.setSelected(True)
  1575. wired.setSelected(False)
  1576. def stampType(n = ""):
  1577. ''' Returns the identifier value if it exists, otherwise False. '''
  1578. if isAnchor(n):
  1579. return "anchor"
  1580. elif isWired(n):
  1581. return "wired"
  1582. else:
  1583. return False
  1584. def nodeType(n=""):
  1585. '''Returns the node type: Camera, Deep, 3D, Particles, Image or False'''
  1586. try:
  1587. nodeClass = n.Class()
  1588. except:
  1589. return False
  1590. if nodeClass.startswith("Deep") and nodeClass not in DeepExceptionClasses:
  1591. return "Deep"
  1592. elif nodeClass.startswith("Particle") and nodeClass not in ParticleExceptionClasses:
  1593. return "Particle"
  1594. elif nodeClass in ["Camera", "Camera2"]:
  1595. return "Camera"
  1596. elif nodeClass in ["Axis", "Axis2"]:
  1597. return "Axis"
  1598. elif (n.knob("render_mode") and n.knob("display")) or nodeClass in ["Axis2","GeoNoOp","EditGeo"]:
  1599. return "3D"
  1600. else:
  1601. return "2D"
  1602. def allAnchors(selection=""):
  1603. nodes = nuke.allNodes()
  1604. if selection == "":
  1605. anchors = [a for a in nodes if isAnchor(a)]
  1606. else:
  1607. anchors = [a for a in nodes if a in selection and isAnchor(a)]
  1608. return anchors
  1609. def allWireds(selection=""):
  1610. nodes = nuke.allNodes()
  1611. if selection == "":
  1612. wireds = [a for a in nodes if isWired(a)]
  1613. else:
  1614. wireds = [a for a in nodes if a in selection and isWired(a)]
  1615. return wireds
  1616. def totalAnchors(selection=""):
  1617. num_anchors = len(allAnchors(selection))
  1618. return num_anchors
  1619. def allTags(selection=""):
  1620. all_tags = set()
  1621. for ni in allAnchors():
  1622. try:
  1623. tags_value = ni["tags"].value()
  1624. tags = re.split(" *, *",tags_value.strip()) # Remove leading/trailing spaces and separate by commas (with or without spaces)
  1625. all_tags.update(tags)
  1626. except:
  1627. pass
  1628. all_tags = filter(None,list(all_tags))
  1629. all_tags.sort(key=str.lower)
  1630. return all_tags
  1631. def findAnchorsByTitle(title = "", selection=""):
  1632. ''' Returns list of nodes '''
  1633. if title == "":
  1634. return None
  1635. if selection == "":
  1636. found_anchors = [a for a in allAnchors() if a.knob("title") and a.knob("title").value() == title]
  1637. else:
  1638. found_anchors = [a for a in selection if a in allAnchors() and a.knob("title") and a.knob("title").value() == title]
  1639. return found_anchors
  1640. def titleIsLegal(title=""):
  1641. '''
  1642. Convenience function to determine which stamp titles are legal.
  1643. titleIsLegal(title) -> True or False
  1644. '''
  1645. if not title or title == "":
  1646. return False
  1647. return True
  1648. def isAnchor(node=""):
  1649. ''' True or False '''
  1650. return True if all(node.knob(i) for i in ["identifier","title"]) and node["identifier"].value() == "anchor" else False
  1651. def isWired(node=""):
  1652. ''' True or False '''
  1653. return True if all(node.knob(i) for i in ["identifier","title"]) and node["identifier"].value() == "wired" else False
  1654. def findBackdrops(node = ""):
  1655. ''' Returns a list containing the backdrops that contain the given node.'''
  1656. if node =="":
  1657. return []
  1658. x = node.xpos()
  1659. y = node.ypos()
  1660. w = node.screenWidth()
  1661. h = node.screenHeight()
  1662. backdrops = []
  1663. for b in nuke.allNodes("BackdropNode"):
  1664. bx = int(b['xpos'].value())
  1665. by = int(b['ypos'].value())
  1666. br = int(b['bdwidth'].value())+bx
  1667. bt =int(b['bdheight'].value())+by
  1668. if x >= bx and (x+w) <= br and y > by and (y+h) <= bt:
  1669. backdrops.append(b)
  1670. return backdrops
  1671. def realInput(node, stopOnLabel=False, mode=""):
  1672. '''
  1673. Returns the first input node that is not a Dot or a Stamp
  1674. stopOnLabel=False. True: Stop when it's a dot or NoOp but it has a label.
  1675. mode="title": ignores TitleIgnoreClasses
  1676. mode="tags": ignores TagsIgnoreClasses
  1677. '''
  1678. try:
  1679. n = node
  1680. if stampType(n) or n.Class() in InputIgnoreClasses or (mode=="title" and n.Class() in TitleIgnoreClasses) or (mode=="tags" and n.Class() in TagsIgnoreClasses):
  1681. if stopOnLabel and n.knob("label") and n["label"].value().strip()!="":
  1682. return n
  1683. if n.input(0):
  1684. n = n.input(0)
  1685. return realInput(n,stopOnLabel,mode)
  1686. else:
  1687. return n
  1688. else:
  1689. return n
  1690. except:
  1691. return node
  1692. def nodeToScript(node = ""):
  1693. ''' Returns a node as a tcl string, similar as nodecopy without messing with the clipboard '''
  1694. orig_sel_nodes = nuke.selectedNodes()
  1695. if node == "":
  1696. node = nuke.selectedNode()
  1697. if not node:
  1698. return ""
  1699. for i in orig_sel_nodes:
  1700. i.setSelected(False)
  1701. node.setSelected(True)
  1702. clipboard = QtWidgets.QApplication.clipboard()
  1703. ctext = clipboard.text()
  1704. nuke.nodeCopy("%clipboard%")
  1705. node_as_script = clipboard.text()
  1706. clipboard.setText(ctext)
  1707. node.setSelected(False)
  1708. for i in orig_sel_nodes:
  1709. i.setSelected(True)
  1710. return node_as_script
  1711. def nodesFromScript(script = ""):
  1712. ''' Returns string as a node, similar as nodepaste without messing with the clipboard '''
  1713. if script == "":
  1714. return
  1715. clipboard = QtWidgets.QApplication.clipboard()
  1716. ctext = clipboard.text()
  1717. clipboard.setText(script)
  1718. nuke.nodePaste("%clipboard%")
  1719. clipboard.setText(ctext)
  1720. return True
  1721. def stampCount(anchor_name=""):
  1722. if anchor_name=="":
  1723. return len(allWireds())
  1724. stamps = [s for s in allWireds() if s["anchor"].value() == anchor_name]
  1725. return len(stamps)
  1726. def toNoOp(node=""):
  1727. '''Turns a given node into a NoOp that shares everything else'''
  1728. global Stamps_LockCallbacks
  1729. Stamps_LockCallbacks = True
  1730. if node == "":
  1731. return
  1732. if node.Class() == "NoOp":
  1733. return
  1734. nsn = nuke.selectedNodes()
  1735. [i.setSelected(False) for i in nsn]
  1736. scr = nodeToScript(node)
  1737. scr = re.sub(r"\n[\s]*[\w]+[\s]*{\n","\nNoOp {\n",scr)
  1738. scr = re.sub(r"^[\s]*[\w]+[\s]*{\n","NoOp {\n",scr)
  1739. legal_starts = ["set","version","push","NoOp","help","onCreate","name","knobChanged","autolabel","tile_color","gl_color","note_font","selected","hide_input"]
  1740. scr_split = scr.split("addUserKnob",1)
  1741. scr_first = scr_split[0].split("\n")
  1742. for i, line in enumerate(scr_first):
  1743. if not any([line.startswith(x) or line.startswith(" "+x) for x in legal_starts]):
  1744. scr_first[i] = ""
  1745. scr_first = "\n".join(filter(None,scr_first)+[""])
  1746. scr_split[0] = scr_first
  1747. scr = "addUserKnob".join(scr_split)
  1748. node.setSelected(True)
  1749. xp = node.xpos()
  1750. yp = node.ypos()
  1751. xw = node.screenWidth()/2
  1752. d = nuke.createNode("Dot")
  1753. d.setInput(0,node)
  1754. inp = None
  1755. [i.setSelected(True) for i in nsn]
  1756. nuke.delete(node)
  1757. d.setSelected(False)
  1758. d.setSelected(True)
  1759. d.setXYpos(xp+xw-d.screenWidth()/2,yp-18)
  1760. nodesFromScript(scr)
  1761. n = nuke.selectedNode()
  1762. n.setXYpos(xp,yp)
  1763. nuke.delete(d)
  1764. for i in nsn:
  1765. try:
  1766. i.setSelected(True)
  1767. except:
  1768. pass
  1769. Stamps_LockCallbacks = False
  1770. def allToNoOp():
  1771. ''' Turns all the stamps into NoOps '''
  1772. for n in nuke.allNodes():
  1773. if stampType(n) and n.Class() != "NoOp":
  1774. toNoOp(n)
  1775. #################################
  1776. ### Menu functions
  1777. #################################
  1778. def refreshStamps(ns=""):
  1779. '''Refresh all the wired Stamps in the script, to spot any wrong styles or connections.'''
  1780. stamps = allWireds(ns)
  1781. x = 0
  1782. failed = []
  1783. failed_names = []
  1784. for s in stamps:
  1785. if not wiredReconnect(s):
  1786. x += 1 # Errors
  1787. failed.append(s)
  1788. else:
  1789. try:
  1790. s["reconnect_this"].execute()
  1791. except:
  1792. pass
  1793. failed_names = ", ".join([i.name() for i in failed])
  1794. if x==0:
  1795. if ns == "":
  1796. nuke.message("All Stamps refreshed! No errors detected.")
  1797. else:
  1798. nuke.message("Selected Stamps refreshed! No errors detected.")
  1799. else:
  1800. [i.setSelected(False) for i in nuke.selectedNodes()]
  1801. [i.setSelected(True) for i in failed]
  1802. if ns == "":
  1803. nuke.message("All Stamps refreshed. Found {0} connection error/s:\n\n{1}".format(str(x), failed_names))
  1804. else:
  1805. nuke.message("Selected Stamps refreshed. Found {0} connection error/s:\n\n{1}".format(str(x), failed_names))
  1806. def addTags(ns=""):
  1807. if ns=="":
  1808. ns = nuke.selectedNodes()
  1809. if not len(ns):
  1810. if not nuke.ask("Nothing is selected. Do you wish to add tags to ALL nodes in the script?"):
  1811. return
  1812. ns = nuke.allNodes()
  1813. global stamps_addTags_panel
  1814. stamps_addTags_panel = AddTagsPanel(all_tags = allTags(), default_tags = "")
  1815. if stamps_addTags_panel.exec_():
  1816. all_nodes = stamps_addTags_panel.allNodes
  1817. added_tags = stamps_addTags_panel.tags.strip()
  1818. added_tags = re.split(r"[\s]*,[\s]*", added_tags)
  1819. i = 0
  1820. for n in ns:
  1821. if isAnchor(n):
  1822. tags_knob = n.knob("tags")
  1823. elif isWired(n):
  1824. a_name = n.knob("anchor").value()
  1825. if nuke.exists(a_name):
  1826. a = nuke.toNode(a_name)
  1827. if a in ns or not isAnchor(a):
  1828. continue
  1829. tags_knob = a.knob("tags")
  1830. elif all_nodes and n.Class() not in NodeExceptionClasses:
  1831. tags_knob = n.knob("stamp_tags")
  1832. if not tags_knob:
  1833. tags_knob = nuke.String_Knob('stamp_tags','Stamp Tags', "")
  1834. tags_knob.setTooltip("Stamps: Comma-separated tags you can define for each Anchor, that will help you find it when invoking the Stamp Selector by pressing the Stamps shortkey with nothing selected.")
  1835. n.addKnob(tags_knob)
  1836. else:
  1837. continue
  1838. existing_tags = re.split(r"[\s]*,[\s]*", tags_knob.value().strip())
  1839. merged_tags = filter(None,list(set(existing_tags + added_tags)))
  1840. tags_knob.setValue(", ".join(merged_tags))
  1841. i += 1
  1842. continue
  1843. if i>0:
  1844. if all_nodes:
  1845. nuke.message("Added the specified tag/s to {} nodes.".format(str(i)))
  1846. else:
  1847. nuke.message("Added the specified tag/s to {} Anchor Stamps.".format(str(i)))
  1848. return
  1849. def renameTag(ns=""):
  1850. if ns=="":
  1851. ns = nuke.selectedNodes()
  1852. if not len(ns):
  1853. ns = nuke.allNodes()
  1854. global stamps_renameTag_panel
  1855. stamps_renameTag_panel = RenameTagPanel(all_tags = allTags())
  1856. if stamps_renameTag_panel.exec_():
  1857. all_nodes = stamps_renameTag_panel.allNodes
  1858. if all_nodes:
  1859. ns = nuke.allNodes()
  1860. added_tag = str(stamps_renameTag_panel.tag.strip())
  1861. added_tagReplace = str(stamps_renameTag_panel.tagReplace.strip())
  1862. i = 0
  1863. for n in ns:
  1864. if isAnchor(n):
  1865. tags_knob = n.knob("tags")
  1866. elif isWired(n):
  1867. a_name = n.knob("anchor").value()
  1868. if nuke.exists(a_name):
  1869. a = nuke.toNode(a_name)
  1870. if a in ns or not isAnchor(a):
  1871. continue
  1872. tags_knob = a.knob("tags")
  1873. elif n.Class() not in NodeExceptionClasses:
  1874. tags_knob = n.knob("stamp_tags")
  1875. if not tags_knob:
  1876. continue
  1877. else:
  1878. continue
  1879. existing_tags = filter(None,re.split(r"[\s]*,[\s]*", tags_knob.value()))
  1880. added_tag_list = re.split(r"[\s]*,[\s]*", added_tag.strip())
  1881. added_tagReplace_list = re.split(r"[\s]*,[\s]*", added_tagReplace)
  1882. merged_tags = existing_tags
  1883. for atag in added_tag_list:
  1884. for rtag in added_tagReplace_list:
  1885. merged_tags = [rtag if x == atag else x for x in merged_tags]
  1886. merged_tags = filter(None,merged_tags)
  1887. if merged_tags != existing_tags:
  1888. tags_knob.setValue(", ".join(merged_tags))
  1889. i += 1
  1890. continue
  1891. if i>0:
  1892. nuke.message("Renamed the specified tag on {} nodes.".format(str(i)))
  1893. return
  1894. def selectedReconnectByName():
  1895. ns = [n for n in nuke.selectedNodes() if isWired(n)]
  1896. for n in ns:
  1897. try:
  1898. n["reconnect_this"].execute()
  1899. except:
  1900. pass
  1901. def selectedReconnectByTitle():
  1902. ns = [n for n in nuke.selectedNodes() if isWired(n)]
  1903. for n in ns:
  1904. try:
  1905. n["reconnect_by_title_this"].execute()
  1906. except:
  1907. pass
  1908. def selectedReconnectBySelection():
  1909. ns = [n for n in nuke.selectedNodes() if isWired(n)]
  1910. for n in ns:
  1911. try:
  1912. n["reconnect_by_selection_this"].execute()
  1913. except:
  1914. pass
  1915. def selectedToggleAutorec():
  1916. ns = [n for n in nuke.selectedNodes() if n.knob("auto_reconnect_by_title")]
  1917. if any([not n.knob("auto_reconnect_by_title").value() for n in ns]):
  1918. if nuke.ask("Are you sure you want to set <b>auto-reconnect by title</b> True on all the selected stamps?"):
  1919. count = 0
  1920. for n in ns:
  1921. n.knob("auto_reconnect_by_title").setValue(1)
  1922. count += 1
  1923. nuke.message("<b>auto-reconnect by title</b> is now True on {} selected stamps.".format(str(count)))
  1924. else:
  1925. count = 0
  1926. for n in ns:
  1927. n.knob("auto_reconnect_by_title").setValue(0)
  1928. count += 1
  1929. nuke.message("<b>auto-reconnect by title</b> is now <b>False</b> on {} selected stamps.".format(str(count)))
  1930. def selectedSelectSimilar():
  1931. ns = [n for n in nuke.selectedNodes() if isWired(n) or isAnchor(n)]
  1932. for n in ns:
  1933. try:
  1934. if n.knob("selectSimilar"):
  1935. n["selectSimilar"].execute()
  1936. if n.knob("selectStamps"):
  1937. n["selectStamps"].execute()
  1938. except:
  1939. pass
  1940. def showInNukepedia():
  1941. from webbrowser import open as openUrl
  1942. openUrl("http://www.nukepedia.com/gizmos/other/stamps")
  1943. def showInGithub():
  1944. from webbrowser import open as openUrl
  1945. openUrl("https://github.com/adrianpueyo/stamps")
  1946. def showHelp():
  1947. from webbrowser import open as openUrl
  1948. openUrl("http://www.adrianpueyo.com/Stamps_v1.0.pdf")
  1949. def showVideo():
  1950. from webbrowser import open as openUrl
  1951. openUrl("https://vimeo.com/adrianpueyo/knobscripter2")
  1952. def stampBuildMenus():
  1953. global Stamps_MenusLoaded
  1954. if not Stamps_MenusLoaded:
  1955. Stamps_MenusLoaded = True
  1956. m = nuke.menu('Nuke')
  1957. m.addCommand('Edit/Stamps/Make Stamp', 'stamps.goStamp()', STAMPS_SHORTCUT, icon="stamps.png")
  1958. m.addCommand('Edit/Stamps/Add tag\/s to selected nodes', 'stamps.addTags()')
  1959. m.addCommand('Edit/Stamps/Rename Stamp tag', 'stamps.renameTag()')
  1960. m.addCommand('Edit/Stamps/Refresh all Stamps', 'stamps.refreshStamps()')
  1961. m.addCommand('Edit/Stamps/Selected/Reconnect by Name', 'stamps.selectedReconnectByName()')
  1962. m.addCommand('Edit/Stamps/Selected/Reconnect by Title', 'stamps.selectedReconnectByTitle()')
  1963. m.addCommand('Edit/Stamps/Selected/Reconnect by Selection', 'stamps.selectedReconnectBySelection()')
  1964. m.menu('Edit').menu('Stamps').menu('Selected').addSeparator()
  1965. m.addCommand('Edit/Stamps/Selected/Refresh', 'stamps.refreshStamps(nuke.selectedNodes())')
  1966. m.addCommand('Edit/Stamps/Selected/Select Similar', 'stamps.selectedSelectSimilar()')
  1967. m.addCommand('Edit/Stamps/Selected/Toggle auto-rec... by title ', 'stamps.selectedToggleAutorec()')
  1968. m.addCommand('Edit/Stamps/Advanced/Convert all Stamps to NoOp', 'stamps.allToNoOp()')
  1969. m.menu('Edit').menu('Stamps').addSeparator()
  1970. m.addCommand('Edit/Stamps/GitHub', 'stamps.showInGithub()')
  1971. m.addCommand('Edit/Stamps/Nukepedia', 'stamps.showInNukepedia()')
  1972. m.menu('Edit').menu('Stamps').addSeparator()
  1973. m.addCommand('Edit/Stamps/Video tutorials', 'stamps.showVideo()')
  1974. m.addCommand('Edit/Stamps/Documentation (.pdf)', 'stamps.showHelp()')
  1975. nuke.menu('Nodes').addCommand('Other/Stamps', 'stamps.goStamp()', STAMPS_SHORTCUT, icon="stamps.png")
  1976. #################################
  1977. ### MAIN IMPLEMENTATION
  1978. #################################
  1979. def goStamp(ns=""):
  1980. ''' Main stamp function, the one that is called when pressing the main shortcut. '''
  1981. if ns=="":
  1982. ns = nuke.selectedNodes()
  1983. if not len(ns):
  1984. if not totalAnchors(): # If no anchors on the script, create an anchor with no input
  1985. stampCreateAnchor(no_default_tag = True)
  1986. else:
  1987. stampCreateWired() # Selection panel in order to create a stamp
  1988. return
  1989. elif len(ns) == 1 and ns[0].Class() in NodeExceptionClasses:
  1990. if not totalAnchors(): # If no anchors on the script, return
  1991. return
  1992. else:
  1993. stampCreateWired() # Selection panel in order to create a stamp
  1994. return
  1995. else:
  1996. # Warn if the selection is too big
  1997. if len(ns) > 10 and not nuke.ask("You have "+str(len(ns))+" nodes selected.\nDo you want make stamps for all of them?"):
  1998. return
  1999. # Main loop
  2000. extra_tags = []
  2001. for n in ns:
  2002. if n in NodeExceptionClasses:
  2003. continue
  2004. elif isAnchor(n):
  2005. stampCreateWired(n) # Make a child to n
  2006. elif isWired(n):
  2007. stampDuplicateWired(n) # Make a copy of n next to it
  2008. else:
  2009. if n.knob("stamp_tags"):
  2010. stampCreateAnchor(n, extra_tags = n.knob("stamp_tags").value().split(","), no_default_tag = True)
  2011. else:
  2012. extra_tags = stampCreateAnchor(n, extra_tags = extra_tags) # Create anchor via anchor creation panel
  2013. if "Cryptomatte" in n.Class() and n.knob("matteOnly"):
  2014. n['matteOnly'].setValue(1)
  2015. stampBuildMenus()