def PopulateSubmitter(gui): """ This function populates an instance of DeadlineTab with the UI controls that make up the submission dialog. This tab is instantiated by Katana every time the user selects "Tabs -> Thinkbox -> Submit to Deadline" from the menu bar in Katana. Essentially, this function serves as a deferred __init__ implementation for the tab class that can be easily updated via the Deadline repository. :param gui: An instance of DeadlineTab from the Client folder. The instance gets created upon selecting the "Deadline" tab option in Katana. The tab class itself is defined in the Client file (deployed to localappdata/.../katanasubmitter/tabs directory) and is a skeleton class that invokes this function to populate the tab with UI controls and assign them to attributes of the tab instance. """ global submissionInfo print("Grabbing submitter info...") try: stringSubInfo = CallDeadlineCommand([ "-prettyJSON", "-GetSubmissionInfo", "Pools", "Groups", "MaxPriority", "UserHomeDir", "RepoDir:submission/Katana/Main", "RepoDir:submission/Integration/Main", ], useDeadlineBg=True) output = json.loads(stringSubInfo, encoding="utf-8") except: print("Unable to get submitter info from Deadline:\n\n" + traceback.format_exc()) raise if output["ok"]: submissionInfo = output["result"] else: print( "DeadlineCommand returned a bad result and was unable to grab the submitter info.\n\n" + output["result"]) raise ValueError(output["result"]) # Create a widget with a vertical box layout as a container for widgets to include in the tab scrollWidget = QWidget() scrollLayout = QGridLayout(scrollWidget) scrollLayout.setSpacing(4) scrollLayout.setContentsMargins(4, 4, 4, 4) buttonLayout = QHBoxLayout() # First layout: General options scrollLayout.addWidget(CreateSeparator("Job Description"), 0, 0, 1, 3) jobNameLabel = QLabel("Job Name") jobNameLabel.setToolTip( "The name of your job. This is optional, and if left blank, it will default to 'Untitled'." ) scrollLayout.addWidget(jobNameLabel, 1, 0) gui.jobNameWidget = QLineEdit( os.path.basename(FarmAPI.GetKatanaFileName()).split('.')[0]) scrollLayout.addWidget(gui.jobNameWidget, 1, 1, 1, 1) commentLabel = QLabel("Comment") commentLabel.setToolTip( "A simple description of your job. This is optional and can be left blank." ) scrollLayout.addWidget(commentLabel, 2, 0) gui.commentWidget = QLineEdit("") scrollLayout.addWidget(gui.commentWidget, 2, 1, 1, 1) departmentLabel = QLabel("Department") departmentLabel.setToolTip( "The department you belong to. This is optional and can be left blank." ) scrollLayout.addWidget(departmentLabel, 3, 0) gui.departmentWidget = QLineEdit("") scrollLayout.addWidget(gui.departmentWidget, 3, 1, 1, 1) # Second layout: Job options scrollLayout.addWidget(CreateSeparator("Job Options"), 4, 0, 1, 3) pools = submissionInfo["Pools"] poolLabel = QLabel("Pool") poolLabel.setToolTip("The pool that your job will be submitted to.") scrollLayout.addWidget(poolLabel, 5, 0) gui.poolsWidget = QComboBox() gui.poolsWidget.addItems(pools) scrollLayout.addWidget(gui.poolsWidget, 5, 1) secondPoolLabel = QLabel("Secondary Pool") secondPoolLabel.setToolTip( "The secondary pool lets you specify a pool to use if the primary pool does not have any available Slaves." ) scrollLayout.addWidget(secondPoolLabel, 6, 0) gui.secondPoolsWidget = QComboBox() gui.secondPoolsWidget.addItems(pools) scrollLayout.addWidget(gui.secondPoolsWidget, 6, 1) groups = submissionInfo["Groups"] groupLabel = QLabel("Group") groupLabel.setToolTip("The group that your job will be submitted to.") scrollLayout.addWidget(groupLabel, 7, 0) gui.groupWidget = QComboBox() gui.groupWidget.addItems(groups) scrollLayout.addWidget(gui.groupWidget, 7, 1) priorityLabel = QLabel("Priority") priorityLabel.setToolTip( "A job can have a numeric priority from 0 to 100, where 0 is the lowest priority and 100 is the highest." ) scrollLayout.addWidget(priorityLabel, 8, 0) maxPriority = submissionInfo["MaxPriority"] gui.priorityBox = QSpinBox() gui.priorityBox.setMinimum(0) gui.priorityBox.setMaximum(maxPriority) scrollLayout.addWidget(gui.priorityBox, 8, 1) taskTimeoutLabel = QLabel("Task Timeout") taskTimeoutLabel.setToolTip( "The number of minutes a Slave has to render a task for this job before it requeues it. Specify 0 for no limit." ) scrollLayout.addWidget(taskTimeoutLabel, 9, 0) gui.taskTimeoutBox = QSpinBox() gui.taskTimeoutBox.setMinimum(0) gui.taskTimeoutBox.setMaximum(10000) scrollLayout.addWidget(gui.taskTimeoutBox, 9, 1) concurrentTasksLabel = QLabel("Concurrent Tasks") concurrentTasksLabel.setToolTip( "The number of tasks that can render concurrently on a single Slave. This is useful if the rendering application only uses one thread to render and your Slaves have multiple CPUs." ) scrollLayout.addWidget(concurrentTasksLabel, 10, 0) gui.concurrentTasksWidget = QSpinBox() scrollLayout.addWidget(gui.concurrentTasksWidget, 10, 1) gui.concurrentTasksWidget.setMinimum(1) gui.concurrentTasksWidget.setMaximum(16) gui.limitTasksSlaveLimit = QCheckBox("Limit Tasks To Slave's Task Limit") gui.limitTasksSlaveLimit.setToolTip( "If you limit the tasks to a Slave's task limit, then by default, the Slave won't dequeue more tasks then it has CPUs. This task limit can be overridden for individual Slaves by an administrator." ) scrollLayout.addWidget(gui.limitTasksSlaveLimit, 10, 2) machineLimitLabel = QLabel("Machine Limit") machineLimitLabel.setToolTip( "Use the Machine Limit to specify the maximum number of machines that can render your job at one time. Specify 0 for no limit." ) scrollLayout.addWidget(machineLimitLabel, 11, 0) gui.machineLimitWidget = QSpinBox() scrollLayout.addWidget(gui.machineLimitWidget, 11, 1) gui.isBlackListWidget = QCheckBox("Machine List Is Blacklist") gui.isBlackListWidget.setToolTip( "You can force the job to render on specific machines by using a whitelist, or you can avoid specific machines by using a blacklist." ) scrollLayout.addWidget(gui.isBlackListWidget, 11, 2) machineListLabel = QLabel("Machine List") machineListLabel.setToolTip( "The whitelisted or blacklisted list of machines.") scrollLayout.addWidget(machineListLabel, 12, 0) machineListLayout = QHBoxLayout() gui.machineListWidget = QLineEdit("") machineListLayout.addWidget(gui.machineListWidget) getMachineListWidget = QPushButton("...") getMachineListWidget.pressed.connect( lambda: BrowseMachineList(gui.machineListWidget)) machineListLayout.addWidget(getMachineListWidget) scrollLayout.addLayout(machineListLayout, 12, 1, 1, 2) limitsLabel = QLabel("Limits") limitsLabel.setToolTip("The Limits that your job requires.") scrollLayout.addWidget(limitsLabel, 13, 0) limitsLayout = QHBoxLayout() gui.limitsWidget = QLineEdit("") limitsLayout.addWidget(gui.limitsWidget) getLimitsWidget = QPushButton("...") getLimitsWidget.pressed.connect(lambda: BrowseLimitList(gui.limitsWidget)) limitsLayout.addWidget(getLimitsWidget) scrollLayout.addLayout(limitsLayout, 13, 1, 1, 2) dependenciesLabel = QLabel("Dependencies") dependenciesLabel.setToolTip( "Specify existing jobs that this job will be dependent on. This job will not start until the specified dependencies finish rendering." ) scrollLayout.addWidget(dependenciesLabel, 14, 0) dependenciesLayout = QHBoxLayout() gui.dependenciesWidget = QLineEdit("") dependenciesLayout.addWidget(gui.dependenciesWidget) getDependenciesWidget = QPushButton("...") getDependenciesWidget.pressed.connect( lambda: BrowseDependencyList(gui.dependenciesWidget)) dependenciesLayout.addWidget(getDependenciesWidget) scrollLayout.addLayout(dependenciesLayout, 14, 1, 1, 2) onJobCompleteLabel = QLabel("On Job Complete") onJobCompleteLabel.setToolTip( "If desired, you can automatically archive or delete the job when it completes." ) scrollLayout.addWidget(onJobCompleteLabel, 15, 0) gui.onJobCompleteWidget = QComboBox() gui.onJobCompleteWidget.addItems(["Nothing", "Archive", "Delete"]) scrollLayout.addWidget(gui.onJobCompleteWidget, 15, 1) gui.submitSuspendedWidget = QCheckBox("Submit Job as Suspended") gui.submitSuspendedWidget.setToolTip( "If enabled, the job will submit in the suspended state. This is useful if you don't want the job to start rendering right away. Just resume it from the Monitor when you want it to render." ) scrollLayout.addWidget(gui.submitSuspendedWidget, 15, 2) # Third layout: Katana options scrollLayout.addWidget(CreateSeparator("Katana Options"), 16, 0, 1, 3) frameRangeLabel = QLabel("Frame Range") frameRangeLabel.setToolTip("The list of frames to render.") scrollLayout.addWidget(frameRangeLabel, 17, 0) gui.frameRangeWidget = QLineEdit("") # Populate based on frame range scrollLayout.addWidget(gui.frameRangeWidget, 17, 1, 1, 1) frameRange = FarmAPI.GetSceneFrameRange() gui.frameRangeWidget.setText( str(frameRange['start']) + "-" + str(frameRange['end'])) gui.submitSceneBox = QCheckBox("Submit Katana Scene File") gui.submitSceneBox.setToolTip( "If this option is enabled, the scene file will be submitted with the job, and then copied locally to the Slave machine during rendering." ) scrollLayout.addWidget(gui.submitSceneBox, 17, 2) framesPerTaskLabel = QLabel("Frames Per Task") framesPerTaskLabel.setToolTip( "This is the number of frames that will be rendered at a time for each job task." ) scrollLayout.addWidget(framesPerTaskLabel, 18, 0) gui.framesPerTaskWidget = QSpinBox() gui.framesPerTaskWidget.setMinimum(1) scrollLayout.addWidget(gui.framesPerTaskWidget, 18, 1, 1, 1) gui.useWorkingDirectory = QCheckBox("Use Working Directory") gui.useWorkingDirectory.setToolTip( "If enabled, the current working directory will be used during rendering. This is required if your Katana project file contains relative paths." ) gui.useWorkingDirectory.setChecked(True) scrollLayout.addWidget(gui.useWorkingDirectory, 18, 2) renderNodeSelectLabel = QLabel("Render Node Submission") renderNodeSelectLabel.setToolTip( "Choose to render the whole scene, render all nodes as separate jobs, or render separate nodes" ) scrollLayout.addWidget(renderNodeSelectLabel, 19, 0) gui.renderSelectBox = QComboBox() gui.renderSelectBox.addItems( ["Submit All Render Nodes As Separate Jobs", "Select Render Node"]) scrollLayout.addWidget(gui.renderSelectBox, 19, 1) gui.includeImageWrite = QCheckBox("Include ImageWrite Nodes") gui.includeImageWrite.setToolTip( "If enabled, ImageWrite nodes will be included for submission.") scrollLayout.addWidget(gui.includeImageWrite, 19, 2) renderNodeLabel = QLabel("Render Node") renderNodeLabel.setToolTip( "Set the render node to render with, or leave blank to use the node already set." ) scrollLayout.addWidget(renderNodeLabel, 20, 0) gui.frameDependent = QCheckBox("Submit Jobs As Frame Dependent") gui.frameDependent.setToolTip( "If enabled, the Katana Job(s) will have Frame Dependencies. If your scene contains static content, do not use!" ) scrollLayout.addWidget(gui.frameDependent, 20, 2) gui.renderNodeBox = QComboBox() gui.renderSelectBox.currentIndexChanged.connect( lambda: RenderSelectionChanged(gui.renderSelectBox, gui.renderNodeBox)) scrollLayout.addWidget(gui.renderNodeBox, 20, 1) gui.renderNodeBox.setDisabled(True) # Submit button buttonLayoutSpacer = QSpacerItem(0, 0, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) buttonLayout.addItem(buttonLayoutSpacer) gui.pipelineToolStatusLabel = QLabel("No Pipeline Tools Set") gui.pipelineToolStatusLabel.setAlignment(QtCore.Qt.AlignCenter) buttonLayout.addWidget(gui.pipelineToolStatusLabel) pipelineToolsButton = QPushButton("Pipeline Tools") pipelineToolsButton.pressed.connect(lambda: PipelineToolsClicked(gui)) buttonLayout.addWidget(pipelineToolsButton) submitButton = QPushButton("Submit") submitButton.pressed.connect(lambda: SubmitPressed(gui)) buttonLayout.addWidget(submitButton) scrollLayout.addLayout(buttonLayout, 21, 0, 1, 3) verticalStretchLayout = QVBoxLayout() verticalStretchLayout.addStretch() scrollLayout.addLayout(verticalStretchLayout, 22, 0) scrollArea = QScrollArea() scrollArea.setWidget(scrollWidget) scrollArea.setWidgetResizable(True) scrollArea.setFrameStyle(QFrame.NoFrame + QFrame.Plain) vLayout = QVBoxLayout() vLayout.setObjectName('vLayout') vLayout.addWidget(scrollArea) gui.setLayout(vLayout) LoadStickySettings(gui) try: pipelineToolStatusMessage = RetrievePipelineToolStatus( raiseOnExitCode=True) except subprocess.CalledProcessError as e: pipelineToolStatusMessage = HandlePipelineToolsCalledProcessError(e) UpdatePipelineToolStatusLabel(gui, pipelineToolStatusMessage) # Populate the render node drop down based on the effective check state # of the "Include ImageWrite Nodes" checkbox after sticky settings are applied PopulateRenderNodeDropDown(gui.includeImageWrite.isChecked(), gui.renderNodeBox) # We delay wiring up this signal handler until after the sticky settings are applied to avoid # rebuilding the drop-down list multiple times unnecessarily gui.includeImageWrite.stateChanged.connect( lambda checked: PopulateRenderNodeDropDown(checked, gui.renderNodeBox)) # Check if this tab is part of a pane in the main window, or if it is contained in a floating pane if gui.window() != UI4.App.MainWindow.CurrentMainWindow(): # Resize the floating pane's window to accommodate the tab's widgets requiredSize = scrollWidget.sizeHint() gui.window().resize(max(requiredSize.width() + 20, 200), min(requiredSize.height() + 40, 1000))
def setup_ui(self): l_nodes_raw = NodegraphAPI.GetAllNodesByType('Render') l_nodes = [] for n in l_nodes_raw: np = n.getInputPortByIndex(0) if np.getNumConnectedPorts(): l_nodes.append(n) l_renders = FarmAPI.GetSortedDependencyList(l_nodes) l_aovs = [] for render_node in l_renders: if len(render_node['outputs']) > 0: for aov in render_node['outputs']: l_aovs.append([render_node['name'], aov[ 'name'], aov['outputLocation']]) # setting output directory to display in label if len(l_renders) > 0: if len(l_renders[0]['outputs']) > 1: output_location_of_some_layer = l_renders[ 0]['outputs'][1]['outputLocation'] self.output_directory, layer = os.path.split( output_location_of_some_layer) self.ui.label_5.setText(self.output_directory) else: self.ui.label_5.setText("Output path not found. Contact TD.") checkboxList = [] nodes_L, nodes_R = [], [] for item in l_aovs: title = item[0] checkboxList.append(title) checkboxList = sorted(list(set(checkboxList))) for i in range(len(checkboxList)): name = checkboxList[i] if name[len(name) - 1] == "L": nodes_L.append(name) else: nodes_R.append(name) # Add to list showing L nodes self.ui.tableWidget.setRowCount(len(nodes_L)) for i in range(len(nodes_L)): name = nodes_L[i] item = QtGui.QTableWidgetItem('%s' % name) check = Qt.Checked item.setCheckState(check) self.ui.tableWidget.setItem(i, 0, item) sf_mode = QTableWidgetItem(u"*双击编辑") check = Qt.Unchecked sf_mode.setCheckState(check) self.ui.tableWidget.setItem(i, 1, sf_mode) # Add to list showing R and other nodes self.ui.tableWidget_2.setRowCount(len(nodes_R)) for i in range(len(nodes_R)): name = nodes_R[i] item = QtGui.QTableWidgetItem('%s' % name) check = Qt.Checked item.setCheckState(check) self.ui.tableWidget_2.setItem(i, 0, item) sf_mode = QTableWidgetItem(u"*双击编辑") check = Qt.Unchecked sf_mode.setCheckState(check) self.ui.tableWidget_2.setItem(i, 1, sf_mode) f_range = FarmAPI.GetSceneFrameRange() self.ui.lineEdit.setText(str(f_range['start'])) self.ui.lineEdit_2.setText(str(f_range['end'])) return