def button_row(channel=None): row = QtGui.QWidget() row.setLayout(QtGui.QHBoxLayout()) row.layout().setContentsMargins(0, 0, 0, 0) row.layout().setSpacing(1) label = '"%s"' % channel if channel else 'All' button = QtGui.QPushButton(silk_icon('arrow_refresh', 12), 'Fuzz') button.setFixedSize(button.sizeHint().boundedTo( QtCore.QSize(1000, 20))) button.setToolTip('Fuzzy Match %s' % label) channels = [channel] if channel else None button.clicked.connect( lambda *args: self._on_fuzzy_match(channels=channels)) row.layout().addWidget(button) button = QtGui.QPushButton(silk_icon('cross', 12), 'Unlink') button.setFixedSize(button.sizeHint().boundedTo( QtCore.QSize(1000, 20))) button.setToolTip('Unlink %s' % label) button.clicked.connect( lambda *args: self._on_unlink(channels=channels)) row.layout().addWidget(button) return row
def silk_widget(name, size=16, tooltip=None): icon = QtGui.QIcon(silk(name)) label = QtGui.QLabel() label.setPixmap(icon.pixmap(size, size)) label.setFixedSize(QtCore.QSize(size, size)) if tooltip: label.setToolTip(tooltip) return label
def submit(self, func, *args, **kwargs): self._queue.put((func, args, kwargs)) with self._worker_lock: if len(self._workers) < self._max_workers: worker = QtCore.QThread() worker.run = functools.partial(self._target, worker) worker.start() self._workers.append(worker)
def _setup_ui(self): self.setWindowTitle('Geocache Import') self.setWindowFlags(Qt.Tool) self.setMinimumWidth(900) self.setMinimumHeight(550) main_layout = QtGui.QVBoxLayout() self.setLayout(main_layout) self._scroll_widget = area = QtGui.QScrollArea() main_layout.addWidget(area) area.setFrameShape(QtGui.QFrame.NoFrame) area.setWidgetResizable(True) area_widget = QtGui.QWidget() area.setWidget(area_widget) self._scroll_layout = QtGui.QVBoxLayout() area_widget.setLayout(self._scroll_layout) self._scroll_layout.addStretch() button_layout = QtGui.QHBoxLayout() main_layout.addLayout(button_layout) button = QtGui.QPushButton("Add Geocache...") button.setMinimumSize(button.sizeHint().expandedTo(QtCore.QSize( 100, 0))) button_layout.addWidget(button) button.clicked.connect(self._on_add_geocache) self._sync_timeline_checkbox = QtGui.QCheckBox("Sync Timeline") self._sync_timeline_checkbox.setChecked(True) button_layout.addWidget(self._sync_timeline_checkbox) button_layout.addStretch() button = QtGui.QPushButton("Apply") button.setMinimumSize(button.sizeHint().expandedTo(QtCore.QSize( 100, 0))) button_layout.addWidget(button) button.clicked.connect(self._on_apply_clicked) button = QtGui.QPushButton("Save") button.setMinimumSize(button.sizeHint().expandedTo(QtCore.QSize( 100, 0))) button_layout.addWidget(button) button.clicked.connect(self._on_save_clicked)
def _setup_post_ui(self): self.layout().setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0) button = QtGui.QPushButton(silk_icon('arrow_switch', 12), "Edit") button.clicked.connect(self._on_mapping_clicked) button.setFixedSize(button.sizeHint().boundedTo(QtCore.QSize(1000, 22))) self._main_layout.addWidget(button) button = QtGui.QPushButton(silk_icon('delete', 12), "Delete") button.clicked.connect(self._on_delete) button.setFixedSize(button.sizeHint().boundedTo(QtCore.QSize(1000, 22))) self._main_layout.addWidget(button) # Finally setup the mapping UI. This requires self.meshes() to work. self._cache_changed()
def _setup_ui(self): self._field = QtGui.QLineEdit() self._field.editingFinished.connect(self._on_field_changed) self._main_layout.addWidget(self._field) button = QtGui.QPushButton(silk_icon("arrow_refresh", 12), "Update") button.clicked.connect(self._on_update) button.setFixedSize(button.sizeHint().boundedTo(QtCore.QSize(1000, 20))) self._main_layout.addWidget(button) super(Selection, self)._setup_ui()
def index(self, row, col, parent): if col > 0: #print 'HERE2', QtCore.QModelIndex() return QtCore.QModelIndex() node = self.node_from_index(parent) try: child = node.children()[row] except IndexError: #print 'HERE3', QtCore.QModelIndex() return QtCore.QModelIndex() if child.index is None: debug('child.index is None: %r -> %r', child, child.state) child.index = self.createIndex(row, col, child) if child.parent is None: debug('\tchild.parent is also None') child.parent = node #print 'HERE1', child.index return child.index
def _setup_ui(self): # Lots of layouts... self.setLayout(QtGui.QVBoxLayout()) ## Cache widgets self._cache_selector = CacheSelector(parent=self) self._cache_selector.path_changed = lambda path: self._cache_changed() self.layout().addLayout(self._cache_selector) ## Geometry widgets self.layout().addWidget(QtGui.QLabel("Geometry & References")) self._geometry_layout = QtGui.QVBoxLayout() self.layout().addLayout(self._geometry_layout) ## Buttons. button_layout = QtGui.QHBoxLayout() self.layout().addLayout(button_layout) button = QtGui.QPushButton(silk_icon('link_add', 12), "Add Reference Link") button.clicked.connect(self._on_add_reference_link) button.setMaximumHeight(22) button_layout.addWidget(button) button = QtGui.QPushButton(silk_icon('link_add', 12), "Add Selection Link") button.clicked.connect(self._on_add_selection_link) button.setMaximumHeight(22) button_layout.addWidget(button) button_layout.addStretch() button = QtGui.QPushButton(silk_icon('cross', 12), "") button.clicked.connect(self._on_ignore_button) button.setFixedSize(button.sizeHint().boundedTo(QtCore.QSize(1000, 22))) button_layout.addWidget(button) # Mappings. self._mapping_layout = QtGui.QFormLayout() self.layout().addLayout(self._mapping_layout)
def standard_setup(): """Non-standalone user setup.""" # Setup background check every time a new scene is opened. def check_references(*args): import metatools.imports import sgpublish.check.maya metatools.imports.autoreload(sgpublish.check.maya) sgpublish.check.maya.start_background_check() for name, type_ in [ ('after_open', OpenMaya.MSceneMessage.kAfterOpen), # TODO: Re-enable this one when we know the general uitools.threads stuff # is stable, since a crash while saving is tremendously bad. # ('after_save', OpenMaya.MSceneMessage.kAfterSave), # We used to have kAfterCreateRererence and kAfterLoadReference in here # too, but for now it is # a relatively corner case for it to not be paired with creating a # reference, and the logic is clever enough to stack several update # requests together. ]: __mayatools_usersetup__['name'] = OpenMaya.MSceneMessage.addCallback( type_, check_references, ) # Setup background check every 5 minutes. try: from uitools.qt import QtCore import sgpublish.check.maya except ImportError as e: print '[sgpublish] Missing dependency:', e else: __mayatools_usersetup__['timer'] = timer = QtCore.QTimer() timer.timeout.connect(sgpublish.check.maya.start_background_check) timer.setInterval(1000 * 60 * 5) # Every 5 minutes. timer.start()
class PlayblastTable(QtGui.QTableWidget): refresh = QtCore.pyqtSignal() def __init__(self, parent = None): super(PlayblastTable, self).__init__(parent) self.setColumnCount(3) self.setColumnWidth(0, 100) self.verticalHeader().hide() self.horizontalHeader().setStretchLastSection(True) self.setHorizontalHeaderLabels(['First Frame', 'Name', 'Creation Time']) self.setAlternatingRowColors(True) self.setSelectionBehavior(self.SelectRows) self.setSortingEnabled(True) self.sortItems(2, Qt.DescendingOrder) self.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) self.itemDoubleClicked .connect(lambda x: self.flipbook_playblast()) def add_playblasts(self, playblasts): for playblast in sorted(playblasts, key = lambda pb: (' None' if pb.user_category == 'none' else pb.user_category, pb.name)): row = self.rowCount() self.setRowCount(row + 1) self.setRowHeight(row, 57) thumb = PlayblastThumbnail(playblast.first_frame) thumb.playblast = playblast self.setCellWidget(row, 0, thumb) name = QtGui.QTableWidgetItem(playblast.name) self.setItem(row, 1, name) date = QtGui.QTableWidgetItem(playblast.created_at.isoformat(' ')) self.setItem(row, 2, date) def contextMenuEvent(self, event): menu = QtGui.QMenu(self) flipbook_action = menu.addAction("Flipbook") flipbook_action.triggered.connect(self.flipbook_playblast) qt_action = menu.addAction("Make Quicktime") qt_action.triggered.connect(self.make_quicktime) refresh_action = menu.addAction("Refresh") refresh_action.triggered.connect(self.refresh.emit) delete_action = menu.addAction("Delete") delete_action.triggered.connect(self.delete_playblasts) action = menu.exec_(event.globalPos()) def current_playblast(self): row = self.currentRow() thumb = self.cellWidget(row, 0) if row is not None else None playblast = thumb and thumb.playblast return playblast def current_path(self): playblast = self.current_playblast() path = playblast and playblast.directory return path if path and os.path.exists(path) else None def delete_playblasts(self): rows = [] for item in self.selectedItems(): if not item.row() in rows: rows.append(item.row()) for row in reversed(sorted(rows)): dirname = None thumb = self.cellWidget(row, 0) if row is not None else None if thumb and thumb.playblast and thumb.playblast.directory and os.path.exists(thumb.playblast.directory): dirname = thumb.playblast.directory print "rm", dirname, row self.removeRow(row) if dirname: shutil.rmtree(dirname) def flipbook_playblast(self): playblast = self.current_playblast() cmd = ['rv', '[', os.path.join(playblast.directory, '*.jpg'), '-fps', str(24), ']'] if playblast.audio: cmd.extend(['-over', '[', playblast.audio, ']']) # fix for launching rv from maya on mac # http://www.tweaksoftware.com/static/documentation/rv/current/html/maya_tools_help.html#_osx_maya_2014 env = dict(os.environ) if 'QT_MAC_NO_NATIVE_MENUBAR' in env: del env['QT_MAC_NO_NATIVE_MENUBAR'] print subprocess.list2cmdline(cmd) proc = subprocess.Popen(cmd, env = env) def make_quicktime(self): playblast = self.current_playblast() cmd = ['make_quicktime', playblast.first_frame] if playblast.audio: cmd.extend(['--audio', playblast.audio]) if playblast.maya_file: cmd.extend(['--shotdir', playblast.maya_file]) print subprocess.list2cmdline(cmd) subprocess.Popen(cmd)
def sizeHint(self): if self._loaded: return self.pixmap().size() else: return QtCore.QSize(100, 57)
class Model(QtCore.QAbstractItemModel): _pixmaps = {} # There appears to be a bug in Maya's PySide 2, in which the default # arguments aren't registered as metatypes and you get this sort of message: # QObject::connect: Cannot queue arguments of type 'QList<QPersistentModelIndex>' # (Make sure 'QList<QPersistentModelIndex>' is registered using qRegisterMetaType().) # You don't get that if you call in the same thread, so we "fix" the problem # with these extra signals which just pass on the request. _legacy_layoutAboutToBeChanged = QtCore.pyqtSignal() _legacy_layoutChanged = QtCore.pyqtSignal() def __init__(self, root_state=None, sgfs=None, shotgun=None, session=None): super(Model, self).__init__() self._root_state = root_state or {} self._root = None self.sgfs = sgfs or SGFS(shotgun=shotgun, session=session) # Force a more reasonable timeout. Note that this does change the # global parameters on the shotgun object. self.sgfs.session.shotgun.close() self.sgfs.session.shotgun.config.timeout_secs = 5 self.sgfs.session.shotgun.config.max_rpc_attempts = 1 self.threadpool = ThreadPool(8, lifo=True) self._node_types = [] # See comment above. for name in 'layoutChanged', 'layoutAboutToBeChanged': old = getattr(self, name) new = getattr(self, '_legacy_' + name) setattr(self, name, new) new.connect(old.emit) def _thread_target(self): while True: try: func, args, kwargs = self._thread_queue.get() func(*args, **kwargs) except Exception: traceback.print_exc() def register_node_type(self, node_type): self._node_types.append(node_type) def index_from_state(self, state): # debug('index_from_state %r', sorted(state)) last_match = None nodes = [self.root()] while True: if not nodes: break node = nodes.pop(0) # Skip over groups. It would be nice if the group class would be # able to property handle this logic, but we don't want a "positive # match" (for the purposes of traversing the group) to result in # not selecting something real (because it is at a lower level than # the last group). if isinstance(node, Group): nodes.extend(node.children()) continue # debug('match?: %r <- %r', node, sorted(node.state)) if node.parent is None or node.parent.child_matches_initial_state( node, state): # debug('YES!!') # Trigger initial async. node.children() node.add_raw_children(node.get_temp_children_from_state(state)) nodes.extend(node.children()) last_match = node if last_match: return last_match.index def construct_node(self, key, view_data, state): for node_type in self._node_types: try: return node_type(self, key, view_data, state) except TypeError: pass return Leaf(self, key, view_data, state) def hasChildren(self, index): node = self.node_from_index(index) return not node.is_leaf() def root(self): if self._root is None: self._root = self.construct_node(None, {}, self._root_state) self._root.index = QtCore.QModelIndex() self._root.parent = None return self._root def node_from_index(self, index): return index.internalPointer() if index.isValid() else self.root() def rowCount(self, parent): node = self.node_from_index(parent) return len(node.children()) def columnCount(self, parent): return 1 def index(self, row, col, parent): if col > 0: #print 'HERE2', QtCore.QModelIndex() return QtCore.QModelIndex() node = self.node_from_index(parent) try: child = node.children()[row] except IndexError: #print 'HERE3', QtCore.QModelIndex() return QtCore.QModelIndex() if child.index is None: debug('child.index is None: %r -> %r', child, child.state) child.index = self.createIndex(row, col, child) if child.parent is None: debug('\tchild.parent is also None') child.parent = node #print 'HERE1', child.index return child.index def parent(self, child): node = self.node_from_index(child) if node.parent is not None: return node.parent.index else: return QtCore.QModelIndex() def flags(self, index): if not index.isValid(): return None node = self.node_from_index(index) if node.view_data.get('disabled'): return Qt.NoItemFlags else: return super(Model, self).flags(index) def data(self, index, role): if not index.isValid(): return None node = self.node_from_index(index) if role == Qt.DisplayRole: try: return node.view_data[Qt.DisplayRole] except KeyError: return None if role == Qt.DecorationRole: if node.error_count: return icon('fatcow/cross') if node.is_loading: return icon('fatcow/arrow_refresh') node = self.node_from_index(index) data = node.view_data.get(Qt.DecorationRole) if data is None: return None if isinstance(data, QtGui.QColor): key = (data.red(), data.green(), data.blue()) if key not in self._pixmaps: pixmap = QtGui.QPixmap(16, 16) pixmap.fill(Qt.transparent) painter = QtGui.QPainter(pixmap) brush = QtGui.QBrush(data) painter.setBrush(brush) painter.setPen(data.darker()) painter.drawRect(2, 2, 12, 12) painter.end() self._pixmaps[key] = pixmap return self._pixmaps[key] if isinstance(data, basestring): try: return self._pixmaps[data] except KeyError: pass self._pixmaps[data] = pixmap = icon(data) or QtGui.QPixmap( data) return pixmap return data if role == Qt.FontRole: if node.view_data.get(Qt.FontRole) is not None: return node.view_data[Qt.FontRole] return None if role == Qt.ForegroundRole: if node.error_count: return QtGui.QColor.fromRgb(128, 0, 0) if role == HeaderDisplayRole: try: return node.view_data['header'] except KeyError: pass # Passthrough other roles. try: return node.view_data[role] except KeyError: return None
def indexAt(self, point): return QtCore.QModelIndex()
def moveCursor(self, action, modifiers): #debug('moveCursor(%r, %r)', action, modifiers) return self.model().index(0, 0, QtCore.QModelIndex())
class Host(QtCore.QThread): # (message) executor_message = QtCore.pyqtSignal([object]) # (worker, type, message) worker_message = QtCore.pyqtSignal([object, object, object]) def __init__(self, conn): super(Host, self).__init__() # Will be set to None if the connection is closed. self.conn = conn # Set by a "config" message from the executor. self.max_workers = None # All workers we ever see, by uuid. self.workers = {} # All workers with are non of ACTIVE, FAILED, or DEPENDENCY_FAILED. self.unfinished_workers = [] def run(self): try: while True: # Trigger state changes across all workers. We don't need to # bother cascading state changes to earlier workers since # dependencies can only be to previous workers. active_count = sum( int(w.state == ACTIVE) for w in self.unfinished_workers) for worker in self.unfinished_workers: # Don't bother poking anyone who is waiting if we have # already reached max_workers. allow_start = (self.max_workers is None or active_count < self.max_workers) old_state = worker.state worker.poke(allow_start) if worker.state != old_state: # Adjust active count. active_count -= int(old_state == ACTIVE) active_count += int(worker.state == ACTIVE) # Send state transition message. self.worker_message.emit( worker, "state_changed", dict( old=old_state, new=worker.state, )) # Prune all complete workers. self.unfinished_workers = [ w for w in self.unfinished_workers if w.state not in finished_states ] rlist = [ worker.conn for worker in self.unfinished_workers if worker.conn is not None ] if self.conn is not None: rlist.append(self.conn) if not rlist: # Wait for changes if there is something that failed, as # the user may hit "Retry". if any(x.state in failed_states for x in self.workers.itervalues()): time.sleep(0.25) continue # There is nothing left to do, and the executor is closed. break rlist, _, _ = select.select(rlist, [], []) for conn in rlist: if conn is self.conn: owner_type = 'executor' else: owner_type = 'worker' worker = [ x for x in self.unfinished_workers if x.conn is conn ][0] # Get a message, turning EOFs into shutdown messages. # TODO: should these actually be "eof"? try: msg = conn.recv() except EOFError: msg = {'type': 'shutdown'} type_ = msg.pop('type', None) # debug('Host: %r sent %r:\n%s', owner_type, type_, pprint.pformat(msg)) # Send the message to methods on ourself, as well as to # the workers. handler = getattr( self, 'do_%s_%s' % (owner_type, type_ or 'unknown'), None) if owner_type == 'executor': if handler: handler(**msg) self.executor_message.emit(msg) else: if handler: handler(worker, **msg) self.worker_message.emit(worker, type_, msg) except: traceback.print_exc() QtGui.QApplication.exit(1) finally: if self.conn is not None: self.conn.send(dict(type='shutdown')) # debug("AT THE END") if not any(w.state in failed_states for w in self.workers.itervalues()): QtGui.QApplication.exit(0) def send(self, msg): if self.conn is not None: self.conn.send(msg) def do_executor_config(self, max_workers=NotSet, **msg): # debug('config: max_workers=%r', max_workers) if max_workers is not NotSet: self.max_workers = max_workers def do_executor_submit(self, uuid, **msg): worker = Worker(self, uuid, **msg) self.workers[uuid] = worker self.unfinished_workers.append(worker) self.worker_message.emit(worker, "new", msg) def do_executor_shutdown(self, **msg): # debug('Host: executor shut down') self.conn = None def do_worker_notify(self, worker, **msg): msg.setdefault('icon', worker.icon) msg.setdefault('title', worker.name) utils.notify(**msg) def do_worker_result(self, worker, **msg): self.worker_message.emit(worker, 'state_changed', dict(old=worker.state, new=COMPLETE)) worker.state = COMPLETE # Forward the message. msg['type'] = 'result' msg['uuid'] = worker.uuid self.send(msg) def do_worker_exception(self, worker, **msg): self.worker_message.emit(worker, 'state_changed', dict(old=worker.state, new=FAILED)) worker.state = FAILED # Forward the message. msg['type'] = 'exception' msg['uuid'] = worker.uuid self.send(msg) msg.setdefault('exception_name', 'Unknown') msg.setdefault('exception_message', 'unknown') msg.setdefault('exception_traceback', '') utils.notify( title='Job Failed: %s' % (worker.name or 'Untitled'), message= '{exception_name}: {exception_message}\n{exception_traceback}'. format(**msg), sticky=True, icon=utils.icon(worker.icon) if worker.icon else None, ) def do_worker_shutdown(self, worker): # It wasn't done it's job. if worker.state not in finished_states: self.do_worker_exception( worker, **dict( type='exception', exception_name='RuntimeError', exception_message='worker shutdown unexpectedly; was %r' % worker.state, exception=RuntimeError( 'worker shutdown unexpectedly; was %r' % worker.state), ))
def parent(self, child): node = self.node_from_index(child) if node.parent is not None: return node.parent.index else: return QtCore.QModelIndex()
class Picker(QtGui.QTabWidget): pathChanged = QtCore.pyqtSignal(object) def __init__(self, parent = None, selection_mode = QtGui.QTableWidget.SingleSelection,): super(Picker, self).__init__(parent) self._playblasts = [] self._selection_mode = selection_mode self._find_legacy_playblasts() self._tables_by_name = {} self._setup_ui() def _setup_ui(self): self.currentChanged.connect(self._current_tab_changed) tables = self._tables_by_name for playblast in sorted(self._playblasts, key=lambda pb: (' None' if pb.user_category == 'none' else pb.user_category, pb.name) ): if playblast.user_category not in tables: table = PlayblastTable() table.itemSelectionChanged.connect(self._table_selection_changed) table.refresh.connect(self.refresh) if self._selection_mode: table.setSelectionMode(self._selection_mode) tables[playblast.user_category] = table self.addTab(table, "Playblasts" if playblast.user_category == "none" else playblast.user_category.title()) table = tables[playblast.user_category] table.add_playblasts([playblast]) for table in tables.itervalues(): table.resizeColumnToContents(1) table.resizeColumnToContents(2) def refresh(self): for table in self._tables_by_name.itervalues(): table.clearContents() table.setRowCount(0) self._playblasts = [] self._find_legacy_playblasts() self._setup_ui() def _find_legacy_playblasts(self): # This is the folder that they are stored in. if not os.path.exists('/var/tmp/srv_playblast'): return for name in os.listdir('/var/tmp/srv_playblast'): directory = os.path.join('/var/tmp/srv_playblast', name) # Try to grab the first frame. try: file_names = os.listdir(directory) except OSError as e: if e.errno == 20: # Not a folder. continue raise frame_gen = (x for x in sorted(file_names) if os.path.splitext(x)[1] in ('.jpg', '.jpeg')) first_frame = next(frame_gen, None) if first_frame is None: continue audio = None maya_file = None audio_text = next((x for x in sorted(file_names) if os.path.splitext(x)[1] in ('.txt',))) if audio_text: audio, maya_file, frame_rate = parse_audio_txt(os.path.join(directory, audio_text)) first_frame = os.path.join(directory, first_frame) user_category_path = os.path.join(directory, 'approval_status') user_category = open(user_category_path).read() if os.path.exists(user_category_path) else None user_category = str(user_category).lower() self._playblasts.append(PlayblastInfo( name = name, directory = directory, user_category = user_category, first_frame = first_frame, created_at = datetime.datetime.fromtimestamp(os.path.getctime(first_frame)), audio = audio, maya_file = maya_file )) def autoSetMinimumWidth(self): width = 0 for table in self._tables_by_name.itervalues(): width = max(width, sum(table.columnWidth(i) for i in xrange(table.columnCount()))) if width: self.setMinimumWidth(width) def _table_selection_changed(self): path = self.currentPath() self.pathChanged.emit(path) def _current_tab_changed(self): path = self.currentPath() self.pathChanged.emit(path) def currentPath(self): table = self.currentWidget() return table.current_path()
def root(self): if self._root is None: self._root = self.construct_node(None, {}, self._root_state) self._root.index = QtCore.QModelIndex() self._root.parent = None return self._root