Package nukescripts :: Module renderdialog
[hide private]
[frames] | no frames]

Source Code for Module nukescripts.renderdialog

   1  # Copyright (c) 2010 The Foundry Visionmongers Ltd.  All Rights Reserved. 
   2  from __future__ import with_statement 
   3  import nuke 
   4  import nukescripts 
   5  import flipbooking 
   6  from nukescripts import pyAppUtils 
   7  import os 
   8  import string 
   9  import subprocess 
  10  import uuid 
  11  import re 
  12   
  13  from threading import Thread 
  14  from nukescripts import utils, sys, os, captureViewer 
  15  from fnFlipbookRenderer import getFlipbookRenderer 
  16   
  17  #import traceback 
  18   
  19  ############################################################################## 
  20  # Constants for the module 
  21  ############################################################################## 
  22   
  23  _USE_SETTINGS_FROM_CUSTOM = 'Custom' 
  24  _FRAME_RANGE_GLOBAL = 'global' 
  25  _FRAME_RANGE_INPUT = 'input' 
  26  _FRAME_RANGE_INOUT = 'in-out' 
  27  _FRAME_RANGE_VISIBLE = 'visible' 
  28  _FRAME_RANGE_CUSTOM = 'custom' 
  29   
  30   
  31  ############################################################################## 
  32  # Dialogs 
  33  ############################################################################## 
  34   
35 -class DialogState:
36 - def __init__(self):
37 self._state = {}
38
39 - def get(self, knob, defaultValue = None):
40 """Return the given knob's stored last state value. 41 If none exists, defaultValue is returned. 42 Values are stored in a dict referenced by knob name, so names must be unique!""" 43 return self.getValue(knob.name(), defaultValue)
44
45 - def save(self, knob):
46 """Store the knob's current value as the 'last state' for the next time the dialog is opened. 47 Values are stored in a dict referenced by knob name, so names must be unique!""" 48 self.saveValue(knob.name(), knob.value())
49
50 - def setKnob(self, knob, defaultValue = None):
51 """Convenience method for setting a value straight on a knob.""" 52 knob.setValue(self.get(knob, defaultValue))
53 - def saveValue(self, id, value):
54 """Stores the value with the given id.""" 55 self._state[id] = value
56 - def getValue(self, id, defaultValue = None):
57 """Recalls the value. If it was not set before, it will return the defaultValue.""" 58 return self._state.get(id, defaultValue)
59 60 _gRenderDialogState = DialogState() 61 _gFlipbookDialogState = DialogState() 62 _gViewerCaptureDialogState = DialogState() 63
64 -class ExecuteDialog(nukescripts.PythonPanel):
65 - def _titleString(self):
66 return "Execute"
67
68 - def _idString(self):
69 return "uk.co.thefoundry.ExecuteDialog"
70
71 - def _addLeadingKnobs(self):
72 """Add knobs that must appear first.""" 73 return
74
75 - def _addPreKnobs(self):
76 """Add knobs that must appear before the render knobs.""" 77 return
78
79 - def _addPostKnobs(self):
80 """Add knobs that must appear after the render knobs.""" 81 return
82
83 - def _addTrailingKnobs(self):
84 """Add knobs that must appear at the very end.""" 85 return
86
87 - def _getDefaultViews(self):
88 oc = nuke.OutputContext() 89 allViews = [oc.viewname(i) for i in xrange(1, oc.viewcount())] 90 return " ".join(allViews)
91
92 - def _addViewKnob(self):
93 """Add knobs for view selection.""" 94 oc = nuke.OutputContext() 95 if (oc.viewcount() > 2): 96 self._viewSelection = nuke.MultiView_Knob("multi_view", "Views") 97 self._viewSelection.fromScript(self._state.get(self._viewSelection, self._getDefaultViews())) 98 self.addKnob(self._viewSelection) 99 self._viewSelection.clearFlag(nuke.NO_MULTIVIEW)
100
101 - def addKnob(self, knob):
102 """Add the knob and make sure it cannot be animated.""" 103 knob.setFlag(nuke.NO_ANIMATION | nuke.NO_MULTIVIEW) 104 super(ExecuteDialog, self).addKnob(knob)
105
106 - def __init__(self, dialogState, groupContext, nodeSelection = [], exceptOnError = True):
107 self._state = dialogState 108 self._nodeSelection = nodeSelection 109 self._exceptOnError = exceptOnError 110 self._dialogKnobs = None 111 112 113 nukescripts.PythonPanel.__init__(self, self._titleString(), self._idString(), False) 114 115 self._viewers = {} 116 for n in nuke.allNodes("Viewer", groupContext): 117 self._viewers[n.name()] = n 118 119 self._addKnobs()
120
121 - def _addKnobs(self):
122 self._dialogKnobs = [] 123 beforeKnobs = self.knobs() 124 self._addLeadingKnobs() 125 126 self._addPreKnobs() 127 128 # Frame range knobs 129 specialRanges = [_FRAME_RANGE_GLOBAL, _FRAME_RANGE_INPUT, _FRAME_RANGE_CUSTOM] 130 for viewer in self._viewers.keys(): 131 specialRanges.append(viewer + '/' + _FRAME_RANGE_INOUT) 132 specialRanges.append(viewer + '/' + _FRAME_RANGE_VISIBLE) 133 134 self._rangeEnum = nuke.CascadingEnumeration_Knob( "frame_range", "Frame range", specialRanges ) 135 self._rangeEnum.setTooltip("Select the frame range for the flipbook") 136 self._state.setKnob(self._rangeEnum, _FRAME_RANGE_INPUT) 137 self.addKnob( self._rangeEnum ) 138 139 self._frameRange = nuke.String_Knob( "frame_range_string", "") 140 self._frameRange.setTooltip("Custom frame range") 141 self._frameRange.clearFlag(nuke.STARTLINE) 142 if self._rangeEnum.value() == _FRAME_RANGE_CUSTOM: 143 self._state.setKnob(self._frameRange, str(nuke.root().frameRange())) 144 else: 145 self._setFrameRangeFromSource(self._rangeEnum.value()) 146 147 self.addKnob(self._frameRange) 148 149 self._addPostKnobs() 150 151 self._addViewKnob() 152 153 self._addTrailingKnobs() 154 155 self._continueOnError = nuke.Boolean_Knob("continue", "Continue on error") 156 self._continueOnError.setTooltip("Continue on error") 157 self._state.setKnob(self._continueOnError, True) 158 self._continueOnError.setFlag(nuke.STARTLINE) 159 self.addKnob(self._continueOnError) 160 161 self._dialogKnobs = set(beforeKnobs.values()) ^ set(self.knobs().values())
162 163
164 - def knobChanged( self, knob ):
165 self._state.save(knob) 166 if (knob == self._frameRange): 167 self._rangeEnum.setValue("custom") 168 self._state.save(self._rangeEnum) 169 self._state.saveValue("customRange", knob.value()) 170 if (knob == self._rangeEnum): 171 self._setFrameRangeFromSource(knob.value()) 172 self._state.save(self._frameRange)
173
174 - def _setFrameRangeFromSource(self, source):
175 if (source == _FRAME_RANGE_INPUT): 176 try: 177 activeInput = nuke.activeViewer().activeInput() 178 self._frameRange.setValue(str(nuke.activeViewer().node().upstreamFrameRange(activeInput))) 179 except: 180 self._frameRange.setValue(str(nuke.root().frameRange())) 181 elif (source == _FRAME_RANGE_GLOBAL): 182 self._frameRange.setValue(str(nuke.root().frameRange())) 183 elif (source == _FRAME_RANGE_CUSTOM): 184 customRange = self._state.getValue("customRange", None) 185 if customRange: 186 self._frameRange.setValue(str(customRange)) 187 else: 188 self._frameRangeFromViewer(*source.split('/'));
189
190 - def _frameRangeFromViewer( self, viewer, frameRange ):
191 """Set the framerange knob to have the framerange from the given viewer.""" 192 if frameRange == _FRAME_RANGE_VISIBLE: 193 viewerRange = str(self._viewers[viewer].visibleRange()) 194 else: 195 viewerRange = str(self._viewers[viewer].playbackRange()) 196 self._frameRange.setValue(viewerRange)
197
198 - def _selectedViews(self):
199 try: 200 return self._viewSelection.value().split() 201 except AttributeError: 202 # If we didn't add the view selection knob, there should be just the one view. 203 return [nuke.OutputContext().viewname(1)]
204
205 - def addToPane(self):
207
208 - def run(self):
209 frame_ranges = nuke.FrameRanges(self._frameRange.value().split(',')) 210 views = self._selectedViews() 211 try: 212 nuke.Undo().disable() 213 nuke.executeMultiple(self._nodeSelection, frame_ranges, views, continueOnError = self._continueOnError.value()) 214 except RuntimeError, e: 215 if self._exceptOnError or e.args[0][0:9] != "Cancelled": # TO DO: change this to an exception type 216 raise 217 finally: 218 nuke.Undo().enable()
219
220 -class RenderDialog(ExecuteDialog):
221 deprecatedWarningShown = False 222 shouldShowBGRender = False 223 ShowWarning = True
224 - def _titleString(self):
225 return "Render"
226 227
228 - def _idString(self):
229 return "uk.co.thefoundry.RenderDialog"
230
231 - def __init__(self, 232 dialogState, 233 groupContext, 234 nodeSelection = [], 235 exceptOnError = True, 236 allowFrameServer = True):
237 self._allowFrameServer = allowFrameServer 238 ExecuteDialog.__init__(self, dialogState, groupContext, nodeSelection, exceptOnError)
239
240 - def _addPreKnobs( self ):
241 if self.isTimelineWrite(): 242 self._timelineRender = nuke.Boolean_Knob("timeline_render", "Render to timeline" ) 243 self._state.setKnob( self._timelineRender, True) 244 self._timelineRender.setFlag(nuke.STARTLINE) 245 self.addKnob(self._timelineRender)
246 247
249 if not RenderDialog.deprecatedWarningShown: 250 RenderDialog.deprecatedWarningShown = True; 251 nuke.tprint("Render in Background option functionality has been deprecated and replaced with the Frame Server.")
252
253 - def _addTrailingKnobs(self):
254 # Proxy 255 self._useProxy = nuke.Boolean_Knob("use_proxy", "Use proxy") 256 self._useProxy.setTooltip("Use proxy") 257 self._useProxy.setFlag(nuke.STARTLINE) 258 self._state.setKnob(self._useProxy, nuke.root().proxy()) 259 self.addKnob(self._useProxy) 260 261 self._frameserverRender = nuke.Boolean_Knob("frameserver_render", "Render using frame server") 262 self._frameserverRender.setTooltip("Render using frame server") 263 select_frame_server = nuke.toNode("preferences").knob("RenderWithFrameServer").getValue() 264 self._state.setKnob(self._frameserverRender, select_frame_server) 265 self._frameserverRender.setVisible(self.frameserverRenderAvailable()) 266 self._frameserverRender.setFlag(nuke.STARTLINE) 267 self.addKnob(self._frameserverRender) 268 269 self._bgRender = nuke.Boolean_Knob("bg_render", "Render in background") 270 self._state.setKnob(self._bgRender, False) 271 self._bgRender.setVisible(self.backgroundRenderAvailable()) 272 self._bgRender.setFlag(nuke.STARTLINE) 273 self.addKnob(self._bgRender) 274 275 self._numThreads = nuke.Int_Knob("num_threads", "Thread limit") 276 self._numThreads.setVisible(self.isBackgrounded()) 277 self._state.setKnob(self._numThreads, max(nuke.NUM_CPUS / 2, 1)) 278 self.addKnob(self._numThreads) 279 self._maxMem = nuke.String_Knob("max_memory", "Memory limit") 280 self._state.setKnob(self._maxMem, str(max(nuke.memory("max_usage") / 2097152, 16)) + "M") 281 self._maxMem.setVisible(self.isBackgrounded()) 282 self.addKnob(self._maxMem) 283 if self.isBackgrounded(): 284 self._showDeprecatedWarningMessage()
285
286 - def _getBackgroundLimits(self):
287 #Deprecated 288 return { 289 "maxThreads": self._numThreads.value(), 290 "maxCache": self._maxMem.value() }
291
292 - def knobChanged( self, knob ):
293 ExecuteDialog.knobChanged(self, knob) 294 295 timelineRender = False 296 try: 297 timelineRender = self._timelineRender.value() 298 except: 299 pass 300 301 bgRenderVisible = not timelineRender and self.backgroundRenderAvailable() 302 showBGRenderOptions = bgRenderVisible and self.isBackgrounded() 303 self._bgRender.setVisible(bgRenderVisible) 304 self._numThreads.setVisible(showBGRenderOptions) 305 self._maxMem.setVisible(showBGRenderOptions) 306 #if knob changed is the bgRender, set it visible 307 if knob is self._bgRender: 308 self._showDeprecatedWarningMessage() 309 310 311 if timelineRender and self.isTimelineWrite(): 312 self._rangeEnum.setValue( _FRAME_RANGE_GLOBAL ) 313 self._setFrameRangeFromSource(self._rangeEnum.value()) 314 self._rangeEnum.setEnabled( False ) 315 self._frameRange.setEnabled( False ) 316 self._frameserverRender.setVisible(False) 317 self._useProxy.setVisible( False ) 318 self._continueOnError.setVisible( False ) 319 else: 320 self._rangeEnum.setEnabled( True ) 321 self._frameRange.setEnabled( True ) 322 if self._allowFrameServer: 323 self._frameserverRender.setVisible(self.frameserverRenderAvailable()) 324 self._useProxy.setVisible( True ) 325 self._continueOnError.setVisible( True )
326 327
328 - def isBackgrounded(self):
329 """Return whether the background rendering option is enabled.""" 330 #Deprecated 331 return self._bgRender.value() and self.backgroundRenderAvailable()
332 333
334 - def isFrameserverEnabled(self):
335 """Return whether the frame server option is enabled.""" 336 return self._frameserverRender.value() and self.frameserverRenderAvailable()
337 338
339 - def backgroundRenderAvailable(self):
340 """Return whether background rendering should be allowed for this render""" 341 return RenderDialog.shouldShowBGRender or self.renderContainsContainers()
342 343
344 - def frameserverRenderAvailable(self):
345 return self._allowFrameServer and not self.renderContainsContainers()
346 347
348 - def extractContainerNodes(self, nodeSelection):
349 containerWriteNodes = [] 350 nonContainerWriteNodes = [] 351 #extract mov nodes from the rest 352 for node in nodeSelection: 353 knob = node.knobs().get("file_type",None) 354 if knob: 355 fileType = knob.value() 356 if fileType in ("mov", "mov32", "mov64", "ffmpeg", "mxf"): 357 containerWriteNodes.append(node) 358 else: 359 nonContainerWriteNodes.append(node) 360 return (nonContainerWriteNodes, containerWriteNodes)
361 362
363 - def isTimelineWrite(self):
364 365 if not nuke.env['studio']: 366 return False 367 368 if len(self._nodeSelection) > 1 or len(self._nodeSelection) < 1: 369 return False 370 371 write = self._nodeSelection[0] 372 373 if write == nuke.root(): 374 ## must be a render of all 'write' nodes, this is tricky as there may be other executable nodes apart from write nodes 375 ## lets assume the write nodes are write, writegeo, particlecache, and diskcache 376 377 # there is a bug here however as there may be groups they are executable which will be skipped right now 378 379 writeNodes = nuke.allNodes( 'Write' ) 380 writeNodes.extend( nuke.allNodes( 'WriteGeo') ) 381 writeNodes.extend( nuke.allNodes( 'ParticleCache') ) 382 writeNodes.extend( nuke.allNodes( 'DiskCache') ) 383 384 if len(writeNodes) > 1: 385 return False 386 387 if len(writeNodes) > 0: 388 write = writeNodes[0] 389 390 timelineWriteNode = None 391 392 try: 393 from foundry.frameserver.nuke.workerapplication import GetWriteNode 394 timelineWriteNode = GetWriteNode() 395 except: 396 pass 397 398 if not timelineWriteNode: 399 return False 400 401 if timelineWriteNode.fullName() != write.fullName(): 402 return False 403 404 ## double check that this script is actually in a timeline 405 try: 406 from hiero.ui import isInAnyProject 407 return isInAnyProject( nuke.scriptName() ) 408 except: 409 pass 410 411 return False
412
413 - def saveFileToRender(self, prefix, forceSaveNew):
414 import datetime 415 now = datetime.datetime.now() 416 timestamp = now.strftime('%H%M_%S.%f_%d-%m-%Y') 417 418 if nuke.env['nc']: 419 nukeExt = ".nknc" 420 if nuke.env['indie']: 421 nukeExt = ".nkind" 422 else: 423 nukeExt = ".nk" 424 425 wasSaved = False 426 #if file is unsaved 427 if nuke.Root().name() == 'Root': 428 fileName = "%s.%s%s"%(prefix,timestamp, nukeExt) 429 filePath = "/".join([os.environ["NUKE_TEMP_DIR"], fileName]) 430 nuke.scriptSaveToTemp(filePath) 431 wasSaved = True 432 else: 433 originalPath = nuke.scriptName() 434 #if file is saved but not up to date 435 if nuke.Root().modified() == True or forceSaveNew: 436 extensionIndex = originalPath.rfind(nukeExt) 437 noExt = originalPath[:extensionIndex] if extensionIndex>=0 else originalPath 438 head, tail = os.path.split(noExt) 439 fileName = "%s.%s.%s%s"%(prefix,tail,timestamp,nukeExt) 440 filePath = "/".join([head,fileName]) 441 try: 442 nuke.scriptSaveToTemp(filePath) 443 wasSaved = True 444 except RuntimeError: 445 nuke.tprint("Exception when saving script to: %s. Saving to NUKE_TEMP_DIR"%filePath) 446 fileName = originalPath.split()[-1] 447 filePath = "/".join([os.environ["NUKE_TEMP_DIR"], fileName]) 448 nuke.scriptSaveToTemp(filePath) 449 wasSaved = True 450 #if file is saved 451 else: 452 filePath = originalPath 453 return (filePath, wasSaved)
454
455 - def getNodeSelection(self):
456 nodeSelection = self._nodeSelection 457 nodesToRenderCount= len(nodeSelection) 458 if nodesToRenderCount==1: 459 if nodeSelection[0] == nuke.root(): 460 nodeSelection = nuke.allNodes("Write") 461 nodeSelection.extend(nuke.allNodes("DeepWrite")) 462 return nodeSelection
463
464 - def renderContainsContainers(self):
465 (_, containerNodesToRender) = self.extractContainerNodes(self.getNodeSelection()) 466 if containerNodesToRender: 467 return True 468 return False
469
470 - def renderToFrameServer(self,frame_ranges, views):
471 nodeSelection = self.getNodeSelection() 472 (nodesToRender, containerNodesToRender) = self.extractContainerNodes(nodeSelection) 473 474 #check views and frame pading 475 for node in nodeSelection: 476 path = node["file"].getText() 477 needsPadding = frame_ranges.getRange(0).frames()>1 478 hasPadding = nuke.filename( node, nuke.REPLACE ) != path or node["file_type"].value()=="mov" 479 if needsPadding and not hasPadding: 480 nuke.message("%s cannot be executed for multiple frames."%node.fullName()) 481 return 482 #there's no way to find if writer can write multiple views per file in python 483 needsViews = (len(views)!=1 or views[0] != "main" ) 484 hasViews = path.find("%v")!=-1 or path.find("%V")!=-1 or node["file_type"].value()=="exr" 485 if needsViews and not hasViews: 486 nuke.message("%s cannot be executed for multiple views."%node.fullName()) 487 return 488 489 #if there's any container to be rendered, show a warning, render synchronously later 490 if containerNodesToRender: 491 if RenderDialog.ShowWarning: 492 from PySide2.QtWidgets import (QMessageBox, QCheckBox) 493 message = "It is currently not possible to render container formats using the frame server. Select Continue to render in the current Nuke session.\n\nPlease see the user guide for more information." 494 messageBox = QMessageBox(QMessageBox.Warning, "Warning", message, QMessageBox.Cancel) 495 messageBox.addButton("Continue",QMessageBox.AcceptRole) 496 dontShowCheckBox = QCheckBox("Don't show this message again") 497 dontShowCheckBox.blockSignals(True) 498 messageBox.addButton(dontShowCheckBox, QMessageBox.ResetRole) 499 result = messageBox.exec_() 500 if result != QMessageBox.AcceptRole: 501 return 502 if dontShowCheckBox.isChecked(): 503 RenderDialog.ShowWarning = False 504 505 import hiero.ui.nuke_bridge.FnNsFrameServer as FrameServer 506 sortRenderOrder = lambda w : w.knobs()["render_order"].getValue() 507 if nodesToRender: 508 nodesToRender.sort(key = sortRenderOrder) 509 name, wasSaved = self.saveFileToRender("tmp_unsaved",False) 510 try: 511 for node in nodesToRender: 512 FrameServer.renderFrames( 513 name, 514 frame_ranges, 515 node.fullName(), 516 self._selectedViews()) 517 finally: 518 if wasSaved: 519 os.unlink(name) 520 521 if containerNodesToRender: 522 containerNodesToRender.sort(key = sortRenderOrder) 523 nuke.executeMultiple(containerNodesToRender, frame_ranges, views, continueOnError = self._continueOnError.value())
524 525
526 - def run(self):
527 ##this will render the full framerange of the script 528 if self.isTimelineWrite() and self._timelineRender.value(): 529 from hiero.ui.nuke_bridge.nukestudio import scriptSaveAndReRender 530 scriptSaveAndReRender() 531 return 532 533 frame_ranges = nuke.FrameRanges(self._frameRange.value().split(',')) 534 views = self._selectedViews() 535 rootProxyMode = nuke.root().proxy() 536 try: 537 nuke.Undo().disable() 538 nuke.root().setProxy(self._useProxy.value()) 539 if self.isFrameserverEnabled(): 540 self.renderToFrameServer(frame_ranges,views) 541 elif self.isBackgrounded(): 542 nuke.executeBackgroundNuke(nuke.EXE_PATH, self._nodeSelection, 543 frame_ranges, views, self._getBackgroundLimits(), continueOnError = self._continueOnError.value()) 544 else: 545 nuke.executeMultiple(self._nodeSelection, frame_ranges, views, continueOnError = self._continueOnError.value()) 546 except RuntimeError, e: 547 if self._exceptOnError or e.args[0][0:9] != "Cancelled": # TO DO: change this to an exception type 548 raise 549 finally: 550 nuke.root().setProxy(rootProxyMode) 551 nuke.Undo().enable()
552
553 -class FlipbookDialog( RenderDialog ):
554 - def _titleString( self ):
555 return "Flipbook"
556
557 - def _idString( self ):
558 return "uk.co.thefoundry.FlipbookDialog"
559
560 - def __init__(self, dialogState, groupContext, node, takeNodeSettings):
561 # Init attributes 562 self._node = node 563 self._takeNodeSettings = takeNodeSettings 564 self._customKnobs = [] 565 566 # init super 567 RenderDialog.__init__(self, dialogState, groupContext) 568 569 # Override the initial frame range value 570 self._state.setKnob(self._rangeEnum, _FRAME_RANGE_INPUT) 571 self._setFrameRangeFromSource(self._rangeEnum.value()) 572 573 if self._takeNodeSettings: 574 self._viewerForSettings.setValue(node.name()) 575 self.knobChanged(self._viewerForSettings)
576
577 - def _addLeadingKnobs( self ):
578 flipbookNames = flipbooking.gFlipbookFactory.getNames() 579 self._flipbookEnum = nuke.Enumeration_Knob( "flipbook", "Flipbook", flipbookNames ) 580 self._flipbookEnum.setTooltip("Select Flipbook Player") 581 defaultFlipbook = nuke.toNode('preferences').knob('defaultFlipbook').getValue() 582 if not defaultFlipbook or not defaultFlipbook in flipbookNames: 583 defaultFlipbook = flipbookNames[0] 584 self._state.setKnob(self._flipbookEnum, defaultFlipbook) 585 self.addKnob( self._flipbookEnum ) 586 587 self._viewerForSettings = nuke.Enumeration_Knob("viewer_settings", "Use Settings From", self._viewers.keys() + [_USE_SETTINGS_FROM_CUSTOM]) 588 self._viewerForSettings.setTooltip("Select your viewer setting for the flipbook") 589 if not self._takeNodeSettings: 590 self._viewerForSettings.setValue(_USE_SETTINGS_FROM_CUSTOM) 591 self.addKnob(self._viewerForSettings)
592
593 - def _addPreKnobs( self ):
594 # Region of Interest knobs 595 self._useRoi = nuke.Boolean_Knob("use_roi", "Enable ROI") 596 self._useRoi.setTooltip("Use Region of Interest (ROI)") 597 self._useRoi.setFlag(nuke.STARTLINE) 598 self._state.setKnob(self._useRoi, False) 599 self.addKnob(self._useRoi) 600 self._roi = nuke.BBox_Knob("roi", "Region of Interest") 601 self._state.setKnob(self._roi, (0, 0, 0, 0)) 602 self.addKnob(self._roi) 603 self._roi.setVisible(self._useRoi.value()) 604 605 # Channel knobs 606 self._channels = nuke.Channel_Knob( "channels_knob", "Channels") 607 self._channels.setTooltip("Select channels for the flipbook") 608 if self._node.Class() == "Write": 609 self._channels.setValue(self._node.knob("channels").value()) 610 else: 611 self._state.setKnob(self._channels, "rgba") 612 self._channels.setFlag(nuke.STARTLINE | nuke.NO_CHECKMARKS) 613 self.addKnob( self._channels )
614
615 - def _addPostKnobs( self ):
616 # LUT knobs 617 self._luts = nuke.Enumeration_Knob("lut", "LUT", nuke.ViewerProcess.registeredNames()) 618 self._luts.setTooltip("Select LUT for the flipbook") 619 if self._takeNodeSettings: 620 self._state.setKnob(self._luts, self._lutFromViewer(self._viewerForSettings.value())) 621 else: 622 self._state.setKnob(self._luts, self._lutFromViewer()) 623 self.addKnob(self._luts) 624 625 self._burnInLUT = nuke.Boolean_Knob("burnin", "Burn in the LUT") 626 self._burnInLUT.setTooltip("Burn in the LUT") 627 self._state.setKnob(self._burnInLUT, False) 628 self.addKnob(self._burnInLUT) 629 630 # Audio knobs 631 audioList = [] 632 audioList.append( "None" ) 633 for node in nuke.allNodes("AudioRead"): 634 audioList.append( node.name() ) 635 self._audioSource = nuke.Enumeration_Knob( "audio", "Audio", audioList ) 636 self._audioSource.setTooltip("Include audio to flipbook") 637 self._state.setKnob(self._audioSource, audioList[0] ) 638 self.addKnob( self._audioSource ) 639 640 super(FlipbookDialog, self)._addPostKnobs() 641 642 self.flipbookKnobs()
643
644 - def flipbookKnobs(self):
645 try: 646 beforeKnobs = self.knobs() 647 flipbookToRun = flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value()) 648 flipbookToRun.dialogKnobs(self) 649 afterKnobs = self.knobs() 650 self._customKnobs = list(set(beforeKnobs.values()) ^ set(afterKnobs.values())) 651 except NotImplementedError: 652 pass
653
654 - def _getDefaultViews(self):
655 return nuke.activeViewer().view()
656
657 - def _addViewKnob(self):
658 oc = nuke.OutputContext() 659 self._views = [oc.viewname(i) for i in xrange(1, oc.viewcount())] 660 if (oc.viewcount() > 2): 661 supportedViews = self._selectedFlipbook().capabilities()["maximumViews"] 662 if (int(supportedViews) > 1): 663 self._viewSelection = nuke.MultiView_Knob("views", "Views") 664 else: 665 self._viewSelection = nuke.OneView_Knob("views", "View", self._views) 666 activeView = nuke.activeViewer().view() 667 if activeView == "": 668 activeView = self._views[0] 669 670 # Retrieve previous view selection or default to selecting all available views 671 previousViews = self._state.getValue(self._viewSelection.name(), " ".join(self._views)).split() 672 # Get the intersection of the previous selection and the available views 673 viewsToRestore = set(self._views).intersection(previousViews) 674 if viewsToRestore: 675 self._viewSelection.setValue(" ".join(viewsToRestore)) 676 else: 677 self._viewSelection.setValue(activeView) 678 self.addKnob(self._viewSelection) 679 self._viewSelection.clearFlag(nuke.NO_MULTIVIEW)
680
681 - def _selectedFlipbook(self):
682 return flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value())
683
684 - def _lutFromViewer(self, viewerName = ""):
685 try: 686 if viewerName == "": 687 return nuke.ViewerProcess.node().knob("current").value() 688 else: 689 return nuke.ViewerProcess.node(viewer=viewerName).knob("current").value() 690 except AttributeError: 691 return "None"
692
693 - def _isViewerSettingKnob(self, knob):
694 return knob == self._useRoi or knob == self._roi or knob == self._channels or knob == self._useProxy or knob == self._frameRange or knob == self._rangeEnum or knob == self._luts
695
696 - def _setKnobAndStore(self, knob, val):
697 knob.setValue(val) 698 self._state.save(knob)
699
700 - def knobChanged(self, knob):
701 RenderDialog.knobChanged(self, knob) 702 if (knob == self._viewerForSettings): 703 if self._viewerForSettings.value() != _USE_SETTINGS_FROM_CUSTOM: 704 viewer = self._viewers[self._viewerForSettings.value()] 705 self._setKnobAndStore(self._useRoi, viewer.roiEnabled()) 706 roi = viewer.roi() 707 if roi != None: 708 self._roi.fromDict(roi) 709 self._state.save(self._roi) 710 self._channels.fromScript(viewer.knob("channels").toScript()) 711 self._state.save(self._channels) 712 self._setKnobAndStore(self._useProxy, nuke.root().proxy()) 713 self._frameRangeFromViewer(viewer.name(), _FRAME_RANGE_INOUT) 714 self._state.save(self._frameRange) 715 self._setKnobAndStore(self._rangeEnum, viewer.name() + '/' + _FRAME_RANGE_INOUT) 716 self._roi.setVisible(self._useRoi.value()) 717 self._setKnobAndStore(self._luts, self._lutFromViewer(viewer.name())) 718 elif (knob == self._useRoi): 719 self._roi.setVisible(self._useRoi.value()) 720 elif self._isViewerSettingKnob(knob): 721 self._viewerForSettings.setValue(_USE_SETTINGS_FROM_CUSTOM) 722 self._state.save(self._viewerForSettings) 723 elif knob == self._luts: 724 self._burnInLUT.setEnabled(self._luts.value() != "None") 725 726 if knob == self._flipbookEnum: 727 for k in self._dialogKnobs: 728 self.removeKnob(k) 729 self.removeKnob(self.okButton) 730 self.removeKnob(self.cancelButton) 731 self._customKnobs = [] 732 self._addKnobs() 733 self._makeOkCancelButton() 734 elif knob in self._customKnobs: 735 try: 736 flipbookToRun = flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value()) 737 flipbookToRun.dialogKnobChanged(self, knob) 738 except NotImplementedError: 739 pass
740
741 - def _getIntermediateFileType(self):
742 return _gFlipbookDialogState.getValue('intermediateFormat', 'exr')
743
744 - def _getIntermediatePath(self):
745 """Get the path for the temporary files. May be filled in using printf syntax.""" 746 flipbooktmp="" 747 if flipbooktmp == "": 748 try: 749 flipbooktmp = self._selectedFlipbook().cacheDir() 750 except: 751 try: 752 flipbooktmp = os.environ["NUKE_DISK_CACHE"] 753 except: 754 flipbooktmp = nuke.value("preferences.DiskCachePath") 755 756 if len(self._selectedViews()) > 1: 757 flipbookFileNameTemp = "nuke_tmp_flip.%04d.%V." + self._getIntermediateFileType() 758 else: 759 flipbookFileNameTemp = "nuke_tmp_flip.%04d." + self._getIntermediateFileType() 760 flipbooktmpdir = os.path.join(flipbooktmp, "flipbook") 761 if not os.path.exists(flipbooktmpdir): 762 os.mkdir(flipbooktmpdir) 763 764 if not os.path.isdir(flipbooktmpdir): 765 raise RuntimeError("%s already exists and is not a directory, please delete before flipbooking again" % flipbooktmpdir) 766 flipbooktmp = os.path.join(flipbooktmpdir, flipbookFileNameTemp) 767 768 if nuke.env['WIN32']: 769 flipbooktmp = re.sub(r"\\", "/", str(flipbooktmp)) 770 return flipbooktmp
771
772 - def _requireIntermediateNode(self, nodeToTest):
773 if nodeToTest.Class() == "Read" or nodeToTest.Class() == "Write": 774 flipbookToRun = flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value()) 775 flipbookCapabilities = flipbookToRun.capabilities() 776 777 # Check if we can read it in directly.. 778 filePath = nuke.filename(nodeToTest) 779 # There might be a prefix that overrides the extension, if so, this will 780 # confuse the flipbook probably, so just create a render. 781 if ':' in filePath: 782 readerPrefix = filePath.split(':')[0] 783 if len(readerPrefix) > 1: # 1 is a drive letter 784 return True 785 fileExtension = os.path.splitext(filePath)[1].lower()[1:] 786 787 # Check if the flipbook supports the file extension. Allow a wildcard to 788 # indicate 'all files' (such as for the internal flipbook functionality) 789 supportedFileTypes = flipbookCapabilities.get("fileTypes", []) 790 flipbookSupportsFileType = (supportedFileTypes == ["*"]) or (fileExtension in supportedFileTypes) 791 if not flipbookSupportsFileType: 792 return True 793 794 # Not all flipbooks can handle weird channels 795 flipbookSupportsArbitraryChannels = flipbookCapabilities.get("arbitraryChannels", False) 796 if self._channels.value() not in set(["rgb", "rgba", "alpha"]) and not flipbookSupportsArbitraryChannels: 797 return True 798 channelKnob = nodeToTest.knob("channels") 799 if channelKnob != None and channelKnob.value() != self._channels.value(): 800 return True 801 802 ## if flipbook doesn't support roi and the roi option is on we need the intermediate node because we're going to insert a crop 803 if self._useRoi.value() and self._useRoi.enabled(): 804 if not flipbookCapabilities.get("roi", False): 805 return True 806 807 if self._burnInLUT.value() and self._burnInLUT.enabled(): 808 return True 809 810 # Separate view files 811 # TODO Temporary workaround to deal with MediaSource not being aware of 812 # the %V notation for specifiying separate view files. 813 if "%V" in filePath or "%v" in filePath: 814 return True 815 816 return False 817 else: 818 return True
819
820 - def _getBurninWriteColorspace(self):
821 """Helper function to get the appropriate colorspace to set on the Write node when burning the 822 viewer color transform into the render.""" 823 824 writeColorspace = None 825 826 lut = self._getLUT() 827 828 # Determine whether we're using original Nuke, rather than OCIO, color management. 829 usingNukeColorspaces = False 830 rootNode = nuke.root() 831 if rootNode: 832 colorManagementKnob = rootNode.knob("colorManagement") 833 if colorManagementKnob: 834 usingNukeColorspaces = (colorManagementKnob.value() == "Nuke") # Note: Must use value() rather than getValue(). 835 836 if usingNukeColorspaces: 837 838 # We're using Nuke colorspace management so the our lut knob will correspond to the appropriate 839 # colorspace name, except for rec1886, which we need to map to Gamma2.4. 840 # If the lut is the special case of None then don't set writeColorspace (to match the original behaviour). 841 # (The rec1886 -> Gamma2.4 mapping matches what's done in register_default_viewer_processes, in 842 # NukeScripts/src/ViewerProcess.py - I suppose we could consider adding something into 843 # nuke/src/Python/PythonObjects/ViewerProcess.cpp to allow the python code to look-up into ViewerProcessMap 844 # so we could parse the registered args to obtain the colorspace but that seems like overkill and not much 845 # less fragile.) 846 if lut == "rec1886": 847 writeColorspace = "Gamma2.4" 848 elif lut != "None": 849 writeColorspace = lut 850 851 else: 852 853 # We're using OCIO color management so we expect our lut string to contain a view name 854 # followed by a single space and a display name in parantheses. 855 # For example, the aces 1.0.1 config has lots of views but all for the display called ACES, 856 # hence here we might get lut set to, for example, 857 # DCDM P3 gamut clip (ACES) 858 # As another example, the spi-vfx config has views for two displays, DCIP3 and sRGB, so we might get 859 # Film (DCIP3) 860 # or 861 # Film (sRGB) 862 # etc. 863 # The nuke-default config has a single display called 'default' so we get lut set to 864 # None (default) 865 # or 866 # sRGB (default) 867 # etc. The nuke-default case is a bit confusing because the _view_ names almost match colorspace names 868 # and also correspond to legacy Nuke colorspace managament options. 869 # 870 # Anyway, what we actually need to return is the actual colorspace the particualr combination of 871 # view and display name map to, as defined in the relevant OCIO config file. 872 873 displayName = "" 874 viewName = "" 875 876 NUKE_LUT_FORMAT = '(.*) \((.*)\)$' 877 matchResult = re.match( NUKE_LUT_FORMAT , lut) 878 if matchResult is not None: 879 880 viewName = matchResult.group(1) 881 displayName = matchResult.group(2) 882 883 if rootNode: 884 writeColorspace = rootNode.getOCIOColorspaceFromViewTransform(displayName, viewName) 885 886 return writeColorspace
887 888
889 - def _createIntermediateNode(self):
890 """Create a write node to render out the current node so that output may be used for flipbooking.""" 891 flipbooktmp = self._getIntermediatePath() 892 893 fieldname = "file" 894 if self._useProxy.value(): 895 fieldname = "proxy" 896 897 fixup = nuke.createNode("Group", "tile_color 0xff000000", inpanel = False) 898 with fixup: 899 fixup.setName("Flipbook") 900 inputNode = nuke.createNode("Input", inpanel = False) 901 shuffle = nuke.createNode("Shuffle", inpanel = False) 902 shuffle.knob("in").setValue(self._channels.value()) 903 if self._useRoi.value(): 904 crop = nuke.createNode( "Crop", inpanel = False ) 905 crop['box'].fromScript( self._roi.toScript() ) 906 write = nuke.createNode("Write", fieldname+" {"+flipbooktmp+"}", inpanel = False) 907 write.knob('file_type').setValue(self._getIntermediateFileType()) 908 selectedViews = self._selectedViews() 909 write.knob('views').fromScript(" ".join(selectedViews)) 910 911 if self._getIntermediateFileType() == "exr": 912 write.knob('compression').setValue("B44") 913 # Set the 'heroview' to be the first of the selected views. If we don't 914 # do this then then 'heroview' is by default set to be 1 which may not 915 # be a valid view for this clip. The 'heroview' is used as a fallback if 916 # no view has been set on the reader. This assumes the user has selected 917 # sensible views if they haven't then the write may still fail. 918 if len(selectedViews) > 0: 919 firstView = nuke.OutputContext().viewFromName(selectedViews[0]) 920 write.knob('heroview').setValue(firstView) 921 922 writeColorspace = "" 923 924 if self._burnInLUT.value(): 925 # The user has chosen to burn the viewer transform into the intermedate render so set the colorspace 926 # on the Write node appropriately. 927 writeColorspace = self._getBurninWriteColorspace() 928 else: 929 # The viewer transform is not being burnt into the intermediate render, set the Write node's colorspace 930 # to whatever the current working space is - when reading the file back in the flipbook we'll ssume 931 # the media was written out in the working space. 932 rootNode = nuke.root() 933 if rootNode: 934 workingSpaceKnob = rootNode.knob("workingSpaceLUT") 935 if workingSpaceKnob: 936 writeColorspace = workingSpaceKnob.value() 937 938 if writeColorspace: 939 write.knob('colorspace').setValue(writeColorspace) 940 941 outputNode = nuke.createNode("Output", inpanel = False) 942 #If called on a Viewer connect fixup node to the one immediately above if exists. 943 if self._node.Class() == "Viewer": 944 fixup.setInput(0, self._node.input(int(nuke.knob(self._node.fullName()+".input_number")))) 945 else: 946 fixup.setInput(0, self._node) 947 948 return (fixup, write)
949
950 - def _getLUT(self):
951 return self._luts.value()
952
953 - def _getAudio(self):
954 nukeNode = nuke.toNode( self._audioSource.value() ) 955 ret = "" 956 if nukeNode != None: 957 ret = nukeNode["file"].getEvaluatedValue() 958 959 return ret
960
961 - def _getOptions(self, nodeToFlipbook):
962 options = { 963 } 964 965 try: 966 options['pixelAspect'] = float(nuke.value(nodeToFlipbook.name()+".pixel_aspect")) 967 except: 968 pass 969 970 try: 971 f = nodeToFlipbook.format() 972 options['dimensions'] = { 'width' : f.width(), 'height' : f.height() } 973 except: 974 pass 975 976 # LUT 977 if not self._burnInLUT.value(): 978 inputColourspace = "linear" 979 outputColourspace = "linear" 980 # Check if we have a different than linear input 981 if self._node.Class() == "Read" or self._node.Class() == "Write": 982 lut = self._node.knob("colorspace").value() 983 # Might be in the format of "default (foo)", if so, get at "foo". 984 if lut[:7] == "default": 985 lut = lut[9:-1] 986 inputColourspace = lut 987 988 # Check our output 989 lut = self._getLUT() 990 if lut != "None": 991 outputColourspace = lut 992 993 if inputColourspace == outputColourspace: 994 options["lut"] = inputColourspace 995 else: 996 options["lut"] = inputColourspace + "-" + outputColourspace 997 # AUDIO 998 audioTrack = self._getAudio() 999 if audioTrack != "": 1000 options["audio"] = audioTrack 1001 1002 # ROI 1003 if self._useRoi.value(): 1004 roi = self._roi.toDict() 1005 if (roi["r"] - roi["x"] > 0) and (roi["t"] - roi["y"] > 0): 1006 options["roi"] = bboxToTopLeft(int(nuke.value(nodeToFlipbook.name()+".actual_format.height")), roi) 1007 1008 1009 return options
1010
1011 - def run(self):
1012 flipbookToRun = flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value()) 1013 if (flipbookToRun): 1014 if not os.access(flipbookToRun.path(), os.X_OK): 1015 raise RuntimeError("%s cannot be executed (%s)." % (flipbookToRun.name(), flipbookToRun.path(),) ) 1016 1017 nodeToFlipbook = None 1018 rootProxyMode = nuke.root().proxy() 1019 try: 1020 # Need this to prevent Bug 5295 1021 nuke.Undo().disable() 1022 nuke.root().setProxy(self._useProxy.value()) 1023 1024 calledOnNode = self._node 1025 if self._node.Class() == "Viewer": 1026 self._node = self._node.input(int(self._node.knob("input_number").value())) 1027 1028 renderer = getFlipbookRenderer(self, flipbookToRun) 1029 renderer.doFlipbook() 1030 1031 except Exception, e: 1032 import traceback 1033 traceback.print_exc(file=sys.stdout) 1034 print "exception ",e 1035 finally: 1036 #if an intermediate node was created, delete it 1037 if self._node != renderer._nodeToFlipbook: 1038 nuke.delete(renderer._nodeToFlipbook) 1039 nuke.root().setProxy(rootProxyMode) 1040 nuke.Undo().enable() 1041 else: 1042 raise RuntimeError("No flipbook called " + self._flipbookEnum.value() + " found. Was it deregistered while the dialog was open?")
1043
1044 -class ViewerCaptureDialog( FlipbookDialog ):
1045 - def _titleString( self ):
1046 return "Capture"
1047
1048 - def _idString( self ):
1049 return "uk.co.thefoundry.ViewerCaptureDialog"
1050
1051 - def __init__(self, dialogState, groupContext, node):
1052 1053 # init super 1054 FlipbookDialog.__init__(self, dialogState, groupContext, node, True) 1055 self._frameserverRender.setVisible(False) 1056 self._bgRender.setVisible(False) 1057 self._useProxy.setVisible(False) 1058 self._continueOnError.setVisible(False) 1059 1060 self._viewerForSettings.setVisible(False) 1061 1062 self._useRoi.setVisible(False) 1063 self._roi.setVisible(False) 1064 1065 self._channels.setVisible(False) 1066 self._luts.setVisible(False) 1067 1068 self._audioSource.setVisible(False) 1069 self._burnInLUT.setVisible(False) 1070 1071 try: 1072 ## this may not exist, just ignore if not present 1073 self._viewSelection.setVisible(False) 1074 except: 1075 pass 1076 1077 customWriteActive = node['file'].getValue() != self._getIntermediatePath() and node['file'].getValue() != '' 1078 self._customWrite = nuke.Boolean_Knob( 'custom', 'Customise write path' ) 1079 self._customWrite.setValue( customWriteActive ) 1080 self.addKnob( self._customWrite ) 1081 1082 self._noFlipbook = nuke.Boolean_Knob( 'write', 'No flipbook' ) 1083 self._noFlipbook.setFlag( nuke.STARTLINE ) 1084 self._noFlipbook.setVisible( customWriteActive ) 1085 self._noFlipbook.setValue( self._state.get( self._noFlipbook, False ) ) 1086 self.addKnob( self._noFlipbook ) 1087 1088 self._file = nuke.File_Knob( 'file', 'Write path' ) 1089 defaultPath = self._node['file'].value() 1090 if defaultPath == self._getIntermediatePath(): 1091 defaultPath = '' 1092 self._file.setValue ( self._state.get(self._file, defaultPath ) ) 1093 self._file.setVisible( customWriteActive ) 1094 1095 self.addKnob( self._file )
1096
1097 - def knobChanged( self, knob ):
1098 try: 1099 FlipbookDialog.knobChanged(self, knob) 1100 self._frameserverRender.setVisible(False) 1101 if (knob == self._customWrite): 1102 self._noFlipbook.setVisible( self._customWrite.value() ) 1103 self._file.setVisible( self._customWrite.value() ) 1104 if ( not self._customWrite.value() ): 1105 self._node['file'].setValue( self._getIntermediatePath() ) 1106 elif( knob == self._noFlipbook ): 1107 self._flipbookEnum.setEnabled( not self._noFlipbook.value() ) 1108 except: 1109 pass
1110
1111 - def _getIntermediateFileType(self):
1112 return 'jpg'
1113
1114 - def captureViewer(self):
1115 """ Return an instance of a CaptureViewer class, which when executed captures the viewer. 1116 """ 1117 flipbook = flipbooking.gFlipbookFactory.getApplication(self._flipbookEnum.value()) 1118 if flipbook and not os.access(flipbook.path(), os.X_OK): 1119 raise RuntimeError("%s cannot be executed (%s)." % (flipbook.name(), flipbook.path(),) ) 1120 1121 # build up the args 1122 frameRange = self._frameRange.value() 1123 viewer = self._node 1124 selectedViews = self._selectedViews() 1125 defaultWritePath = self._getIntermediatePath() 1126 customWritePath = self._file.value() if self._customWrite.value() else "" 1127 doFlipbook = not self._noFlipbook.value() 1128 doCleanup = False 1129 1130 return captureViewer.CaptureViewer(flipbook, frameRange, viewer, selectedViews, defaultWritePath, customWritePath, doFlipbook, doCleanup)
1131
1132 -def _showDialog( dialog ):
1133 """ Shows the with showModalDialog() and then calls dialog.run() if it returns True """ 1134 # If there is a viewer playing, stop it, it interferes with updating knobs on the dialog 1135 if nuke.activeViewer(): 1136 nuke.activeViewer().stop() 1137 1138 if (dialog.showModalDialog() == True): 1139 dialog.run()
1140
1141 -def showExecuteDialog(nodesToExecute, exceptOnError = True):
1142 """Present a dialog that executes the given list of nodes.""" 1143 groupContext = nuke.root() 1144 d = ExecuteDialog(_gRenderDialogState, groupContext, nodesToExecute, exceptOnError) 1145 _showDialog(d) 1146
1147 -def showRenderDialog(nodesToRender, exceptOnError = True, allowFrameServer = True):
1148 """Present a dialog that renders the given list of nodes.""" 1149 groupContext = nuke.root() 1150 d = RenderDialog(_gRenderDialogState, groupContext, nodesToRender, exceptOnError, allowFrameServer) 1151 _showDialog(d)
1152
1153 -def _getFlipbookDialog(node, takeNodeSettings = False):
1154 """Returns the flipbook dialog object created when flipbooking node""" 1155 if node is None: 1156 raise RuntimeError("Can't launch flipbook, require a node."); 1157 if node.Class() == "Viewer" and node.inputs() == 0: 1158 raise RuntimeError("Can't launch flipbook, there is nothing connected to the viewed input."); 1159 1160 if not (nuke.canCreateNode("Write")): 1161 nuke.message("Flipbooking is not permitted in Nuke Assist") 1162 return 1163 1164 groupContext = nuke.root() 1165 1166 e = FlipbookDialog(_gFlipbookDialogState, groupContext, node, takeNodeSettings) 1167 return e
1168
1169 -def showFlipbookDialog(node, takeNodeSettings = False):
1170 """Present a dialog that flipbooks the given node.""" 1171 e = _getFlipbookDialog( node, takeNodeSettings ) 1172 _showDialog(e)
1173 1174 # because the capture button could have been triggered via a progress bar in update_handles 1175 # we defer the capture run until the main event loop is pumped
1176 -class ViewerCaptureDialogThread(Thread):
1177 - def __init__(self, captureViewer):
1178 Thread.__init__(self) 1179 self.captureViewer = captureViewer
1180
1181 - def run(self):
1182 ## only runs the dialog capture function when we are outside update_handles 1183 utils.executeInMainThreadWithResult( self.captureViewer, )
1184
1185 -def showViewerCaptureDialog(node):
1186 if node is None: 1187 raise RuntimeError("Can't launch flipbook, requires a viewer node."); 1188 if node.Class() != "Viewer" and node.inputs() == 0: 1189 raise RuntimeError("Can't launch flipbook, this is not a viewer node."); 1190 1191 if not (nuke.canCreateNode("Write")): 1192 nuke.message("Viewer capture is not permitted in Nuke Assist") 1193 return 1194 1195 groupContext = nuke.root() 1196 e = ViewerCaptureDialog(_gViewerCaptureDialogState, groupContext, node) 1197 if (e.showModalDialog() == True): 1198 # Bug 38516 - Be careful about what gets passed to the ViewerCaptureDialogThread, since anything 1199 # created here will be destroyed in a different thread. This can cause problems, such as crashes, 1200 # if a Qt widget gets passed. 1201 captureViewer = e.captureViewer() 1202 thread = ViewerCaptureDialogThread(captureViewer) 1203 thread.start()
1204
1205 -def showFlipbookDialogForSelected():
1206 """Present a dialog that flipbooks the currently selected node.""" 1207 try: 1208 showFlipbookDialog(nuke.selectedNode()) 1209 except ValueError, ve: 1210 raise RuntimeError("Can't launch flipbook, %s." % (ve.args[0]))
1211
1212 -def bboxToTopLeft(height, roi):
1213 """Convert the roi passed from a origin at the bottom left to the top left. 1214 Also replaces the r and t keys with w and h keys. 1215 @param height: the height used to determine the top. 1216 @param roi: the roi with a bottom left origin, must have x, y, r & t keys. 1217 @result dict with x, y, w & h keys""" 1218 topLeftRoi = { 1219 "x": roi["x"], 1220 "y": height - roi["y"] - (roi["t"] - roi["y"]), 1221 "w": roi["r"] - roi["x"], 1222 "h": roi["t"] - roi["y"] } 1223 return topLeftRoi
1224
1225 -def setRenderDialogDefaultOption(name, value):
1226 """ Set a particular option to the given value. The type of the value differs per option, giving the wrong value may result in exceptions. The options are read every time the dialog is opened, though not every knob in the dialog has it's value stored.""" 1227 _gRenderDialogState.saveValue(name, value)
1228
1229 -def setFlipbookDefaultOption(name, value):
1230 """ Set a particular option to the given value. The type of the value differs per option, giving the wrong value may result in exceptions. The options are read every time the dialog is opened, though not every knob in the dialog has it's value stored.""" 1231 _gFlipbookDialogState.saveValue(name, value)
1232