Hello all,

I posted a while ago about a UI script that could help with file 
processing, however it fell short as you would have to contain your 
processing functionality (which could get quite massive) inside the script 
or reference in your module.

It is essentially Windows based but I am curious to see how it holds up 
against OSX.

*So...I revised the class and have come up with File Runner:*

- 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!

*Feel free to edit the class however you see fit*

I have been playing around with it doing some simple stuff but it seems to 
be in working order :)

Enjoy!

-- 
You received this message because you are subscribed to the Google Groups 
"Python Programming for Autodesk Maya" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/python_inside_maya/27223267-cb5b-4f87-b20f-f66cfe050363%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
__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()

Reply via email to