|
@@ -1,1026 +0,0 @@
|
|
|
-# Copyright (c) Contributors to the Open 3D Engine Project.
|
|
|
-# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
|
-#
|
|
|
-# SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
|
-#
|
|
|
-#
|
|
|
-
|
|
|
-"""
|
|
|
-Usage
|
|
|
-=====
|
|
|
-Put usage instructions here.
|
|
|
-
|
|
|
-Output
|
|
|
-======
|
|
|
-Put output information here.
|
|
|
-
|
|
|
-Notes:
|
|
|
-In order to run this, you'll need to verify that the "mayapy_path" class attribute corresponds to the location on
|
|
|
-your machine. Currently I've just included mapping instructions for Maya StingrayPBS materials, although most of
|
|
|
-the needed elements are in place to carry out additional materials inside of Maya pretty quickly moving forward.
|
|
|
-I've marked areas that still need refinement (or to be added altogether) with TODO comments
|
|
|
-
|
|
|
-TODO- Docstrings need work... wanted to get descriptions in but they need to be set for Sphinx
|
|
|
-TODO- Add 3ds Max interoperability
|
|
|
-Links:
|
|
|
-https://blender.stackexchange.com/questions/100497/use-blenders-bpy-in-projects-outside-blender
|
|
|
-https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2019/ENU/3DSMax-Batch/files/
|
|
|
-GUID-0968FF0A-5ADD-454D-B8F6-1983E76A4AF9-htm.html
|
|
|
-
|
|
|
-TODO- Look at dynaconf and wire in a solid means for configuration settings
|
|
|
-TODO- This hasn't been "designed"- might be worth it to consider the visual design to ensure the most effective and
|
|
|
- attractive UI
|
|
|
-TODO- Allow revisions to Model
|
|
|
-
|
|
|
-Reading FBX file information (might come in handy later)
|
|
|
--- Materials information can be extracted from ASCII fbx pretty easily, binary is possible but more difficult
|
|
|
--- FBX files could be exported as ASCII files and I could use regex there to extract material information
|
|
|
--- I couldn't get pyfbx_i42 to work, but purportedly it can extract information from binary files. You may just have
|
|
|
-to use the specified python versions
|
|
|
-"""
|
|
|
-# built-ins
|
|
|
-import collections
|
|
|
-import logging
|
|
|
-import subprocess
|
|
|
-import json
|
|
|
-import sys
|
|
|
-import os
|
|
|
-import re
|
|
|
-
|
|
|
-# should give access to Lumberyard Qt dlls and PySide2
|
|
|
-from PySide2 import QtWidgets, QtCore, QtGui
|
|
|
-from PySide2.QtCore import Slot
|
|
|
-from PySide2.QtWidgets import QApplication
|
|
|
-import shiboken2
|
|
|
-from shiboken2 import wrapInstance
|
|
|
-
|
|
|
-# local imports
|
|
|
-from model import MaterialsModel
|
|
|
-from drag_and_drop import DragAndDrop
|
|
|
-import dcc_material_mapping as dcc_map
|
|
|
-
|
|
|
-# global space
|
|
|
-main_window_pointer = None
|
|
|
-main_app_window = None
|
|
|
-
|
|
|
-
|
|
|
-class MaterialsToLumberyard(QtWidgets.QWidget):
|
|
|
- def __init__(self, output_material_type='PBR', cli_values=None, parent=None):
|
|
|
- super(MaterialsToLumberyard, self).__init__(parent)
|
|
|
-
|
|
|
- self.app = QtWidgets.QApplication.instance()
|
|
|
- self.setWindowFlags(QtCore.Qt.Window)
|
|
|
- self.setGeometry(50, 50, 800, 520)
|
|
|
- self.setObjectName('MaterialsToLumberyard')
|
|
|
- self.setWindowTitle(' ')
|
|
|
- self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint)
|
|
|
- self.isTopLevel()
|
|
|
-
|
|
|
- self.cli_enabled = cli_values
|
|
|
- self.output_material_type = output_material_type
|
|
|
- self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
|
|
|
- self.directory_path = os.path.dirname(os.path.abspath(__file__))
|
|
|
- self.mayapy_path = os.path.abspath("C:/Program Files/Autodesk/Maya2020/bin/mayapy.exe")
|
|
|
- self.blender_path = self.get_blender_path()
|
|
|
- self.bold_font_large = QtGui.QFont('Helvetica', 7, QtGui.QFont.Bold)
|
|
|
- self.medium_font = QtGui.QFont('Helvetica', 7, QtGui.QFont.Normal)
|
|
|
- self.blessed_file_extensions = 'ma mb fbx max blend'.split(' ')
|
|
|
-
|
|
|
- self.dcc_materials_dictionary = {}
|
|
|
- self.lumberyard_materials_dictionary = {}
|
|
|
- self.lumberyard_material_nodes = []
|
|
|
- self.target_file_list = []
|
|
|
- self.current_scene = None
|
|
|
- self.model = None
|
|
|
- self.total_materials = 0
|
|
|
-
|
|
|
- self.main_container = QtWidgets.QVBoxLayout(self)
|
|
|
- self.main_container.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.main_container.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.setLayout(self.main_container)
|
|
|
- self.content_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.content_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.content_layout.setContentsMargins(10, 3, 10, 5)
|
|
|
- self.main_container.addLayout(self.content_layout)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> Header Bar
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- self.header_bar_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.lumberyard_logo_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.lumberyard_logo_layout.setAlignment(QtCore.Qt.AlignLeft)
|
|
|
- logo_path = os.path.join(self.directory_path, 'resources', 'lumberyard_logo.png')
|
|
|
- logo_pixmap = QtGui.QPixmap(logo_path)
|
|
|
- self.lumberyard_logo = QtWidgets.QLabel()
|
|
|
- self.lumberyard_logo.setPixmap(logo_pixmap)
|
|
|
- self.lumberyard_logo_layout.addWidget(self.lumberyard_logo)
|
|
|
- self.header_bar_layout.addLayout(self.lumberyard_logo_layout)
|
|
|
-
|
|
|
- self.switch_combobox_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.switch_combobox_layout.setAlignment(QtCore.Qt.AlignRight)
|
|
|
- self.switch_layout_combobox = QtWidgets.QComboBox()
|
|
|
- self.set_combobox_items_accessibility()
|
|
|
- self.switch_layout_combobox.setFixedSize(250, 30)
|
|
|
- self.combobox_items = ['Add Source Files', 'Source File List', 'DCC Material Values', 'Export Materials']
|
|
|
- self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}')
|
|
|
- self.switch_layout_combobox.addItems(self.combobox_items)
|
|
|
- self.switch_combobox_layout.addWidget(self.switch_layout_combobox)
|
|
|
- self.header_bar_layout.addLayout(self.switch_combobox_layout)
|
|
|
-
|
|
|
- self.content_layout.addSpacing(5)
|
|
|
- self.content_layout.addLayout(self.header_bar_layout)
|
|
|
-
|
|
|
- # ++++++++++++++++++++++++++++++++++++++++++++++++#
|
|
|
- # File Source Table / Attributes (Stacked Layout) #
|
|
|
- # ++++++++++++++++++++++++++++++++++++++++++++++++#
|
|
|
-
|
|
|
- self.content_stacked_layout = QtWidgets.QStackedLayout()
|
|
|
- self.content_layout.addLayout(self.content_stacked_layout)
|
|
|
- self.switch_layout_combobox.currentIndexChanged.connect(self.layout_combobox_changed)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> Add Source Files
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- frame_color_value = '75,75,75'
|
|
|
- highlight_color_value = '20,106,30'
|
|
|
- self.drag_and_drop_widget = DragAndDrop(frame_color_value, highlight_color_value)
|
|
|
- self.drag_and_drop_widget.drop_update.connect(self.drag_and_drop_file_update)
|
|
|
- self.drag_and_drop_widget.drop_over.connect(self.drag_and_drop_over)
|
|
|
- self.drag_and_drop_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.drag_and_drop_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.drag_and_drop_layout.setAlignment(QtCore.Qt.AlignCenter)
|
|
|
- self.drag_and_drop_widget.setLayout(self.drag_and_drop_layout)
|
|
|
-
|
|
|
- start_message = 'Drag source files here, or use file browser button below to get started.'
|
|
|
- self.drag_and_drop_label = QtWidgets.QLabel(start_message)
|
|
|
- self.drag_and_drop_label.setStyleSheet('color: white;')
|
|
|
- self.drag_and_drop_layout.addWidget(self.drag_and_drop_label)
|
|
|
- self.drag_and_drop_layout.addSpacing(10)
|
|
|
-
|
|
|
- self.select_files_button_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.select_files_button_layout.setAlignment(QtCore.Qt.AlignCenter)
|
|
|
- self.select_files_button = QtWidgets.QPushButton('Select Files')
|
|
|
- self.select_files_button_layout.addWidget(self.select_files_button)
|
|
|
- self.select_files_button.clicked.connect(self.select_files_button_clicked)
|
|
|
- self.select_files_button.setFixedSize(80, 35)
|
|
|
- self.drag_and_drop_layout.addLayout(self.select_files_button_layout)
|
|
|
- self.content_stacked_layout.addWidget(self.drag_and_drop_widget)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> Files Table
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- self.target_files_table = QtWidgets.QTableWidget()
|
|
|
- self.target_files_table.setFocusPolicy(QtCore.Qt.NoFocus)
|
|
|
- self.target_files_table.setColumnCount(2)
|
|
|
- self.target_files_table.setAlternatingRowColors(True)
|
|
|
- self.target_files_table.setHorizontalHeaderLabels(['File List', ''])
|
|
|
- self.target_files_table.horizontalHeader().setStyleSheet('QHeaderView::section '
|
|
|
- '{background-color: rgb(220, 220, 220); '
|
|
|
- 'padding-top:7px; padding-left:5px;}')
|
|
|
- self.target_files_table.verticalHeader().hide()
|
|
|
- files_header = self.target_files_table.horizontalHeader()
|
|
|
- files_header.setFixedHeight(30)
|
|
|
- files_header.setDefaultAlignment(QtCore.Qt.AlignLeft)
|
|
|
- files_header.setContentsMargins(10, 10, 0, 0)
|
|
|
- files_header.setDefaultSectionSize(60)
|
|
|
- files_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
|
|
- files_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
|
|
|
- self.target_files_table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
|
|
|
- self.content_stacked_layout.addWidget(self.target_files_table)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> Scene Information Table
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- self.material_tree_view = QtWidgets.QTreeView()
|
|
|
- self.headers = ['Key', 'Value']
|
|
|
- self.material_tree_view.setStyleSheet('QTreeView::item {height:25px;} QHeaderView::section '
|
|
|
- '{background-color: rgb(220, 220, 220); height:30px; padding-left:10px}')
|
|
|
- self.material_tree_view.setFocusPolicy(QtCore.Qt.NoFocus)
|
|
|
- self.material_tree_view.setAlternatingRowColors(True)
|
|
|
- self.material_tree_view.setUniformRowHeights(True)
|
|
|
- self.content_stacked_layout.addWidget(self.material_tree_view)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> LY Material Definitions
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- self.lumberyard_material_definitions_widget = QtWidgets.QWidget()
|
|
|
- self.lumberyard_material_definitions_layout = QtWidgets.QHBoxLayout(self.lumberyard_material_definitions_widget)
|
|
|
- self.lumberyard_material_definitions_layout.setSpacing(0)
|
|
|
- self.lumberyard_material_definitions_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.lumberyard_material_definitions_frame = QtWidgets.QFrame(self.lumberyard_material_definitions_widget)
|
|
|
- self.lumberyard_material_definitions_frame.setGeometry(0, 0, 5000, 5000)
|
|
|
- self.lumberyard_material_definitions_frame.setStyleSheet('background-color:rgb(75,75,75);')
|
|
|
- self.lumberyard_material_definitions_scroller = QtWidgets.QScrollArea()
|
|
|
- self.scroller_widget = QtWidgets.QWidget()
|
|
|
- self.scroller_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.scroller_widget.setLayout(self.scroller_layout)
|
|
|
- self.lumberyard_material_definitions_scroller.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
|
- self.lumberyard_material_definitions_scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
|
|
- self.lumberyard_material_definitions_scroller.setWidgetResizable(True)
|
|
|
- self.lumberyard_material_definitions_scroller.setWidget(self.scroller_widget)
|
|
|
- self.lumberyard_material_definitions_layout.addWidget(self.lumberyard_material_definitions_scroller)
|
|
|
- self.content_stacked_layout.addWidget(self.lumberyard_material_definitions_widget)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> File processing buttons
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- self.process_files_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.content_layout.addLayout(self.process_files_layout)
|
|
|
- self.process_files_button = QtWidgets.QPushButton('Process Added Files')
|
|
|
- self.process_files_button.setFixedHeight(50)
|
|
|
- self.process_files_button.clicked.connect(self.process_listed_files_clicked)
|
|
|
- self.process_files_layout.addWidget(self.process_files_button)
|
|
|
-
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
- # ---->> Status bar / Loader
|
|
|
- # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
|
|
-
|
|
|
- # TODO- Move all processing of files to another thread and display progress with loader
|
|
|
-
|
|
|
- self.status_bar = QtWidgets.QStatusBar()
|
|
|
- self.status_bar.setStyleSheet('background-color: rgb(220, 220, 220);')
|
|
|
- self.status_bar.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.status_bar.setSizeGripEnabled(False)
|
|
|
- self.message_readout_label = QtWidgets.QLabel('Ready.')
|
|
|
- self.message_readout_label.setStyleSheet('padding-left: 10px')
|
|
|
- self.status_bar.addWidget(self.message_readout_label)
|
|
|
-
|
|
|
- self.progress_bar = QtWidgets.QProgressBar()
|
|
|
- self.progress_bar_widget = QtWidgets.QWidget()
|
|
|
- self.progress_bar_widget_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.progress_bar_widget_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.progress_bar_widget_layout.setAlignment(QtCore.Qt.AlignRight)
|
|
|
- self.progress_bar_widget.setLayout(self.progress_bar_widget_layout)
|
|
|
- self.status_bar.addPermanentWidget(self.progress_bar_widget)
|
|
|
- self.progress_bar_widget_layout.addWidget(self.progress_bar)
|
|
|
- self.progress_bar.setFixedSize(180, 20)
|
|
|
- self.main_container.addWidget(self.status_bar)
|
|
|
- self.initialize()
|
|
|
-
|
|
|
- ############################
|
|
|
- # UI Display Layers ########
|
|
|
- ############################
|
|
|
-
|
|
|
- def initialize(self):
|
|
|
- if self.cli_enabled:
|
|
|
- print('CLI ACCESS:::::::::::\nValues passed: {}'.format(self.cli_enabled))
|
|
|
- self.target_file_list = self.cli_enabled
|
|
|
- self.process_file_list()
|
|
|
- self.export_selected_materials()
|
|
|
-
|
|
|
- def populate_source_files_table(self):
|
|
|
- """
|
|
|
- Adds selected files from the 'Source Files' section of the UI. This creates each item listing in the table
|
|
|
- as well as adds a 'Remove' button that will clear corresponding item from the table. Processed files will
|
|
|
- get color coded, based on whether or not the materials in the file could be successfully processed. Subsequent
|
|
|
- searches will not clear items from the table currently, as each item acts as a register of materials that have
|
|
|
- and have not yet been processed.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.target_files_table.setRowCount(0)
|
|
|
- for index, entry in enumerate(self.target_file_list):
|
|
|
- entry = entry[1] if type(entry) == list else entry
|
|
|
- self.target_files_table.insertRow(index)
|
|
|
- item = QtWidgets.QTableWidgetItem(' {}'.format(entry))
|
|
|
- self.target_files_table.setRowHeight(index, 45)
|
|
|
- remove_button = QtWidgets.QPushButton('Remove')
|
|
|
- remove_button.setFixedWidth(60)
|
|
|
- remove_button.clicked.connect(self.remove_source_file_clicked)
|
|
|
- self.target_files_table.setItem(index, 0, item)
|
|
|
- self.target_files_table.setCellWidget(index, 1, remove_button)
|
|
|
-
|
|
|
- def populate_dcc_material_values_tree(self):
|
|
|
- """
|
|
|
- Sets the materials model class to the file attribute tree.
|
|
|
- :return:
|
|
|
- """
|
|
|
- # TODO- Create mechanism for collapsing previously gathered materials, and or pushing them further down the list
|
|
|
- self.material_tree_view.setModel(self.model)
|
|
|
- self.material_tree_view.expandAll()
|
|
|
- self.material_tree_view.resizeColumnToContents(0)
|
|
|
-
|
|
|
- def populate_export_materials_list(self):
|
|
|
- """
|
|
|
- Once all materials have been analyzed inside of DCC applications, the 'Export Materials' view lists all
|
|
|
- materials presented as their Lumberyard counterparts. Each listing displays a representation of the material
|
|
|
- file based on its corresponding DCC material values and file connections.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.reset_export_materials_description()
|
|
|
- for count, value in enumerate(self.lumberyard_materials_dictionary):
|
|
|
- material_definition_node = MaterialNode([value, self.lumberyard_materials_dictionary[value]], count)
|
|
|
- self.lumberyard_material_nodes.append(material_definition_node)
|
|
|
- self.scroller_layout.addWidget(material_definition_node)
|
|
|
- self.scroller_layout.addLayout(self.create_separator_line())
|
|
|
-
|
|
|
- ############################
|
|
|
- # TBD ########
|
|
|
- ############################
|
|
|
-
|
|
|
- def process_file_list(self):
|
|
|
- """
|
|
|
- The entry point for reading DCC files and extracting values. Files are filtered and separated
|
|
|
- by DCC app (based on file extensions) before processing is done.
|
|
|
-
|
|
|
- Supported DCC applications:
|
|
|
- Maya (.ma, .mb, .fbx), 3dsMax(.max), Blender(.blend)
|
|
|
- :return:
|
|
|
- """
|
|
|
- files_dict = {'maya': [], 'max': [], 'blender': [], 'na': []}
|
|
|
- for file_location in self.target_file_list:
|
|
|
- file_name = os.path.basename(str(file_location))
|
|
|
- file_extension = os.path.splitext(file_name)[1]
|
|
|
- target_application = self.get_target_application(file_extension)
|
|
|
- if target_application in files_dict.keys():
|
|
|
- files_dict[target_application].append(file_location)
|
|
|
-
|
|
|
- for key, values in files_dict.items():
|
|
|
- try:
|
|
|
- if key == 'maya' and len(values):
|
|
|
- self.get_maya_material_values(values)
|
|
|
- elif key == 'max' and len(values):
|
|
|
- self.get_max_material_values(values)
|
|
|
- elif key == 'blender' and len(values):
|
|
|
- self.get_blender_material_values(values)
|
|
|
- else:
|
|
|
- pass
|
|
|
- except Exception as e:
|
|
|
- # TODO- Allow corrective actions or some display of errors if this fails?
|
|
|
- logging.warning('Could not process files. Error: {}'.format(e))
|
|
|
-
|
|
|
- if self.dcc_materials_dictionary:
|
|
|
- self.set_transfer_status(self.dcc_materials_dictionary)
|
|
|
- # Create Model with extracted values from file list
|
|
|
- self.set_material_model()
|
|
|
- # Setup Lumberyard Material File Values
|
|
|
- self.set_export_materials_description()
|
|
|
- # Update UI Layout
|
|
|
- self.populate_export_materials_list()
|
|
|
- self.switch_layout_combobox.setCurrentIndex(3)
|
|
|
- self.set_ui_buttons()
|
|
|
- self.message_readout_label.setText('Ready.')
|
|
|
-
|
|
|
- def reset_export_materials_description(self):
|
|
|
- pass
|
|
|
-
|
|
|
- def reset_all_values(self):
|
|
|
- pass
|
|
|
-
|
|
|
- def create_separator_line(self):
|
|
|
- """ Convenience function for adding separation line to the UI. """
|
|
|
- layout = QtWidgets.QHBoxLayout()
|
|
|
- line = QtWidgets.QLabel()
|
|
|
- line.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
|
|
|
- line.setLineWidth(1)
|
|
|
- line.setFixedHeight(10)
|
|
|
- layout.addWidget(line)
|
|
|
- layout.setContentsMargins(8, 0, 8, 0)
|
|
|
- return layout
|
|
|
-
|
|
|
- def export_selected_materials(self):
|
|
|
- """
|
|
|
- This will eventually be revised to save material definitions in the proper place in the user's project folder,
|
|
|
- but for now material definitions will be saved to the desktop.
|
|
|
- :return:
|
|
|
- """
|
|
|
- for node in self.lumberyard_material_nodes:
|
|
|
- if node.material_name_checkbox.isChecked():
|
|
|
- output_path = os.path.dirname(node.material_info['sourceFile'])
|
|
|
- node.material_info.pop('sourceFile')
|
|
|
- output = os.path.join(output_path, '{}.material'.format(node.material_name))
|
|
|
- with open(output, 'w', encoding='utf-8') as material_file:
|
|
|
- json.dump(node.material_info, material_file, ensure_ascii=False, indent=4)
|
|
|
-
|
|
|
- ############################
|
|
|
- # Getters/Setters ##########
|
|
|
- ############################
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def get_target_application(file_extension):
|
|
|
- """
|
|
|
- Searches compatible file extensions and returns one of three Application names- Maya, 3dsMax, or Blender.
|
|
|
- :param file_extension: Passed file extension used to determine DCC Application it originated from.
|
|
|
- :return: Returns the application corresponding to the extension if found- otherwise returns a Boolean None
|
|
|
- """
|
|
|
- app_extensions = {'maya': ['.ma', '.mb', '.fbx'], 'max': ['.max'], 'blender': ['.blend']}
|
|
|
- target_dcc_application = [key for key, values in app_extensions.items() if file_extension in values]
|
|
|
- if target_dcc_application:
|
|
|
- return target_dcc_application[0]
|
|
|
- return None
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def get_lumberyard_material_template(shader_type):
|
|
|
- """
|
|
|
- Loads material descriptions from the Lumberyard installation, providing a template to compare and convert DCC
|
|
|
- shaders to Lumberyard material definitions. This is the first step in the comparison. The second step is to
|
|
|
- compare these values with specific mapping instructions for DCC Application and DCC material type to arrive at
|
|
|
- a converted material.
|
|
|
- :param shader_type: The type of Lumberyard shader to pair material attributes to (i.e. PBR Shader)
|
|
|
- :return: File dictionary of the available boilerplate Lumberyard shader settings.
|
|
|
- """
|
|
|
- definitions = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources',
|
|
|
- '{}.template.material'.format(shader_type))
|
|
|
- if os.path.exists(definitions):
|
|
|
- with open(definitions) as f:
|
|
|
- return json.load(f)
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def get_lumberyard_material_properties(name, dcc_app, material_type, file_connections):
|
|
|
- """
|
|
|
- This system will probably need rethinking if DCCs and compatible materials grow. I've tried to keep this
|
|
|
- flexible so that it can be expanded with more apps and materials.
|
|
|
-
|
|
|
- :param name: Material name from within the DCC application
|
|
|
- :param dcc_app: The application that the material was sourced from
|
|
|
- :param material_type: DCC material type
|
|
|
- :param file_connections: Texture files found attached to the materials
|
|
|
- """
|
|
|
-
|
|
|
- material_properties = {}
|
|
|
- if dcc_app == 'Maya':
|
|
|
- material_properties = dcc_map.get_maya_material_mapping(name, material_type, file_connections)
|
|
|
- elif dcc_app == 'Blender':
|
|
|
- material_properties = dcc_map.get_blender_material_mapping(name, material_type, file_connections)
|
|
|
- elif dcc_app == '3dsMax':
|
|
|
- material_properties = dcc_map.get_max_material_mapping(name, material_type, file_connections)
|
|
|
- else:
|
|
|
- pass
|
|
|
- return material_properties
|
|
|
-
|
|
|
- @staticmethod
|
|
|
- def get_filename_increment(name):
|
|
|
- """
|
|
|
- Convenience function that assists in ensuring that if any materials are encountered with the same name, an
|
|
|
- underscore and number is appended to it to prevent overwrites.
|
|
|
- :param name: The name of the material. The function searches the string for increment numbers, and either adds
|
|
|
- one to any encountered, or adds an "_1" if passed name is the first duplicate encountered.
|
|
|
- :return: The adjusted name with a unique incremental value.
|
|
|
- """
|
|
|
- last_number = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+')
|
|
|
- number_found = last_number.search(name)
|
|
|
- if number_found:
|
|
|
- next_number = str(int(number_found.group(1)) + 1)
|
|
|
- start, end = number_found.span(1)
|
|
|
- name = name[:max(end - len(next_number), start)] + next_number + name[end:]
|
|
|
- return name
|
|
|
-
|
|
|
- def get_maya_material_values(self, target_files):
|
|
|
- """
|
|
|
- Launches Maya Standalone and processes list of materials for each scene passed to the 'target_files' argument.
|
|
|
- Also sets the environment paths needed for an instance of Maya's Python distribution. After files are processed
|
|
|
- a single dictionary of scene materials is returned, and added to the "materials_dictionary" scene attribute.
|
|
|
- :param target_files: List of files filtered from total list of files requested for processing that have a
|
|
|
- Maya file extension
|
|
|
- :return:
|
|
|
- """
|
|
|
-
|
|
|
- # TODO- Set load process to a separate thread and wire load progress bar up
|
|
|
-
|
|
|
- try:
|
|
|
- script_path = str(os.path.join(self.directory_path, 'maya_materials.py'))
|
|
|
- target_files.append(self.total_materials)
|
|
|
- runtime_env = os.environ.copy()
|
|
|
- runtime_env['MAYA_LOCATION'] = os.path.dirname(self.mayapy_path)
|
|
|
- runtime_env['PYTHONPATH'] = os.path.dirname(self.mayapy_path)
|
|
|
- command = f'{self.mayapy_path} "{script_path}"'
|
|
|
- for file in target_files:
|
|
|
- command += f' "{file}"'
|
|
|
- p = subprocess.Popen(command, shell=False, env=runtime_env, stdout=subprocess.PIPE)
|
|
|
- output = p.communicate()[0]
|
|
|
- self.set_material_dictionary(json.loads(output))
|
|
|
- except Exception as e:
|
|
|
- logging.warning('maya error: {}'.format(e))
|
|
|
-
|
|
|
- def get_max_material_values(self, target_files):
|
|
|
- """
|
|
|
- This has not been implemented yet.
|
|
|
-
|
|
|
- :param target_files: List of files filtered from total list of files requested for processing that have a
|
|
|
- .max file extension
|
|
|
- :return:
|
|
|
- """
|
|
|
- logging.debug('Max Target file: {}'.format(target_files))
|
|
|
-
|
|
|
- def get_blender_material_values(self, target_files):
|
|
|
- """
|
|
|
- This has not been implemented yet.
|
|
|
-
|
|
|
- :param target_files: List of files filtered from total list of files requested for processing that have a
|
|
|
- .blend file extension
|
|
|
- :return:
|
|
|
- """
|
|
|
- logging.debug('Blender Target file: {}'.format(target_files))
|
|
|
- script_path = str(os.path.join(self.directory_path, 'blender_materials.py'))
|
|
|
- target_files.append(self.total_materials)
|
|
|
- p = subprocess.Popen([self.blender_path, '--background', '--python', script_path, '--', target_files])
|
|
|
- output = p.communicate()[0]
|
|
|
- self.set_material_dictionary(json.loads(output))
|
|
|
-
|
|
|
- def get_blender_path(self):
|
|
|
- """
|
|
|
- Finds latest Blender version installed on the user machine for command line file processing.
|
|
|
-
|
|
|
- :return: Most current version available (or none)
|
|
|
- """
|
|
|
- blender_base_directory = os.path.join(os.path.join('C:\\', 'Program Files', 'Blender Foundation'))
|
|
|
- blender_versions_found = []
|
|
|
- for (directory_path, directory_name, filenames) in os.walk(blender_base_directory):
|
|
|
- for filename in filenames:
|
|
|
- if filename == 'blender.exe':
|
|
|
- blender_versions_found.append(os.path.join(directory_path, filename))
|
|
|
-
|
|
|
- if blender_versions_found:
|
|
|
- return max(blender_versions_found, key=os.path.getctime)
|
|
|
- else:
|
|
|
- return None
|
|
|
-
|
|
|
- def set_combobox_items_accessibility(self):
|
|
|
- """
|
|
|
- Locks items from within the combobox until the sections they connect to have content
|
|
|
- :return:
|
|
|
- """
|
|
|
- # TODO- Add this functionality
|
|
|
- pass
|
|
|
-
|
|
|
- def set_transfer_status(self, transfer_info):
|
|
|
- """
|
|
|
- Colorizes listings in the 'Source Files' view of the UI after processing to green or red, indicating whether or
|
|
|
- not scene analysis successfully returned compatible materials and their values.
|
|
|
- :param transfer_info: Each file the scripts attempt to process return a receipt of the success or failure of
|
|
|
- the analysis.
|
|
|
- :return:
|
|
|
- """
|
|
|
-
|
|
|
- # TODO- Include some way to get error information if analysis fails, and potentially offer the means to
|
|
|
- # repair values as they map to intended Lumberyard shader type
|
|
|
-
|
|
|
- for row in range(self.target_files_table.rowCount()):
|
|
|
- for key, values in transfer_info.items():
|
|
|
- row_path = self.target_files_table.item(row, 0).text().strip()
|
|
|
- scene_processed = {x for x in transfer_info if values['SceneName'].replace('\\', '/') == row_path}
|
|
|
- if scene_processed:
|
|
|
- self.target_files_table.item(row, 0).setBackground(QtGui.QColor(192, 255, 171))
|
|
|
- break
|
|
|
- else:
|
|
|
- self.target_files_table.item(row, 0).setBackground(QtGui.QColor(255, 177, 171))
|
|
|
-
|
|
|
- def set_export_materials_description(self):
|
|
|
- root = self.model.rootItem
|
|
|
- for row in range(self.model.rowCount()):
|
|
|
- source_file = self.model.get_attribute_value('SceneName', root.child(row))
|
|
|
- name = self.model.get_attribute_value('MaterialName', root.child(row))
|
|
|
- material_type = self.model.get_attribute_value('MaterialType', root.child(row))
|
|
|
- dcc_app = self.model.get_attribute_value('DccApplication', root.child(row))
|
|
|
- file_connections = {}
|
|
|
- shader_attributes = {}
|
|
|
-
|
|
|
- for childIndex in range(root.child(row).childCount()):
|
|
|
- child_item = root.child(row).child(childIndex)
|
|
|
- child_value = child_item.itemData
|
|
|
- if child_item.childCount():
|
|
|
- target_dict = file_connections if child_value[0] == 'FileConnections' else shader_attributes
|
|
|
- for subChildIndex in range(child_item.childCount()):
|
|
|
- sub_child_data = child_item.child(subChildIndex).itemData
|
|
|
- target_dict[sub_child_data[0]] = sub_child_data[1]
|
|
|
- self.set_material_description(source_file, name, dcc_app, material_type, file_connections)
|
|
|
-
|
|
|
- def set_material_dictionary(self, dcc_dictionary):
|
|
|
- """
|
|
|
- Adds all material descriptions pulled from each DCC file analyzed to the "materials_dictionary" class attribute.
|
|
|
- This function runs each time a subprocess is launched to gather DCC application material values.
|
|
|
- :param dcc_dictionary: The dictionary of values for each material analyzed by each specific DCC file list
|
|
|
- return analyzed values
|
|
|
- :return:
|
|
|
- """
|
|
|
- logging.debug('DCC Dictionary: {}'.format(json.dumps(dcc_dictionary, indent=4)))
|
|
|
- self.total_materials += len(dcc_dictionary)
|
|
|
- self.dcc_materials_dictionary.update(dcc_dictionary)
|
|
|
-
|
|
|
- def set_material_model(self, initialize=True):
|
|
|
- """
|
|
|
- Once all materials have been gathered across a selected file set query, this organizes the values into a
|
|
|
- QT Model Class
|
|
|
- :param initialize: Default is set to boolean True. If a model has already been established in the current
|
|
|
- session, the initialize parameter would be set to false, and the values added to the Model. All changes to
|
|
|
- the model would then be redistributed to other informational views in the UI.
|
|
|
- :return:
|
|
|
- """
|
|
|
- if initialize:
|
|
|
- self.model = MaterialsModel(self.headers, self.dcc_materials_dictionary)
|
|
|
- else:
|
|
|
- self.model.update()
|
|
|
- self.dcc_materials_dictionary.clear()
|
|
|
- self.populate_dcc_material_values_tree()
|
|
|
-
|
|
|
- def set_ui_buttons(self):
|
|
|
- """
|
|
|
- Handles UI buttons for each of the three stacked layout views (Source Files, DCC Material Values,
|
|
|
- Export Materials)
|
|
|
- :return:
|
|
|
- """
|
|
|
- display_index = self.content_stacked_layout.currentIndex()
|
|
|
- self.switch_layout_combobox.setEnabled(True)
|
|
|
- self.process_files_button.setText('Process Listed Files')
|
|
|
- # Add Source Files Layout ------------------------------->>
|
|
|
- if display_index == 0:
|
|
|
- self.process_files_button.setEnabled(True)
|
|
|
-
|
|
|
- # Source File List -------------------------------------->>
|
|
|
- elif display_index == 1:
|
|
|
- self.process_files_button.setEnabled(True)
|
|
|
-
|
|
|
- # DCC Material Values Layout ---------------------------->>
|
|
|
- elif display_index == 2:
|
|
|
- self.process_files_button.setEnabled(False)
|
|
|
-
|
|
|
- # Export Materials Layout ------------------------------->>
|
|
|
- else:
|
|
|
- self.process_files_button.setText('Export Selected Materials')
|
|
|
- if self.lumberyard_materials_dictionary:
|
|
|
- self.process_files_button.setEnabled(True)
|
|
|
-
|
|
|
- def set_material_description(self, source_file, name, dcc_app, material_type, file_connections):
|
|
|
- """
|
|
|
- Build dictionary for material description based on extracted values
|
|
|
-
|
|
|
- :param source_file: The file that the material was extracted from
|
|
|
- :param name: Name of material
|
|
|
- :param dcc_app: Source file type of material (Maya, Blender or 3ds Max)
|
|
|
- :param material_type: Material type within app (i.e. Stingray PBS)
|
|
|
- :param file_connections: Texture files found connected to the shader
|
|
|
- :return:
|
|
|
- """
|
|
|
-
|
|
|
- default_settings = self.get_lumberyard_material_template('standardPBR')
|
|
|
- material = collections.OrderedDict(sourceFile=source_file, description=name,
|
|
|
- materialType=default_settings.get('materialType'),
|
|
|
- parentMaterial=default_settings.get('parentMaterial'),
|
|
|
- propertyLayoutVersion=default_settings.get('propertyLayoutVersion'),
|
|
|
- properties=self.get_lumberyard_material_properties(name, dcc_app,
|
|
|
- material_type,
|
|
|
- file_connections))
|
|
|
- name += self.output_material_type
|
|
|
- self.lumberyard_materials_dictionary[name if name not in self.lumberyard_materials_dictionary.keys() else
|
|
|
- self.get_filename_increment(name)] = material
|
|
|
-
|
|
|
- ############################
|
|
|
- # Button Actions ###########
|
|
|
- ############################
|
|
|
-
|
|
|
- def remove_source_file_clicked(self):
|
|
|
- """
|
|
|
- In the Source File view of the UI layout, this will remove the listed file in its respective row. If files
|
|
|
- have not been processed yet, it prevents that file from being analyzed. If the files have already been
|
|
|
- analyzed, this will remove the materials from stored values.
|
|
|
- :return:
|
|
|
- """
|
|
|
- file_index = self.target_files_table.indexAt(self.sender().pos())
|
|
|
- del self.target_file_list[file_index.row()]
|
|
|
- self.populate_files_table()
|
|
|
-
|
|
|
- def process_listed_files_clicked(self):
|
|
|
- """
|
|
|
- The button serves a dual purpose, depending on the current layout of the window. 'Process listed files'
|
|
|
- initiates the DCC file analysis that extracts material information. In the "Export Materials" layout, this
|
|
|
- button (for now) will export material files corresponding to each analyzed material. Exported material files
|
|
|
- are routed to the directories of the respective files processed.
|
|
|
- :return:
|
|
|
- """
|
|
|
-
|
|
|
- if self.sender().text() == 'Process Added Files':
|
|
|
- self.message_readout_label.setText('Gathering Material Information...')
|
|
|
- self.app.processEvents()
|
|
|
- self.process_file_list()
|
|
|
- else:
|
|
|
- self.export_selected_materials()
|
|
|
-
|
|
|
- def select_files_button_clicked(self):
|
|
|
- """
|
|
|
- This dialog allows user to select DCC files to be processed for the materials present for conversion.
|
|
|
- :return:
|
|
|
- """
|
|
|
-
|
|
|
- # TODO- Eventually it might be worth it to allow files from multiple locations to be selected. Currently
|
|
|
- # this only allows single/multiple files from a single directory to be selected, although drag and drop
|
|
|
- # allows multiple locations
|
|
|
-
|
|
|
- dialog = QtWidgets.QFileDialog(self, 'Shift-Select Target Files', self.desktop_location)
|
|
|
- dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
|
|
- dialog.setNameFilter('Compatible Files (*.ma *.mb *.fbx *.max *.blend)')
|
|
|
- dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True)
|
|
|
- file_view = dialog.findChild(QtWidgets.QListView, 'listView')
|
|
|
-
|
|
|
- # Workaround for selecting multiple files with File Dialog
|
|
|
- if file_view:
|
|
|
- file_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
|
|
- f_tree_view = dialog.findChild(QtWidgets.QTreeView)
|
|
|
- if f_tree_view:
|
|
|
- f_tree_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
|
|
-
|
|
|
- if dialog.exec_() == QtWidgets.QDialog.Accepted:
|
|
|
- self.target_file_list += dialog.selectedFiles()
|
|
|
- if self.target_file_list:
|
|
|
- self.populate_source_files_table()
|
|
|
- self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list)))
|
|
|
- self.process_files_button.setEnabled(True)
|
|
|
-
|
|
|
- def layout_combobox_changed(self):
|
|
|
- """
|
|
|
- Handles main window layout combobox index change.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.content_stacked_layout.setCurrentIndex(self.switch_layout_combobox.currentIndex())
|
|
|
- self.set_ui_buttons()
|
|
|
-
|
|
|
- def reset_clicked(self):
|
|
|
- """
|
|
|
- Brings the application and all variables back to their initial state.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.reset_all_values()
|
|
|
-
|
|
|
- ############################
|
|
|
- # Slots ####################
|
|
|
- ############################
|
|
|
-
|
|
|
- @Slot(list)
|
|
|
- def drag_and_drop_file_update(self, file_list):
|
|
|
- for file in file_list:
|
|
|
- if os.path.basename(file).split('.')[-1] in self.blessed_file_extensions:
|
|
|
- self.target_file_list.append(file)
|
|
|
- self.drag_and_drop_widget.urls.clear()
|
|
|
- self.populate_source_files_table()
|
|
|
- self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list)))
|
|
|
- self.drag_and_drop_label.setStyleSheet('color: white;')
|
|
|
-
|
|
|
- @Slot(bool)
|
|
|
- def drag_and_drop_over(self, is_over):
|
|
|
- if is_over:
|
|
|
- self.drag_and_drop_label.setStyleSheet('color: rgb(0, 255, 0);')
|
|
|
- else:
|
|
|
- self.drag_and_drop_label.setStyleSheet('color: white;')
|
|
|
-
|
|
|
-
|
|
|
-class MaterialNode(QtWidgets.QWidget):
|
|
|
- def __init__(self, material_info, current_position, parent=None):
|
|
|
- super(MaterialNode, self).__init__(parent)
|
|
|
-
|
|
|
- self.material_name = material_info[0]
|
|
|
- self.material_info = material_info[1]
|
|
|
- self.current_position = current_position
|
|
|
- self.property_settings = {}
|
|
|
-
|
|
|
- self.small_font = QtGui.QFont("Helvetica", 7, QtGui.QFont.Bold)
|
|
|
- self.bold_font = QtGui.QFont("Helvetica", 8, QtGui.QFont.Bold)
|
|
|
- self.main_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.main_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
- self.setLayout(self.main_layout)
|
|
|
-
|
|
|
- self.background_frame = QtWidgets.QFrame(self)
|
|
|
- self.background_frame.setGeometry(0, 0, 5000, 5000)
|
|
|
- self.background_frame.setStyleSheet('background-color:rgb(220, 220, 220);')
|
|
|
-
|
|
|
- # ########################
|
|
|
- # Title Bar
|
|
|
- # ########################
|
|
|
-
|
|
|
- self.title_bar_widget = QtWidgets.QWidget()
|
|
|
- self.title_bar_layout = QtWidgets.QHBoxLayout(self.title_bar_widget)
|
|
|
- self.title_bar_layout.setContentsMargins(10, 0, 10, 0)
|
|
|
- self.title_bar_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.title_bar_frame = QtWidgets.QFrame(self.title_bar_widget)
|
|
|
- self.title_bar_frame.setGeometry(0, 0, 5000, 40)
|
|
|
- self.title_bar_frame.setStyleSheet('background-color:rgb(193,154,255);')
|
|
|
- self.main_layout.addWidget(self.title_bar_widget)
|
|
|
- self.material_name_checkbox = QtWidgets.QCheckBox(self.material_name)
|
|
|
- self.material_name_checkbox.setFixedHeight(35)
|
|
|
- self.material_name_checkbox.setStyleSheet('spacing:10px; color:white')
|
|
|
- self.material_name_checkbox.setFont(self.bold_font)
|
|
|
- self.material_name_checkbox.setChecked(True)
|
|
|
- self.title_bar_layout.addWidget(self.material_name_checkbox)
|
|
|
-
|
|
|
- self.material_file_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.material_file_layout.setAlignment(QtCore.Qt.AlignRight)
|
|
|
- self.source_file = QtWidgets.QLabel(os.path.basename(self.material_info['sourceFile']))
|
|
|
- self.source_file.setStyleSheet('color:white;')
|
|
|
- self.source_file.setFont(self.small_font)
|
|
|
- self.material_file_layout.addWidget(self.source_file)
|
|
|
- self.material_file_layout.addSpacing(10)
|
|
|
-
|
|
|
- self.edit_button = QtWidgets.QPushButton('Edit')
|
|
|
- self.edit_button.clicked.connect(self.edit_button_clicked)
|
|
|
- self.edit_button.setFixedWidth(55)
|
|
|
- self.material_file_layout.addWidget(self.edit_button)
|
|
|
- self.title_bar_layout.addLayout(self.material_file_layout)
|
|
|
-
|
|
|
- self.information_layout = QtWidgets.QHBoxLayout()
|
|
|
- self.information_layout.setContentsMargins(10, 0, 10, 10)
|
|
|
- self.main_layout.addLayout(self.information_layout)
|
|
|
-
|
|
|
- # ########################
|
|
|
- # Details layout
|
|
|
- # ########################
|
|
|
-
|
|
|
- self.details_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.details_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.details_groupbox = QtWidgets.QGroupBox("Details")
|
|
|
- self.details_groupbox.setFixedWidth(200)
|
|
|
- self.details_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
|
|
|
- "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
|
|
|
- "subcontrol-position: top left;}")
|
|
|
- self.details_layout.addSpacing(15)
|
|
|
- self.material_type_label = QtWidgets.QLabel('Material Type')
|
|
|
- self.material_type_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);')
|
|
|
- self.material_type_label.setFixedHeight(25)
|
|
|
- self.material_type_label.setFont(self.bold_font)
|
|
|
- self.details_layout.addWidget(self.material_type_label)
|
|
|
-
|
|
|
- self.material_type_combobox = QtWidgets.QComboBox()
|
|
|
- self.material_type_combobox.setFixedHeight(30)
|
|
|
- self.material_type_combobox.setStyleSheet('QCombobox QAbstractItemView { padding-left: 15px; }')
|
|
|
- material_type_items = [' Standard PBR']
|
|
|
- self.material_type_combobox.addItems(material_type_items)
|
|
|
- self.details_layout.addWidget(self.material_type_combobox)
|
|
|
- self.details_layout.addSpacing(10)
|
|
|
-
|
|
|
- self.description_label = QtWidgets.QLabel('Description')
|
|
|
- self.description_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);')
|
|
|
- self.description_label.setFixedHeight(25)
|
|
|
- self.description_label.setFont(self.bold_font)
|
|
|
- self.details_layout.addWidget(self.description_label)
|
|
|
-
|
|
|
- self.description_box = QtWidgets.QTextEdit('This space is reserved for additional information.')
|
|
|
- self.details_layout.addWidget(self.description_box)
|
|
|
- self.information_layout.addWidget(self.details_groupbox)
|
|
|
- self.details_groupbox.setLayout(self.details_layout)
|
|
|
-
|
|
|
- # ########################
|
|
|
- # Properties layout
|
|
|
- # ########################
|
|
|
-
|
|
|
- self.properties_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.properties_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.properties_groupbox = QtWidgets.QGroupBox("Properties")
|
|
|
- self.properties_groupbox.setFixedWidth(150)
|
|
|
- self.properties_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
|
|
|
- "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
|
|
|
- "subcontrol-position: top left;}")
|
|
|
- self.properties_list_widget = QtWidgets.QListWidget()
|
|
|
- self.material_properties = ['ambientOcclusion', 'baseColor', 'emissive', 'metallic', 'roughness', 'specularF0',
|
|
|
- 'normal', 'opacity']
|
|
|
- self.properties_list_widget.addItems(self.material_properties)
|
|
|
- self.properties_list_widget.itemSelectionChanged.connect(self.property_selection_changed)
|
|
|
- self.properties_layout.addSpacing(15)
|
|
|
- self.properties_layout.addWidget(self.properties_list_widget)
|
|
|
- self.information_layout.addWidget(self.properties_groupbox)
|
|
|
- self.properties_groupbox.setLayout(self.properties_layout)
|
|
|
-
|
|
|
- # ########################
|
|
|
- # Attributes layout
|
|
|
- # ########################
|
|
|
-
|
|
|
- self.attributes_layout = QtWidgets.QVBoxLayout()
|
|
|
- self.attributes_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
|
- self.attributes_groupbox = QtWidgets.QGroupBox("Attributes")
|
|
|
- self.attributes_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; "
|
|
|
- "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); "
|
|
|
- "subcontrol-position: top left;}")
|
|
|
- self.information_layout.addWidget(self.attributes_groupbox)
|
|
|
- self.attributes_layout.addSpacing(15)
|
|
|
- self.attributes_table = QtWidgets.QTableWidget()
|
|
|
- self.attributes_table.setFocusPolicy(QtCore.Qt.NoFocus)
|
|
|
- self.attributes_table.setColumnCount(2)
|
|
|
- self.attributes_table.setAlternatingRowColors(True)
|
|
|
- self.attributes_table.setHorizontalHeaderLabels(['Attribute', 'Value'])
|
|
|
- self.attributes_table.verticalHeader().hide()
|
|
|
- attributes_table_header = self.attributes_table.horizontalHeader()
|
|
|
- attributes_table_header.setStyleSheet('QHeaderView::section {background-color: rgb(220, 220, 220);}')
|
|
|
- attributes_table_header.setDefaultAlignment(QtCore.Qt.AlignLeft)
|
|
|
- attributes_table_header.setContentsMargins(10, 10, 0, 0)
|
|
|
- attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
|
|
- attributes_table_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)
|
|
|
- attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Interactive)
|
|
|
- self.attributes_layout.addWidget(self.attributes_table)
|
|
|
- self.attributes_groupbox.setLayout(self.attributes_layout)
|
|
|
- self.initialize_display_values()
|
|
|
-
|
|
|
- def initialize_display_values(self):
|
|
|
- """
|
|
|
- Initializes all of the widget item information for material based on the DCC application info the class has
|
|
|
- been passed.
|
|
|
- :return:
|
|
|
- """
|
|
|
- for material_property in self.material_properties:
|
|
|
- if material_property in self.material_info.get('properties'):
|
|
|
- self.property_settings[material_property] = self.material_info['properties'].get(material_property)
|
|
|
- current_row = self.material_properties.index(material_property)
|
|
|
- current_item = self.properties_list_widget.takeItem(current_row)
|
|
|
- self.properties_list_widget.insertItem(0, current_item)
|
|
|
- else:
|
|
|
- self.property_settings[material_property] = 'inactive'
|
|
|
- current_row = self.material_properties.index(material_property)
|
|
|
- item = self.properties_list_widget.item(current_row)
|
|
|
- item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEnabled)
|
|
|
- item.setFlags(item.flags() & ~QtCore.Qt.ItemIsSelectable)
|
|
|
-
|
|
|
- self.properties_list_widget.setCurrentRow(0)
|
|
|
- self.set_attributes_table(self.get_selected_property())
|
|
|
-
|
|
|
- def set_attributes_table(self, selected_property):
|
|
|
- """
|
|
|
- Displays the key, value pairs for the item selected in the Properties list widget
|
|
|
- :param selected_property: The item in the Properties list widget that is currently selected. Only active
|
|
|
- values are displayed.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.attributes_table.setRowCount(0)
|
|
|
- row_count = 0
|
|
|
- for key, value in self.property_settings[selected_property].items():
|
|
|
- self.attributes_table.insertRow(row_count)
|
|
|
- key_item = QtWidgets.QTableWidgetItem(key)
|
|
|
- self.attributes_table.setItem(row_count, 0, key_item)
|
|
|
- value_item = QtWidgets.QTableWidgetItem(value)
|
|
|
- self.attributes_table.setItem(row_count, 1, value_item)
|
|
|
- row_count += 1
|
|
|
-
|
|
|
- def get_selected_property(self):
|
|
|
- """
|
|
|
- Convenience function to get current value selected in the Properties list widget.
|
|
|
- :return:
|
|
|
- """
|
|
|
- return self.properties_list_widget.currentItem().text()
|
|
|
-
|
|
|
- def update_model(self):
|
|
|
- """
|
|
|
- Not sure if this will go away, but if desired, I could make attribute values able to be revised after
|
|
|
- materials have been scraped from the DCC materials
|
|
|
- :return:
|
|
|
- """
|
|
|
- pass
|
|
|
-
|
|
|
- def edit_button_clicked(self):
|
|
|
- """
|
|
|
- This is in place in the event that we want to allow material revisions for properties to be made after
|
|
|
- DCC processing step has already been executed. The idea would basically be to surface an editable
|
|
|
- table where values can be added, removed or changed within the final material definition.
|
|
|
- :return:
|
|
|
- """
|
|
|
- logging.debug('Edit button clicked')
|
|
|
-
|
|
|
- def property_selection_changed(self):
|
|
|
- """
|
|
|
- Fired when index of list view selected property selection has changed.
|
|
|
- :return:
|
|
|
- """
|
|
|
- self.set_attributes_table(self.get_selected_property())
|
|
|
-
|
|
|
-
|
|
|
-def is_valid_file(file_name):
|
|
|
- """
|
|
|
- The acts as a clearinghouse for DCC file types supported by the script
|
|
|
- :param file_name: Reads the extension of the filename for filtering
|
|
|
- :return:
|
|
|
- """
|
|
|
- target_extensions = 'ma mb fbx blend max'.split(' ')
|
|
|
- if file_name.split('.')[-1] in target_extensions:
|
|
|
- return True
|
|
|
- return False
|
|
|
-
|
|
|
-
|
|
|
-def launch_material_converter(window_type='standalone', material_type='PBR', target_files=None):
|
|
|
- """
|
|
|
- The setup for this will be revised once this is fully integrated into the DCCsi system. Currently only the
|
|
|
- standalone (default) and command line entry points work as intended.
|
|
|
- :param window_type: The method of access for material conversion (standalone, command_line, maya_native, max_native)
|
|
|
- :param material_type: Type of output material desired for import into Lumberyard. Currently only PBR is supported
|
|
|
- :param target_files: DCC app files to process for converted Lumberyard materials
|
|
|
- :return:
|
|
|
- """
|
|
|
- if window_type == 'command_line':
|
|
|
- MaterialsToLumberyard(material_type, target_files)
|
|
|
- elif window_type == 'maya_native':
|
|
|
- from maya import OpenMayaUI as omui
|
|
|
- main_window_pointer = omui.MQtUtil.mainWindow()
|
|
|
- main_app_window = wrapInstance(long(main_window_pointer), QtWidgets.QWidget)
|
|
|
- MaterialsToLumberyard(material_type, None, main_app_window)
|
|
|
- elif window_type == 'max_native':
|
|
|
- from pymxs import runtime as rt
|
|
|
- main_window_pointer = QtWidgets.QWidget.find(rt.windows.getMAXHWND())
|
|
|
- main_app_window = shiboken2.wrapInstance(shiboken2.getCppPointer(main_window_pointer)[0], QtWidgets.QMainWindow)
|
|
|
- MaterialsToLumberyard(material_type, None, main_app_window)
|
|
|
- else:
|
|
|
- app = QApplication(sys.argv)
|
|
|
- app_ui = MaterialsToLumberyard()
|
|
|
- app_ui.show()
|
|
|
- sys.exit(app.exec_())
|
|
|
-
|
|
|
-
|
|
|
-if __name__ == '__main__':
|
|
|
- launch_material_converter()
|
|
|
-
|