Copy link to clipboard
Copied
I asume it could be done by reading with "find text" option in something like total commander ? is there any easy quick method?
Hey Kirill,
I have now a folder in my scripts directory named "for_kirill", because you have so many great ideas and I hope to make them all happen.
For this one, here is a working prototype, not the nicest UI and not really tested, but it should serve the purpose.
Just copy this code and run it in the SD Python Editor (see the attached video, for a quick demo)
import xml.etree.ElementTree as ET
import os
import sd # type: ignore
from PySide6 import QtWidgets
from PySide6 import QtGui
cl
...
Copy link to clipboard
Copied
Hello,
Indeed, considering SBS files use the XML format they are quite easy to parse. You can check the 'options' tag for export options, which should include output names, file names and destination path.
Here is a sample :
<options>
<option>
<name v="export/fromGraph/outputs/metallic"/>
<value v="true"/>
</option>
<option>
<name v="export/fromGraph/pattern"/>
<value v="$(graph)_$(identifier)_$(colorspace)"/>
</option>
</options>
I hope this is helpful!
Best regards.
Copy link to clipboard
Copied
Sorry I am not sure I understand . For example I remember one of my sbs files should have a graph "grass3_3 " and output grass3_3_nm.tga
While the sbs itself is named other way and I don;t remember how exactly. Includes many outputs and versions. In your example I see nothing like graph or file name .
I accustomed to use "total commander" . it has "find text" in search dialog. It has many options and one of them is "office xml +EPUB " and also like : UTF8, unicode UTF16, Hex, RegEX, ANSI chrseet (windows), ANSII Dos. Sometimes I can't find specific thing in a project folder while 100% sure the graph of my interest should be there. So should "office xml" be enough or shoold I include other encoding options? Maybe a way to do it in Windows file explorer itself or Adobe bridge? What would be an easy way?
Would be nice probably if sbs file could list its content as keywords, tags whatever Windows could find. Or write XMP side car files with this info as an option. Almost evry digital assets manager can read them. I asked chat gpt to make me such script for Blender and it got super easy to find a specific object but no sucsses with Designer . Chat just can't do anything working for Designer.
Copy link to clipboard
Copied
Hey Kirill,
I have now a folder in my scripts directory named "for_kirill", because you have so many great ideas and I hope to make them all happen.
For this one, here is a working prototype, not the nicest UI and not really tested, but it should serve the purpose.
Just copy this code and run it in the SD Python Editor (see the attached video, for a quick demo)
import xml.etree.ElementTree as ET
import os
import sd # type: ignore
from PySide6 import QtWidgets
from PySide6 import QtGui
class ListItem(QtWidgets.QWidget):
def __init__(
self,
filepath: str,
):
super().__init__()
self.filepath = filepath
# LAYOUT
self.header_layout = QtWidgets.QVBoxLayout(self)
self.filename_label = QtWidgets.QLabel(filepath)
self.filename_label.setSizePolicy(
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding,
)
self.header_layout.addWidget(self.filename_label)
def mouseDoubleClickEvent(self, event):
ctx = sd.getContext()
app = ctx.getSDApplication()
app.getPackageMgr().loadUserPackage(self.filepath)
return super().mouseDoubleClickEvent(event)
def enterEvent(self, event):
self.filename_label.setText(f"<html><body><u>{self.filepath}</u></body></html>")
self.filename_label.setCursor(QtGui.Qt.CursorShape.PointingHandCursor)
super().enterEvent(event)
self.update()
def leaveEvent(self, event):
self.filename_label.setText(self.filepath)
self.filename_label.setCursor(QtGui.Qt.CursorShape.ArrowCursor)
super().leaveEvent(event)
self.update()
class ScrollableListWidget(QtWidgets.QScrollArea):
def __init__(self, parent=None):
super().__init__(parent)
self.list_items = []
self.list_widget = QtWidgets.QWidget()
self.list_layout = QtWidgets.QVBoxLayout(self.list_widget)
self.setStyleSheet(f"background-color:#262626;")
self.setWidget(self.list_widget)
self.setWidgetResizable(True)
def add_list_item(self, item):
item.setParent(self)
self.list_items.append(item)
self.list_layout.insertWidget(self.list_layout.count() - 1, item)
def clear_list_items(self):
for list_item in self.list_items:
self.list_layout.removeWidget(list_item)
list_item.setParent(None)
self.list_items.clear()
class SBSFileSearchDialog(QtWidgets.QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Search SBS Files")
self.setMinimumSize(640, 256)
self.search_path = os.getcwd()
# FILE PATH
self.search_path_layout = QtWidgets.QHBoxLayout()
self.lbl_search_path = QtWidgets.QLabel(self.search_path)
self.btn_search_path = QtWidgets.QPushButton("Choose Path")
self.btn_search_path.setFixedWidth(96)
self.btn_search_path.clicked.connect(self.choose_search_file_path)
self.search_path_layout.addWidget(self.lbl_search_path)
self.search_path_layout.addWidget(self.btn_search_path)
# SEARCH KEYWORD
self.search_string_layout = QtWidgets.QHBoxLayout()
self.txt_search_string = QtWidgets.QLineEdit("")
self.txt_search_string.setPlaceholderText("Keyword...")
self.btn_search_string = QtWidgets.QPushButton("Search")
self.btn_search_string.setFixedWidth(96)
self.btn_search_string.clicked.connect(self.search_string_in_files)
self.search_string_layout.addWidget(self.txt_search_string)
self.search_string_layout.addWidget(self.btn_search_string)
# SEARCH OPTIONS
self.search_options_layout = QtWidgets.QGridLayout()
# MATCHING
self.lbl_search_options = QtWidgets.QLabel("Search Options")
self.opt_match_case = QtWidgets.QCheckBox("Match Case")
self.opt_match_whole_word = QtWidgets.QCheckBox("Match Whole Word")
self.search_options_layout.addWidget(self.lbl_search_options, 0, 1)
self.search_options_layout.addWidget(self.opt_match_case, 1, 1)
self.search_options_layout.addWidget(self.opt_match_whole_word, 2, 1)
# INCLUDE / EXCLUDE
self.lbl_include_options = QtWidgets.QLabel("Include Options")
self.opt_include_graph_identifier = QtWidgets.QCheckBox("Graph Identifier")
self.opt_include_graph_identifier.setChecked(True)
self.opt_include_output_identifier = QtWidgets.QCheckBox("Outputs Identifier")
self.opt_include_input_parameters = QtWidgets.QCheckBox("Graph Parameters")
self.opt_include_dependencies = QtWidgets.QCheckBox("Graph Dependencies")
self.search_options_layout.addWidget(self.lbl_include_options, 0, 0)
self.search_options_layout.addWidget(self.opt_include_graph_identifier, 1, 0)
self.search_options_layout.addWidget(self.opt_include_output_identifier, 2, 0)
self.search_options_layout.addWidget(self.opt_include_input_parameters, 3, 0)
self.search_options_layout.addWidget(self.opt_include_dependencies, 4, 0)
# RESULTS
self.lbl_search_results = QtWidgets.QLabel(
"Search Results (Double Click to open the package in SD)"
)
self.list_search_results = ScrollableListWidget()
# MAIN LAYOUT
self.main_layout = QtWidgets.QVBoxLayout(self)
self.main_layout.addLayout(self.search_path_layout)
self.main_layout.addLayout(self.search_string_layout)
self.main_layout.addLayout(self.search_options_layout)
self.main_layout.addSpacerItem(QtWidgets.QSpacerItem(0, 8))
self.main_layout.addWidget(self.lbl_search_results)
self.main_layout.addWidget(self.list_search_results)
def choose_search_file_path(self):
directory = QtWidgets.QFileDialog.getExistingDirectory(
None, "Select Directory", "."
)
if directory:
self.lbl_search_path.setText(directory)
self.search_path = directory
def search_string_in_files(self):
self.list_search_results.clear_list_items()
keyword = self.txt_search_string.text()
sbs_files = self.list_sbs_files(self.search_path)
for current_file_index, sbs_file in enumerate(sbs_files):
print(
f"SEARCHING FILE {current_file_index + 1}/{len(sbs_files)}:\n{sbs_file}"
)
keyword_found = False
filepath = sbs_file
tree = ET.parse(filepath)
root = tree.getroot()
dependencies = None
content = None
graphs = None
dependencies = root.find("dependencies")
content = root.find("content")
if content is not None:
graphs = content.findall("graph")
if self.opt_include_graph_identifier.isChecked():
for graph in graphs:
if graph is not None:
identifier = graph.find("identifier")
if identifier is not None:
value = identifier.attrib.get("v")
if self.match_keyword(value, keyword):
keyword_found = True
if self.opt_include_dependencies.isChecked():
if dependencies is not None:
for dependency in dependencies:
if dependency is not None:
filename = dependency.find("filename")
if filename is not None:
value = filename.attrib.get("v")
if self.match_keyword(value, keyword):
keyword_found = True
if self.opt_include_input_parameters.isChecked():
if graph is not None:
for graph in graphs:
paraminputs = graph.find("paraminputs")
if paraminputs is not None:
for paraminput in paraminputs:
if paraminput is not None:
identifier = paraminput.find("identifier")
if identifier is not None:
value = identifier.attrib.get("v")
if self.match_keyword(value, keyword):
keyword_found = True
if self.opt_include_output_identifier.isChecked():
for graph in graphs:
if graph is not None:
graphoutputs = graph.find("graphOutputs")
if graphoutputs is not None:
for graphoutput in graphoutputs:
if graphoutput is not None:
identifier = graphoutput.find("identifier")
if identifier is not None:
value = identifier.attrib.get("v")
if self.match_keyword(value, keyword):
keyword_found = True
if keyword_found:
list_item = ListItem(filepath)
self.list_search_results.add_list_item(list_item)
def match_keyword(self, search_value: str, keyword: str):
if self.opt_match_case.isChecked():
if self.opt_match_whole_word.isChecked():
if keyword == search_value:
return True
else:
if keyword in search_value:
return True
else:
if self.opt_match_whole_word.isChecked():
if keyword.lower() == search_value.lower():
return True
else:
if keyword.lower() in search_value.lower():
return True
return False
def list_sbs_files(self, directory):
sbs_files = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".sbs"):
sbs_files.append(os.path.join(root, file))
return sbs_files
dialog = SBSFileSearchDialog()
dialog.show()
Stay healthy and creative
Marco
Copy link to clipboard
Copied
Thank you very much Marco. Such a great script . Works perfectly.