Skip to content

Commit

Permalink
Operator Manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Titov committed Apr 12, 2021
1 parent f0dc92e commit 8e90cd1
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,6 @@ def __init__(self):
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.SingleSelection)

def setSectionsResizeMode(self):
header = self.header()
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)

def hasSelection(self):
"""Returns True if selection model has selected items, False otherwise."""
return self.selectionModel().hasSelection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,15 @@ def updateWindowTitle(self):
title += ': ' + self._library_path
self.setWindowTitle(title)

def _setSectionsResizeMode(self):
header = self.view.header()
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
header.setSectionResizeMode(1, QHeaderView.Stretch)
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)

def setLibrary(self, library_path):
self.model.setLibrary(library_path)
self.view.setSectionsResizeMode()
self._setSectionsResizeMode()
self._library_path = library_path
self.updateWindowTitle()

Expand Down
38 changes: 4 additions & 34 deletions python2.7libs/houdini_tdk/operator_manager/model/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""

import os
from operator import attrgetter

try:
from PyQt5.QtWidgets import *
Expand All @@ -31,6 +32,8 @@

import hou

from .node_type_proxy import NodeTypeProxy

ICON_SIZE = 16
INSTALLED_ICON = hou.qt.Icon('TOP_status_cooked', ICON_SIZE, ICON_SIZE)
NOT_INSTALLED_ICON = hou.qt.Icon('TOP_status_error', ICON_SIZE, ICON_SIZE)
Expand All @@ -39,39 +42,6 @@
TextRole = Qt.UserRole + 1


class NodeTypeProxy(object):
__slots__ = 'node_type', '_name', '_name_with_category', '_label', '_icon', '_library_file_path'

def __init__(self, node_type):
self.node_type = node_type
self._name = node_type.name()
self._name_with_category = node_type.nameWithCategory()
self._label = node_type.description()
self._icon = node_type.icon()
if node_type.definition() is None:
self._library_file_path = 'Internal'
else:
self._library_file_path = node_type.definition().libraryFilePath()

def name(self):
return self._name

def nameWithCategory(self):
return self._name_with_category

def description(self):
return self._label

def icon(self):
return self._icon

def libraryFilePath(self):
return self._library_file_path

def __getattr__(self, attr_name):
return self.node_type.__getattribute__(attr_name)


class OperatorManagerLibraryModel(QAbstractItemModel):
def __init__(self, parent=None):
super(OperatorManagerLibraryModel, self).__init__(parent)
Expand Down Expand Up @@ -102,7 +72,7 @@ def updateData(self):
else:
multiple_def_libs.append(lib_path)
self._types[lib_path] = tuple(sorted((NodeTypeProxy(d.nodeType()) for d in definitions),
key=lambda tp: tp.nameWithCategory()))
key=attrgetter('nameWithCategory')))

single_def_libs = tuple(sorted(single_def_libs.keys(), key=single_def_libs.get))
self._types['Internal'] = tuple(sorted(internal_types,
Expand Down
138 changes: 98 additions & 40 deletions python2.7libs/houdini_tdk/operator_manager/model/node_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from operator import attrgetter

try:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
Expand All @@ -29,56 +31,85 @@

import hou

from .node_type_proxy import NodeTypeProxy

ICON_SIZE = 16
EMPTY_ICON = hou.qt.Icon('MISC_empty', ICON_SIZE, ICON_SIZE)

TextRole = Qt.UserRole + 1

class TypeNameItem(object):
__slots__ = 'category_name', 'type_name',

def __init__(self, category_name, type_name):
self.category_name = category_name
self.type_name = type_name

def __eq__(self, other):
return other == self.type_name


class OperatorManagerNodeTypeModel(QAbstractItemModel):
def __init__(self, parent=None):
super(OperatorManagerNodeTypeModel, self).__init__(parent)

self._names = ()
self._category_names = ()
self._type_names = {}
self._types = {}

def updateData(self):
self.beginResetModel()

node_types = {}
types = {}

for category in hou.nodeTypeCategories().values():
for category_name, category in hou.nodeTypeCategories().items():
category_types = types[category_name] = {}
for node_type in category.nodeTypes().values():
if node_type.definition() is None:
continue

_, _, name, _ = node_type.nameComponents()
if name not in node_types:
node_types[name] = [node_type]
type_name = node_type.nameComponents()[2]
if type_name not in category_types:
category_types[type_name] = [node_type]
else:
node_types[name].append(node_type)
category_types[type_name].append(node_type)

self._names = tuple(sorted(node_types.keys()))
self._types = {
name: tuple(sorted(node_types[name], key=lambda t: t.name()))
for name in node_types.keys()
}
# Remove empty categories
for category_name in tuple(types.keys()):
if len(types[category_name]) == 0:
del types[category_name]

self._category_names = tuple(sorted(types.keys()))

self._type_names = {}
for category_name in self._category_names:
self._type_names[category_name] = tuple(TypeNameItem(category_name, type_name)
for type_name in sorted(types[category_name].keys()))

types[category_name] = {
type_name: tuple(sorted(map(NodeTypeProxy, node_types), key=attrgetter('name')))
for type_name, node_types in types[category_name].items()
}

self._types = types

self.endResetModel()

def hasChildren(self, parent):
if not parent.isValid():
return bool(self._names)
elif isinstance(parent.internalPointer(), basestring):
return bool(self._category_names)
elif isinstance(parent.internalPointer(), (basestring, TypeNameItem)):
return True

return False
else:
return False

def rowCount(self, parent):
if not parent.isValid():
return len(self._names)
return len(self._category_names)
elif isinstance(parent.internalPointer(), basestring):
category_name = parent.internalPointer()
return len(self._type_names[category_name])
elif isinstance(parent.internalPointer(), TypeNameItem):
type_name_item = parent.internalPointer()
return len(self._types[type_name_item.category_name][type_name_item.type_name])
else:
return len(self._types[parent.internalPointer()])
return 0

def columnCount(self, parent):
return 4
Expand All @@ -87,22 +118,38 @@ def parent(self, index):
if not index.isValid():
return QModelIndex()

item = index.internalPointer()
if isinstance(item, basestring):
item_data = index.internalPointer()

if isinstance(item_data, basestring):
return QModelIndex()
elif isinstance(item_data, TypeNameItem):
return self.createIndex(self._type_names[item_data.category_name].index(item_data.type_name), 0,
item_data.category_name)
elif isinstance(item_data, NodeTypeProxy):
node_type = item_data
category_name = node_type.category().name()
type_name = node_type.nameComponents()[2]
type_name_index = self._type_names[category_name].index(type_name)
type_name_item = self._type_names[category_name][type_name_index]
return self.createIndex(type_name_index, 0, type_name_item)
else:
type_name = item.nameComponents()[2]
return self.createIndex(self._types[type_name].index(item), 0, type_name)
return QModelIndex()

def index(self, row, column, parent):
if not self.hasIndex(row, column, parent):
return QModelIndex()

if not parent.isValid():
return self.createIndex(row, column, self._names[row])
else:
type_name = parent.internalPointer()
return self.createIndex(row, column, self._types[type_name][row])
return self.createIndex(row, column, self._category_names[row])
elif isinstance(parent.internalPointer(), basestring):
category_name = parent.internalPointer()
type_name_item = self._type_names[category_name][row]
return self.createIndex(row, column, type_name_item)
elif isinstance(parent.internalPointer(), TypeNameItem):
type_name_item = parent.internalPointer()
return self.createIndex(row, column,
self._types[type_name_item.category_name][type_name_item.type_name][row])
return QModelIndex()

def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable
Expand All @@ -114,33 +161,44 @@ def data(self, index, role):
item_data = index.internalPointer()

if role == Qt.UserRole:
if isinstance(item_data, NodeTypeProxy):
item_data = item_data.node_type
return item_data

column = index.column()

if not isinstance(item_data, hou.NodeType):
type_name = item_data
if isinstance(item_data, basestring):
if item_data in self._category_names:
category_name = item_data
if column == 0:
if role == Qt.DisplayRole:
return category_name
elif isinstance(item_data, TypeNameItem):
if column == 0:
if role == Qt.DisplayRole:
return type_name
else:
node_type = item_data
return item_data.type_name
elif isinstance(item_data, NodeTypeProxy):
node_type_proxy = item_data
if column == 0:
if role == Qt.DisplayRole:
return node_type.description()
return node_type_proxy.description()
elif role == Qt.DecorationRole:
try:
return hou.qt.Icon(node_type.icon(), ICON_SIZE, ICON_SIZE)
return hou.qt.Icon(node_type_proxy.icon(), ICON_SIZE, ICON_SIZE)
except hou.OperationFailed:
return EMPTY_ICON
elif column == 1:
namespace = node_type_proxy.nameComponents()[1]
if role == Qt.DisplayRole:
namespace = node_type.nameComponents()[1]
return namespace or '-'
elif role == Qt.TextAlignmentRole:
return Qt.AlignLeft | Qt.AlignVCenter if namespace else Qt.AlignCenter
elif column == 2:
version = node_type_proxy.nameComponents()[3]
if role == Qt.DisplayRole:
version = node_type.nameComponents()[3]
return version or '-'
elif role == Qt.TextAlignmentRole:
return Qt.AlignLeft | Qt.AlignVCenter if version else Qt.AlignCenter
elif column == 3:
if role == Qt.DisplayRole:
return node_type.definition().libraryFilePath()
return node_type_proxy.libraryFilePath()
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Tool Development Kit for SideFX Houdini
Copyright (C) 2021 Ivan Titov
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""


class NodeTypeProxy(object):
__slots__ = 'node_type', '_name', '_name_with_category', '_label', '_icon', '_library_file_path'

def __init__(self, node_type):
self.node_type = node_type
self._name = node_type.name()
self._name_with_category = node_type.nameWithCategory()
self._label = node_type.description()
self._icon = node_type.icon()
if node_type.definition() is None:
self._library_file_path = 'Internal'
else:
self._library_file_path = node_type.definition().libraryFilePath()

def name(self):
return self._name

def nameWithCategory(self):
return self._name_with_category

def description(self):
return self._label

def icon(self):
return self._icon

def libraryFilePath(self):
return self._library_file_path

def __getattr__(self, attr_name):
return self.node_type.__getattribute__(attr_name)
1 change: 1 addition & 0 deletions python2.7libs/houdini_tdk/operator_manager/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def __init__(self):

self.setItemDelegate(OperatorManagerLibraryDelegate())
self.setAlternatingRowColors(True)
self.setStyleSheet('QTreeView::item {padding: 0 10px;}')

self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
Expand Down
Loading

0 comments on commit 8e90cd1

Please sign in to comment.