Hooks: append batch setup in flowgraph

CUA_append_batch
Author: Stefan

New version with more options and view modes

This script will show up in the main menu (bottom right) and you can assign it a shortcut. I use shift+cmd+number

1) Copy some your useful (sub) setups in a folder of your choice. For now, there is a hardcoded path by default: “/opt/Autodesk//shared/presets/batch_collection”
You can also add and remove other paths (anywhere on your computer or on a network).
2) Open the app from the menu or with a shortcut. (i use shift+cmd+number)
3) Start typing the name of a setup, (letter case doesn’t matter) and you’ll see a drop down list of setups starting with what you typed.
4) click on the setup you want to append in your flowgraph and hit enter.

Click on the “Paths” button to open the paths window.
From there you can add and remove paths. The config is saved where the script lives for now.
The plan is to save it per user in a near future.
In this window (paths), if you click on a path, a new window will open, showing a list of all setups found there.
The “List All” button will list all setups in all paths.

Notes: It is not possible yet (Flame python API) to append the setup under the cursor, but you can frame the now selected node(s) using spacebar+f (first you need to click somewhere in the flame UI, but not in the flowgraph area)

Have fun and leave a comment if you have a question.

CUA_append_batch_v03a.py

Code:

'''
Stefan Gaillot
xenjee@gmail.com
2018/10/26
CUA_append_batch_v03a.py

if this is in a hook showing in the main menu, you can assign a shortcut to this utility app. I use shift+cmd+number.

------------- Main window -------------
- Opens a text dialog (+ 'paths' button) where you can start typing the name
of a desired setup and hit enter to append load the 'module' in your flowgraph
- Typing is predictive: when you type the first letter, a drop down list will show, with all setup starting with the letter(s) you typed.
- It's not case sensitive so you just type without having to guess or remember the letter cases in the setup name.
- The 'Paths' button opens a new window (Paths window) that allows to add and remove folders to the list of paths to look into,
and view selective lists of setups per path, or for all paths.

------------- Notes -------------
- The app looks for batch setups in given folders.
The original scan is recusrsive, so you can keep things organized by categories (subfolders) in the main 'batch_collection' folder (or your custom paths)
A hard coded path is added by default: default_folder = "/opt/Autodesk/shared/presets/batch_collection"
- For now, the config json file (list of paths) is saved next to the script.
-> The plan is to save it per user in the active user prefs folder.
Adds the default path at first and then let the user chose where the app should parse setups from.

------------- Paths Window -------------
- 3 buttons: add and remove paths (destinations/folders), and list all setups ('List All') in all paths.
- The json 'config' file (setups_paths) is updated in realtime.

------------- HOW TO USE -------------

1) Open the app, from main menu or with a shortcut.
2) If you already put a collections of setup in the default directory, start typing the name of a setup.
3) If you wish to add your own custom folders (not recursive yet), click on the 'Paths' button.
4) Click 'add path'
5) Navigate to your desired folder, select it and click open.
6) repeat as needed, or select a path in the list and hit 'remove path' if you want to clean up the list.
7) Close the window.
8) Close the app.
9) Things will be in order next time you open the app, start typing, select a setup, hit enter ... Bam!

In the 'Add Paths' window, if you click on a path, a new window will open showing the list of setups found in it.
There is also a 'List All' button to list all setups ('List All') in all paths.

-------------------

TODO - GENERAL
- Save the json file (list of folders to look into) ... per user.
- Add the ability to append the setup from the list view window (select and enter)

-------------------

'''

# import sys
import os
import json
import flame

from tank.platform.qt import QtGui, QtCore
# from PySide2 import QtWidgets, QtCore, QtGui

print "-" * 80
print "CUA_append_batch_v02d.py"
print "-" * 80

# os.path stuff:
# Turn the relative __file__ value into it's full path:
absolute_path = os.path.realpath(__file__)
# Use the os module to split the filepath using '/' as a seperator to creates a list from which we pick IDs []
root_path = '/'.join(absolute_path.split('/')[0:-1])

# Option to navigate down to the desired folder and append a path with sys.path:
# sys.path.append("{root}/modules".format(root=root_path))
# print "{root}/modules".format(root=root_path)

# ########################################################################

global AppendWindow, PathsWindow, ListstWindow

my_list = []

global setups_paths
# setups_paths = '/opt/Autodesk/shared/presets/setups_paths.json'
setups_paths = "{root}/setups_paths.json".format(root=root_path)

class AppendWindow(QtGui.QWidget):

def __init__(self, parent=None):
super(AppendWindow, self).__init__(parent)

# get cursor position and set offset values
self.cursor_pos = QtGui.QCursor.pos()
self.offset_cursor_pos = QtCore.QPoint(20, 40)
# Set where to look for batch setups >>> your own path goes there:
self.default_folder = "/opt/Autodesk/shared/presets/batch_collection"

# self.setMinimumSize(QtCore.QSize(280, 50))
self.setFixedSize(QtCore.QSize(290, 50))
self.move(self.cursor_pos - self.offset_cursor_pos)
self.setWindowTitle("Append Setup")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

self.Hbox_Layout = QtGui.QHBoxLayout()

self.line = QtGui.QLineEdit(self)
self.line.move(10, 10)
self.line.resize(250, 25)
self.line.setPlaceholderText('type setup name')
self.line.returnPressed.connect(self.append_setup)

self.paths_btn = QtGui.QPushButton('Paths', self)
self.paths_btn.move(210, 10)
self.connect(self.paths_btn, QtCore.SIGNAL('clicked()'), self.paths_btn_clicked)

batch_setups_list = []
items_in_json_content = PathsWindow().load_from_json()
for item in items_in_json_content:
for root, dirs, files in os.walk(item):
for file in files:
if file.endswith(".batch"):
file_only = file.split('/')[-1]
file_no_ext = os.path.splitext(file_only)[0]
batch_setups_list.append(file_no_ext)
# print "batch_setups_list in for os.walk loop", batch_setups_list

# Predictive typing. Fed with the list of available nodes in batch and what user entry.
self.completer = QtGui.QCompleter(batch_setups_list, self.line)
self.completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.completer.setCompletionMode(QtGui.QCompleter.PopupCompletion)
self.completer.setMaxVisibleItems(20)
self.line.setCompleter(self.completer)

self.show()

def paths_btn_clicked(self):
self.myPathsWindow = PathsWindow()
self.myPathsWindow.show()

# ##################################################### DEF APPEND SETUP #########################################################
def append_setup(self):

# ############## loop through json_content ################
loaded_json_content = PathsWindow().load_from_json()

for _path in loaded_json_content:
for root, dirs, files in os.walk(_path):
for file in files:
if file.endswith(".batch"):
# print "File appended, print file: ", file
if self.line.text() in file.split('/')[-1]:
filepath_reconstructed = os.path.join(root, self.line.text())
print "filepath_reconstructed: ", filepath_reconstructed
# ######################### append setup #############################
flame.batch.append_setup(str(filepath_reconstructed))
self.close()

class PathsWindow(QtGui.QWidget):

global os, json, my_list
import os

def __init__(self, parent=None):
super(PathsWindow, self).__init__(parent)

self.cursor_pos = QtGui.QCursor.pos()
self.offset_cursor_pos = QtCore.QPoint(440, 400)

self.setMinimumSize(QtCore.QSize(290, 80))
self.move(self.cursor_pos - self.offset_cursor_pos)
# self.move(200, 10)
self.setWindowTitle("collections")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

self.layout = QtGui.QHBoxLayout()

self.path_listwidget = QtGui.QListWidget(self)
# self.path_listwidget.setFixedSize(500, 90)
self.path_listwidget.setMinimumWidth(500)
self.path_listwidget.setMinimumHeight(90)
self.path_listwidget.move(10, 10)
# ############################################################ itemClicked #################################################################
self.path_listwidget.itemClicked.connect(self.feedpath_to_listWindow)

# add the default collection only if it's not there already.
if "/opt/Autodesk/shared/presets/batch_collection" not in self.load_from_json():
self.path_listwidget.addItem("/opt/Autodesk/shared/presets/batch_collection")

# Load the config into path_listwidget if it exists
if os.path.isfile(setups_paths):
get_json = self.load_from_json()
for item in get_json:
self.path_listwidget.addItem(item)

self.add_path_button = QtGui.QPushButton('Add Path', self)
self.add_path_button.setFixedSize(70, 25)
self.add_path_button.move(210, 10)
self.add_path_button.clicked.connect(self.add_path)

self.del_path_button = QtGui.QPushButton('Del Path', self)
self.del_path_button.setFixedSize(70, 25)
self.del_path_button.move(180, 10)
self.del_path_button.clicked.connect(self.remove_selected_path)

self.list_all_button = QtGui.QPushButton('list All', self)
self.list_all_button.setFixedSize(70, 25)
self.list_all_button.move(180, 10)
# ###################################################### list_all BUTTON clicked ############################################################
self.list_all_button.clicked.connect(self.feedlist_to_listWindow)

# layouts
self.layout = QtGui.QHBoxLayout()
self.buttons_area = QtGui.QVBoxLayout()
self.paths_area = QtGui.QVBoxLayout()

self.buttons_area.addWidget(self.add_path_button)
self.buttons_area.addWidget(self.del_path_button)
self.buttons_area.addWidget(self.list_all_button)
self.paths_area.addWidget(self.path_listwidget)

self.layout.addLayout(self.buttons_area)
self.layout.addLayout(self.paths_area)
self.setLayout(self.layout)

self.save_to_json()

# self.show()

self.home = ''

def add_path(self):
# home = os.getenv("HOME")
# home = AppendWindow().default_folder
# home = ''
if dir:
my_dir = QtGui.QFileDialog.getExistingDirectory(self, "Browser", self.home, QtGui.QFileDialog.ShowDirsOnly)
print "os.path.dirname(my_dir): ", os.path.dirname(my_dir)
self.home = os.path.dirname(my_dir)
my_list.append(my_dir)
self.path_listwidget.addItem(my_dir)
self.save_to_json()
self.load_from_json()

# save the list of paths to a json file (config-ish)
def save_to_json(self):

global setups_paths

itemsTextList = [str(self.path_listwidget.item(i).text()) for i in range(self.path_listwidget.count())]

with open(setups_paths, 'w') as saved_selections:
saved_selections.write(json.dumps(itemsTextList))

def load_from_json(self):
json_content = []
with open(setups_paths, 'r') as file_fd:
for line in file_fd.readlines():
cnv_line = eval(line.strip())
json_content.extend(cnv_line)
file_fd.close()
# print "JSON CONTENT: ", json_content
return json_content

def remove_selected_path(self):
listItems = self.path_listwidget.selectedItems()
if not listItems:
return
for item in listItems:
self.path_listwidget.takeItem(self.path_listwidget.row(item))
self.save_to_json()

# ##################################################### FEED PATH to ListWindow class #########################################################
def feedpath_to_listWindow(self):
current_item_path = self.path_listwidget.currentItem().text()
self.show_ListstWindow = ListstWindow(current_item_path)
self.show_ListstWindow.show()

# ##################################################### FEED LIST to listWindow class ##########################################################
def feedlist_to_listWindow(self):
list_from_json = self.load_from_json()
self.show_ListstWindow = ListstWindow(list_from_json)
self.show_ListstWindow.show()

class ListstWindow(QtGui.QDialog):

def __init__(self, *args, **kwargs):
super(ListstWindow, self).__init__()

paths = args[0]

print ''
print "Is path a list? ", isinstance(paths, list)

if type(paths) is not list:
paths = [paths]
print "Now it should be a list: ", isinstance(paths, list)

self.cursor_pos = QtGui.QCursor.pos()
self.offset_cursor_pos = QtCore.QPoint(300, 10)
self.move(- self.cursor_pos - self.offset_cursor_pos)
# self.setMinimumSize(200, 400)

self.setWindowTitle("Toolkit")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

self.layout = QtGui.QVBoxLayout()

self.setups_listwidget = QtGui.QListWidget(self)
self.setups_listwidget.setSortingEnabled(True)
self.setups_listwidget.sortItems()  # ascending by default

for path in paths:
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith(".batch"):
file_only = file.split('/')[-1]
file_no_ext = os.path.splitext(file_only)[0]
self.setups_listwidget.addItem(file_no_ext)

# self.setups_listwidget.setMinimumSize(300, self.setups_listwidget.sizeHintForRow(1) * self.setups_listwidget.count() + 2 * self.setups_listwidget.frameWidth())
self.setups_listwidget.setMinimumSize(300, 500)
self.layout.addWidget(self.setups_listwidget)
self.setLayout(self.layout)
self.show()

# FLAME MENU ENTRY

def getMainMenuCustomUIActions():

script1 = {}
script1["name"] = "append_batch"
script1["caption"] = "append batch"

Menu1 = {}
Menu1["name"] = "append batch"
Menu1["actions"] = (script1,)

return (Menu1,)

# If click this menu, do that:
def customUIAction(info, userData):

if info['name'] == 'append_batch':
# AppendWindow()

import flame
import os

# ######### CONFIG FILEPATH ############ #
setups_paths = "{root}/setups_paths.json".format(root=root_path)
print "setups_paths: ", setups_paths

# create a dummy list of dict to create and feed a .json config file if there was none.
default_path = ["/opt/Autodesk/shared/presets/batch_collection"]

if not os.path.isfile(setups_paths):
print "-" * 30 + "FILE NOT FOUND, CREATING FILE" + "-" * 30

with open(setups_paths, "w+") as myfilepy:
myfilepy.write(json.dumps(default_path))
myfilepy.close()

form = AppendWindow()
form.show()
return form

# end of script

Leave a Reply