ExpandOptionsFullCombinatorials.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. import sys
  7. import functools
  8. import azlmbr.bus
  9. import azlmbr.shadermanagementconsole
  10. import azlmbr.shader
  11. import azlmbr.math
  12. import azlmbr.name
  13. import azlmbr.rhi
  14. import azlmbr.atom
  15. import azlmbr.atomtools
  16. import GenerateShaderVariantListUtil
  17. from PySide2 import QtWidgets
  18. from PySide2 import QtCore
  19. from PySide2 import QtGui
  20. from PySide2.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QListWidget, QPushButton, QMessageBox, QLabel, QGroupBox, QComboBox, QSplitter, QWidget, QLineEdit, QSpinBox, QCheckBox, QButtonGroup, QRadioButton, QAction, QMenu
  21. # globals
  22. numVariantsInDocument = 0
  23. optionsByNames = {} # dict lookup accelerator (name to descriptor)
  24. valueRestrictions = {} # option name to list of values that are accepted for this option (absence of registration in this dict = use the whole range)
  25. # constants
  26. SortAlpha = 0
  27. SortRank = 1
  28. SortCost = 2
  29. def nextElement(inlist, after):
  30. """ O(N) complexity, generic utility """
  31. """ nextElement(inlist=['a', 'b', 'c'], after='b') is 'c' """
  32. try:
  33. idx = inlist.index(after)
  34. return inlist[idx + 1]
  35. except:
  36. return after + 1 # need to return something comparable to `end` so that the test "digit > maxValue(desc)" in the `increment` function may pass
  37. def inclusiveRange(start, end):
  38. return range(start, end + 1)
  39. def isDocumentOpen(document_id: azlmbr.math.Uuid) -> bool:
  40. return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
  41. azlmbr.bus.Broadcast, "IsDocumentOpen", document_id)
  42. def beginEdit(document_id):
  43. azlmbr.atomtools.AtomToolsDocumentRequestBus(
  44. azlmbr.bus.Event, "BeginEdit", document_id)
  45. def endEdit(document_id):
  46. azlmbr.atomtools.AtomToolsDocumentRequestBus(
  47. azlmbr.bus.Event, "EndEdit", document_id)
  48. def transferSelection(qlistSrc, qlistDst):
  49. '''intended to work with 2 QListWidget'''
  50. items = qlistSrc.selectedItems()
  51. qlistDst.addItems([x.text() for x in items])
  52. for i in items:
  53. qlistSrc.takeItem(qlistSrc.row(i))
  54. def listItems(qlist):
  55. return (qlist.item(i) for i in range(0, qlist.count()))
  56. def getRank(name):
  57. return optionsByNames[name].GetOrder()
  58. def getCost(name):
  59. return optionsByNames[name].GetCostEstimate()
  60. def infoStr(name):
  61. '''for tooltip'''
  62. return f"rank: {getRank(name)} | cost: {getCost(name)}"
  63. def sumCost(descriptors):
  64. return sum((x.GetCostEstimate() for x in descriptors))
  65. @functools.cache # memoize
  66. def totalCost():
  67. return sumCost(optionsByNames.values())
  68. def toInt(digit):
  69. '''extract the python integer from a boxed option description'''
  70. return digit.GetIndex() + 0
  71. def createOptionValue(intVal):
  72. return azlmbr.shadermanagementconsole.ShaderManagementConsoleRequestBus(
  73. azlmbr.bus.Broadcast, 'MakeShaderOptionValueFromInt',
  74. intVal)
  75. def intFromOptionValueName(optionName, valueNameStr):
  76. n = azlmbr.name.Name(valueNameStr)
  77. return toInt(optionsByNames[optionName].FindValue(n))
  78. def hasRestrictions(optionName):
  79. return optionName in valueRestrictions \
  80. and len(valueRestrictions[optionName]) < optionsByNames[optionName].GetValuesCount() # if everything is "checked" that's no restriction
  81. # check if a value should be skipped for counting (not included in enumeration because it's unchecked)
  82. def isValueRestricted(optionName, valueNameStr):
  83. valAsInt = intFromOptionValueName(optionName, valueNameStr)
  84. if hasRestrictions(optionName):
  85. return valAsInt not in valueRestrictions[optionName]
  86. return False
  87. # "virtualize" access to GetMinValue to reflect the potential value-space restriction
  88. def getMinValue(descriptor):
  89. global valueRestrictions
  90. name = str(descriptor.GetName())
  91. if hasRestrictions(name):
  92. return createOptionValue(valueRestrictions[name][0])
  93. else:
  94. return descriptor.GetMinValue()
  95. # "virtualize" access to GetMaxValue to reflect the potential value-space restriction
  96. def getMaxValue(descriptor):
  97. global valueRestrictions
  98. name = str(descriptor.GetName())
  99. if hasRestrictions(name):
  100. return createOptionValue(valueRestrictions[name][-1])
  101. else:
  102. return descriptor.GetMaxValue()
  103. # same concept as above
  104. def getValuesCount(descriptor):
  105. global valueRestrictions
  106. name = str(descriptor.GetName())
  107. if hasRestrictions(name):
  108. return len(valueRestrictions[name])
  109. else:
  110. return descriptor.GetValuesCount()
  111. # "virtualize" `increment by one` to be able to jump over restricted values
  112. def getNextValueInt(descriptor, digit):
  113. global valueRestrictions
  114. name = str(descriptor.GetName())
  115. if hasRestrictions(name):
  116. possibles = valueRestrictions[name]
  117. return nextElement(inlist=possibles, after=digit)
  118. else:
  119. return digit + 1
  120. # small window to select sub-ranges into an option's possible values
  121. class ValueSelector(QDialog):
  122. def __init__(self, optionName, optionValuesNames):
  123. super().__init__()
  124. self.setWindowTitle("[" + optionName + "] - restrict enumerated values")
  125. self.optionName = optionName
  126. self.optionValuesNames = optionValuesNames
  127. self.initUI()
  128. def initUI(self):
  129. mainvl = QVBoxLayout(self)
  130. self.valueList = QListWidget()
  131. for idx, optVN in enumerate(self.optionValuesNames):
  132. self.valueList.insertItem(idx, optVN)
  133. added = self.valueList.item(idx)
  134. added.setFlags(added.flags() | QtCore.Qt.ItemIsUserCheckable)
  135. added.setCheckState(QtCore.Qt.Unchecked if isValueRestricted(self.optionName, optVN) else QtCore.Qt.Checked)
  136. mainvl.addWidget(self.valueList)
  137. twoBtnsHL = QHBoxLayout(self)
  138. self.cancel = QPushButton("Cancel")
  139. twoBtnsHL.addWidget(self.cancel)
  140. self.ok = QPushButton("Ok")
  141. twoBtnsHL.addWidget(self.ok)
  142. mainvl.addLayout(twoBtnsHL)
  143. self.cancel.clicked.connect(self.close)
  144. self.ok.clicked.connect(self.storeRestriction)
  145. self.setMaximumWidth(500)
  146. self.setMaximumHeight(1100)
  147. self.resize(380, 300)
  148. # on OK click
  149. def storeRestriction(self):
  150. '''update the global dictionary of usable values for this option'''
  151. global valueRestrictions
  152. restrictedList = [intFromOptionValueName(self.optionName, x.text()) for x in listItems(self.valueList) if x.checkState() == QtCore.Qt.Checked]
  153. if len(restrictedList) == 0:
  154. msgBox = QMessageBox()
  155. msgBox.setText("Keep at least one value")
  156. msgBox.setInformativeText("Nothing to enumerate: just remove the option from the pariticipants altogether.")
  157. msgBox.setStandardButtons(QMessageBox.Ok)
  158. msgBox.exec()
  159. return
  160. # save:
  161. valueRestrictions[self.optionName] = restrictedList
  162. self.accept() # exit dialog
  163. # that's the control that holds 2 face to face lists doing communicating vases
  164. class DoubleList(QtCore.QObject):
  165. left = QListWidget()
  166. leftSubLbl = QLabel()
  167. right = QListWidget()
  168. rightSubLbl = QLabel()
  169. add = QPushButton(">")
  170. rem = QPushButton("<")
  171. layout = QHBoxLayout()
  172. changed = QtCore.Signal()
  173. order = SortCost
  174. def __init__(self, optionNamesList):
  175. super(DoubleList, self).__init__()
  176. self.left.setSelectionMode(QListWidget.MultiSelection)
  177. self.right.setSelectionMode(QListWidget.MultiSelection)
  178. for i in optionNamesList:
  179. self.left.addItem(i)
  180. self.maintainOrder()
  181. subV1 = QVBoxLayout()
  182. subV1.addWidget(self.left)
  183. subV1.addWidget(self.leftSubLbl)
  184. self.layout.addLayout(subV1)
  185. midstack = QVBoxLayout()
  186. midstack.addStretch()
  187. midstack.addWidget(self.add)
  188. midstack.addWidget(self.rem)
  189. midstack.addStretch()
  190. self.layout.addLayout(midstack)
  191. subV2 = QVBoxLayout()
  192. subV2.addWidget(self.right)
  193. subV2.addWidget(self.rightSubLbl)
  194. self.layout.addLayout(subV2)
  195. self.left.setStyleSheet("""QListWidget{ background: #27292D; }""")
  196. self.right.setStyleSheet("""QListWidget{ background: #262B35; }""")
  197. self.add.clicked.connect(lambda: self.addClick())
  198. self.rem.clicked.connect(lambda: self.remClick())
  199. self.refreshCountLabels()
  200. self.right.viewport().installEventFilter(self)
  201. self.right.setMouseTracking(True)
  202. def addClick(self):
  203. transferSelection(self.left, self.right)
  204. self.changed.emit()
  205. self.refreshCountLabels()
  206. def remClick(self):
  207. transferSelection(self.right, self.left)
  208. self.maintainOrder()
  209. self.changed.emit()
  210. self.refreshCountLabels()
  211. def resetAllToLeft(self):
  212. self.right.selectAll()
  213. self.remClick()
  214. self.left.clearSelection()
  215. def maintainOrder(self):
  216. elems = [li.text() for li in listItems(self.left)]
  217. self.left.clear()
  218. keyGetters = [lambda x: x, getRank, getCost]
  219. elems.sort(reverse = self.order==SortCost, key=keyGetters[self.order])
  220. self.left.addItems(elems)
  221. for li in listItems(self.left):
  222. li.setToolTip(infoStr(li.text()))
  223. def refreshCountLabels(self):
  224. self.leftSubLbl.setText(str(self.left.count()) + " elements")
  225. self.rightSubLbl.setText(str(self.right.count()) + " elements")
  226. def refreshLabelColors(self): # to mark value-restricted options
  227. for elem in listItems(self.right):
  228. if hasRestrictions(elem.text()):
  229. elem.setForeground(QtGui.QColor(0xbf, 0x8f, 0xff)) # mauve
  230. else:
  231. elem.setForeground(QtGui.QBrush()) # default
  232. def eventFilter(self, source, event):
  233. if event.type() == QtCore.QEvent.ContextMenu and source is self.right.viewport():
  234. menu = QMenu()
  235. menu.addAction("Customize generated values")
  236. if menu.exec_(event.globalPos()):
  237. self.restrictValueSpace()
  238. return True
  239. return False
  240. def restrictValueSpace(self):
  241. items = self.right.selectedItems()
  242. if len(items) == 1:
  243. optName = items[0].text()
  244. desc = optionsByNames[optName]
  245. optionValuesNames = [desc.GetValueName(createOptionValue(v)).ToString() for v in inclusiveRange(toInt(desc.GetMinValue()), toInt(desc.GetMaxValue()))]
  246. subdialog = ValueSelector(optName, optionValuesNames)
  247. subdialog.setModal(True)
  248. if subdialog.exec() == QDialog.Accepted:
  249. self.changed.emit()
  250. self.refreshLabelColors()
  251. class Dialog(QDialog):
  252. def __init__(self, options):
  253. super().__init__()
  254. self.optionDescriptors = options
  255. self.initUI()
  256. self.participantNames = []
  257. self.listOfDigitArrays = []
  258. def initUI(self):
  259. self.setWindowTitle("Full variant combinatorics enumeration for select options")
  260. # Create the layout for the dialog box
  261. mainvl = QVBoxLayout(self)
  262. leftrightCut = QHBoxLayout()
  263. mainvl.addLayout(leftrightCut)
  264. # Create the list box on the left
  265. self.optionsSelector = DoubleList([x.GetName().ToString() for x in self.optionDescriptors])
  266. listGroup = QGroupBox("Add desired participating options from the left bucket, to the selection bucket on the right. Right click on options in the right bucket to further configure used values.")
  267. listGroup.setLayout(self.optionsSelector.layout)
  268. vsplitter = QSplitter(QtCore.Qt.Horizontal)
  269. vsplitter.addWidget(listGroup)
  270. leftrightCut.addWidget(vsplitter)
  271. # Create the buttons on the right
  272. actionPaneLayout = QVBoxLayout()
  273. sortHbox = QHBoxLayout()
  274. sortHbox.addWidget(QLabel("Sort options by:"))
  275. self.sortChoices = QComboBox()
  276. self.sortChoices.addItem("Alphabetical")
  277. self.sortChoices.addItem("Rank")
  278. self.sortChoices.addItem("Analyzed Cost")
  279. self.sortChoices.setCurrentIndex(2)
  280. self.sortChoices.currentIndexChanged.connect(self.changeSort)
  281. self.sortChoices.view().setMinimumWidth(self.sortChoices.minimumSizeHint().width() * 1.4) # fix a Qt bug that doesn't prepare enough space
  282. #self.sortChoices.SizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
  283. sortHbox.addWidget(self.sortChoices)
  284. sortHbox.addStretch()
  285. actionPaneLayout.addLayout(sortHbox)
  286. autoSelGp = QGroupBox("Auto selection suggestion from targeted count (prioritizing highest sorted options)")
  287. autoSelGpHL = QHBoxLayout()
  288. autoSelGp.setLayout(autoSelGpHL)
  289. autoSelGpHL.addWidget(QLabel("Target:"))
  290. self.target = QSpinBox()
  291. self.target.setMaximum(100000)
  292. self.target.setValue(32)
  293. autoSelGpHL.addWidget(self.target)
  294. suggest = QPushButton("Generate auto selection", self)
  295. suggest.clicked.connect(self.autogenSelectionBucket)
  296. autoSelGpHL.addWidget(suggest)
  297. autoSelGpHL.addStretch()
  298. actionPaneLayout.addWidget(autoSelGp)
  299. mixOptBox = QGroupBox("Generation method:")
  300. mixOptBoxHL = QHBoxLayout()
  301. mixOptBox.setLayout(mixOptBoxHL)
  302. self.mixOpt_append = QRadioButton("Append")
  303. self.mixOpt_append.setToolTip("Just add the generated combinations at the end of the variant list, with non-participating options set as dynamic")
  304. self.mixOpt_append.setChecked(True)
  305. self.mixOpt_multiply = QRadioButton("Multiply")
  306. self.mixOpt_multiply.setToolTip("generated_combinatorials x current_variants (i.e combine by mixing)")
  307. mixOptGp = QButtonGroup()
  308. mixOptGp.addButton(self.mixOpt_append)
  309. mixOptGp.addButton(self.mixOpt_multiply)
  310. self.mixOpt_append.clicked.connect(self.refreshSelection)
  311. self.mixOpt_multiply.clicked.connect(self.refreshSelection)
  312. mixOptBoxHL.addWidget(self.mixOpt_append)
  313. mixOptBoxHL.addWidget(self.mixOpt_multiply)
  314. actionPaneLayout.addWidget(mixOptBox)
  315. genBox = QGroupBox("Estimations from current selection:")
  316. genBoxGL = QGridLayout()
  317. genBox.setLayout(genBoxGL)
  318. genBoxGL.addWidget(QLabel("Generation count: "), 0,0)
  319. self.directGenCount = QLineEdit()
  320. self.directGenCount.setReadOnly(True)
  321. genBoxGL.addWidget(self.directGenCount, 0,1)
  322. genBoxGL.addWidget(QLabel("Final variant count: "), 1,0)
  323. self.totalVariantsPostSend = QLineEdit()
  324. self.totalVariantsPostSend.setReadOnly(True)
  325. genBoxGL.addWidget(self.totalVariantsPostSend, 1,1)
  326. actionPaneLayout.addLayout(genBoxGL)
  327. self.coveredLbl = QLabel()
  328. genBoxGL.addWidget(self.coveredLbl, 2,0)
  329. self.makeCostLabel()
  330. actionPaneLayout.addWidget(genBox)
  331. genListBtn = QPushButton("Generate variant list", self)
  332. genListBtn.clicked.connect(self.generateVariants)
  333. genListBtn.setToolTip("Enumerate the variants*options matrix, and send to document")
  334. actionPaneLayout.addWidget(genListBtn)
  335. exitBtn = QPushButton("Exit", self)
  336. actionPaneLayout.addWidget(exitBtn)
  337. exitBtn.clicked.connect(self.close)
  338. actionPaneLayout.addStretch()
  339. phonywg = QWidget()
  340. phonywg.setLayout(actionPaneLayout)
  341. vsplitter.addWidget(phonywg)
  342. leftrightCut.addWidget(vsplitter)
  343. self.optionsSelector.changed.connect(self.refreshSelection)
  344. def changeSort(self):
  345. self.optionsSelector.order = self.sortChoices.currentIndex()
  346. self.optionsSelector.maintainOrder()
  347. def getParticipantOptionDescs(self):
  348. '''access option descriptors selected in the right bucket (in the form of a generator)'''
  349. return (optionsByNames[x.text()] for x in listItems(self.optionsSelector.right))
  350. def calculateCombinationCountInducedByCurrentParticipants(self):
  351. '''calculate the count of variants that will result from enumerating participant options'''
  352. participants = self.getParticipantOptionDescs()
  353. count = 0
  354. for p in participants:
  355. if count == 0:
  356. count = getValuesCount(p) # initial value
  357. else:
  358. count = count * getValuesCount(p)
  359. return count
  360. def calculateResultVariantCountAfterExpedite(self, calculatedGenCount):
  361. if self.mixOpt_append.isChecked():
  362. return numVariantsInDocument + calculatedGenCount
  363. elif self.mixOpt_multiply.isChecked():
  364. return numVariantsInDocument * calculatedGenCount
  365. def makeCostLabel(self):
  366. curSum = sumCost(self.getParticipantOptionDescs())
  367. total = totalCost()
  368. percent = 0 if total == 0 else curSum * 100 // total
  369. self.coveredLbl.setText(f"Cost covered by current selection: {curSum}/{total} ({percent}%)")
  370. def refreshSelection(self):
  371. '''triggered after a change in participants'''
  372. count = self.calculateCombinationCountInducedByCurrentParticipants()
  373. self.directGenCount.setText(str(count))
  374. self.makeCostLabel()
  375. self.totalVariantsPostSend.setText(str(self.calculateResultVariantCountAfterExpedite(count)))
  376. def autogenSelectionBucket(self):
  377. '''reset right bucket to an automatically suggested content'''
  378. self.optionsSelector.resetAllToLeft()
  379. startBucket = listItems(self.optionsSelector.left)
  380. expandCount = 1
  381. for itemWidget in startBucket:
  382. newCount = expandCount * getValuesCount(optionsByNames[itemWidget.text()])
  383. if newCount > self.target.value():
  384. break # stop here
  385. else:
  386. itemWidget.setSelected(True)
  387. expandCount = newCount
  388. self.optionsSelector.addClick()
  389. # requirement len(digitArray) == len(descriptorArray)
  390. # digits are integers (ShaderOptionValue). descriptors are RPI::ShaderOptionDescriptor
  391. @staticmethod
  392. def allMaxedOut(digitArray, descriptorArray):
  393. '''verify if an array of option-values, has all values corresponding to the described max-value, for their respective option'''
  394. zipped = zip(digitArray, descriptorArray)
  395. return all((digit == toInt(getMaxValue(desc)) for digit, desc in zipped))
  396. @staticmethod
  397. def increment(digit, descriptor):
  398. '''increment one digit in its own base-space. return pair or new digit and carry bit'''
  399. carry = False
  400. digit = getNextValueInt(descriptor, digit)
  401. if digit > toInt(getMaxValue(descriptor)):
  402. digit = toInt(getMinValue(descriptor))
  403. carry = True
  404. return (digit, carry)
  405. @staticmethod
  406. def incrementArray(digitArray, descriptorArray):
  407. '''+1 operation in the digitArray'''
  408. carry = True # we consider that the LSB is always to increment
  409. for i in reversed(range(0, len(digitArray))):
  410. if carry:
  411. digitArray[i], carry = Dialog.increment(digitArray[i], descriptorArray[i])
  412. @staticmethod
  413. def toNames(digitArray, descriptorArray):
  414. return [desc.GetValueName(createOptionValue(digit)) for digit, desc in zip(digitArray, descriptorArray)]
  415. def generateVariants(self):
  416. '''make a list of azlmbr.shader.ShaderVariantInfo that fully enumerate the combinatorics of the selected options space'''
  417. steps = self.calculateCombinationCountInducedByCurrentParticipants()
  418. progressDialog = QtWidgets.QProgressDialog("Enumerating", "Cancel", 0, steps)
  419. progressDialog.setMaximumWidth(400)
  420. progressDialog.setMaximumHeight(100)
  421. progressDialog.setModal(True)
  422. progressDialog.setWindowTitle("Generate variants")
  423. genOptDescs = list(self.getParticipantOptionDescs())
  424. self.participantNames = [x.GetName() for x in genOptDescs]
  425. self.listOfDigitArrays = []
  426. digits = [toInt(getMinValue(desc)) for desc in genOptDescs]
  427. c = 0
  428. if len(digits) > 0:
  429. while(True):
  430. self.listOfDigitArrays.extend(self.toNames(digits, genOptDescs)) # flat list
  431. if Dialog.allMaxedOut(digits, genOptDescs):
  432. break # we are done enumerating the "digit" space
  433. Dialog.incrementArray(digits, genOptDescs) # go to next, doing digit cascade
  434. progressDialog.setValue(c)
  435. c = c + 1
  436. if progressDialog.wasCanceled():
  437. self.listOfDigitArrays = []
  438. return
  439. progressDialog.close()
  440. def main():
  441. print("==== Begin shader variant expand option combinatorials script ====")
  442. if len(sys.argv) < 2:
  443. print(f"argv count is {len(sys.argv)}. The script requires a documentID as input argument.")
  444. return
  445. documentId = azlmbr.math.Uuid_CreateString(sys.argv[1])
  446. if not isDocumentOpen(documentId):
  447. print(f"document ID {documentId} not opened")
  448. return
  449. print(f"getting options for uuid {documentId}")
  450. # Get options
  451. optionCount = azlmbr.shadermanagementconsole.ShaderManagementConsoleDocumentRequestBus(
  452. azlmbr.bus.Event, 'GetShaderOptionDescriptorCount',
  453. documentId)
  454. if optionCount == 0:
  455. print("No options to work with on that document")
  456. return
  457. optionDescriptors = [None] * optionCount
  458. for i in range(0, optionCount):
  459. optionDescriptors[i] = azlmbr.shadermanagementconsole.ShaderManagementConsoleDocumentRequestBus(
  460. azlmbr.bus.Event, 'GetShaderOptionDescriptor',
  461. documentId, i)
  462. if optionDescriptors[i] is None:
  463. print(f"Error: Couldn't access option descriptor at {i}")
  464. return
  465. print(f"got list of {len(optionDescriptors)} options")
  466. global optionsByNames
  467. for optDesc in optionDescriptors:
  468. optionsByNames[optDesc.GetName().ToString()] = optDesc
  469. # Get current variant list to append our expansion after it
  470. variantList = azlmbr.shadermanagementconsole.ShaderManagementConsoleDocumentRequestBus(
  471. azlmbr.bus.Event,
  472. 'GetShaderVariantListSourceData',
  473. documentId
  474. )
  475. global numVariantsInDocument
  476. numVariantsInDocument = len(variantList.shaderVariants)
  477. dialog = Dialog(optionDescriptors)
  478. try:
  479. dialog.exec()
  480. except:
  481. print("exited early due to exception")
  482. return
  483. numVal = len(dialog.listOfDigitArrays)
  484. if numVal == 0:
  485. return
  486. mode = "append" if dialog.mixOpt_append.isChecked() else ("multiply" if dialog.mixOpt_multiply.isChecked() else "<error>")
  487. print(f"sending {numVal} values. ({numVal/len(dialog.participantNames)} new variants). in '{mode}' mode")
  488. beginEdit(documentId)
  489. # passing the result
  490. if dialog.mixOpt_append.isChecked(): # append mode
  491. azlmbr.shadermanagementconsole.ShaderManagementConsoleDocumentRequestBus(
  492. azlmbr.bus.Event,
  493. 'AppendSparseVariantSet',
  494. documentId,
  495. dialog.participantNames,
  496. dialog.listOfDigitArrays
  497. )
  498. elif dialog.mixOpt_multiply.isChecked(): # mix mode (enumerate the new variants fully with the old ones)
  499. azlmbr.shadermanagementconsole.ShaderManagementConsoleDocumentRequestBus(
  500. azlmbr.bus.Event,
  501. 'MultiplySparseVariantSet',
  502. documentId,
  503. dialog.participantNames,
  504. dialog.listOfDigitArrays
  505. )
  506. endEdit(documentId)
  507. #if __name__ == "__main__":
  508. main()
  509. print("==== End shader variant script ====")