Integrating up through commit 90f050496
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# from maya_mapping import MayaMapping
|
||||
# from max_mapping import MaxMapping
|
||||
# from blender_mapping import BlenderMapping
|
||||
|
||||
# def get_dcc_mapping(app):
|
||||
# app = app.lower()
|
||||
# if app == 'maya':
|
||||
# return MayaMapping()
|
||||
# elif app == '3dsmax':
|
||||
# return MaxMapping()
|
||||
# elif app == 'blender':
|
||||
# return BlenderMapping()
|
||||
# else:
|
||||
# return NullMapping(app)
|
||||
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
Usage
|
||||
=====
|
||||
Put usage instructions here.
|
||||
|
||||
Output
|
||||
======
|
||||
Put output information here.
|
||||
|
||||
Relevant Links:
|
||||
https://pythonhosted.org/an_example_pypi_project/sphinx.html
|
||||
https://github.com/ideasman42/pyfbx_i42
|
||||
https://pypi.org/project/py-fbx/
|
||||
https://www.quora.com/How-do-I-execute-Maya-script-without-lauching-Maya
|
||||
https://stackoverflow.com/questions/27437733/use-external-python-script-to-open-maya-and-run-another-script-inside-maya
|
||||
|
||||
Notes:
|
||||
-- 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
|
||||
|
||||
-- Jonny wants me to use pathlib wherever possible for OO pathing, as well as python-box (aka Box) for dict access
|
||||
|
||||
# Things to do:
|
||||
--> Create the cube demo for Jonny with 4 attached materials
|
||||
--> Create mapping widget for stacked layout
|
||||
--> Allow export of JSON file for demo
|
||||
--> Allow custom field entries for description, etc.
|
||||
--> Need to figure out how to clear pointers for stored data properly
|
||||
"""
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide2.QtCore import Signal, Slot, QThread, QAbstractItemModel, QModelIndex, QObject
|
||||
from maya import OpenMayaUI as omui
|
||||
from maya.standalone import initialize
|
||||
from shiboken2 import wrapInstance
|
||||
import pymel.core as pm
|
||||
# import sphinx
|
||||
# import azpy
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
mayaMainWindowPtr = omui.MQtUtil.mainWindow()
|
||||
mayaMainWindow = wrapInstance(long(mayaMainWindowPtr), QtWidgets.QWidget)
|
||||
|
||||
|
||||
class MayaToLumberyard(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(MayaToLumberyard, self).__init__(parent)
|
||||
|
||||
self.app = QtWidgets.QApplication.instance()
|
||||
self.setParent(mayaMainWindow)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setGeometry(50, 50, 600, 500)
|
||||
self.setObjectName('MaterialsToLumberyard')
|
||||
self.setWindowTitle('Maya To Lumberyard')
|
||||
self.isTopLevel()
|
||||
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint)
|
||||
|
||||
self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
|
||||
self.bold_font_large = QtGui.QFont('Plastique', 7, QtGui.QFont.Bold)
|
||||
self.medium_font = QtGui.QFont('Plastique', 7, QtGui.QFont.Normal)
|
||||
self.target_file_list = []
|
||||
self.materials_dict = {}
|
||||
self.material_definitions = {}
|
||||
self.processed_materials = []
|
||||
self.current_scene = pm.sceneName()
|
||||
self.model = None
|
||||
self.total_transfer_materials = 1
|
||||
|
||||
self.main_container = QtWidgets.QVBoxLayout(self)
|
||||
self.main_container.setAlignment(QtCore.Qt.AlignTop)
|
||||
self.setLayout(self.main_container)
|
||||
|
||||
# Header Bar ------>
|
||||
self.header_bar_layout = QtWidgets.QHBoxLayout()
|
||||
self.select_files_button = QtWidgets.QPushButton('Select Files')
|
||||
self.select_files_button.clicked.connect(self.choose_files_clicked)
|
||||
self.select_files_button.setFixedSize(80, 35)
|
||||
self.header_bar_layout.addWidget(self.select_files_button)
|
||||
self.header_bar_layout.addSpacing(15)
|
||||
self.use_current_file_checkbox = QtWidgets.QCheckBox('Use Current File')
|
||||
self.use_current_file_checkbox.setFont(self.bold_font_large)
|
||||
self.use_current_file_checkbox.clicked.connect(self.use_current_file_clicked)
|
||||
self.header_bar_layout.addWidget(self.use_current_file_checkbox)
|
||||
self.header_bar_layout.addSpacing(100)
|
||||
self.switch_layout_combobox = QtWidgets.QComboBox()
|
||||
self.switch_layout_combobox.setEnabled(False)
|
||||
self.switch_layout_combobox.setFixedSize(250, 30)
|
||||
self.combobox_items = ['Target Files', 'Extracted Values', 'Material Tree']
|
||||
self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}')
|
||||
self.switch_layout_combobox.addItems(self.combobox_items)
|
||||
self.header_bar_layout.addWidget(self.switch_layout_combobox)
|
||||
self.header_bar_layout.addSpacing(4)
|
||||
self.main_container.addSpacing(5)
|
||||
self.main_container.addLayout(self.header_bar_layout)
|
||||
|
||||
# Separation Line ------>
|
||||
self.separatorLayout1 = QtWidgets.QHBoxLayout()
|
||||
self.line1 = QtWidgets.QLabel()
|
||||
self.line1.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
|
||||
self.line1.setLineWidth(1)
|
||||
self.line1.setFixedHeight(10)
|
||||
self.separatorLayout1.addWidget(self.line1)
|
||||
self.main_container.addLayout(self.separatorLayout1)
|
||||
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
# File Source Table / Attributes (Stacked Layout) #
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
self.content_stacked_layout = QtWidgets.QStackedLayout()
|
||||
self.main_container.addLayout(self.content_stacked_layout)
|
||||
self.switch_layout_combobox.currentIndexChanged.connect(self.layout_combobox_changed)
|
||||
|
||||
# --- 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 {padding-top:9px; padding-left:10px;}')
|
||||
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.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||
files_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||
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 {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.material_definitions_widget = QtWidgets.QWidget()
|
||||
self.material_definitions_layout = QtWidgets.QHBoxLayout(self.material_definitions_widget)
|
||||
self.material_definitions_layout.setSpacing(0)
|
||||
self.material_definitions_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.material_definitions_frame = QtWidgets.QFrame(self.material_definitions_widget)
|
||||
self.material_definitions_frame.setGeometry(0, 0, 5000, 5000)
|
||||
self.material_definitions_frame.setStyleSheet('background-color:rgb(150,150,150);')
|
||||
|
||||
self.title_bar_widget = QtWidgets.QWidget()
|
||||
self.title_bar_layout = QtWidgets.QHBoxLayout(self.title_bar_widget)
|
||||
self.title_bar_layout.setContentsMargins(17, 17, 17, 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, 60)
|
||||
self.title_bar_frame.setStyleSheet('background-color:rgb(102,69,153);')
|
||||
self.material_definitions_layout.addWidget(self.title_bar_widget)
|
||||
|
||||
self.material_name = QtWidgets.QCheckBox('StingrayPBS2')
|
||||
self.material_name.setStyleSheet('spacing:10px; color:white')
|
||||
self.material_name.setFont(self.bold_font_large)
|
||||
self.material_name.setChecked(True)
|
||||
self.title_bar_layout.addWidget(self.material_name)
|
||||
|
||||
# Forward/Back Buttons -------------------------->>
|
||||
self.previous_next_button_layout = QtWidgets.QHBoxLayout()
|
||||
self.item_count = QtWidgets.QLabel('1 of 10')
|
||||
self.previous_next_button_layout.addWidget(self.item_count)
|
||||
self.previous_next_button_layout.addSpacing(10)
|
||||
self.previous_next_button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.previous_next_button_layout.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.previous_button = QtWidgets.QToolButton()
|
||||
self.previous_button.setArrowType(QtCore.Qt.LeftArrow)
|
||||
self.previous_button.setStyleSheet('background-color:rgb(110,110,110);')
|
||||
self.previous_button.clicked.connect(self.previous_button_clicked)
|
||||
self.previous_next_button_layout.addWidget(self.previous_button)
|
||||
self.next_button = QtWidgets.QToolButton()
|
||||
self.next_button.setArrowType(QtCore.Qt.RightArrow)
|
||||
self.next_button.setStyleSheet('background-color:rgb(110,110,110);')
|
||||
self.next_button.clicked.connect(self.next_button_clicked)
|
||||
self.previous_next_button_layout.addWidget(self.next_button)
|
||||
self.title_bar_layout.addLayout(self.previous_next_button_layout)
|
||||
self.content_stacked_layout.addWidget(self.material_definitions_widget)
|
||||
|
||||
# File processing buttons ------>
|
||||
self.process_files_layout = QtWidgets.QHBoxLayout()
|
||||
self.main_container.addLayout(self.process_files_layout)
|
||||
self.process_files_button = QtWidgets.QPushButton('Process Listed Files')
|
||||
self.process_files_button.setFixedHeight(50)
|
||||
self.process_files_button.clicked.connect(self.process_files_clicked)
|
||||
self.process_files_layout.addWidget(self.process_files_button)
|
||||
self.reset_button = QtWidgets.QPushButton('Reset')
|
||||
self.reset_button.setFixedSize(50, 50)
|
||||
self.reset_button.clicked.connect(self.reset_clicked)
|
||||
self.reset_button.setEnabled(False)
|
||||
self.process_files_layout.addWidget(self.reset_button)
|
||||
self.initialize_window()
|
||||
|
||||
def initialize_window(self):
|
||||
if self.current_scene:
|
||||
self.target_file_list = [self.current_scene]
|
||||
self.use_current_file_checkbox.setChecked(True)
|
||||
self.populate_files_table()
|
||||
|
||||
def populate_files_table(self):
|
||||
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.setStyleSheet('border-width:0px; background-color:rgb(100,100,100);')
|
||||
remove_button.clicked.connect(self.remove_file_clicked)
|
||||
self.target_files_table.setItem(index, 0, item)
|
||||
self.target_files_table.setCellWidget(index, 1, remove_button)
|
||||
|
||||
def process_file_list(self):
|
||||
file_processing_errors = []
|
||||
for maya_file_location in self.target_file_list:
|
||||
try:
|
||||
if maya_file_location != self.current_scene:
|
||||
pm.openFile(maya_file_location, force=True)
|
||||
self.current_scene = maya_file_location
|
||||
self.get_scene_materials_description()
|
||||
except Exception as e:
|
||||
file_processing_errors.append([maya_file_location, e])
|
||||
|
||||
# Create Model with extracted values from file list
|
||||
self.set_material_model()
|
||||
|
||||
# Setup Lumberyard Material File Values
|
||||
self.map_materials()
|
||||
print ('MaterialDefinitions:'.format(self.material_definitions))
|
||||
print json.dumps(self.material_definitions, sort_keys=True, indent=4)
|
||||
|
||||
# Update UI Layout
|
||||
self.set_material_view()
|
||||
self.switch_layout_combobox.setCurrentIndex(2)
|
||||
self.set_ui_buttons()
|
||||
|
||||
def map_materials(self):
|
||||
root = self.model.rootItem
|
||||
for row in range(self.model.rowCount()):
|
||||
name = self.model.get_attribute_value('MaterialName', root.child(row))
|
||||
material_type = self.model.get_attribute_value('MaterialType', 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_pbr_material_description(name, material_type, file_connections)
|
||||
|
||||
def reset_all_values(self):
|
||||
pass
|
||||
# Need to figure out how to clear pointers for stored data properly
|
||||
# self.target_files_table.setRowCount(0)
|
||||
# self.model.beginResetModel()
|
||||
# self.model.qDeleteAll(mResults)
|
||||
# self.model.endResetModel()
|
||||
# self.initialize_window()
|
||||
|
||||
############################
|
||||
# Getters/Setters ##########
|
||||
############################
|
||||
|
||||
@staticmethod
|
||||
def get_materials(target_mesh):
|
||||
shading_group = pm.listConnections(pm.PyNode(target_mesh), type='shadingEngine')
|
||||
materials = pm.ls(pm.listConnections(shading_group), materials=1)
|
||||
return list(set(materials))
|
||||
|
||||
@staticmethod
|
||||
def get_shader(material_name):
|
||||
connections = pm.listConnections(material_name, type='shadingEngine')[0]
|
||||
shader_name = '{}.surfaceShader'.format(connections)
|
||||
shader = pm.listConnections(shader_name)[0]
|
||||
return shader
|
||||
|
||||
@staticmethod
|
||||
def get_shader_information(shader):
|
||||
shader_file_connections = {}
|
||||
for node in pm.listConnections(shader, type='file', c=True):
|
||||
shader_file_connections[str(node[0])] = str(pm.getAttr(node[1].fileTextureName))
|
||||
shader_attributes = {}
|
||||
for shader_attribute in pm.listAttr(shader, s=True, iu=True):
|
||||
try:
|
||||
shader_attributes[str(shader_attribute)] = pm.getAttr('{}.{}'.format(shader, shader_attribute))
|
||||
except pm.MayaAttributeError as e:
|
||||
print ('MayaAttributeError: {}'.format(e))
|
||||
|
||||
return shader_file_connections, shader_attributes
|
||||
|
||||
@staticmethod
|
||||
def get_shader_properties(name, material_type, file_connections):
|
||||
""" This system will probably need rethinking if DCCs and compatible materials grow """
|
||||
attr_list = {}
|
||||
if material_type == 'StingrayPBS':
|
||||
naming_exceptions = {'color': 'baseColor', 'ao': 'ambientOcclusion'}
|
||||
maps = 'color, metallic, roughness, normal, emissive, ao, opacity'.split(', ')
|
||||
for m in maps:
|
||||
texture_attribute = 'TEX_{}_map'.format(m)
|
||||
for tex in file_connections.keys():
|
||||
if tex.find(texture_attribute) != -1:
|
||||
key = m if m not in naming_exceptions else naming_exceptions.get(m)
|
||||
attr_list[key] = {'useTexture': 'true',
|
||||
'textureMap': file_connections.get('{}.{}'.format(name, texture_attribute))}
|
||||
return attr_list
|
||||
|
||||
@staticmethod
|
||||
def get_increment(name):
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_material_template(shader_type):
|
||||
definitions = os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.material'.format(shader_type))
|
||||
if os.path.exists(definitions):
|
||||
with open(definitions) as f:
|
||||
return json.load(f)
|
||||
|
||||
def get_scene_materials_description(self):
|
||||
scene_geo = pm.ls(v=True, geometry=True)
|
||||
for target_mesh in scene_geo:
|
||||
material_list = self.get_materials(target_mesh)
|
||||
for material_name in material_list:
|
||||
material_type = pm.nodeType(material_name, api=True)
|
||||
material_listed = [x for x in self.materials_dict if self.materials_dict[x]['MaterialName'] == material_name]
|
||||
|
||||
if not material_listed:
|
||||
self.set_material_dict(str(material_name), str(material_type), target_mesh)
|
||||
else:
|
||||
mesh_list = self.materials_dict[material_name].get('AppliedMesh')
|
||||
if not isinstance(mesh_list, list):
|
||||
self.materials_dict[material_name]['AppliedMesh'] = [mesh_list, target_mesh]
|
||||
else:
|
||||
mesh_list.append(target_mesh)
|
||||
|
||||
def set_material_dict(self, material_name, material_type, material_mesh):
|
||||
shader = self.get_shader(material_name)
|
||||
shader_file_connections, shader_attributes = self.get_shader_information(shader)
|
||||
material_dict = {'MaterialName': material_name, 'MaterialType': material_type, 'AppliedMesh': material_mesh,
|
||||
'FileConnections': shader_file_connections, 'SceneName': str(self.current_scene),
|
||||
'MaterialAttributes': shader_attributes}
|
||||
material_name = 'Material_{}'.format(self.total_transfer_materials)
|
||||
self.materials_dict[material_name] = material_dict
|
||||
self.total_transfer_materials += 1
|
||||
|
||||
def set_material_model(self):
|
||||
self.model = MaterialsModel(self.headers, self.materials_dict)
|
||||
|
||||
def set_material_view(self):
|
||||
self.material_tree_view.setModel(self.model)
|
||||
self.material_tree_view.expandAll()
|
||||
self.material_tree_view.resizeColumnToContents(0)
|
||||
|
||||
def set_pbr_material_description(self, name, material_type, file_connections):
|
||||
# Build dictionary for material description based on extracted values
|
||||
default_settings = self.get_material_template('pbr')
|
||||
material = {'description': name,
|
||||
'materialType': default_settings.get('materialType'),
|
||||
'parentMaterial': default_settings.get('parentMaterial'),
|
||||
'propertyLayoutVersion': default_settings.get('propertyLayoutVersion'),
|
||||
'properties': self.get_shader_properties(name, material_type, file_connections)}
|
||||
self.material_definitions[name if name not in self.material_definitions.keys() else self.get_increment(name)] = material
|
||||
|
||||
def set_ui_buttons(self):
|
||||
display_index = self.content_stacked_layout.currentIndex()
|
||||
self.switch_layout_combobox.setEnabled(True)
|
||||
# Target Files
|
||||
if display_index == 0:
|
||||
self.use_current_file_checkbox.setEnabled(True)
|
||||
self.select_files_button.setEnabled(True)
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setText('Process Listed Files')
|
||||
|
||||
# Extracted Values
|
||||
elif display_index == 1:
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setEnabled(False)
|
||||
self.use_current_file_checkbox.setEnabled(False)
|
||||
self.select_files_button.setEnabled(False)
|
||||
|
||||
# Material Tree
|
||||
else:
|
||||
self.use_current_file_checkbox.setEnabled(False)
|
||||
self.select_files_button.setEnabled(False)
|
||||
self.process_files_button.setText('Export Selected Materials')
|
||||
if self.material_definitions:
|
||||
self.process_files_button.setEnabled(True)
|
||||
|
||||
############################
|
||||
# Button Actions ###########
|
||||
############################
|
||||
|
||||
def use_current_file_clicked(self):
|
||||
self.current_scene = pm.sceneName()
|
||||
if self.use_current_file_checkbox.isChecked():
|
||||
self.target_file_list.insert(0, self.current_scene)
|
||||
self.target_file_list = list(set(self.target_file_list))
|
||||
else:
|
||||
if self.current_scene in self.target_file_list:
|
||||
del self.target_file_list[self.target_file_list.index(self.current_scene)]
|
||||
self.populate_files_table()
|
||||
|
||||
def remove_file_clicked(self):
|
||||
file_index = self.target_files_table.indexAt(self.sender().pos())
|
||||
target_file = self.target_file_list[file_index.row()]
|
||||
if target_file == pm.sceneName():
|
||||
self.use_current_file_checkbox.setChecked(False)
|
||||
del self.target_file_list[file_index.row()]
|
||||
self.populate_files_table()
|
||||
|
||||
def process_files_clicked(self):
|
||||
self.process_file_list()
|
||||
|
||||
def choose_files_clicked(self):
|
||||
dialog = QtWidgets.QFileDialog(self, 'Shift-Select Target Files', self.desktop_location)
|
||||
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
dialog.setNameFilter('Maya Files (*.ma *.mb *.fbx)')
|
||||
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_files_table()
|
||||
self.process_files_button.setEnabled(True)
|
||||
|
||||
def layout_combobox_changed(self):
|
||||
self.content_stacked_layout.setCurrentIndex(self.switch_layout_combobox.currentIndex())
|
||||
self.set_ui_buttons()
|
||||
|
||||
def reset_clicked(self):
|
||||
self.reset_all_values()
|
||||
|
||||
def previous_button_clicked(self):
|
||||
print ('Previous button clicked')
|
||||
|
||||
def next_button_clicked(self):
|
||||
print ('Next button clicked')
|
||||
|
||||
|
||||
class MaterialsModel(QAbstractItemModel):
|
||||
def __init__(self, headers, data, parent=None):
|
||||
super(MaterialsModel, self).__init__(parent)
|
||||
|
||||
self.rootItem = TreeNode(headers)
|
||||
self.parents = [self.rootItem]
|
||||
self.indentations = [0]
|
||||
self.create_data(data)
|
||||
|
||||
def create_data(self, data, indent=-1):
|
||||
if type(data) == dict:
|
||||
indent += 1
|
||||
position = 4 * indent
|
||||
for key, value in data.iteritems():
|
||||
if position > self.indentations[-1]:
|
||||
if self.parents[-1].childCount() > 0:
|
||||
self.parents.append(self.parents[-1].child(self.parents[-1].childCount() - 1))
|
||||
self.indentations.append(position)
|
||||
else:
|
||||
while position < self.indentations[-1] and len(self.parents) > 0:
|
||||
self.parents.pop()
|
||||
self.indentations.pop()
|
||||
parent = self.parents[-1]
|
||||
parent.insertChildren(parent.childCount(), 1, parent.columnCount())
|
||||
parent.child(parent.childCount() - 1).setData(0, key)
|
||||
value_string = str(value) if type(value) != dict else str('')
|
||||
parent.child(parent.childCount() - 1).setData(1, value_string)
|
||||
try:
|
||||
self.create_data(value, indent)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attribute_value(search_string, search_column):
|
||||
for childIndex in range(search_column.childCount()):
|
||||
child_item = search_column.child(childIndex)
|
||||
child_value = child_item.itemData
|
||||
if child_value[0] == search_string:
|
||||
return child_value[1]
|
||||
return None
|
||||
|
||||
def index(self, row, column, index=QModelIndex()):
|
||||
""" Returns the index of the item in the model specified by the given row, column and parent index """
|
||||
if not self.hasIndex(row, column, index):
|
||||
return QModelIndex()
|
||||
if not index.isValid():
|
||||
item = self.rootItem
|
||||
else:
|
||||
item = index.internalPointer()
|
||||
|
||||
child = item.child(row)
|
||||
if child:
|
||||
return self.createIndex(row, column, child)
|
||||
return QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
"""
|
||||
Returns the parent of the model item with the given index If the item has no parent,
|
||||
an invalid QModelIndex is returned
|
||||
"""
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
item = index.internalPointer()
|
||||
if not item:
|
||||
return QModelIndex()
|
||||
|
||||
parent = item.parentItem
|
||||
if parent == self.rootItem:
|
||||
return QModelIndex()
|
||||
else:
|
||||
return self.createIndex(parent.childNumber(), 0, parent)
|
||||
|
||||
def rowCount(self, index=QModelIndex()):
|
||||
"""
|
||||
Returns the number of rows under the given parent. When the parent is valid it means that
|
||||
rowCount is returning the number of children of parent
|
||||
"""
|
||||
if index.isValid():
|
||||
parent = index.internalPointer()
|
||||
else:
|
||||
parent = self.rootItem
|
||||
return parent.childCount()
|
||||
|
||||
def columnCount(self, index=QModelIndex()):
|
||||
""" Returns the number of columns for the children of the given parent """
|
||||
return self.rootItem.columnCount()
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
""" Returns the data stored under the given role for the item referred to by the index """
|
||||
if index.isValid() and role == QtCore.Qt.DisplayRole:
|
||||
return index.internalPointer().data(index.column())
|
||||
elif not index.isValid():
|
||||
return self.rootItem.data(index.column())
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
""" Returns the data for the given role and section in the header with the specified orientation """
|
||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||
return self.rootItem.data(section)
|
||||
|
||||
|
||||
class TreeNode(object):
|
||||
def __init__(self, data, parent=None):
|
||||
self.parentItem = parent
|
||||
self.itemData = data
|
||||
self.children = []
|
||||
|
||||
def child(self, row):
|
||||
return self.children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self.children)
|
||||
|
||||
def childNumber(self):
|
||||
if self.parentItem is not None:
|
||||
return self.parentItem.children.index(self)
|
||||
|
||||
def columnCount(self):
|
||||
return len(self.itemData)
|
||||
|
||||
def data(self, column):
|
||||
return self.itemData[column]
|
||||
|
||||
def insertChildren(self, position, count, columns):
|
||||
if position < 0 or position > len(self.children):
|
||||
return False
|
||||
for row in range(count):
|
||||
data = [v for v in range(columns)]
|
||||
item = TreeNode(data, self)
|
||||
self.children.insert(position, item)
|
||||
|
||||
def parent(self):
|
||||
return self.parentItem
|
||||
|
||||
def setData(self, column, value):
|
||||
if column < 0 or column >= len(self.itemData):
|
||||
return False
|
||||
self.itemData[column] = value
|
||||
|
||||
|
||||
def delete_instances():
|
||||
for obj in mayaMainWindow.children():
|
||||
if str(type(obj)) == "<class 'DCC_Materials.maya_materials_export.MayaToLumberyard'>":
|
||||
if obj.__class__.__name__ == "MayaToLumberyard":
|
||||
obj.setParent(None)
|
||||
obj.deleteLater()
|
||||
|
||||
|
||||
def show_ui():
|
||||
delete_instances()
|
||||
ui = MayaToLumberyard(mayaMainWindow)
|
||||
ui.show()
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"ambientOcclusion": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"emissive": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"intensity": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"metallic": {
|
||||
"factor": 0.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"roughness": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif"
|
||||
},
|
||||
"specularF0": {
|
||||
"factor": 0.5,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"normal": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif"
|
||||
},
|
||||
"opacity": {
|
||||
"doubleSided": false,
|
||||
"factor": 1.0,
|
||||
"cutoutAlpha": false,
|
||||
"cutoutThreshold": 0.5,
|
||||
"useBaseColorTextureAlpha": false,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import pymel.core as pmc
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
def syspath():
|
||||
print 'sys.path:'
|
||||
for p in sys.path:
|
||||
print ' ' + p
|
||||
|
||||
|
||||
def info(obj):
|
||||
"""Prints information about the object."""
|
||||
|
||||
lines = ['Info for %s' % obj.name(),
|
||||
'Attributes:']
|
||||
# Get the name of all attributes
|
||||
for a in obj.listAttr():
|
||||
lines.append(' ' + a.name())
|
||||
lines.append('MEL type: %s' % obj.type())
|
||||
lines.append('MRO:')
|
||||
lines.extend([' ' + t.__name__ for t in type(obj).__mro__])
|
||||
result = '\n'.join(lines)
|
||||
print result
|
||||
|
||||
|
||||
def _is_pymel(obj):
|
||||
try: # (1)
|
||||
module = obj.__module__ # (2)
|
||||
except AttributeError: # (3)
|
||||
try:
|
||||
module = obj.__name__ # (4)
|
||||
except AttributeError:
|
||||
return None # (5)
|
||||
return module.startswith('pymel') # (6)
|
||||
|
||||
|
||||
def _py_to_helpstr(obj):
|
||||
if isinstance(obj, basestring):
|
||||
return 'search.html?q=%s' % (obj.replace(' ', '+'))
|
||||
if not _is_pymel(obj):
|
||||
return None
|
||||
if isinstance(obj, types.ModuleType):
|
||||
return ('generated/%(module)s.html#module-%(module)s' %
|
||||
dict(module=obj.__name__))
|
||||
if isinstance(obj, types.MethodType):
|
||||
return ('generated/classes/%(module)s/'
|
||||
'%(module)s.%(typename)s.html'
|
||||
'#%(module)s.%(typename)s.%(methname)s' % dict(
|
||||
module=obj.__module__,
|
||||
typename=obj.im_class.__name__,
|
||||
methname=obj.__name__))
|
||||
if isinstance(obj, types.FunctionType):
|
||||
return ('generated/functions/%(module)s/'
|
||||
'%(module)s.%(funcname)s.html'
|
||||
'#%(module)s.%(funcname)s' % dict(
|
||||
module=obj.__module__,
|
||||
funcname=obj.__name__))
|
||||
if not isinstance(obj, type):
|
||||
obj = type(obj)
|
||||
return ('generated/classes/%(module)s/'
|
||||
'%(module)s.%(typename)s.html'
|
||||
'#%(module)s.%(typename)s' % dict(
|
||||
module=obj.__module__,
|
||||
typename=obj.__name__))
|
||||
|
||||
|
||||
def test_py_to_helpstr():
|
||||
def dotest(obj, ideal):
|
||||
result = _py_to_helpstr(obj)
|
||||
assert result == ideal, '%s != %s' % (result, ideal)
|
||||
dotest('maya rocks', 'search.html?q=maya+rocks')
|
||||
dotest(pmc.nodetypes,
|
||||
'generated/pymel.core.nodetypes.html'
|
||||
'#module-pymel.core.nodetypes')
|
||||
dotest(pmc.nodetypes.Joint,
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint')
|
||||
dotest(pmc.nodetypes.Joint(),
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint')
|
||||
dotest(pmc.nodetypes.Joint().getTranslation,
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint.getTranslation')
|
||||
dotest(pmc.joint,
|
||||
'generated/functions/pymel.core.animation/'
|
||||
'pymel.core.animation.joint.html'
|
||||
'#pymel.core.animation.joint')
|
||||
dotest(object(), None)
|
||||
dotest(10, None)
|
||||
dotest([], None)
|
||||
dotest(sys, None)
|
||||
|
||||
|
||||
def test_py_to_helpstrFAIL():
|
||||
assert 1 == 2, '1 != 2'
|
||||
|
||||
|
||||
import webbrowser # (1)
|
||||
HELP_ROOT_URL = ('http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/PyMel/')# (2)
|
||||
|
||||
|
||||
def pmhelp(obj): # (3)
|
||||
"""Gives help for a pymel or python object.
|
||||
|
||||
If obj is not a PyMEL object, use Python's built-in
|
||||
`help` function.
|
||||
If obj is a string, open a web browser to a search in the
|
||||
PyMEL help for the string.
|
||||
Otherwise, open a web browser to the page for the object.
|
||||
"""
|
||||
tail = _py_to_helpstr(obj)
|
||||
if tail is None:
|
||||
help(obj) # (4)
|
||||
else:
|
||||
webbrowser.open(HELP_ROOT_URL + tail) # (5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_py_to_helpstr()
|
||||
print 'Tests ran successfully.'
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""This module creates a DccScriptingInterface menu in Lumberyard.
|
||||
This module is designed to only run in Lumberyard and py3.7+"""
|
||||
# -- Standard Python modules
|
||||
import sys
|
||||
import os
|
||||
import importlib.util
|
||||
|
||||
# -- External Python modules
|
||||
from pathlib import Path
|
||||
|
||||
# -- DCCsi Extension Modules
|
||||
import azpy
|
||||
|
||||
# Lumberyard extension modules
|
||||
import azlmbr
|
||||
import azlmbr.bus
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# some basic DCCsi module setup, DCCsi bootstrap already performed some
|
||||
from dynaconf import settings
|
||||
settings.setenv()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
_log_level = int(settings.DCCSI_LOGLEVEL)
|
||||
if settings.DCCSI_GDEBUG:
|
||||
_log_level = int(10) # force debug level
|
||||
|
||||
_MODULENAME = r'DCCsi.SDK.Lumberyard.Scripts.set_menu'
|
||||
|
||||
_LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=_log_level)
|
||||
_LOGGER.debug(f'Invoking:: {0}.'.format({_MODULENAME}))
|
||||
|
||||
# early attach WingIDE debugger (can refactor to include other IDEs later)
|
||||
if settings.DCCSI_DEV_MODE:
|
||||
from azpy.env_bool import env_bool
|
||||
if not env_bool('DCCSI_DEBUGGER_ATTACHED', False):
|
||||
# if not already attached lets do it here
|
||||
from azpy.test.entry_test import connect_wing
|
||||
foo = connect_wing()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# validate pyside before continuing
|
||||
try:
|
||||
azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'IsActive')
|
||||
params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, 'GetQtBootstrapParameters')
|
||||
params is not None and params.mainWindowId is not 0
|
||||
from PySide2 import QtWidgets
|
||||
except Exception as e:
|
||||
_LOGGER.error(f'Pyside not available, exception: {e}')
|
||||
raise e
|
||||
|
||||
# keep going, import the other PySide2 bits we will use
|
||||
from PySide2 import QtGui
|
||||
from PySide2.QtCore import Slot
|
||||
from shiboken2 import wrapInstance, getCppPointer
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
def get_ly_mainwindow():
|
||||
widget_main_window = QtWidgets.QWidget.find(params.mainWindowId)
|
||||
widget_main_window = wrapInstance(int(getCppPointer(widget_main_window)[0]), QtWidgets.QMainWindow)
|
||||
return widget_main_window
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
class SampleUI(QtWidgets.QDialog):
|
||||
"""Lightweight UI Test Class created a button"""
|
||||
def __init__(self, parent, title='Not Set'):
|
||||
super(SampleUI, self).__init__(parent)
|
||||
self.setWindowTitle(title)
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
mainLayout = QtWidgets.QHBoxLayout()
|
||||
testBtn = QtWidgets.QPushButton("I am just a Button man!")
|
||||
mainLayout.addWidget(testBtn)
|
||||
self.setLayout(mainLayout)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
_tag_str = 'DccScriptingInterface'
|
||||
_LOGGER.info(f'Creating {_tag_str} Menu')
|
||||
# Lumberyards main window
|
||||
widget_main_window = get_ly_mainwindow()
|
||||
|
||||
# create our own menuBar
|
||||
dccsi_menu = widget_main_window.menuBar().addMenu(f"&{_tag_str}")
|
||||
|
||||
# nest a menu for util/tool launching
|
||||
dccsi_launch_menu = dccsi_menu.addMenu("Launch Utility")
|
||||
|
||||
# (1) add the first utility: Substance Builder
|
||||
# to do, make adding this a data-driven dynaconf option configured per-project?
|
||||
action_launch_sub_builder = dccsi_launch_menu.addAction("Substance Builder")
|
||||
|
||||
@Slot()
|
||||
def clicked_launch_sub_builder():
|
||||
# breadcrumbs
|
||||
if settings.DCCSI_GDEBUG:
|
||||
debug_msg = "Clicked action_launch_sub_builder"
|
||||
print(debug_msg)
|
||||
_LOGGER.debug(debug_msg)
|
||||
|
||||
_SUB_BUILDER_PATH = Path(settings.DCCSIG_PATH,
|
||||
'SDK',
|
||||
'Substance',
|
||||
'builder')
|
||||
_SUB_BUILDER_BOOTSTRAP = Path(_SUB_BUILDER_PATH, 'bootstrap.py')
|
||||
|
||||
# bootstrap is a generic name and we have many of them
|
||||
# this ensures we are loading a specific one
|
||||
_spec_bootstrap = importlib.util.spec_from_file_location("dccsi.sdk.substance.builder.bootstrap",
|
||||
_SUB_BUILDER_BOOTSTRAP)
|
||||
_app_bootstrap = importlib.util.module_from_spec(_spec_bootstrap)
|
||||
_spec_bootstrap.loader.exec_module(_app_bootstrap)
|
||||
|
||||
while 1: # simple PySide2 test, set to 0 to disable
|
||||
ui = SampleUI(parent=widget_main_window, title='Atom: Substance Builder')
|
||||
ui.show()
|
||||
break
|
||||
return
|
||||
|
||||
# Add click event to menu bar
|
||||
action_launch_sub_builder.triggered.connect(clicked_launch_sub_builder)
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# add launching the dcc material converter util to the menu
|
||||
# to do, make adding this a data-driven dynaconf option?
|
||||
action_launch_dcc_mat_converter = dccsi_launch_menu.addAction("DCC Material Converter")
|
||||
|
||||
@Slot()
|
||||
def clicked_launch_dcc_mat_converter():
|
||||
if settings.DCCSI_GDEBUG:
|
||||
debug_msg = "Clicked action_launch_dcc_mat_converter"
|
||||
print(debug_msg)
|
||||
_LOGGER.debug(debug_msg)
|
||||
|
||||
while 1: # simple PySide2 test, set to 0 to disable
|
||||
ui = SampleUI(parent=widget_main_window, title='Atom: DCC Material Converter')
|
||||
ui.show()
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
# Add click event to menu bar
|
||||
action_launch_dcc_mat_converter.triggered.connect(clicked_launch_dcc_mat_converter)
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:646b6d93b2c672bbbbcb46af1bfcaf26ca37c8a0c2b218989b145417bb6b7c93
|
||||
size 262272
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:86209db0389b152c709d529e9d5705219b44bfbf712a26788a5c2ab0adc0c373
|
||||
size 98452
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bd95898a04b81b80b095dbef34523f3b70a8c14bc9f82116f732ab648f25658b
|
||||
size 2096788
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a85a15d5c60102f414b4ad03604dbd1c78e3d1c1fe445f60db158b2c069f792d
|
||||
size 25165972
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
# from maya_mapping import MayaMapping
|
||||
# from max_mapping import MaxMapping
|
||||
# from blender_mapping import BlenderMapping
|
||||
|
||||
# def get_dcc_mapping(app):
|
||||
# app = app.lower()
|
||||
# if app == 'maya':
|
||||
# return MayaMapping()
|
||||
# elif app == '3dsmax':
|
||||
# return MaxMapping()
|
||||
# elif app == 'blender':
|
||||
# return BlenderMapping()
|
||||
# else:
|
||||
# return NullMapping(app)
|
||||
|
||||
__all__ = ['blender_materials',
|
||||
'dcc_material_mapping',
|
||||
'drag_and_drop',
|
||||
'main',
|
||||
'materials_export',
|
||||
'max_materials',
|
||||
'maya_materials',
|
||||
'model']
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import bpy
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
class BlenderMaterials(object):
|
||||
def __init__(self, files_list, materials_count):
|
||||
super(BlenderMaterials, self).__init__()
|
||||
|
||||
self.files_list = files_list
|
||||
self.current_scene = None
|
||||
self.materials_dictionary = {}
|
||||
self.materials_count = materials_count
|
||||
self.get_material_information()
|
||||
|
||||
def get_material_information(self):
|
||||
"""
|
||||
Main entry point for the material information extraction. Because this class is run
|
||||
in Standalone mode as a subprocess, the list is passed as a string- some parsing/measures
|
||||
need to be taken in order to separate values that originated as a list before passed.
|
||||
|
||||
:return: A dictionary of all of the materials gathered. Sent back to main UI through stdout
|
||||
"""
|
||||
for file in file_list.split(','):
|
||||
self.current_scene = file.replace('\'', '')
|
||||
print('File in BlenderMaterials to be processed: {}'.format(self.current_scene))
|
||||
|
||||
# pm.openFile(self.current_scene, force=True)
|
||||
# self.set_material_descriptions()
|
||||
# json.dump(self.materials_dictionary, sys.stdout)
|
||||
|
||||
@staticmethod
|
||||
def get_materials(target_mesh):
|
||||
"""
|
||||
Gathers a list of all materials attached to each mesh's shader
|
||||
|
||||
:param target_mesh: The target mesh to pull attached material information from.
|
||||
:return: List of unique material values attached to the mesh passed as an argument.
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_shader(material_name):
|
||||
"""
|
||||
Convenience function for obtaining the shader that the specified material (as an argument)
|
||||
is attached to.
|
||||
|
||||
:param material_name: Takes the material name as an argument to get associated shader object
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_shader_information(shader):
|
||||
"""
|
||||
Helper function for extracting shader/material attributes used to form the DCC specific dictionary
|
||||
of found material values for conversion.
|
||||
|
||||
:param shader: The target shader object to analyze
|
||||
:return: Complete set (in the form of two dictionaries) of file connections and material attribute values
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_material_dictionary(self, material_name, material_type, material_mesh):
|
||||
"""
|
||||
When a unique material has been found, this creates a dictionary entry with all relevant material values. This
|
||||
includes material attributes as well as attached file textures. Later in the process this information is
|
||||
leveraged when creating the Lumberyard material definition.
|
||||
|
||||
:param material_name: The name attached to the material
|
||||
:param material_type: Specific type of material (Arnold, Stingray, etc.)
|
||||
:param material_mesh: Mesh that the material is applied to
|
||||
:return:
|
||||
"""
|
||||
self.materials_count += 1
|
||||
pass
|
||||
# shader = self.get_shader(material_name)
|
||||
# shader_file_connections, shader_attributes = self.get_shader_information(shader)
|
||||
# material_dictionary = {'MaterialName': material_name, 'MaterialType': material_type, 'DccApplication': 'Maya',
|
||||
# 'AppliedMesh': material_mesh, 'FileConnections': shader_file_connections,
|
||||
# 'SceneName': str(self.current_scene), 'MaterialAttributes': shader_attributes}
|
||||
# material_name = 'Material_{}'.format(self.materials_count)
|
||||
# self.materials_dictionary[material_name] = material_dictionary
|
||||
|
||||
def set_material_descriptions(self):
|
||||
"""
|
||||
This function serves as the clearinghouse for all analyzed materials passing through the system.
|
||||
It will determine whether or not the found material has already been processed, or if it needs to
|
||||
be added to the final material dictionary. In the event that an encountered material has already
|
||||
been processed, this function creates a register of all meshes it is applied to in the 'AppliedMesh'
|
||||
attribute.
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
# scene_geo = pm.ls(v=True, geometry=True)
|
||||
# for target_mesh in scene_geo:
|
||||
# material_list = self.get_materials(target_mesh)
|
||||
# for material_name in material_list:
|
||||
# material_type = pm.nodeType(material_name, api=True)
|
||||
# material_listed = [x for x in self.materials_dictionary
|
||||
# if self.materials_dictionary[x]['MaterialName'] == material_name]
|
||||
#
|
||||
# if not material_listed:
|
||||
# self.set_material_dictionary(str(material_name), str(material_type), str(target_mesh))
|
||||
# else:
|
||||
# mesh_list = self.materials_dictionary[material_name].get('AppliedMesh')
|
||||
# if not isinstance(mesh_list, list):
|
||||
# self.materials_dictionary[material_name]['AppliedMesh'] = [mesh_list, target_mesh]
|
||||
# else:
|
||||
# mesh_list.append(target_mesh)
|
||||
|
||||
# def get_material_information():
|
||||
# material_attributes = {}
|
||||
# for target_object in bpy.data.objects:
|
||||
# if target_object.type == 'MESH':
|
||||
# print('Object: {} Material: {}'.format(target_object, target_object.active_material))
|
||||
# assigned_material = target_object.active_material
|
||||
# material_nodes = assigned_material.node_tree.nodes
|
||||
#
|
||||
# for node in material_nodes:
|
||||
# for material_input in node.inputs:
|
||||
# try:
|
||||
# attribute_name = material_input.name
|
||||
# attribute_value = node.inputs[attribute_name].default_value
|
||||
# material_attributes[attribute_name] = str(attribute_value)
|
||||
# except Exception as e:
|
||||
# print ('[{}] Exception encountered: {}'.format(material_input.name, e))
|
||||
#
|
||||
# print(json.dumps(material_attributes, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
|
||||
|
||||
# get_material_information()
|
||||
|
||||
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
# Maya Specific Shader Mapping #
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(len(sys.argv))
|
||||
print(sys.argv[5])
|
||||
# arg_values = sys.argv[5].split(',')
|
||||
# scene_information = []
|
||||
# for value in arg_values:
|
||||
# print(value)
|
||||
# scene_information.append(value)
|
||||
# initialize_scene(scene_information)
|
||||
# instance = BlenderMaterials(file_list, total_material_count)
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
|
||||
def get_maya_material_mapping(name, material_type, file_connections):
|
||||
material_properties = {}
|
||||
if material_type == 'StingrayPBS':
|
||||
print('Mapping StingrayPBS')
|
||||
maps = 'color, metallic, roughness, normal, emissive, ao, opacity'.split(', ')
|
||||
naming_exceptions = {'color': 'baseColor', 'ao': 'ambientOcclusion'}
|
||||
for m in maps:
|
||||
texture_attribute = 'TEX_{}_map'.format(m)
|
||||
for tex in file_connections.keys():
|
||||
if tex.find(texture_attribute) != -1:
|
||||
key = m if m not in naming_exceptions else naming_exceptions.get(m)
|
||||
logging.debug('Key, Value: {} {}.{}'.format(key, name, texture_attribute))
|
||||
material_properties[key] = {'useTexture': 'true',
|
||||
'textureMap': file_connections.get(
|
||||
'{}.{}'.format(name, texture_attribute))}
|
||||
elif material_type == 'aiStandardSurface':
|
||||
print('Mapping AiStandardSurface')
|
||||
# TODO- Occlusion is based on a more difficult setup- there is no standard channel. Set this up as time permits
|
||||
maps = 'baseColor, metalness, specularRoughness, normal, emissionColor, opacity'.split(', ')
|
||||
naming_exceptions = {'metalness': 'metallic', 'specularRoughness': 'roughness', 'emissionColor': 'emissive'}
|
||||
for m in maps:
|
||||
key = m if m not in naming_exceptions.keys() else naming_exceptions.get(m)
|
||||
texture_attribute = m
|
||||
for tex in file_connections.keys():
|
||||
if tex.find(texture_attribute) != -1:
|
||||
logging.debug('Key, Value: {} {}.{}'.format(key, name, texture_attribute))
|
||||
material_properties[key] = {'useTexture': 'true',
|
||||
'textureMap': file_connections.get(
|
||||
'{}.{}'.format(name, texture_attribute))}
|
||||
else:
|
||||
pass
|
||||
|
||||
return material_properties
|
||||
|
||||
|
||||
def get_blender_material_mapping(name, material_type, file_connections):
|
||||
pass
|
||||
|
||||
|
||||
def get_max_material_mapping(name, material_type, file_connections):
|
||||
pass
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
from PySide2 import QtWidgets, QtCore
|
||||
from PySide2.QtCore import Signal
|
||||
|
||||
|
||||
class DragAndDrop(QtWidgets.QWidget):
|
||||
drop_update = QtCore.Signal(list)
|
||||
drop_over = QtCore.Signal(bool)
|
||||
|
||||
def __init__(self, frame_color=None, highlight=None, parent=None):
|
||||
super(DragAndDrop, self).__init__(parent)
|
||||
|
||||
self.urls = []
|
||||
self.frame_color = frame_color
|
||||
self.frame_highlight = highlight
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
self.drag_and_drop_frame = QtWidgets.QFrame(self)
|
||||
self.drag_and_drop_frame.setGeometry(0, 0, 5000, 5000)
|
||||
self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color))
|
||||
|
||||
def dragEnterEvent(self, e):
|
||||
if e.mimeData().hasUrls:
|
||||
e.accept()
|
||||
self.drop_over.emit(True)
|
||||
if self.frame_highlight:
|
||||
self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_highlight))
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
def dragLeaveEvent(self, e):
|
||||
self.drop_over.emit(False)
|
||||
|
||||
if self.frame_highlight:
|
||||
self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color))
|
||||
|
||||
|
||||
def dragMoveEvent(self, e):
|
||||
if e.mimeData().hasUrls:
|
||||
e.accept()
|
||||
else:
|
||||
e.ignore()
|
||||
|
||||
def dropEvent(self, e):
|
||||
if e.mimeData().hasUrls:
|
||||
e.setDropAction(QtCore.Qt.CopyAction)
|
||||
e.accept()
|
||||
|
||||
for url in e.mimeData().urls():
|
||||
file_name = str(url.toLocalFile())
|
||||
self.urls.append(file_name)
|
||||
self.drop_update.emit(self.urls)
|
||||
if self.frame_highlight:
|
||||
self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color))
|
||||
else:
|
||||
e.ignore()
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3a78856a5dddee1466183f56dad21148dcf2f093fb316f4aa3af6842912fff61
|
||||
size 8087
|
||||
+957
@@ -0,0 +1,957 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
"""
|
||||
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- Add command line access (Be sure to use pathlib and box libraries)
|
||||
TODO- Docstrings need work... wanted to get descriptions in but they need to be set for Sphinx
|
||||
TODO- Add Blender and 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
|
||||
TODO- Create several test files from different DCC Applications with different materials
|
||||
|
||||
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
|
||||
"""
|
||||
import logging
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide2.QtCore import Slot
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
from dcc_materials.model import MaterialsModel
|
||||
from dcc_materials.drag_and_drop import DragAndDrop
|
||||
import dcc_materials.dcc_material_mapping as mat_map
|
||||
|
||||
|
||||
class MaterialsToLumberyard(QtWidgets.QWidget):
|
||||
def __init__(self, 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.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
|
||||
self.directory_path = os.path.dirname(os.path.abspath(__file__))
|
||||
self.lumberyard_materials_directory = os.path.join(self.desktop_location, 'LumberyardMaterials')
|
||||
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, 'img/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)
|
||||
|
||||
############################
|
||||
# UI Display Layers ########
|
||||
############################
|
||||
|
||||
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:
|
||||
"""
|
||||
if not os.path.exists(self.lumberyard_materials_directory):
|
||||
os.makedirs(self.lumberyard_materials_directory)
|
||||
|
||||
for node in self.lumberyard_material_nodes:
|
||||
if node.material_name_checkbox.isChecked():
|
||||
output = os.path.join(self.lumberyard_materials_directory, '{}.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__)), '{}.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 = mat_map.get_maya_material_mapping(name, material_type, file_connections)
|
||||
elif dcc_app == 'Blender':
|
||||
material_properties = mat_map.get_blender_material_mapping(name, material_type, file_connections)
|
||||
elif dcc_app == '3dsMax':
|
||||
material_properties = mat_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):
|
||||
logging.debug('Max Target file: {}'.format(target_files))
|
||||
|
||||
def get_blender_material_values(self, target_files):
|
||||
logging.debug('Blender Target file: {}'.format(target_files))
|
||||
|
||||
script_path = str(os.path.join(self.directory_path, 'blender_materials.py'))
|
||||
# command = f'{self.blender_path} "{script_path}" "{target_files}" "{self.total_materials}"'
|
||||
# p = subprocess.Popen(command, shell=True, env=runtime_env, stdout=subprocess.PIPE)
|
||||
|
||||
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):
|
||||
blender_base_directory = os.path.join(os.path.join('C:\\', 'Program Files', 'Blender Foundation'))
|
||||
blender_versions_found = []
|
||||
for (dirpath, dirnames, filenames) in os.walk(blender_base_directory):
|
||||
for filename in filenames:
|
||||
if filename == 'blender.exe':
|
||||
blender_versions_found.append(os.path.join(dirpath, 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
|
||||
# effectively repair values as they mat_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'] == 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('pbr')
|
||||
material = {'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)}
|
||||
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.
|
||||
:return:
|
||||
"""
|
||||
|
||||
# TODO- Need to decide how the materials are going to be routed. At this stage they will just be saved to the
|
||||
# desktop, but I assume that we want these files to be saved to an associated project folder
|
||||
|
||||
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.
|
||||
|
||||
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.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())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
materials_to_lumberyard = MaterialsToLumberyard()
|
||||
materials_to_lumberyard.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
+852
@@ -0,0 +1,852 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
"""
|
||||
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- Add command line access
|
||||
TODO- Docstrings need work... wanted to get descriptions in but they need to be set for Sphinx
|
||||
TODO- Add Blender and 3ds Max interoperablity
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- Jonny wants me to use pathlib wherever possible for OO pathing, as well as python-box (aka Box) for dict access
|
||||
|
||||
# Things to do:
|
||||
--> Create the cube demo for Jonny with 4 attached materials
|
||||
--> Allow export of JSON file for demo
|
||||
--> Need to figure out how to clear pointers for stored data properly
|
||||
--> Allow for command line control
|
||||
|
||||
"""
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide2.QtWidgets import QApplication
|
||||
from model import MaterialsModel, TreeNode
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
class MaterialsToLumberyard(QtWidgets.QWidget):
|
||||
def __init__(self, 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('Materials To Lumberyard')
|
||||
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint)
|
||||
self.isTopLevel()
|
||||
|
||||
self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
|
||||
self.lumberyard_materials_directory = os.path.join(self.desktop_location, 'LumberyardMaterials')
|
||||
self.mayapy_path = os.path.abspath('C:/"Program Files"/Autodesk/Maya2020/bin/mayapy.exe')
|
||||
self.bold_font_large = QtGui.QFont('Helvetica', 7, QtGui.QFont.Bold)
|
||||
self.medium_font = QtGui.QFont('Helvetica', 7, QtGui.QFont.Normal)
|
||||
|
||||
self.materials_dictionary = {}
|
||||
self.material_definitions = {}
|
||||
self.material_definitions_nodes = []
|
||||
self.processed_materials = []
|
||||
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.select_files_layout = QtWidgets.QHBoxLayout()
|
||||
self.select_files_layout.setAlignment(QtCore.Qt.AlignLeft)
|
||||
self.select_files_button = QtWidgets.QPushButton('Select Files')
|
||||
self.select_files_button.clicked.connect(self.select_files_button_clicked)
|
||||
self.select_files_button.setFixedSize(90, 35)
|
||||
self.select_files_layout.addWidget(self.select_files_button)
|
||||
self.header_bar_layout.addLayout(self.select_files_layout)
|
||||
self.switch_combobox_layout = QtWidgets.QHBoxLayout()
|
||||
self.switch_combobox_layout.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.switch_layout_combobox = QtWidgets.QComboBox()
|
||||
self.switch_layout_combobox.setEnabled(False)
|
||||
self.switch_layout_combobox.setFixedSize(250, 30)
|
||||
self.combobox_items = ['Source Files', 'DCC Material Values', 'Export Materials']
|
||||
self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}')
|
||||
self.switch_layout_combobox.addItems(self.combobox_items)
|
||||
self.header_bar_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)
|
||||
|
||||
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||||
# ---->> 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.material_definitions_widget = QtWidgets.QWidget()
|
||||
self.material_definitions_layout = QtWidgets.QHBoxLayout(self.material_definitions_widget)
|
||||
self.material_definitions_layout.setSpacing(0)
|
||||
self.material_definitions_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.material_definitions_frame = QtWidgets.QFrame(self.material_definitions_widget)
|
||||
self.material_definitions_frame.setGeometry(0, 0, 5000, 5000)
|
||||
self.material_definitions_frame.setStyleSheet('background-color:rgb(75,75,75);')
|
||||
self.material_definitions_scroller = QtWidgets.QScrollArea()
|
||||
self.scroller_widget = QtWidgets.QWidget()
|
||||
self.scroller_layout = QtWidgets.QVBoxLayout()
|
||||
self.scroller_widget.setLayout(self.scroller_layout)
|
||||
self.material_definitions_scroller.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
self.material_definitions_scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
self.material_definitions_scroller.setWidgetResizable(True)
|
||||
self.material_definitions_scroller.setWidget(self.scroller_widget)
|
||||
self.material_definitions_layout.addWidget(self.material_definitions_scroller)
|
||||
self.content_stacked_layout.addWidget(self.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 Listed 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)
|
||||
self.reset_button = QtWidgets.QPushButton('Reset')
|
||||
self.reset_button.setFixedSize(50, 50)
|
||||
self.reset_button.clicked.connect(self.reset_clicked)
|
||||
self.reset_button.setEnabled(False)
|
||||
self.process_files_layout.addWidget(self.reset_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)
|
||||
|
||||
############################
|
||||
# UI Display Layers ########
|
||||
############################
|
||||
|
||||
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.material_definitions):
|
||||
material_definition_node = MaterialNode([value, self.material_definitions[value]], count)
|
||||
self.material_definitions_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():
|
||||
for value in values:
|
||||
transfer_info = {'file_location': value, 'success': True}
|
||||
try:
|
||||
if key == 'maya' and len(value):
|
||||
self.get_maya_material_values(value)
|
||||
elif key == 'max' and len(value):
|
||||
self.get_max_material_values(value)
|
||||
elif key == 'blender' and len(value):
|
||||
self.get_blender_material_values(value)
|
||||
else:
|
||||
pass
|
||||
except Exception as e:
|
||||
# TODO- Allow corrective actions or some display of errors if this fails?
|
||||
transfer_info['success'] = [value, e]
|
||||
self.set_transfer_status(transfer_info)
|
||||
|
||||
if self.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(2)
|
||||
self.set_ui_buttons()
|
||||
self.message_readout_label.setText('Ready.')
|
||||
|
||||
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
|
||||
|
||||
script_path = str(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'maya_materials.py'))
|
||||
runtime_env = os.environ.copy()
|
||||
runtime_env['MAYA_LOCATION'] = os.path.dirname(self.mayapy_path)
|
||||
runtime_env['PYMEL_SKIP_MEL_INIT'] = '1'
|
||||
runtime_env['PYTHONPATH'] = os.path.dirname(self.mayapy_path)
|
||||
command = f'{self.mayapy_path} "{script_path}" "{target_files}" "{self.total_materials}"'
|
||||
p = subprocess.Popen(command, shell=True, env=runtime_env, stdout=subprocess.PIPE)
|
||||
output = p.communicate()[0]
|
||||
self.set_material_dictionary(json.loads(output))
|
||||
|
||||
def get_max_material_values(self, target_file):
|
||||
print('Max Target file: {}'.format(target_file))
|
||||
|
||||
def get_blender_material_values(self, target_file):
|
||||
print('Blender Target file: {}'.format(target_file))
|
||||
|
||||
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:
|
||||
"""
|
||||
if not os.path.exists(self.lumberyard_materials_directory):
|
||||
os.makedirs(self.lumberyard_materials_directory)
|
||||
|
||||
for node in self.material_definitions_nodes:
|
||||
if node.material_name_checkbox.isChecked():
|
||||
output = os.path.join(self.lumberyard_materials_directory, '{}.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__)), '{}.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, material_type, file_connections):
|
||||
""" This system will probably need rethinking if DCCs and compatible materials grow """
|
||||
material_properties = {}
|
||||
if material_type == 'StingrayPBS':
|
||||
naming_exceptions = {'color': 'baseColor', 'ao': 'ambientOcclusion'}
|
||||
maps = 'color, metallic, roughness, normal, emissive, ao, opacity'.split(', ')
|
||||
for m in maps:
|
||||
texture_attribute = 'TEX_{}_map'.format(m)
|
||||
for tex in file_connections.keys():
|
||||
if tex.find(texture_attribute) != -1:
|
||||
key = m if m not in naming_exceptions else naming_exceptions.get(m)
|
||||
material_properties[key] = {'useTexture': 'true',
|
||||
'textureMap': file_connections.get('{}.{}'.format(name, texture_attribute))}
|
||||
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 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
|
||||
# effectively repair values as they map to intended Lumberyard shader type
|
||||
|
||||
for row in range(self.target_files_table.rowCount()):
|
||||
if self.target_files_table.item(row, 0).text().strip() == transfer_info['file_location']:
|
||||
if transfer_info['success']:
|
||||
self.target_files_table.item(row, 0).setBackground(QtGui.QColor(192, 255, 171))
|
||||
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))
|
||||
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_pbr_material_description(source_file, name, 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 launced 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:
|
||||
"""
|
||||
self.total_materials += len(dcc_dictionary)
|
||||
self.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.materials_dictionary)
|
||||
else:
|
||||
self.model.update()
|
||||
self.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)
|
||||
# Source Files Layout ----------------------------------->>
|
||||
if display_index == 0:
|
||||
self.select_files_button.setEnabled(True)
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setText('Process Listed Files')
|
||||
|
||||
# DCC Material Values Layout ---------------------------->>
|
||||
elif display_index == 1:
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setEnabled(False)
|
||||
self.select_files_button.setEnabled(False)
|
||||
|
||||
# Export Materials Layout ------------------------------->>
|
||||
else:
|
||||
self.select_files_button.setEnabled(False)
|
||||
self.process_files_button.setText('Export Selected Materials')
|
||||
if self.material_definitions:
|
||||
self.process_files_button.setEnabled(True)
|
||||
|
||||
def set_pbr_material_description(self, source_file, name, material_type, file_connections):
|
||||
# Build dictionary for material description based on extracted values
|
||||
default_settings = self.get_lumberyard_material_template('pbr')
|
||||
material = {'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, material_type, file_connections)}
|
||||
self.material_definitions[name if name not in self.material_definitions.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.
|
||||
:return:
|
||||
"""
|
||||
|
||||
# TODO- Need to decide how the materials are going to be routed. At this stage they will just be saved to the
|
||||
# desktop, but I assume that we want these files to be saved to an associated project folder
|
||||
|
||||
if self.sender().text() == 'Process Listed 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.
|
||||
|
||||
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.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()
|
||||
|
||||
|
||||
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'])
|
||||
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)
|
||||
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:
|
||||
"""
|
||||
print('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())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
materials_to_lumberyard = MaterialsToLumberyard()
|
||||
materials_to_lumberyard.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
# import pymxs
|
||||
|
||||
def get_material_information(file_location):
|
||||
print('[Max] Get Material Information: {}'.format(file_location))
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
from PySide2 import QtCore
|
||||
import maya.standalone
|
||||
maya.standalone.initialize(name='python')
|
||||
import pymel.core as pm
|
||||
import json
|
||||
import sys
|
||||
import logging
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
|
||||
class MayaMaterials(QtCore.QObject):
|
||||
def __init__(self, files_list, materials_count, parent=None):
|
||||
super(MayaMaterials, self).__init__(parent)
|
||||
|
||||
self.files_list = files_list
|
||||
self.current_scene = None
|
||||
self.materials_dictionary = {}
|
||||
self.materials_count = int(materials_count)
|
||||
self.get_material_information()
|
||||
|
||||
def get_material_information(self):
|
||||
"""
|
||||
Main entry point for the material information extraction. Because this class is run
|
||||
in Standalone mode as a subprocess, the list is passed as a string- some parsing/measures
|
||||
need to be taken in order to separate values that originated as a list before passed.
|
||||
|
||||
:return: A dictionary of all of the materials gathered. Sent back to main UI through stdout
|
||||
"""
|
||||
for file in file_list:
|
||||
self.current_scene = file.replace('\'', '')
|
||||
pm.openFile(self.current_scene, force=True)
|
||||
self.set_material_descriptions()
|
||||
json.dump(self.materials_dictionary, sys.stdout)
|
||||
|
||||
@staticmethod
|
||||
def get_materials(target_mesh):
|
||||
"""
|
||||
Gathers a list of all materials attached to each mesh's shader
|
||||
|
||||
:param target_mesh: The target mesh to pull attached material information from.
|
||||
:return: List of unique material values attached to the mesh passed as an argument.
|
||||
"""
|
||||
shading_group = pm.listConnections(pm.PyNode(target_mesh), type='shadingEngine')
|
||||
materials = pm.ls(pm.listConnections(shading_group), materials=1)
|
||||
return list(set(materials))
|
||||
|
||||
@staticmethod
|
||||
def get_shader(material_name):
|
||||
"""
|
||||
Convenience function for obtaining the shader that the specified material (as an argument)
|
||||
is attached to.
|
||||
|
||||
:param material_name: Takes the material name as an argument to get associated shader object
|
||||
:return:
|
||||
"""
|
||||
connections = pm.listConnections(material_name, type='shadingEngine')[0]
|
||||
shader_name = '{}.surfaceShader'.format(connections)
|
||||
shader = pm.listConnections(shader_name)[0]
|
||||
return shader
|
||||
|
||||
@staticmethod
|
||||
def get_shader_information(shader, material_mesh):
|
||||
"""
|
||||
Helper function for extracting shader/material attributes used to form the DCC specific dictionary
|
||||
of found material values for conversion.
|
||||
|
||||
:param shader: The target shader object to analyze
|
||||
:param material_mesh: The material mesh needs to be passed to search for textures attached to it.
|
||||
:return: Complete set (in the form of two dictionaries) of file connections and material attribute values
|
||||
"""
|
||||
shader_file_connections = {}
|
||||
shading_group = pm.listConnections(material_mesh, type='shadingEngine')
|
||||
material = pm.ls(pm.listConnections(shading_group), materials=1)[0]
|
||||
used_texture_nodes = set(pm.listHistory(material, type='file'))
|
||||
for node in used_texture_nodes:
|
||||
for attr in pm.listConnections(node, plugs=1):
|
||||
try:
|
||||
if attr.split('.')[0] == material:
|
||||
shader_file_connections[str(attr)] = pm.getAttr(node.fileTextureName)
|
||||
break
|
||||
except pm.MayaAttributeError:
|
||||
pass
|
||||
|
||||
shader_attributes = {}
|
||||
for shader_attribute in pm.listAttr(shader, s=True, iu=True):
|
||||
try:
|
||||
shader_attributes[str(shader_attribute)] = str(pm.getAttr('{}.{}'.format(shader, shader_attribute)))
|
||||
except pm.MayaAttributeError as e:
|
||||
logging.error('MayaAttributeError: {}'.format(e))
|
||||
|
||||
return shader_file_connections, shader_attributes
|
||||
|
||||
def set_material_dictionary(self, material_name, material_type, material_mesh):
|
||||
"""
|
||||
When a unique material has been found, this creates a dictionary entry with all relevant material values. This
|
||||
includes material attributes as well as attached file textures. Later in the process this information is
|
||||
leveraged when creating the Lumberyard material definition.
|
||||
|
||||
:param material_name: The name attached to the material
|
||||
:param material_type: Specific type of material (Arnold, Stingray, etc.)
|
||||
:param material_mesh: Mesh that the material is applied to
|
||||
:return:
|
||||
"""
|
||||
self.materials_count += 1
|
||||
shader = self.get_shader(material_name)
|
||||
shader_file_connections, shader_attributes = self.get_shader_information(shader, material_mesh)
|
||||
material_dictionary = {'MaterialName': material_name, 'MaterialType': material_type, 'DccApplication': 'Maya',
|
||||
'AppliedMesh': material_mesh, 'FileConnections': shader_file_connections,
|
||||
'SceneName': str(self.current_scene), 'MaterialAttributes': shader_attributes}
|
||||
material_name = 'Material_{}'.format(self.materials_count)
|
||||
self.materials_dictionary[material_name] = material_dictionary
|
||||
|
||||
def set_material_descriptions(self):
|
||||
"""
|
||||
This function serves as the clearinghouse for all analyzed materials passing through the system.
|
||||
It will determine whether or not the found material has already been processed, or if it needs to
|
||||
be added to the final material dictionary. In the event that an encountered material has already
|
||||
been processed, this function creates a register of all meshes it is applied to in the 'AppliedMesh'
|
||||
attribute.
|
||||
:return:
|
||||
"""
|
||||
scene_geo = pm.ls(v=True, geometry=True)
|
||||
for target_mesh in scene_geo:
|
||||
material_list = self.get_materials(target_mesh)
|
||||
for material_name in material_list:
|
||||
material_type = pm.nodeType(material_name, api=True)
|
||||
material_listed = [x for x in self.materials_dictionary
|
||||
if self.materials_dictionary[x]['MaterialName'] == material_name]
|
||||
|
||||
if not material_listed:
|
||||
self.set_material_dictionary(str(material_name), str(material_type), str(target_mesh))
|
||||
else:
|
||||
mesh_list = self.materials_dictionary[material_name].get('AppliedMesh')
|
||||
if not isinstance(mesh_list, list):
|
||||
self.materials_dictionary[material_name]['AppliedMesh'] = [mesh_list, target_mesh]
|
||||
else:
|
||||
mesh_list.append(target_mesh)
|
||||
|
||||
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
# Maya Specific Shader Mapping #
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
|
||||
file_list = sys.argv[1:-1]
|
||||
count = sys.argv[-1]
|
||||
instance = MayaMaterials(file_list, count)
|
||||
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
from PySide2.QtCore import QAbstractItemModel, QModelIndex, Qt
|
||||
|
||||
|
||||
class MaterialsModel(QAbstractItemModel):
|
||||
def __init__(self, headers, data, parent=None):
|
||||
super(MaterialsModel, self).__init__(parent)
|
||||
|
||||
self.rootItem = TreeNode(headers)
|
||||
self.parents = [self.rootItem]
|
||||
self.indentations = [0]
|
||||
self.create_data(data)
|
||||
|
||||
def create_data(self, data, indent=-1):
|
||||
"""
|
||||
Recursive loop that structures Model data into tree form.
|
||||
:param data: Row information.
|
||||
:param indent: Column information. This helps to facilitate the creation of nested rows.
|
||||
:return:
|
||||
"""
|
||||
if type(data) == dict:
|
||||
indent += 1
|
||||
position = 4 * indent
|
||||
for key, value in data.items():
|
||||
if position > self.indentations[-1]:
|
||||
if self.parents[-1].childCount() > 0:
|
||||
self.parents.append(self.parents[-1].child(self.parents[-1].childCount() - 1))
|
||||
self.indentations.append(position)
|
||||
else:
|
||||
while position < self.indentations[-1] and len(self.parents) > 0:
|
||||
self.parents.pop()
|
||||
self.indentations.pop()
|
||||
parent = self.parents[-1]
|
||||
parent.insertChildren(parent.childCount(), 1, parent.columnCount())
|
||||
parent.child(parent.childCount() - 1).setData(0, key)
|
||||
value_string = str(value) if type(value) != dict else str('')
|
||||
parent.child(parent.childCount() - 1).setData(1, value_string)
|
||||
try:
|
||||
self.create_data(value, indent)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attribute_value(search_string, search_column):
|
||||
""" Convenience function for quickly accessing row information based on attribute keys. """
|
||||
for childIndex in range(search_column.childCount()):
|
||||
child_item = search_column.child(childIndex)
|
||||
child_value = child_item.itemData
|
||||
if child_value[0] == search_string:
|
||||
return child_value[1]
|
||||
return None
|
||||
|
||||
def index(self, row, column, index=QModelIndex()):
|
||||
""" Returns the index of the item in the model specified by the given row, column and parent index """
|
||||
if not self.hasIndex(row, column, index):
|
||||
return QModelIndex()
|
||||
if not index.isValid():
|
||||
item = self.rootItem
|
||||
else:
|
||||
item = index.internalPointer()
|
||||
|
||||
child = item.child(row)
|
||||
if child:
|
||||
return self.createIndex(row, column, child)
|
||||
return QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
"""
|
||||
Returns the parent of the model item with the given index If the item has no parent,
|
||||
an invalid QModelIndex is returned
|
||||
"""
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
item = index.internalPointer()
|
||||
if not item:
|
||||
return QModelIndex()
|
||||
|
||||
parent = item.parentItem
|
||||
if parent == self.rootItem:
|
||||
return QModelIndex()
|
||||
else:
|
||||
return self.createIndex(parent.childNumber(), 0, parent)
|
||||
|
||||
def rowCount(self, index=QModelIndex()):
|
||||
"""
|
||||
Returns the number of rows under the given parent. When the parent is valid it means that
|
||||
rowCount is returning the number of children of parent
|
||||
"""
|
||||
if index.isValid():
|
||||
parent = index.internalPointer()
|
||||
else:
|
||||
parent = self.rootItem
|
||||
return parent.childCount()
|
||||
|
||||
def columnCount(self, index=QModelIndex()):
|
||||
""" Returns the number of columns for the children of the given parent """
|
||||
return self.rootItem.columnCount()
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
""" Returns the data stored under the given role for the item referred to by the index """
|
||||
if index.isValid() and role == Qt.DisplayRole:
|
||||
return index.internalPointer().data(index.column())
|
||||
elif not index.isValid():
|
||||
return self.rootItem.data(index.column())
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
""" Returns the data for the given role and section in the header with the specified orientation """
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||
return self.rootItem.data(section)
|
||||
|
||||
|
||||
class TreeNode(object):
|
||||
def __init__(self, data, parent=None):
|
||||
self.parentItem = parent
|
||||
self.itemData = data
|
||||
self.children = []
|
||||
|
||||
def child(self, row):
|
||||
return self.children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self.children)
|
||||
|
||||
def childNumber(self):
|
||||
if self.parentItem is not None:
|
||||
return self.parentItem.children.index(self)
|
||||
|
||||
def columnCount(self):
|
||||
return len(self.itemData)
|
||||
|
||||
def data(self, column):
|
||||
return self.itemData[column]
|
||||
|
||||
def insertChildren(self, position, count, columns):
|
||||
if position < 0 or position > len(self.children):
|
||||
return False
|
||||
for row in range(count):
|
||||
data = [v for v in range(columns)]
|
||||
item = TreeNode(data, self)
|
||||
self.children.insert(position, item)
|
||||
|
||||
def parent(self):
|
||||
return self.parentItem
|
||||
|
||||
def setData(self, column, value):
|
||||
if column < 0 or column >= len(self.itemData):
|
||||
return False
|
||||
self.itemData[column] = value
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"ambientOcclusion": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"emissive": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"intensity": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"metallic": {
|
||||
"factor": 0.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"roughness": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif"
|
||||
},
|
||||
"specularF0": {
|
||||
"factor": 0.5,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"normal": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif"
|
||||
},
|
||||
"opacity": {
|
||||
"doubleSided": false,
|
||||
"factor": 1.0,
|
||||
"cutoutAlpha": false,
|
||||
"cutoutThreshold": 0.5,
|
||||
"useBaseColorTextureAlpha": false,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
REM
|
||||
REM For complete copyright and license terms please see the LICENSE at the root of this
|
||||
REM distribution (the "License"). All use of this software is governed by the License,
|
||||
REM or, if provided, by the license below or the license accompanying this file. Do not
|
||||
REM remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
:: Set up and run LY Python CMD prompt
|
||||
:: Sets up the DccScriptingInterface_Env,
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface Cmd
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..\..\..\..\..\..)
|
||||
|
||||
:: Change to root Lumberyard dev dir
|
||||
:: Don't use the LY_DEV so we can test that ENVAR!!!
|
||||
CD /d %DEV_REL_PATH%
|
||||
set Rel_Dev=%CD%
|
||||
echo Rel_Dev = %Rel_Dev%
|
||||
:: Restore original directory
|
||||
popd
|
||||
|
||||
set DCCSI_PYTHON_INSTALL=%Rel_Dev%\Tools\Python\3.7.5\windows
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_INSTALL%;%PATH%
|
||||
|
||||
:: dcc scripting interface gem path
|
||||
set DCCSIG_PATH=%Rel_Dev%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface
|
||||
echo DCCSIG_PATH = %DCCSIG_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%PATH%
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=false)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCCsi, DCC Material Converter
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %DCCSIG_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%PATH%
|
||||
|
||||
set PYTHONPATH=%DCCSIG_PATH%;%PYTHONPATH%
|
||||
|
||||
CALL %DCCSI_PYTHON_INSTALL%\python.exe "%DCCSIG_PATH%\SDK\Maya\Scripts\Python\kitbash_converter\standalone.py"
|
||||
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
|
||||
exit /b 0
|
||||
+1076
File diff suppressed because it is too large
Load Diff
+372
@@ -0,0 +1,372 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# This file is designed to run in Maya Standalone- it imports existing FBX files and reformats materials as Stingray
|
||||
# PBS materials, and attempts to attach PBR Metal/Rough texture sets generated for each asset. There is currently a
|
||||
# bug in Maya that prevents this from being possible, although it has been officially logged with Autodesk so hopefully
|
||||
# a fix will exist in an updated version
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# maya imports
|
||||
from PySide2 import QtCore
|
||||
import maya.cmds as mc
|
||||
import maya.standalone
|
||||
maya.standalone.initialize(name='python')
|
||||
mc.loadPlugin("fbxmaya")
|
||||
import maya.mel as mel
|
||||
mel.eval('loadPlugin fbxmaya')
|
||||
|
||||
# built-ins
|
||||
import logging as _logging
|
||||
import collections
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
module_name = 'kitbash_converter.process_fbx_file'
|
||||
_LOGGER = _logging.getLogger(module_name)
|
||||
|
||||
|
||||
SUPPORTED_MATERIAL_PROPERTIES = ['baseColor', 'emissive', 'metallic', 'roughness', 'normal', 'opacity']
|
||||
|
||||
|
||||
class ProcessFbxFile(QtCore.QObject):
|
||||
def __init__(self, fbx_file, base_directory, relative_destination_path, parent=None):
|
||||
super(ProcessFbxFile, self).__init__(parent)
|
||||
self.fbx_file = fbx_file
|
||||
self.base_directory = base_directory
|
||||
self.textures_directory = self.get_textures_directory()
|
||||
self.relative_destination_path = relative_destination_path
|
||||
self.default_material_definition = 'standardPBR.template.material'
|
||||
self.transfer_data = {}
|
||||
self.process_file()
|
||||
|
||||
def process_file(self):
|
||||
"""
|
||||
This makes a copy of the Standard PBR material for use as a template when processing a Kitbash3d
|
||||
asset. Then it imports the FBX file into Maya and begins the separation and material conversion
|
||||
process.
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('MAYA STANDALONE PROCESS STARTED ---------------------------------------------')
|
||||
_LOGGER.info('FBX File Passed: {}'.format(self.fbx_file))
|
||||
_LOGGER.info('Base Directory: {}'.format(self.base_directory))
|
||||
_LOGGER.info('Destination Directory: {}'.format(self.relative_destination_path))
|
||||
_LOGGER.info('-----------------------------------------------------------------------------\n')
|
||||
|
||||
if self.textures_directory and os.path.exists(self.fbx_file):
|
||||
# Get Material Definition Template
|
||||
with open(self.default_material_definition) as json_file:
|
||||
self.default_material_definition = json.load(json_file)
|
||||
|
||||
mc.file(self.fbx_file, i=True, type="FBX")
|
||||
return_dictionary = self.process_groups()
|
||||
_LOGGER.info('ReturnDictionary: {}'.format(return_dictionary))
|
||||
json.dump(return_dictionary, sys.stdout)
|
||||
_LOGGER.info('Process Complete.')
|
||||
|
||||
def process_groups(self):
|
||||
"""
|
||||
This is the main function of the module and orchestrates the complete set of steps for extracting
|
||||
each subobject from the asset FBX file and generating the .material files for use in Lumberyard.
|
||||
:return:
|
||||
"""
|
||||
asset_dictionary = {}
|
||||
ignore_list = ['persp', 'top', 'front', 'side']
|
||||
group_list = []
|
||||
for item in mc.ls(assemblies=True):
|
||||
if item not in ignore_list:
|
||||
group_list.append(item)
|
||||
|
||||
for grp in group_list:
|
||||
_LOGGER.info('\n_\n////////////////////////////////\n--> {}\n////////////////////////////////'.format(grp))
|
||||
asset_material_dictionary = self.get_asset_materials(grp)
|
||||
material_dictionary = []
|
||||
for material_name, texture_set in asset_material_dictionary.items():
|
||||
_LOGGER.info('+++++ MaterialName: {} TextureSet: {}'.format(material_name, texture_set))
|
||||
fbx_parts_list = grp.split('_')[1:-1]
|
||||
new_fbx_name = ('_').join(fbx_parts_list)
|
||||
_LOGGER.info('NewFBXName: {}'.format(new_fbx_name))
|
||||
material_definition_name = '{}_{}.material'.format(new_fbx_name, material_name)
|
||||
_LOGGER.info('Material File: {}'.format(material_definition_name))
|
||||
|
||||
# Export FBX
|
||||
output_directory = os.path.join(base_directory, self.relative_destination_path, new_fbx_name)
|
||||
_LOGGER.info('FBX Output Directory: {}'.format(output_directory))
|
||||
if not os.path.exists(output_directory):
|
||||
os.makedirs(output_directory)
|
||||
self.export_fbx(grp, os.path.join(output_directory, '{}.fbx'.format(new_fbx_name)))
|
||||
material_definition = self.create_object_material(output_directory, material_definition_name,
|
||||
texture_set, material_name)
|
||||
asset_dictionary[new_fbx_name] = {'asset_location': output_directory}
|
||||
return asset_dictionary
|
||||
|
||||
def find_textures(self, material_name):
|
||||
"""
|
||||
This is run if there were no files attached to materials. There appears to be material sets for all materials,
|
||||
and in the case of glass textures they just are not assigned.
|
||||
:param material_name: The material name inside of the FBX- if there are textures they will begin with this.
|
||||
"""
|
||||
_LOGGER.info('Searching for textures for the following material: {}'.format(material_name))
|
||||
texture_set = {}
|
||||
for file in os.listdir(self.textures_directory):
|
||||
if file.startswith(material_name):
|
||||
try:
|
||||
texture_type = self.get_texture_type(file)
|
||||
texture_set[texture_type] = os.path.join(self.textures_directory, file)
|
||||
except Exception as e:
|
||||
_LOGGER.info('Could not create texture record for file [{}]... Exception: {}'.format(file, e))
|
||||
return texture_set
|
||||
|
||||
def create_object_material(self, output_directory, material_definition_name, texture_dictionary, material_name):
|
||||
"""
|
||||
Using the file textures present, this function takes materials and associated texture sets
|
||||
and forms the .material file based on those values for each material/fbx found
|
||||
:param output_directory: The destination directory for the exported assets
|
||||
:param material_definition_name: The name for the exported .material file
|
||||
:param texture_dictionary: All the file textures associated with a material
|
||||
:return:
|
||||
"""
|
||||
output_path = os.path.join(output_directory, material_definition_name)
|
||||
_LOGGER.info('Creating Material: {} OutputPath: {}'.format(material_definition_name, output_path))
|
||||
|
||||
if not os.path.exists(output_path):
|
||||
material_definition = collections.OrderedDict()
|
||||
material_template = self.default_material_definition.copy()
|
||||
try:
|
||||
for key, values in material_template.items():
|
||||
# These values remain constant
|
||||
if key != 'properties':
|
||||
material_definition[key] = values
|
||||
else:
|
||||
modified_items = {}
|
||||
for material_property in SUPPORTED_MATERIAL_PROPERTIES:
|
||||
for k, v in values.items():
|
||||
if k.lower() in texture_dictionary.keys():
|
||||
temp_dict = self.get_texture_attributes(k.lower(), texture_dictionary, material_name)
|
||||
modified_items[k] = temp_dict
|
||||
|
||||
if modified_items:
|
||||
material_definition[key] = modified_items
|
||||
|
||||
if len(material_definition) > 4:
|
||||
_LOGGER.info('\n++++++++++++++\n+++++++++++++++\nFinal Material: {}\n++++++++++++++\n'
|
||||
'+++++++++++++++\n_\n'.format(json.dumps(material_definition, indent=4, sort_keys=False)))
|
||||
self.export_lumberyard_material(output_path, material_definition)
|
||||
except Exception as e:
|
||||
_LOGGER.info('Problem creating Material Definition: {}'.format(e))
|
||||
|
||||
def remediate_empty_material(self, material_name):
|
||||
_LOGGER.info('Resolving empty material::::::: {}'.format(material_name))
|
||||
|
||||
##############################
|
||||
# Getters/Setters ############
|
||||
##############################
|
||||
|
||||
def get_asset_materials(self, target_group):
|
||||
"""
|
||||
Pulls information about assigned materials from materials in order to coordinate file textures present
|
||||
for each material being set up for Lumberyard.
|
||||
:param target_group: Kitbash 3d assets contain several subobjects grouped under locators. "Target group"
|
||||
signifies a group from the total set of groups present.
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Getting Asset Materials... Target Group: {}'.format(target_group))
|
||||
materials_dictionary = {}
|
||||
mc.select(target_group, hierarchy=True)
|
||||
|
||||
target_nodes = mc.ls(sl=True, dag=True, s=True)
|
||||
sg = mc.listConnections(target_nodes, type= 'shadingEngine')
|
||||
materials = list(set(mc.ls(mc.listConnections(sg), materials=True)))
|
||||
|
||||
for material_name in materials:
|
||||
found_textures = []
|
||||
texture_files = [x for x in mc.listConnections(material_name, plugs=1, source=1) if x.endswith('outColor')]
|
||||
for file_name in texture_files:
|
||||
try:
|
||||
found_textures.append(mc.getAttr('{}.fileTextureName'.format(file_name.split('.')[0])))
|
||||
except Exception as e:
|
||||
_LOGGER.info('Error occured: {}'.format(e))
|
||||
|
||||
texture_set = self.get_texture_set(found_textures) if found_textures else self.find_textures(material_name)
|
||||
if texture_set:
|
||||
materials_dictionary[material_name] = texture_set
|
||||
else:
|
||||
attribute_list = self.remediate_empty_material(material_name)
|
||||
if attribute_list:
|
||||
materials_dictionary[material_name] = attribute_list
|
||||
return materials_dictionary
|
||||
|
||||
def get_textures_directory(self):
|
||||
for (root, dirs, files) in os.walk(self.base_directory, topdown=True):
|
||||
for dir in dirs:
|
||||
if dir.lower() == 'textures':
|
||||
return os.path.join(root, dir)
|
||||
return None
|
||||
|
||||
def get_texture_set(self, texture_files):
|
||||
"""
|
||||
Gets the entire set of textures associated with a material using a single texture.
|
||||
:param texture_files:
|
||||
:return:
|
||||
"""
|
||||
texture_set = {}
|
||||
for texture_file in texture_files:
|
||||
base_texture_name = self.get_base_texture_name(texture_file)
|
||||
for file in os.listdir(self.textures_directory):
|
||||
if file.startswith(base_texture_name):
|
||||
try:
|
||||
texture_type = self.get_texture_type(file)
|
||||
texture_set[texture_type] = os.path.join(self.textures_directory, file)
|
||||
_LOGGER.info('{} texture added. Path::: {}'.format(texture_type, os.path.join(self.textures_directory, file)))
|
||||
except Exception:
|
||||
pass
|
||||
return texture_set
|
||||
|
||||
def get_texture_type(self, file):
|
||||
"""
|
||||
Tries to extract the texture type from a texture filename (ie "basecolor", "roughness")
|
||||
|
||||
:param file: The file name to get the texture type from
|
||||
"""
|
||||
file_stem = file.split('.')[0]
|
||||
file_parts = file_stem.split('_')
|
||||
if len(file_parts) > 1:
|
||||
return file_parts[-1]
|
||||
|
||||
def get_texture_attributes(self, texture_type, texture_dictionary, material_name):
|
||||
target_path = os.path.normpath(texture_dictionary[texture_type])
|
||||
if texture_type == 'opacity':
|
||||
temp_dict = self.get_opacity_settings(material_name, target_path)
|
||||
elif texture_type == 'subsurfacescattering':
|
||||
temp_dict = self.get_sss_settings(material_name, target_path)
|
||||
elif texture_type == 'emissive':
|
||||
temp_dict = self.get_emissive_settings(material_name, target_path)
|
||||
elif texture_type == 'basecolor':
|
||||
temp_dict = self.get_basecolor_settings(material_name, target_path)
|
||||
else:
|
||||
temp_dict = {'textureMap': self.get_relative_path(target_path)}
|
||||
|
||||
return temp_dict
|
||||
|
||||
def get_opacity_settings(self, material_name, target_path):
|
||||
"""
|
||||
Reads Maya material to construct material opacity attributes
|
||||
|
||||
:param material_name: The Maya Material name being translated for Lumberyard
|
||||
:param target_path: The path to the texture file
|
||||
:return: Attribute values as a dictionary
|
||||
"""
|
||||
_LOGGER.info('Getting opacity settings...')
|
||||
return {'alphaSource': 'Split', 'mode': 'Cutout', 'textureMap': self.get_relative_path(target_path)}
|
||||
|
||||
def get_sss_settings(self, material_name, target_path):
|
||||
"""
|
||||
Reads Maya material to construct material subsurface scattering attributes
|
||||
|
||||
:param material_name: The Maya material name being translated for Lumberyard
|
||||
:param target_path: The path to the texture file
|
||||
:return: Attribute values as a dictionary
|
||||
"""
|
||||
_LOGGER.info('Getting Subsurface Scattering settings...')
|
||||
return {'enableSubsurfaceScattering': True, 'influenceMap': self.get_relative_path(target_path)}
|
||||
|
||||
def get_emissive_settings(self, material_name, target_path):
|
||||
"""
|
||||
Reads Maya material to construct material emissive attributes
|
||||
|
||||
:param material_name: The Maya material name being translated for Lumberyard
|
||||
:param target_path: The path to the texture file
|
||||
:return: Attribute values as a dictionary
|
||||
"""
|
||||
_LOGGER.info('Getting Emissive settings...')
|
||||
return {'enable': True, 'intensity': 4, 'textureMap': self.get_relative_path(target_path)}
|
||||
|
||||
def get_basecolor_settings(self, material_name, target_path):
|
||||
"""
|
||||
Reads Maya material to construct material BaseColor settings
|
||||
|
||||
:param material_name: The Maya material name being translated for Lumberyard
|
||||
:param target_path: The path to the texture file
|
||||
:return: Attribute values as a dictionary
|
||||
"""
|
||||
_LOGGER.info('Getting BaseColor settings...')
|
||||
# Get shader color values
|
||||
return {'textureMap': self.get_relative_path(target_path)}
|
||||
|
||||
def get_base_texture_name(self, texture_path):
|
||||
"""
|
||||
Tries to extract texture name without the added texture type (ie "basecolor", "roughness", etc)
|
||||
|
||||
:param texture_path: The path to the texture from which to extract the base texture name
|
||||
"""
|
||||
path_list = texture_path.split('\\')
|
||||
file_name = path_list[-1]
|
||||
texture_list = file_name.split('_')
|
||||
base_texture_name = ('_').join(texture_list[:-1])
|
||||
return base_texture_name
|
||||
|
||||
def get_relative_path(self, full_path):
|
||||
"""
|
||||
Material definitions use relative paths for file textures- this function takes the full paths of assets and
|
||||
converts to the abbreviated relative path format needed for Lumberyard to source the files
|
||||
|
||||
:param full_path: Full path to the asset
|
||||
:return:
|
||||
"""
|
||||
truncated_path = self.textures_directory.split('AtomContent\\')[-1]
|
||||
start_directory = truncated_path.split('\\')[0]
|
||||
path_list = full_path.split('\\')
|
||||
return_path = ('/').join(path_list[path_list.index(start_directory):])
|
||||
return return_path
|
||||
|
||||
##############################
|
||||
# Export Files ###############
|
||||
##############################
|
||||
|
||||
def export_fbx(self, group_name, output_path):
|
||||
"""
|
||||
Exports sub-objects from Kitbash3d assets, groups of assets are contained in a single FBX for delivery.
|
||||
|
||||
:param group_name: Group name inside of FBX- subobjects are grouped under locators
|
||||
:param output_path: The destination path for the exported FBX object
|
||||
:return:
|
||||
"""
|
||||
if not os.path.exists(output_path):
|
||||
mc.move(0, 0, 0, group_name, absolute=True)
|
||||
mc.select(group_name, hierarchy=True)
|
||||
mc.FBXExport('-file', output_path, '-s')
|
||||
mc.select(clear=True)
|
||||
|
||||
def export_lumberyard_material(self, output_path, material_description):
|
||||
"""
|
||||
Takes one final dictionary with information gathered in the process and saves with JSON formatting into a
|
||||
.material file
|
||||
|
||||
:param output:
|
||||
:param material_description:
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Output .material++++++>> {}'.format(output_path))
|
||||
with open(output_path, 'w') as material_file:
|
||||
json.dump(dict(material_description), material_file, ensure_ascii=False, indent=4)
|
||||
|
||||
|
||||
fbx_file = sys.argv[1]
|
||||
_LOGGER.info('FBX file: {}'.format(fbx_file))
|
||||
base_directory = sys.argv[-2]
|
||||
_LOGGER.info('Base Directory: {}'.format(base_directory))
|
||||
relative_destination_path = sys.argv[-1].replace('/', '\\')
|
||||
_LOGGER.info('Relative Destination Path: {}'.format(relative_destination_path))
|
||||
ProcessFbxFile(fbx_file, base_directory, relative_destination_path)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
"""Boostraps and Starts Standalone DCC Material Converter utility"""
|
||||
|
||||
# built in's
|
||||
import os
|
||||
import site
|
||||
|
||||
_MODULE_PATH = os.path.abspath(__file__)
|
||||
|
||||
_DCCSIG_REL_PATH = "../../../.."
|
||||
_DCCSIG_PATH = os.path.join(_MODULE_PATH, _DCCSIG_REL_PATH)
|
||||
_DCCSIG_PATH = os.path.normpath(_DCCSIG_PATH)
|
||||
|
||||
_DCCSIG_PATH = os.getenv('DCCSIG_PATH',
|
||||
os.path.abspath(_DCCSIG_PATH))
|
||||
|
||||
# we don't have access yet to the DCCsi Lib\site-packages
|
||||
site.addsitedir(_DCCSIG_PATH) # PYTHONPATH
|
||||
|
||||
# azpy bootstrapping and extensions
|
||||
import azpy.config_utils
|
||||
_config = azpy.config_utils.get_dccsi_config()
|
||||
settings = _config.get_config_settings(setup_ly_pyside=True)
|
||||
|
||||
from main import launch_kitbash_converter
|
||||
|
||||
launch_kitbash_converter()
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"general": {
|
||||
"texcoord": 0
|
||||
},
|
||||
"ambientOcclusion": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"emissive": {
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"enable": false,
|
||||
"intensity": -5.0,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"metallic": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"roughness": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif"
|
||||
},
|
||||
"specularF0": {
|
||||
"factor": 0.5,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"normal": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif"
|
||||
},
|
||||
"subsurfaceScattering": {
|
||||
"enableSubsurfaceScattering": true,
|
||||
"influenceMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif",
|
||||
"quality": 0.4,
|
||||
"scatterDistance": 8,
|
||||
"subsurfaceScatterFactor": 1
|
||||
},
|
||||
"opacity": {
|
||||
"mode": "Opaque",
|
||||
"alphaSource": "Packed",
|
||||
"doubleSided": false,
|
||||
"factor": 1.0,
|
||||
"cutoutAlpha": false,
|
||||
"cutoutThreshold": 0.5,
|
||||
"useBaseColorTextureAlpha": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"uv": {
|
||||
"center": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"offsetU": 0,
|
||||
"offsetV": 0,
|
||||
"rotateDegrees": 0,
|
||||
"scale": 1.0,
|
||||
"tileU": 1.0,
|
||||
"tileV": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.temp
|
||||
materialsdb.dat
|
||||
materialsdb.dir
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
:: coding:utf-8
|
||||
:: !/usr/bin/python
|
||||
::
|
||||
:: All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
:: its licensors.
|
||||
::
|
||||
:: For complete copyright and license terms please see the LICENSE at the root of this
|
||||
:: distribution (the "License"). All use of this software is governed by the License,
|
||||
:: or, if provided, by the license below or the license accompanying this file. Do not
|
||||
:: remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
::
|
||||
|
||||
@echo off
|
||||
:: Set up and run LY Python CMD prompt
|
||||
:: Sets up the DccScriptingInterface_Env,
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface Cmd
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
|
||||
CALL %~dp0\Project_Env.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCC Scripting Interface CMD ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Create command prompt with environment
|
||||
CALL %windir%\system32\cmd.exe
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
:: coding:utf-8
|
||||
:: !/usr/bin/python
|
||||
::
|
||||
:: All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
:: its licensors.
|
||||
::
|
||||
:: For complete copyright and license terms please see the LICENSE at the root of this
|
||||
:: distribution (the "License"). All use of this software is governed by the License,
|
||||
:: or, if provided, by the license below or the license accompanying this file. Do not
|
||||
:: remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
::
|
||||
|
||||
@echo off
|
||||
:: Launches maya with a bunch of local hooks for Lumberyard
|
||||
:: ToDo: move all of this to a .json data driven boostrapping system
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
echo ________________________________
|
||||
echo ~ calling PROJ_Env.bat
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
|
||||
:: PY version Major
|
||||
set DCCSI_PY_VERSION_MAJOR=2
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
|
||||
:: PY version Major
|
||||
set DCCSI_PY_VERSION_MINOR=7
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
|
||||
:: Maya Version
|
||||
set MAYA_VERSION=2020
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
|
||||
:: if a local customEnv.bat exists, run it
|
||||
IF EXIST "%~dp0Project_Env.bat" CALL %~dp0Project_Env.bat
|
||||
|
||||
echo ________________________________
|
||||
echo Launching Maya %MAYA_VERSION% for Lumberyard...
|
||||
|
||||
:::: Set Maya native project acess to this project
|
||||
::set MAYA_PROJECT=%LY_PROJECT%
|
||||
::echo MAYA_PROJECT = %MAYA_PROJECT%
|
||||
|
||||
:: DX11 Viewport
|
||||
Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11
|
||||
|
||||
:: Default to the right version of Maya if we can detect it... and launch
|
||||
IF EXIST "%MAYA_LOCATION%\bin\Maya.exe" (
|
||||
start "" "%MAYA_LOCATION%\bin\Maya.exe" %*
|
||||
) ELSE (
|
||||
Where maya.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo Maya.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" Maya.exe %*
|
||||
)
|
||||
)
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
|
||||
exit /b 0
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
:: coding:utf-8
|
||||
:: !/usr/bin/python
|
||||
::
|
||||
:: All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
:: its licensors.
|
||||
::
|
||||
:: For complete copyright and license terms please see the LICENSE at the root of this
|
||||
:: distribution (the "License"). All use of this software is governed by the License,
|
||||
:: or, if provided, by the license below or the license accompanying this file. Do not
|
||||
:: remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
::
|
||||
|
||||
@echo off
|
||||
:: Launches Wing IDE and the DccScriptingInterface Project Files
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up LY DCCsi WingIDE Dev Env...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
|
||||
SET ABS_PATH=%~dp0
|
||||
echo Current Dir, %ABS_PATH%
|
||||
|
||||
:: WingIDE version Major
|
||||
SET WING_VERSION_MAJOR=7
|
||||
echo WING_VERSION_MAJOR = %WING_VERSION_MAJOR%
|
||||
|
||||
:: WingIDE version Major
|
||||
SET WING_VERSION_MINOR=1
|
||||
echo WING_VERSION_MINOR = %WING_VERSION_MINOR%
|
||||
|
||||
:: note the changed path from IDE to Pro
|
||||
set WINGHOME=%PROGRAMFILES(X86)%\Wing Pro %WING_VERSION_MAJOR%.%WING_VERSION_MINOR%
|
||||
echo WINGHOME = %WINGHOME%
|
||||
|
||||
CALL %~dp0\Project_Env.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ WingIDE Version %WING_VERSION_MAJOR%.%WING_VERSION_MINOR%
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
SET WING_PROJ=%DCCSIG_PATH%\Solutions\.wing\DCCsi_%WING_VERSION_MAJOR%x.wpr
|
||||
echo WING_PROJ = %WING_PROJ%
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Launching %LY_PROJECT% project in WingIDE %WING_VERSION_MAJOR%.%WING_VERSION_MINOR% ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
|
||||
IF EXIST "%WINGHOME%\bin\wing.exe" (
|
||||
start "" "%WINGHOME%\bin\wing.exe" "%WING_PROJ%"
|
||||
) ELSE (
|
||||
Where wing.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo wing.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" wing.exe "%WING_PROJ%"
|
||||
)
|
||||
)
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
:: coding:utf-8
|
||||
:: !/usr/bin/python
|
||||
::
|
||||
:: All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
:: its licensors.
|
||||
::
|
||||
:: For complete copyright and license terms please see the LICENSE at the root of this
|
||||
:: distribution (the "License"). All use of this software is governed by the License,
|
||||
:: or, if provided, by the license below or the license accompanying this file. Do not
|
||||
:: remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
:: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
::
|
||||
|
||||
@echo off
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
for %%a in (.) do set LY_PROJECT=%%~na
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up LY DSI PROJECT Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo LY_PROJECT = %LY_PROJECT%
|
||||
|
||||
:: Put you project env vars and overrides here
|
||||
|
||||
:: chanhe the relative path up to dev
|
||||
set DEV_REL_PATH=../../..
|
||||
set ABS_PATH=%~dp0
|
||||
|
||||
:: Override the default maya version
|
||||
set MAYA_VERSION=2020
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
|
||||
set LY_PROJECT_PATH=%ABS_PATH%
|
||||
echo LY_PROJECT_PATH = %LY_PROJECT_PATH%
|
||||
|
||||
:: Change to root Lumberyard dev dir
|
||||
CD /d %LY_PROJECT_PATH%\%DEV_REL_PATH%
|
||||
set LY_DEV=%CD%
|
||||
echo LY_DEV = %LY_DEV%
|
||||
|
||||
CALL %LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Env.bat
|
||||
|
||||
rem :: Constant Vars (Global)
|
||||
rem SET LYPY_GDEBUG=0
|
||||
rem echo LYPY_GDEBUG = %LYPY_GDEBUG%
|
||||
rem SET LYPY_DEV_MODE=0
|
||||
rem echo LYPY_DEV_MODE = %LYPY_DEV_MODE%
|
||||
rem SET LYPY_DEBUGGER=WING
|
||||
rem echo LYPY_DEBUGGER = %LYPY_DEBUGGER%
|
||||
|
||||
:: Restore original directory
|
||||
popd
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %ABS_PATH%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0User_Env.bat" CALL %~dp0User_Env.bat
|
||||
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
__all__ = [
|
||||
'create_maya_files',
|
||||
'constants'
|
||||
'image_conversion',
|
||||
'isolate_and_assign',
|
||||
'lumberyard_data',
|
||||
'cli_control',
|
||||
'utilities',
|
||||
'main'
|
||||
]
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import click
|
||||
import os
|
||||
import main as app_main
|
||||
|
||||
|
||||
@click.version_option('1.0.0')
|
||||
@click.option('--output', default='PBR', help='Lumberyard material type. Current options: [pbr_basic]')
|
||||
@click.argument('operands', type=click.STRING, nargs=-1)
|
||||
@click.command(context_settings=dict(ignore_unknown_options=True))
|
||||
def main(output, operands):
|
||||
target_files = []
|
||||
for index, operand in enumerate(operands):
|
||||
entry_path = os.path.abspath(str(operand))
|
||||
if os.path.isdir(entry_path):
|
||||
for directory_path, directory_names, file_names in os.walk(entry_path):
|
||||
for file_name in file_names:
|
||||
if is_valid_file(file_name):
|
||||
target_files.append(os.path.join(entry_path, file_name))
|
||||
else:
|
||||
if is_valid_file(operand):
|
||||
target_files.append(operand)
|
||||
|
||||
if len(target_files):
|
||||
app_main.launch_cli(output, target_files)
|
||||
|
||||
|
||||
def is_valid_file(file_name):
|
||||
target_extensions = 'ma mb fbx blend max'.split(' ')
|
||||
if file_name.split('.')[-1] in target_extensions:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
# File Description:
|
||||
# This is an area to make customized changes in how the script looks for and processes files
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
IMAGE_TYPES = ['.tif', '.tiff', '.png', '.jpg', '.jpeg', '.tga']
|
||||
|
||||
DIRECTORY_EXCLUSION_LIST = ['.mayaSwatches']
|
||||
|
||||
LUMBERYARD_DATA_FILES = ['.mtl', '.assetinfo', '.material']
|
||||
|
||||
EXPORT_MATERIAL_TYPES = ['Illum']
|
||||
|
||||
PREFERRED_IMAGE_FORMAT = '.tif'
|
||||
|
||||
TRANSFER_EXTENSIONS = ['.fbx', '.assetinfo']
|
||||
|
||||
IMAGE_KEYS = {
|
||||
'ddn': ['ddn'],
|
||||
'ddna': ['ddna'],
|
||||
'diffuse': ['diffuse', 'diff', 'dif', 'd'],
|
||||
'emissive': ['emis', 'emissive', 'e', 'emiss'],
|
||||
'specular': ['spec', 'specular'],
|
||||
'scattering': ['scattering', 'sss'],
|
||||
'normal': ['normal'],
|
||||
'basecolor': ['basecolor', 'albedo']
|
||||
}
|
||||
|
||||
LOAD_WEIGHTS = {
|
||||
'maya': 4,
|
||||
'fbx': 2,
|
||||
'material': 1
|
||||
}
|
||||
|
||||
SUPPORTED_MATERIAL_PROPERTIES = ['general', 'ambientOcclusion', 'baseColor', 'emissive',
|
||||
'metallic', 'roughness', 'specularF0', 'normal', 'opacity', 'uv']
|
||||
|
||||
MATERIAL_SUFFIX_LIST = ['_mat', '_m']
|
||||
|
||||
DIELECTRIC_METALLIC_COLOR = (.04, .04, .04)
|
||||
|
||||
# For more information see OpenImageIO docs
|
||||
# https://openimageio.readthedocs.io/en/latest/pythonbindings.html
|
||||
# Recommended weight values for:
|
||||
# ImageBufAlgo.channel_sum
|
||||
WEIGHTED_RGB_VALUES = (.2126, .7152, .0722)
|
||||
|
||||
# MATERIAL DB
|
||||
FBX_DIRECTORY_PATH = 'directorypath'
|
||||
FBX_DIRECTORY_NAME = 'directoryname'
|
||||
FBX_FILES = 'fbxfiles'
|
||||
FBX_MATERIALS = 'materials'
|
||||
FBX_TEXTURES = 'textures'
|
||||
FBX_MAYA_FILE = 'mayafile'
|
||||
FBX_DESIGNER_FILE = 'designerfile'
|
||||
FBX_TEXTURE_MODIFICATIONS = 'modifications'
|
||||
FBX_NUMERICAL_SETTINGS = 'numericalsettings'
|
||||
FBX_MATERIAL_FILE = 'materialfile'
|
||||
FBX_ASSIGNED_GEO = 'assigned'
|
||||
|
||||
# Threshold values for baked vertex color
|
||||
# id mask images when no UVs present
|
||||
EMPTY_IMAGE_LOW = 260000
|
||||
EMPTY_IMAGE_LOW = 270000
|
||||
+366
@@ -0,0 +1,366 @@
|
||||
# coding:utf-8
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# This file is designed to run in Maya Standalone- it imports existing FBX files and reformats materials as Stingray
|
||||
# PBS materials, and attempts to attach PBR Metal/Rough texture sets generated for each asset. There is currently a
|
||||
# bug in Maya that prevents this from being possible, although it has been officially logged with Autodesk so hopefully
|
||||
# a fix will exist in an updated version
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# maya imports
|
||||
from PySide2 import QtCore
|
||||
import maya.cmds as mc
|
||||
import maya.standalone
|
||||
maya.standalone.initialize(name='python')
|
||||
mc.loadPlugin("fbxmaya")
|
||||
import maya.mel as mel
|
||||
mel.eval('loadPlugin fbxmaya')
|
||||
|
||||
# built-ins
|
||||
import logging as _logging
|
||||
import shelve
|
||||
import json
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
# local tool imports
|
||||
import constants
|
||||
|
||||
|
||||
module_name = 'legacy_asset_converter.create_maya_files'
|
||||
_LOGGER = _logging.getLogger(module_name)
|
||||
|
||||
|
||||
class CreateMayaFiles(QtCore.QObject):
|
||||
def __init__(self, files_list, base_directory, destination_directory, modify_naming, parent=None):
|
||||
super(CreateMayaFiles, self).__init__(parent)
|
||||
self.files_list = files_list
|
||||
self.base_directory = base_directory
|
||||
self.destination_directory = destination_directory
|
||||
self.modify_naming = modify_naming
|
||||
self.scene_shader_info = None
|
||||
self.materials_db = shelve.open('materialsdb', protocol=2)
|
||||
self.new_file = True
|
||||
self.file_name = None
|
||||
self.target_database_listing = None
|
||||
self.material_list = {}
|
||||
self.transfer_data = {}
|
||||
self.create_maya_files()
|
||||
|
||||
def create_maya_files(self):
|
||||
"""
|
||||
The main function of the script- this runs when the standalone session begins, and creates a Maya file
|
||||
based on each FBX file the script is passed through the subprocess that launches it. Due to the bug mentioned
|
||||
in the description above, a template file ('stingray_helper.ma') is loaded into the file so Maya at least
|
||||
knows what a Stingray material is- the root of the bug is that when it loads up it is unaware of the Stingray
|
||||
material node as well as its extended attributes. This workaround informs Maya of what a Stingray material is,
|
||||
although it is limited to this information only- it still doesn't know what the attributes of the material are
|
||||
that are necessary for making texture connections. This function processes each file, makes a note of it, and
|
||||
continues to move through the passed list until all FBX files have had companion Maya files created, and then
|
||||
returns a receipt of converted files.
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Base Directory: {}'.format(self.base_directory))
|
||||
_LOGGER.info('Destination Directory: {}'.format(self.destination_directory))
|
||||
_LOGGER.info('Files List: {}'.format(self.files_list))
|
||||
|
||||
for count, fbx_file in enumerate(self.files_list):
|
||||
target_file_path = os.path.join(self.base_directory, fbx_file)
|
||||
target_file_path = target_file_path.replace('\\', '/')
|
||||
self.file_name = os.path.basename(fbx_file)
|
||||
maya_file_name = os.path.join(self.destination_directory,'{}.ma'.format(os.path.splitext(self.file_name)[0]))
|
||||
sys.stdout.write('maya_{}'.format(maya_file_name))
|
||||
self.flush_then_wait()
|
||||
_LOGGER.info('\n\n++++++++++\n++++++++++>>>>\n {}\n++++++++++>>>>\n++++++++++\n'.format(self.file_name))
|
||||
|
||||
if not os.path.isfile(maya_file_name):
|
||||
try:
|
||||
self.reset_transfer_values()
|
||||
mc.file('stingray_helper.ma', o=True, force=True)
|
||||
mc.listAttr('StingrayTemplate')
|
||||
mc.listAttr('StingrayTemplate')
|
||||
mc.file(target_file_path, i=True, type="FBX")
|
||||
self.get_file_information(self.file_name, True)
|
||||
self.replace_materials()
|
||||
mc.delete('stingrayHelper')
|
||||
# mel.eval('cleanUpScene 3')
|
||||
mc.file(rename=maya_file_name)
|
||||
mc.file(save=True, type='mayaAscii', force=True)
|
||||
_LOGGER.info('Maya File Processing Complete... Transferring:')
|
||||
_LOGGER.info('FileName: {}'.format(self.file_name))
|
||||
_LOGGER.info('MayaFileName: {}'.format(maya_file_name))
|
||||
self.transfer_data[self.file_name] = {'maya_file': maya_file_name}
|
||||
except Exception as e:
|
||||
_LOGGER.info('Error Processing File: {} -- {}'.format(e, target_file_path))
|
||||
else:
|
||||
mc.file(maya_file_name, force=True, o=True)
|
||||
self.get_file_information(fbx_file, false)
|
||||
try:
|
||||
return_dictionary = {self.target_database_listing: self.transfer_data}
|
||||
json.dump(return_dictionary, sys.stdout)
|
||||
self.materials_db.close()
|
||||
except Exception as e:
|
||||
_LOGGER.info('Error: {} -- {}'.format(e, self.transfer_data))
|
||||
|
||||
def flush_then_wait(self):
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
time.sleep(0.5)
|
||||
|
||||
def get_file_information(self, fbx_file, new_file):
|
||||
"""
|
||||
Draws information relating to the target directory of the shelve database to gather texture assignments
|
||||
:param fbx_file:
|
||||
:param new_file:
|
||||
:return:
|
||||
"""
|
||||
self.new_file = new_file
|
||||
for key, values in self.materials_db.items():
|
||||
if os.path.normpath(values['directorypath']) == self.base_directory:
|
||||
self.target_database_listing = key
|
||||
self.material_list = values['fbxfiles'][fbx_file]
|
||||
break
|
||||
|
||||
def reset_transfer_values(self):
|
||||
"""
|
||||
Refreshes variables in between each FBX file processed
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.new_file = True
|
||||
self.material_list.clear()
|
||||
|
||||
def replace_materials(self):
|
||||
"""
|
||||
Finds the currently assigned Phong and Lambert shaders customary with Legacy material setups in FBX files
|
||||
and replaces with Stingray PBS materials.
|
||||
:return:
|
||||
"""
|
||||
for key, values in self.material_list['materials'].items():
|
||||
_LOGGER.info('\n_\n---------|| Processing material: {}'.format(key))
|
||||
if self.new_file:
|
||||
try:
|
||||
material_name = str(self.resolve_legacy_material(key))
|
||||
_LOGGER.info('MaterialName: {}'.format(material_name))
|
||||
_LOGGER.info('MaterialInfo: {}'.format(values))
|
||||
if values['assigned']:
|
||||
target_assignments = values['assigned']
|
||||
_LOGGER.info('TargetAssignments: {}'.format(target_assignments))
|
||||
sha, sg = self.get_material(material_name)
|
||||
_LOGGER.info('ShaderInfo: {}, {}'.format(sha, sg))
|
||||
if target_assignments:
|
||||
_LOGGER.info('Target Assignments exist- Assigning Shader...')
|
||||
self.set_material(sha, sg, target_assignments)
|
||||
# Disabled until Autodesk fixes bug
|
||||
# self.set_texture_maps(material_name, values['textures'])
|
||||
except Exception as e:
|
||||
_LOGGER.info('Cannot replace materials... Error: {}'.format(e))
|
||||
|
||||
def resolve_legacy_material(self, material_name):
|
||||
"""
|
||||
Deletes the legacy Phong and Lambert materials so it can name newly generated Stingray materials with the
|
||||
same name.
|
||||
:param material_name: Legacy material name to be re-applied to new material
|
||||
:return:
|
||||
"""
|
||||
mc.delete(material_name)
|
||||
for listing in constants.MATERIAL_SUFFIX_LIST:
|
||||
if material_name.lower().endswith(listing) and self.modify_naming == 'True':
|
||||
string_length = len(listing) * -1
|
||||
new_material_name = material_name[:string_length]
|
||||
self.material_list[new_material_name] = self.material_list[material_name]
|
||||
del self.material_list[material_name]
|
||||
return new_material_name
|
||||
return material_name
|
||||
|
||||
def get_texture_modifications(self, material_name):
|
||||
"""
|
||||
Checks for tiling information drawn from the corresponding mtl file. Because the attributes are currently
|
||||
not able to be accessed, this function has been tabled for now
|
||||
|
||||
:param material_name: Target material name
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Scanning for texture modifications...')
|
||||
for material in self.textures_modifications:
|
||||
_LOGGER.info('Modification listing:::: {}'.format(material))
|
||||
|
||||
def get_material(self, material_name):
|
||||
"""
|
||||
Gets material assignments and swaps legacy formatted materials with Stingray PBS materials
|
||||
:param material_name: Target material name
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Shaders in scene: {}'.format(self.get_materials_in_scene()))
|
||||
sha = None
|
||||
sg = None
|
||||
if material_name in self.get_materials_in_scene():
|
||||
_LOGGER.info('Deleting material: {}'.format(material_name))
|
||||
mc.delete(material_name)
|
||||
|
||||
_LOGGER.info('Creating Stingray material: {}'.format(material_name))
|
||||
try:
|
||||
sha = mc.shadingNode('StingrayPBS', asShader=True, name=material_name)
|
||||
sg = mc.sets(renderable=True, noSurfaceShader=True, empty=True)
|
||||
mc.connectAttr(sha + '.outColor', sg + '.surfaceShader', force=True)
|
||||
except Exception as e:
|
||||
_LOGGER.info('Shader creation failed: {}'.format(e))
|
||||
return sha, sg
|
||||
|
||||
def get_scene_shader_info(self):
|
||||
"""
|
||||
Audits scene to gather all materials present in scene geometry (that is to be converted)
|
||||
:return:
|
||||
"""
|
||||
self.scene_shader_info = {}
|
||||
scene_geo = mc.ls(v=True, geometry=True)
|
||||
for target_mesh in scene_geo:
|
||||
try:
|
||||
shading_groups = list(set(mc.listConnections(target_mesh, type='shadingEngine')))
|
||||
for sg in shading_groups:
|
||||
if sg not in self.scene_shader_info.keys():
|
||||
self.scene_shader_info[sg] = list(set(mc.ls(mc.listConnections(sg), materials=True)))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def get_materials_in_scene(self):
|
||||
"""
|
||||
Audits scene to gather all materials present in the Hypershade, and returns them in a list
|
||||
:return:
|
||||
"""
|
||||
material_list = []
|
||||
for shading_engine in mc.ls(type='shadingEngine'):
|
||||
if mc.sets(shading_engine, q=True):
|
||||
for material in mc.ls(mc.listConnections(shading_engine), materials=True):
|
||||
material_list.append(material)
|
||||
return material_list
|
||||
|
||||
def set_material(self, sha, sg, assignment_list):
|
||||
"""
|
||||
Assigns specified material to specified mesh
|
||||
:param sha: Material name
|
||||
:param sg: Shading Group
|
||||
:param assignment_list: List of geometry to assign shader to
|
||||
:return:
|
||||
"""
|
||||
assignment_list = list(set([x.replace('.', '|') for x in assignment_list]))
|
||||
_LOGGER.info('\n_\nSET MATERIAL: {} {{{{{{{{{{{{{{{{{{{{{{'.format(sha))
|
||||
_LOGGER.info('Assignment list--> {}'.format(assignment_list))
|
||||
for item in assignment_list:
|
||||
try:
|
||||
mc.sets(item, e=True, forceElement=sg)
|
||||
except Exception as e:
|
||||
_LOGGER.info('Material assignment failed: {}'.format(e))
|
||||
|
||||
def set_texture_maps(self, material_name, texture_list):
|
||||
"""
|
||||
Plugs texture files into texture slots in the shader for specified material name. Currently due to the
|
||||
aforementioned bug this functionality does not work as intended, but it remains for when Autodesk supplies a fix
|
||||
:param material_name:
|
||||
:param texture_list:
|
||||
:return:
|
||||
"""
|
||||
shader_translation_keys = {
|
||||
'BaseColor': 'color',
|
||||
'Roughness': 'roughness',
|
||||
'Metallic': 'metallic',
|
||||
'Emissive': 'emissive',
|
||||
'Normal': 'normal'
|
||||
}
|
||||
|
||||
_LOGGER.info('\n_\nSET_TEXTURE_MAPS {{{{{{{{{{{{{{{{{{{{{{')
|
||||
_LOGGER.info('Material--> {}'.format(material_name))
|
||||
for texture_type, texture_path in texture_list.items():
|
||||
try:
|
||||
_LOGGER.info('Texture type: {} Texture path: {}'.format(texture_type, texture_path))
|
||||
file_count = len(mc.ls(type='file')) + 1
|
||||
texture_file = 'file{}'.format(file_count)
|
||||
mc.shadingNode('file', asTexture=True, name=texture_file)
|
||||
mc.setAttr('{}.fileTextureName'.format(texture_file), texture_path, type="string")
|
||||
_LOGGER.info('TexturePath: {}'.format(texture_path))
|
||||
_LOGGER.info('Attributes: {}'.format(mc.listAttr(material_name)))
|
||||
mc.setAttr('{}.use_{}_map'.format(material_name, shader_translation_keys[texture_type]), 1)
|
||||
mc.connectAttr('{}.outColor'.format(texture_file),
|
||||
'{}.TEX_{}_map'.format(material_name, shader_translation_keys[texture_type]), force=True)
|
||||
except Exception as e:
|
||||
_LOGGER.info('Conversion failed: {}'.format(e))
|
||||
|
||||
# Revise to make generic and put in Maya utility class --------------------------------------------------------
|
||||
# def get_textures_by_connections(self, material_name):
|
||||
# _LOGGER.info(':::::: Get textures by connections ::::::')
|
||||
# material_files = [x for x in mc.listConnections(material_name, plugs=1, source=1) if x.startswith('file')]
|
||||
# searched_values = []
|
||||
# for material_file in material_files:
|
||||
# try:
|
||||
# texture_path = mc.getAttr('{}.fileTextureName'.format(material_file.split('.')[0]))
|
||||
# texture_path_list = texture_path.split('/')
|
||||
# if 'ShaderFX' not in texture_path_list:
|
||||
# if texture_path:
|
||||
# search_string = self.get_base_texture_name(texture_path)
|
||||
# if search_string not in searched_values:
|
||||
# _LOGGER.info('TexturePath: {}'.format(texture_path))
|
||||
# searched_values.append(search_string)
|
||||
# search_results_list = self.get_texture_set(search_string, material_name)
|
||||
# if search_results_list:
|
||||
# self.material_list[material_name] = search_results_list
|
||||
#
|
||||
# except mc.MayaAttributeError:
|
||||
# pass
|
||||
|
||||
# def get_texture_set(self, search_string, material_name):
|
||||
# target_key = self.base_directory.split('\\')[-1]
|
||||
# for key, values in self.materials_db.items():
|
||||
# if values['directoryname'] == target_key:
|
||||
# for texture_key, texture_set in values['textures'].items():
|
||||
# for k, v in texture_set.items():
|
||||
# texture_base = self.get_base_texture_name(v).lower()
|
||||
# if texture_base.find('_') != -1:
|
||||
# texture_base = '_'.join(texture_base.split('_')[:-1])
|
||||
# if texture_base == search_string:
|
||||
# _LOGGER.info('MatchFound:::::::::::::::')
|
||||
# _LOGGER.info('Finding modifications... MaterialName: {}'.format(material_name))
|
||||
# if material_name in self.textures_modifications.keys():
|
||||
# texture_set['modifications'] = self.textures_modifications[material_name]
|
||||
# _LOGGER.info('Texture modifications found: {}'.format(self.textures_modifications[material_name]))
|
||||
# return texture_set
|
||||
# return None
|
||||
|
||||
# def get_base_texture_name(self, texture_path):
|
||||
# path_base = os.path.basename(texture_path)
|
||||
# base_texture_name = os.path.splitext(path_base)[0]
|
||||
# if path_base.find('_') != -1:
|
||||
# base = path_base.split('_')[-1]
|
||||
# suffix = base.split('.')[0]
|
||||
# naming_key_found = None
|
||||
# for key, values in self.texture_naming_dict.items():
|
||||
# for v in values:
|
||||
# if suffix == v:
|
||||
# base_list = path_base.split('_')[:-1]
|
||||
# base_texture_name = '_'.join(base_list)
|
||||
# return base_texture_name
|
||||
# return base_texture_name
|
||||
|
||||
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
# Maya Specific Shader Mapping #
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
|
||||
_LOGGER.info('MAYA STANDALONE FILE FIRING: {}'.format(sys.argv))
|
||||
file_list = sys.argv[1:-3]
|
||||
base_directory = sys.argv[-3]
|
||||
destination_directory = sys.argv[-2]
|
||||
modify_naming = sys.argv[-1]
|
||||
CreateMayaFiles(file_list, base_directory, destination_directory, modify_naming)
|
||||
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
# coding:utf-8
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# This file is contains OpenImageIO operations for file texture conversions
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# built-ins
|
||||
from shutil import copyfile
|
||||
import logging as _logging
|
||||
from pathlib import Path
|
||||
import constants
|
||||
import os
|
||||
|
||||
# 3rd Party (we may or do provide)
|
||||
import OpenImageIO
|
||||
from OpenImageIO import ImageInput, ImageOutput, ImageBuf, ImageSpec, ImageBufAlgo, ROI
|
||||
|
||||
|
||||
module_name = 'legacy_asset_converter.main.image_conversion'
|
||||
_LOGGER = _logging.getLogger(module_name)
|
||||
|
||||
|
||||
def get_pbr_textures(legacy_textures, destination_directory, search_path, base_directory):
|
||||
pbr_textures = {}
|
||||
for texture_type, texture_path in legacy_textures.items():
|
||||
_LOGGER.info(f'TEXTURETYPE::> {texture_type} TEXTUREPATH::> {texture_path}')
|
||||
if not texture_path.is_file():
|
||||
_LOGGER.info(f'(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( Missing:::: {texture_path}')
|
||||
existing_path = resolve_path(texture_path, base_directory)
|
||||
if not existing_path:
|
||||
continue
|
||||
_LOGGER.info(f'Found texture [{existing_path}]. Continuing...')
|
||||
texture_path = Path(existing_path)
|
||||
if texture_type == 'diffuse':
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'BaseColor')
|
||||
pbr_textures['BaseColor'] = transfer_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
elif texture_type == 'specular':
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'Metallic')
|
||||
pbr_textures['Metallic'] = convert_metallic_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
elif texture_type == 'emittance':
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'Emissive')
|
||||
pbr_textures['Emissive'] = transfer_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
elif texture_type == 'scattering':
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'Scattering')
|
||||
pbr_textures['SubsurfaceScattering'] = transfer_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
elif texture_type == 'bumpmap':
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'Normal')
|
||||
pbr_textures['Normal'] = convert_normal_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
if texture_path.stem.endswith('ddna'):
|
||||
dst = get_converted_filename(texture_path, destination_directory, 'Roughness')
|
||||
pbr_textures['Roughness'] = convert_roughness_texture(dst, texture_path) if not os.path.isfile(dst) else dst
|
||||
else:
|
||||
pass
|
||||
|
||||
if pbr_textures and 'Metallic' not in pbr_textures.keys():
|
||||
_LOGGER.info(f'Metallic Not found... adding: {pbr_textures}')
|
||||
try:
|
||||
pbr_texture_keys = list(pbr_textures.keys())
|
||||
texture_path = Path(pbr_textures[pbr_texture_keys[0]])
|
||||
filename = texture_path.name.replace(get_texture_type(texture_path), 'Metallic')
|
||||
_LOGGER.info(f'Filename: {filename}')
|
||||
target_file_path = destination_directory / filename
|
||||
_LOGGER.info(f'TargetFilePath: {target_file_path}')
|
||||
if target_file_path.is_file():
|
||||
_LOGGER.info(f'File found: {target_file_path}')
|
||||
pbr_textures['Metallic'] = os.path.normpath(target_file_path)
|
||||
else:
|
||||
_LOGGER.info(f'Creating Generic Metallic File: {target_file_path}')
|
||||
convert_metallic_texture(target_file_path)
|
||||
pbr_textures['Metallic'] = target_file_path
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_LOGGER.info(f'OUTPUT // -------------->> {pbr_textures}')
|
||||
return pbr_textures
|
||||
|
||||
|
||||
def resolve_path(texture_path, base_directory):
|
||||
_LOGGER.info(f'Resolving Path... Filename[{texture_path.name}: {base_directory}')
|
||||
for (root, dirs, files) in os.walk(base_directory.parent, topdown=True):
|
||||
for file in files:
|
||||
target_file = file.split('.')[0].lower()
|
||||
if target_file == texture_path.stem.lower():
|
||||
return os.path.abspath(os.path.join(root, file))
|
||||
return None
|
||||
|
||||
|
||||
def transfer_texture(dst, src, overwrite=False):
|
||||
if not os.path.exists(dst) or overwrite:
|
||||
return Path(transfer_file(src, dst))
|
||||
return Path(dst)
|
||||
|
||||
|
||||
def convert_normal_texture(dst, src, overwrite=False):
|
||||
if not os.path.exists(dst) or overwrite:
|
||||
try:
|
||||
rgba = ImageBuf(str(src))
|
||||
spec = get_image_spec(rgba)
|
||||
rgb = ImageBufAlgo.channels(rgba, (0, 1, 2))
|
||||
write_image(rgba, dst, spec['format'])
|
||||
_LOGGER.info(f'Output Normal Map: {dst}')
|
||||
except Exception as e:
|
||||
_LOGGER.info(f'{src} -- Normal Map Conversion Failed. Error: {e}')
|
||||
return None
|
||||
return Path(dst)
|
||||
|
||||
|
||||
def convert_roughness_texture(dst, src, overwrite=False):
|
||||
if not os.path.exists(dst) or overwrite:
|
||||
try:
|
||||
rgba = ImageBuf(str(src))
|
||||
spec = get_image_spec(rgba)
|
||||
alpha = ImageBufAlgo.channels(rgba, (3,))
|
||||
roughness = ImageBufAlgo.invert(alpha)
|
||||
write_image(roughness, dst, spec['format'])
|
||||
_LOGGER.info(f'Output Roughness Map: {dst}')
|
||||
except Exception as e:
|
||||
_LOGGER.info(f'{src} -- Roughness Map Conversion Failed. Error: {e}')
|
||||
return None
|
||||
return Path(dst)
|
||||
|
||||
|
||||
def convert_metallic_texture(dst, src=None, overwrite=False):
|
||||
_LOGGER.info(f'Convert Metallic ::::: >> {dst}')
|
||||
if not os.path.exists(dst) or overwrite:
|
||||
try:
|
||||
if src:
|
||||
_LOGGER.info(f'Converting Metallic image using: {src}')
|
||||
# Get min/max pixels ---------------------->>
|
||||
buf = ImageBuf(str(src))
|
||||
minval, maxval = find_min_max(buf)
|
||||
buf.clear()
|
||||
print(f'Minval: {minval}')
|
||||
print(f'Maxval: {maxval}')
|
||||
|
||||
# Run remap filter using mix/max values -------->>
|
||||
buf = ImageBuf(str(src))
|
||||
spec = get_image_spec(buf)
|
||||
img_width = spec['resolution'][0]
|
||||
img_height = spec['resolution'][1]
|
||||
remap = get_contrast_remap(buf, minval, maxval)
|
||||
remove_color = remove_color_from_image(remap, dst)
|
||||
else:
|
||||
_LOGGER.info(f'Creating Dielectric Metallic Map')
|
||||
buf = ImageBuf(ImageSpec(4, 4, 1, "uint8"))
|
||||
ImageBufAlgo.fill(buf, constants.DIELECTRIC_METALLIC_COLOR, ROI(0, 1, 0, 1))
|
||||
write_image(buf, os.path.normpath(dst), 'uint8')
|
||||
_LOGGER.info(f'Output Metallic Map: {dst}')
|
||||
except Exception as e:
|
||||
src = 'None' if not src else src
|
||||
_LOGGER.info(f'{src} -- Metallic Map Conversion Failed. Error: {e}')
|
||||
return None
|
||||
return Path(dst)
|
||||
|
||||
|
||||
def get_contrast_remap(buf, minval, maxval):
|
||||
range_threshold = (maxval - minval) * .4
|
||||
ceiling_adjustment = .65
|
||||
white_point = float(round(maxval * ceiling_adjustment, 3))
|
||||
black_point = float(round(white_point - range_threshold, 3))
|
||||
percentage = 15
|
||||
black_point = (percentage * (white_point - black_point) / 100) + black_point
|
||||
remap = ImageBufAlgo.contrast_remap(buf, black=black_point, white=white_point)
|
||||
return remap
|
||||
|
||||
|
||||
def remove_color_from_image(buf, dst):
|
||||
"""
|
||||
Some operations leave color in what should remain grayscale images (i.e. 'contrast_remap'). This function attempts
|
||||
to gently remove color information without affecting the luminosity of the image
|
||||
:param buf:
|
||||
:param dst:
|
||||
:return:
|
||||
"""
|
||||
buf.read(convert='float')
|
||||
lin = ImageBufAlgo.colorconvert(buf, "sRGB", "linear")
|
||||
luma = ImageBufAlgo.channel_sum(buf, constants.WEIGHTED_RGB_VALUES)
|
||||
luma = ImageBufAlgo.colorconvert(luma, "linear", "sRGB")
|
||||
write_image(luma, dst, 'uint8')
|
||||
|
||||
|
||||
def find_min_max(buf):
|
||||
"""
|
||||
When attempting to automate the conversion of specular maps to metallic maps, the first step is to find high values
|
||||
of luminosity in the image and compare against the low values. The intention is to expose what would likely be
|
||||
metal and non-metal areas, and with this information attempt to clamp values to either black and white for the
|
||||
generation of the metallic map. This function decreases the size of the image to a manageable representation of
|
||||
pixel luminosity values, scans for the high and low values and returns those values to be further manipulated by
|
||||
the contrast remap filter
|
||||
:param buf: The image buffer containing the pixel information that needs to be scanned
|
||||
:return:
|
||||
"""
|
||||
# Resize Image ------------------->>
|
||||
goal_width = 512
|
||||
goal_height = 512
|
||||
|
||||
spec = buf.spec()
|
||||
w = spec.width
|
||||
h = spec.height
|
||||
nchans = spec.nchannels
|
||||
aspect = float(w) / float(h)
|
||||
if aspect >= 1.0:
|
||||
goal_height = int(h * goal_height / w)
|
||||
else:
|
||||
goal_width = (w * goal_width / h)
|
||||
resized = ImageBuf(ImageSpec(goal_width, goal_height, spec.nchannels, spec.format))
|
||||
ImageBufAlgo.resize(resized, buf)
|
||||
|
||||
# Scan buffer for min/max
|
||||
pixels = resized.get_pixels(OpenImageIO.UINT8)
|
||||
minval = 255
|
||||
maxval = 0
|
||||
test_dimensions = 512
|
||||
|
||||
for y in range(test_dimensions):
|
||||
for x in range(test_dimensions):
|
||||
pixel_value = pixels[y][x]
|
||||
average_value = sum(pixel_value) / 3
|
||||
|
||||
if average_value < minval:
|
||||
minval = average_value
|
||||
if average_value > maxval:
|
||||
maxval = average_value
|
||||
|
||||
minimum_level = minval / 255
|
||||
maximum_level = maxval / 255
|
||||
|
||||
return minimum_level, maximum_level
|
||||
|
||||
|
||||
def get_converted_filename(src, dst, texture_type):
|
||||
"""
|
||||
Takes the structure of an existing filename extracted from the legacy mtl files, and renames it, base on the
|
||||
texture type argument that is passed to it
|
||||
:param src: The legacy filename to be manipulated
|
||||
:param dst: The destination directory that the new file will be saved to
|
||||
:param texture_type: PBR texture type
|
||||
:return:
|
||||
"""
|
||||
if src.is_file():
|
||||
filename = src.name.replace(get_texture_type(src), texture_type)
|
||||
dst = os.path.normpath(dst / filename)
|
||||
_LOGGER.info(f'+=+=+=+=+=+=+=+=+=+=+=+ {dst} -- {os.path.isfile(dst)}')
|
||||
return dst
|
||||
|
||||
|
||||
def get_image_spec(target_image):
|
||||
"""
|
||||
Pulls metadata about the image to be leveraged in file operations
|
||||
:param target_image: Path to the target image intended for alterations
|
||||
:return:
|
||||
"""
|
||||
spec = target_image.spec()
|
||||
info = {'resolution': (spec.width, spec.height, spec.x, spec.y), 'channels': spec.channelnames,
|
||||
'format': str(spec.format)}
|
||||
if spec.channelformats:
|
||||
info['channelformats'] = str(spec.channelformats)
|
||||
info['alpha channel'] = str(spec.alpha_channel)
|
||||
info['z channel'] = str(spec.z_channel)
|
||||
info['deep'] = str(spec.deep)
|
||||
for i in range(len(spec.extra_attribs)):
|
||||
if type(spec.extra_attribs[i].value) == str:
|
||||
info[spec.extra_attribs[i].name] = spec.extra_attribs[i].value
|
||||
else:
|
||||
info[spec.extra_attribs[i].name] = spec.extra_attribs[i].value
|
||||
return info
|
||||
|
||||
|
||||
def get_texture_basename(image_path):
|
||||
"""
|
||||
Attempts to extract the naming convention base for each texture set
|
||||
:param image_path: Path to an image from the texture set to use for basename extraction
|
||||
:return:
|
||||
"""
|
||||
image_name = image_path.stem
|
||||
if image_name.find('_') != -1:
|
||||
image_components = image_name.split('_')
|
||||
return ('_').join(image_components[:-1])
|
||||
return None
|
||||
|
||||
|
||||
def get_texture_type(image_path):
|
||||
"""
|
||||
Gets the PBR texture type from the filename.
|
||||
:param image_path: Path to the image from which to extract pbr type
|
||||
:return:
|
||||
"""
|
||||
filename = Path(image_path)
|
||||
texture_type = filename.stem.split('_')[-1]
|
||||
return texture_type
|
||||
|
||||
|
||||
def set_image_spec(spec):
|
||||
"""
|
||||
Writes new specification for images manipulated or created when needed
|
||||
:param spec:
|
||||
:return:
|
||||
"""
|
||||
output_spec = ImageSpec()
|
||||
output_spec.set_format(OpenImageIO.UINT8)
|
||||
output_spec.width = spec['resolution'][0]
|
||||
output_spec.height = spec['resolution'][0]
|
||||
output_spec.nchannels = 1
|
||||
return output_spec
|
||||
|
||||
|
||||
def write_image(image, filename, image_format):
|
||||
"""
|
||||
Writes final processed image after operations have been completed and final result in buffer
|
||||
|
||||
:param image: Image buffer
|
||||
:param filename: Image name to save
|
||||
:param image_format: File format for export
|
||||
:return:
|
||||
"""
|
||||
if not image.has_error:
|
||||
image.set_write_format(image_format)
|
||||
image.write(filename)
|
||||
if image.has_error:
|
||||
_LOGGER.info(f'Error writing {filename}: {image.geterror()}')
|
||||
|
||||
|
||||
def transfer_file(src, dst):
|
||||
"""
|
||||
Some legacy textures only need to be renamed an moved to the processed folder with no further manipulation
|
||||
required. This function handles this transfer operation
|
||||
:param src: Source of the image intended for transfer
|
||||
:param dst: The destination path for the transferred image
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info(f'{src} +++TRANSFER FILE+++>> {dst}')
|
||||
try:
|
||||
copyfile(src, dst)
|
||||
return dst
|
||||
except Exception as e:
|
||||
_LOGGER.info(f'Copy error encountered: {e}')
|
||||
return None
|
||||
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
# coding:utf-8
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# This is a temporary workaround script for previewing Stingray materials applied to FBX files in material conversion.
|
||||
# The reason this is needed is because of a bug found in automating Stingray material assignmens
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
import pymel.core as pm
|
||||
import maya.mel as mel
|
||||
import constants
|
||||
import random
|
||||
import os
|
||||
|
||||
|
||||
def get_materials_in_scene():
|
||||
for shading_engine in pm.ls(type=pm.nt.ShadingEngine):
|
||||
if len(shading_engine):
|
||||
for material in shading_engine.surfaceShader.listConnections():
|
||||
yield material
|
||||
|
||||
|
||||
def get_file_textures(material_name):
|
||||
try:
|
||||
file_textures = []
|
||||
material_files = [x for x in pm.listConnections(material_name, plugs=1, source=1) if x.startswith('file')]
|
||||
for file_name in material_files:
|
||||
try:
|
||||
file_textures.append(pm.getAttr('{}.fileTextureName'.format(file_name.split('.')[0])))
|
||||
except pm.MayaAttributeError:
|
||||
pass
|
||||
return file_textures
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
def get_search_string(texture_name):
|
||||
"""
|
||||
Creates a "base" string derived from the passed texture name for finding associated files
|
||||
:param texture_name: The texture name from which to extract the search string
|
||||
:return:
|
||||
"""
|
||||
filename = os.path.basename(texture_name)
|
||||
base = filename.split('.')[0]
|
||||
basename_list = base.split('_')
|
||||
search_string = '_'.join(basename_list[:-1])
|
||||
return search_string
|
||||
|
||||
|
||||
def get_texture_type(search_string, file_name):
|
||||
"""
|
||||
Gets the type of PBR texture for assignments based on the texture filename
|
||||
:param search_string: The convention basename for file texture
|
||||
:param file_name: This is a filename extracted from the mtl file- used purely as a template for newly created PBR textures
|
||||
:return:
|
||||
"""
|
||||
texture_types = ['BaseColor', 'Metallic', 'Normal', 'Emissive', 'Roughness']
|
||||
target_string = file_name.replace(search_string, '')
|
||||
base = target_string.split('.')[0]
|
||||
if base[1:] in texture_types:
|
||||
return base[1:]
|
||||
return None
|
||||
|
||||
|
||||
def get_output_file_size(file_path):
|
||||
"""
|
||||
When creating texture masks for further processing, this function checks output filesize to determine if there are
|
||||
any UVs present for the object it's created for. There are many assets without UVS or with UVs that entirely
|
||||
encapsulate the 0 to 1 UV space- and they come out in the size fiting between the range below. Without any content
|
||||
there is no need for the generated mask- so it is summarily removed
|
||||
:param file_path: Path to the vertex map masks generated by object UVs
|
||||
:return:
|
||||
"""
|
||||
size = os.stat(file_path).st_size
|
||||
if (constants.EMPTY_IMAGE_LOW < size < constants.EMPTY_IMAGE_HIGH) == True:
|
||||
os.remove(file_path)
|
||||
|
||||
|
||||
def attach_texture(texture_type, material, shading_group_name, file_path):
|
||||
material_info = {
|
||||
'BaseColor': 'color',
|
||||
'Metallic': 'metallic',
|
||||
'Normal': 'normal',
|
||||
'Emissive': 'emissive',
|
||||
'Roughness': 'roughness'
|
||||
}
|
||||
|
||||
file_count = len(pm.ls(type='file')) + 1
|
||||
texture_file = 'file{}'.format(file_count)
|
||||
file_node = pm.shadingNode('file', asTexture=True, name=texture_file)
|
||||
pm.setAttr('{}.fileTextureName'.format(texture_file), file_path, type="string")
|
||||
pm.setAttr('{}.use_{}_map'.format(material, material_info[texture_type]), True)
|
||||
pm.connectAttr('{}.outColor'.format(texture_file), '{}.TEX_{}_map'.format(material, material_info[texture_type]),
|
||||
force=True)
|
||||
|
||||
|
||||
def combine_meshes():
|
||||
"""
|
||||
Combines geometry based on material assignment. This function was created before the incorporation of .assetinfo
|
||||
files while processing. I intend to revamp the system, as this practice was found to be problematic when LOD meshes
|
||||
were found in files containing the same materials. Leaving this in for now until a better system can be created as
|
||||
this still helps in many cases to produce a mesh for preview
|
||||
:return:
|
||||
"""
|
||||
pm.showHidden(all=True)
|
||||
pm.select(pm.ls(geometry=True))
|
||||
selected_geo = pm.selected()
|
||||
print(selected_geo)
|
||||
if len(selected_geo) > 1:
|
||||
combined_mesh = pm.polyUnite(selected_geo)
|
||||
pm.delete(ch=True)
|
||||
return combined_mesh[0]
|
||||
else:
|
||||
return selected_geo
|
||||
|
||||
|
||||
def create_material_masks(target_mesh, material_list, base_directory):
|
||||
"""
|
||||
Fuction for generating vertex map masks of existing UVs. If metallic maps needed to be constructed by hand this
|
||||
made mask assignments in Substance Designer a quicker process. There might still be a need for this process for
|
||||
other things so I'm leaving this in here for now- eventually it might land in a Maya utility class.
|
||||
:param target_mesh: The mesh to generate maps from
|
||||
:param material_list: The list of materials needing masks generated for
|
||||
:param base_directory: The directory to save vertex map masks to
|
||||
:return:
|
||||
"""
|
||||
container = separate_applied_materials(target_mesh)
|
||||
children = pm.listRelatives(container, children=True)
|
||||
|
||||
for item in children:
|
||||
if not item.endswith('_pbr_GEO'):
|
||||
pm.delete(item)
|
||||
else:
|
||||
pm.hide(item)
|
||||
|
||||
print('\n::::::::::::::::::::::::::::::::')
|
||||
print('Bake material masks')
|
||||
print('\n::::::::::::::::::::::::::::::::')
|
||||
print('Children: {}'.format(children))
|
||||
print('Material List: {}'.format(material_list))
|
||||
|
||||
for material in material_list:
|
||||
print('Material: {}'.format(material))
|
||||
separated_mesh_object = '{}_GEO'.format(material)
|
||||
if separated_mesh_object in children:
|
||||
pm.showHidden(separated_mesh_object)
|
||||
bake_vertex_colors(separated_mesh_object, material, base_directory)
|
||||
cleanup_object(separated_mesh_object, material)
|
||||
print('\n')
|
||||
|
||||
pm.setToolTo('selectSuperContext')
|
||||
pm.select(clear=True)
|
||||
|
||||
|
||||
def cleanup_object(target_mesh, material):
|
||||
"""
|
||||
Vertex masks are created using vertex colors, but once the maps have been generated the vertex colors should be
|
||||
removed. This function handles the cleanup process.
|
||||
:param target_mesh:
|
||||
:param material:
|
||||
:return:
|
||||
"""
|
||||
pm.select(target_mesh)
|
||||
pm.polyColorPerVertex(rem=True)
|
||||
pm.delete(ch=True)
|
||||
pm.hyperShade(assign=material)
|
||||
pm.hide()
|
||||
pm.select(clear=True)
|
||||
|
||||
|
||||
def bake_vertex_colors(target_mesh, material, base_directory):
|
||||
file_path = get_file_path(material, base_directory)
|
||||
print('Baking verts for: {} Path: {}'.format(target_mesh, file_path))
|
||||
pm.select(target_mesh)
|
||||
pm.hyperShade(assign='lambert1')
|
||||
mel.eval('artAttrColorPerVertexToolScript 4;')
|
||||
pm.polyOptions(colorMaterialChannel='DIFFUSE')
|
||||
pm.polyColorPerVertex(r=1, g=1, b=1, a=1, cdo=True)
|
||||
pm.select(clear=True)
|
||||
|
||||
try:
|
||||
pm.select(target_mesh, r=True)
|
||||
mel.eval('artAttrColorPerVertexToolScript 4;')
|
||||
context = pm.currentCtx()
|
||||
pm.artAttrPaintVertexCtx(context, e=True, esf=file_path, fsx=4096, fsy=4096)
|
||||
get_output_file_size(file_path)
|
||||
except RuntimeError as e:
|
||||
print('+++++++++++++++++')
|
||||
print('Bake Failed: {}'.format(e))
|
||||
print('+++++++++++++++++')
|
||||
|
||||
|
||||
def get_file_path(material, base_directory):
|
||||
texture_list = get_file_textures(material)
|
||||
for item in texture_list:
|
||||
search_string = get_search_string(item)
|
||||
target_file = os.path.join(base_directory, '{}_Metallic.tif'.format(search_string))
|
||||
if os.path.isfile(target_file):
|
||||
return target_file
|
||||
return None
|
||||
|
||||
|
||||
def separate_applied_materials(target_object):
|
||||
target_object = target_object[0] if isinstance(target_object, list) else target_object
|
||||
try:
|
||||
shading_groups = list(set(pm.listConnections(target_object.getShape(), type='shadingEngine')))
|
||||
except AttributeError:
|
||||
shading_groups = list(set(pm.listConnections(target_object, type='shadingEngine')))
|
||||
|
||||
materials_container = pm.group(empty=1, n='separated_materials')
|
||||
for count, sg in enumerate(shading_groups):
|
||||
material = pm.ls(pm.listConnections(sg), materials=True)[0]
|
||||
target_material = pm.listConnections(sg + '.surfaceShader')
|
||||
clone = '{}_GEO'.format(target_material[0])
|
||||
pm.duplicate(target_object, n=clone)
|
||||
pm.parent(clone, 'separated_materials')
|
||||
material = pm.listConnections(sg, s=True, d=False)
|
||||
pm.select(clone)
|
||||
pm.runtime.ConvertSelectionToFaces()
|
||||
temp_set = str(pm.sets())
|
||||
temp_grps = pm.listConnections(material[0], type='shadingEngine')
|
||||
pm.select(pm.sets(temp_grps[0], int=temp_set))
|
||||
pm.runtime.InvertSelection()
|
||||
pm.delete()
|
||||
|
||||
pm.delete(target_object)
|
||||
return materials_container
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
The entry function of the script. This finds existing materials on FBX objects, converts to Stingray materials,
|
||||
and assigns corresponding textures.
|
||||
:return:
|
||||
"""
|
||||
file_name = pm.sceneName()
|
||||
base_directory = os.path.dirname(pm.sceneName())
|
||||
print('Base Directory: {}'.format(base_directory))
|
||||
textured_materials = []
|
||||
for item in get_materials_in_scene():
|
||||
if str(type(item)) == "<class 'pymel.core.nodetypes.StingrayPBS'>":
|
||||
print('Material--> {}'.format(item))
|
||||
shading_group = pm.listConnections(item, type="shadingEngine")[0]
|
||||
file_textures = get_file_textures(item[:-4])
|
||||
if file_textures:
|
||||
print('FileTexture: {}'.format(file_textures))
|
||||
textured_materials.append(item)
|
||||
if len(file_textures) > 1:
|
||||
print('Multiple textures found :::::::::::: {}'.format(file_textures))
|
||||
print('Scene: {} Material: {}'.format(file_name, item))
|
||||
else:
|
||||
search_string = get_search_string(file_textures[0])
|
||||
if search_string:
|
||||
print('Search String: {}'.format(search_string))
|
||||
print('Found these related textures:')
|
||||
for target_file in os.listdir(base_directory):
|
||||
if target_file.startswith(search_string):
|
||||
print('-->>>>>> {}'.format(target_file))
|
||||
texture_type = get_texture_type(search_string, target_file)
|
||||
if texture_type:
|
||||
textured_materials.append(item)
|
||||
file_path = os.path.join(base_directory, target_file)
|
||||
attach_texture(texture_type, item, shading_group, file_path)
|
||||
|
||||
print('')
|
||||
|
||||
target_mesh = combine_meshes()
|
||||
textured_materials = list(set(textured_materials))
|
||||
create_material_masks(target_mesh, textured_materials, base_directory)
|
||||
pm.saveFile(force=True)
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
# coding:utf-8
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# This file contains Lumberyard specific file and helper functions for handling material conversion
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import logging as _logging
|
||||
from pathlib import Path
|
||||
from box import Box
|
||||
import constants
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
module_name = 'legacy_asset_converter.main.lumberyard_data'
|
||||
_LOGGER = _logging.getLogger(module_name)
|
||||
|
||||
|
||||
def walk_directories(target_path):
|
||||
"""
|
||||
Records file and directory structure information for all content within specified base folder for operation
|
||||
|
||||
:target path: The base directory from which to perform the os.walk
|
||||
:return:
|
||||
"""
|
||||
directory_audit = {}
|
||||
scan_type, base_directory = get_base_directory(target_path)
|
||||
base_directory_name = base_directory.name
|
||||
exclusion_list = constants.DIRECTORY_EXCLUSION_LIST
|
||||
|
||||
directory_index = 0
|
||||
for (root, dirs, files) in os.walk(base_directory, topdown=True):
|
||||
temp_dictionary = {}
|
||||
root = Path(root)
|
||||
directory_name = root.name
|
||||
is_object_directory = True if [x for x in root.iterdir() if x.suffix == '.fbx'] else False
|
||||
_LOGGER.info(f'\n_\nScanning Directory: {directory_name} -- Object directory? {is_object_directory}')
|
||||
|
||||
if is_object_directory:
|
||||
if directory_name not in exclusion_list:
|
||||
temp_dictionary = Box({'directoryname': directory_name, 'directorypath': str(root)})
|
||||
files_dictionary = scan_directory(root)
|
||||
|
||||
subdirectories = [x for x in root.iterdir() if x.is_dir()]
|
||||
for subdirectory in subdirectories:
|
||||
target_path = subdirectory.name
|
||||
if not [x for x in subdirectory.iterdir() if x.suffix == '.fbx'] and target_path \
|
||||
not in (exclusion_list +[base_directory_name]):
|
||||
files_dictionary.update(scan_directory(root / subdirectory))
|
||||
temp_dictionary['files'] = files_dictionary
|
||||
directory_audit[directory_index] = temp_dictionary
|
||||
directory_index += 1
|
||||
return Box(directory_audit)
|
||||
|
||||
|
||||
def scan_directory(directory_path):
|
||||
"""
|
||||
Finds all FBX files, which are needed for the materials conversion process
|
||||
|
||||
:param directory_path:
|
||||
:return:
|
||||
"""
|
||||
files_dictionary = {}
|
||||
for filename in directory_path.iterdir():
|
||||
filename = Path(filename)
|
||||
extension = filename.suffix
|
||||
if extension in (constants.IMAGE_TYPES + constants.LUMBERYARD_DATA_FILES + ['.fbx']):
|
||||
files_dictionary[extension] = [directory_path / filename] if filename.suffix not in list(files_dictionary) else \
|
||||
files_dictionary[extension] + [directory_path / filename]
|
||||
return files_dictionary
|
||||
|
||||
|
||||
def get_base_directory(target_path):
|
||||
"""
|
||||
The "walk" function can be passed both directory paths as well as file paths (for single asset processing). In the
|
||||
event that a file path is passed, this will get the file path's containing folder for auditing related files
|
||||
:param target_path: The path to check
|
||||
:return:
|
||||
"""
|
||||
base_directory = target_path
|
||||
scan_type = 'directory'
|
||||
if target_path.is_file():
|
||||
scan_type = 'file'
|
||||
base_directory = Path(target_path.parent)
|
||||
return scan_type, base_directory
|
||||
|
||||
|
||||
def get_material_info(target_directory, filename):
|
||||
"""
|
||||
Extracts material information from legacy .mtl files for conversion to the .material format
|
||||
:param target_directory:
|
||||
:param filename:
|
||||
:return:
|
||||
"""
|
||||
materials_list = {}
|
||||
texture_modifications = {}
|
||||
target_mtl = target_directory / filename
|
||||
if target_mtl.is_file():
|
||||
tree = ET.parse(target_mtl)
|
||||
root = tree.getroot()
|
||||
for material in root.iter('Material'):
|
||||
material_attributes = {}
|
||||
if 'Name' in material.attrib.keys():
|
||||
for key, value in material.attrib.items():
|
||||
material_attributes[key] = value
|
||||
|
||||
material_textures = {}
|
||||
for textures in material.findall('Textures'):
|
||||
for texture in textures.findall('Texture'):
|
||||
map_name = texture.get('Map')
|
||||
file_path = texture.get('File')
|
||||
material_textures[map_name.lower()] = Path(file_path)
|
||||
if texture.findall('TexMod'):
|
||||
listing = texture.findall('TexMod')
|
||||
for child in listing:
|
||||
texture_modifications[material_attributes['Name']] = child.attrib
|
||||
if material_attributes:
|
||||
temp_list = Box({'assigned': [], 'attributes': material_attributes, 'modifications': texture_modifications,
|
||||
'textures': material_textures})
|
||||
materials_list[material_attributes['Name']] = temp_list
|
||||
return Box(materials_list)
|
||||
|
||||
|
||||
def get_asset_info(target_directory, filename, target_material):
|
||||
"""
|
||||
Gathers material assignments for specified assets for .assetinfo files that typically accompany FBX files
|
||||
:param target_directory: The directory to search for .assetinfo file
|
||||
:param filename: The name of the FBX file for which information is being gathered
|
||||
:param target_material: The material contained in the FBX file that the script is finding geo assignments for
|
||||
:return:
|
||||
"""
|
||||
target_file = target_directory / filename.lower()
|
||||
assignment_list = []
|
||||
if target_file.is_file():
|
||||
try:
|
||||
tree = ET.parse(target_file)
|
||||
root = tree.getroot()
|
||||
for layer_listing in root.iter('Class'):
|
||||
target_value = layer_listing.get('value')
|
||||
if target_value and target_value.find('.') != -1:
|
||||
search_string = target_value.split('.')[-1]
|
||||
|
||||
if search_string == target_material:
|
||||
asset_list = target_value.split('.')
|
||||
crop_path = asset_list[2:-1]
|
||||
asset_path = '.'.join(crop_path)
|
||||
if asset_path not in assignment_list:
|
||||
assignment_list.append(asset_path)
|
||||
except Exception as e:
|
||||
_LOGGER.info('AssetInfo file found, but information extraction failed.')
|
||||
else:
|
||||
_LOGGER.info('AssetInfo file not found. Cannot gather material assignments.')
|
||||
return assignment_list
|
||||
|
||||
|
||||
def export_lumberyard_material(output, material_description):
|
||||
"""
|
||||
Takes one final dictionary with information gathered in the process and saves with JSON formatting into a
|
||||
.material file
|
||||
|
||||
:param output:
|
||||
:param material_description:
|
||||
:return:
|
||||
"""
|
||||
with open(output, 'w', encoding='utf-8') as material_file:
|
||||
json.dump(material_description, material_file, ensure_ascii=False, indent=4)
|
||||
|
||||
+1845
File diff suppressed because it is too large
Load Diff
+85
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"general": {
|
||||
"texcoord": 0
|
||||
},
|
||||
"ambientOcclusion": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"emissive": {
|
||||
"color": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"enable": false,
|
||||
"intensity": -5.0,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"metallic": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"roughness": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif"
|
||||
},
|
||||
"specularF0": {
|
||||
"factor": 0.5,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"normal": {
|
||||
"factor": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif"
|
||||
},
|
||||
"subsurfaceScattering": {
|
||||
"enableSubsurfaceScattering": true,
|
||||
"influenceMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif",
|
||||
"quality": 0.4,
|
||||
"scatterDistance": 8,
|
||||
"subsurfaceScatterFactor": 1
|
||||
},
|
||||
"opacity": {
|
||||
"doubleSided": false,
|
||||
"factor": 1.0,
|
||||
"cutoutAlpha": false,
|
||||
"cutoutThreshold": 0.5,
|
||||
"useBaseColorTextureAlpha": false,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"uv": {
|
||||
"center": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"offsetU": 0,
|
||||
"offsetV": 0,
|
||||
"rotateDegrees": 0,
|
||||
"scale": 1.0,
|
||||
"tileU": 1.0,
|
||||
"tileV": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d42de1023180dc9ff4f9db8c9718c0d476ecf1fe7be99e53b26ee7939ea55174
|
||||
size 133641
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08fa77d80e790d2580f4a6ae6b204357a091c4e675e038e4ef9e1c0bebb5b6f6
|
||||
size 11285
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import socket
|
||||
import pickle
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
# BUFFER_SIZE = 4096
|
||||
# port = 20201
|
||||
#
|
||||
# if len(sys.argv) > 1:
|
||||
# port = sys.argv[1]
|
||||
#
|
||||
# def SendCommand(target_command):
|
||||
# maya_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
# maya_socket.connect(('localhost', port))
|
||||
#
|
||||
# try:
|
||||
# maya_socket.send("import maya.cmds as cmds".encode())
|
||||
# data = maya_socket.recv(BUFFER_SIZE)
|
||||
# maya_socket.send("cmds.polySphere()".encode())
|
||||
# data = maya_socket.recv(BUFFER_SIZE)
|
||||
#
|
||||
# # This is a workaround for replacing "null bytes" and converting
|
||||
# # return information as a list (as opposed to a string)
|
||||
# result = eval(data.decode().replace('\x00', ''))
|
||||
# print(result[0])
|
||||
# except Exception as e:
|
||||
# print ('Connection Failed: {}'.format(e))
|
||||
# finally:
|
||||
# maya_socket.close()
|
||||
#
|
||||
#
|
||||
# # maya.send('import maya.cmds as mc; mc.polyCube()')
|
||||
# # maya.close()
|
||||
#
|
||||
# if __name__=='__main__':
|
||||
# target_command = "import maya.cmds as mc; mc.polySphere();"
|
||||
# SendCommand(target_command)
|
||||
|
||||
|
||||
|
||||
|
||||
#Port Number 20201
|
||||
# MayaVersion + 0 (Mel) or 1 (Python)
|
||||
#
|
||||
# import maya.cmds as mc
|
||||
# mc.commandPort(name=":20201", sourceType="python")
|
||||
#
|
||||
#
|
||||
# His user setup has this in it:
|
||||
#
|
||||
# if not mc.about(batch=True):
|
||||
# mc.commandPort(name=":20200", sourceType="mel")
|
||||
# mc.commandPort(name=":20201", sourceType="python")
|
||||
|
||||
|
||||
class MayaClient(object):
|
||||
PORT = 20201
|
||||
BUFFER_SIZE = 4096
|
||||
|
||||
def __init__(self):
|
||||
self.maya_socket = None
|
||||
self.port = self.__class__.PORT
|
||||
|
||||
def connect(self, port=-1):
|
||||
if port >= 0:
|
||||
self.port = port
|
||||
try:
|
||||
self.maya_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.maya_socket.connect(('localhost', self.PORT))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
try:
|
||||
self.maya_socket.close()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
return True
|
||||
|
||||
def send(self, cmd):
|
||||
try:
|
||||
self.maya_socket.sendall(cmd.encode())
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
return self.recv()
|
||||
|
||||
def recv(self):
|
||||
try:
|
||||
data = self.maya_socket.recv(MayaClient.BUFFER_SIZE)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
return data.decode().replace('\x00', '')
|
||||
|
||||
##################
|
||||
# COMMANDS #####
|
||||
##################
|
||||
|
||||
def echo(self, text):
|
||||
cmd = "eval(\"'{}'\")".format(text)
|
||||
return self.send(cmd)
|
||||
|
||||
def new_file(self):
|
||||
cmd = "cmds.file(new=True, force=True)"
|
||||
return self.send(cmd)
|
||||
|
||||
def create_primitive(self, shape):
|
||||
cmd = ''
|
||||
if shape == 'sphere':
|
||||
cmd += 'cmds.polySphere()'
|
||||
elif shape == 'cube':
|
||||
cmd += 'cmds.polyCube()'
|
||||
else:
|
||||
print('Invalid Shape: {}'.format(shape))
|
||||
return None
|
||||
result = self.send(cmd)
|
||||
return eval(result)
|
||||
|
||||
def translate(self, node, translation):
|
||||
cmd = "cmds.setAttr('{0}.translate', {1}, {2}, {3})".format(node, *translation)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
maya_client = MayaClient()
|
||||
if maya_client.connect():
|
||||
print('Connected successfully')
|
||||
print('Echo: {}'.format(maya_client.echo('hello world')))
|
||||
|
||||
file_name = maya_client.new_file()
|
||||
print(file_name)
|
||||
|
||||
nodes = maya_client.create_primitive('sphere')
|
||||
print(nodes)
|
||||
|
||||
maya_client.translate(nodes[0], [0, 10, 0])
|
||||
|
||||
nodes = maya_client.create_primitive('cube')
|
||||
print(nodes)
|
||||
|
||||
|
||||
if maya_client.disconnect():
|
||||
print('Disconnected successfully')
|
||||
else:
|
||||
print('Failed to connect')
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
maya_client = MayaClient()
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
# coding:utf-8
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# File Description:
|
||||
# Contains helper functions to assist when processing files using the DCCsi. This file will be consistently added to
|
||||
# as new helper functions are needed
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
import logging as _logging
|
||||
from pathlib import Path
|
||||
import pathlib
|
||||
from box import Box
|
||||
import os
|
||||
|
||||
|
||||
module_name = 'legacy_asset_converter.main.utilities'
|
||||
_LOGGER = _logging.getLogger(module_name)
|
||||
|
||||
|
||||
def convert_box_dict_to_standard(target_dict):
|
||||
"""
|
||||
This converts all aspects of a dictionary to be compatible for Python 2.x and 3.x. Converting
|
||||
dictionaries back and forth is needed in the event that they need to be processed with DCC apps (
|
||||
specifically Maya) that run on earlier versions of Python. The Pathlib and Box modules will cause
|
||||
errors otherwise.
|
||||
|
||||
:param target_dict: This specifies which dictionary to convert
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Convert Box database with Pathlib to standard dictionary listings for Maya 2.7')
|
||||
converted_dictionary = {}
|
||||
for key, values in target_dict.items():
|
||||
updated_dictionary = {}
|
||||
for k, v in values.items():
|
||||
if str(type(v)) == "<class 'box.box.Box'>":
|
||||
updated_dictionary[k] = v.to_dict()
|
||||
elif str(type(v)) == "<class 'box.box_list.BoxList'>":
|
||||
updated_dictionary[k] = [os.path.normpath(x) for x in v.to_list()]
|
||||
elif isinstance(v, pathlib.WindowsPath):
|
||||
updated_dictionary[k] = os.path.abspath(v)
|
||||
else:
|
||||
updated_dictionary[k] = v
|
||||
converted_dictionary[key] = clear_pathlib_instances(updated_dictionary)
|
||||
return converted_dictionary
|
||||
|
||||
|
||||
def convert_standard_dict_to_box(target_dict):
|
||||
"""
|
||||
This converts all aspects of a dictionary to the preferred formatting for Python 3.x to run it.
|
||||
To allow more clarity when handling dictionaries extensive use of Box and Pathlib is using in
|
||||
the handling of data. Unfortunately this is not compatible with Python 2.7 so in some situations
|
||||
the formatting needs to be removed. This reinstates formatting to include Box and Pathlib.
|
||||
|
||||
:param target_dict: This specifies which dictionary to convert
|
||||
:return:
|
||||
"""
|
||||
_LOGGER.info('Convert Standard dictionary database back to Box Dictionary with Pathlib')
|
||||
converted_dictionary = {}
|
||||
for key, values in target_dict.items():
|
||||
updated_dictionary = Box({})
|
||||
for k, v in values.items():
|
||||
if str(type(v)) == "<class 'dict'>":
|
||||
updated_dictionary[k] = Box(v)
|
||||
elif str(type(v)) == "<class 'list'>":
|
||||
updated_dictionary[k] = BoxList(v)
|
||||
elif os.path.isfile(v):
|
||||
updated_dictionary[k] = Path(v)
|
||||
else:
|
||||
updated_dictionary[k] = v
|
||||
converted_dictionary[key] = add_pathlib_instances(updated_dictionary)
|
||||
return Box(converted_dictionary)
|
||||
|
||||
|
||||
def add_pathlib_instances(target_dictionary):
|
||||
"""
|
||||
Converts string paths to pathlib instances.
|
||||
:param target_dictionary:
|
||||
:return:
|
||||
"""
|
||||
pathlib_added = {}
|
||||
for k, v in target_dictionary.items():
|
||||
if isinstance(v, dict):
|
||||
nested = add_pathlib_instances(v)
|
||||
if len(nested.keys()):
|
||||
pathlib_added[k] = nested
|
||||
elif isinstance(v, Box):
|
||||
nested = add_pathlib_instances(v)
|
||||
if len(nested.keys()):
|
||||
pathlib_added[k] = nested
|
||||
elif isinstance(v, str):
|
||||
if os.path.isfile(v):
|
||||
pathlib_added[k] = Path(v)
|
||||
elif os.path.isdir(v):
|
||||
pathlib_added[k] = Path(v)
|
||||
else:
|
||||
pathlib_added[k] = v
|
||||
else:
|
||||
pathlib_added[k] = v
|
||||
return pathlib_added
|
||||
|
||||
|
||||
def clear_pathlib_instances(target_dictionary):
|
||||
"""
|
||||
Removes all pathlib instances and replaces with string paths
|
||||
:param target_dictionary:
|
||||
:return:
|
||||
"""
|
||||
clean = {}
|
||||
for k, v in target_dictionary.items():
|
||||
if isinstance(v, dict):
|
||||
nested = clear_pathlib_instances(v)
|
||||
if len(nested.keys()):
|
||||
clean[k] = nested
|
||||
elif isinstance(v, pathlib.WindowsPath):
|
||||
clean[k] = os.path.abspath(v)
|
||||
else:
|
||||
clean[k] = v
|
||||
return clean
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
//Maya 2020 Project Definition
|
||||
|
||||
workspace -fr "fluidCache" "";
|
||||
workspace -fr "JT_ATF" "";
|
||||
workspace -fr "images" "Assets/Textures";
|
||||
workspace -fr "offlineEdit" ".maya_data/scenes/edits";
|
||||
workspace -fr "STEP_ATF Export" "";
|
||||
workspace -fr "furShadowMap" "";
|
||||
workspace -fr "SVG" "";
|
||||
workspace -fr "scripts" "Maya/Scripts";
|
||||
workspace -fr "DAE_FBX" "";
|
||||
workspace -fr "shaders" "Maya/Shaders";
|
||||
workspace -fr "NX_ATF" "";
|
||||
workspace -fr "CATIAV5_ATF Export" "";
|
||||
workspace -fr "furFiles" "";
|
||||
workspace -fr "OBJ" ".maya_data/obj";
|
||||
workspace -fr "PARASOLID_ATF Export" "";
|
||||
workspace -fr "FBX export" "Assets/Objects";
|
||||
workspace -fr "furEqualMap" "";
|
||||
workspace -fr "textures" "Assets/textures";
|
||||
workspace -fr "BIF" "";
|
||||
workspace -fr "lights" ".maya_data/renderData/shaders";
|
||||
workspace -fr "DAE_FBX export" "";
|
||||
workspace -fr "aliasWire" ".maya_data/data";
|
||||
workspace -fr "CATIAV5_ATF" "";
|
||||
workspace -fr "SAT_ATF Export" "";
|
||||
workspace -fr "movie" ".maya_data/movies";
|
||||
workspace -fr "ASS Export" "";
|
||||
workspace -fr "mayaAscii" "";
|
||||
workspace -fr "move" ".maya_data";
|
||||
workspace -fr "autoSave" ".maya_data/autoSave";
|
||||
workspace -fr "NX_ATF Export" "";
|
||||
workspace -fr "sound" ".maya_data/sound";
|
||||
workspace -fr "mayaBinary" "";
|
||||
workspace -fr "timeEditor" "";
|
||||
workspace -fr "RIBexport" ".maya_data/data";
|
||||
workspace -fr "DWG_ATF" "";
|
||||
workspace -fr "mentalray" ".maya_data/renderData/mentalray";
|
||||
workspace -fr "JT_ATF Export" "";
|
||||
workspace -fr "iprImages" ".maya_data/renderData/iprImages";
|
||||
workspace -fr "FBX" "Assets/Objects";
|
||||
workspace -fr "renderData" ".maya_data/renderData";
|
||||
workspace -fr "CATIAV4_ATF" "";
|
||||
workspace -fr "fileCache" "";
|
||||
workspace -fr "Fbx" "Objects";
|
||||
workspace -fr "eps" "";
|
||||
workspace -fr "IGESexport" ".maya_data/data";
|
||||
workspace -fr "3dPaintTextures" ".maya_data/3dPaintTextures";
|
||||
workspace -fr "DXF_ATF Export" "";
|
||||
workspace -fr "mel" ".maya_data/mel";
|
||||
workspace -fr "translatorData" "";
|
||||
workspace -fr "IGES" ".maya_data/data";
|
||||
workspace -fr "particles" ".maya_data/particles";
|
||||
workspace -fr "DXFexport" ".maya_data/data";
|
||||
workspace -fr "DXF_ATF" "";
|
||||
workspace -fr "scene" "Assets/Objects";
|
||||
workspace -fr "renderScenes" ".maya_data/renderScenes";
|
||||
workspace -fr "SAT_ATF" "";
|
||||
workspace -fr "PROE_ATF" "";
|
||||
workspace -fr "WIRE_ATF Export" "";
|
||||
workspace -fr "sourceImages" "ArtSource/Images";
|
||||
workspace -fr "RIB" ".maya_data/data";
|
||||
workspace -fr "furImages" "";
|
||||
workspace -fr "clips" ".maya_data/clips";
|
||||
workspace -fr "Adobe(R) Illustrator(R)" ".maya_data/data";
|
||||
workspace -fr "animExport" ".maya_data/data";
|
||||
workspace -fr "mentalRay" ".maya_data/mentalRay";
|
||||
workspace -fr "STEP_ATF" "";
|
||||
workspace -fr "DWG_ATF Export" "";
|
||||
workspace -fr "depth" ".maya_data/renderData/depth";
|
||||
workspace -fr "sceneAssembly" "";
|
||||
workspace -fr "IGES_ATF Export" "";
|
||||
workspace -fr "teClipExports" "";
|
||||
workspace -fr "IGES_ATF" "";
|
||||
workspace -fr "PARASOLID_ATF" "";
|
||||
workspace -fr "ASS" "";
|
||||
workspace -fr "Substance" ".maya_data/data";
|
||||
workspace -fr "audio" ".maya_data/sound";
|
||||
workspace -fr "EPS" ".maya_data/data";
|
||||
workspace -fr "Alembic" "Assets/Objects";
|
||||
workspace -fr "diskCache" ".maya_data/cache";
|
||||
workspace -fr "illustrator" "";
|
||||
workspace -fr "WIRE_ATF" "";
|
||||
workspace -fr "templates" "ArtSource/SceneTemplates";
|
||||
workspace -fr "animImport" ".maya_data/data";
|
||||
workspace -fr "OBJexport" "Assets/Objects";
|
||||
workspace -fr "furAttrMap" "";
|
||||
workspace -fr "DXF" ".maya_data/data";
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
|
||||
__all__ = ['maya_materials_export'] # populate with modules to control imports
|
||||
|
||||
# from maya_mapping import MayaMapping
|
||||
# from max_mapping import MaxMapping
|
||||
# from blender_mapping import BlenderMapping
|
||||
|
||||
# def get_dcc_mapping(app):
|
||||
# app = app.lower()
|
||||
# if app == 'maya':
|
||||
# return MayaMapping()
|
||||
# elif app == '3dsmax':
|
||||
# return MaxMapping()
|
||||
# elif app == 'blender':
|
||||
# return BlenderMapping()
|
||||
# else:
|
||||
# return NullMapping(app)
|
||||
|
||||
+632
@@ -0,0 +1,632 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
"""
|
||||
Usage
|
||||
=====
|
||||
Put usage instructions here.
|
||||
|
||||
Output
|
||||
======
|
||||
Put output information here.
|
||||
|
||||
Relevant Links:
|
||||
https://pythonhosted.org/an_example_pypi_project/sphinx.html
|
||||
https://github.com/ideasman42/pyfbx_i42
|
||||
https://pypi.org/project/py-fbx/
|
||||
https://www.quora.com/How-do-I-execute-Maya-script-without-lauching-Maya
|
||||
https://stackoverflow.com/questions/27437733/use-external-python-script-to-open-maya-and-run-another-script-inside-maya
|
||||
|
||||
Notes:
|
||||
-- Materials information can be extracted from ASCII fbx pretty easily
|
||||
-- binary is possible but more difficult
|
||||
-- FBX files could be exported as ASCII files
|
||||
-- I could use regex there to extract material information
|
||||
-- I couldn't get pyfbx_i42 to work,
|
||||
-- ^ purportedly it can extract information from binary files.
|
||||
-- You may just have to use the specified python versions
|
||||
|
||||
-- Jonny wants me to use pathlib wherever possible for OO pathing, as well as python-box (aka Box) for dict access
|
||||
|
||||
# Things to do:
|
||||
--> Create the cube demo for Jonny with 4 attached materials
|
||||
--> Create mapping widget for stacked layout
|
||||
--> Allow export of JSON file for demo
|
||||
--> Allow custom field entries for description, etc.
|
||||
--> Need to figure out how to clear pointers for stored data properly
|
||||
"""
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide2.QtCore import Signal, Slot, QThread, QAbstractItemModel, QModelIndex, QObject
|
||||
from maya import OpenMayaUI as omui
|
||||
from maya.standalone import initialize
|
||||
from shiboken2 import wrapInstance
|
||||
import pymel.core as pm
|
||||
# import sphinx
|
||||
# import azpy
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
mayaMainWindowPtr = omui.MQtUtil.mainWindow()
|
||||
mayaMainWindow = wrapInstance(long(mayaMainWindowPtr), QtWidgets.QWidget)
|
||||
|
||||
|
||||
class MayaToLumberyard(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(MayaToLumberyard, self).__init__(parent)
|
||||
|
||||
self.app = QtWidgets.QApplication.instance()
|
||||
self.setParent(mayaMainWindow)
|
||||
self.setWindowFlags(QtCore.Qt.Window)
|
||||
self.setGeometry(50, 50, 600, 500)
|
||||
self.setObjectName('MaterialsToLumberyard')
|
||||
self.setWindowTitle('Maya To Lumberyard')
|
||||
self.isTopLevel()
|
||||
self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint)
|
||||
|
||||
self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop')
|
||||
self.bold_font_large = QtGui.QFont('Plastique', 7, QtGui.QFont.Bold)
|
||||
self.medium_font = QtGui.QFont('Plastique', 7, QtGui.QFont.Normal)
|
||||
self.target_file_list = []
|
||||
self.materials_dict = {}
|
||||
self.material_definitions = {}
|
||||
self.processed_materials = []
|
||||
self.current_scene = pm.sceneName()
|
||||
self.model = None
|
||||
self.total_transfer_materials = 1
|
||||
|
||||
self.main_container = QtWidgets.QVBoxLayout(self)
|
||||
self.main_container.setAlignment(QtCore.Qt.AlignTop)
|
||||
self.setLayout(self.main_container)
|
||||
|
||||
# Header Bar ------>
|
||||
self.header_bar_layout = QtWidgets.QHBoxLayout()
|
||||
self.select_files_button = QtWidgets.QPushButton('Select Files')
|
||||
self.select_files_button.clicked.connect(self.choose_files_clicked)
|
||||
self.select_files_button.setFixedSize(80, 35)
|
||||
self.header_bar_layout.addWidget(self.select_files_button)
|
||||
self.header_bar_layout.addSpacing(15)
|
||||
self.use_current_file_checkbox = QtWidgets.QCheckBox('Use Current File')
|
||||
self.use_current_file_checkbox.setFont(self.bold_font_large)
|
||||
self.use_current_file_checkbox.clicked.connect(self.use_current_file_clicked)
|
||||
self.header_bar_layout.addWidget(self.use_current_file_checkbox)
|
||||
self.header_bar_layout.addSpacing(100)
|
||||
self.switch_layout_combobox = QtWidgets.QComboBox()
|
||||
self.switch_layout_combobox.setEnabled(False)
|
||||
self.switch_layout_combobox.setFixedSize(250, 30)
|
||||
self.combobox_items = ['Target Files', 'Extracted Values', 'Material Tree']
|
||||
self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}')
|
||||
self.switch_layout_combobox.addItems(self.combobox_items)
|
||||
self.header_bar_layout.addWidget(self.switch_layout_combobox)
|
||||
self.header_bar_layout.addSpacing(4)
|
||||
self.main_container.addSpacing(5)
|
||||
self.main_container.addLayout(self.header_bar_layout)
|
||||
|
||||
# Separation Line ------>
|
||||
self.separatorLayout1 = QtWidgets.QHBoxLayout()
|
||||
self.line1 = QtWidgets.QLabel()
|
||||
self.line1.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken)
|
||||
self.line1.setLineWidth(1)
|
||||
self.line1.setFixedHeight(10)
|
||||
self.separatorLayout1.addWidget(self.line1)
|
||||
self.main_container.addLayout(self.separatorLayout1)
|
||||
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
# File Source Table / Attributes (Stacked Layout) #
|
||||
# ++++++++++++++++++++++++++++++++++++++++++++++++#
|
||||
self.content_stacked_layout = QtWidgets.QStackedLayout()
|
||||
self.main_container.addLayout(self.content_stacked_layout)
|
||||
self.switch_layout_combobox.currentIndexChanged.connect(self.layout_combobox_changed)
|
||||
|
||||
# --- 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 {padding-top:9px; padding-left:10px;}')
|
||||
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.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||
files_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||
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 {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.material_definitions_widget = QtWidgets.QWidget()
|
||||
self.material_definitions_layout = QtWidgets.QHBoxLayout(self.material_definitions_widget)
|
||||
self.material_definitions_layout.setSpacing(0)
|
||||
self.material_definitions_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.material_definitions_frame = QtWidgets.QFrame(self.material_definitions_widget)
|
||||
self.material_definitions_frame.setGeometry(0, 0, 5000, 5000)
|
||||
self.material_definitions_frame.setStyleSheet('background-color:rgb(150,150,150);')
|
||||
|
||||
self.title_bar_widget = QtWidgets.QWidget()
|
||||
self.title_bar_layout = QtWidgets.QHBoxLayout(self.title_bar_widget)
|
||||
self.title_bar_layout.setContentsMargins(17, 17, 17, 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, 60)
|
||||
self.title_bar_frame.setStyleSheet('background-color:rgb(102,69,153);')
|
||||
self.material_definitions_layout.addWidget(self.title_bar_widget)
|
||||
|
||||
self.material_name = QtWidgets.QCheckBox('StingrayPBS2')
|
||||
self.material_name.setStyleSheet('spacing:10px; color:white')
|
||||
self.material_name.setFont(self.bold_font_large)
|
||||
self.material_name.setChecked(True)
|
||||
self.title_bar_layout.addWidget(self.material_name)
|
||||
|
||||
# Forward/Back Buttons -------------------------->>
|
||||
self.previous_next_button_layout = QtWidgets.QHBoxLayout()
|
||||
self.item_count = QtWidgets.QLabel('1 of 10')
|
||||
self.previous_next_button_layout.addWidget(self.item_count)
|
||||
self.previous_next_button_layout.addSpacing(10)
|
||||
self.previous_next_button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.previous_next_button_layout.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.previous_button = QtWidgets.QToolButton()
|
||||
self.previous_button.setArrowType(QtCore.Qt.LeftArrow)
|
||||
self.previous_button.setStyleSheet('background-color:rgb(110,110,110);')
|
||||
self.previous_button.clicked.connect(self.previous_button_clicked)
|
||||
self.previous_next_button_layout.addWidget(self.previous_button)
|
||||
self.next_button = QtWidgets.QToolButton()
|
||||
self.next_button.setArrowType(QtCore.Qt.RightArrow)
|
||||
self.next_button.setStyleSheet('background-color:rgb(110,110,110);')
|
||||
self.next_button.clicked.connect(self.next_button_clicked)
|
||||
self.previous_next_button_layout.addWidget(self.next_button)
|
||||
self.title_bar_layout.addLayout(self.previous_next_button_layout)
|
||||
self.content_stacked_layout.addWidget(self.material_definitions_widget)
|
||||
|
||||
# File processing buttons ------>
|
||||
self.process_files_layout = QtWidgets.QHBoxLayout()
|
||||
self.main_container.addLayout(self.process_files_layout)
|
||||
self.process_files_button = QtWidgets.QPushButton('Process Listed Files')
|
||||
self.process_files_button.setFixedHeight(50)
|
||||
self.process_files_button.clicked.connect(self.process_files_clicked)
|
||||
self.process_files_layout.addWidget(self.process_files_button)
|
||||
self.reset_button = QtWidgets.QPushButton('Reset')
|
||||
self.reset_button.setFixedSize(50, 50)
|
||||
self.reset_button.clicked.connect(self.reset_clicked)
|
||||
self.reset_button.setEnabled(False)
|
||||
self.process_files_layout.addWidget(self.reset_button)
|
||||
self.initialize_window()
|
||||
|
||||
def initialize_window(self):
|
||||
if self.current_scene:
|
||||
self.target_file_list = [self.current_scene]
|
||||
self.use_current_file_checkbox.setChecked(True)
|
||||
self.populate_files_table()
|
||||
|
||||
def populate_files_table(self):
|
||||
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.setStyleSheet('border-width:0px; background-color:rgb(100,100,100);')
|
||||
remove_button.clicked.connect(self.remove_file_clicked)
|
||||
self.target_files_table.setItem(index, 0, item)
|
||||
self.target_files_table.setCellWidget(index, 1, remove_button)
|
||||
|
||||
def process_file_list(self):
|
||||
file_processing_errors = []
|
||||
for maya_file_location in self.target_file_list:
|
||||
try:
|
||||
if maya_file_location != self.current_scene:
|
||||
pm.openFile(maya_file_location, force=True)
|
||||
self.current_scene = maya_file_location
|
||||
self.get_scene_materials_description()
|
||||
except Exception as e:
|
||||
file_processing_errors.append([maya_file_location, e])
|
||||
|
||||
# Create Model with extracted values from file list
|
||||
self.set_material_model()
|
||||
|
||||
# Setup Lumberyard Material File Values
|
||||
self.map_materials()
|
||||
print ('MaterialDefinitions:'.format(self.material_definitions))
|
||||
print json.dumps(self.material_definitions, sort_keys=True, indent=4)
|
||||
|
||||
# Update UI Layout
|
||||
self.set_material_view()
|
||||
self.switch_layout_combobox.setCurrentIndex(2)
|
||||
self.set_ui_buttons()
|
||||
|
||||
def map_materials(self):
|
||||
root = self.model.rootItem
|
||||
for row in range(self.model.rowCount()):
|
||||
name = self.model.get_attribute_value('MaterialName', root.child(row))
|
||||
material_type = self.model.get_attribute_value('MaterialType', 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_pbr_material_description(name, material_type, file_connections)
|
||||
|
||||
def reset_all_values(self):
|
||||
pass
|
||||
# Need to figure out how to clear pointers for stored data properly
|
||||
# self.target_files_table.setRowCount(0)
|
||||
# self.model.beginResetModel()
|
||||
# self.model.qDeleteAll(mResults)
|
||||
# self.model.endResetModel()
|
||||
# self.initialize_window()
|
||||
|
||||
############################
|
||||
# Getters/Setters ##########
|
||||
############################
|
||||
|
||||
@staticmethod
|
||||
def get_materials(target_mesh):
|
||||
shading_group = pm.listConnections(pm.PyNode(target_mesh), type='shadingEngine')
|
||||
materials = pm.ls(pm.listConnections(shading_group), materials=1)
|
||||
return list(set(materials))
|
||||
|
||||
@staticmethod
|
||||
def get_shader(material_name):
|
||||
connections = pm.listConnections(material_name, type='shadingEngine')[0]
|
||||
shader_name = '{}.surfaceShader'.format(connections)
|
||||
shader = pm.listConnections(shader_name)[0]
|
||||
return shader
|
||||
|
||||
@staticmethod
|
||||
def get_shader_information(shader):
|
||||
shader_file_connections = {}
|
||||
for node in pm.listConnections(shader, type='file', c=True):
|
||||
shader_file_connections[str(node[0])] = str(pm.getAttr(node[1].fileTextureName))
|
||||
shader_attributes = {}
|
||||
for shader_attribute in pm.listAttr(shader, s=True, iu=True):
|
||||
try:
|
||||
shader_attributes[str(shader_attribute)] = pm.getAttr('{}.{}'.format(shader, shader_attribute))
|
||||
except pm.MayaAttributeError as e:
|
||||
print ('MayaAttributeError: {}'.format(e))
|
||||
|
||||
return shader_file_connections, shader_attributes
|
||||
|
||||
@staticmethod
|
||||
def get_shader_properties(name, material_type, file_connections):
|
||||
""" This system will probably need rethinking if DCCs and compatible materials grow """
|
||||
attr_list = {}
|
||||
if material_type == 'StingrayPBS':
|
||||
naming_exceptions = {'color': 'baseColor', 'ao': 'ambientOcclusion'}
|
||||
maps = 'color, metallic, roughness, normal, emissive, ao, opacity'.split(', ')
|
||||
for m in maps:
|
||||
texture_attribute = 'TEX_{}_map'.format(m)
|
||||
for tex in file_connections.keys():
|
||||
if tex.find(texture_attribute) != -1:
|
||||
key = m if m not in naming_exceptions else naming_exceptions.get(m)
|
||||
attr_list[key] = {'useTexture': 'true',
|
||||
'textureMap': file_connections.get('{}.{}'.format(name, texture_attribute))}
|
||||
return attr_list
|
||||
|
||||
@staticmethod
|
||||
def get_increment(name):
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_material_template(shader_type):
|
||||
definitions = os.path.join(os.path.dirname(os.path.abspath(__file__)), '{}.material'.format(shader_type))
|
||||
if os.path.exists(definitions):
|
||||
with open(definitions) as f:
|
||||
return json.load(f)
|
||||
|
||||
def get_scene_materials_description(self):
|
||||
scene_geo = pm.ls(v=True, geometry=True)
|
||||
for target_mesh in scene_geo:
|
||||
material_list = self.get_materials(target_mesh)
|
||||
for material_name in material_list:
|
||||
material_type = pm.nodeType(material_name, api=True)
|
||||
material_listed = [x for x in self.materials_dict if self.materials_dict[x]['MaterialName'] == material_name]
|
||||
|
||||
if not material_listed:
|
||||
self.set_material_dict(str(material_name), str(material_type), target_mesh)
|
||||
else:
|
||||
mesh_list = self.materials_dict[material_name].get('AppliedMesh')
|
||||
if not isinstance(mesh_list, list):
|
||||
self.materials_dict[material_name]['AppliedMesh'] = [mesh_list, target_mesh]
|
||||
else:
|
||||
mesh_list.append(target_mesh)
|
||||
|
||||
def set_material_dict(self, material_name, material_type, material_mesh):
|
||||
shader = self.get_shader(material_name)
|
||||
shader_file_connections, shader_attributes = self.get_shader_information(shader)
|
||||
material_dict = {'MaterialName': material_name, 'MaterialType': material_type, 'AppliedMesh': material_mesh,
|
||||
'FileConnections': shader_file_connections, 'SceneName': str(self.current_scene),
|
||||
'MaterialAttributes': shader_attributes}
|
||||
material_name = 'Material_{}'.format(self.total_transfer_materials)
|
||||
self.materials_dict[material_name] = material_dict
|
||||
self.total_transfer_materials += 1
|
||||
|
||||
def set_material_model(self):
|
||||
self.model = MaterialsModel(self.headers, self.materials_dict)
|
||||
|
||||
def set_material_view(self):
|
||||
self.material_tree_view.setModel(self.model)
|
||||
self.material_tree_view.expandAll()
|
||||
self.material_tree_view.resizeColumnToContents(0)
|
||||
|
||||
def set_pbr_material_description(self, name, material_type, file_connections):
|
||||
# Build dictionary for material description based on extracted values
|
||||
default_settings = self.get_material_template('standardpbr.template')
|
||||
material = {'description': name,
|
||||
'materialType': default_settings.get('materialType'),
|
||||
'parentMaterial': default_settings.get('parentMaterial'),
|
||||
'propertyLayoutVersion': default_settings.get('propertyLayoutVersion'),
|
||||
'properties': self.get_shader_properties(name, material_type, file_connections)}
|
||||
self.material_definitions[name if name not in self.material_definitions.keys() else self.get_increment(name)] = material
|
||||
|
||||
def set_ui_buttons(self):
|
||||
display_index = self.content_stacked_layout.currentIndex()
|
||||
self.switch_layout_combobox.setEnabled(True)
|
||||
# Target Files
|
||||
if display_index == 0:
|
||||
self.use_current_file_checkbox.setEnabled(True)
|
||||
self.select_files_button.setEnabled(True)
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setText('Process Listed Files')
|
||||
|
||||
# Extracted Values
|
||||
elif display_index == 1:
|
||||
self.reset_button.setEnabled(True)
|
||||
self.process_files_button.setEnabled(False)
|
||||
self.use_current_file_checkbox.setEnabled(False)
|
||||
self.select_files_button.setEnabled(False)
|
||||
|
||||
# Material Tree
|
||||
else:
|
||||
self.use_current_file_checkbox.setEnabled(False)
|
||||
self.select_files_button.setEnabled(False)
|
||||
self.process_files_button.setText('Export Selected Materials')
|
||||
if self.material_definitions:
|
||||
self.process_files_button.setEnabled(True)
|
||||
|
||||
############################
|
||||
# Button Actions ###########
|
||||
############################
|
||||
|
||||
def use_current_file_clicked(self):
|
||||
self.current_scene = pm.sceneName()
|
||||
if self.use_current_file_checkbox.isChecked():
|
||||
self.target_file_list.insert(0, self.current_scene)
|
||||
self.target_file_list = list(set(self.target_file_list))
|
||||
else:
|
||||
if self.current_scene in self.target_file_list:
|
||||
del self.target_file_list[self.target_file_list.index(self.current_scene)]
|
||||
self.populate_files_table()
|
||||
|
||||
def remove_file_clicked(self):
|
||||
file_index = self.target_files_table.indexAt(self.sender().pos())
|
||||
target_file = self.target_file_list[file_index.row()]
|
||||
if target_file == pm.sceneName():
|
||||
self.use_current_file_checkbox.setChecked(False)
|
||||
del self.target_file_list[file_index.row()]
|
||||
self.populate_files_table()
|
||||
|
||||
def process_files_clicked(self):
|
||||
self.process_file_list()
|
||||
|
||||
def choose_files_clicked(self):
|
||||
dialog = QtWidgets.QFileDialog(self, 'Shift-Select Target Files', self.desktop_location)
|
||||
dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile)
|
||||
dialog.setNameFilter('Maya Files (*.ma *.mb *.fbx)')
|
||||
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_files_table()
|
||||
self.process_files_button.setEnabled(True)
|
||||
|
||||
def layout_combobox_changed(self):
|
||||
self.content_stacked_layout.setCurrentIndex(self.switch_layout_combobox.currentIndex())
|
||||
self.set_ui_buttons()
|
||||
|
||||
def reset_clicked(self):
|
||||
self.reset_all_values()
|
||||
|
||||
def previous_button_clicked(self):
|
||||
print ('Previous button clicked')
|
||||
|
||||
def next_button_clicked(self):
|
||||
print ('Next button clicked')
|
||||
|
||||
|
||||
class MaterialsModel(QAbstractItemModel):
|
||||
def __init__(self, headers, data, parent=None):
|
||||
super(MaterialsModel, self).__init__(parent)
|
||||
|
||||
self.rootItem = TreeNode(headers)
|
||||
self.parents = [self.rootItem]
|
||||
self.indentations = [0]
|
||||
self.create_data(data)
|
||||
|
||||
def create_data(self, data, indent=-1):
|
||||
if type(data) == dict:
|
||||
indent += 1
|
||||
position = 4 * indent
|
||||
for key, value in data.iteritems():
|
||||
if position > self.indentations[-1]:
|
||||
if self.parents[-1].childCount() > 0:
|
||||
self.parents.append(self.parents[-1].child(self.parents[-1].childCount() - 1))
|
||||
self.indentations.append(position)
|
||||
else:
|
||||
while position < self.indentations[-1] and len(self.parents) > 0:
|
||||
self.parents.pop()
|
||||
self.indentations.pop()
|
||||
parent = self.parents[-1]
|
||||
parent.insertChildren(parent.childCount(), 1, parent.columnCount())
|
||||
parent.child(parent.childCount() - 1).setData(0, key)
|
||||
value_string = str(value) if type(value) != dict else str('')
|
||||
parent.child(parent.childCount() - 1).setData(1, value_string)
|
||||
try:
|
||||
self.create_data(value, indent)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_attribute_value(search_string, search_column):
|
||||
for childIndex in range(search_column.childCount()):
|
||||
child_item = search_column.child(childIndex)
|
||||
child_value = child_item.itemData
|
||||
if child_value[0] == search_string:
|
||||
return child_value[1]
|
||||
return None
|
||||
|
||||
def index(self, row, column, index=QModelIndex()):
|
||||
""" Returns the index of the item in the model specified by the given row, column and parent index """
|
||||
if not self.hasIndex(row, column, index):
|
||||
return QModelIndex()
|
||||
if not index.isValid():
|
||||
item = self.rootItem
|
||||
else:
|
||||
item = index.internalPointer()
|
||||
|
||||
child = item.child(row)
|
||||
if child:
|
||||
return self.createIndex(row, column, child)
|
||||
return QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
"""
|
||||
Returns the parent of the model item with the given index If the item has no parent,
|
||||
an invalid QModelIndex is returned
|
||||
"""
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
item = index.internalPointer()
|
||||
if not item:
|
||||
return QModelIndex()
|
||||
|
||||
parent = item.parentItem
|
||||
if parent == self.rootItem:
|
||||
return QModelIndex()
|
||||
else:
|
||||
return self.createIndex(parent.childNumber(), 0, parent)
|
||||
|
||||
def rowCount(self, index=QModelIndex()):
|
||||
"""
|
||||
Returns the number of rows under the given parent. When the parent is valid it means that
|
||||
rowCount is returning the number of children of parent
|
||||
"""
|
||||
if index.isValid():
|
||||
parent = index.internalPointer()
|
||||
else:
|
||||
parent = self.rootItem
|
||||
return parent.childCount()
|
||||
|
||||
def columnCount(self, index=QModelIndex()):
|
||||
""" Returns the number of columns for the children of the given parent """
|
||||
return self.rootItem.columnCount()
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
""" Returns the data stored under the given role for the item referred to by the index """
|
||||
if index.isValid() and role == QtCore.Qt.DisplayRole:
|
||||
return index.internalPointer().data(index.column())
|
||||
elif not index.isValid():
|
||||
return self.rootItem.data(index.column())
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
""" Returns the data for the given role and section in the header with the specified orientation """
|
||||
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
|
||||
return self.rootItem.data(section)
|
||||
|
||||
|
||||
class TreeNode(object):
|
||||
def __init__(self, data, parent=None):
|
||||
self.parentItem = parent
|
||||
self.itemData = data
|
||||
self.children = []
|
||||
|
||||
def child(self, row):
|
||||
return self.children[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self.children)
|
||||
|
||||
def childNumber(self):
|
||||
if self.parentItem is not None:
|
||||
return self.parentItem.children.index(self)
|
||||
|
||||
def columnCount(self):
|
||||
return len(self.itemData)
|
||||
|
||||
def data(self, column):
|
||||
return self.itemData[column]
|
||||
|
||||
def insertChildren(self, position, count, columns):
|
||||
if position < 0 or position > len(self.children):
|
||||
return False
|
||||
for row in range(count):
|
||||
data = [v for v in range(columns)]
|
||||
item = TreeNode(data, self)
|
||||
self.children.insert(position, item)
|
||||
|
||||
def parent(self):
|
||||
return self.parentItem
|
||||
|
||||
def setData(self, column, value):
|
||||
if column < 0 or column >= len(self.itemData):
|
||||
return False
|
||||
self.itemData[column] = value
|
||||
|
||||
|
||||
def delete_instances():
|
||||
for obj in mayaMainWindow.children():
|
||||
if str(type(obj)) == "<class 'DCC_Materials.maya_materials_export.MayaToLumberyard'>":
|
||||
if obj.__class__.__name__ == "MayaToLumberyard":
|
||||
obj.setParent(None)
|
||||
obj.deleteLater()
|
||||
|
||||
|
||||
def show_ui():
|
||||
delete_instances()
|
||||
ui = MayaToLumberyard(mayaMainWindow)
|
||||
ui.show()
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import pymel.core as pmc
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
def syspath():
|
||||
print 'sys.path:'
|
||||
for p in sys.path:
|
||||
print ' ' + p
|
||||
|
||||
|
||||
def info(obj):
|
||||
"""Prints information about the object."""
|
||||
|
||||
lines = ['Info for %s' % obj.name(),
|
||||
'Attributes:']
|
||||
# Get the name of all attributes
|
||||
for a in obj.listAttr():
|
||||
lines.append(' ' + a.name())
|
||||
lines.append('MEL type: %s' % obj.type())
|
||||
lines.append('MRO:')
|
||||
lines.extend([' ' + t.__name__ for t in type(obj).__mro__])
|
||||
result = '\n'.join(lines)
|
||||
print result
|
||||
|
||||
|
||||
def _is_pymel(obj):
|
||||
try: # (1)
|
||||
module = obj.__module__ # (2)
|
||||
except AttributeError: # (3)
|
||||
try:
|
||||
module = obj.__name__ # (4)
|
||||
except AttributeError:
|
||||
return None # (5)
|
||||
return module.startswith('pymel') # (6)
|
||||
|
||||
|
||||
def _py_to_helpstr(obj):
|
||||
if isinstance(obj, basestring):
|
||||
return 'search.html?q=%s' % (obj.replace(' ', '+'))
|
||||
if not _is_pymel(obj):
|
||||
return None
|
||||
if isinstance(obj, types.ModuleType):
|
||||
return ('generated/%(module)s.html#module-%(module)s' %
|
||||
dict(module=obj.__name__))
|
||||
if isinstance(obj, types.MethodType):
|
||||
return ('generated/classes/%(module)s/'
|
||||
'%(module)s.%(typename)s.html'
|
||||
'#%(module)s.%(typename)s.%(methname)s' % dict(
|
||||
module=obj.__module__,
|
||||
typename=obj.im_class.__name__,
|
||||
methname=obj.__name__))
|
||||
if isinstance(obj, types.FunctionType):
|
||||
return ('generated/functions/%(module)s/'
|
||||
'%(module)s.%(funcname)s.html'
|
||||
'#%(module)s.%(funcname)s' % dict(
|
||||
module=obj.__module__,
|
||||
funcname=obj.__name__))
|
||||
if not isinstance(obj, type):
|
||||
obj = type(obj)
|
||||
return ('generated/classes/%(module)s/'
|
||||
'%(module)s.%(typename)s.html'
|
||||
'#%(module)s.%(typename)s' % dict(
|
||||
module=obj.__module__,
|
||||
typename=obj.__name__))
|
||||
|
||||
|
||||
def test_py_to_helpstr():
|
||||
def dotest(obj, ideal):
|
||||
result = _py_to_helpstr(obj)
|
||||
assert result == ideal, '%s != %s' % (result, ideal)
|
||||
dotest('maya rocks', 'search.html?q=maya+rocks')
|
||||
dotest(pmc.nodetypes,
|
||||
'generated/pymel.core.nodetypes.html'
|
||||
'#module-pymel.core.nodetypes')
|
||||
dotest(pmc.nodetypes.Joint,
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint')
|
||||
dotest(pmc.nodetypes.Joint(),
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint')
|
||||
dotest(pmc.nodetypes.Joint().getTranslation,
|
||||
'generated/classes/pymel.core.nodetypes/'
|
||||
'pymel.core.nodetypes.Joint.html'
|
||||
'#pymel.core.nodetypes.Joint.getTranslation')
|
||||
dotest(pmc.joint,
|
||||
'generated/functions/pymel.core.animation/'
|
||||
'pymel.core.animation.joint.html'
|
||||
'#pymel.core.animation.joint')
|
||||
dotest(object(), None)
|
||||
dotest(10, None)
|
||||
dotest([], None)
|
||||
dotest(sys, None)
|
||||
|
||||
|
||||
def test_py_to_helpstrFAIL():
|
||||
assert 1 == 2, '1 != 2'
|
||||
|
||||
|
||||
import webbrowser # (1)
|
||||
HELP_ROOT_URL = ('http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/PyMel/')# (2)
|
||||
|
||||
|
||||
def pmhelp(obj): # (3)
|
||||
"""Gives help for a pymel or python object.
|
||||
|
||||
If obj is not a PyMEL object, use Python's built-in
|
||||
`help` function.
|
||||
If obj is a string, open a web browser to a search in the
|
||||
PyMEL help for the string.
|
||||
Otherwise, open a web browser to the page for the object.
|
||||
"""
|
||||
tail = _py_to_helpstr(obj)
|
||||
if tail is None:
|
||||
help(obj) # (4)
|
||||
else:
|
||||
webbrowser.open(HELP_ROOT_URL + tail) # (5)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_py_to_helpstr()
|
||||
print 'Tests ran successfully.'
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"description": "",
|
||||
"materialType": "Materials/Types/StandardPBR.materialtype",
|
||||
"parentMaterial": "",
|
||||
"propertyLayoutVersion": 3,
|
||||
"properties": {
|
||||
"ambientOcclusion": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"baseColor": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"emissive": {
|
||||
"color": [ 1.0, 1.0, 1.0 ],
|
||||
"intensity": 1.0,
|
||||
"useTexture": false,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif"
|
||||
},
|
||||
"metallic": {
|
||||
"factor": 0.0,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"roughness": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif"
|
||||
},
|
||||
"specularF0": {
|
||||
"factor": 0.5,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
},
|
||||
"normal": {
|
||||
"factor": 1.0,
|
||||
"useTexture": true,
|
||||
"textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif"
|
||||
},
|
||||
"opacity": {
|
||||
"doubleSided": false,
|
||||
"factor": 1.0,
|
||||
"cutoutAlpha": false,
|
||||
"cutoutThreshold": 0.5,
|
||||
"useBaseColorTextureAlpha": false,
|
||||
"useTexture": false,
|
||||
"textureMap": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""
|
||||
Module Documentation:
|
||||
DccScriptingInterface:: SDK//maya//scripts//set_shelf.py
|
||||
|
||||
This module manages a custom shelf in maya for the DCCsi
|
||||
Reference: https://gist.github.com/vshotarov/1c3176fe9e38dcaadd1e56c2f15c95d9
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
# -- Standard Python modules
|
||||
# none
|
||||
|
||||
# -- External Python modules
|
||||
# none
|
||||
|
||||
# -- DCCsi Extension Modules
|
||||
# none
|
||||
|
||||
# -- Maya Extension Modules
|
||||
import maya.cmds as mc
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _null(*args):
|
||||
pass
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
class customShelf(_Custom_Shelf):
|
||||
'''This is an example shelf.'''
|
||||
|
||||
def build(self):
|
||||
self.add_button(label="button1")
|
||||
self.add_button("button2")
|
||||
self.add_button("popup")
|
||||
p = mc.popupMenu(b=1)
|
||||
self.add_menu_item(p, "popupMenuItem1")
|
||||
self.add_menu_item(p, "popupMenuItem2")
|
||||
sub = self.add_submenu(p, "subMenuLevel1")
|
||||
self.add_menu_item(sub, "subMenuLevel1Item1")
|
||||
sub2 = self.add_submenu(sub, "subMenuLevel2")
|
||||
self.add_menu_item(sub2, "subMenuLevel2Item1")
|
||||
self.add_menu_item(sub2, "subMenuLevel2Item2")
|
||||
self.add_menu_item(sub, "subMenuLevel1Item2")
|
||||
self.add_menu_item(p, "popupMenuItem3")
|
||||
self.add_button("button3")
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
class _Custom_Shelf():
|
||||
'''A simple class to build custom shelves in maya.
|
||||
The build method is empty and an inheriting class should override'''
|
||||
|
||||
def __init__(self, name="DCCsi", icon_path=""):
|
||||
self._name = name
|
||||
|
||||
self._icon_path = icon_path
|
||||
|
||||
self._label_background_color = (0, 0, 0, 0)
|
||||
self._label_colour = (.9, .9, .9)
|
||||
|
||||
self._clean_old_shlef()
|
||||
|
||||
mc.setParent(self._name)
|
||||
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
'''Override this method in custom class.
|
||||
Otherwise, nothing is added to the shelf.'''
|
||||
pass
|
||||
|
||||
def add_button(self,
|
||||
label='<NotSet>',
|
||||
icon="commandButton.png",
|
||||
command=_null,
|
||||
doubleCommand=_null):
|
||||
'''Adds a shelf button with the specified label,
|
||||
command, double click command and image.'''
|
||||
mc.setParent(self._name)
|
||||
if icon:
|
||||
icon = self._icon_path + icon
|
||||
mc.shelfButton(width=37, height=37,
|
||||
image=icon,
|
||||
label=label,
|
||||
command=command,
|
||||
doubleClickCommand=doubleCommand,
|
||||
imageOverlayLabel=label,
|
||||
overlayLabelBackColor=self._label_background_color,
|
||||
overlayLabelColor=self._label_colour)
|
||||
|
||||
def add_menu_item(self, parent, label, command=_null, icon=""):
|
||||
'''Adds a shelf button with the specified label,
|
||||
command, double click command and image.'''
|
||||
if icon:
|
||||
icon = self._icon_path + icon
|
||||
return mc.menuItem(p=parent, l=label, c=command, i="")
|
||||
|
||||
def add_submenu(self, parent, label, icon=None):
|
||||
'''Adds a sub menu item with the specified label and icon
|
||||
to the specified parent popup menu.'''
|
||||
if icon:
|
||||
icon = self._icon_path + icon
|
||||
return mc.menuItem(p=parent, l=label, i=icon, subMenu=1)
|
||||
|
||||
def _clean_old_shlef(self):
|
||||
'''Checks if the shelf exists and empties it if it does or
|
||||
creates it if it does not.'''
|
||||
if mc.shelfLayout(self._name, ex=1):
|
||||
if mc.shelfLayout(self._name, q=1, ca=1):
|
||||
for each in mc.shelfLayout(self._name, q=1, ca=1):
|
||||
mc.deleteUI(each)
|
||||
else:
|
||||
mc.shelfLayout(self._name, p="ShelfLayout")
|
||||
|
||||
|
||||
# ==========================================================================
|
||||
# Module Tests
|
||||
# ==========================================================================
|
||||
if __name__ == '__main__':
|
||||
customShelf()
|
||||
pass
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
|
||||
|
||||
|
||||
+3
-4
@@ -46,6 +46,9 @@ import logging as _logging
|
||||
import azpy
|
||||
from azpy.constants import *
|
||||
from azpy.env_base import _BASE_ENVVAR_DICT
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# -- maya imports
|
||||
import maya.cmds as cmds
|
||||
@@ -55,10 +58,6 @@ import maya.mel as mel
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
from azpy import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# global space
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
its licensors.
|
||||
|
||||
For complete copyright and license terms please see the LICENSE at the root of this
|
||||
distribution (the "License"). All use of this software is governed by the License,
|
||||
or, if provided, by the license below or the license accompanying this file. Do not
|
||||
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
"DccScriptingInterface" aka DCCsi is a Gem for O3DE to extend and interface with dcc tools
|
||||
in the python ecosystem. Each dcc tool may have it's own specific version of python.
|
||||
Most are some version of py3+. O3DE provides an install of py3+ and manages package
|
||||
dependancies with requirements.txt files and the cmake build system.
|
||||
|
||||
However Autodesk Maya still uses a version of py2.7 and so we need an alternate way
|
||||
to deal with package management for this DCC tool.
|
||||
|
||||
Maya ships with it's own python interpreter called mayapy.exe
|
||||
|
||||
Generally it is located here:
|
||||
C:\Program Files\Autodesk\Maya2020\bin\mayapy.exe
|
||||
|
||||
The python install and site-packages are here:
|
||||
C:\Program Files\Autodesk\Maya2020\Python\Lib\site-packages
|
||||
|
||||
A general goal of the DCCsi is be self-maintained, and to not taint the users installed applications of environment.
|
||||
|
||||
So we boostrap additional access to site-packages in our userSetup.py:
|
||||
"C:\Depot\Lumberyard\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\Maya\Scripts\userSetup.py"
|
||||
|
||||
We don't want users to have to install or use Python2.7 although with maya and possibly other dcc tools we don't have that control. Maya still is on Python2.7, so instead of forcing another install of python we can just use mayapy to manage extensions.
|
||||
|
||||
Pip may already be installed, you can check like so (your maya install path may be different):
|
||||
|
||||
C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip --version
|
||||
|
||||
If pip is not available yet for your mayapy.
|
||||
|
||||
First find out where th site-packages is located
|
||||
|
||||
C:\Program Files\Autodesk\Maya2020\bin>mayapy -m site
|
||||
sys.path = [
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\bin',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\bin\\python27.zip',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\DLLs',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\plat-win',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\lib-tk',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python',
|
||||
'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages',
|
||||
]
|
||||
USER_BASE: 'C:\\Users\\gallowj\\AppData\\Roaming\\Python' (exists)
|
||||
USER_SITE: 'C:\\Users\\gallowj\\AppData\\Roaming\\Python\\Python27\\site-packages' (doesn't exist)
|
||||
ENABLE_USER_SITE: True
|
||||
|
||||
This is the location we are looking for:
|
||||
C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages
|
||||
|
||||
download get-pip.py and put into the above ^ directory:
|
||||
https://bootstrap.pypa.io/pip/2.7/get-pip.py
|
||||
|
||||
Put that in the root of site-packages:
|
||||
C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages\\get-pip.py
|
||||
|
||||
With get-pip module ready, we run it to install pip:
|
||||
C:\Program Files\Autodesk\Maya2020\bin>mayapy -m get-pip
|
||||
|
||||
Now you should be able to run the following command and verify pip:
|
||||
C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip --version
|
||||
pip 20.3.4 from C:\Users\< you >\AppData\Roaming\Python\Python27\site-packages\pip (python 2.7)
|
||||
|
||||
Now your local maya install is all set up with pip so you can install additional python packages to use in maya. (note: not all packages are compatible with maya)
|
||||
|
||||
Now you will want to run the following file to finish setup...
|
||||
We have a requirements.txt file with the extension packages we use in the DCCsi.
|
||||
You'll need the repo/branch path of your Lumberyard(O3DE) install.
|
||||
And you'll need to know where the DCCsi, we will install package dependancies there.
|
||||
|
||||
C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip install -r C:\Depot\Lumberyard\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\Maya\requirements.txt -t C:\Depot\Lumberyard\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\Lib\2.x\2.7.x\site-packages
|
||||
@@ -0,0 +1,33 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile --generate-hashes requirements.txt
|
||||
#
|
||||
typing==3.7.4.3 \
|
||||
--hash=sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9 \
|
||||
--hash=sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# dynaconf
|
||||
click==7.1.2 \
|
||||
--hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \
|
||||
--hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc
|
||||
# via
|
||||
# -r requirements.txt
|
||||
# pip-tools
|
||||
dynaconf==3.1.4 \
|
||||
--hash=sha256:b2f472d83052f809c5925565b8a2ba76a103d5dc1dbb9748b693ed67212781b9 \
|
||||
--hash=sha256:e6f383b84150b70fc439c8b2757581a38a58d07962aa14517292dcce1a77e160
|
||||
# via -r requirements.txt
|
||||
pathlib==1.0.1 \
|
||||
--hash=sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f
|
||||
# via -r requirements.txt
|
||||
python-box==3.4.6 \
|
||||
--hash=sha256:694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9 \
|
||||
--hash=sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10
|
||||
# via -r requirements.txt
|
||||
unipath==1.1 \
|
||||
--hash=sha256:09839adcc72e8a24d4f76d63656f30b5a1f721fc40c9bcd79d8c67bdd8b47dae \
|
||||
--hash=sha256:e6257e508d8abbfb6ddd8ec357e33589f1f48b1599127f23b017124d90b0fff7
|
||||
# via -r requirements.txt
|
||||
+1
-1
@@ -40,7 +40,7 @@ def convert(src):
|
||||
def get_image_spec(target_image):
|
||||
spec = target_image.spec()
|
||||
info = {'resolution': (spec.width, spec.height, spec.x, spec.y), 'channels': spec.channelnames,
|
||||
'format': str(spec.format)}
|
||||
'format': str(spec.format)}
|
||||
if spec.channelformats :
|
||||
info['channelformats'] = str(spec.channelformats)
|
||||
info['alpha channel'] = str(spec.alpha_channel)
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
REM
|
||||
REM For complete copyright and license terms please see the LICENSE at the root of this
|
||||
REM distribution (the "License"). All use of this software is governed by the License,
|
||||
REM or, if provided, by the license below or the license accompanying this file. Do not
|
||||
REM remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
:: Set up and run LY Python CMD prompt
|
||||
:: Sets up the DccScriptingInterface_Env,
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface Cmd
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..\..\..\..)
|
||||
|
||||
:: Change to root Lumberyard dev dir
|
||||
:: Don't use the LY_DEV so we can test that ENVAR!!!
|
||||
CD /d %DEV_REL_PATH%
|
||||
set Rel_Dev=%CD%
|
||||
echo Rel_Dev = %Rel_Dev%
|
||||
:: Restore original directory
|
||||
popd
|
||||
|
||||
set DCCSI_PYTHON_INSTALL=%Rel_Dev%\Tools\Python\3.7.5\windows
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_INSTALL%;%PATH%
|
||||
|
||||
:: dcc scripting interface gem path
|
||||
set DCCSIG_PATH=%Rel_Dev%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface
|
||||
echo DCCSIG_PATH = %DCCSIG_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%PATH%
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=false)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCCsi, DCC Material Converter
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %DCCSIG_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%PATH%
|
||||
|
||||
set PYTHONPATH=%DCCSIG_PATH%;%PYTHONPATH%
|
||||
|
||||
CALL %DCCSI_PYTHON_INSTALL%\python.exe "%DCCSIG_PATH%\SDK\PythonTools\DCC_Material_Converter\standalone.py"
|
||||
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
|
||||
exit /b 0
|
||||
+13
-9
@@ -41,15 +41,7 @@ Reading FBX file information (might come in handy later)
|
||||
-- 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
|
||||
"""
|
||||
|
||||
from PySide2 import QtWidgets, QtCore, QtGui
|
||||
from PySide2.QtCore import Slot
|
||||
from PySide2.QtWidgets import QApplication
|
||||
import shiboken2
|
||||
from shiboken2 import wrapInstance
|
||||
from model import MaterialsModel
|
||||
from drag_and_drop import DragAndDrop
|
||||
import dcc_material_mapping as dcc_map
|
||||
# built-ins
|
||||
import collections
|
||||
import logging
|
||||
import subprocess
|
||||
@@ -58,7 +50,19 @@ 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
|
||||
|
||||
|
||||
+3
-3
@@ -12,10 +12,10 @@
|
||||
import MaxPlus
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
def get_material_information():
|
||||
for mesh_object in MaxPlus.Core.GetRootNode().Children:
|
||||
print('Object---> {}'.format(mesh_object))
|
||||
|
||||
|
||||
|
||||
|
||||
get_material_information()
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
||||
"""Boostraps and Starts Standalone DCC Material Converter utility"""
|
||||
|
||||
# built in's
|
||||
import os
|
||||
import site
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# \dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\PythonTools\DCC_Material_Converter\standalone.py
|
||||
_MODULE_PATH = os.path.abspath(__file__)
|
||||
|
||||
_DCCSIG_REL_PATH = "../../../.."
|
||||
_DCCSIG_PATH = os.path.join(_MODULE_PATH, _DCCSIG_REL_PATH)
|
||||
_DCCSIG_PATH = os.path.normpath(_DCCSIG_PATH)
|
||||
|
||||
_DCCSIG_PATH = os.getenv('DCCSIG_PATH',
|
||||
os.path.abspath(_DCCSIG_PATH))
|
||||
|
||||
# we don't have access yet to the DCCsi Lib\site-packages
|
||||
site.addsitedir(_DCCSIG_PATH) # PYTHONPATH
|
||||
|
||||
# azpy bootstrapping and extensions
|
||||
import azpy.config_utils
|
||||
_config = azpy.config_utils.get_dccsi_config()
|
||||
settings = _config.get_config_settings(setup_ly_pyside=True)
|
||||
|
||||
from main import launch_material_converter
|
||||
|
||||
launch_material_converter()
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import logging as _logging
|
||||
|
||||
# azpy extensions
|
||||
import azpy.config_utils
|
||||
_config = azpy.config_utils.get_dccsi_config()
|
||||
settings = _config.get_config_settings(setup_ly_pyside=True)
|
||||
|
||||
# 3rd Party (we may or do provide)
|
||||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_GDEBUG)
|
||||
|
||||
for handler in _logging.root.handlers[:]:
|
||||
_logging.root.removeHandler(handler)
|
||||
|
||||
_MODULENAME = 'DCCsi.SDK.pythontools.launcher.main'
|
||||
|
||||
_log_level = _logging.INFO
|
||||
if _G_DEBUG:
|
||||
_log_level = _logging.DEBUG
|
||||
|
||||
_LOGGER = azpy.initialize_logger(name=_MODULENAME,
|
||||
log_to_file=True,
|
||||
default_log_level=_log_level)
|
||||
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_MODULENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
def main():
|
||||
from PySide2.QtWidgets import QApplication, QPushButton
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
app = QApplication([]) # Start an application.
|
||||
window = QWidget() # Create a window.
|
||||
layout = QVBoxLayout() # Create a layout.
|
||||
button = QPushButton("I'm just a Button man") # Define a button
|
||||
layout.addWidget(QLabel('Hello World!')) # Add a label
|
||||
layout.addWidget(button) # Add the button man
|
||||
window.setLayout(layout) # Pass the layout to the window
|
||||
window.show() # Show window
|
||||
app.exec_() # Execute the App
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
# The __init__.py files help guide import statements without automatically
|
||||
# importing all of the modules
|
||||
"""DCCsi.sdk.substance.builder.__init__"""
|
||||
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# global space
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.Substance.builder'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
#
|
||||
__all__ = ['bootstrap',
|
||||
'atom_material',
|
||||
'sb_gui_main',
|
||||
'sbs_to_sbsar',
|
||||
'sbsar_info',
|
||||
'sbsar_render',
|
||||
'sbsar_utils',
|
||||
'substance_tools',
|
||||
'watchdog',
|
||||
'ui']
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
if _DCCSI_DEV_MODE:
|
||||
# If in dev mode this will test imports of __all__
|
||||
from azpy import test_imports
|
||||
_LOGGER.debug('Testing Imports from {0}'.format(_PACKAGENAME))
|
||||
test_imports(__all__,
|
||||
_pkg=_PACKAGENAME,
|
||||
_logger=_LOGGER)
|
||||
# -------------------------------------------------------------------------
|
||||
del _LOGGER
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
#import simplejson as json
|
||||
import json
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
from azpy.constants import *
|
||||
|
||||
# 3rdparty (we provide)
|
||||
from box import Box
|
||||
from pathlib import Path
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.substance.builder.atom_material'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# early attach WingIDE debugger (can refactor to include other IDEs later)
|
||||
if _DCCSI_DEV_MODE:
|
||||
from azpy.test.entry_test import connect_wing
|
||||
foo = connect_wing()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# previous
|
||||
class AtomPBR:
|
||||
def __init__(self, material_file):
|
||||
|
||||
# loading .material File
|
||||
self.material_file = material_file
|
||||
self.input_data = open(self.material_file, "r")
|
||||
self.material = json.load(self.input_data)
|
||||
self.mat_box = Box(self.material)
|
||||
self.input_data.close()
|
||||
|
||||
# List of texture slots
|
||||
self.tex = ['baseColor', 'metallic', 'roughness', 'normalMap', 'opacity']
|
||||
|
||||
# Construct texture maps
|
||||
self.basecolor_tex = ""
|
||||
self.metallic_tex = ""
|
||||
self.roughness_tex = ""
|
||||
self.normalmap_tex = ""
|
||||
self.opacity_tex = ""
|
||||
|
||||
def load(self, material_file):
|
||||
input_data = open(material_file, "r")
|
||||
self.material = json.load(input_data)
|
||||
self.mat_box = Box(self.material)
|
||||
input_data.close()
|
||||
|
||||
def get_map(self, tex_slot):
|
||||
return self.mat_box.properties[tex_slot].parameters.textureMap
|
||||
|
||||
def set_map(self, tex_slot, tex_map):
|
||||
self.mat_box.properties[tex_slot].parameters.textureMap = tex_map
|
||||
|
||||
def write(self, material_out):
|
||||
output_data = open(material_out, "w+")
|
||||
output_data.write(json.dumps(self.mat_box, indent=4))
|
||||
output_data.close()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# new?
|
||||
class AtomMaterial:
|
||||
def __init__(self, material_file):
|
||||
# loading .material File
|
||||
self.material_file = material_file
|
||||
self.input_data = open(self.material_file, "r")
|
||||
self.material = json.load(self.input_data)
|
||||
self.mat_box = Box(self.material)
|
||||
self.input_data.close()
|
||||
|
||||
# List of texture slots
|
||||
# old tex maps
|
||||
# self.tex = ['DiffuseMap', 'NormalMap', 'SpecularMap', 'EnvironmentMap']
|
||||
self.tex = ['baseColor', 'metallic', 'roughness', 'specularF0', 'normal', 'opacity']
|
||||
|
||||
self.texture_map = {'baseColor': 'baseColor',
|
||||
'metallic': 'metallic',
|
||||
'roughness': 'roughness',
|
||||
'specularF0': 'specular',
|
||||
'normal': 'normal',
|
||||
'opacity': 'opacity'
|
||||
}
|
||||
|
||||
def load(self, material_file):
|
||||
input_data = open(material_file, "r")
|
||||
self.material = json.load(input_data)
|
||||
self.mat_box = Box(self.material)
|
||||
input_data.close()
|
||||
|
||||
def get_material_type(self):
|
||||
return self.mat_box.materialType
|
||||
|
||||
# old getMap function
|
||||
# def getMap(self, tex_slot):
|
||||
# return self.mat_box.properties.general[tex_slot]
|
||||
|
||||
def get_map(self, tex_slot):
|
||||
return self.mat_box.properties[tex_slot].textureMap
|
||||
|
||||
def set_map(self, tex_slot, tex_map):
|
||||
self.mat_box.properties[tex_slot].textureMap = tex_map
|
||||
self.mat_box.properties[tex_slot].useTexture = True
|
||||
self.mat_box.properties[tex_slot].factor = 1.0
|
||||
|
||||
def write(self, material_out):
|
||||
|
||||
if not material_out.parent.exists():
|
||||
try:
|
||||
material_out.parent.mkdir(mode=0o777, parents=True, exist_ok=True)
|
||||
_LOGGER.info('mkdir: {}'.format(material_out.parent))
|
||||
except Exception as e:
|
||||
_LOGGER.error(e)
|
||||
raise(e)
|
||||
else:
|
||||
_LOGGER.info('exists: {}'.format(material_out.parent))
|
||||
|
||||
material_out.touch()
|
||||
output_data = open(str(material_out), "w+")
|
||||
output_data.write(json.dumps(self.mat_box, indent=4))
|
||||
output_data.close()
|
||||
return material_out
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.info("Test Run:: {0}.".format({_PACKAGENAME}))
|
||||
_LOGGER.info("{0} :: if __name__ == '__main__':".format(_PACKAGENAME))
|
||||
|
||||
material_path = Path(Path(__file__).parent.parent, 'resources', 'atom')
|
||||
# material_01 = AtomPBR("atom_pbr.material", "awesome.material")
|
||||
# material_01 = AtomPBR("atom_pbr.material")
|
||||
material_01 = AtomMaterial(Path(material_path, "StandardPBR_AllProperties.material"))
|
||||
# material_01.load("atom_pbr.material")
|
||||
# material_01.map(material_01.tex[2]).textureMap = "materials/substance/amazing_xzzzx.tif"
|
||||
# # print(material_01.metallic)
|
||||
# material_01.write("awesome.material")
|
||||
# print(material_01.tex[2])
|
||||
# print(material_01.getMap(material_01.tex[3]))
|
||||
|
||||
# material_01.baesColor_tex = "materials/substance/amazing_bc.tif"
|
||||
# material_01.setMap(material_01.tex[0], material_01.baesColor_tex)
|
||||
# material_01.write("awesome.material")
|
||||
|
||||
# Test material parser for the new format.
|
||||
material_01.baseColor_tex = "Textures/Streaming/streaming99.dds"
|
||||
material_01.metallic_tex = "Textures/Streaming/streaming99.dds"
|
||||
material_01.set_map(material_01.tex[0], material_01.baseColor_tex)
|
||||
material_01.set_map(material_01.tex[1], material_01.metallic_tex)
|
||||
material_out = material_01.write(Path(material_path, "atom_variant00.material"))
|
||||
_LOGGER.info('materialType is:: {}'.format(material_01.get_material_type()))
|
||||
|
||||
if material_out.exists():
|
||||
_LOGGER.info('Wrote material file: {}'.format(material_out))
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
+958
@@ -0,0 +1,958 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
from __future__ import unicode_literals
|
||||
# from builtins import str
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
# built in's
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import logging as _logging
|
||||
from collections import OrderedDict
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# normal scripts we would do the following 'from dynaconf import settings'
|
||||
# for GUI apps that need PySide2 we init in the following manner instead
|
||||
import azpy.config_utils
|
||||
_config = azpy.config_utils.get_dccsi_config()
|
||||
settings = _config.get_config_settings(setup_ly_pyside=True)
|
||||
|
||||
from pathlib import Path
|
||||
from pathlib import PurePath
|
||||
|
||||
# Lumberyard extensions
|
||||
#import azpy
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_GDEBUG)
|
||||
|
||||
for handler in _logging.root.handlers[:]:
|
||||
_logging.root.removeHandler(handler)
|
||||
|
||||
_MODULENAME = 'DCCsi.SDK.substance.builder.sb_gui_main'
|
||||
|
||||
_log_level = _logging.INFO
|
||||
if _G_DEBUG:
|
||||
_log_level = _logging.DEBUG
|
||||
|
||||
_LOGGER = azpy.initialize_logger(name=_MODULENAME,
|
||||
log_to_file=True,
|
||||
default_log_level=_log_level)
|
||||
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_MODULENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# early attach WingIDE debugger (can refactor to include other IDEs later)
|
||||
if _DCCSI_DEV_MODE:
|
||||
from azpy.test.entry_test import connect_wing
|
||||
foo = connect_wing()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# now we should be cooking with gas (and can use the dnynamic settings)
|
||||
# this block is really for standalone
|
||||
from dynaconf import settings
|
||||
import config
|
||||
_LOGGER.debug('config.py is: {}'.format(config))
|
||||
|
||||
# initialize the Lumberyard Qt / PySide2
|
||||
config.init_ly_pyside(settings.LY_DEV) # for standalone
|
||||
settings.setenv() # for standalone
|
||||
|
||||
# log debug info about Qt/PySide2
|
||||
_LOGGER.debug('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH))
|
||||
_LOGGER.debug('LY_BIN_PATH: {}'.format(settings.LY_BIN_PATH))
|
||||
_LOGGER.debug('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH))
|
||||
_LOGGER.debug('QT_QPA_PLATFORM_PLUGIN_PATH: {}'.format(settings.QT_QPA_PLATFORM_PLUGIN_PATH))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Qt and Pyside2 imports block
|
||||
import PySide2
|
||||
_LOGGER.debug('PySide2 is:'.format(PySide2.__file__))
|
||||
|
||||
import PySide2.QtCore
|
||||
from PySide2 import QtCore, QtWidgets
|
||||
# from PySide2.QtCore import QTimer
|
||||
from PySide2.QtCore import QProcess, Signal, Slot, QTextCodec
|
||||
from PySide2.QtGui import QTextCursor, QColor
|
||||
from PySide2.QtWidgets import QApplication, QPlainTextEdit
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: move this into some kind of substance specific configuration
|
||||
# SDK\substance should probably have it's own .env and settings.py (dynaconf)
|
||||
# Note: pysbs has it's own installer and it wants to install itself directly
|
||||
# into a python distribution (assumes vanilla external)
|
||||
# we don't want to modify lumberyards core distribution
|
||||
# and we can't ship pusbs anyway, so I used pip install --target
|
||||
# and installed it into a folder within the substance local installation
|
||||
# so a data driven configuration needs to allow this to be easily set
|
||||
# outside of the DCCsi
|
||||
from azpy.constants import PATH_SAT_INSTALL_PATH
|
||||
_PYSBS_DIR_PATH = Path(PATH_SAT_INSTALL_PATH).resolve()
|
||||
site.addsitedir(str(_PYSBS_DIR_PATH)) # 'install' is the folder I created
|
||||
|
||||
import pysbs
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as pysbs_context
|
||||
|
||||
# local modules
|
||||
import sbsar_utils
|
||||
from atom_material import AtomMaterial
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# To Do: still should manage via dynaconf (dynamic config and settings)
|
||||
from azpy.constants import ENVAR_LY_DEV
|
||||
_LY_DEV = Path(os.getenv(ENVAR_LY_DEV, None)).resolve()
|
||||
|
||||
from azpy.constants import ENVAR_LY_PROJECT
|
||||
_LY_PROJECT = os.getenv(ENVAR_LY_PROJECT, None)
|
||||
|
||||
from azpy.constants import ENVAR_LY_PROJECT_PATH
|
||||
_LY_PROJECT_PATH = Path(os.getenv(ENVAR_LY_PROJECT_PATH, None)).resolve()
|
||||
|
||||
from azpy.constants import ENVAR_DCCSI_SDK_PATH
|
||||
_DCCSI_SDK_PATH = Path(os.getenv(ENVAR_DCCSI_SDK_PATH, None)).resolve()
|
||||
|
||||
# build some reuseable path parts
|
||||
_PROJECT_ASSET_PATH = Path(_LY_PROJECT_PATH).resolve()
|
||||
_PROJECT_ASSETS_PATH = Path(_LY_PROJECT_PATH, 'Materials').resolve()
|
||||
|
||||
# To Do: figure out a proper way to deal with Lumberyard game projects
|
||||
_GEM_MATPLAY_PATH = Path(_LY_DEV, 'Gems', 'AtomContent', 'AtomMaterialPlayground').resolve()
|
||||
_GEM_ROYALTYFREE = Path(_LY_DEV, 'Gems', 'AtomContent', 'RoyaltyFreeAssets').resolve()
|
||||
_GEM_SUBSOURCELIBRARY = Path(_LY_DEV, 'Gems', 'AtomContent', 'SubstanceSourceLibrary').resolve()
|
||||
_SUB_LIBRARY_PATH = Path(_GEM_SUBSOURCELIBRARY, 'Assets', 'SubstanceSource', 'Library').resolve()
|
||||
# ^ This hard codes a bunch of known asset gems, again bad
|
||||
# To Do: figure out a proper way to scrap the gem registry from project
|
||||
|
||||
# path to watcher script
|
||||
_WATCHER_SCRIPT_PATH = Path(_DCCSI_SDK_PATH, 'substance', 'builder', 'watchdog', '__init__.py').resolve()
|
||||
|
||||
_TEX_RNDR_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve()
|
||||
_MAT_OUTPUT_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve()
|
||||
_SBSAR_COOK_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# sys.path.insert(0, os.path.abspath('../ui/'))
|
||||
# import main_win
|
||||
# sys.path.insert(0, os.path.abspath('..'))
|
||||
# from watchdog import *
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
class Window(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None, project_path=None, default_material_path=None):
|
||||
super(Window, self).__init__(parent)
|
||||
|
||||
# we should really init non-Qt stuff and set things up as properties
|
||||
if project_path is None:
|
||||
self.project_path = str(_LY_PROJECT_PATH)
|
||||
else:
|
||||
self.project_path = Path(project_path)
|
||||
|
||||
if default_material_path is None:
|
||||
self._default_material_path = Path(_DCCSIG_PATH,
|
||||
'sdk',
|
||||
'substance',
|
||||
'resources',
|
||||
'atom',
|
||||
'StandardPBR_AllProperties.material')
|
||||
else:
|
||||
self._default_material_path = default_material_path
|
||||
|
||||
# the start building up qt specific ui, etc
|
||||
# but really, much of this should be moved to a self.setup_ui() method
|
||||
self.root_directory = str(_PROJECT_ASSET_PATH)
|
||||
# os.chdir(self.root_directory) # change working dir path
|
||||
|
||||
self.browseButton = self.create_button("&Browse...", self.browse)
|
||||
self.browseButton1 = self.create_button("&Browse...", self.browse)
|
||||
self.findButton = self.create_button("&Find", self.find)
|
||||
self.removeButton = self.create_button("&Remove", self.remove)
|
||||
self.startWatch = self.create_button("&Start Watch", self.watch)
|
||||
self.killButton = self.create_button("&Stop Watch", self.kill)
|
||||
self.clearlogButton = self.create_button("&Clear Log", self.clear_log)
|
||||
self.atomMaterial = self.create_button("&Atom Material", self.atom_material)
|
||||
|
||||
self.substance_ext_filters = ["*.sbs", "*.sbsar"]
|
||||
|
||||
self.textComboBox = self.createComboBox()
|
||||
# ^^ this is never added to a layout ... it's a phatom text entry that is empty
|
||||
# it is never updated to add text
|
||||
# but it's contents are retreived for use in self.find()
|
||||
|
||||
self.atomMatNameComboBox = self.createComboBox("Setup Material name here(default:sbsar name)")
|
||||
|
||||
# _texturePath = Path(self.project_path, 'Assets', 'SubstanceLibrary', 'textures').resolve()
|
||||
self.texRenderPathComboBox = self.createComboBox(str(_TEX_RNDR_PATH))
|
||||
self.matOutputPathComboBox = self.createComboBox(str(_MAT_OUTPUT_PATH))
|
||||
|
||||
# self.directoryComboBox = self.createComboBox(QtCore.QDir.currentPath())
|
||||
# I changed this to scan the _LY_PROJECT
|
||||
# self.sbsarDirectory = self.return_1st_sbsar(Path(self.project_path, 'Assets')).resolve().parent
|
||||
self.sbsarDirectory = QtCore.QDir()
|
||||
self.sbsarDirectory.setCurrent(str(_PROJECT_ASSET_PATH))
|
||||
self.sbsarDirectory.setNameFilters(self.substance_ext_filters)
|
||||
|
||||
self.directoryComboBox = self.createComboBox(self.sbsarDirectory.canonicalPath())
|
||||
self.builderDirComboBox = self.createComboBox(self.sbsarDirectory.canonicalPath())
|
||||
|
||||
# I DO NOT like or want all of this ui creation here in the __init__,
|
||||
# it is literally no better then a .ui file
|
||||
# Rob G is wrong, except he is right if we are building with custom widgets
|
||||
# but almost nothing here is custom so it's waste of lines of code
|
||||
# extremely difficult to iterate on ... I feel this contributes to
|
||||
# the length of time spent to stand this tool up
|
||||
# textLabel = QtWidgets.QLabel("Containing text:")
|
||||
self.directoryLabel = QtWidgets.QLabel("Watch directory:")
|
||||
self.builderdirLabel = QtWidgets.QLabel("Watch directory:")
|
||||
self.filesFoundLabel = QtWidgets.QLabel()
|
||||
self.watcherLogLabel = QtWidgets.QLabel("Watcher Log")
|
||||
self.fileListLabel = QtWidgets.QLabel("File List")
|
||||
self.sbsarPresetLabel = QtWidgets.QLabel("Sbsar Preset:")
|
||||
self.OutputResLabel = QtWidgets.QLabel("Output Size:")
|
||||
self.OutputsLabel = QtWidgets.QLabel("Outputs:")
|
||||
self.atomMatLabel = QtWidgets.QLabel("Material Name: ")
|
||||
self.atomMatDirLabel = QtWidgets.QLabel("Material Path : ")
|
||||
self.textOutputPathLabel = QtWidgets.QLabel("Texture Output:")
|
||||
self.randomSeedLabel = QtWidgets.QLabel("Random Seed:")
|
||||
|
||||
self.watcher_log = QPlainTextEdit()
|
||||
self.watcher_log.setReadOnly(True)
|
||||
# self.watcher_log.setFixedHeight(500)
|
||||
self.watcher_log.setMaximumBlockCount(10000) # limit console to 10000 lines
|
||||
self.watcher_log._cursor_output = self.watcher_log.textCursor()
|
||||
|
||||
self.create_files_table()
|
||||
buttonsLayout = QtWidgets.QHBoxLayout()
|
||||
buttonsLayout.addStretch()
|
||||
# buttonsLayout.addWidget(self.atomMaterial)
|
||||
# buttonsLayout.addSpacing(50)
|
||||
buttonsLayout.addWidget(self.findButton)
|
||||
# buttonsLayout.addWidget(self.removeButton)
|
||||
watcherBtnLayout = QtWidgets.QHBoxLayout()
|
||||
watcherBtnLayout.addStretch()
|
||||
watcherBtnLayout.addWidget(self.startWatch)
|
||||
watcherBtnLayout.addWidget(self.killButton)
|
||||
watcherBtnLayout.addWidget(self.clearlogButton)
|
||||
|
||||
self.setObjectName("Dialog")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self)
|
||||
self.gridLayout.setObjectName("gridLayout1_0")
|
||||
self.tabLayout = QtWidgets.QTabWidget(self)
|
||||
self.tabLayout.setObjectName("tabLayout")
|
||||
self.tabBuilder = QtWidgets.QTabWidget()
|
||||
self.tabBuilder.setObjectName("tabBuilder")
|
||||
self.gridLayout1_1 = QtWidgets.QGridLayout(self.tabBuilder)
|
||||
self.gridLayout1_1.setObjectName("gridLayout1_1")
|
||||
self.builderLayout = QtWidgets.QGridLayout()
|
||||
self.builderLayout.setObjectName("builderLayout")
|
||||
|
||||
# self.extensionLabel = QtWidgets.QLabel("Watch extensions:")
|
||||
# self.builderLayout.addWidget(self.extensionLabel, 0, 0, 1, 1)
|
||||
# self.extFilterComboBox = self.createComboBox(str(self.substance_ext_filters))
|
||||
# self.builderLayout.addWidget(self.extFilterComboBox, 0, 1, 1, 2)
|
||||
|
||||
# filename for search
|
||||
self.filenameSearchLabel = QtWidgets.QLabel("Filename search:")
|
||||
self.builderLayout.addWidget(self.filenameSearchLabel, 0, 0, 1, 1)
|
||||
self.filenameSearchComboBox = self.createComboBox(None)
|
||||
self.builderLayout.addWidget(self.filenameSearchComboBox, 0, 1, 1, 2)
|
||||
|
||||
self.builderLayout.addWidget(self.builderdirLabel, 1, 0)
|
||||
self.builderLayout.addWidget(self.builderDirComboBox, 1, 1, 1, 1)
|
||||
self.builderLayout.addWidget(self.browseButton1, 1, 2, 1, 1)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.addWidget(self.filesTable)
|
||||
self.verticalLayout.addWidget(self.filesFoundLabel)
|
||||
self.builderLayout.addLayout(self.verticalLayout, 2, 0, 1, 3)
|
||||
self.builderLayout.addLayout(buttonsLayout, 4, 0, 1, 3)
|
||||
self.inputGroupBox = QtWidgets.QGroupBox("Input Parameters")
|
||||
self.inputGridLayout = QtWidgets.QGridLayout(self.inputGroupBox)
|
||||
self.inputGridLayout.addWidget(self.randomSeedLabel, 2, 0, 1, 1)
|
||||
self.randomSeedComboBox = self.createComboBox("0000000000")
|
||||
self.inputGridLayout.addWidget(self.randomSeedComboBox, 2, 1, 1, 2)
|
||||
self.randomSeedBtn = self.create_button("&Generate", self.randomseed)
|
||||
self.inputGridLayout.addWidget(self.randomSeedBtn, 2, 3, 1, 1)
|
||||
self.randomSeedComboBox.setDisabled(True)
|
||||
self.gridLayout1_1.addLayout(self.builderLayout, 0, 0, 1, 1)
|
||||
self.tabLayout.addTab(self.tabBuilder, "Builder")
|
||||
|
||||
self.tabWatcher = QtWidgets.QTabWidget()
|
||||
self.tabWatcher.setObjectName("tabWatcher")
|
||||
self.gridLayout2_1 = QtWidgets.QGridLayout(self.tabWatcher)
|
||||
self.gridLayout2_1.setObjectName("gridLayout2_0")
|
||||
self.watcherLayout = QtWidgets.QGridLayout()
|
||||
self.watcherLayout.setObjectName("watcherLayout")
|
||||
self.watcherLayout.addWidget(self.directoryLabel, 0, 0)
|
||||
self.watcherLayout.addWidget(self.directoryComboBox, 0, 1)
|
||||
self.watcherLayout.addWidget(self.browseButton, 0, 2)
|
||||
self.watcherHBoxLayout = QtWidgets.QHBoxLayout()
|
||||
self.watcherHBoxLayout.addWidget(self.watcher_log)
|
||||
self.watcherLayout.addLayout(self.watcherHBoxLayout, 1, 0, 1, 3)
|
||||
self.watcherLayout.addLayout(watcherBtnLayout, 2, 0, 1, 3)
|
||||
self.gridLayout2_1.addLayout(self.watcherLayout, 0, 0, 1, 1)
|
||||
self.tabLayout.addTab(self.tabWatcher, "Watcher")
|
||||
self.gridLayout.addWidget(self.tabLayout)
|
||||
# self.setLayout(self.watcherLayout)
|
||||
|
||||
self.setWindowTitle("Substance Builder")
|
||||
self.resize(800, 600)
|
||||
# self.path = self.directoryComboBox.currentText()
|
||||
self.path = Path(self.project_path)
|
||||
self.groupboxOutputs = QtWidgets.QGroupBox("Outputs")
|
||||
self.groupboxMat = QtWidgets.QGroupBox("Atom Material && Textures")
|
||||
self.watcher_script = _WATCHER_SCRIPT_PATH
|
||||
self.outputRenderPath = _TEX_RNDR_PATH
|
||||
self.preset = ''
|
||||
self.presetItem = ''
|
||||
self.texRenderpath = self.texRenderPathComboBox.currentText()
|
||||
self.res = 11
|
||||
self.output_name = '{inputGraphUrl}_{outputNodeName}'
|
||||
self.randomstr = 1
|
||||
self.preset_index = -1
|
||||
self.outputCookPath = _SBSAR_COOK_PATH
|
||||
self.selected_tex = []
|
||||
self.material_path = _MAT_OUTPUT_PATH
|
||||
|
||||
self._reader = self.start_reader()
|
||||
|
||||
def start_reader(self):
|
||||
# set up
|
||||
self._reader = ProcessOutputReader()
|
||||
self._reader.produce_output.connect(self.append_output)
|
||||
self._reader.start('python', ['-u', self.watcher_script, self.sbsarDirectory])
|
||||
return self._reader
|
||||
|
||||
@Slot(str)
|
||||
def append_output(self, text):
|
||||
self.watcher_log._cursor_output.insertText(text)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def scroll_to_last_line(self):
|
||||
cursor = self.watcher_log.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
|
||||
QTextCursor.StartOfLine)
|
||||
self.watcher_log.setTextCursor(cursor)
|
||||
|
||||
def output_text(self, text):
|
||||
self.watcher_log._cursor_output.insertText(text)
|
||||
self.watcher_log.scroll_to_last_line()
|
||||
|
||||
def browse(self):
|
||||
self.root_directory = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||
"Find watcher folder",
|
||||
str(_SUB_LIBRARY_PATH))
|
||||
if self.root_directory:
|
||||
if self.directoryComboBox.findText(self.root_directory) or self.builderDirComboBox.findText(
|
||||
self.root_directory) == -1:
|
||||
self.directoryComboBox.addItem(self.root_directory)
|
||||
self.builderDirComboBox.addItem(self.root_directory)
|
||||
|
||||
self.directoryComboBox.setCurrentIndex(self.directoryComboBox.findText(self.root_directory))
|
||||
self.builderDirComboBox.setCurrentIndex(self.builderDirComboBox.findText(self.root_directory))
|
||||
|
||||
self.path = self.root_directory
|
||||
self.find()
|
||||
return self.path
|
||||
|
||||
def browseRenderPath(self):
|
||||
self.texRenderpath = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||
"Setup textures output path",
|
||||
str(_TEX_RNDR_PATH))
|
||||
if self.texRenderpath:
|
||||
if self.texRenderPathComboBox.findText(self.texRenderpath) == -1:
|
||||
self.texRenderPathComboBox.addItem(self.texRenderpath)
|
||||
|
||||
self.texRenderPathComboBox.setCurrentIndex(self.texRenderPathComboBox.findText(self.texRenderpath))
|
||||
self.texRenderPathComboBox.setCurrentText(self.texRenderpath)
|
||||
return self.texRenderpath
|
||||
|
||||
def browseMaterialPath(self):
|
||||
self.material_path = QtWidgets.QFileDialog.getExistingDirectory(self,
|
||||
"Setup Atom Material output path",
|
||||
str(_MAT_OUTPUT_PATH))
|
||||
if self.material_path:
|
||||
if self.matOutputPathComboBox.findText(self.material_path) == -1:
|
||||
self.matOutputPathComboBox.addItem(self.material_path)
|
||||
|
||||
self.matOutputPathComboBox.setCurrentIndex(self.matOutputPathComboBox.findText(self.material_path))
|
||||
self.matOutputPathComboBox.setCurrentText(self.material_path)
|
||||
return self.material_path
|
||||
|
||||
@staticmethod
|
||||
def updateComboBox(comboBox):
|
||||
if comboBox.findText(comboBox.currentText()) == -1:
|
||||
comboBox.addItem(comboBox.currentText())
|
||||
|
||||
def find(self, rootpath=None, filename=None, FILE_TEMPLATES=['*.sbs', '*.sbsar']):
|
||||
self.filesTable.setRowCount(0)
|
||||
|
||||
if not filename:
|
||||
fileName = self.filenameSearchComboBox.currentText()
|
||||
# ^ this looks like it is meant to either be a file name pattern for search
|
||||
# or ext patterns? Or both?
|
||||
|
||||
text = self.textComboBox.currentText()
|
||||
# ^ what is this even for???
|
||||
# it never gets filled out but we continue to pass it on?
|
||||
# I assume it's really meant to be a file name and file ext pattern search?
|
||||
|
||||
if rootpath:
|
||||
self.path = rootpath
|
||||
else:
|
||||
self.path = self.builderDirComboBox.currentText()
|
||||
|
||||
self.directoryComboBox.setCurrentText(self.path)
|
||||
# self.path = "C:/Users/chunghao/Documents/Allegorithmic/Substance Designer/sbsar"
|
||||
|
||||
self.updateComboBox(self.filenameSearchComboBox)
|
||||
self.updateComboBox(self.textComboBox)
|
||||
self.updateComboBox(self.directoryComboBox)
|
||||
|
||||
self.currentDir = QtCore.QDir(str(Path(self.path).resolve()))
|
||||
|
||||
dirModel = QtWidgets.QFileSystemModel()
|
||||
dirModel.setRootPath(self.currentDir.canonicalPath())
|
||||
dirModel.setFilter(QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Dirs)
|
||||
if fileName:
|
||||
FILE_TEMPLATES = [str(fileName)]
|
||||
dirModel.setNameFilters(FILE_TEMPLATES)
|
||||
|
||||
# right now this filters to *.sbs and *.sbsar
|
||||
# we use the project\assets dir as the root and query the filesystem
|
||||
# instead of only a single directory
|
||||
iterator = QtCore.QDirIterator(self.currentDir.canonicalPath(),
|
||||
FILE_TEMPLATES,
|
||||
QtCore.QDir.Files,
|
||||
QtCore.QDirIterator.Subdirectories)
|
||||
|
||||
# now we can iterate over that filtered filesystem
|
||||
files = []
|
||||
while iterator.hasNext():
|
||||
files.append(iterator.next())
|
||||
files.sort()
|
||||
|
||||
# if not fileName or filename == '':
|
||||
# fileName = "*"
|
||||
# files = self.currentDir.entryList([fileName],
|
||||
# QtCore.QDir.Files | QtCore.QDir.NoSymLinks)
|
||||
|
||||
files = self.find_files(files, text)
|
||||
self.show_files(files)
|
||||
return self.path
|
||||
|
||||
def find_files(self, files, text):
|
||||
progressDialog = QtWidgets.QProgressDialog(self)
|
||||
|
||||
progressDialog.setCancelButtonText("&Cancel")
|
||||
progressDialog.setRange(0, len(files))
|
||||
progressDialog.setWindowTitle("Find Files")
|
||||
|
||||
foundFiles = []
|
||||
|
||||
for i in range(len(files)):
|
||||
progressDialog.setValue(i)
|
||||
progressDialog.setLabelText("Searching file number %d of %d..." % (i, len(files)))
|
||||
QtCore.qApp.processEvents()
|
||||
|
||||
if progressDialog.wasCanceled():
|
||||
break
|
||||
|
||||
inFile = QtCore.QFile(self.currentDir.absoluteFilePath(files[i]))
|
||||
|
||||
if inFile.open(QtCore.QIODevice.ReadOnly):
|
||||
stream = QtCore.QTextStream(inFile)
|
||||
while not stream.atEnd():
|
||||
if progressDialog.wasCanceled():
|
||||
break
|
||||
line = stream.readLine()
|
||||
if text in line:
|
||||
foundFiles.append(files[i])
|
||||
break
|
||||
|
||||
progressDialog.close()
|
||||
|
||||
return foundFiles
|
||||
|
||||
def show_files(self, files):
|
||||
for fn in files:
|
||||
file = QtCore.QFile(self.currentDir.absoluteFilePath(fn))
|
||||
size = QtCore.QFileInfo(file).size()
|
||||
|
||||
fileNameItem = QtWidgets.QTableWidgetItem(fn)
|
||||
fileNameItem.setFlags(fileNameItem.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
|
||||
sizeItem = QtWidgets.QTableWidgetItem("%d KB" % (int((size + 1023) / 1024)))
|
||||
sizeItem.setTextAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
|
||||
sizeItem.setFlags(sizeItem.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
row = self.filesTable.rowCount()
|
||||
self.filesTable.insertRow(row)
|
||||
self.filesTable.setItem(row, 0, fileNameItem)
|
||||
self.filesTable.setItem(row, 1, sizeItem)
|
||||
self.filesTable.setAlternatingRowColors(True)
|
||||
self.filesTable.setStyleSheet("alternate-background-color: #444444; background-color: #4d4d4d;")
|
||||
self.filesFoundLabel.setText(
|
||||
"%d file(s) found (Double click on a SBSAR to cook, SBS file to load.)" % len(files))
|
||||
|
||||
def create_button(self, text, member):
|
||||
button = QtWidgets.QPushButton(text)
|
||||
button.clicked.connect(member)
|
||||
return button
|
||||
|
||||
def createComboBox(self, text=""):
|
||||
comboBox = QtWidgets.QComboBox()
|
||||
comboBox.setEditable(True)
|
||||
comboBox.addItem(text)
|
||||
comboBox.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Preferred)
|
||||
return comboBox
|
||||
|
||||
def create_files_table(self):
|
||||
self.filesTable = QtWidgets.QTableWidget(0, 2)
|
||||
# self.filesTable.setMinimumHeight(300)
|
||||
# self.filesTable.setFixedHeight(300)
|
||||
self.filesTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.filesTable.setHorizontalHeaderLabels(("File Name", "Size"))
|
||||
self.filesTable.horizontalHeaderItem(0).setTextAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignLeft)
|
||||
self.filesTable.horizontalHeaderItem(1).setTextAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
|
||||
# self.filesTable.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignRight)
|
||||
self.filesTable.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||
self.filesTable.horizontalHeader().setDefaultSectionSize(5)
|
||||
self.filesTable.verticalHeader().hide()
|
||||
# self.filesTable.verticalHeader().show()
|
||||
self.filesTable.setColumnWidth(1, 80)
|
||||
self.filesTable.setShowGrid(False)
|
||||
self.filesTable.verticalHeader().setDefaultSectionSize(5)
|
||||
# style = "::section {""background-color: (241, 255, 175); }"
|
||||
# self.filesTable.setStyleSheet(style)
|
||||
# self.filesTable.setFixedHeight(200)
|
||||
self.filesTable.cellActivated.connect(self.open_file_of_item)
|
||||
|
||||
def create_sbsar_tex(self):
|
||||
_LOGGER.debug('Running: .create_sbsar_tex')
|
||||
# self.builderLayout.addWidget(self.OutputsLabel, 10, 0)
|
||||
self.sbsarName = self.item.text().replace(".sbsar", "")
|
||||
self.groupboxOutputs = QtWidgets.QGroupBox("Output Texture Maps")
|
||||
self.horizontalLayoutOuputs = QtWidgets.QHBoxLayout(self.groupboxOutputs)
|
||||
self.horizontalLayoutOuputs.setObjectName(("horizontalLayoutOuputs"))
|
||||
# self.vertical_layout_outputs = QtWidgets.QVBoxLayout(self.groupboxOutputs)
|
||||
# self.vertical_layout_outputs.setObjectName("vertical_layout_outputs")
|
||||
self.sbsar_tex_outputs = sbsar_utils.output_info(self.path, self.sbsarName, 'tex_maps')
|
||||
|
||||
for index, tex in enumerate(self.sbsar_tex_outputs):
|
||||
_LOGGER.info(tex)
|
||||
#text_item_checkbox = QtWidgets.QCheckBox(self.groupboxOutputs)
|
||||
#text_item_checkbox.setObjectName(self.sbsarName + "_" + tex)
|
||||
# text_item_checkbox.setEnabled(True)
|
||||
# text_item_checkbox.setChecked(True)
|
||||
# text_item_checkbox.setText(tex)
|
||||
# self.horizontalLayoutOuputs.addWidget(text_item_checkbox)
|
||||
|
||||
self.sbsar_tex_outputs[index] = QtWidgets.QCheckBox(self.groupboxOutputs)
|
||||
self.sbsar_tex_outputs[index].setObjectName(self.sbsarName + "_" + tex)
|
||||
self.sbsar_tex_outputs[index].setEnabled(True)
|
||||
self.sbsar_tex_outputs[index].setChecked(True)
|
||||
self.sbsar_tex_outputs[index].setText(tex)
|
||||
self.horizontalLayoutOuputs.addWidget(self.sbsar_tex_outputs[index])
|
||||
|
||||
self.builderLayout.addWidget(self.groupboxOutputs, 7, 0, 1, 3)
|
||||
|
||||
def create_sbsar_presets(self):
|
||||
_LOGGER.debug('Running: .create_sbsar_presets')
|
||||
self.inputGridLayout.addWidget(self.sbsarPresetLabel, 0, 0, 1, 1)
|
||||
self.sbsar_presets = sbsar_utils.output_info(self.path, self.item.text().replace(".sbsar", ""), 'presets')
|
||||
self.comboBox_SbsarPreset = QtWidgets.QComboBox(self)
|
||||
self.comboBox_SbsarPreset.addItem("Default preset")
|
||||
for self.presetItem in self.sbsar_presets:
|
||||
_LOGGER.info(self.presetItem)
|
||||
# preset_index = 0
|
||||
self.comboBox_SbsarPreset.addItem(self.presetItem, self.presetItem)
|
||||
# self.comboBox_SbsarPreset.setItemText(preset_index, self.presetItem)
|
||||
# preset_index += 1
|
||||
|
||||
self.inputGridLayout.addWidget(self.comboBox_SbsarPreset, 0, 1, 1, 3)
|
||||
self.comboBox_SbsarPreset.activated.connect(self.preset_selected)
|
||||
|
||||
def preset_selected(self, index):
|
||||
self.preset = self.comboBox_SbsarPreset.itemData(index)
|
||||
if self.preset in self.sbsar_presets:
|
||||
self.atomMatNameComboBox.setCurrentText(self.sbsarName + "_" + self.preset.replace(" ", ""))
|
||||
_LOGGER.info('Preset(variant): ' + self.atomMatNameComboBox.currentText())
|
||||
self.preset_index = index - 1
|
||||
_LOGGER.info('The Preset index: ' + str(self.preset_index))
|
||||
return self.preset_index
|
||||
# return self.atomMatNameComboBox.currentText()
|
||||
else:
|
||||
self.atomMatNameComboBox.setCurrentText(self.sbsarName)
|
||||
_LOGGER.info('Default preset selected.')
|
||||
self.preset_index = index - 1
|
||||
_LOGGER.info(self.preset_index)
|
||||
return self.preset_index
|
||||
|
||||
def create_tex_res(self):
|
||||
self.inputGridLayout.addWidget(self.OutputResLabel, 1, 0, 1, 1)
|
||||
self.resSelector = QtWidgets.QComboBox(self)
|
||||
# self.resSelector.addItem("512x512", "$outputsize@9,9")
|
||||
# self.resSelector.addItem("1024x1024", "$outputsize@10,10")
|
||||
# self.resSelector.addItem("2048x2048", "$outputsize@11,11")
|
||||
self.resSelector.addItem("512x512", 9)
|
||||
self.resSelector.addItem("1024x1024", 10)
|
||||
self.resSelector.addItem("2048x2048", 11)
|
||||
self.resSelector.addItem("4096x4096", 12)
|
||||
self.resSelector.addItem("8096x8096", 13)
|
||||
self.inputGridLayout.addWidget(self.resSelector, 1, 1, 1, 3)
|
||||
self.resSelector.activated.connect(self.res_selected)
|
||||
self.resSelector.setCurrentIndex(2)
|
||||
|
||||
def res_selected(self, index):
|
||||
self.res = self.resSelector.itemData(index)
|
||||
_LOGGER.info(self.res)
|
||||
return self.res
|
||||
|
||||
def remove(self):
|
||||
self.builderLayout.removeWidget(self.groupboxOutputs)
|
||||
self.groupboxOutputs.close()
|
||||
|
||||
def kill(self):
|
||||
self._reader.kill()
|
||||
|
||||
def clear_log(self):
|
||||
self.watcher_log.clear()
|
||||
|
||||
def atom_material(self, preset=None,
|
||||
material_type=None,
|
||||
tex_ext='.tif',
|
||||
mat_ext='.material'):
|
||||
"""To Do"""
|
||||
if preset is None:
|
||||
preset = self.preset
|
||||
|
||||
preset = str(preset).replace(" ", "-")
|
||||
|
||||
# setup material template
|
||||
if material_type is None:
|
||||
material = AtomMaterial(self._default_material_path)
|
||||
|
||||
mat_file_tag = str(self.atomMatNameComboBox.currentText()).replace(" ", "_")
|
||||
|
||||
# file_path = Path(self.atomMatNameComboBox.currentText())
|
||||
# file_name = file_path.stem
|
||||
# ^^ object oriented paths do all these things you want without destrpying the path
|
||||
# write less code, use your autocomplete, reed the docs
|
||||
# p.name, Final component only (name.ext)
|
||||
# p.stem, Final component without extension.
|
||||
# p.ext, Extension only. .suffix in pathlib
|
||||
|
||||
# if you want relative paths here is a better way
|
||||
# first of all, assume we know the project we are in
|
||||
#_LY_PROJECT_PATH
|
||||
|
||||
texture_output_path = Path(self.texRenderPathComboBox.currentText()).resolve()
|
||||
rel_tex_path = None
|
||||
for p in texture_output_path.parts:
|
||||
if _LY_PROJECT == p:
|
||||
index = texture_output_path.parts.index(_LY_PROJECT)
|
||||
rel_tuple = texture_output_path.parts[index + 1:]
|
||||
rel_tex_path = Path(*list(rel_tuple))
|
||||
|
||||
# if 'ambientocclusion' in self.preset:
|
||||
# num_index = 5
|
||||
# else:
|
||||
num_index = 5
|
||||
# ^^ ok what is all this magic mumbo jumbo?
|
||||
|
||||
if preset is None or preset == '':
|
||||
preset_slug = preset # gets rid of _ when not needed
|
||||
else:
|
||||
preset_slug = '_' + preset
|
||||
|
||||
for tex_index in range(num_index):
|
||||
# tex_map_name = material.texture_map[material.tex[tex_index]] + tex_ext
|
||||
# ^ this is not so great, AtomMaterial is very rigid and only very specifically
|
||||
# supports a hand coded implmentation of StandardPBR
|
||||
# we should have a more generic material class that can consume any
|
||||
# then we can somehow check for the type we are compatible with
|
||||
# after that you might want a wrapper class above the atom material data that knows how
|
||||
# to map from substance to the atom material, and builds a specific data structure above
|
||||
# the generic one
|
||||
|
||||
material_texture_slot = material.texture_map[material.tex[tex_index]]
|
||||
material_slot_texture = material_texture_slot + tex_ext
|
||||
|
||||
# text_map_path = Path(rel_tex_path + "\\" + tex_file_name + "_" + tex_map_name).resolve()
|
||||
texture_slug = mat_file_tag + preset_slug + '_' + material_slot_texture
|
||||
rel_texture_map_path = Path(rel_tex_path, texture_slug) # don't .resolve()
|
||||
|
||||
# posiz / slash
|
||||
material.set_map(material.tex[tex_index], str(rel_texture_map_path.as_posix()))
|
||||
|
||||
# mat_path = self.material_path + "/" + self.atomMatNameComboBox.currentText().replace(" ", "") + ".material"
|
||||
|
||||
material_path = Path(self.material_path, mat_file_tag + preset_slug + mat_ext)
|
||||
material.write(material_path)
|
||||
|
||||
self.preset = ''
|
||||
|
||||
_LOGGER.info('{}'.format(material_path))
|
||||
|
||||
return material_path
|
||||
|
||||
def create_single(self):
|
||||
if self.preset is None:
|
||||
self.atom_material(self.preset)
|
||||
else:
|
||||
self.atom_material(self.preset)
|
||||
|
||||
def create_all_material(self):
|
||||
# always make the make
|
||||
# self.atom_material('base')
|
||||
self.atom_material(self.preset)
|
||||
|
||||
# then make any others
|
||||
if len(self.sbsar_presets) > 0:
|
||||
for i in self.sbsar_presets:
|
||||
_LOGGER.info('Preset(variant): {}'.format(i.replace(" ", "")))
|
||||
self.atom_material(i)
|
||||
|
||||
def print_something(self, words):
|
||||
self.words = words
|
||||
_LOGGER.info(self.words)
|
||||
|
||||
def watch(self):
|
||||
self._reader.start('python', ['-u', self.watcher_script, self.sbsarDirectory])
|
||||
|
||||
def create_atom_mat(self, item_path=None):
|
||||
self.atom_matPath_button = self.create_button("&Browse", self.browseMaterialPath)
|
||||
self.atom_material_button = self.create_button("&Create Single", self.create_single)
|
||||
self.atom_material_output = self.create_button("&Create All", self.create_all_material)
|
||||
self.renderPathbutton = self.create_button("&Output Path", self.browseRenderPath)
|
||||
self.rendeTexbutton = self.create_button("&Render Textures", self.render_tex)
|
||||
|
||||
if item_path is None:
|
||||
item_path = Path(self.item.text()).resolve()
|
||||
|
||||
# self.sbsarName = self.item.text().replace(".sbsar", "")
|
||||
self.sbsarName = item_path.stem
|
||||
self.groupboxMat = QtWidgets.QGroupBox("Atom Material")
|
||||
self.atomMatNameComboBox.setCurrentText(self.sbsarName)
|
||||
# self.atomMatNameComboBox.setDisabled(True)
|
||||
# self.texRenderPathComboBox.setCurrentText(self.path)
|
||||
self.gridLayoutMat = QtWidgets.QGridLayout(self.groupboxMat)
|
||||
self.MatHBox0 = QtWidgets.QHBoxLayout()
|
||||
self.MatHBox0.addWidget(self.atomMatDirLabel)
|
||||
self.MatHBox0.addWidget(self.matOutputPathComboBox)
|
||||
self.MatHBox0.addWidget(self.atom_matPath_button)
|
||||
# self.MatHBox0.addWidget(self.atom_matPath_button2)
|
||||
self.gridLayoutMat.addLayout(self.MatHBox0, 0, 0, 1, 4)
|
||||
self.MatHBox = QtWidgets.QHBoxLayout()
|
||||
self.MatHBox.addWidget(self.atomMatLabel)
|
||||
self.MatHBox.addWidget(self.atomMatNameComboBox)
|
||||
self.MatHBox.addWidget(self.atom_material_output)
|
||||
self.MatHBox.addWidget(self.atom_material_button)
|
||||
self.gridLayoutMat.addLayout(self.MatHBox, 1, 0, 1, 4)
|
||||
self.MatHBox1 = QtWidgets.QHBoxLayout()
|
||||
self.MatHBox1.addWidget(self.textOutputPathLabel)
|
||||
self.MatHBox1.addWidget(self.texRenderPathComboBox)
|
||||
self.MatHBox1.addWidget(self.renderPathbutton)
|
||||
self.MatHBox1.addWidget(self.rendeTexbutton)
|
||||
self.gridLayoutMat.addLayout(self.MatHBox1, 2, 0, 1, 4)
|
||||
|
||||
self.builderLayout.addWidget(self.groupboxMat, 8, 0, 1, 3)
|
||||
# _LOG.info("\n")
|
||||
|
||||
def randomseed(self):
|
||||
import random
|
||||
random_num = random.randrange(1, 10 ** 10)
|
||||
self.randomstr = str(random_num)
|
||||
self.randomSeedComboBox.setCurrentText(str(random_num))
|
||||
_LOGGER.info('Random Seed: ' + self.randomstr)
|
||||
return self.randomstr
|
||||
|
||||
def render_tex(self):
|
||||
"""To Do"""
|
||||
|
||||
texture_output_path = Path(self.texRenderPathComboBox.currentText()).resolve()
|
||||
|
||||
sbsar_item_path = Path(self.item.text()).resolve()
|
||||
|
||||
if not texture_output_path.exists():
|
||||
try:
|
||||
texture_output_path.mkdir(mode=0o777)
|
||||
_LOGGER.info('mkdir: {}'.format(texture_output_path))
|
||||
except Exception as e:
|
||||
_LOGGER.ERROR(e)
|
||||
raise(e)
|
||||
else:
|
||||
_LOGGER.info('exists: {}'.format(texture_output_path))
|
||||
|
||||
_LOGGER.info('Render texture from: ' + self.path + "/" + self.item.text())
|
||||
|
||||
# for index, tex in enumerate(self.groupboxOutputs.children()):
|
||||
# if self.groupboxOutputs.children()
|
||||
|
||||
for index, tex in enumerate(self.sbsar_tex_outputs):
|
||||
if self.sbsar_tex_outputs[index].isChecked() is True:
|
||||
self.selected_tex.append(tex.text())
|
||||
print(self.selected_tex)
|
||||
|
||||
self.outputCookPath = sbsar_item_path.parent
|
||||
|
||||
sbsar_utils.render_sbsar(self.outputCookPath,
|
||||
self.selected_tex,
|
||||
sbsar_item_path.stem,
|
||||
texture_output_path,
|
||||
self.preset_index,
|
||||
self.res,
|
||||
self.randomstr)
|
||||
self.selected_tex = []
|
||||
|
||||
def find_1st_sbsar(self, seedpath=None):
|
||||
i = None # previously broke if found none
|
||||
|
||||
if seedpath is None:
|
||||
seedpath = Path(self.project_path, 'Assets')
|
||||
|
||||
filelist = []
|
||||
for r, d, f in os.walk(seedpath):
|
||||
for file in f:
|
||||
filelist.append(file)
|
||||
for i in range(len(filelist)):
|
||||
if filelist[i].split(".")[-1] == "sbsar":
|
||||
return i
|
||||
else:
|
||||
i += 1
|
||||
return i
|
||||
|
||||
def return_1st_sbsar(self, seedpath=None):
|
||||
if seedpath is None:
|
||||
seedpath = Path(self.project_path, 'Assets')
|
||||
|
||||
filelist = []
|
||||
for r, d, f in os.walk(seedpath):
|
||||
for file in f:
|
||||
if Path(file).ext == ".sbsar":
|
||||
filelist.append(Path(r, file).absolute())
|
||||
|
||||
return filelist[0]
|
||||
|
||||
def open_file_of_item(self, row, column):
|
||||
self.item = self.filesTable.item(row, 0)
|
||||
item_path = Path(self.item.text()).resolve()
|
||||
_LOGGER.debug('Item path: {}'.format(item_path))
|
||||
|
||||
# Load sbsar parameters.
|
||||
if PurePath(item_path).suffix == ".sbsar":
|
||||
self.create_sbsar_presets()
|
||||
self.create_tex_res()
|
||||
self.builderLayout.removeWidget(self.groupboxOutputs)
|
||||
self.groupboxOutputs.close()
|
||||
self.create_sbsar_tex()
|
||||
self.builderLayout.removeWidget(self.groupboxMat)
|
||||
self.groupboxMat.close()
|
||||
self.builderLayout.addWidget(self.inputGroupBox, 5, 0, 1, 3)
|
||||
self.randomSeedComboBox.setCurrentText("0000000000")
|
||||
self.create_atom_mat(item_path)
|
||||
|
||||
# Cook sbsar file and trigger watcher to generate textures.
|
||||
elif self.item.text().split(".")[-1] == "sbs":
|
||||
sbsar_utils.cook_sbsar(self.path + "/" + self.item.text(), sbsar_utils._PYSBS_CONTEXT, self.path,
|
||||
self.item.text().split(".")[0])
|
||||
sbsar_utils.output_info()
|
||||
|
||||
|
||||
class ProcessOutputReader(QProcess):
|
||||
produce_output = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# merge stderr channel into stdout channel
|
||||
self.setProcessChannelMode(QProcess.MergedChannels)
|
||||
# prepare decoding process' output to Unicode
|
||||
self._codec = QTextCodec.codecForLocale()
|
||||
self._decoder_stdout = self._codec.makeDecoder()
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self._decoder_stderr = codec.makeDecoder()
|
||||
|
||||
self.readyReadStandardOutput.connect(self._ready_read_standard_output)
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self.readyReadStandardError.connect(self._ready_read_standard_error)
|
||||
|
||||
@Slot()
|
||||
def _ready_read_standard_output(self):
|
||||
raw_bytes = self.readAllStandardOutput()
|
||||
text = self._decoder_stdout.toUnicode(raw_bytes)
|
||||
self.produce_output.emit(text)
|
||||
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# @Slot()
|
||||
# def _ready_read_standard_error(self):
|
||||
# raw_bytes = self.readAllStandardError()
|
||||
# text = self._decoder_stderr.toUnicode(raw_bytes)
|
||||
# self.produce_output.emit(text)
|
||||
|
||||
|
||||
def substance_builder_launcher():
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
window = Window()
|
||||
|
||||
_LOGGER.info('cwd: {}'.format(os.getcwd())) # project
|
||||
_LOGGER.info('file: {}'.format(__file__)) # *might* come back relative
|
||||
|
||||
# we should ensure we know the abs path
|
||||
qss_filepath = Path(_DCCSIG_PATH).resolve().absolute()
|
||||
qss_filepath = Path(qss_filepath, 'SDK', 'substance', 'builder',
|
||||
'ui', 'stylesheets', 'LYstyle.qss').resolve().absolute()
|
||||
window.setStyleSheet(qss_filepath.read_text())
|
||||
# window.find() # uhm ... this is VERY odd, shouldn't you do this in __init__?
|
||||
window.find()
|
||||
first_item = window.find_1st_sbsar()
|
||||
window.show()
|
||||
# window.open_file_of_item(first_item, 0)
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.info("Test Run:: {0}.".format({_MODULENAME}))
|
||||
_LOGGER.info("{0} :: if __name__ == '__main__':".format(_MODULENAME))
|
||||
|
||||
_LOGGER.info('file: {}'.format(os.getcwd())) # project
|
||||
_LOGGER.info('file: {}'.format(__file__)) # *might* come back relative
|
||||
|
||||
substance_builder_launcher()
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import subprocess
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
from azpy.constants import *
|
||||
|
||||
# 3rdparty
|
||||
from unipath import Path
|
||||
from unipath import FILES
|
||||
import click
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: manage with dynaconf environment
|
||||
_PYSBS_DIR_PATH = Path(PATH_PROGRAMFILES_X64,
|
||||
'Allegorithmic',
|
||||
'Substance Automation Toolkit',
|
||||
'Python API',
|
||||
'install').resolve()
|
||||
|
||||
site.addsitedir(str(_PYSBS_DIR_PATH)) # 'install' is the folder I created
|
||||
|
||||
# Susbstance
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as pysbs_context
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.substance.builder.sbs_to_sbsar'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Defining CONSTANTS
|
||||
# To Do: shouldn't need this _BASE_ENVVAR_DICT (replace with dynaconf config)
|
||||
from collections import OrderedDict
|
||||
_SYNTH_ENV_DICT = OrderedDict()
|
||||
_SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT)
|
||||
# grab a specific path from the base_env
|
||||
_PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH]
|
||||
_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH]
|
||||
|
||||
# build some reuseable path parts
|
||||
_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm()
|
||||
_PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm()
|
||||
|
||||
_PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm()
|
||||
_PATH_MOCK_SBSAR = Path(_PATH_MOCK_SUBLIB, 'sbsar').norm()
|
||||
|
||||
_PATH_MOCK_MAT = Path(_PATH_MOCK_ASSETS, 'Textures').norm()
|
||||
_PATH_MOCK_MAT_SUB = Path(_PATH_MOCK_MAT, 'Substance').norm()
|
||||
|
||||
# this will combine two parts into a single path (object)
|
||||
# It also returnd the fixed-up version (norm)
|
||||
_PATH_INPUT_SBS = Path(_PATH_MOCK_SBS, 'alien_rock_coral_formation', 'alien_rock_coral_formation.sbs').norm()
|
||||
_PATH_COOK_OUTPUT = _PATH_MOCK_SBSAR.norm()
|
||||
_PATH_RENDER_OUTPUT = _PATH_MOCK_MAT_SUB
|
||||
|
||||
_SBS_NAME = _PATH_INPUT_SBS.split('.sbs')[0].split('\\')[-1]
|
||||
|
||||
# quick test variables, will be removed
|
||||
_PYSBS_CONTEXT = pysbs_context.Context()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@click.command()
|
||||
@click.option('--sbs_path', default=_PATH_INPUT_SBS, help='Sbs path.')
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar output path.')
|
||||
@click.option('--sbsar_name', default=_SBS_NAME, help='Sbsar output name.')
|
||||
def sbs_to_sbsar(sbs_path, sbsar_path, sbsar_name):
|
||||
""" Cook SBSAR from SBS """
|
||||
sbs_path = Path(sbs_path).norm()
|
||||
click.echo(sbs_path)
|
||||
sbsar_path = Path(sbsar_path).norm()
|
||||
click.echo(_SBS_NAME)
|
||||
pysbs_batch.sbscooker(quiet=True,
|
||||
inputs=sbs_path,
|
||||
includes=_PYSBS_CONTEXT.getDefaultPackagePath(),
|
||||
alias=_PYSBS_CONTEXT.getUrlAliasMgr().getAllAliases(),
|
||||
output_path=sbsar_path,
|
||||
output_name=sbsar_name,
|
||||
compression_mode=2).wait()
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_PACKAGENAME))
|
||||
|
||||
_LOGGER.debug(sbs_to_sbsar())
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
from azpy.constants import *
|
||||
|
||||
# 3rdparty
|
||||
from unipath import Path
|
||||
import click
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: manage with dynaconf environment
|
||||
_PYSBS_DIR_PATH = Path(PATH_PROGRAMFILES_X64,
|
||||
'Allegorithmic',
|
||||
'Substance Automation Toolkit',
|
||||
'Python API',
|
||||
'install').resolve()
|
||||
|
||||
site.addsitedir(str(_PYSBS_DIR_PATH)) # 'install' is the folder I created
|
||||
|
||||
# Susbstance
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as pysbs_context
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.substance.builder.sbsar_info'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# global space debug flag
|
||||
_G_DEBUG = os.getenv(ENVAR_DCCSI_GDEBUG, False)
|
||||
|
||||
# global space debug flag
|
||||
_DCCSI_DEV_MODE = os.getenv(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_MODULE_PATH = Path(__file__)
|
||||
|
||||
_ORG_TAG = 'Amazon_Lumberyard'
|
||||
_APP_TAG = 'DCCsi'
|
||||
_TOOL_TAG = 'sdk.substance.builder.sbsar_info'
|
||||
_TYPE_TAG = 'module'
|
||||
|
||||
_MODULENAME = __name__
|
||||
if _MODULENAME is '__main__':
|
||||
_MODULENAME = _TOOL_TAG
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Defining CONSTANTS
|
||||
# To Do: shouldn't need this _BASE_ENVVAR_DICT (replace with dynaconf config)
|
||||
from collections import OrderedDict
|
||||
_SYNTH_ENV_DICT = OrderedDict()
|
||||
_SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT)
|
||||
# grab a specific path from the base_env
|
||||
_PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH]
|
||||
_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH]
|
||||
|
||||
# build some reuseable path parts
|
||||
_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm()
|
||||
_PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm()
|
||||
|
||||
_PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm()
|
||||
_PATH_MOCK_SBSAR = Path(_PATH_MOCK_SUBLIB, 'sbsar').norm()
|
||||
|
||||
_PATH_MOCK_MAT = Path(_PATH_MOCK_ASSETS, 'Textures').norm()
|
||||
_PATH_MOCK_MAT_SUB = Path(_PATH_MOCK_MAT, 'Substance').norm()
|
||||
|
||||
# this will combine two parts into a single path (object)
|
||||
# It also returnd the fixed-up version (norm)
|
||||
_PATH_INPUT_SBS = Path(_PATH_MOCK_SBS, 'bronze_yellow.sbs').norm()
|
||||
_PATH_COOK_OUTPUT = _PATH_MOCK_SBSAR.norm()
|
||||
_PATH_RENDER_OUTPUT = _PATH_MOCK_MAT_SUB
|
||||
|
||||
|
||||
# quick test variables, will be removed
|
||||
_PYSBS_CONTEXT = pysbs_context.Context()
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def output_info(_outputCookPath, _outputName):
|
||||
""" SBSAR information"""
|
||||
info_lists = []
|
||||
input_n_output = pysbs_batch.sbsrender_info(input=os.path.join(_outputCookPath, _outputName + '.sbsar'),
|
||||
stdout=subprocess.PIPE)
|
||||
for info_list in input_n_output.stdout.read().splitlines():
|
||||
info_lists.append(info_list.decode('utf-8'))
|
||||
|
||||
_outputs, _params, _presets, _inputs, _input, _input_type = [], [], [], [], [], []
|
||||
|
||||
for info_list in info_lists:
|
||||
if 'OUTPUT' in info_list:
|
||||
_outputs.append(info_list.split(' ')[3])
|
||||
elif 'INPUT $' in info_list:
|
||||
_params.append(info_list.split(' ')[3:])
|
||||
elif 'PRESET' in info_list:
|
||||
_presets.append(info_list.split('PRESET ')[1])
|
||||
elif 'INPUT' in info_list and (not '$'in info_list):
|
||||
_inputs.append(info_list.split(' ')[3:])
|
||||
_input.append(info_list.split(' ')[3:][0])
|
||||
_input_type.append(info_list.split(' ')[3:][1])
|
||||
|
||||
info_list = {'texmaps': _outputs,
|
||||
'params': _params,
|
||||
'presets': _presets,
|
||||
'inputs': _inputs,
|
||||
'input': _input,
|
||||
'input_type': _input_type}
|
||||
|
||||
return info_list
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
@click.command()
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar directory.')
|
||||
@click.option('--sbsar_name', default=_PATH_INPUT_SBS.stem, help='Sbsar name.')
|
||||
@click.option('--output_type', default='inputs', help='Output Type: \n'
|
||||
'texmaps | presets | inputs | input | input_type | params')
|
||||
def sbsar_info(sbsar_path, sbsar_name, output_type):
|
||||
click.echo(output_info(sbsar_path, sbsar_name)[output_type])
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_PACKAGENAME))
|
||||
_LOGGER.debug("Test Run:: {0}.".format({_MODULENAME}))
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_TOOL_TAG))
|
||||
|
||||
_LOGGER.debug(sbsar_info())
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
from azpy.constants import *
|
||||
|
||||
# 3rdparty (we provide)
|
||||
from unipath import Path
|
||||
import click
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: manage with dynaconf environment
|
||||
_PYSBS_DIR_PATH = Path(PATH_PROGRAMFILES_X64,
|
||||
'Allegorithmic',
|
||||
'Substance Automation Toolkit',
|
||||
'Python API',
|
||||
'install').resolve()
|
||||
|
||||
site.addsitedir(str(_PYSBS_DIR_PATH)) # 'install' is the folder I created
|
||||
|
||||
# Susbstance
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as pysbs_context
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.substance.builder.sbsar_render'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Defining CONSTANTS
|
||||
# To Do: shouldn't need this _BASE_ENVVAR_DICT (replace with dynaconf config)
|
||||
from collections import OrderedDict
|
||||
_SYNTH_ENV_DICT = OrderedDict()
|
||||
_SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT)
|
||||
# grab a specific path from the base_env
|
||||
_PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH]
|
||||
_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH]
|
||||
|
||||
# build some reuseable path parts
|
||||
_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm()
|
||||
_PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm()
|
||||
|
||||
_PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm()
|
||||
_PATH_MOCK_SBSAR = Path(_PATH_MOCK_SUBLIB, 'sbsar').norm()
|
||||
|
||||
_PATH_MOCK_MAT = Path(_PATH_MOCK_ASSETS, 'Textures').norm()
|
||||
_PATH_MOCK_MAT_SUB = Path(_PATH_MOCK_MAT, 'Substance').norm()
|
||||
|
||||
# this will combine two parts into a single path (object)
|
||||
# It also returnd the fixed-up version (norm)
|
||||
_PATH_INPUT_SBS = Path(_PATH_MOCK_SBS, 'alien_rock_coral_formation','alien_rock_coral_formation.sbs').norm()
|
||||
_PATH_COOK_OUTPUT = _PATH_MOCK_SBSAR.norm()
|
||||
_PATH_RENDER_OUTPUT = _PATH_MOCK_MAT_SUB
|
||||
|
||||
# quick test variables, will be removed
|
||||
_OUTPUT_SIZE = 9
|
||||
_user_preset = ['Red Coral', 'Red Sand', 'Grey Stone']
|
||||
integer1 = 0
|
||||
integer2 = [0, 0]
|
||||
float1 = 0.0
|
||||
float2 = [0.0, 0.0]
|
||||
|
||||
_PYSBS_CONTEXT = pysbs_context.Context()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@click.command()
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar directory.')
|
||||
@click.option('--sbsar_name', default=_PATH_INPUT_SBS.stem, help='Sbsar name.')
|
||||
@click.option('--tex_path', default=_PATH_RENDER_OUTPUT, help='Texture output path.')
|
||||
@click.option('--output_size', default=9, help='512x512, 9 | 1024x1024, 10 | 2048x2048, 11')
|
||||
def sbsar_render(sbsar_path, sbsar_name, tex_path, output_size):
|
||||
""" Doc String"""
|
||||
pysbs_batch.sbsrender_render(inputs=os.path.join(sbsar_path, sbsar_name + '.sbsar'),
|
||||
# input_graph=_inputGraphPath,
|
||||
output_path=tex_path,
|
||||
output_name='{inputGraphUrl}_{outputNodeName}',
|
||||
output_format='tif',
|
||||
set_value=['$outputsize@%s,%s' % (output_size, output_size), '$randomseed@1'],
|
||||
# use_preset = _user_preset[0]
|
||||
no_report=True,
|
||||
verbose=True
|
||||
).wait()
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_PACKAGENAME))
|
||||
|
||||
_LOGGER.debug(sbsar_render())
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
import subprocess
|
||||
import logging as _logging
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
|
||||
# we boostrap access to some lib site-packages
|
||||
from dynaconf import settings
|
||||
from pathlib import Path
|
||||
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_DEV_MODE)
|
||||
|
||||
_MODULENAME = 'DCCsi.SDK.substance.builder.sbsar_utils'
|
||||
_LOGGER = _logging.getLogger(_MODULENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_MODULENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: manage with dynaconf environment
|
||||
from azpy.constants import PATH_SAT_INSTALL_PATH
|
||||
_SAT_INSTALL_PATH = Path(PATH_SAT_INSTALL_PATH).resolve()
|
||||
site.addsitedir(str(_SAT_INSTALL_PATH)) # 'install' is the folder I created
|
||||
|
||||
# Susbstance
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as _pysbs_context
|
||||
|
||||
_PYSBS_CONTEXT = _pysbs_context.Context()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def cook_sbsar(input_sbs, cook_output_path):
|
||||
""" Doc String"""
|
||||
input_sbs = Path(input_sbs).resolve()
|
||||
cook_output_path = Path(cook_output_path).resolve()
|
||||
if not cook_output_path.exists():
|
||||
try:
|
||||
cook_output_path.mkdir()
|
||||
except:
|
||||
_LOGGER.warning('Could not mkdir: {}'.format(cook_output_path))
|
||||
output_name = input_sbs.stem
|
||||
pysbs_batch.sbscooker(quiet=True,
|
||||
inputs=str(input_sbs),
|
||||
includes=_PYSBS_CONTEXT.getDefaultPackagePath(),
|
||||
alias=_PYSBS_CONTEXT.getUrlAliasMgr().getAllAliases(),
|
||||
output_path=str(cook_output_path),
|
||||
output_name=output_name,
|
||||
compression_mode=2).wait()
|
||||
new_file = Path(cook_output_path, output_name + '.sbsar').resolve()
|
||||
if new_file.exists():
|
||||
return new_file
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def info_sbsar(input_path):
|
||||
""" Doc String"""
|
||||
sbsar_info = []
|
||||
in_file = Path(input_path).resolve()
|
||||
input_n_output = pysbs_batch.sbsrender_info(input=str(in_file),
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
for info in input_n_output.stdout.read().splitlines():
|
||||
sbsar_info.append(info.decode('utf-8'))
|
||||
|
||||
return sbsar_info
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def output_info(cook_output_path, output_name, output_type):
|
||||
""" Doc String"""
|
||||
info_lists = []
|
||||
input_file = Path(cook_output_path, output_name + '.sbsar').resolve()
|
||||
input_n_output = pysbs_batch.sbsrender_info(input=str(input_file),
|
||||
stdout=subprocess.PIPE)
|
||||
|
||||
for info_list in input_n_output.stdout.read().splitlines():
|
||||
info_lists.append(info_list.decode('utf-8'))
|
||||
|
||||
tex_maps, params, output_size, presets, inputs = [], [], [], [], []
|
||||
|
||||
for info_list in info_lists:
|
||||
if 'OUTPUT' in info_list:
|
||||
tex_maps.append(info_list.split(' ')[3])
|
||||
elif 'INPUT $' in info_list:
|
||||
params.append(info_list.split(' ')[3:])
|
||||
elif 'PRESET' in info_list:
|
||||
presets.append(info_list.split('PRESET ')[1])
|
||||
elif 'INPUT' in info_list and not '$' in info_list:
|
||||
inputs.append(info_list.split(' ')[3:])
|
||||
|
||||
output_info = {'tex_maps': tex_maps,
|
||||
'params': params,
|
||||
'output_size': output_size,
|
||||
'presets': presets,
|
||||
'inputs': inputs}
|
||||
return output_info[output_type]
|
||||
# To Do: Make preset a optional argument.
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
def render_sbsar(cook_output_path, output_texture_type, sbsar_name,
|
||||
render_to_output_path, use_preset, output_size, random_seed):
|
||||
""" Render textures maps from SBSAR"""
|
||||
# _tex_output = ['diffuse', 'basecolor', 'normal']
|
||||
# print(_tex_output)
|
||||
cook_output_path = Path(cook_output_path).resolve()
|
||||
texture_path = Path(render_to_output_path).resolve()
|
||||
sbsar_file = Path(cook_output_path, sbsar_name + '.sbsar').resolve()
|
||||
|
||||
if output_info(cook_output_path, sbsar_name, 'presets'):
|
||||
if use_preset == -1:
|
||||
preset_base = '_'
|
||||
pysbs_batch.sbsrender_render(inputs=str(sbsar_file),
|
||||
# input_graph=_inputGraphPath,
|
||||
input_graph_output=str(output_texture_type),
|
||||
output_path=str(texture_path),
|
||||
output_name=sbsar_name + preset_base + '{outputNodeName}',
|
||||
output_format='tif',
|
||||
set_value=['$outputsize@{},{}'.format(output_size, output_size),
|
||||
'$randomseed@{}'.format(random_seed)],
|
||||
no_report=True,
|
||||
verbose=True
|
||||
).wait()
|
||||
else:
|
||||
preset_name = output_info(cook_output_path, sbsar_name, 'presets')[use_preset]
|
||||
preset_base = '_'
|
||||
pysbs_batch.sbsrender_render(inputs=str(sbsar_file),
|
||||
# input_graph=_inputGraphPath,
|
||||
input_graph_output=str(output_texture_type),
|
||||
output_path=str(texture_path),
|
||||
output_name=sbsar_name + preset_base + preset_name.replace(' ', '') + '_{outputNodeName}',
|
||||
output_format='tif',
|
||||
set_value=['$outputsize@{},{}'.format(output_size, output_size),
|
||||
'$randomseed@{}'.format(random_seed)],
|
||||
use_preset=preset_name,
|
||||
no_report=True,
|
||||
verbose=True
|
||||
).wait()
|
||||
else:
|
||||
pysbs_batch.sbsrender_render(inputs=str(sbsar_file),
|
||||
# input_graph=_inputGraphPath,
|
||||
input_graph_output=str(output_texture_type),
|
||||
output_path=str(texture_path),
|
||||
output_name=sbsar_name + '_{outputNodeName}',
|
||||
output_format='tif',
|
||||
set_value=['$outputsize@{},{}'.format(output_size, output_size),
|
||||
'$randomseed@{}'.format(random_seed)],
|
||||
no_report=True,
|
||||
verbose=True
|
||||
).wait()
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == "__main__":
|
||||
"""Run this file as main"""
|
||||
# ---------------------------------------------------------------------
|
||||
# Defining CONSTANTS
|
||||
# To Do: shouldn't need this _SYNTH_ENV_DICT (replace with dynaconf config)
|
||||
from azpy import synthetic_env
|
||||
_SYNTH_ENV_DICT = synthetic_env.stash_env()
|
||||
|
||||
from azpy.constants import ENVAR_DCCSIG_PATH
|
||||
from azpy.constants import ENVAR_LY_PROJECT_PATH
|
||||
|
||||
# grab a specific path from the base_env
|
||||
_PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH]
|
||||
|
||||
# use DCCsi as the project path for this test
|
||||
_LY_PROJECT_PATH = _PATH_DCCSI
|
||||
|
||||
_PROJECT_ASSETS_PATH = Path(_LY_PROJECT_PATH, 'Assets').resolve()
|
||||
_PROJECT_MATERIALS_PATH = Path(_PROJECT_ASSETS_PATH, 'Materials').resolve()
|
||||
|
||||
# this will combine two parts into a single path (object)
|
||||
# It also returnd the fixed-up version (norm)
|
||||
_PATH_OUTPUT = Path(_PROJECT_MATERIALS_PATH, 'Fabric')
|
||||
_PATH_INPUT_SBS = Path(_PROJECT_MATERIALS_PATH, 'Fabric', 'fabric.sbsar')
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_MODULENAME))
|
||||
|
||||
_LOGGER.debug('_SYNTH_ENV_DICT: {}'.format(_SYNTH_ENV_DICT))
|
||||
|
||||
_LOGGER.info('presets: {}'.format(output_info(_PATH_OUTPUT,
|
||||
_PATH_INPUT_SBS.stem,
|
||||
'presets')))
|
||||
|
||||
_LOGGER.info('params: {}'.format(output_info(_PATH_OUTPUT,
|
||||
_PATH_INPUT_SBS.stem,
|
||||
'params')))
|
||||
|
||||
_LOGGER.info('inputs: {}'.format(output_info(_PATH_OUTPUT,
|
||||
_PATH_INPUT_SBS.stem,
|
||||
'inputs')))
|
||||
|
||||
_LOGGER.info('tex_maps: {}'.format(output_info(_PATH_OUTPUT,
|
||||
_PATH_INPUT_SBS.stem,
|
||||
'tex_maps')))
|
||||
|
||||
_LOGGER.info(info_sbsar(Path(_PATH_INPUT_SBS)))
|
||||
new_file = cook_sbsar(_PATH_INPUT_SBS, Path(_PATH_OUTPUT, '.tests'))
|
||||
if new_file:
|
||||
_LOGGER.info('Cooked out: {}'.format(new_file))
|
||||
render_sbsar(cook_output_path=Path(_PATH_OUTPUT, '.tests'),
|
||||
output_texture_type='basecolor',
|
||||
sbsar_name=_PATH_INPUT_SBS.stem,
|
||||
render_to_output_path=Path(_PATH_OUTPUT, '.tests'),
|
||||
use_preset=-1,
|
||||
output_size=256,
|
||||
random_seed=1001)
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
# coding:utf-8
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# -- This line is 75 characters -------------------------------------------
|
||||
"""Empty Doc String""" # To Do: add documentation
|
||||
# -------------------------------------------------------------------------
|
||||
# built-ins
|
||||
import os
|
||||
import sys
|
||||
import site
|
||||
|
||||
# Lumberyard extensions
|
||||
from azpy.env_bool import env_bool
|
||||
from azpy.constants import ENVAR_DCCSI_GDEBUG
|
||||
from azpy.constants import ENVAR_DCCSI_DEV_MODE
|
||||
from azpy.constants import *
|
||||
import sbsar_utils
|
||||
|
||||
# 3rdparty
|
||||
from unipath import Path
|
||||
import click
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# substance automation toolkit (aka pysbs)
|
||||
# To Do: manage with dynaconf environment
|
||||
_PYSBS_DIR_PATH = Path(PATH_PROGRAMFILES_X64,
|
||||
'Allegorithmic',
|
||||
'Substance Automation Toolkit',
|
||||
'Python API',
|
||||
'install').resolve()
|
||||
|
||||
site.addsitedir(str(_PYSBS_DIR_PATH)) # 'install' is the folder I created
|
||||
|
||||
# Susbstance
|
||||
import pysbs.batchtools as pysbs_batch
|
||||
import pysbs.context as pysbs_context
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# set up global space, logging etc.
|
||||
_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False)
|
||||
_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False)
|
||||
|
||||
_PACKAGENAME = __name__
|
||||
if _PACKAGENAME is '__main__':
|
||||
_PACKAGENAME = 'DCCsi.SDK.substance.builder.substance_tools'
|
||||
|
||||
import azpy
|
||||
_LOGGER = azpy.initialize_logger(_PACKAGENAME)
|
||||
_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME}))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Defining CONSTANTS
|
||||
# To Do: shouldn't need this _BASE_ENVVAR_DICT (replace with dynaconf config)
|
||||
from collections import OrderedDict
|
||||
_SYNTH_ENV_DICT = OrderedDict()
|
||||
_SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT)
|
||||
# grab a specific path from the base_env
|
||||
_PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH]
|
||||
_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH]
|
||||
|
||||
# build some reuseable path parts
|
||||
_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm()
|
||||
_PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm()
|
||||
|
||||
_PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm()
|
||||
_PATH_MOCK_SBSAR = Path(_PATH_MOCK_SUBLIB, 'sbsar').norm()
|
||||
|
||||
_PATH_MOCK_MAT = Path(_PATH_MOCK_ASSETS, 'Textures').norm()
|
||||
_PATH_MOCK_MAT_SUB = Path(_PATH_MOCK_MAT, 'Substance').norm()
|
||||
|
||||
_PATH_INPUT_SBS = Path(_PATH_MOCK_SBS, 'bronze_yellow.sbs').norm()
|
||||
_PATH_COOK_OUTPUT = _PATH_MOCK_SBSAR.norm()
|
||||
_PATH_RENDER_OUTPUT = _PATH_MOCK_MAT_SUB
|
||||
|
||||
_SBS_NAME = _PATH_INPUT_SBS.split('.sbs')[0].split('\\')[-1]
|
||||
|
||||
_PYSBS_CONTEXT = pysbs_context.Context()
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@click.group()
|
||||
def builder_tools():
|
||||
pass
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@builder_tools.command()
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar directory.')
|
||||
@click.option('--sbsar_name', default=_PATH_INPUT_SBS.stem, help='Sbsar name.')
|
||||
@click.option('--output_type', default='inputs', help='Output Type: \n'
|
||||
'tex_maps | presets | inputs | input | input_type | params')
|
||||
def info(sbsar_path, sbsar_name, output_type):
|
||||
"""SBSAR information"""
|
||||
click.echo(sbsar_utils.output_info(sbsar_path, sbsar_name, output_type))
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@builder_tools.command()
|
||||
@click.option('--sbs_path', default=_PATH_INPUT_SBS, help='Sbs path.')
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar output path.')
|
||||
def sbs2sbsar(sbs_path, sbsar_path):
|
||||
""" Cook SBSAR from SBS"""
|
||||
sbsar_utils.cook_sbsar(sbs_path, sbsar_path)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
@builder_tools.command()
|
||||
@click.option('--sbsar_path', default=_PATH_COOK_OUTPUT, help='Sbsar directory.')
|
||||
@click.option('--sbsar_name', default=_PATH_INPUT_SBS.stem, help='Sbsar name.')
|
||||
@click.option('--tex_path', default=_PATH_RENDER_OUTPUT, help='Texture output path.')
|
||||
@click.option('--preset', required=False, help='Preset')
|
||||
@click.option('--output_size', default=9, help='512x512, 9 | 1024x1024, 10 | 2048x2048, 11')
|
||||
def render(sbsar_path, sbsar_name, tex_path, preset, output_size):
|
||||
""" Render textures maps from SBSAR"""
|
||||
sbsar_utils.render_sbsar(sbsar_path, sbsar_name, tex_path, preset, output_size)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Main Code Block, runs this script as main (testing)
|
||||
# -------------------------------------------------------------------------
|
||||
if __name__ == '__main__':
|
||||
"""Run this file as main"""
|
||||
|
||||
_LOGGER.debug("{0} :: if __name__ == '__main__':".format(_PACKAGENAME))
|
||||
|
||||
_LOGGER.debug(builder_tools())
|
||||
|
||||
# remove the logger
|
||||
del _LOGGER
|
||||
# ---- END ---------------------------------------------------------------
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
# https://gist.github.com/blubberdiblub/007bb92991d01ad29877931f75260b39
|
||||
|
||||
import sys
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QProcess, QTextCodec
|
||||
from PyQt5.QtGui import QTextCursor
|
||||
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
|
||||
|
||||
|
||||
class ProcessOutputReader(QProcess):
|
||||
produce_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# merge stderr channel into stdout channel
|
||||
self.setProcessChannelMode(QProcess.MergedChannels)
|
||||
|
||||
# prepare decoding process' output to Unicode
|
||||
codec = QTextCodec.codecForLocale()
|
||||
self._decoder_stdout = codec.makeDecoder()
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self._decoder_stderr = codec.makeDecoder()
|
||||
|
||||
self.readyReadStandardOutput.connect(self._ready_read_standard_output)
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self.readyReadStandardError.connect(self._ready_read_standard_error)
|
||||
|
||||
@pyqtSlot()
|
||||
def _ready_read_standard_output(self):
|
||||
raw_bytes = self.readAllStandardOutput()
|
||||
text = self._decoder_stdout.toUnicode(raw_bytes)
|
||||
self.produce_output.emit(text)
|
||||
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# @pyqtSlot()
|
||||
# def _ready_read_standard_error(self):
|
||||
# raw_bytes = self.readAllStandardError()
|
||||
# text = self._decoder_stderr.toUnicode(raw_bytes)
|
||||
# self.produce_output.emit(text)
|
||||
|
||||
|
||||
class MyConsole(QPlainTextEdit):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setReadOnly(True)
|
||||
self.setMaximumBlockCount(10000) # limit console to 10000 lines
|
||||
|
||||
self._cursor_output = self.textCursor()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def append_output(self, text):
|
||||
self._cursor_output.insertText(text)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def scroll_to_last_line(self):
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
|
||||
QTextCursor.StartOfLine)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
|
||||
# create the application instance
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# create a process output reader
|
||||
reader = ProcessOutputReader()
|
||||
|
||||
# create a console and connect the process output reader to it
|
||||
console = MyConsole()
|
||||
reader.produce_output.connect(console.append_output)
|
||||
|
||||
reader.start('python', ['-u', 'C:\\dccapi\\dev\\Gems\\DccScriptingInterface\\LyPy\\si_substance\\builder\\watchdog'
|
||||
'\\__init__.py', 'C:\\Users\\chunghao\\Documents\\Allegorithmic\\Substance Designer'
|
||||
'\\sbsar']) # start the process
|
||||
console.show() # make the console visible
|
||||
app.exec_() # run the PyQt main loop
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QProcess, Signal, Slot, QTextCodec
|
||||
from PySide2.QtGui import QTextCursor
|
||||
from PySide2.QtWidgets import QApplication, QPlainTextEdit
|
||||
from PySide2.QtCore import QTimer
|
||||
|
||||
class ProcessOutputReader(QProcess):
|
||||
produce_output = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# merge stderr channel into stdout channel
|
||||
self.setProcessChannelMode(QProcess.MergedChannels)
|
||||
# prepare decoding process' output to Unicode
|
||||
self._codec = QTextCodec.codecForLocale()
|
||||
self._decoder_stdout = self._codec.makeDecoder()
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self._decoder_stderr = codec.makeDecoder()
|
||||
|
||||
self.readyReadStandardOutput.connect(self._ready_read_standard_output)
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self.readyReadStandardError.connect(self._ready_read_standard_error)
|
||||
|
||||
@Slot()
|
||||
def _ready_read_standard_output(self):
|
||||
raw_bytes = self.readAllStandardOutput()
|
||||
text = self._decoder_stdout.toUnicode(raw_bytes)
|
||||
self.produce_output.emit(text)
|
||||
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# @Slot()
|
||||
# def _ready_read_standard_error(self):
|
||||
# raw_bytes = self.readAllStandardError()
|
||||
# text = self._decoder_stderr.toUnicode(raw_bytes)
|
||||
# self.produce_output.emit(text)
|
||||
|
||||
|
||||
class MyConsole(QPlainTextEdit):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setReadOnly(True)
|
||||
self.setMaximumBlockCount(10000) # limit console to 10000 lines
|
||||
|
||||
self._cursor_output = self.textCursor()
|
||||
|
||||
@Slot(str)
|
||||
def append_output(self, text):
|
||||
self._cursor_output.insertText(text)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def scroll_to_last_line(self):
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
|
||||
QTextCursor.StartOfLine)
|
||||
self.setTextCursor(cursor)
|
||||
|
||||
def output_text(self, text):
|
||||
self._cursor_output.insertText(text)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
# create the application instance
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
# create a process output reader
|
||||
reader = ProcessOutputReader()
|
||||
|
||||
# create a console and connect the process output reader to it
|
||||
console = MyConsole()
|
||||
reader.produce_output.connect(console.append_output)
|
||||
|
||||
reader.start('python', ['-u', 'C:\\dccapi\\dev\\Gems\\DccScriptingInterface\\LyPy\\si_substance\\builder\\watchdog'
|
||||
'\\__init__.py', 'C:\\Users\\chunghao\\Documents\\Allegorithmic\\Substance Designer'
|
||||
'\\sbsar']) # start the process
|
||||
console.show() # make the console visible
|
||||
# app.exec_() # run the PyQt main loop
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(lambda: None)
|
||||
timer.start(100)
|
||||
|
||||
sys.exit(app.exec_())
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PySide2.QtCore import QFile, QSize
|
||||
from PySide2.QtUiTools import QUiLoader
|
||||
from PySide2.QtWidgets import QApplication, QSizePolicy
|
||||
from PySide2.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout
|
||||
|
||||
_MODULE_DIR_PATH = os.path.dirname(os.path.abspath(__file__))
|
||||
_UI_FILEPATH = "{0}\\\\sbs_builder_widget.ui".format(_MODULE_DIR_PATH)
|
||||
_PROGRAM_NAME_VERSION = 'Substance Builder'
|
||||
|
||||
|
||||
class UiLoader(QUiLoader):
|
||||
def __init__(self, baseInstance):
|
||||
super(UiLoader, self).__init__(baseInstance)
|
||||
self._baseInstance = baseInstance
|
||||
|
||||
def createWidget(self, classname, parent=None, name=""):
|
||||
widget = super(UiLoader, self).createWidget(
|
||||
classname, parent, name)
|
||||
|
||||
if parent is None:
|
||||
return self._baseInstance
|
||||
else:
|
||||
setattr(self._baseInstance, name, widget)
|
||||
return widget
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self, ui_file=_UI_FILEPATH, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
# Setup central widget and layout
|
||||
self.central_widget = QWidget()
|
||||
self.central_layout = QVBoxLayout(self.central_widget)
|
||||
self.central_layout.setContentsMargins(8, 8, 8, 8)
|
||||
self.setCentralWidget(self.central_widget)
|
||||
self.setup_layout(ui_file)
|
||||
self.setMinimumSize(QSize(675, 825))
|
||||
|
||||
def setup_layout(self, ui_file):
|
||||
self.layout = QHBoxLayout()
|
||||
self.widget = MyWidget(ui_file, self)
|
||||
self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.layout.addWidget(self.widget)
|
||||
self.central_layout.addLayout(self.layout)
|
||||
|
||||
|
||||
class MyWidget(QWidget):
|
||||
def __init__(self, ui_file=_UI_FILEPATH, parent=None):
|
||||
super().__init__(parent)
|
||||
loader = UiLoader(parent)
|
||||
file = QFile(ui_file)
|
||||
file.open(QFile.ReadOnly)
|
||||
loader.load(file, self)
|
||||
file.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
mainWindow = MainWindow(_UI_FILEPATH)
|
||||
mainWindow.setWindowTitle(_PROGRAM_NAME_VERSION)
|
||||
mainWindow.show()
|
||||
sys.exit(app.exec_())
|
||||
+565
@@ -0,0 +1,565 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>560</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>240</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Watcher</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QLineEdit" name="lineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>122</x>
|
||||
<y>44</y>
|
||||
<width>211</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Watch folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QToolButton" name="toolButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>340</x>
|
||||
<y>44</y>
|
||||
<width>25</width>
|
||||
<height>19</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>24</x>
|
||||
<y>70</y>
|
||||
<width>92</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Wacher Extensions</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>24</x>
|
||||
<y>44</y>
|
||||
<width>64</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Wacher Foler</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="radioButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>24</x>
|
||||
<y>21</y>
|
||||
<width>81</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> On / Off</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>122</x>
|
||||
<y>70</y>
|
||||
<width>211</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>*.sbsar, *.sbs</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QTextEdit" name="textEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>100</y>
|
||||
<width>331</width>
|
||||
<height>131</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>280</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>SBSAR</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<width>70</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>RandomSeed</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>50</y>
|
||||
<width>20</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>100</y>
|
||||
<width>121</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create .material</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_8">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>150</y>
|
||||
<width>70</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Glossness</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>100</y>
|
||||
<width>21</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>50</y>
|
||||
<width>47</width>
|
||||
<height>13</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>30</y>
|
||||
<width>41</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preset</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>190</y>
|
||||
<width>121</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Render textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>240</y>
|
||||
<width>181</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>30</y>
|
||||
<width>71</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Atom Material</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>190</y>
|
||||
<width>70</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Opacity</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>170</y>
|
||||
<width>81</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Roughness</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBox_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>120</y>
|
||||
<width>101</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string> 512 * 512</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1024 * 1024</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2048 * 2048</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="lineEdit_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>70</y>
|
||||
<width>121</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>30</y>
|
||||
<width>41</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>50</y>
|
||||
<width>101</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Grey Stone</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Red Coral</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Red Stone</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QToolButton" name="toolButton_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>210</x>
|
||||
<y>240</y>
|
||||
<width>25</width>
|
||||
<height>19</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>210</y>
|
||||
<width>361</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_7">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>90</y>
|
||||
<width>70</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Specular</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>110</y>
|
||||
<width>70</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Metallic</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>50</y>
|
||||
<width>71</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Base Color</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
<y>240</y>
|
||||
<width>121</width>
|
||||
<height>23</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bake SBSAR from SBS</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>130</y>
|
||||
<width>81</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Normal map</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>220</x>
|
||||
<y>50</y>
|
||||
<width>20</width>
|
||||
<height>161</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>80</y>
|
||||
<width>91</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Parameters</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QSpinBox" name="spinBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>170</y>
|
||||
<width>101</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBox_6">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>140</x>
|
||||
<y>70</y>
|
||||
<width>70</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Albedo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>toolButton</sender>
|
||||
<signal>pressed()</signal>
|
||||
<receiver>lineEdit</receiver>
|
||||
<slot>clear()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>362</x>
|
||||
<y>63</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>237</x>
|
||||
<y>63</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
+494
@@ -0,0 +1,494 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>651</width>
|
||||
<height>807</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Watcher</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_2">
|
||||
<property name="text">
|
||||
<string> On / Off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Wacher Foler</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_6">
|
||||
<property name="text">
|
||||
<string>Watch folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="toolButton_3">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Wacher Extensions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_7">
|
||||
<property name="text">
|
||||
<string>*.sbsar, *.sbs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="textEdit_2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Sbsar</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="tableWidget_fileslist">
|
||||
<property name="columnCount">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>100</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderDefaultSectionSize">
|
||||
<number>20</number>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #01</string>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<underline>false</underline>
|
||||
<strikeout>false</strikeout>
|
||||
<stylestrategy>PreferDefault</stylestrategy>
|
||||
</font>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #02</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #03</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #04</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #05</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #06</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #07</string>
|
||||
</property>
|
||||
</row>
|
||||
<row>
|
||||
<property name="text">
|
||||
<string>Item #08</string>
|
||||
</property>
|
||||
</row>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>File Path</string>
|
||||
</property>
|
||||
</column>
|
||||
<item row="0" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item01.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item02.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item03.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item04.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item05.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item06.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item07.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<property name="text">
|
||||
<string>C:\Users\chunghao\Documents\Allegorithmic\Substance Designer\sbsar\item08.sbsar</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_parameters">
|
||||
<property name="title">
|
||||
<string>Parameters</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_14">
|
||||
<property name="text">
|
||||
<string>Preset</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_16">
|
||||
<property name="text">
|
||||
<string>RandomSeed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox_3">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Grey Stone</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Red Coral</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Red Stone</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBox_4">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string> 512 * 512</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1024 * 1024</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2048 * 2048</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBox_2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Output">
|
||||
<property name="title">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_9">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Base Color</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_10">
|
||||
<property name="text">
|
||||
<string>Albedo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_11">
|
||||
<property name="text">
|
||||
<string>Specular</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_12">
|
||||
<property name="text">
|
||||
<string>Metallic</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_13">
|
||||
<property name="text">
|
||||
<string>Normal map</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_14">
|
||||
<property name="text">
|
||||
<string>Glossness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_15">
|
||||
<property name="text">
|
||||
<string>Roughness</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_16">
|
||||
<property name="text">
|
||||
<string>Opacity</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Material">
|
||||
<property name="title">
|
||||
<string>Atom Material</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_12">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_4"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_3">
|
||||
<property name="text">
|
||||
<string>Create .material</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_4">
|
||||
<property name="text">
|
||||
<string>Render textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_5"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton_2">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_5">
|
||||
<property name="text">
|
||||
<string>Bake SBSAR from SBS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>461</width>
|
||||
<height>502</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Watcher</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_8">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QRadioButton" name="radioButton_2">
|
||||
<property name="text">
|
||||
<string> On / Off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_13">
|
||||
<property name="text">
|
||||
<string>Wacher Foler</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_6">
|
||||
<property name="text">
|
||||
<string>Watch folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QToolButton" name="toolButton_3">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Wacher Extensions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="lineEdit_7">
|
||||
<property name="text">
|
||||
<string>*.sbsar, *.sbs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="textEdit_2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Sbsar</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_parameters">
|
||||
<property name="title">
|
||||
<string>Parameters</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2"/>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Output">
|
||||
<property name="title">
|
||||
<string>Output</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_9">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Base Color</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_10">
|
||||
<property name="text">
|
||||
<string>Albedo</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_11">
|
||||
<property name="text">
|
||||
<string>Specular</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_12">
|
||||
<property name="text">
|
||||
<string>Metallic</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_13">
|
||||
<property name="text">
|
||||
<string>Normal map</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_14">
|
||||
<property name="text">
|
||||
<string>Glossness</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_15">
|
||||
<property name="text">
|
||||
<string>Roughness</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_16">
|
||||
<property name="text">
|
||||
<string>Opacity</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_Material">
|
||||
<property name="title">
|
||||
<string>Atom Material</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="lineEdit_5"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButton_2">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_5">
|
||||
<property name="text">
|
||||
<string>Bake SBSAR from SBS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
# -------------------------------------------------------------------------
|
||||
import sys, os
|
||||
|
||||
from PySide2 import QtCore, QtWidgets
|
||||
from PySide2.QtCore import QTimer
|
||||
from PySide2.QtCore import QProcess, Signal, Slot, QTextCodec
|
||||
from PySide2.QtGui import QTextCursor
|
||||
from PySide2.QtWidgets import QApplication, QPlainTextEdit
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
import builder
|
||||
# sys.path.insert(0, os.path.abspath('../ui/'))
|
||||
# import main_win
|
||||
sys.path.insert(0, os.path.abspath('../watchdog/'))
|
||||
import watchdog
|
||||
|
||||
class Window(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(Window, self).__init__(parent)
|
||||
self.browseButton = self.create_button("&Browse...", self.browse)
|
||||
self.findButton = self.create_button("&Find", self.find)
|
||||
self.removeButton = self.create_button("&Remove", self.remove)
|
||||
self.startWatch = self.create_button("&Start Watch", self.watch)
|
||||
self.killButton = self.create_button("&Stop Watch", self.kill)
|
||||
self.fileComboBox = self.createComboBox("*")
|
||||
self.textComboBox = self.createComboBox()
|
||||
# self.directoryComboBox = self.createComboBox(QtCore.QDir.currentPath())
|
||||
self.sbsarDirectory = "C:/Users/chunghao/Documents/Allegorithmic/Substance Designer/sbsar"
|
||||
self.directoryComboBox = self.createComboBox(self.sbsarDirectory)
|
||||
self.extensionLabel = QtWidgets.QLabel("Watch extensions:")
|
||||
# textLabel = QtWidgets.QLabel("Containing text:")
|
||||
self.directoryLabel = QtWidgets.QLabel("Watch directory:")
|
||||
self.filesFoundLabel = QtWidgets.QLabel()
|
||||
self.wacherLogLabel = QtWidgets.QLabel("Watcher Log")
|
||||
self.fileListLabel = QtWidgets.QLabel("File List")
|
||||
self.sbsarPresetLabel = QtWidgets.QLabel("Sbsar Preset")
|
||||
self.OutputResLabel = QtWidgets.QLabel("Output Size")
|
||||
self.OutputsLabel = QtWidgets.QLabel("Outputs")
|
||||
|
||||
self.watcher_log = QPlainTextEdit()
|
||||
self.watcher_log.setReadOnly(True)
|
||||
self.watcher_log.setMaximumBlockCount(10000) # limit console to 10000 lines
|
||||
|
||||
self.watcher_log._cursor_output = self.watcher_log.textCursor()
|
||||
|
||||
self.create_files_table()
|
||||
|
||||
buttonsLayout = QtWidgets.QHBoxLayout()
|
||||
buttonsLayout.addStretch()
|
||||
buttonsLayout.addWidget(self.findButton)
|
||||
# buttonsLayout.addWidget(self.removeButton)
|
||||
buttonsLayout.addWidget(self.startWatch)
|
||||
buttonsLayout.addWidget(self.killButton)
|
||||
|
||||
self.mainLayout = QtWidgets.QGridLayout()
|
||||
self.mainLayout.addWidget(self.extensionLabel, 0, 0)
|
||||
self.mainLayout.addWidget(self.fileComboBox, 0, 1, 1, 2)
|
||||
# self.mainLayout.addWidget(textLabel, 1, 0)
|
||||
# self.mainLayout.addWidget(self.textComboBox, 1, 1, 1, 2)
|
||||
self.mainLayout.addWidget(self.directoryLabel, 2, 0)
|
||||
self.mainLayout.addWidget(self.wacherLogLabel, 3, 0)
|
||||
self.mainLayout.addWidget(self.fileListLabel, 4, 0)
|
||||
self.mainLayout.addWidget(self.directoryComboBox, 2, 1)
|
||||
self.mainLayout.addWidget(self.browseButton, 2, 2)
|
||||
self.mainLayout.addWidget(self.watcher_log, 3, 1, 1, 3)
|
||||
self.mainLayout.addWidget(self.filesTable, 4, 1, 1, 3)
|
||||
self.mainLayout.addWidget(self.filesFoundLabel, 5, 1)
|
||||
self.mainLayout.addLayout(buttonsLayout, 6, 0, 1, 3)
|
||||
self.setLayout(self.mainLayout)
|
||||
|
||||
self.setWindowTitle("Substance Builder")
|
||||
self.resize(800, 300)
|
||||
self.path = self.directoryComboBox.currentText()
|
||||
self.groupbox_outputs = QtWidgets.QGroupBox("Outputs")
|
||||
self.watcher_script = "C:/dccapi/dev/Gems/DccScriptingInterface/LyPy/si_substance/builder/watchdog/__init__.py"
|
||||
|
||||
@Slot(str)
|
||||
def append_output(self, text):
|
||||
self.watcher_log._cursor_output.insertText(text)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def scroll_to_last_line(self):
|
||||
cursor = self.watcher_log.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
|
||||
QTextCursor.StartOfLine)
|
||||
self.watcher_log.setTextCursor(cursor)
|
||||
|
||||
def output_text(self, text):
|
||||
self.watcher_log._cursor_output.insertText(text)
|
||||
self.watcher_log.scroll_to_last_line()
|
||||
def browse(self):
|
||||
# self.directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Find Files",
|
||||
# QtCore.QDir.currentPath())
|
||||
self.directory = QtWidgets.QFileDialog.getExistingDirectory(self, "Find watcher folder",
|
||||
"C:/Users/chunghao/Documents/Allegorithmic/Substance Designer/sbsar")
|
||||
if self.directory:
|
||||
if self.directoryComboBox.findText(self.directory) == -1:
|
||||
self.directoryComboBox.addItem(self.directory)
|
||||
|
||||
self.directoryComboBox.setCurrentIndex(self.directoryComboBox.findText(self.directory))
|
||||
self.path = self.directory
|
||||
self.find()
|
||||
return self.path
|
||||
|
||||
@staticmethod
|
||||
def updateComboBox(comboBox):
|
||||
if comboBox.findText(comboBox.currentText()) == -1:
|
||||
comboBox.addItem(comboBox.currentText())
|
||||
|
||||
def find(self):
|
||||
self.filesTable.setRowCount(0)
|
||||
|
||||
fileName = self.fileComboBox.currentText()
|
||||
text = self.textComboBox.currentText()
|
||||
self.path = self.directoryComboBox.currentText()
|
||||
# self.path = "C:/Users/chunghao/Documents/Allegorithmic/Substance Designer/sbsar"
|
||||
|
||||
self.updateComboBox(self.fileComboBox)
|
||||
self.updateComboBox(self.textComboBox)
|
||||
self.updateComboBox(self.directoryComboBox)
|
||||
|
||||
self.currentDir = QtCore.QDir(self.path)
|
||||
if not fileName:
|
||||
fileName = "*"
|
||||
files = self.currentDir.entryList([fileName],
|
||||
QtCore.QDir.Files | QtCore.QDir.NoSymLinks)
|
||||
|
||||
if text:
|
||||
files = self.find_files(files, text)
|
||||
self.show_files(files)
|
||||
return self.path
|
||||
|
||||
def find_files(self, files, text):
|
||||
progressDialog = QtWidgets.QProgressDialog(self)
|
||||
|
||||
progressDialog.setCancelButtonText("&Cancel")
|
||||
progressDialog.setRange(0, len(files))
|
||||
progressDialog.setWindowTitle("Find Files")
|
||||
|
||||
foundFiles = []
|
||||
|
||||
for i in range(len(files)):
|
||||
progressDialog.setValue(i)
|
||||
progressDialog.setLabelText("Searching file number %d of %d..." % (i, len(files)))
|
||||
QtCore.qApp.processEvents()
|
||||
|
||||
if progressDialog.wasCanceled():
|
||||
break
|
||||
|
||||
inFile = QtCore.QFile(self.currentDir.absoluteFilePath(files[i]))
|
||||
|
||||
if inFile.open(QtCore.QIODevice.ReadOnly):
|
||||
stream = QtCore.QTextStream(inFile)
|
||||
while not stream.atEnd():
|
||||
if progressDialog.wasCanceled():
|
||||
break
|
||||
line = stream.readLine()
|
||||
if text in line:
|
||||
foundFiles.append(files[i])
|
||||
break
|
||||
|
||||
progressDialog.close()
|
||||
|
||||
return foundFiles
|
||||
|
||||
def show_files(self, files):
|
||||
for fn in files:
|
||||
# file = QtCore.QFile(self.currentDir.absoluteFilePath(fn))
|
||||
# size = QtCore.QFileInfo(file).size()
|
||||
|
||||
fileNameItem = QtWidgets.QTableWidgetItem(fn)
|
||||
fileNameItem.setFlags(fileNameItem.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
# sizeItem = QtWidgets.QTableWidgetItem("%d KB" % (int((size + 1023) / 1024)))
|
||||
# sizeItem.setTextAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignRight)
|
||||
# sizeItem.setFlags(sizeItem.flags() ^ QtCore.Qt.ItemIsEditable)
|
||||
row = self.filesTable.rowCount()
|
||||
self.filesTable.insertRow(row)
|
||||
self.filesTable.setItem(row, 0, fileNameItem)
|
||||
# self.filesTable.setItem(row, 1, sizeItem)
|
||||
|
||||
self.filesFoundLabel.setText("%d file(s) found (Double click on a file to open it)" % len(files))
|
||||
|
||||
def create_button(self, text, member):
|
||||
button = QtWidgets.QPushButton(text)
|
||||
button.clicked.connect(member)
|
||||
return button
|
||||
|
||||
def createComboBox(self, text=""):
|
||||
comboBox = QtWidgets.QComboBox()
|
||||
comboBox.setEditable(True)
|
||||
comboBox.addItem(text)
|
||||
comboBox.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Preferred)
|
||||
return comboBox
|
||||
|
||||
def create_files_table(self):
|
||||
self.filesTable = QtWidgets.QTableWidget(0, 1)
|
||||
self.filesTable.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
|
||||
self.filesTable.setHorizontalHeaderLabels(("File Name", ""))
|
||||
self.filesTable.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||
# self.filesTable.verticalHeader().hide()
|
||||
self.filesTable.verticalHeader().show()
|
||||
self.filesTable.setShowGrid(True)
|
||||
self.filesTable.cellActivated.connect(self.open_fileOfitem)
|
||||
|
||||
def create_sbsar_params(self):
|
||||
self.mainLayout.addWidget(self.OutputsLabel, 9, 0)
|
||||
self.sbsarName = self.item.text().replace(".sbsar", "")
|
||||
self.groupbox_outputs = QtWidgets.QGroupBox("Outputs")
|
||||
self.horizontalLayoutOuputs = QtWidgets.QHBoxLayout(self.groupbox_outputs)
|
||||
self.horizontalLayoutOuputs.setObjectName(("horizontalLayoutOuputs"))
|
||||
# self.vertical_layout_outputs = QtWidgets.QVBoxLayout(self.groupbox_outputs)
|
||||
# self.vertical_layout_outputs.setObjectName("vertical_layout_outputs")
|
||||
self.sbsar_tex_outputs = builder.output_info(self.path, self.sbsarName)['_outputs']
|
||||
|
||||
for index, tex in enumerate(self.sbsar_tex_outputs):
|
||||
print(tex, end=" ")
|
||||
self.sbsar_tex_outputs[index] = QtWidgets.QCheckBox(self.groupbox_outputs)
|
||||
self.sbsar_tex_outputs[index].setEnabled(True)
|
||||
self.sbsar_tex_outputs[index].setChecked(True)
|
||||
self.sbsar_tex_outputs[index].setObjectName(self.sbsarName+"_"+tex)
|
||||
self.sbsar_tex_outputs[index].setText(tex)
|
||||
self.horizontalLayoutOuputs.addWidget(self.sbsar_tex_outputs[index])
|
||||
self.mainLayout.addWidget(self.groupbox_outputs, 9, 1, 1, 3)
|
||||
print("\n")
|
||||
|
||||
def create_sbsar_presets(self):
|
||||
self.mainLayout.addWidget(self.sbsarPresetLabel, 7, 0)
|
||||
self.sbsar_presets = builder.output_info(self.path, self.item.text().replace(".sbsar", ""))['_presets']
|
||||
self.comboBox_SbsarPreset = QtWidgets.QComboBox(self)
|
||||
for self.presetItem in self.sbsar_presets:
|
||||
print(self.presetItem, end=", ")
|
||||
preset_index = 0
|
||||
self.comboBox_SbsarPreset.addItem(self.presetItem)
|
||||
self.comboBox_SbsarPreset.setItemText(preset_index, self.presetItem)
|
||||
preset_index += 1
|
||||
|
||||
self.mainLayout.addWidget(self.comboBox_SbsarPreset, 7, 1, 1, 3)
|
||||
print("\n")
|
||||
self.comboBox_SbsarPreset.activated[str].connect(self.preset_selected)
|
||||
|
||||
def preset_selected(self, text):
|
||||
self.selected_text = text
|
||||
print(self.selected_text)
|
||||
|
||||
def create_tex_res(self):
|
||||
self.mainLayout.addWidget(self.OutputResLabel, 8, 0)
|
||||
self.resSelector = QtWidgets.QComboBox(self)
|
||||
self.res = ["512x512", "1024x1024", "2048x2048"]
|
||||
self.resSelector.addItems(self.res)
|
||||
|
||||
self.mainLayout.addWidget(self.resSelector, 8, 1, 1, 3)
|
||||
self.resSelector.activated[str].connect(self.res_selected)
|
||||
|
||||
def res_selected(self):
|
||||
if self.resSelector.currentIndex() == 0: print("$outputsize@9,9")
|
||||
elif self.resSelector.currentIndex() == 1: print("$outputsize@10,10")
|
||||
elif self.resSelector.currentIndex() == 2: print("$outputsize@11,11")
|
||||
|
||||
def remove(self):
|
||||
|
||||
self.mainLayout.removeWidget(self.groupbox_outputs)
|
||||
self.groupbox_outputs.close()
|
||||
|
||||
def kill(self):
|
||||
reader.kill()
|
||||
|
||||
def watch(self):
|
||||
reader.start('python', ['-u', window.watcher_script, window.sbsarDirectory])
|
||||
|
||||
def open_fileOfitem(self, row, column):
|
||||
self.item = self.filesTable.item(row, 0)
|
||||
print(self.path+"/"+self.item.text())
|
||||
if self.item.text().split(".")[-1] == "sbsar":
|
||||
self.create_sbsar_presets()
|
||||
self.create_tex_res()
|
||||
self.mainLayout.removeWidget(self.groupbox_outputs)
|
||||
self.groupbox_outputs.close()
|
||||
self.create_sbsar_params()
|
||||
elif self.item.text().split(".")[-1] == "sbs":
|
||||
print("This is a Substance File")
|
||||
builder.cook_sbsar(self.path+"/"+self.item.text(), builder._context, self.path, self.item.text().split(".")[0])
|
||||
# builder.output_info()
|
||||
|
||||
class ProcessOutputReader(QProcess):
|
||||
produce_output = Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
# merge stderr channel into stdout channel
|
||||
self.setProcessChannelMode(QProcess.MergedChannels)
|
||||
# prepare decoding process' output to Unicode
|
||||
self._codec = QTextCodec.codecForLocale()
|
||||
self._decoder_stdout = self._codec.makeDecoder()
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self._decoder_stderr = codec.makeDecoder()
|
||||
|
||||
self.readyReadStandardOutput.connect(self._ready_read_standard_output)
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# self.readyReadStandardError.connect(self._ready_read_standard_error)
|
||||
|
||||
@Slot()
|
||||
def _ready_read_standard_output(self):
|
||||
raw_bytes = self.readAllStandardOutput()
|
||||
text = self._decoder_stdout.toUnicode(raw_bytes)
|
||||
self.produce_output.emit(text)
|
||||
|
||||
# only necessary when stderr channel isn't merged into stdout:
|
||||
# @Slot()
|
||||
# def _ready_read_standard_error(self):
|
||||
# raw_bytes = self.readAllStandardError()
|
||||
# text = self._decoder_stderr.toUnicode(raw_bytes)
|
||||
# self.produce_output.emit(text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication(sys.argv)
|
||||
reader = ProcessOutputReader()
|
||||
window = Window()
|
||||
reader.produce_output.connect(window.append_output)
|
||||
reader.start('python', ['-u', window.watcher_script, window.sbsarDirectory])
|
||||
window.show()
|
||||
timer = QTimer()
|
||||
timer.timeout.connect(lambda: None)
|
||||
timer.start(10)
|
||||
# sys.exit(reader.kill())
|
||||
sys.exit(app.exec_())
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
/* ============================================================= */
|
||||
/* Global styles */
|
||||
/* ============================================================= */
|
||||
|
||||
|
||||
*
|
||||
{
|
||||
background-color: #444444;
|
||||
color: white;
|
||||
font-family: "Amazon Ember";
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
QMainWindow,
|
||||
QDialog,
|
||||
QDockWidget
|
||||
{
|
||||
color: #cccccc;
|
||||
background-color: #393a3c;
|
||||
}
|
||||
|
||||
QTextEdit,
|
||||
QPlainText,
|
||||
QLineEdit,
|
||||
QSpinBox,
|
||||
QDoubleSpinBox,
|
||||
QComboBox
|
||||
{
|
||||
background-color: #e9e9e9;
|
||||
color: black;
|
||||
font-family: "Amazon Ember";
|
||||
border-radius: 2px;
|
||||
border-width: 0px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
@import "Text.qss";
|
||||
@import "PushButton.qss";
|
||||
@import "Menu.qss";
|
||||
@import "CheckBox.qss";
|
||||
@import "RadioButton.qss";
|
||||
@import "ToolTip.qss";
|
||||
@import "ProgressBar.qss";
|
||||
@import "ColorPicker.qss";
|
||||
@import "Slider.qss";
|
||||
@import "Card.qss";
|
||||
@import "BrowseEdit.qss";
|
||||
@import "BreadCrumbs.qss";
|
||||
@import "LineEdit.qss";
|
||||
@import "ComboBox.qss";
|
||||
@import "SegmentControl.qss";
|
||||
@import "SpinBox.qss";
|
||||
@import "ScrollBar.qss";
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
BreadCrumbs are QWidgets with a QHBoxLayout with 0 content margins and one QLabel child widget.
|
||||
|
||||
*/
|
||||
|
||||
AzQtComponents--BreadCrumbs
|
||||
{
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
AzQtComponents--BrowseEdit
|
||||
{
|
||||
border-color: transparent;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
AzQtComponents--BrowseEdit QLineEdit
|
||||
{
|
||||
height: 16px;
|
||||
border-width: 0px;
|
||||
border-style: solid;
|
||||
border-color: #444444;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
AzQtComponents--BrowseEdit QLineEdit:focus
|
||||
{
|
||||
background-color: #FFFFFF;
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
|
||||
AzQtComponents--BrowseEdit QLineEdit:disabled
|
||||
{
|
||||
background-color: #777777;
|
||||
border-color: #777777;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
AzQtComponents--BrowseEdit QToolButton
|
||||
{
|
||||
max-width: 15px;
|
||||
min-width: 15px;
|
||||
max-height: 14px;
|
||||
min-height: 14px;
|
||||
background-color: #444444;
|
||||
border-color: #000000;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
AzQtComponents--Card
|
||||
{
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 1px solid rgb(33, 34, 35);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
AzQtComponents--CardNotification
|
||||
{
|
||||
background-color: #1AFDC32D;
|
||||
margin: 2px;
|
||||
border: 1px solid #FDC32D;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
AzQtComponents--CardNotification #HeaderFrame,
|
||||
AzQtComponents--CardNotification #Icon,
|
||||
AzQtComponents--CardNotification #Title
|
||||
{
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* CardHeader is a QFrame with a QHBoxLayout that contains the following children:
|
||||
* #Expander: a QCheckBox used as a "tree expander".
|
||||
* #Icon: a QLabel displaying an icon. The icon is set from c++.
|
||||
* #Title: a QLabel displaying text.
|
||||
* #ContextMenu: a QPushButton used to launch a context menu.
|
||||
*/
|
||||
|
||||
AzQtComponents--CardHeader
|
||||
{
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Background
|
||||
{
|
||||
margin: 2px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.primaryCardHeader
|
||||
{
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
.secondaryCardHeader
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Background[readOnly=true]
|
||||
{
|
||||
|
||||
background-repeat: repeat-xy;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Background[readOnly=false]
|
||||
{
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Title
|
||||
{
|
||||
font-weight: bold;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Expander
|
||||
{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
qproperty-flat: true;
|
||||
border: none;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
selection-color: transparent;
|
||||
selection-background-color: transparent;
|
||||
image: none;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Expander:enabled:checked
|
||||
{
|
||||
image: url(:/Cards/img/UI20/Cards/group_open.png);
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Expander:enabled:!checked
|
||||
{
|
||||
image: url(:/Cards/img/UI20/Cards/group_closed.png);
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Icon,
|
||||
AzQtComponents--CardHeader #WarningIcon
|
||||
{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #ContextMenu
|
||||
{
|
||||
qproperty-flat: true;
|
||||
border: none;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
selection-color: transparent;
|
||||
selection-background-color: transparent;
|
||||
image: url(:/Cards/img/UI20/Cards/menu_ico.png);
|
||||
}
|
||||
|
||||
AzQtComponents--CardHeader #Help
|
||||
{
|
||||
qproperty-flat: true;
|
||||
border: none;
|
||||
color: transparent;
|
||||
background-color: transparent;
|
||||
selection-color: transparent;
|
||||
selection-background-color: transparent;
|
||||
image: url(:/Cards/img/UI20/Cards/help.png);
|
||||
}
|
||||
|
||||
.separator
|
||||
{
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
AzQtComponents--Card #SeparatorContainer
|
||||
{
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QCheckBox
|
||||
{
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: rgb(204, 204, 204);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:!focus {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid white;
|
||||
border-radius: 2px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:focus {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid rgb(201, 170, 254);
|
||||
border-radius: 4px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:disabled {
|
||||
border-color: rgb(128, 128, 128);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:indeterminate:!focus {
|
||||
image: url(../builder/ui/stylesheets/img/UI20/combo-tristate.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:indeterminate:focus {
|
||||
image: url(../builder/ui/stylesheets/img/UI20/combo-tristate-focus.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked:!focus {
|
||||
image: url(../builder/ui/stylesheets/img/UI20/combo-checked.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked:focus {
|
||||
image: url(../builder/ui/stylesheets/img/UI20/combo-checked-focus.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked:disabled {
|
||||
image: url(../builder/ui/stylesheets/img/UI20/combo-checked-disabled.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator
|
||||
{
|
||||
width: 32px;
|
||||
height: 16px;
|
||||
border-width: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:Unchecked
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-unchecked.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:disabled
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-unchecked-disabled.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:Unchecked:focus
|
||||
{
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
border-width: 0px;
|
||||
margin: 0px;
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-unchecked-focus.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:Checked
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-checked.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:Checked:disabled
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-checked-disabled.png);
|
||||
}
|
||||
|
||||
QCheckBox[class="ToggleSwitch"]::indicator:Checked:focus
|
||||
{
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
border-width: 0px;
|
||||
margin: 0px;
|
||||
image: url(../builder/ui/stylesheets/img/UI20/toggle-checked-focus.png);
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
AzQtComponents--ColorPicker
|
||||
{
|
||||
padding: 100px;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker *
|
||||
{
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QTextEdit,
|
||||
AzQtComponents--ColorPicker QPlainText,
|
||||
AzQtComponents--ColorPicker QLineEdit,
|
||||
AzQtComponents--ColorPicker QSpinBox,
|
||||
AzQtComponents--ColorPicker QDoubleSpinBox
|
||||
{
|
||||
padding-bottom: -2px;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QToolButton
|
||||
{
|
||||
border: none;
|
||||
max-width: 16px;
|
||||
min-width: 16px;
|
||||
max-height: 16px;
|
||||
min-height: 16px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QLabel,
|
||||
AzQtComponents--ColorPicker QCheckBox,
|
||||
AzQtComponents--PaletteView,
|
||||
AzQtComponents--ColorPicker #Container
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QScrollArea
|
||||
{
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QLabel
|
||||
{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
AzQtComponents--PaletteCard AzQtComponents--CardHeader
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
AzQtComponents--PaletteCard AzQtComponents--CardHeader #Title
|
||||
{
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
AzQtComponents--PaletteCard[modified="true"] AzQtComponents--CardHeader #Title
|
||||
{
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
AzQtComponents--PaletteCard AzQtComponents--CardHeader #Expander:enabled:checked
|
||||
{
|
||||
image: url(:/ColorPickerDialog/PaletteCard/open.png);
|
||||
}
|
||||
|
||||
AzQtComponents--PaletteCard AzQtComponents--CardHeader #Expander:enabled:!checked
|
||||
{
|
||||
image: url(:/ColorPickerDialog/PaletteCard/closed.png);
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QSpinBox,
|
||||
AzQtComponents--ColorPicker QDoubleSpinBox
|
||||
{
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker QSpinBox::down-button,
|
||||
AzQtComponents--ColorPicker QSpinBox::up-button,
|
||||
AzQtComponents--ColorPicker QDoubleSpinBox::down-button,
|
||||
AzQtComponents--ColorPicker QDoubleSpinBox::up-button
|
||||
{
|
||||
width: 0px;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker .HorizontalSeparator
|
||||
{
|
||||
border-top: 1px solid #393a3c;
|
||||
border-bottom: 1px solid #393a3c;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
background: #222222;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorPicker AzQtComponents--PaletteView
|
||||
{
|
||||
border: none;
|
||||
}
|
||||
|
||||
AzQtComponents--ColorGrid,
|
||||
AzQtComponents--ColorPreview,
|
||||
AzQtComponents--Swatch
|
||||
{
|
||||
/* Note: Swatches will ignore any 'background*' property set here */
|
||||
border: 1px solid #333333;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
AzQtComponents--Swatch:selected
|
||||
{
|
||||
/* Note: Swatches will ignore any 'background*' property set here */
|
||||
border: 2px solid #C8ABFF;
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
/* NOTE: a number of QComboBox properties are defined in BaseStyleSheet.qss already, along with other text entry controls */
|
||||
|
||||
/* ComboBox */
|
||||
|
||||
QComboBox
|
||||
{
|
||||
height: 16px;
|
||||
padding-left: 8px;
|
||||
margin: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
QComboBox:focus
|
||||
{
|
||||
background-color: #FFFFFF;
|
||||
margin: 2px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
border-color: #C09EFF;
|
||||
}
|
||||
|
||||
QComboBox:disabled
|
||||
{
|
||||
background-color: #777777;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
QComboBox::drop-down
|
||||
{
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
QComboBox::down-arrow
|
||||
{
|
||||
image: url("../builder/ui/stylesheets/img/triangle3.png");
|
||||
}
|
||||
|
||||
QComboBox::down-arrow:disabled
|
||||
{
|
||||
image: url("../builder/ui/stylesheets/img/triangle1.png");
|
||||
}
|
||||
|
||||
/* Popup */
|
||||
|
||||
QComboBox QAbstractItemView
|
||||
{
|
||||
background-color: #222222;
|
||||
padding: 4px 0px 4px 0px;
|
||||
border-radius: 2px;
|
||||
outline: none; /* Disable focus rect */
|
||||
show-decoration-selected: 1;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::item
|
||||
{
|
||||
color: #FFFFFF;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-right: 24px;
|
||||
padding-left: 2px; /* There is already 22px from the check mark */
|
||||
|
||||
/* Without that the padding don't apply */
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
/*
|
||||
This is needed as a result of using padding / border in ::item as
|
||||
selection-background-color and selection-color are no more honored...
|
||||
*/
|
||||
QComboBox QAbstractItemView::item:selected
|
||||
{
|
||||
background-color: #444444;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::item:disabled
|
||||
{
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::separator
|
||||
{
|
||||
height: 1px;
|
||||
background: #444444;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::indicator
|
||||
{
|
||||
/* Keep in sync with the check mark image size */
|
||||
width: 12px;
|
||||
height: 9px;
|
||||
image: none;
|
||||
}
|
||||
|
||||
QComboBox QAbstractItemView::indicator:checked
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/menu-check.png);
|
||||
}
|
||||
+2022
File diff suppressed because it is too large
Load Diff
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
/* NOTE: a number of QLineEdit properties are defined in BaseStyleSheet.qss already, along with other text entry controls */
|
||||
|
||||
QLineEdit
|
||||
{
|
||||
height: 16px;
|
||||
padding-left: 8px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
QLineEdit[HasSearchAction=true] {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
QLineEdit:focus
|
||||
{
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
QLineEdit:disabled
|
||||
{
|
||||
background-color: #777777;
|
||||
color: #999999;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QMenuBar::item:selected
|
||||
{
|
||||
background: #222222;
|
||||
}
|
||||
|
||||
|
||||
QMenu
|
||||
{
|
||||
background-color: #222222;
|
||||
color: #FFFFFF;
|
||||
padding: 4px 0px 4px 0px;
|
||||
|
||||
/* Note: with QMenu, only 'margin' works, not 'margin-bottom' or 'margin-top'. */
|
||||
}
|
||||
|
||||
QMenu::item
|
||||
{
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
QMenu::item:selected
|
||||
{
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
QMenu::item:disabled
|
||||
{
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
QMenu::separator
|
||||
{
|
||||
height: 1px;
|
||||
background: #444444;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QProgressBar
|
||||
{
|
||||
border: 0px transparent #000000;
|
||||
border-radius: 0px;
|
||||
background: #888888;
|
||||
height: 7px;
|
||||
font-size: 1px; /* hack to get around the fact that QProgressBar's minimumSizeHint
|
||||
is computed from font size, even when text is invisible */
|
||||
qproperty-textVisible: False;
|
||||
}
|
||||
|
||||
QProgressBar::chunk
|
||||
{
|
||||
background: #B48BFF;
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
/* =============================================================
|
||||
Push Buttons
|
||||
|
||||
Painting of the background and the frame of PushButtons
|
||||
are done in code, in PushButton.cpp.
|
||||
|
||||
It's done there because the default Qt rendering, or at least
|
||||
the Fusion style, which we use as our base style, doesn't
|
||||
properly anti-alias and position the button bounding box
|
||||
when we customize the background colors and styles here. Also,
|
||||
there's literally no way to get the colors and styling info out
|
||||
of the stylesheet.
|
||||
|
||||
Below is the configuration of the text colors for the
|
||||
PushButtons and the font size, which can be done here in the
|
||||
stylesheet.
|
||||
|
||||
Everything else can be configured via PushButton.ini
|
||||
============================================================= */
|
||||
|
||||
QPushButton
|
||||
{
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
font-family: "Amazon Ember";
|
||||
}
|
||||
|
||||
QPushButton:hover,
|
||||
QPushButton:pressed
|
||||
{
|
||||
color: white;
|
||||
}
|
||||
|
||||
QPushButton:disabled
|
||||
{
|
||||
color: #C8C8C8;
|
||||
}
|
||||
|
||||
QPushButton.Primary,
|
||||
QPushButton:default,
|
||||
QToolButton::menu-indicator,
|
||||
QToolButton.SmallIcon::menu-indicator
|
||||
{
|
||||
color: white;
|
||||
}
|
||||
|
||||
QPushButton.Primary:disabled,
|
||||
QPushButton:default:disabled,
|
||||
QToolButton.SmallIcon:disabled::menu-indicator
|
||||
{
|
||||
color: #C8C8C8;
|
||||
}
|
||||
|
||||
|
||||
QPushButton::menu-indicator
|
||||
{
|
||||
subcontrol-position: right center;
|
||||
subcontrol-origin: padding;
|
||||
left: -8px;
|
||||
image: url("../builder/ui/stylesheets/img/triangle1.png")
|
||||
}
|
||||
|
||||
QPushButton.SmallIcon
|
||||
{
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QRadioButton
|
||||
{
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: rgb(204, 204, 204);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
QRadioButton::indicator:!focus
|
||||
{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator:focus
|
||||
{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator:checked
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-checked.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:checked:disabled
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-checked-disabled.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:checked:focus
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-checked-focus.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:unchecked
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-unchecked.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:unchecked:disabled
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-unchecked-disabled.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator:unchecked:focus
|
||||
{
|
||||
image: url(../builder/ui/stylesheets/img/UI20/radio-unchecked-disabled.png);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QScrollBar:vertical
|
||||
{
|
||||
border: 4px solid rgba(85, 85, 85, 65%);
|
||||
width: 16px;
|
||||
background-color: rgba(85, 85, 85, 65%);
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QScrollBar:horizontal
|
||||
{
|
||||
border: 4px solid rgba(85, 85, 85, 65%);
|
||||
height: 16px;
|
||||
background-color: rgba(85, 85, 85, 65%);
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QScrollBar::handle
|
||||
{
|
||||
border: 0px solid #000000;
|
||||
border-radius: 4px;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
QScrollBar::add-line,
|
||||
QScrollBar::sub-line
|
||||
{
|
||||
border: none;
|
||||
background: none;
|
||||
color: none;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QTabWidget
|
||||
{
|
||||
}
|
||||
|
||||
/* An empty ::pane rule removes the default pane style. */
|
||||
QTabWidget::pane
|
||||
{
|
||||
}
|
||||
|
||||
QTabBar
|
||||
{
|
||||
qproperty-drawBase: 0;
|
||||
alignment: center;
|
||||
}
|
||||
|
||||
QTabBar::tab
|
||||
{
|
||||
background-color: #333333;
|
||||
border-color: #000000;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
height: 28px;
|
||||
|
||||
line-height: 24px;
|
||||
|
||||
margin-left: 0px;
|
||||
|
||||
min-width: 100px
|
||||
}
|
||||
|
||||
QTabBar::tab::middle,
|
||||
QTabBar::tab::last
|
||||
{
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
QTabBar::tab:selected
|
||||
{
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
QTabBar::tab:hover
|
||||
|
||||
{
|
||||
background-color: #444444;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
QSpinBox,
|
||||
QDoubleSpinBox
|
||||
{
|
||||
height: 16px;
|
||||
margin: 2px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
QSpinBox:focus,
|
||||
QDoubleSpinBox:focus
|
||||
{
|
||||
margin: 0px;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
border-color: #C19CFE;
|
||||
}
|
||||
|
||||
QSpinBox:disabled,
|
||||
QDoubleSpinBox:disabled
|
||||
{
|
||||
background-color: #777777;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
QSpinBox::up-button,
|
||||
QDoubleSpinBox::up-button,
|
||||
QSpinBox::down-button,
|
||||
QDoubleSpinBox::down-button
|
||||
{
|
||||
width: 14px;
|
||||
height: 6px;
|
||||
border-width: 1px;
|
||||
border-color: #222222;
|
||||
border-style: solid;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
QSpinBox::up-button,
|
||||
QDoubleSpinBox::up-button
|
||||
{
|
||||
border-top-right-radius: 2px;
|
||||
image: url(../builder/ui/stylesheets/img/UI20/spinbox-up-arrow.png);
|
||||
}
|
||||
|
||||
QSpinBox::down-button,
|
||||
QDoubleSpinBox::down-button
|
||||
{
|
||||
border-bottom-right-radius: 2px;
|
||||
image: url(../builder/ui/stylesheets/img/UI20/spinbox-down-arrow.png);
|
||||
}
|
||||
|
||||
QSpinBox::up-button:pressed,
|
||||
QDoubleSpinBox::up-button:pressed,
|
||||
QSpinBox::down-button:pressed,
|
||||
QDoubleSpinBox::down-button:pressed
|
||||
{
|
||||
background-color: #111111;
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c45dda65605edfffe83701f467acbc28230fefdfedcddd1879d12e534f1c903
|
||||
size 1228
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b959d561237d63b859eb9a0bb5ef1fa614a5788b3604a4515232b904803e2e2b
|
||||
size 59380
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b327b9b1ab34d6facc574b4602eb678afa97825bf1aa721fa7505ef5c43ab051
|
||||
size 59380
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:febc3883ac09ea579877deb49a41441f618a1558f63a838ca3e216c65c638b1c
|
||||
size 448
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7e89ed1a88e4b7488a34f9d5ecc3cf231d1c3b2b225fe956e009431ae0cd02d3
|
||||
size 438
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user