main.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026
  1. # Copyright (c) Contributors to the Open 3D Engine Project.
  2. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR MIT
  5. #
  6. #
  7. """
  8. Usage
  9. =====
  10. Put usage instructions here.
  11. Output
  12. ======
  13. Put output information here.
  14. Notes:
  15. In order to run this, you'll need to verify that the "mayapy_path" class attribute corresponds to the location on
  16. your machine. Currently I've just included mapping instructions for Maya StingrayPBS materials, although most of
  17. the needed elements are in place to carry out additional materials inside of Maya pretty quickly moving forward.
  18. I've marked areas that still need refinement (or to be added altogether) with TODO comments
  19. TODO- Docstrings need work... wanted to get descriptions in but they need to be set for Sphinx
  20. TODO- Add 3ds Max interoperability
  21. Links:
  22. https://blender.stackexchange.com/questions/100497/use-blenders-bpy-in-projects-outside-blender
  23. https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2019/ENU/3DSMax-Batch/files/
  24. GUID-0968FF0A-5ADD-454D-B8F6-1983E76A4AF9-htm.html
  25. TODO- Look at dynaconf and wire in a solid means for configuration settings
  26. TODO- This hasn't been "designed"- might be worth it to consider the visual design to ensure the most effective and
  27. attractive UI
  28. TODO- Allow revisions to Model
  29. Reading FBX file information (might come in handy later)
  30. -- Materials information can be extracted from ASCII fbx pretty easily, binary is possible but more difficult
  31. -- FBX files could be exported as ASCII files and I could use regex there to extract material information
  32. -- I couldn't get pyfbx_i42 to work, but purportedly it can extract information from binary files. You may just have
  33. to use the specified python versions
  34. """
  35. # built-ins
  36. import collections
  37. import logging
  38. import subprocess
  39. import json
  40. import sys
  41. import os
  42. import re
  43. # should give access to Lumberyard Qt dlls and PySide2
  44. from PySide2 import QtWidgets, QtCore, QtGui
  45. from PySide2.QtCore import Slot
  46. from PySide2.QtWidgets import QApplication
  47. import shiboken2
  48. from shiboken2 import wrapInstance
  49. # local imports
  50. from model import MaterialsModel
  51. from drag_and_drop import DragAndDrop
  52. import dcc_material_mapping as dcc_map
  53. # global space
  54. main_window_pointer = None
  55. main_app_window = None
  56. class MaterialsToLumberyard(QtWidgets.QWidget):
  57. def __init__(self, output_material_type='PBR', cli_values=None, parent=None):
  58. super(MaterialsToLumberyard, self).__init__(parent)
  59. self.app = QtWidgets.QApplication.instance()
  60. self.setWindowFlags(QtCore.Qt.Window)
  61. self.setGeometry(50, 50, 800, 520)
  62. self.setObjectName('MaterialsToLumberyard')
  63. self.setWindowTitle(' ')
  64. self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint)
  65. self.isTopLevel()
  66. self.cli_enabled = cli_values
  67. self.output_material_type = output_material_type
  68. self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
  69. self.directory_path = os.path.dirname(os.path.abspath(__file__))
  70. self.mayapy_path = os.path.abspath("C:/Program Files/Autodesk/Maya2020/bin/mayapy.exe")
  71. self.blender_path = self.get_blender_path()
  72. self.bold_font_large = QtGui.QFont('Helvetica', 7, QtGui.QFont.Bold)
  73. self.medium_font = QtGui.QFont('Helvetica', 7, QtGui.QFont.Normal)
  74. self.blessed_file_extensions = 'ma mb fbx max blend'.split(' ')
  75. self.dcc_materials_dictionary = {}
  76. self.lumberyard_materials_dictionary = {}
  77. self.lumberyard_material_nodes = []
  78. self.target_file_list = []
  79. self.current_scene = None
  80. self.model = None
  81. self.total_materials = 0
  82. self.main_container = QtWidgets.QVBoxLayout(self)
  83. self.main_container.setContentsMargins(0, 0, 0, 0)
  84. self.main_container.setAlignment(QtCore.Qt.AlignTop)
  85. self.setLayout(self.main_container)
  86. self.content_layout = QtWidgets.QVBoxLayout()
  87. self.content_layout.setAlignment(QtCore.Qt.AlignTop)
  88. self.content_layout.setContentsMargins(10, 3, 10, 5)
  89. self.main_container.addLayout(self.content_layout)
  90. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  91. # ---->> Header Bar
  92. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  93. self.header_bar_layout = QtWidgets.QHBoxLayout()
  94. self.lumberyard_logo_layout = QtWidgets.QHBoxLayout()
  95. self.lumberyard_logo_layout.setAlignment(QtCore.Qt.AlignLeft)
  96. logo_path = os.path.join(self.directory_path, 'resources', 'lumberyard_logo.png')
  97. logo_pixmap = QtGui.QPixmap(logo_path)
  98. self.lumberyard_logo = QtWidgets.QLabel()
  99. self.lumberyard_logo.setPixmap(logo_pixmap)
  100. self.lumberyard_logo_layout.addWidget(self.lumberyard_logo)
  101. self.header_bar_layout.addLayout(self.lumberyard_logo_layout)
  102. self.switch_combobox_layout = QtWidgets.QHBoxLayout()
  103. self.switch_combobox_layout.setAlignment(QtCore.Qt.AlignRight)
  104. self.switch_layout_combobox = QtWidgets.QComboBox()
  105. self.set_combobox_items_accessibility()
  106. self.switch_layout_combobox.setFixedSize(250, 30)
  107. self.combobox_items = ['Add Source Files', 'Source File List', 'DCC Material Values', 'Export Materials']
  108. self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}')
  109. self.switch_layout_combobox.addItems(self.combobox_items)
  110. self.switch_combobox_layout.addWidget(self.switch_layout_combobox)
  111. self.header_bar_layout.addLayout(self.switch_combobox_layout)
  112. self.content_layout.addSpacing(5)
  113. self.content_layout.addLayout(self.header_bar_layout)
  114. # ++++++++++++++++++++++++++++++++++++++++++++++++#
  115. # File Source Table / Attributes (Stacked Layout) #
  116. # ++++++++++++++++++++++++++++++++++++++++++++++++#
  117. self.content_stacked_layout = QtWidgets.QStackedLayout()
  118. self.content_layout.addLayout(self.content_stacked_layout)
  119. self.switch_layout_combobox.currentIndexChanged.connect(self.layout_combobox_changed)
  120. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  121. # ---->> Add Source Files
  122. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  123. frame_color_value = '75,75,75'
  124. highlight_color_value = '20,106,30'
  125. self.drag_and_drop_widget = DragAndDrop(frame_color_value, highlight_color_value)
  126. self.drag_and_drop_widget.drop_update.connect(self.drag_and_drop_file_update)
  127. self.drag_and_drop_widget.drop_over.connect(self.drag_and_drop_over)
  128. self.drag_and_drop_layout = QtWidgets.QVBoxLayout()
  129. self.drag_and_drop_layout.setContentsMargins(0, 0, 0, 0)
  130. self.drag_and_drop_layout.setAlignment(QtCore.Qt.AlignCenter)
  131. self.drag_and_drop_widget.setLayout(self.drag_and_drop_layout)
  132. start_message = 'Drag source files here, or use file browser button below to get started.'
  133. self.drag_and_drop_label = QtWidgets.QLabel(start_message)
  134. self.drag_and_drop_label.setStyleSheet('color: white;')
  135. self.drag_and_drop_layout.addWidget(self.drag_and_drop_label)
  136. self.drag_and_drop_layout.addSpacing(10)
  137. self.select_files_button_layout = QtWidgets.QHBoxLayout()
  138. self.select_files_button_layout.setAlignment(QtCore.Qt.AlignCenter)
  139. self.select_files_button = QtWidgets.QPushButton('Select Files')
  140. self.select_files_button_layout.addWidget(self.select_files_button)
  141. self.select_files_button.clicked.connect(self.select_files_button_clicked)
  142. self.select_files_button.setFixedSize(80, 35)
  143. self.drag_and_drop_layout.addLayout(self.select_files_button_layout)
  144. self.content_stacked_layout.addWidget(self.drag_and_drop_widget)
  145. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  146. # ---->> Files Table
  147. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  148. self.target_files_table = QtWidgets.QTableWidget()
  149. self.target_files_table.setFocusPolicy(QtCore.Qt.NoFocus)
  150. self.target_files_table.setColumnCount(2)
  151. self.target_files_table.setAlternatingRowColors(True)
  152. self.target_files_table.setHorizontalHeaderLabels(['File List', ''])
  153. self.target_files_table.horizontalHeader().setStyleSheet('QHeaderView::section '
  154. '{background-color: rgb(220, 220, 220); '
  155. 'padding-top:7px; padding-left:5px;}')
  156. self.target_files_table.verticalHeader().hide()
  157. files_header = self.target_files_table.horizontalHeader()
  158. files_header.setFixedHeight(30)
  159. files_header.setDefaultAlignment(QtCore.Qt.AlignLeft)
  160. files_header.setContentsMargins(10, 10, 0, 0)
  161. files_header.setDefaultSectionSize(60)
  162. files_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
  163. files_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
  164. self.target_files_table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
  165. self.content_stacked_layout.addWidget(self.target_files_table)
  166. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  167. # ---->> Scene Information Table
  168. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  169. self.material_tree_view = QtWidgets.QTreeView()
  170. self.headers = ['Key', 'Value']
  171. self.material_tree_view.setStyleSheet('QTreeView::item {height:25px;} QHeaderView::section '
  172. '{background-color: rgb(220, 220, 220); height:30px; padding-left:10px}')
  173. self.material_tree_view.setFocusPolicy(QtCore.Qt.NoFocus)
  174. self.material_tree_view.setAlternatingRowColors(True)
  175. self.material_tree_view.setUniformRowHeights(True)
  176. self.content_stacked_layout.addWidget(self.material_tree_view)
  177. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  178. # ---->> LY Material Definitions
  179. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  180. self.lumberyard_material_definitions_widget = QtWidgets.QWidget()
  181. self.lumberyard_material_definitions_layout = QtWidgets.QHBoxLayout(self.lumberyard_material_definitions_widget)
  182. self.lumberyard_material_definitions_layout.setSpacing(0)
  183. self.lumberyard_material_definitions_layout.setContentsMargins(0, 0, 0, 0)
  184. self.lumberyard_material_definitions_frame = QtWidgets.QFrame(self.lumberyard_material_definitions_widget)
  185. self.lumberyard_material_definitions_frame.setGeometry(0, 0, 5000, 5000)
  186. self.lumberyard_material_definitions_frame.setStyleSheet('background-color:rgb(75,75,75);')
  187. self.lumberyard_material_definitions_scroller = QtWidgets.QScrollArea()
  188. self.scroller_widget = QtWidgets.QWidget()
  189. self.scroller_layout = QtWidgets.QVBoxLayout()
  190. self.scroller_widget.setLayout(self.scroller_layout)
  191. self.lumberyard_material_definitions_scroller.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
  192. self.lumberyard_material_definitions_scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  193. self.lumberyard_material_definitions_scroller.setWidgetResizable(True)
  194. self.lumberyard_material_definitions_scroller.setWidget(self.scroller_widget)
  195. self.lumberyard_material_definitions_layout.addWidget(self.lumberyard_material_definitions_scroller)
  196. self.content_stacked_layout.addWidget(self.lumberyard_material_definitions_widget)
  197. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  198. # ---->> File processing buttons
  199. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  200. self.process_files_layout = QtWidgets.QHBoxLayout()
  201. self.content_layout.addLayout(self.process_files_layout)
  202. self.process_files_button = QtWidgets.QPushButton('Process Added Files')
  203. self.process_files_button.setFixedHeight(50)
  204. self.process_files_button.clicked.connect(self.process_listed_files_clicked)
  205. self.process_files_layout.addWidget(self.process_files_button)
  206. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  207. # ---->> Status bar / Loader
  208. # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  209. # TODO- Move all processing of files to another thread and display progress with loader
  210. self.status_bar = QtWidgets.QStatusBar()
  211. self.status_bar.setStyleSheet('background-color: rgb(220, 220, 220);')
  212. self.status_bar.setContentsMargins(0, 0, 0, 0)
  213. self.status_bar.setSizeGripEnabled(False)
  214. self.message_readout_label = QtWidgets.QLabel('Ready.')
  215. self.message_readout_label.setStyleSheet('padding-left: 10px')
  216. self.status_bar.addWidget(self.message_readout_label)
  217. self.progress_bar = QtWidgets.QProgressBar()
  218. self.progress_bar_widget = QtWidgets.QWidget()
  219. self.progress_bar_widget_layout = QtWidgets.QHBoxLayout()
  220. self.progress_bar_widget_layout.setContentsMargins(0, 0, 0, 0)
  221. self.progress_bar_widget_layout.setAlignment(QtCore.Qt.AlignRight)
  222. self.progress_bar_widget.setLayout(self.progress_bar_widget_layout)
  223. self.status_bar.addPermanentWidget(self.progress_bar_widget)
  224. self.progress_bar_widget_layout.addWidget(self.progress_bar)
  225. self.progress_bar.setFixedSize(180, 20)
  226. self.main_container.addWidget(self.status_bar)
  227. self.initialize()
  228. ############################
  229. # UI Display Layers ########
  230. ############################
  231. def initialize(self):
  232. if self.cli_enabled:
  233. print('CLI ACCESS:::::::::::\nValues passed: {}'.format(self.cli_enabled))
  234. self.target_file_list = self.cli_enabled
  235. self.process_file_list()
  236. self.export_selected_materials()
  237. def populate_source_files_table(self):
  238. """
  239. Adds selected files from the 'Source Files' section of the UI. This creates each item listing in the table
  240. as well as adds a 'Remove' button that will clear corresponding item from the table. Processed files will
  241. get color coded, based on whether or not the materials in the file could be successfully processed. Subsequent
  242. searches will not clear items from the table currently, as each item acts as a register of materials that have
  243. and have not yet been processed.
  244. :return:
  245. """
  246. self.target_files_table.setRowCount(0)
  247. for index, entry in enumerate(self.target_file_list):
  248. entry = entry[1] if type(entry) == list else entry
  249. self.target_files_table.insertRow(index)
  250. item = QtWidgets.QTableWidgetItem(' {}'.format(entry))
  251. self.target_files_table.setRowHeight(index, 45)
  252. remove_button = QtWidgets.QPushButton('Remove')
  253. remove_button.setFixedWidth(60)
  254. remove_button.clicked.connect(self.remove_source_file_clicked)
  255. self.target_files_table.setItem(index, 0, item)
  256. self.target_files_table.setCellWidget(index, 1, remove_button)
  257. def populate_dcc_material_values_tree(self):
  258. """
  259. Sets the materials model class to the file attribute tree.
  260. :return:
  261. """
  262. # TODO- Create mechanism for collapsing previously gathered materials, and or pushing them further down the list
  263. self.material_tree_view.setModel(self.model)
  264. self.material_tree_view.expandAll()
  265. self.material_tree_view.resizeColumnToContents(0)
  266. def populate_export_materials_list(self):
  267. """
  268. Once all materials have been analyzed inside of DCC applications, the 'Export Materials' view lists all
  269. materials presented as their Lumberyard counterparts. Each listing displays a representation of the material
  270. file based on its corresponding DCC material values and file connections.
  271. :return:
  272. """
  273. self.reset_export_materials_description()
  274. for count, value in enumerate(self.lumberyard_materials_dictionary):
  275. material_definition_node = MaterialNode([value, self.lumberyard_materials_dictionary[value]], count)
  276. self.lumberyard_material_nodes.append(material_definition_node)
  277. self.scroller_layout.addWidget(material_definition_node)
  278. self.scroller_layout.addLayout(self.create_separator_line())
  279. ############################
  280. # TBD ########
  281. ############################
  282. def process_file_list(self):
  283. """
  284. The entry point for reading DCC files and extracting values. Files are filtered and separated
  285. by DCC app (based on file extensions) before processing is done.
  286. Supported DCC applications:
  287. Maya (.ma, .mb, .fbx), 3dsMax(.max), Blender(.blend)
  288. :return:
  289. """
  290. files_dict = {'maya': [], 'max': [], 'blender': [], 'na': []}
  291. for file_location in self.target_file_list:
  292. file_name = os.path.basename(str(file_location))
  293. file_extension = os.path.splitext(file_name)[1]
  294. target_application = self.get_target_application(file_extension)
  295. if target_application in files_dict.keys():
  296. files_dict[target_application].append(file_location)
  297. for key, values in files_dict.items():
  298. try:
  299. if key == 'maya' and len(values):
  300. self.get_maya_material_values(values)
  301. elif key == 'max' and len(values):
  302. self.get_max_material_values(values)
  303. elif key == 'blender' and len(values):
  304. self.get_blender_material_values(values)
  305. else:
  306. pass
  307. except Exception as e:
  308. # TODO- Allow corrective actions or some display of errors if this fails?
  309. logging.warning('Could not process files. Error: {}'.format(e))
  310. if self.dcc_materials_dictionary:
  311. self.set_transfer_status(self.dcc_materials_dictionary)
  312. # Create Model with extracted values from file list
  313. self.set_material_model()
  314. # Setup Lumberyard Material File Values
  315. self.set_export_materials_description()
  316. # Update UI Layout
  317. self.populate_export_materials_list()
  318. self.switch_layout_combobox.setCurrentIndex(3)
  319. self.set_ui_buttons()
  320. self.message_readout_label.setText('Ready.')
  321. def reset_export_materials_description(self):
  322. pass
  323. def reset_all_values(self):
  324. pass
  325. def create_separator_line(self):
  326. """ Convenience function for adding separation line to the UI. """
  327. layout = QtWidgets.QHBoxLayout()
  328. line = QtWidgets.QLabel()
  329. line.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
  330. line.setLineWidth(1)
  331. line.setFixedHeight(10)
  332. layout.addWidget(line)
  333. layout.setContentsMargins(8, 0, 8, 0)
  334. return layout
  335. def export_selected_materials(self):
  336. """
  337. This will eventually be revised to save material definitions in the proper place in the user's project folder,
  338. but for now material definitions will be saved to the desktop.
  339. :return:
  340. """
  341. for node in self.lumberyard_material_nodes:
  342. if node.material_name_checkbox.isChecked():
  343. output_path = os.path.dirname(node.material_info['sourceFile'])
  344. node.material_info.pop('sourceFile')
  345. output = os.path.join(output_path, '{}.material'.format(node.material_name))
  346. with open(output, 'w', encoding='utf-8') as material_file:
  347. json.dump(node.material_info, material_file, ensure_ascii=False, indent=4)
  348. ############################
  349. # Getters/Setters ##########
  350. ############################
  351. @staticmethod
  352. def get_target_application(file_extension):
  353. """
  354. Searches compatible file extensions and returns one of three Application names- Maya, 3dsMax, or Blender.
  355. :param file_extension: Passed file extension used to determine DCC Application it originated from.
  356. :return: Returns the application corresponding to the extension if found- otherwise returns a Boolean None
  357. """
  358. app_extensions = {'maya': ['.ma', '.mb', '.fbx'], 'max': ['.max'], 'blender': ['.blend']}
  359. target_dcc_application = [key for key, values in app_extensions.items() if file_extension in values]
  360. if target_dcc_application:
  361. return target_dcc_application[0]
  362. return None
  363. @staticmethod
  364. def get_lumberyard_material_template(shader_type):
  365. """
  366. Loads material descriptions from the Lumberyard installation, providing a template to compare and convert DCC
  367. shaders to Lumberyard material definitions. This is the first step in the comparison. The second step is to
  368. compare these values with specific mapping instructions for DCC Application and DCC material type to arrive at
  369. a converted material.
  370. :param shader_type: The type of Lumberyard shader to pair material attributes to (i.e. PBR Shader)
  371. :return: File dictionary of the available boilerplate Lumberyard shader settings.
  372. """
  373. definitions = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources',
  374. '{}.template.material'.format(shader_type))
  375. if os.path.exists(definitions):
  376. with open(definitions) as f:
  377. return json.load(f)
  378. @staticmethod
  379. def get_lumberyard_material_properties(name, dcc_app, material_type, file_connections):
  380. """
  381. This system will probably need rethinking if DCCs and compatible materials grow. I've tried to keep this
  382. flexible so that it can be expanded with more apps and materials.
  383. :param name: Material name from within the DCC application
  384. :param dcc_app: The application that the material was sourced from
  385. :param material_type: DCC material type
  386. :param file_connections: Texture files found attached to the materials
  387. """
  388. material_properties = {}
  389. if dcc_app == 'Maya':
  390. material_properties = dcc_map.get_maya_material_mapping(name, material_type, file_connections)
  391. elif dcc_app == 'Blender':
  392. material_properties = dcc_map.get_blender_material_mapping(name, material_type, file_connections)
  393. elif dcc_app == '3dsMax':
  394. material_properties = dcc_map.get_max_material_mapping(name, material_type, file_connections)
  395. else:
  396. pass
  397. return material_properties
  398. @staticmethod
  399. def get_filename_increment(name):
  400. """
  401. Convenience function that assists in ensuring that if any materials are encountered with the same name, an
  402. underscore and number is appended to it to prevent overwrites.
  403. :param name: The name of the material. The function searches the string for increment numbers, and either adds
  404. one to any encountered, or adds an "_1" if passed name is the first duplicate encountered.
  405. :return: The adjusted name with a unique incremental value.
  406. """
  407. last_number = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
  408. number_found = last_number.search(name)
  409. if number_found:
  410. next_number = str(int(number_found.group(1)) + 1)
  411. start, end = number_found.span(1)
  412. name = name[:max(end - len(next_number), start)] + next_number + name[end:]
  413. return name
  414. def get_maya_material_values(self, target_files):
  415. """
  416. Launches Maya Standalone and processes list of materials for each scene passed to the 'target_files' argument.
  417. Also sets the environment paths needed for an instance of Maya's Python distribution. After files are processed
  418. a single dictionary of scene materials is returned, and added to the "materials_dictionary" scene attribute.
  419. :param target_files: List of files filtered from total list of files requested for processing that have a
  420. Maya file extension
  421. :return:
  422. """
  423. # TODO- Set load process to a separate thread and wire load progress bar up
  424. try:
  425. script_path = str(os.path.join(self.directory_path, 'maya_materials.py'))
  426. target_files.append(self.total_materials)
  427. runtime_env = os.environ.copy()
  428. runtime_env['MAYA_LOCATION'] = os.path.dirname(self.mayapy_path)
  429. runtime_env['PYTHONPATH'] = os.path.dirname(self.mayapy_path)
  430. command = f'{self.mayapy_path} "{script_path}"'
  431. for file in target_files:
  432. command += f' "{file}"'
  433. p = subprocess.Popen(command, shell=False, env=runtime_env, stdout=subprocess.PIPE)
  434. output = p.communicate()[0]
  435. self.set_material_dictionary(json.loads(output))
  436. except Exception as e:
  437. logging.warning('maya error: {}'.format(e))
  438. def get_max_material_values(self, target_files):
  439. """
  440. This has not been implemented yet.
  441. :param target_files: List of files filtered from total list of files requested for processing that have a
  442. .max file extension
  443. :return:
  444. """
  445. logging.debug('Max Target file: {}'.format(target_files))
  446. def get_blender_material_values(self, target_files):
  447. """
  448. This has not been implemented yet.
  449. :param target_files: List of files filtered from total list of files requested for processing that have a
  450. .blend file extension
  451. :return:
  452. """
  453. logging.debug('Blender Target file: {}'.format(target_files))
  454. script_path = str(os.path.join(self.directory_path, 'blender_materials.py'))
  455. target_files.append(self.total_materials)
  456. p = subprocess.Popen([self.blender_path, '--background', '--python', script_path, '--', target_files])
  457. output = p.communicate()[0]
  458. self.set_material_dictionary(json.loads(output))
  459. def get_blender_path(self):
  460. """
  461. Finds latest Blender version installed on the user machine for command line file processing.
  462. :return: Most current version available (or none)
  463. """
  464. blender_base_directory = os.path.join(os.path.join('C:\\', 'Program Files', 'Blender Foundation'))
  465. blender_versions_found = []
  466. for (directory_path, directory_name, filenames) in os.walk(blender_base_directory):
  467. for filename in filenames:
  468. if filename == 'blender.exe':
  469. blender_versions_found.append(os.path.join(directory_path, filename))
  470. if blender_versions_found:
  471. return max(blender_versions_found, key=os.path.getctime)
  472. else:
  473. return None
  474. def set_combobox_items_accessibility(self):
  475. """
  476. Locks items from within the combobox until the sections they connect to have content
  477. :return:
  478. """
  479. # TODO- Add this functionality
  480. pass
  481. def set_transfer_status(self, transfer_info):
  482. """
  483. Colorizes listings in the 'Source Files' view of the UI after processing to green or red, indicating whether or
  484. not scene analysis successfully returned compatible materials and their values.
  485. :param transfer_info: Each file the scripts attempt to process return a receipt of the success or failure of
  486. the analysis.
  487. :return:
  488. """
  489. # TODO- Include some way to get error information if analysis fails, and potentially offer the means to
  490. # repair values as they map to intended Lumberyard shader type
  491. for row in range(self.target_files_table.rowCount()):
  492. for key, values in transfer_info.items():
  493. row_path = self.target_files_table.item(row, 0).text().strip()
  494. scene_processed = {x for x in transfer_info if values['SceneName'].replace('\\', '/') == row_path}
  495. if scene_processed:
  496. self.target_files_table.item(row, 0).setBackground(QtGui.QColor(192, 255, 171))
  497. break
  498. else:
  499. self.target_files_table.item(row, 0).setBackground(QtGui.QColor(255, 177, 171))
  500. def set_export_materials_description(self):
  501. root = self.model.rootItem
  502. for row in range(self.model.rowCount()):
  503. source_file = self.model.get_attribute_value('SceneName', root.child(row))
  504. name = self.model.get_attribute_value('MaterialName', root.child(row))
  505. material_type = self.model.get_attribute_value('MaterialType', root.child(row))
  506. dcc_app = self.model.get_attribute_value('DccApplication', root.child(row))
  507. file_connections = {}
  508. shader_attributes = {}
  509. for childIndex in range(root.child(row).childCount()):
  510. child_item = root.child(row).child(childIndex)
  511. child_value = child_item.itemData
  512. if child_item.childCount():
  513. target_dict = file_connections if child_value[0] == 'FileConnections' else shader_attributes
  514. for subChildIndex in range(child_item.childCount()):
  515. sub_child_data = child_item.child(subChildIndex).itemData
  516. target_dict[sub_child_data[0]] = sub_child_data[1]
  517. self.set_material_description(source_file, name, dcc_app, material_type, file_connections)
  518. def set_material_dictionary(self, dcc_dictionary):
  519. """
  520. Adds all material descriptions pulled from each DCC file analyzed to the "materials_dictionary" class attribute.
  521. This function runs each time a subprocess is launched to gather DCC application material values.
  522. :param dcc_dictionary: The dictionary of values for each material analyzed by each specific DCC file list
  523. return analyzed values
  524. :return:
  525. """
  526. logging.debug('DCC Dictionary: {}'.format(json.dumps(dcc_dictionary, indent=4)))
  527. self.total_materials += len(dcc_dictionary)
  528. self.dcc_materials_dictionary.update(dcc_dictionary)
  529. def set_material_model(self, initialize=True):
  530. """
  531. Once all materials have been gathered across a selected file set query, this organizes the values into a
  532. QT Model Class
  533. :param initialize: Default is set to boolean True. If a model has already been established in the current
  534. session, the initialize parameter would be set to false, and the values added to the Model. All changes to
  535. the model would then be redistributed to other informational views in the UI.
  536. :return:
  537. """
  538. if initialize:
  539. self.model = MaterialsModel(self.headers, self.dcc_materials_dictionary)
  540. else:
  541. self.model.update()
  542. self.dcc_materials_dictionary.clear()
  543. self.populate_dcc_material_values_tree()
  544. def set_ui_buttons(self):
  545. """
  546. Handles UI buttons for each of the three stacked layout views (Source Files, DCC Material Values,
  547. Export Materials)
  548. :return:
  549. """
  550. display_index = self.content_stacked_layout.currentIndex()
  551. self.switch_layout_combobox.setEnabled(True)
  552. self.process_files_button.setText('Process Listed Files')
  553. # Add Source Files Layout ------------------------------->>
  554. if display_index == 0:
  555. self.process_files_button.setEnabled(True)
  556. # Source File List -------------------------------------->>
  557. elif display_index == 1:
  558. self.process_files_button.setEnabled(True)
  559. # DCC Material Values Layout ---------------------------->>
  560. elif display_index == 2:
  561. self.process_files_button.setEnabled(False)
  562. # Export Materials Layout ------------------------------->>
  563. else:
  564. self.process_files_button.setText('Export Selected Materials')
  565. if self.lumberyard_materials_dictionary:
  566. self.process_files_button.setEnabled(True)
  567. def set_material_description(self, source_file, name, dcc_app, material_type, file_connections):
  568. """
  569. Build dictionary for material description based on extracted values
  570. :param source_file: The file that the material was extracted from
  571. :param name: Name of material
  572. :param dcc_app: Source file type of material (Maya, Blender or 3ds Max)
  573. :param material_type: Material type within app (i.e. Stingray PBS)
  574. :param file_connections: Texture files found connected to the shader
  575. :return:
  576. """
  577. default_settings = self.get_lumberyard_material_template('standardPBR')
  578. material = collections.OrderedDict(sourceFile=source_file, description=name,
  579. materialType=default_settings.get('materialType'),
  580. parentMaterial=default_settings.get('parentMaterial'),
  581. propertyLayoutVersion=default_settings.get('propertyLayoutVersion'),
  582. properties=self.get_lumberyard_material_properties(name, dcc_app,
  583. material_type,
  584. file_connections))
  585. name += self.output_material_type
  586. self.lumberyard_materials_dictionary[name if name not in self.lumberyard_materials_dictionary.keys() else
  587. self.get_filename_increment(name)] = material
  588. ############################
  589. # Button Actions ###########
  590. ############################
  591. def remove_source_file_clicked(self):
  592. """
  593. In the Source File view of the UI layout, this will remove the listed file in its respective row. If files
  594. have not been processed yet, it prevents that file from being analyzed. If the files have already been
  595. analyzed, this will remove the materials from stored values.
  596. :return:
  597. """
  598. file_index = self.target_files_table.indexAt(self.sender().pos())
  599. del self.target_file_list[file_index.row()]
  600. self.populate_files_table()
  601. def process_listed_files_clicked(self):
  602. """
  603. The button serves a dual purpose, depending on the current layout of the window. 'Process listed files'
  604. initiates the DCC file analysis that extracts material information. In the "Export Materials" layout, this
  605. button (for now) will export material files corresponding to each analyzed material. Exported material files
  606. are routed to the directories of the respective files processed.
  607. :return:
  608. """
  609. if self.sender().text() == 'Process Added Files':
  610. self.message_readout_label.setText('Gathering Material Information...')
  611. self.app.processEvents()
  612. self.process_file_list()
  613. else:
  614. self.export_selected_materials()
  615. def select_files_button_clicked(self):
  616. """
  617. This dialog allows user to select DCC files to be processed for the materials present for conversion.
  618. :return:
  619. """
  620. # TODO- Eventually it might be worth it to allow files from multiple locations to be selected. Currently
  621. # this only allows single/multiple files from a single directory to be selected, although drag and drop
  622. # allows multiple locations
  623. dialog = QtWidgets.QFileDialog(self, 'Shift-Select Target Files', self.desktop_location)
  624. dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
  625. dialog.setNameFilter('Compatible Files (*.ma *.mb *.fbx *.max *.blend)')
  626. dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
  627. file_view = dialog.findChild(QtWidgets.QListView, 'listView')
  628. # Workaround for selecting multiple files with File Dialog
  629. if file_view:
  630. file_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
  631. f_tree_view = dialog.findChild(QtWidgets.QTreeView)
  632. if f_tree_view:
  633. f_tree_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
  634. if dialog.exec_() == QtWidgets.QDialog.Accepted:
  635. self.target_file_list += dialog.selectedFiles()
  636. if self.target_file_list:
  637. self.populate_source_files_table()
  638. self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list)))
  639. self.process_files_button.setEnabled(True)
  640. def layout_combobox_changed(self):
  641. """
  642. Handles main window layout combobox index change.
  643. :return:
  644. """
  645. self.content_stacked_layout.setCurrentIndex(self.switch_layout_combobox.currentIndex())
  646. self.set_ui_buttons()
  647. def reset_clicked(self):
  648. """
  649. Brings the application and all variables back to their initial state.
  650. :return:
  651. """
  652. self.reset_all_values()
  653. ############################
  654. # Slots ####################
  655. ############################
  656. @Slot(list)
  657. def drag_and_drop_file_update(self, file_list):
  658. for file in file_list:
  659. if os.path.basename(file).split('.')[-1] in self.blessed_file_extensions:
  660. self.target_file_list.append(file)
  661. self.drag_and_drop_widget.urls.clear()
  662. self.populate_source_files_table()
  663. self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list)))
  664. self.drag_and_drop_label.setStyleSheet('color: white;')
  665. @Slot(bool)
  666. def drag_and_drop_over(self, is_over):
  667. if is_over:
  668. self.drag_and_drop_label.setStyleSheet('color: rgb(0, 255, 0);')
  669. else:
  670. self.drag_and_drop_label.setStyleSheet('color: white;')
  671. class MaterialNode(QtWidgets.QWidget):
  672. def __init__(self, material_info, current_position, parent=None):
  673. super(MaterialNode, self).__init__(parent)
  674. self.material_name = material_info[0]
  675. self.material_info = material_info[1]
  676. self.current_position = current_position
  677. self.property_settings = {}
  678. self.small_font = QtGui.QFont("Helvetica", 7, QtGui.QFont.Bold)
  679. self.bold_font = QtGui.QFont("Helvetica", 8, QtGui.QFont.Bold)
  680. self.main_layout = QtWidgets.QVBoxLayout()
  681. self.main_layout.setContentsMargins(0, 0, 0, 0)
  682. self.setLayout(self.main_layout)
  683. self.background_frame = QtWidgets.QFrame(self)
  684. self.background_frame.setGeometry(0, 0, 5000, 5000)
  685. self.background_frame.setStyleSheet('background-color:rgb(220, 220, 220);')
  686. # ########################
  687. # Title Bar
  688. # ########################
  689. self.title_bar_widget = QtWidgets.QWidget()
  690. self.title_bar_layout = QtWidgets.QHBoxLayout(self.title_bar_widget)
  691. self.title_bar_layout.setContentsMargins(10, 0, 10, 0)
  692. self.title_bar_layout.setAlignment(QtCore.Qt.AlignTop)
  693. self.title_bar_frame = QtWidgets.QFrame(self.title_bar_widget)
  694. self.title_bar_frame.setGeometry(0, 0, 5000, 40)
  695. self.title_bar_frame.setStyleSheet('background-color:rgb(193,154,255);')
  696. self.main_layout.addWidget(self.title_bar_widget)
  697. self.material_name_checkbox = QtWidgets.QCheckBox(self.material_name)
  698. self.material_name_checkbox.setFixedHeight(35)
  699. self.material_name_checkbox.setStyleSheet('spacing:10px; color:white')
  700. self.material_name_checkbox.setFont(self.bold_font)
  701. self.material_name_checkbox.setChecked(True)
  702. self.title_bar_layout.addWidget(self.material_name_checkbox)
  703. self.material_file_layout = QtWidgets.QHBoxLayout()
  704. self.material_file_layout.setAlignment(QtCore.Qt.AlignRight)
  705. self.source_file = QtWidgets.QLabel(os.path.basename(self.material_info['sourceFile']))
  706. self.source_file.setStyleSheet('color:white;')
  707. self.source_file.setFont(self.small_font)
  708. self.material_file_layout.addWidget(self.source_file)
  709. self.material_file_layout.addSpacing(10)
  710. self.edit_button = QtWidgets.QPushButton('Edit')
  711. self.edit_button.clicked.connect(self.edit_button_clicked)
  712. self.edit_button.setFixedWidth(55)
  713. self.material_file_layout.addWidget(self.edit_button)
  714. self.title_bar_layout.addLayout(self.material_file_layout)
  715. self.information_layout = QtWidgets.QHBoxLayout()
  716. self.information_layout.setContentsMargins(10, 0, 10, 10)
  717. self.main_layout.addLayout(self.information_layout)
  718. # ########################
  719. # Details layout
  720. # ########################
  721. self.details_layout = QtWidgets.QVBoxLayout()
  722. self.details_layout.setAlignment(QtCore.Qt.AlignTop)
  723. self.details_groupbox = QtWidgets.QGroupBox("Details")
  724. self.details_groupbox.setFixedWidth(200)
  725. self.details_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
  726. "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
  727. "subcontrol-position: top left;}")
  728. self.details_layout.addSpacing(15)
  729. self.material_type_label = QtWidgets.QLabel('Material Type')
  730. self.material_type_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);')
  731. self.material_type_label.setFixedHeight(25)
  732. self.material_type_label.setFont(self.bold_font)
  733. self.details_layout.addWidget(self.material_type_label)
  734. self.material_type_combobox = QtWidgets.QComboBox()
  735. self.material_type_combobox.setFixedHeight(30)
  736. self.material_type_combobox.setStyleSheet('QCombobox QAbstractItemView { padding-left: 15px; }')
  737. material_type_items = [' Standard PBR']
  738. self.material_type_combobox.addItems(material_type_items)
  739. self.details_layout.addWidget(self.material_type_combobox)
  740. self.details_layout.addSpacing(10)
  741. self.description_label = QtWidgets.QLabel('Description')
  742. self.description_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);')
  743. self.description_label.setFixedHeight(25)
  744. self.description_label.setFont(self.bold_font)
  745. self.details_layout.addWidget(self.description_label)
  746. self.description_box = QtWidgets.QTextEdit('This space is reserved for additional information.')
  747. self.details_layout.addWidget(self.description_box)
  748. self.information_layout.addWidget(self.details_groupbox)
  749. self.details_groupbox.setLayout(self.details_layout)
  750. # ########################
  751. # Properties layout
  752. # ########################
  753. self.properties_layout = QtWidgets.QVBoxLayout()
  754. self.properties_layout.setAlignment(QtCore.Qt.AlignTop)
  755. self.properties_groupbox = QtWidgets.QGroupBox("Properties")
  756. self.properties_groupbox.setFixedWidth(150)
  757. self.properties_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
  758. "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
  759. "subcontrol-position: top left;}")
  760. self.properties_list_widget = QtWidgets.QListWidget()
  761. self.material_properties = ['ambientOcclusion', 'baseColor', 'emissive', 'metallic', 'roughness', 'specularF0',
  762. 'normal', 'opacity']
  763. self.properties_list_widget.addItems(self.material_properties)
  764. self.properties_list_widget.itemSelectionChanged.connect(self.property_selection_changed)
  765. self.properties_layout.addSpacing(15)
  766. self.properties_layout.addWidget(self.properties_list_widget)
  767. self.information_layout.addWidget(self.properties_groupbox)
  768. self.properties_groupbox.setLayout(self.properties_layout)
  769. # ########################
  770. # Attributes layout
  771. # ########################
  772. self.attributes_layout = QtWidgets.QVBoxLayout()
  773. self.attributes_layout.setAlignment(QtCore.Qt.AlignTop)
  774. self.attributes_groupbox = QtWidgets.QGroupBox("Attributes")
  775. self.attributes_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
  776. "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
  777. "subcontrol-position: top left;}")
  778. self.information_layout.addWidget(self.attributes_groupbox)
  779. self.attributes_layout.addSpacing(15)
  780. self.attributes_table = QtWidgets.QTableWidget()
  781. self.attributes_table.setFocusPolicy(QtCore.Qt.NoFocus)
  782. self.attributes_table.setColumnCount(2)
  783. self.attributes_table.setAlternatingRowColors(True)
  784. self.attributes_table.setHorizontalHeaderLabels(['Attribute', 'Value'])
  785. self.attributes_table.verticalHeader().hide()
  786. attributes_table_header = self.attributes_table.horizontalHeader()
  787. attributes_table_header.setStyleSheet('QHeaderView::section {background-color: rgb(220, 220, 220);}')
  788. attributes_table_header.setDefaultAlignment(QtCore.Qt.AlignLeft)
  789. attributes_table_header.setContentsMargins(10, 10, 0, 0)
  790. attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
  791. attributes_table_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
  792. attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Interactive)
  793. self.attributes_layout.addWidget(self.attributes_table)
  794. self.attributes_groupbox.setLayout(self.attributes_layout)
  795. self.initialize_display_values()
  796. def initialize_display_values(self):
  797. """
  798. Initializes all of the widget item information for material based on the DCC application info the class has
  799. been passed.
  800. :return:
  801. """
  802. for material_property in self.material_properties:
  803. if material_property in self.material_info.get('properties'):
  804. self.property_settings[material_property] = self.material_info['properties'].get(material_property)
  805. current_row = self.material_properties.index(material_property)
  806. current_item = self.properties_list_widget.takeItem(current_row)
  807. self.properties_list_widget.insertItem(0, current_item)
  808. else:
  809. self.property_settings[material_property] = 'inactive'
  810. current_row = self.material_properties.index(material_property)
  811. item = self.properties_list_widget.item(current_row)
  812. item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEnabled)
  813. item.setFlags(item.flags() & ~QtCore.Qt.ItemIsSelectable)
  814. self.properties_list_widget.setCurrentRow(0)
  815. self.set_attributes_table(self.get_selected_property())
  816. def set_attributes_table(self, selected_property):
  817. """
  818. Displays the key, value pairs for the item selected in the Properties list widget
  819. :param selected_property: The item in the Properties list widget that is currently selected. Only active
  820. values are displayed.
  821. :return:
  822. """
  823. self.attributes_table.setRowCount(0)
  824. row_count = 0
  825. for key, value in self.property_settings[selected_property].items():
  826. self.attributes_table.insertRow(row_count)
  827. key_item = QtWidgets.QTableWidgetItem(key)
  828. self.attributes_table.setItem(row_count, 0, key_item)
  829. value_item = QtWidgets.QTableWidgetItem(value)
  830. self.attributes_table.setItem(row_count, 1, value_item)
  831. row_count += 1
  832. def get_selected_property(self):
  833. """
  834. Convenience function to get current value selected in the Properties list widget.
  835. :return:
  836. """
  837. return self.properties_list_widget.currentItem().text()
  838. def update_model(self):
  839. """
  840. Not sure if this will go away, but if desired, I could make attribute values able to be revised after
  841. materials have been scraped from the DCC materials
  842. :return:
  843. """
  844. pass
  845. def edit_button_clicked(self):
  846. """
  847. This is in place in the event that we want to allow material revisions for properties to be made after
  848. DCC processing step has already been executed. The idea would basically be to surface an editable
  849. table where values can be added, removed or changed within the final material definition.
  850. :return:
  851. """
  852. logging.debug('Edit button clicked')
  853. def property_selection_changed(self):
  854. """
  855. Fired when index of list view selected property selection has changed.
  856. :return:
  857. """
  858. self.set_attributes_table(self.get_selected_property())
  859. def is_valid_file(file_name):
  860. """
  861. The acts as a clearinghouse for DCC file types supported by the script
  862. :param file_name: Reads the extension of the filename for filtering
  863. :return:
  864. """
  865. target_extensions = 'ma mb fbx blend max'.split(' ')
  866. if file_name.split('.')[-1] in target_extensions:
  867. return True
  868. return False
  869. def launch_material_converter(window_type='standalone', material_type='PBR', target_files=None):
  870. """
  871. The setup for this will be revised once this is fully integrated into the DCCsi system. Currently only the
  872. standalone (default) and command line entry points work as intended.
  873. :param window_type: The method of access for material conversion (standalone, command_line, maya_native, max_native)
  874. :param material_type: Type of output material desired for import into Lumberyard. Currently only PBR is supported
  875. :param target_files: DCC app files to process for converted Lumberyard materials
  876. :return:
  877. """
  878. if window_type == 'command_line':
  879. MaterialsToLumberyard(material_type, target_files)
  880. elif window_type == 'maya_native':
  881. from maya import OpenMayaUI as omui
  882. main_window_pointer = omui.MQtUtil.mainWindow()
  883. main_app_window = wrapInstance(long(main_window_pointer), QtWidgets.QWidget)
  884. MaterialsToLumberyard(material_type, None, main_app_window)
  885. elif window_type == 'max_native':
  886. from pymxs import runtime as rt
  887. main_window_pointer = QtWidgets.QWidget.find(rt.windows.getMAXHWND())
  888. main_app_window = shiboken2.wrapInstance(shiboken2.getCppPointer(main_window_pointer)[0], QtWidgets.QMainWindow)
  889. MaterialsToLumberyard(material_type, None, main_app_window)
  890. else:
  891. app = QApplication(sys.argv)
  892. app_ui = MaterialsToLumberyard()
  893. app_ui.show()
  894. sys.exit(app.exec_())
  895. if __name__ == '__main__':
  896. launch_material_converter()