Skip to content

Commit

Permalink
New features
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Titov authored Mar 27, 2020
2 parents a94aef8 + 302fdee commit b06ad3f
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 62 deletions.
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Houdini Tool Development Kit
# Tool Development Kit

## Tools
#### Find Icon
A tool to quickly find available icons.
Allows you to quickly find an icon for your tool.

![Find Icon](/images/find_icon.png)

#### Generate Code
Applies `.asCode()` to selected nodes. Then copies the resulting code to the clipboard.
Applies `.asCode()` to selected nodes. Then copies the resulting code to the clipboard or shows in code editor.

![Generate Code](/images/generate_code.png)

#### Increment HDA Version
Increments the HDA version of the selected node. You can select the version type (major, minor, build, etc.).
Expand All @@ -18,3 +20,19 @@ Increments the HDA version of the selected node. You can select the version type
Shows user data and cached user data of the selected node.

![Show Node User Data](/images/show_node_user_data.png)

## License
```
Tool Development Kit for SideFX Houdini
Copyright (C) 2020 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/>.
```
Binary file modified images/find_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/generate_code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified images/show_node_user_data.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions python2.7libs/houdini_tdk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Tool Development Kit for SideFX Houdini
Copyright (C) 2020 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/>.
"""
53 changes: 53 additions & 0 deletions python2.7libs/houdini_tdk/filter_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Tool Development Kit for SideFX Houdini
Copyright (C) 2020 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/>.
"""

try:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *

Signal = pyqtSignal
except ImportError:
from PySide2.QtWidgets import *
from PySide2.QtGui import *
from PySide2.QtCore import *


class FilterField(QLineEdit):
# Signals
accepted = Signal()

def __init__(self):
super(FilterField, self).__init__()

self.setPlaceholderText('Type to Filter...')

def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Escape:
self.clear()
elif key == Qt.Key_Enter or key == Qt.Key_Return:
self.accepted.emit()
else:
super(FilterField, self).keyPressEvent(event)

def mousePressEvent(self, event):
if event.button() == Qt.MiddleButton and \
event.modifiers() == Qt.ControlModifier:
self.clear()
super(FilterField, self).mousePressEvent(event)
62 changes: 24 additions & 38 deletions python2.7libs/houdini_tdk/find_icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from __future__ import print_function

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

import hou


class FilterField(QLineEdit):
# Signals
accepted = Signal()

def __init__(self):
super(FilterField, self).__init__()

self.setPlaceholderText('Type to Filter...')

def keyPressEvent(self, event):
key = event.key()
if key == Qt.Key_Escape:
self.clear()
elif key == Qt.Key_Enter or key == Qt.Key_Return:
self.accepted.emit()
else:
super(FilterField, self).keyPressEvent(event)

def mousePressEvent(self, event):
if event.button() == Qt.MiddleButton and \
event.modifiers() == Qt.ControlModifier:
self.clear()
super(FilterField, self).mousePressEvent(event)
from .filter_field import FilterField


class IconCache:
Expand All @@ -74,13 +49,19 @@ def icon(name):
return IconCache.data[name]


def fuzzyMatch(pattern, word):
if pattern == word:
def fuzzyMatch(pattern, text):
if pattern == text:
return True, 999999
try:
pattern_start = text.index(pattern)
pattern_length = len(pattern)
return True, pattern_length * pattern_length + (1 - pattern_start / 500.0)
except ValueError:
pass
weight = 0
count = 0
index = 0
for char in word:
for char in text:
try:
if char == pattern[index]:
count += 1
Expand All @@ -90,38 +71,43 @@ def fuzzyMatch(pattern, word):
count = 0
except IndexError:
pass
if count != 0:
weight += count * count
weight += count * count
if index < len(pattern):
return False, weight
return True, weight
return True, weight + (1 - text.index(pattern[0]) / 500.0)


class FuzzyFilterProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super(FuzzyFilterProxyModel, self).__init__(parent)
self.setDynamicSortFilter(True)
self.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.sort(0, Qt.DescendingOrder)

self.pattern = ''

def setFilterPattern(self, pattern):
self.beginResetModel()
self.pattern = pattern.lower()
self.endResetModel()
self.invalidate()

def filterAcceptsRow(self, source_row, source_parent):
if not self.pattern:
return True

source_model = self.sourceModel()
text = source_model.data(source_model.index(source_row, 0, source_parent),
Qt.UserRole)
matches, weight = fuzzyMatch(self.pattern, text.lower())
matches, _ = fuzzyMatch(self.pattern, text.lower())
return matches

def lessThan(self, source_left, source_right):
text1 = source_left.data(Qt.UserRole)
if not self.pattern:
return source_left.row() < source_right.row()

text1 = source_left.data(Qt.DisplayRole)
_, weight1 = fuzzyMatch(self.pattern, text1.lower())

text2 = source_right.data(Qt.UserRole)
text2 = source_right.data(Qt.DisplayRole)
_, weight2 = fuzzyMatch(self.pattern, text2.lower())

return weight1 < weight2
Expand All @@ -133,7 +119,7 @@ def __init__(self, parent=None):

# Data
ICON_INDEX_FILE = hou.expandString('$HFS/houdini/config/Icons/SVGIcons.index')
self.__data = sorted(hou.loadIndexDataFromFile(ICON_INDEX_FILE).keys())
self.__data = tuple(sorted(hou.loadIndexDataFromFile(ICON_INDEX_FILE).keys()))

def rowCount(self, parent):
return len(self.__data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from __future__ import print_function
import os
import tempfile

import hou

Expand All @@ -25,10 +26,20 @@ def generateCode(**kwargs):
nodes = hou.selectedNodes()
if not nodes:
raise hou.Error('No node selected')
code = ''
for node in nodes:
code += node.asCode()
if code:
code = ''.join(node.asCode(brief=True) for node in nodes)
if kwargs['ctrlclick'] or kwargs['shiftclick']:
path = tempfile.mktemp('.py')
with open(path, 'w') as file:
file.write(code)
if len(nodes) == 1:
title = nodes[0].path()
else:
title = os.path.dirname(nodes[0].path())
if kwargs['ctrlclick']:
hou.ui.openFileEditor(title, path)
else:
os.startfile(path)
else:
hou.ui.copyTextToClipboard(code)
hou.ui.setStatusMessage('Code was generated and copied',
hou.severityType.ImportantMessage)
44 changes: 31 additions & 13 deletions python2.7libs/houdini_tdk/show_node_user_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from __future__ import print_function

try:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
Expand All @@ -42,11 +40,15 @@ def __init__(self, parent=None):

def updateDataFromNode(self, node, cached=False):
self.beginResetModel()
if cached:
self.__data = node.cachedUserDataDict()
if node is None:
self.__data = {}
self.__keys = ()
else:
self.__data = node.userDataDict()
self.__keys = tuple(self.__data.keys())
if cached:
self.__data = node.cachedUserDataDict()
else:
self.__data = node.userDataDict()
self.__keys = tuple(self.__data.keys())
self.endResetModel()

def rowCount(self, parent):
Expand All @@ -68,15 +70,18 @@ def __init__(self):


class UserDataWindow(QWidget):
def __init__(self, node, parent=None):
def __init__(self, parent=None):
super(UserDataWindow, self).__init__(parent, Qt.Window)

self.setWindowTitle('Node User Data: ' + node.path())

# Layout
layout = QHBoxLayout(self)
layout.setContentsMargins(4, 4, 4, 4)
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(4, 4, 4, 4)
main_layout.setSpacing(4)

layout = QHBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(4)
main_layout.addLayout(layout)

# Key List
self.user_data_model = UserDataModel()
Expand All @@ -92,11 +97,24 @@ def __init__(self, node, parent=None):
self.user_data_view = QTextEdit()
layout.addWidget(self.user_data_view)

# Data
self.node = None

# Update
update_button = QPushButton('Update from Node')
update_button.clicked.connect(lambda: self.setCurrentNode(self.node))
main_layout.addWidget(update_button)

def _readData(self):
selection_model = self.user_data_list.selectionModel()
value = selection_model.currentIndex().data(Qt.UserRole)
self.user_data_view.setText(str(value))

def setCurrentNode(self, node, cached=False):
self.node = node
self.user_data_model.updateDataFromNode(node, cached)
self.setWindowTitle('Node User Data: ' + node.path())


def showNodeUserData(node=None, cached=False, **kwargs):
if node is None:
Expand All @@ -106,6 +124,6 @@ def showNodeUserData(node=None, cached=False, **kwargs):
elif len(nodes) > 1:
raise hou.Error('Too much nodes selected')
node = nodes[0]
window = UserDataWindow(node, hou.qt.mainWindow())
window.user_data_model.updateDataFromNode(node, cached)
window = UserDataWindow(hou.qt.mainWindow())
window.setCurrentNode(node, cached)
window.show()
7 changes: 4 additions & 3 deletions toolbar/houdini_tdk.shelf
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ fi.findIcon(**kwargs)
</tool>

<tool name="houdini_tdk::generate_code::1.0" label="Generate Code" icon="MISC_python">
<helpText><![CDATA["""Generate Python code for selected nodes and copy to clipboard"""
]]></helpText>
<script scriptType="python"><![CDATA[import houdini_tdk.gen_code as gc
<helpText><![CDATA["""Generate Python code for selected nodes and copy to clipboard.
Use Ctrl+Click to show generated code in Code Editor instead of coping.
Use Shift+Click to show in your default Code Editor."""]]></helpText>
<script scriptType="python"><![CDATA[import houdini_tdk.generate_code as gc
reload(gc)
gc.generateCode(**kwargs)
Expand Down

0 comments on commit b06ad3f

Please sign in to comment.