Created
November 21, 2015 17:51
-
-
Save ben-hearn-sb/dfd79ba2a7a77907cc77 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
__author__ = 'ben.hearn' | |
""" | |
- File Runner is a handy Qt UI class that helps with constant batch processing on files or directories with a twist! | |
- You can apply any external script you have written to process the files | |
How to use: | |
- Simply drag in the directories you want to iterate over into the directories window | |
- Type in your desired file format | |
- Drag in the script you want to process your files into the script window | |
- Type in the name of the function you want to run | |
- Choose whether or not you want the program to recursively process all sub directories | |
- Select your desired directories and your desired script | |
- Click Run. It's as easy as that! | |
Edit the class however you see fit :) | |
Enjoy! | |
""" | |
from PyQt4 import QtCore, QtGui | |
import sys | |
import os | |
import ast | |
import importlib | |
import ntpath | |
class DirectoryFunctions(QtGui.QDialog): | |
def __init__(self): | |
QtGui.QDialog.__init__(self) | |
self.appDataDir = os.path.join(os.getenv('APPDATA'), 'CUSTOM_DIR') | |
if not os.path.exists(self.appDataDir): | |
os.makedirs(self.appDataDir) | |
self.inFocusWidget = None | |
self.allowedScriptExts = ['py', 'mel'] | |
global dirIn | |
self.dirIn = QtGui.QDialog() | |
self.dirIn.resize(850, 450) | |
self.dirIn.setWindowTitle("File Runner") | |
self.dirIn.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) | |
width = self.dirIn.width() | |
# Master display is the table view that shows our | |
self.masterDisplay = QtGui.QTableWidget() | |
self.masterDisplay.setColumnCount(2) | |
self.masterDisplay.setHorizontalHeaderLabels(['Directories', 'Format']) | |
# Setting the stretch to expand with the table | |
self.masterDisplay.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Interactive) | |
self.masterDisplay.horizontalHeader().resizeSection(0, width/2) | |
self.masterDisplay.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) | |
self.masterDisplay.verticalHeader().hide() | |
self.masterDisplay.setObjectName('master_display') | |
self.masterDisplay.itemPressed .connect(lambda: self.setFocusWidget(self.masterDisplay)) | |
# Script layout | |
self.scriptPaths = QtGui.QTableWidget() | |
self.scriptPaths.setColumnCount(3) | |
self.scriptPaths.setHorizontalHeaderLabels(['Script Name', 'Function Name', 'Recursive']) | |
self.scriptPaths.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Interactive) | |
self.scriptPaths.horizontalHeader().resizeSection(0, width/2) | |
self.scriptPaths.horizontalHeader().setResizeMode(1, QtGui.QHeaderView.Stretch) | |
self.scriptPaths.horizontalHeader().setResizeMode(2, QtGui.QHeaderView.Stretch) | |
self.scriptPaths.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) | |
self.scriptPaths.verticalHeader().hide() | |
self.scriptPaths.setObjectName('script_paths') | |
self.scriptPaths.itemPressed .connect(lambda: self.setFocusWidget(self.scriptPaths)) | |
# Create target layout | |
btnRun = QtGui.QPushButton('Run On Selected') | |
targetLayout = QtGui.QHBoxLayout() | |
targetLayout.addWidget(btnRun) | |
# Create appdata button layout | |
btnCreateAppData = QtGui.QPushButton('Save Locations') | |
btnRemoveDir = QtGui.QPushButton('Remove Entry') | |
appLayout = QtGui.QHBoxLayout() | |
appLayout.addWidget(btnCreateAppData) | |
appLayout.addWidget(btnRemoveDir) | |
masterLayout = QtGui.QVBoxLayout() | |
masterLayout.addWidget(self.masterDisplay) | |
masterLayout.addWidget(self.scriptPaths) | |
masterLayout.addLayout(targetLayout) | |
masterLayout.addLayout(appLayout) | |
self.masterDisplay.setAcceptDrops(True) | |
self.masterDisplay.dragEnterEvent = self.dragEnterEvent | |
self.masterDisplay.dragMoveEvent = self.dragMoveEvent | |
self.masterDisplay.dropEvent = self.dropEvent | |
#self.masterDisplay.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) | |
#self.masterDisplay.customContextMenuRequested.connect(self.openMenu) | |
self.scriptPaths.setAcceptDrops(True) | |
self.scriptPaths.dragEnterEvent = self.dragEnterEvent | |
self.scriptPaths.dragMoveEvent = self.dragMoveEvent | |
self.scriptPaths.dropEvent = self.scriptDropEvent | |
btnCreateAppData.pressed.connect(self.createAppData) | |
btnRemoveDir.pressed.connect(self.removeDir) | |
btnRun.pressed.connect(self.runOnSelected) | |
self.dirIn.setLayout(masterLayout) | |
self.dirIn.show() | |
self.setupUI() | |
# ----------------------------------------------------------------------------------------------- # | |
def setFocusWidget(self, table): | |
self.inFocusWidget = table | |
""" Overidden QT drag/drop events """ | |
def dragEnterEvent(self, event): | |
event.accept() | |
def dragMoveEvent(self, event): | |
event.accept() | |
def dropEvent(self, event): | |
md = event.mimeData() | |
if md.hasUrls(): | |
for url in md.urls(): | |
urlPath = str(url.path()) | |
if urlPath.startswith('/'): | |
urlPath = urlPath[1:] | |
if not os.path.isdir(urlPath): | |
event.ignore() | |
continue | |
self.masterDisplay.insertRow(self.masterDisplay.rowCount()) | |
rowNum = self.masterDisplay.rowCount()-1 | |
item = QtGui.QTableWidgetItem(urlPath) | |
formatItem = QtGui.QTableWidgetItem('') | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
formatItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
self.masterDisplay.setItem(rowNum, 0, item) | |
else: | |
event.ignore() | |
event.accept() | |
# ----------------------------------------------------------------------------------------------- # | |
def scriptDropEvent(self, event): | |
md = event.mimeData() | |
if md.hasUrls(): | |
for url in md.urls(): | |
urlPath = str(url.path()) | |
if urlPath.startswith('/'): | |
urlPath = urlPath[1:] | |
if os.path.isdir(urlPath): | |
event.ignore() | |
continue | |
if not urlPath.split('.')[-1] in self.allowedScriptExts: | |
continue | |
self.scriptPaths.insertRow(self.scriptPaths.rowCount()) | |
rowNum = self.scriptPaths.rowCount()-1 | |
item = QtGui.QTableWidgetItem(urlPath) | |
functionNameItem = QtGui.QTableWidgetItem('') | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
functionNameItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
self.scriptPaths.setItem(rowNum, 0, item) | |
pWidget = self.setCenterCheckbox() | |
self.scriptPaths.setCellWidget(rowNum, 2, pWidget) | |
else: | |
event.ignore() | |
event.accept() | |
def setCenterCheckbox(self, checked=False): | |
""" Sets a centred checkbox """ | |
recursiveCheckBox = QtGui.QCheckBox() | |
recursiveCheckBox.setChecked(checked) | |
pWidget = QtGui.QWidget() | |
pLayout = QtGui.QHBoxLayout() | |
pLayout.addWidget(recursiveCheckBox) | |
pLayout.setAlignment(QtCore.Qt.AlignCenter) | |
pLayout.setContentsMargins(0,0,0,0) | |
pWidget.setLayout(pLayout) | |
return pWidget | |
# ----------------------------------------------------------------------------------------------- # | |
def unpackCheckBox(self, table, row, column): | |
return table.cellWidget(row, column).layout().itemAt(0).widget().isChecked() | |
# ----------------------------------------------------------------------------------------------- # | |
def chooseDir(self): | |
""" Allows the user to choose a new directory when you right click """ | |
dirPath = self.openWindowsBrowser() | |
indices = self.masterDisplay.selectedIndexes() | |
if dirPath: | |
dirPath = self.fixPathing(dirPath) | |
if not os.path.isdir(dirPath): | |
return | |
for i in indices: | |
row = i.row() | |
self.createQtContent(dirPath, row, 0, True) | |
# ----------------------------------------------------------------------------------------------- # | |
def addFormat(self, formatString=''): | |
""" Adds the format string to the format column """ | |
indices = self.masterDisplay.selectedIndexes() | |
for i in indices: | |
row = i.row() | |
column = i.column() | |
self.addFormatItem(row, column, formatString) | |
# ----------------------------------------------------------------------------------------------- # | |
def addFormatItem(self, row, column, exportFormat): | |
""" Adds a Qt item to the """ | |
formatItem = QtGui.QTableWidgetItem(exportFormat) | |
formatItem.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
self.masterDisplay.setItem(row, column, formatItem) | |
# ----------------------------------------------------------------------------------------------- # | |
def openWindowsBrowser(self): | |
dirPath = QtGui.QFileDialog.getExistingDirectory(None, 'Select Directory') | |
return dirPath | |
# ----------------------------------------------------------------------------------------------- # | |
def createAppData(self): | |
""" We create an appdata file in this function to store the source level directories and export location """ | |
appDataFile = os.path.join(self.appDataDir, 'custom_dirs.txt') | |
appDataDict = {} | |
scriptDataDict = {} | |
tableWidgets = {self.masterDisplay:appDataDict, self.scriptPaths:scriptDataDict} | |
for table in tableWidgets: | |
for r in range(table.rowCount()): | |
for c in range(table.columnCount()): | |
if c == 0: | |
source = str(table.item(r, c).text()) | |
elif c == 1: | |
if table.item(r, c) is not None: | |
format_function = str(table.item(r, c).text()) | |
else: | |
format_function = '' | |
elif c == 2: | |
checkBoxResult = self.unpackCheckBox(table, r, c) | |
format_function += '**' + str(checkBoxResult) | |
tableWidgets[table].update({source:format_function}) | |
with open(appDataFile, 'w+') as appDataFile: | |
appDataFile.write(str(appDataDict)+'\n') | |
appDataFile.write(str(scriptDataDict)) | |
# ----------------------------------------------------------------------------------------------- # | |
def setupUI(self): | |
""" Sets up the table UI on first boot """ | |
appDataFile = os.path.join(self.appDataDir, 'custom_dirs.txt') | |
exportDirs = None | |
scriptPaths = None | |
if not os.path.exists(appDataFile): | |
return | |
else: | |
with open(appDataFile) as f: | |
i = 0 | |
for line in f: | |
if i == 0: | |
exportDirs = ast.literal_eval(line) | |
elif i == 1: | |
scriptPaths = ast.literal_eval(line) | |
i += 1 | |
dicts = [exportDirs, scriptPaths] | |
for index, d in enumerate(dicts): | |
if index == 0: | |
table = self.masterDisplay | |
elif index == 1: | |
table = self.scriptPaths | |
if len(d) > 0: | |
for index, source in enumerate(d): | |
table.insertRow(index) | |
content = d[source] | |
# If our table is script_paths we need to split our value to get our true/false checkbox value | |
if table.objectName() == 'script_paths': | |
temp = d[source].split('**') | |
content = temp[0] | |
checked = temp[1] | |
if checked == 'True': | |
checked = True | |
else: | |
checked = False | |
pWidget = self.setCenterCheckbox(checked) | |
table.setCellWidget(index, 2, pWidget) | |
self.createQtContent(source, index, 0, table) | |
self.createQtContent(content, index, 1, table) | |
# ----------------------------------------------------------------------------------------------- # | |
def createQtContent(self, content, row, column, table): | |
""" Creates a QTableWidgetItem """ | |
item = QtGui.QTableWidgetItem(content) | |
if column == 1: | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable) | |
else: | |
item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled) | |
table.setItem(row, column, item) | |
# ----------------------------------------------------------------------------------------------- # | |
def fixPathing(self, filePath): | |
""" Replaces pathing slashes """ | |
return filePath.replace('\\', '/') | |
# ----------------------------------------------------------------------------------------------- # | |
def removeDir(self): | |
""" Removes the directory from the UI and saves the file | |
Important tip is to remove rows incrementally in reverse """ | |
try: | |
indices = self.inFocusWidget.selectedIndexes() | |
except AttributeError: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please ensure widget is active') | |
return | |
# Get all row index | |
indexes = [] | |
for i in indices: | |
indexes.append(i.row()) | |
# Reverse sort rows indexes | |
indexes = sorted(indexes, reverse=True) | |
# Delete rows | |
for rowidx in indexes: | |
print 'removing', rowidx | |
self.inFocusWidget.removeRow(rowidx) | |
# ----------------------------------------------------------------------------------------------- # | |
def runOnSelected(self): | |
""" Runs over the selected directories """ | |
indices = self.masterDisplay.selectedIndexes() | |
scriptPathIndices = self.scriptPaths.selectedItems() | |
if len(indices) < 1 or len(scriptPathIndices)< 1: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please select a script and relevant directories') | |
return | |
row = scriptPathIndices[0].row() | |
scriptPath = str(self.scriptPaths.item(row, 0).text()) | |
scriptDir = os.path.dirname(scriptPath) | |
scriptName = ntpath.basename(scriptPath).split('.')[0] | |
functionName = str(self.scriptPaths.item(row, 1).text()) | |
recursive = self.unpackCheckBox(self.scriptPaths, row, 2) | |
if not os.path.exists(scriptPath): | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Your script no longer lives in this location\nPlease remove and choose new location') | |
return | |
if not scriptDir in sys.path: | |
sys.path.append(scriptDir) | |
try: | |
externalFunc = getattr(importlib.import_module(scriptName), functionName) | |
except AttributeError: | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Please check script pathing and function name is correct') | |
return | |
print 'Running' | |
for i in indices: | |
row = i.row() | |
sourceDir = self.masterDisplay.item(row, 0) | |
formatString = self.masterDisplay.item(row, 1) | |
if sourceDir is None or formatString is None or sourceDir == '' or formatString == '': | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info','Cannot run without a source dir or format') | |
return | |
sourceDir = str(sourceDir.text()) | |
formatString = str(formatString.text()) | |
for root, dirs, files in os.walk(sourceDir): | |
if len(files) > 0: | |
self.runExternalFunction(root, files, formatString, externalFunc) | |
if recursive == False: | |
break | |
QtGui.QMessageBox.information(self.masterDisplay, 'Info', 'Process complete') | |
def runExternalFunction(self, root, files, format, func): | |
for f in files: | |
if os.path.splitext(f)[-1] == format: | |
myFile = os.path.join(root, f) | |
func(myFile) | |
def run(): | |
app = QtGui.QApplication(sys.argv) | |
ex = DirectoryFunctions() | |
sys.exit(app.exec_()) | |
run() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment