Create shader from file selection and apply to selected object - python

You select a folder with a bunch of images, select the one you wish to use from a dropdown menu, then select your object and hit apply. My issue is that I cannot do this to multiple objects in a scene, it only changes the material on the first object that is selected. When I try to do this to another object in my scene it just replaces the image on the first object and doesn't create another shader. The goal is to be able to select any object in the scene and apply a texture from the selected list to any of the objects in the scene. Help would be greatly appreciated. I provided the tool down below.
import maya.cmds as cmds
from os import listdir
class TextureImport():
def __init__(self):
if cmds.window(TextureImport, q=True, exists=True):
cmds.deleteUI(TextureImport)
GUI=cmds.window(title="Texture Import Tool", widthHeight=(250,160), s=True, tlb=True)
cmds.rowColumnLayout(numberOfColumns=1, columnAlign=(1, 'center'), columnAttach=(1, 'both', 0), cw=(1,250))
cmds.button(label="Select Directory", command=self.select_dir)
cmds.separator(style='in', h=20)
cmds.optionMenu('optionMenu', label="File List")
cmds.button(label="Clear List", command=self.clear_list)
cmds.separator(style='in', h=20)
cmds.text('Select your object, then:', h=25)
cmds.button(label="Apply Texture", command=self.apply_texture)
cmds.setParent('..')
cmds.showWindow()
def select_dir(self, *args):
basicFilter = "Image Files (*.jpg *.jpeg *.tga *.png *.tiff *.bmp *.psd)"
self.myDir = cmds.fileDialog2 (fileFilter=basicFilter, dialogStyle=2, fm=3)
myFiles = listdir(self.myDir[0])
for items in myFiles:
fileEndings = ('.psd','.PSD','.jpg','JPG','.jpeg','.JPEG','.tga','.TGA','.png','.PNG','.tiff','.TIFF','.bmp','.BMP')
if items.endswith(fileEndings):
cmds.menuItem(items)
else:
cmds.warning(items + 'This is not a valid image type, you fool.')
print myFiles
def clear_list(self, *args):
fileList = cmds.optionMenu('optionMenu', q=True, itemListLong=True)
if fileList:
cmds.deleteUI(fileList)
def apply_texture(self, *args):
object = cmds.ls(sl=True)
selectedMenuItem = cmds.optionMenu('optionMenu', q=True, value=True)
cmds.sets(name='imageMaterialGroup', renderable=True, empty=True)
shaderNode = cmds.shadingNode('phong', name='shaderNode', asShader=True)
fileNode = cmds.shadingNode('file', name='fileTexture', asTexture=True)
cmds.setAttr('fileTexture'+'.fileTextureName', self.myDir[0]+'/'+selectedMenuItem, type="string")
shadingGroup = cmds.sets(name='textureMaterialGroup', renderable=True, empty=True)
cmds.connectAttr('shaderNode'+'.outColor','textureMaterialGroup'+'.surfaceShader', force=True)
cmds.connectAttr('fileTexture'+'.outColor','shaderNode'+'.color', force=True)
cmds.surfaceShaderList('shaderNode', add='imageMaterialGroup')
cmds.sets(object, e=True, forceElement='imageMaterialGroup')
TextureImport()
The goal is to be able to select any object in the scene and apply a texture from the selected list to any of the objects. For example, an artist could set up multiple planes to apply reference images on. This tool would create the shader from the selected files which would make their job very easy. Help on this would be greatly appreciated.

Your problem lies in the way you try to connect the attributes:
fileNode = cmds.shadingNode('file', name='fileTexture', asTexture=True)
cmds.setAttr('fileTexture'+'.fileTextureName', ..., type="string")
You use the explicit name of the file node: 'fileTexture'. The result is that if the node exists, the exisiting node is used instead of your newly created one. You have to build the attribute with the fileNode variable this way:
fileNode = cmds.shadingNode('file', name='fileTexture', asTexture=True)
cmds.setAttr(fileNode+'.fileTextureName', ..., type="string")
The same should be changed in the other connectAttr() functions.

Related

Pre-select multiple files in a QFileDialog

When a "choose files" dialog is displayed I want to pre-select files in a project which are already configured as being "part of" that project, so the user can select new files OR unselect existing (i.e. previously chosen) files.
This answer suggests multiple selection should be possible.
For this MRE, please make 3 files and put them in a suitable ref_dir:
from PyQt5 import QtWidgets
import sys
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.handle_button)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def handle_button(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
ref_dir = 'D:\\temp'
files_list = ['file1.txt', 'file2.txt', 'file3.txt']
fd = QtWidgets.QFileDialog(None, 'Choose project files', ref_dir, '(*.txt)')
fd.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
fd.setOptions(options)
# fd.setVisible(True)
for file in files_list:
print(f'selecting file |{file}|')
fd.selectFile(file)
string_list = fd.exec()
print(f'string list {string_list}')
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Unfortunately, despite ExistingFiles having been chosen as the file mode, I find that it is only the last file selected which has the selection... but I want all three to be selected when the dialog is displayed.
I tried experimenting with setVisible to see whether the multiple selection could be achieved somehow after the dialog is displayed, but this didn't work.
Since a non-native file dialog is being used, we can access its child widgets to control its behavior.
At first I thought about using the selection model of the item views, but this won't update the line edit, which is responsible of checking if the files exist and enabling the Ok button in that case; considering this, the obvious solution is to directly update the line edit instead:
def handle_button(self):
# ...
existing = []
for file in files_list:
if fd.directory().exists(file):
existing.append('"{}"'.format(file))
lineEdit = fd.findChild(QtWidgets.QLineEdit, 'fileNameEdit')
lineEdit.setText(' '.join(existing))
if fd.exec():
print('string list {}'.format(fd.selectedFiles()))
The only drawback of this approach is that the fileSelected and filesSelected signals are not sent.
Musicamante's answer was very, very helpful, in particular showing that the selection is in fact triggered by filling the QLE with path strings.
But in fact there is a fatal flaw when the purpose is as I have stated: unfortunately, if you try to deselect the final selected file in a directory, actually this name is not then removed from the QLE. And in fact, if the QLE is set to blank this disables the "Choose" button. All this is by design: the function of a QFileDialog is either to "open" or to "save", not to "modify".
But I did find a solution, which involves finding the QListView which lists the files in the directory, and then using a signal on its selection model.
Another thing this caters for is what happens when you change directory: obviously, you then want the selection to be updated on the basis of the project's files as found (or not found) inside that directory. I've in fact changed the text of the "choose" button to show that "modification" is the name of the game.
fd = QtWidgets.QFileDialog(app.get_main_window(), 'Modify project files', start_directory, '(*.docx)')
fd.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
fd.setViewMode(QtWidgets.QFileDialog.List)
fd.setLabelText(QtWidgets.QFileDialog.Reject, '&Cancel')
fd.setLabelText(QtWidgets.QFileDialog.Accept, '&Modify')
fd.setOptions(options)
file_name_line_edit = fd.findChild(QtWidgets.QLineEdit, 'fileNameEdit')
list_view = fd.findChild(QtWidgets.QListView, 'listView')
# utility to cope with all permutations of backslashes and forward slashes in path strings:
def split_file_path_str(path_str):
dir_path_str, filename = ntpath.split(path_str)
return dir_path_str, (filename or ntpath.basename(dir_path_str))
fd.displayed_dir = None
sel_model = list_view.selectionModel()
def sel_changed():
if not fd.displayed_dir:
return
selected_file_paths_in_shown_dir = []
sel_col_0s = sel_model.selectedRows()
for sel_col_0 in sel_col_0s:
file_path_str = os.path.join(fd.displayed_dir, sel_col_0.data())
selected_file_paths_in_shown_dir.append(file_path_str)
already_included = file_path_str in self.files_list
if not already_included:
fd.project_files_in_shown_dir.append(file_path_str)
# now find if there are any project files which are now NOT selected
for project_file_path_str in fd.project_files_in_shown_dir:
if project_file_path_str not in selected_file_paths_in_shown_dir:
fd.project_files_in_shown_dir.remove(project_file_path_str)
sel_model.selectionChanged.connect(sel_changed)
def file_dlg_dir_entered(displayed_dir):
displayed_dir = os.path.normpath(displayed_dir)
# this is set to None to prevent unwanted selection processing triggered by setText(...) below
fd.displayed_dir = None
fd.project_files_in_shown_dir = []
existing = []
for file_path_str in self.files_list:
dir_path_str, filename = split_file_path_str(file_path_str)
if dir_path_str == displayed_dir:
existing.append(f'"{file_path_str}"')
fd.project_files_in_shown_dir.append(file_path_str)
file_name_line_edit.setText(' '.join(existing))
fd.displayed_dir = displayed_dir
fd.directoryEntered.connect(file_dlg_dir_entered)
# set the initially displayed directory...
file_dlg_dir_entered(start_directory)
if fd.exec():
# for each file, if not present in self.files_list, add to files list and make self dirty
for project_file_in_shown_dir in fd.project_files_in_shown_dir:
if project_file_in_shown_dir not in self.files_list:
self.files_list.append(project_file_in_shown_dir)
# also add to list widget...
app.get_main_window().ui.files_list.addItem(project_file_in_shown_dir)
if not self.is_dirty():
self.toggle_dirty()
# but we also have to make sure that a file has not been UNselected...
docx_files_in_start_dir = [f for f in os.listdir(fd.displayed_dir) if os.path.isfile(os.path.join(fd.displayed_dir, f)) and os.path.splitext(f)[1] == '.docx' ]
for docx_file_in_start_dir in docx_files_in_start_dir:
docx_file_path_str = os.path.join(fd.displayed_dir, docx_file_in_start_dir)
if docx_file_path_str in self.files_list and docx_file_path_str not in fd.project_files_in_shown_dir:
self.files_list.remove(docx_file_path_str)
list_widget = app.get_main_window().ui.files_list
item_for_removal = list_widget.findItems(docx_file_path_str, QtCore.Qt.MatchExactly)[0]
list_widget.takeItem(list_widget.row(item_for_removal))
if not self.is_dirty():
self.toggle_dirty()
After a few hours of playing around, here's a pretty good way to programatically pre-select multiple files in a QFileDialog:
from pathlib import Path
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QFileDialog, QListView
p_files = Path('/path/to/your/files')
dlg = QFileDialog(
directory=str(p_files),
options=QFileDialog.DontUseNativeDialog)
# get QListView which controls item selection
file_view = dlg.findChild(QListView, 'listView')
# filter files which we want to select based on any condition (eg only .txt files)
# anything will work here as long as you get a list of Path objects or just str filepaths
sel_files = [p for p in p_files.iterdir() if p.suffix == '.txt']
# get selection model (QItemSelectionModel)
sel_model = file_view.selectionModel()
for p in sel_files:
# get idx (QModelIndex) from model() (QFileSystemModel) using str of Path obj
idx = sel_model.model().index(str(p))
# set the active selection using each QModelIndex
# IMPORTANT - need to include the selection type
# see dir(QItemSelectionModel) for all options
sel_model.select(idx, QItemSelectionModel.Select | QItemSelectionModel.Rows)
dlg.exec_()
dlg.selectedFiles()
>>> ['list.txt', 'of.txt', 'selected.txt', 'files.txt']

Python onclick button widget return object

I am trying to build a file/data selector in jupyter notebooks with python. The idea is that I select some files and data channels in the files with the multipleSelect widget and then with a button return a dataFrame.
How can I access the df_object?
#stack example
from ipywidgets import widgets
from IPython.display import display
from IPython.display import clear_output
import pandas as pd
import numpy as np
filenames = ["file1", "file2"]
file_dict = {
"file1":pd.DataFrame(np.arange(5)),
"file2":pd.DataFrame(np.arange(10,15))
}
def data_selection():
sel_file = widgets.SelectMultiple(description="Files",
options=filenames)
display(sel_file)
button = widgets.Button(description="OK")
display(button)
def on_button_clicked(button):
clear_output(wait=True) #clears the previous output
display(sel_file) #displays new selection window
display(button) #displays new button
for f in sel_file.value:
print (f)
display (file_dict[f])
#global df_object #would be a solution but not recommended for sure
df_object = file_dict[f]
return df_object #doesn't work
button.on_click(on_button_clicked)
data_selection()
You really should be using a class for this, and then define all your functions as acting on an instance of that class. Not all of them need to be publicly accessible as well. You can also store the df_objects in a separate attribute like a dictionary and access the dictionary using a separate function. Check out the code below:
class foo(object):
def __init__(self, file1, file2):
self.filenames = [file1, file2]
self.file_dict = {
file1:pd.DataFrame(np.arange(5)),
file2:pd.DataFrame(np.arange(10,15))
}
def _create_widgets(self):
self.sel_file = widgets.SelectMultiple(description='Files',
options=self.filenames,
value=[self.filenames[0]],
)
self.button = widgets.Button(description="OK")
self.button.on_click(self._on_button_clicked)
def _on_button_clicked(self, change):
self.out.clear_output()
self.df_objects = {}
with self.out:
for f in self.sel_file.value:
print(f)
display(self.file_dict[f])
self.df_objects[f] = self.file_dict[f]
def display_widgets(self):
self._create_widgets()
self.out = widgets.Output() # this is the output widget in which the df is displayed
display(widgets.VBox(
[
self.sel_file,
self.button,
self.out
]
)
)
def get_df_objects(self):
return self.df_objects
Then you can create instances and display the widgets like so:
something = foo('a', 'b')
something.display_widgets()
something.get_df_objects() will return a dictionary with the required 'file:dataframe_of_file' key-value pairs.
Hope this helps :)

Populate window in Maya with icons and dynamically adjust depending on window size

I want to create a window in maya, that gets populated with icons from a specific path. I know how to do that, but I also want the icons to adjust dynamically as I change the size of the window.
For example, let's say I have this:
enter image description here
and I want when I resize to get this:
enter image description here
here is a bit of the code I have :
import maya.cmds as cmds
import os
from os import listdir
def UI(*args):
if cmds.window("Test", exists = True):
cmds.deleteUI("Test")
testwindow = cmds.window("Test", t="Test Window", sizeable = 1)
cmds.scrollLayout('srcoll', p = "Test")
cmds.rowColumnLayout("ColLayout", p = "Test", nc = 3)#I imagine the "nc" command is probably useless here, I am just leaving it for testing purposes
cmds.showWindow("Test")
customPath = "C:\Users\$username$\Desktop"
customPathItems = listdir(customPath)
def popUI(*args):
for item in customPathItems:
if item.endswith("_icon.png"):
cmds.iconTextButton(l = item, p = "ColLayout", i = customPath + "/" + item, w = 128, h = 128)
def buildUI(*args):
UI()
popUI()
buildUI()
Any help would be appreciated
What you need is called a Flow layout, where the items inside the layout automatically adjust themselves when the widget is resized.
Here's an example from Qt's documentation that you can fully convert over to Python:
https://doc.qt.io/qt-4.8/qt-layouts-flowlayout-flowlayout-cpp.html
You can also google pyqt flow layout for ones already written in Python.

Creating a simple file browser using python and gtkTreeView

I am trying to create a simple file browser using python and GTK3. Inspired by an another question here I was able to make a small working example
#!/usr/bin/python
import os
from gi.repository import Gtk
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
filesystemTreeStore = Gtk.TreeStore(str)
parents = {}
for (path, dirs, files) in os.walk("/home"):
for subdir in dirs:
parents[os.path.join(path, subdir)] = filesystemTreeStore.append(parents.get(path, None), [subdir])
for item in files:
filesystemTreeStore.append(parents.get(path, None), [item])
filesystemTreeView = Gtk.TreeView(filesystemTreeStore)
renderer = Gtk.CellRendererText()
filesystemColumn = Gtk.TreeViewColumn("Title", renderer, text=0)
filesystemTreeView.append_column(filesystemColumn)
window.add(filesystemTreeView)
window.show_all()
Gtk.main()
The code works, but the result feels not much effective. I was able to read and display the whole linux filesystem, but it took a very long time. One reason could be the usage of os.walk.
Another thing is, that such code does not allow opening empty directories.
For this reason I would like to display only the content of the parent directory for which the listing is made and expand the tree gradually as the user is exploring the tree structure.
I was not able to find a solution for this yet using Python and GTK3. There is a similar solution but for Tkinter
i was able to come with a solution. There could be a better solution, but I am quite happy that it is working as I expected. I append "dummy" nodes to make the folders expandable, even if the are ampty. Had to deal with adding and removing the tree content on expanding/collapsing the treeView.
Here is my solution:
#!/usr/bin/python
import os, stat
from gi.repository import Gtk
from gi.repository.GdkPixbuf import Pixbuf
def populateFileSystemTreeStore(treeStore, path, parent=None):
itemCounter = 0
# iterate over the items in the path
for item in os.listdir(path):
# Get the absolute path of the item
itemFullname = os.path.join(path, item)
# Extract metadata from the item
itemMetaData = os.stat(itemFullname)
# Determine if the item is a folder
itemIsFolder = stat.S_ISDIR(itemMetaData.st_mode)
# Generate an icon from the default icon theme
itemIcon = Gtk.IconTheme.get_default().load_icon("folder" if itemIsFolder else "empty", 22, 0)
# Append the item to the TreeStore
currentIter = treeStore.append(parent, [item, itemIcon, itemFullname])
# add dummy if current item was a folder
if itemIsFolder: treeStore.append(currentIter, [None, None, None])
#increment the item counter
itemCounter += 1
# add the dummy node back if nothing was inserted before
if itemCounter < 1: treeStore.append(parent, [None, None, None])
def onRowExpanded(treeView, treeIter, treePath):
# get the associated model
treeStore = treeView.get_model()
# get the full path of the position
newPath = treeStore.get_value(treeIter, 2)
# populate the subtree on curent position
populateFileSystemTreeStore(treeStore, newPath, treeIter)
# remove the first child (dummy node)
treeStore.remove(treeStore.iter_children(treeIter))
def onRowCollapsed(treeView, treeIter, treePath):
# get the associated model
treeStore = treeView.get_model()
# get the iterator of the first child
currentChildIter = treeStore.iter_children(treeIter)
# loop as long as some childern exist
while currentChildIter:
# remove the first child
treeStore.remove(currentChildIter)
# refresh the iterator of the next child
currentChildIter = treeStore.iter_children(treeIter)
# append dummy node
treeStore.append(treeIter, [None, None, None])
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
# initialize the filesystem treestore
fileSystemTreeStore = Gtk.TreeStore(str, Pixbuf, str)
# populate the tree store
populateFileSystemTreeStore(fileSystemTreeStore, '/home')
# initialize the TreeView
fileSystemTreeView = Gtk.TreeView(fileSystemTreeStore)
# Create a TreeViewColumn
treeViewCol = Gtk.TreeViewColumn("File")
# Create a column cell to display text
colCellText = Gtk.CellRendererText()
# Create a column cell to display an image
colCellImg = Gtk.CellRendererPixbuf()
# Add the cells to the column
treeViewCol.pack_start(colCellImg, False)
treeViewCol.pack_start(colCellText, True)
# Bind the text cell to column 0 of the tree's model
treeViewCol.add_attribute(colCellText, "text", 0)
# Bind the image cell to column 1 of the tree's model
treeViewCol.add_attribute(colCellImg, "pixbuf", 1)
# Append the columns to the TreeView
fileSystemTreeView.append_column(treeViewCol)
# add "on expand" callback
fileSystemTreeView.connect("row-expanded", onRowExpanded)
# add "on collapse" callback
fileSystemTreeView.connect("row-collapsed", onRowCollapsed)
scrollView = Gtk.ScrolledWindow()
scrollView.add(fileSystemTreeView)
# append the scrollView to the window (this)
window.add(scrollView)
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
What you need is commonly called lazy loading, which is currently not supported by/on the ideas page of GtkTreeStore but you can still create your own YourTreeStoreLazy which implements the GtkTreeModel interface. This was done a couple of times in the past but I can not seem to find any reasonable code examples. Have a look at this post and its comments(link gone)wayback archive copy for some ideas on how to approach the implementation of getters.

Display hierarchy of selected object only

I am creating a UI where the user selects an object,
the UI will display its hierarchy of the selected
object.
It is somewhat similar to Outliner but I am unable to
find any documentation/ similar results to what I am
trying to obtain. And btw, I am coding using python...
Even so, is this even possible to do it in the first
place?
Allow me to provide a simple example below:
Say if I selects testCtrl, it will only displays
testCtrl, loc and jnt without showing the Parent (Grp 01)
Eg. Grp 01 --> testCtrl --> loc --> jnt
import maya.cmds as cmds
def customOutliner():
if cmds.ls( sl=True ):
# Create the window/UI for the custom Oultiner
newOutliner = cmds.window(title="Outliner (Custom)", iconName="Outliner*", widthHeight=(250,100))
frame = cmds.frameLayout(labelVisible = False)
customOutliner = cmds.outlinerEditor()
# Create the selection connection network; Selects the active selection
inputList = cmds.selectionConnection( activeList=True )
fromEditor = cmds.selectionConnection()
cmds.outlinerEditor( customOutliner, edit=True, mainListConnection=inputList )
cmds.outlinerEditor( customOutliner, edit=True, selectionConnection=fromEditor )
cmds.showWindow( newOutliner )
else:
cmds.warning('Nothing is selected. Custom Outliner will not be created.')
Make the window:
You want to use the treeView command (documentation) for this. I'm placing it in a formLayout for convenience.
from maya import cmds
from collections import defaultdict
window = cmds.window()
layout = cmds.formLayout()
control = cmds.treeView(parent=layout)
cmds.formLayout(layout, e=True, attachForm=[(control,'top', 2),
(control,'left', 2),
(control,'bottom', 2),
(control,'right', 2)])
cmds.showWindow(window)
Populate the tree view:
For this, we'll use a recursive function so you can build up the hierarchy with nested listRelatives calls (documentation). Start with the result of old faithful ls -sl:
def populateTreeView(control, parent, parentname, counter):
# list all the children of the parent node
children = cmds.listRelatives(parent, children=True, path=True) or []
# loop over the children
for child in children:
# childname is the string after the last '|'
childname = child.rsplit('|')[-1]
# increment the number of spaces
counter[childname] += 1
# make a new string with counter spaces following the name
childname = '{0} {1}'.format(childname, ' '*counter[childname])
# create the leaf in the treeView, named childname, parent parentname
cmds.treeView(control, e=True, addItem=(childname, parentname))
# call this function again, with child as the parent. recursion!
populateTreeView(control, child, childname, counter)
# find the selected object
selection = cmds.ls(sl=True)[0]
# create the root node in the treeView
cmds.treeView(control, e=True, addItem=(selection, ''), hideButtons=True)
# enter the recursive function
populateTreeView(control, selection, '', defaultdict(int))
Comparison of window to outliner.
I've replaced the spaces with X so you can see what's happening. Running this code will use spaces though:
You'll want to read up on the documentation to improve on this, but this should be a great starting point. If you want a live connection to the selection, make a scriptJob to track that, and be sure to clear the treeView before repopulating.

Categories

Resources