Integrating up through commit 90f050496
This commit is contained in:
@@ -17,12 +17,12 @@ export DYNACONF_LY_PROJECT_PATH=`pwd`
|
||||
export DYNACONF_LY_DEV=${LY_PROJECT_PATH}\..\..\..\..
|
||||
|
||||
# LY build folder
|
||||
export DYNACONF_LY_BUILD_PATH=${LY_DEV}\windows_vs2019
|
||||
export DYNACONF_LY_BUILD_PATH=${LY_DEV}\build
|
||||
export DYNACONF_LY_BIN_PATH=${LY_BUILD_PATH}\bin\profile
|
||||
|
||||
# default IDE and debug settings
|
||||
export DYNACONF_DCCSI_GDEBUG=false
|
||||
export DYNACONF_DCCSI_DEV_MODE=false
|
||||
#export DYNACONF_DCCSI_GDEBUG=false
|
||||
#export DYNACONF_DCCSI_DEV_MODE=false
|
||||
export DYNACONF_DCCSI_GDEBUGGER=WING
|
||||
export DYNACONF_DCCSI_LOGLEVEL=20
|
||||
|
||||
@@ -30,18 +30,21 @@ export DYNACONF_DCCSI_LOGLEVEL=20
|
||||
export DYNACONF_DCCSIG_PATH=${LY_DEV}\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface
|
||||
|
||||
# set up default python interpreter (Lumberyard)
|
||||
# we may want to entirely remove these and rely on config.py to dynamically set up
|
||||
# however VScode can be configured with a .env so might be valueable to keep
|
||||
export DYNACONF_DCCSI_PY_VERSION_MAJOR=3
|
||||
export DYNACONF_DCCSI_PY_VERSION_MINOR=7
|
||||
export DYNACONF_DCCSI_PY_VERSION_RELEASE=5
|
||||
export DYNACONF_DCCSI_PY_VERSION_RELEASE=11
|
||||
# To Do: probably move the folder below into the /SDK folder?
|
||||
export DYNACONF_DCCSI_PYTHON_PATH=${DCCSIG_PATH}\3rdParty\Python
|
||||
|
||||
# add access to a Lib location that matches the py version (3.7.x)
|
||||
# switch this for other python version like maya (2.7.x)
|
||||
# switch this for other python version like (2.7.x) for Maya
|
||||
export DYNACONF_DCCSI_PYTHON_LIB_PATH=${DCCSI_PYTHON_PATH}\Lib\${DCCSI_PY_VERSION_MAJOR}.x\${DCCSI_PY_VERSION_MAJOR}.${DCCSI_PY_VERSION_MINOR}.x\site-packages
|
||||
|
||||
# TO DO: figure out how to best deal with OS folder (i.e. 'windows')
|
||||
export DYNACONF_DCCSI_PYTHON_INSTALL=${LY_DEV}\Tools\Python\${DCCSI_PY_VERSION_MAJOR}.${DCCSI_PY_VERSION_MINOR}.${DCCSI_PY_VERSION_RELEASE}\${OS_FOLDER}
|
||||
export DYNACONF_DDCCSI_PY_BASE=${DCCSI_PYTHON_INSTALL}\python.exe
|
||||
export DYNACONF_DCCSI_PYTHON_INSTALL=${LY_DEV}\python
|
||||
export DYNACONF_DDCCSI_PY_BASE=${DCCSI_PYTHON_INSTALL}\python.cmd
|
||||
|
||||
# set up Qt / PySide2
|
||||
# TO DO: These should NOT be set in the global env as they will cause conflicts
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
|
||||
.mayaSwatches
|
||||
.temp
|
||||
__downloads__
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
workspace.xml
|
||||
# Ignore dynaconf secret files
|
||||
.secrets.*
|
||||
settings.local.json
|
||||
settings.local.json
|
||||
azpy/_sample_package_/*
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# exclude everything
|
||||
site-packages/*
|
||||
|
||||
# exception to the rule
|
||||
!site-packages/stub
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
this is just a stub
|
||||
this is a silo directory for sandboxing the addition of python packages
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# exclude everything
|
||||
site-packages/*
|
||||
|
||||
# exception to the rule
|
||||
!site-packages/stub
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
this is just a stub
|
||||
this is a silo directory for sandboxing the addition of python packages
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d101843b6780578d326f94b8dac06ca4d6597703d5a0fd08e6dc18561a16f749
|
||||
size 392292
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f27032e14c278ee82fead2f4ac2726df6321a370b1a6eb6135540ecda565239c
|
||||
size 3146071
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:20fd966e4e19f3f96479a396c0747ec2bd8e6eebb0296ea4303e06252bbd7160
|
||||
size 10512
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:bf0141cf55ff5b468580a63beb1d723d15e08c883a48ffa7e650c54156ab340e
|
||||
size 4619089
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:98e0b98644fbe047dd714686da5357a6a62afe413083ad79e049a5ff2d3d475c
|
||||
size 455961
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6207ce940853cae79d725d725d7b05303e09cd0b940a4d77f1e280742aed0f00
|
||||
size 13161
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6f127dfe53824a7f15a91d1103805172e70f521ce286b6db05bee6c5026c5bd4
|
||||
size 203
|
||||
@@ -10,3 +10,4 @@
|
||||
#
|
||||
|
||||
add_subdirectory(Code)
|
||||
update_pip_requirements(${CMAKE_CURRENT_LIST_DIR}/requirements.txt DccScriptingInterface)
|
||||
@@ -38,25 +38,3 @@ ly_add_target(
|
||||
PRIVATE
|
||||
Gem::DccScriptingInterface.Static
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Tests
|
||||
################################################################################
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
ly_add_target(
|
||||
NAME DccScriptingInterface.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE Gem
|
||||
FILES_CMAKE
|
||||
dccscriptinginterface_tests_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Tests
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzTest
|
||||
Gem::DccScriptingInterface.Static
|
||||
)
|
||||
ly_add_googletest(
|
||||
NAME Gem::DccScriptingInterface.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
class DCCScriptingInterfaceTest
|
||||
: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DCCScriptingInterfaceTest, SanityTest)
|
||||
{
|
||||
ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
|
||||
@@ -0,0 +1 @@
|
||||
Env_Dev.bat
|
||||
@@ -1,305 +0,0 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up Default LY DCC Scripting Interface Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
IF "%DCCSI_LAUNCHERS_PATH%"=="" (set DCCSI_LAUNCHERS_PATH=%~dp0)
|
||||
echo DCCSI_LAUNCHERS_PATH = %DCCSI_LAUNCHERS_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_LAUNCHERS_PATH%;%PATH%
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..)
|
||||
echo DEV_REL_PATH = %DEV_REL_PATH%
|
||||
|
||||
IF "%LY_PROJECT%"=="" (
|
||||
ECHO !!LY_PROJECT NOT defined!!
|
||||
for %%a in (%CD%..\..\..) do set LY_PROJECT=%%~na
|
||||
)
|
||||
echo LY_PROJECT = %LY_PROJECT%
|
||||
|
||||
:: set up the default project path (dccsi)
|
||||
CD /D ..\..\
|
||||
IF "%LY_PROJECT_PATH%"=="" (set LY_PROJECT_PATH=%CD%)
|
||||
echo LY_PROJECT_PATH = %LY_PROJECT_PATH%
|
||||
|
||||
IF "%ABS_PATH%"=="" (set ABS_PATH=%CD%)
|
||||
echo ABS_PATH = %ABS_PATH%
|
||||
|
||||
:: Save current directory and change to target directory
|
||||
pushd %ABS_PATH%
|
||||
|
||||
:: Change to root Lumberyard dev dir
|
||||
CD /d %LY_PROJECT_PATH%\%DEV_REL_PATH%
|
||||
set LY_DEV=%CD%
|
||||
echo LY_DEV = %LY_DEV%
|
||||
:: Restore original directory
|
||||
popd
|
||||
|
||||
:: dcc scripting interface gem path
|
||||
set DCCSIG_PATH=%LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface
|
||||
echo DCCSIG_PATH = %DCCSIG_PATH%
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %DCCSIG_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%
|
||||
:: Default level logger will handle
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=20)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
set MAYA_PROJECT=%LY_PROJECT_PATH%
|
||||
echo MAYA_PROJECT = %MAYA_PROJECT%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%PATH%
|
||||
|
||||
:: dcc python api path
|
||||
set DCCSI_AZPY_PATH=%DCCSIG_PATH%\azpy
|
||||
echo DCCSI_AZPY_PATH = %DCCSI_AZPY_PATH%
|
||||
|
||||
:: per-dcc sdk patj
|
||||
set DCCSI_SDK_PATH=%DCCSIG_PATH%\SDK
|
||||
echo DCCSI_SDK_PATH = %DCCSI_SDK_PATH%
|
||||
|
||||
set DCCSI_LOG_PATH=%DCCSIG_PATH%\.temp\logs
|
||||
echo DCCSI_LOG_PATH = %DCCSI_LOG_PATH%
|
||||
echo.
|
||||
|
||||
:: PY version Major
|
||||
IF "%DCCSI_PY_VERSION_MAJOR%"=="" (set DCCSI_PY_VERSION_MAJOR=3)
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
|
||||
:: PY version Major
|
||||
IF "%DCCSI_PY_VERSION_MINOR%"=="" (set DCCSI_PY_VERSION_MINOR=7)
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
|
||||
IF "%DCCSI_PY_VERSION_RELEASE%"=="" (set DCCSI_PY_VERSION_RELEASE=5)
|
||||
echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE%
|
||||
|
||||
:: shared location for 64bit python 3.7 DEV location
|
||||
set DCCSI_PYTHON_PATH=%DCCSIG_PATH%\3rdParty\Python
|
||||
echo DCCSI_PYTHON_PATH = %DCCSI_PYTHON_PATH%
|
||||
|
||||
:: add access to a Lib location that matches the py version (3.7.x)
|
||||
:: switch this for other python version like maya (2.7.x)
|
||||
IF "%DCCSI_PYTHON_LIB_PATH%"=="" (set DCCSI_PYTHON_LIB_PATH=%DCCSI_PYTHON_PATH%\Lib\%DCCSI_PY_VERSION_MAJOR%.x\%DCCSI_PY_VERSION_MAJOR%.%DCCSI_PY_VERSION_MINOR%.x\site-packages)
|
||||
echo DCCSI_PYTHON_LIB_PATH = %DCCSI_PYTHON_LIB_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_LIB_PATH%;%PATH%
|
||||
|
||||
set DCCSI_PYTHON_ROOT=%LY_DEV%\Tools\Python\%DCCSI_PY_VERSION_MAJOR%.%DCCSI_PY_VERSION_MINOR%.%DCCSI_PY_VERSION_RELEASE%
|
||||
echo DCCSI_PYTHON_ROOT = %DCCSI_PYTHON_ROOT%
|
||||
|
||||
:: shared location for Lumberyard 64bit python 3.x location
|
||||
set DCCSI_PYTHON_INSTALL=%DCCSI_PYTHON_ROOT%\windows
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_INSTALL%;%PATH%
|
||||
|
||||
set LY_PY_INTERNAL=%DCCSI_PYTHON_ROOT%\internal\site-packages\windows
|
||||
echo LY_PY_INTERNAL = %LY_PY_INTERNAL%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%LY_PY_INTERNAL%;%PATH%
|
||||
|
||||
:: shared location for 64bit python 3.7 BASE location
|
||||
set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: shared location for 64bit python 3.7 BASE location
|
||||
set DCCSI_PY_DCCSI=%DCCSI_LAUNCHERS_PATH%\Launch_pyBASE.bat
|
||||
echo DCCSI_PY_DCCSI = %DCCSI_PY_DCCSI%
|
||||
|
||||
:: maya sdk path
|
||||
set DCCSI_SDK_MAYA_PATH=%DCCSI_SDK_PATH%\Maya
|
||||
echo DCCSI_SDK_MAYA_PATH = %DCCSI_SDK_MAYA_PATH%
|
||||
|
||||
set MAYA_MODULE_PATH=%DCCSI_SDK_MAYA_PATH%;%MAYA_MODULE_PATH%
|
||||
echo MAYA_MODULE_PATH = %MAYA_MODULE_PATH%
|
||||
|
||||
:: Default Maya Version
|
||||
IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020)
|
||||
echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION%
|
||||
|
||||
:: Maya File Paths, etc
|
||||
:: https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2015/ENU/Maya/files/Environment-Variables-File-path-variables-htm.html
|
||||
set MAYA_LOCATION=%ProgramFiles%\Autodesk\Maya%DCCSI_MAYA_VERSION%
|
||||
echo MAYA_LOCATION = %MAYA_LOCATION%
|
||||
|
||||
set MAYA_BIN_PATH=%MAYA_LOCATION%\bin\
|
||||
echo MAYA_BIN_PATH = %MAYA_BIN_PATH%
|
||||
|
||||
:: these improve the boot up time
|
||||
IF "%MAYA_DISABLE_CIP%"=="" (set MAYA_DISABLE_CIP=1)
|
||||
echo MAYA_DISABLE_CIP = %MAYA_DISABLE_CIP%
|
||||
|
||||
IF "%MAYA_DISABLE_CER%"=="" (set MAYA_DISABLE_CER=1)
|
||||
echo MAYA_DISABLE_CER = %MAYA_DISABLE_CER%
|
||||
|
||||
IF "%MAYA_DISABLE_CLIC_IPM%"=="" (set MAYA_DISABLE_CLIC_IPM=1)
|
||||
echo MAYA_DISABLE_CLIC_IPM = %MAYA_DISABLE_CLIC_IPM%
|
||||
|
||||
IF "%DCCSI_MAYA_SET_CALLBACKS%"=="" (set DCCSI_MAYA_SET_CALLBACKS=false)
|
||||
echo DCCSI_MAYA_SET_CALLBACKS = %DCCSI_MAYA_SET_CALLBACKS%
|
||||
|
||||
:: setting this to 1 should further improve boot time (I think)
|
||||
::IF "%MAYA_NO_CONSOLE_WINDOW%"=="" (set MAYA_NO_CONSOLE_WINDOW=0)
|
||||
::echo MAYA_NO_CONSOLE_WINDOW = %MAYA_NO_CONSOLE_WINDOW%
|
||||
|
||||
:: shared location for 64bit DCCSI_PY_MAYA python 2.7 DEV location
|
||||
set DCCSI_PY_MAYA=%MAYA_LOCATION%\bin\mayapy.exe
|
||||
echo DCCSI_PY_MAYA = %DCCSI_PY_MAYA%
|
||||
|
||||
:: azpy Maya plugins access
|
||||
:: our path
|
||||
set DCCSI_MAYA_PLUG_IN_PATH=%DCCSI_SDK_MAYA_PATH%\plugins
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_PLUG_IN_PATH=%DCCSI_MAYA_PLUG_IN_PATH%;MAYA_PLUG_IN_PATH
|
||||
echo DCCSI_MAYA_PLUG_IN_PATH = %DCCSI_MAYA_PLUG_IN_PATH%
|
||||
|
||||
:: azpy Maya shelves
|
||||
:: our path
|
||||
set DCCSI_MAYA_SHELF_PATH=%DCCSI_SDK_MAYA_PATH%\Prefs\Shelves
|
||||
set MAYA_SHELF_PATH=%DCCSI_MAYA_SHELF_PATH%
|
||||
echo DCCSI_MAYA_SHELF_PATH = %DCCSI_MAYA_SHELF_PATH%
|
||||
|
||||
:: azpy Maya icons
|
||||
:: our path
|
||||
set DCCSI_MAYA_XBMLANGPATH=%DCCSI_SDK_MAYA_PATH%\Prefs\icons
|
||||
:: also attached to maya's built-it env var
|
||||
set XBMLANGPATH=%DCCSI_MAYA_XBMLANGPATH%;%XBMLANGPATH%
|
||||
echo DCCSI_MAYA_XBMLANGPATH = %DCCSI_MAYA_XBMLANGPATH%
|
||||
|
||||
:: azpy root Maya boostrap, userSetup.py access
|
||||
:: our path
|
||||
set DCCSI_MAYA_SCRIPT_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_PATH = %DCCSI_MAYA_SCRIPT_PATH%
|
||||
|
||||
:: azpy Maya Mel scripts
|
||||
:: our path
|
||||
set DCCSI_MAYA_SCRIPT_MEL_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Mel
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_MEL_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_MEL_PATH = %DCCSI_MAYA_SCRIPT_MEL_PATH%
|
||||
|
||||
:: azpy Maya Py scripts
|
||||
:: our path
|
||||
set DCCSI_MAYA_SCRIPT_PY_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Python
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PY_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_PY_PATH = %DCCSI_MAYA_SCRIPT_PY_PATH%
|
||||
|
||||
:: azpy add all python related paths to PYTHONPATH
|
||||
::set PYTHONPATH=%DCCSIG_PATH%;%DCCSI_AZPY_PATH%;%DCCSI_SDK_MAYA_PATH%;%DCCSI_MAYA_SCRIPT_PATH%;%DCCSI_MAYA_SCRIPT_PY_PATH%;%PYTHONPATH%
|
||||
set PYTHONPATH=%LY_PY_INTERNAL%;%DCCSIG_PATH%;%DCCSI_PYTHON_LIB_PATH%;%DCCSI_MAYA_SCRIPT_PATH%;%DCCSI_MAYA_SCRIPT_PY_PATH%;%PYTHONPATH%
|
||||
echo PYTHONPATH = %PYTHONPATH%
|
||||
|
||||
:: build path
|
||||
IF "%TAG_LY_BUILD_PATH%"=="" (set TAG_LY_BUILD_PATH=windows_vs2019)
|
||||
echo TAG_LY_BUILD_PATH = %TAG_LY_BUILD_PATH%
|
||||
|
||||
IF "%LY_BUILD_PATH%"=="" (set LY_BUILD_PATH=%LY_DEV%\%TAG_LY_BUILD_PATH%)
|
||||
echo LY_BUILD_PATH = %LY_BUILD_PATH%
|
||||
|
||||
:: Substance Designer
|
||||
:: maya sdk path
|
||||
set DCCSI_SUBSTANCE_PATH=%DCCSI_SDK_PATH%\Substance
|
||||
echo DCCSI_SUBSTANCE_PATH = %DCCSI_SUBSTANCE_PATH%
|
||||
:: https://docs.substance3d.com/sddoc/project-preferences-107118596.html#ProjectPreferences-ConfigurationFile
|
||||
:: Path to .exe, "C:\Program Files\Allegorithmic\Substance Designer\Substance Designer.exe"
|
||||
set SUBSTANCE_PATH="%ProgramFiles%\Allegorithmic\Substance Designer"
|
||||
echo SUBSTANCE_PATH = %SUBSTANCE_PATH%
|
||||
|
||||
:: default config
|
||||
set SUBSTANCE_CFG_PATH=%LY_PROJECT_PATH%\DCCsi_default.sbscfg
|
||||
echo SUBSTANCE_CFG_PATH = %SUBSTANCE_CFG_PATH%
|
||||
|
||||
:: WingIDE version Major
|
||||
IF "%DCCSI_WING_VERSION_MAJOR%"=="" (set DCCSI_WING_VERSION_MAJOR=7)
|
||||
echo DCCSI_WING_VERSION_MAJOR = %DCCSI_WING_VERSION_MAJOR%
|
||||
|
||||
:: WingIDE version Major
|
||||
IF "%DCCSI_WING_VERSION_MINOR%"=="" (set DCCSI_WING_VERSION_MINOR=1)
|
||||
echo DCCSI_WING_VERSION_MINOR = %DCCSI_WING_VERSION_MINOR%
|
||||
|
||||
:: put project env variables/paths here
|
||||
set WINGHOME=%PROGRAMFILES(X86)%\Wing Pro %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR%
|
||||
echo WINGHOME = %WINGHOME%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%WINGHOME%;%PATH%
|
||||
|
||||
:: ide and debugger plugs
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE%
|
||||
|
||||
:: add prefered python to the PATH
|
||||
SET PATH=%DCCSIG_PATH%;%DCCSI_AZPY_PATH%;%DCCSI_PY_DEFAULT%;%PATH%
|
||||
echo PATH = %PATH%
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %ABS_PATH%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Default DCCsi Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCC Scripting Interface Core
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCC Scripting Interface Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
IF "%DCCSI_LAUNCHERS_PATH%"=="" (set DCCSI_LAUNCHERS_PATH=%~dp0)
|
||||
echo DCCSI_LAUNCHERS_PATH = %DCCSI_LAUNCHERS_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_LAUNCHERS_PATH%;%PATH%
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug flag (propogates)
|
||||
:: The intent here is to set and globally enter a debug mode
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates earliest debugger connection
|
||||
:: we support attaching to WingIDE... PyCharm and VScode in the future
|
||||
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%
|
||||
:: Default level logger will handle
|
||||
:: Override this to control the setting
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=20)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..)
|
||||
echo DEV_REL_PATH = %DEV_REL_PATH%
|
||||
|
||||
:: You can define the project name
|
||||
:: if not defined we just use the DCCsi path as standin
|
||||
IF "%LY_PROJECT%"=="" (
|
||||
for %%a in (%CD%..\..\..) do set LY_PROJECT=%%~na
|
||||
)
|
||||
echo LY_PROJECT = %LY_PROJECT%
|
||||
|
||||
:: set up the default project path (dccsi)
|
||||
:: if not set we lso use the DCCsi path as standin
|
||||
CD /D ..\..\
|
||||
IF "%LY_PROJECT_PATH%"=="" (set LY_PROJECT_PATH=%CD%)
|
||||
echo LY_PROJECT_PATH = %LY_PROJECT_PATH%
|
||||
|
||||
IF "%ABS_PATH%"=="" (set ABS_PATH=%CD%)
|
||||
echo ABS_PATH = %ABS_PATH%
|
||||
|
||||
:: Save current directory and change to target directory
|
||||
pushd %ABS_PATH%
|
||||
|
||||
:: Change to root Lumberyard dev dir
|
||||
CD /d %LY_PROJECT_PATH%\%DEV_REL_PATH%
|
||||
set LY_DEV=%CD%
|
||||
echo LY_DEV = %LY_DEV%
|
||||
:: Restore original directory
|
||||
popd
|
||||
|
||||
:: dcc scripting interface gem path
|
||||
:: currently know relative path to this gem
|
||||
set DCCSIG_PATH=%LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface
|
||||
echo DCCSIG_PATH = %DCCSIG_PATH%
|
||||
|
||||
:: Change to DCCsi root dir
|
||||
CD /D %DCCSIG_PATH%
|
||||
|
||||
:: per-dcc sdk path
|
||||
set DCCSI_SDK_PATH=%DCCSIG_PATH%\SDK
|
||||
echo DCCSI_SDK_PATH = %DCCSI_SDK_PATH%
|
||||
|
||||
:: temp log location specific to this gem
|
||||
set DCCSI_LOG_PATH=%DCCSIG_PATH%\.temp\logs
|
||||
echo DCCSI_LOG_PATH = %DCCSI_LOG_PATH%
|
||||
|
||||
:: O3DE build path
|
||||
IF "%TAG_LY_BUILD_PATH%"=="" (set TAG_LY_BUILD_PATH=build)
|
||||
echo TAG_LY_BUILD_PATH = %TAG_LY_BUILD_PATH%
|
||||
|
||||
IF "%LY_BUILD_PATH%"=="" (set LY_BUILD_PATH=%LY_DEV%\%TAG_LY_BUILD_PATH%\bin\profile)
|
||||
echo LY_BUILD_PATH = %LY_BUILD_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%LY_BUILD_PATH%;%DCCSIG_PATH%;%DCCSI_AZPY_PATH%;%PATH%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
@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
|
||||
|
||||
:: Sets up extended DCCsi environment for Maya
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCCsi Autodesk Maya Environment
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_MAYA_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
IF "%DCCSI_PY_VERSION_MAJOR%"=="" (set DCCSI_PY_VERSION_MAJOR=2)
|
||||
IF "%DCCSI_PY_VERSION_MINOR%"=="" (set DCCSI_PY_VERSION_MINOR=7)
|
||||
IF "%DCCSI_PY_VERSION_RELEASE%"=="" (set DCCSI_PY_VERSION_RELEASE=11)
|
||||
|
||||
:: Default Maya Version
|
||||
IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020)
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi Autodesk Maya Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE%
|
||||
echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION%
|
||||
|
||||
:::: Set Maya native project acess to this project
|
||||
IF "%MAYA_PROJECT%"=="" (set MAYA_PROJECT=%LY_PROJECT%)
|
||||
echo MAYA_PROJECT = %MAYA_PROJECT%
|
||||
|
||||
:: maya sdk path
|
||||
set DCCSI_SDK_MAYA_PATH=%DCCSI_SDK_PATH%\Maya
|
||||
echo DCCSI_SDK_MAYA_PATH = %DCCSI_SDK_MAYA_PATH%
|
||||
|
||||
set MAYA_MODULE_PATH=%DCCSI_SDK_MAYA_PATH%;%MAYA_MODULE_PATH%
|
||||
echo MAYA_MODULE_PATH = %MAYA_MODULE_PATH%
|
||||
|
||||
:: Maya File Paths, etc
|
||||
:: https://knowledge.autodesk.com/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2015/ENU/Maya/files/Environment-Variables-File-path-variables-htm.html
|
||||
:::: Set Maya native project acess to this project
|
||||
IF "%MAYA_LOCATION%"=="" (set MAYA_LOCATION=%ProgramFiles%\Autodesk\Maya%DCCSI_MAYA_VERSION%)
|
||||
echo MAYA_LOCATION = %MAYA_LOCATION%
|
||||
|
||||
IF "%MAYA_BIN_PATH%"=="" (set MAYA_BIN_PATH=%MAYA_LOCATION%\bin)
|
||||
echo MAYA_BIN_PATH = %MAYA_BIN_PATH%
|
||||
|
||||
:: these improve the boot up time (Ha ... not really much)
|
||||
IF "%MAYA_DISABLE_CIP%"=="" (set MAYA_DISABLE_CIP=1)
|
||||
echo MAYA_DISABLE_CIP = %MAYA_DISABLE_CIP%
|
||||
|
||||
IF "%MAYA_DISABLE_CER%"=="" (set MAYA_DISABLE_CER=1)
|
||||
echo MAYA_DISABLE_CER = %MAYA_DISABLE_CER%
|
||||
|
||||
IF "%MAYA_DISABLE_CLIC_IPM%"=="" (set MAYA_DISABLE_CLIC_IPM=1)
|
||||
echo MAYA_DISABLE_CLIC_IPM = %MAYA_DISABLE_CLIC_IPM%
|
||||
|
||||
IF "%DCCSI_MAYA_SET_CALLBACKS%"=="" (set DCCSI_MAYA_SET_CALLBACKS=false)
|
||||
echo DCCSI_MAYA_SET_CALLBACKS = %DCCSI_MAYA_SET_CALLBACKS%
|
||||
|
||||
:: setting this to 1 should further improve boot time (I think)
|
||||
::IF "%MAYA_NO_CONSOLE_WINDOW%"=="" (set MAYA_NO_CONSOLE_WINDOW=0)
|
||||
::echo MAYA_NO_CONSOLE_WINDOW = %MAYA_NO_CONSOLE_WINDOW%
|
||||
:: But I like the console window for development and debugging early boot
|
||||
|
||||
:: shared location for 64bit DCCSI_PY_MAYA python location (2.7)
|
||||
set DCCSI_PY_MAYA=%MAYA_BIN_PATH%\mayapy.exe
|
||||
echo DCCSI_PY_MAYA = %DCCSI_PY_MAYA%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%MAYA_BIN_PATH%;%PATH%
|
||||
|
||||
:: Local DCCsi Maya plugins access (ours)
|
||||
set DCCSI_MAYA_PLUG_IN_PATH=%DCCSI_SDK_MAYA_PATH%\plugins
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_PLUG_IN_PATH=%DCCSI_MAYA_PLUG_IN_PATH%;MAYA_PLUG_IN_PATH
|
||||
echo DCCSI_MAYA_PLUG_IN_PATH = %DCCSI_MAYA_PLUG_IN_PATH%
|
||||
|
||||
:: Local DCCsi Maya shelves (ours)
|
||||
set DCCSI_MAYA_SHELF_PATH=%DCCSI_SDK_MAYA_PATH%\Prefs\Shelves
|
||||
set MAYA_SHELF_PATH=%DCCSI_MAYA_SHELF_PATH%
|
||||
echo DCCSI_MAYA_SHELF_PATH = %DCCSI_MAYA_SHELF_PATH%
|
||||
|
||||
:: Local DCCsi Maya icons path (ours)
|
||||
set DCCSI_MAYA_XBMLANGPATH=%DCCSI_SDK_MAYA_PATH%\Prefs\icons
|
||||
:: also attached to maya's built-it env var
|
||||
set XBMLANGPATH=%DCCSI_MAYA_XBMLANGPATH%;%XBMLANGPATH%
|
||||
echo DCCSI_MAYA_XBMLANGPATH = %DCCSI_MAYA_XBMLANGPATH%
|
||||
|
||||
:: Local DCCsi Maya Mel scripts (ours)
|
||||
set DCCSI_MAYA_SCRIPT_MEL_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Mel
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_MEL_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_MEL_PATH = %DCCSI_MAYA_SCRIPT_MEL_PATH%
|
||||
|
||||
:: Local DCCsi Maya Python scripts (ours)
|
||||
set DCCSI_MAYA_SCRIPT_PY_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Python
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PY_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_PY_PATH = %DCCSI_MAYA_SCRIPT_PY_PATH%
|
||||
|
||||
:: DCCsi Maya boostrap, userSetup.py access (ours)
|
||||
set DCCSI_MAYA_SCRIPT_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts
|
||||
:: also attached to maya's built-it env var
|
||||
set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PATH%;%MAYA_SCRIPT_PATH%
|
||||
echo DCCSI_MAYA_SCRIPT_PATH = %DCCSI_MAYA_SCRIPT_PATH%
|
||||
|
||||
echo MAYA_SCRIPT_PATH = %MAYA_SCRIPT_PATH%
|
||||
|
||||
:: add all python related paths to PYTHONPATH for package imports
|
||||
set PYTHONPATH=%DCCSI_MAYA_SCRIPT_PATH%;%DCCSI_MAYA_SCRIPT_PY_PATH%;%PYTHONPATH%
|
||||
echo PYTHONPATH = %PYTHONPATH%
|
||||
|
||||
:: DX11 Viewport
|
||||
Set MAYA_VP2_DEVICE_OVERRIDE=VirtualDeviceDx11
|
||||
echo MAYA_VP2_DEVICE_OVERRIDE = %MAYA_VP2_DEVICE_OVERRIDE%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_MAYA_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_PYCHARM_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: version Major
|
||||
SET PYCHARM_VERSION_YEAR=2020
|
||||
:: version Major
|
||||
SET PYCHARM_VERSION_MAJOR=2
|
||||
|
||||
::"C:\Program Files\JetBrains\PyCharm 2019.1.3\bin"
|
||||
::"C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.5\bin\pycharm64.exe"
|
||||
|
||||
:: put project env variables/paths here
|
||||
set PYCHARM_HOME=%PROGRAMFILES%\JetBrains\PyCharm %PYCHARM_VERSION_YEAR%.%PYCHARM_VERSION_MAJOR%
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_IDE%\python.exe
|
||||
|
||||
SET PYCHARM_PROJ=%DCCSIG_PATH%\Solutions
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up O3DE DCCsi Dev Env PyCharm %PYCHARM_VERSION_YEAR%.%PYCHARM_VERSION_MAJOR%.%PYCHARM_VERSION_MINOR%
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo PYCHARM_VERSION_YEAR = %PYCHARM_VERSION_YEAR%
|
||||
echo PYCHARM_VERSION_MAJOR = %PYCHARM_VERSION_MAJOR%
|
||||
echo PYCHARM_HOME = %PYCHARM_HOME%
|
||||
echo PYCHARM_PROJ = %PYCHARM_PROJ%
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_PYCHARM_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
@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
|
||||
|
||||
:: Sets up extended environment for O3DE and DCCsi python
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_PY_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
CALL %~dp0\Env_Core.bat
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi Python Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Python Version
|
||||
:: Ideally these are set to match the O3DE python distribution
|
||||
:: <O3DE>\python\runtime
|
||||
IF "%DCCSI_PY_VERSION_MAJOR%"=="" (set DCCSI_PY_VERSION_MAJOR=3)
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
|
||||
:: PY version Major
|
||||
IF "%DCCSI_PY_VERSION_MINOR%"=="" (set DCCSI_PY_VERSION_MINOR=7)
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
|
||||
IF "%DCCSI_PY_VERSION_RELEASE%"=="" (set DCCSI_PY_VERSION_RELEASE=10)
|
||||
echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE%
|
||||
|
||||
:: shared location for 64bit python 3.7 DEV location
|
||||
:: this defines a DCCsi sandbox for lib site-packages by version
|
||||
:: <O3DE>\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\Lib
|
||||
set DCCSI_PYTHON_PATH=%DCCSIG_PATH%\3rdParty\Python
|
||||
echo DCCSI_PYTHON_PATH = %DCCSI_PYTHON_PATH%
|
||||
|
||||
:: add access to a Lib location that matches the py version (example: 3.7.x)
|
||||
:: switch this for other python versions like maya (2.7.x)
|
||||
IF "%DCCSI_PYTHON_LIB_PATH%"=="" (set DCCSI_PYTHON_LIB_PATH=%DCCSI_PYTHON_PATH%\Lib\%DCCSI_PY_VERSION_MAJOR%.x\%DCCSI_PY_VERSION_MAJOR%.%DCCSI_PY_VERSION_MINOR%.x\site-packages)
|
||||
echo DCCSI_PYTHON_LIB_PATH = %DCCSI_PYTHON_LIB_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_LIB_PATH%;%PATH%
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: location for O3DE python 3.7 location
|
||||
set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.cmd
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE%
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE=%DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
set DCCSI_PY_IDE_PACKAGES=%DCCSI_PY_IDE%\Lib\site-packages
|
||||
echo DCCSI_PY_IDE_PACKAGES = %DCCSI_PY_IDE_PACKAGES%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%DCCSI_PYTHON_INSTALL%;%DCCSI_PY_IDE%;%DCCSI_PY_IDE_PACKAGES%;%PATH%
|
||||
|
||||
:: add all python related paths to PYTHONPATH for package imports
|
||||
set PYTHONPATH=%DCCSIG_PATH%;%DCCSI_PYTHON_LIB_PATH%;%LY_BUILD_PATH%;%PYTHONPATH%
|
||||
echo PYTHONPATH = %PYTHONPATH%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_PY_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+22
-22
@@ -1,7 +1,4 @@
|
||||
:: Need to set up
|
||||
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
@@ -13,21 +10,29 @@ REM remove or modify any license notices. This file is distributed on an "AS IS"
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
:: Set up window
|
||||
TITLE Launcher Substance Builder
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
:: Skip initialization of QT if already completed
|
||||
IF "%DCCSI_ENV_QT_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
|
||||
:: These need to be ADDED to any env or launcher that is py3.7+ and needs PySide2 access
|
||||
:: This bootstraps LUMBERYARDS Qt binaraies and PySide2 packages (which likely won't work in other versions of python)
|
||||
:: If you set these in the env.bat it will cause some Qt apps like WingIDE from starting correctly
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi Qt/PySide2 Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: These need to be ADDED to any env or launcher that needs Qt/PySide2 access
|
||||
:: This bootstraps O3DE Qt binaraies and PySide2 packages (which likely won't work in other versions of python)
|
||||
:: If you set these in the env.bat it may cause some Qt apps like WingIDE from starting correctly (Wing is a Qt app)
|
||||
:: Those apps provide their own Qt bins and Pyside packages (Wing, Substance, Maya, etc.)
|
||||
:: set up Qt/Pyside paths
|
||||
|
||||
@@ -50,18 +55,13 @@ set LY_BIN_PATH=%LY_BUILD_PATH%\bin\profile
|
||||
echo LY_BIN_PATH = %LY_BIN_PATH%
|
||||
SET PATH=%LY_BIN_PATH%;%PATH%
|
||||
|
||||
:: if a local customEnv.bat exists, run it
|
||||
IF EXIST "%~dp0Env.bat" CALL %~dp0Env.bat
|
||||
::ENDLOCAL
|
||||
|
||||
echo ~ Launching DCCsi Substance Builder ...
|
||||
echo ________________________________________________________________
|
||||
echo.
|
||||
|
||||
call Launch_pyBASE "%DCCSIG_PATH%\SDK\substance\builder\sb_gui_main.py"
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_QT_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
|
||||
exit /b 0
|
||||
:END_OF_FILE
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_SUBSTANCE_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi Adobe Substance Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
: Substance Designer
|
||||
:: maya sdk path
|
||||
set DCCSI_SUBSTANCE_PATH=%DCCSI_SDK_PATH%\Substance
|
||||
echo DCCSI_SUBSTANCE_PATH = %DCCSI_SUBSTANCE_PATH%
|
||||
:: https://docs.substance3d.com/sddoc/project-preferences-107118596.html#ProjectPreferences-ConfigurationFile
|
||||
:: Path to .exe, "C:\Program Files\Allegorithmic\Substance Designer\Substance Designer.exe"
|
||||
set SUBSTANCE_PATH="%ProgramFiles%\Allegorithmic\Substance Designer"
|
||||
echo SUBSTANCE_PATH = %SUBSTANCE_PATH%
|
||||
|
||||
:: default config
|
||||
set SUBSTANCE_CFG_PATH=%LY_PROJECT_PATH%\DCCsi_default.sbscfg
|
||||
echo SUBSTANCE_CFG_PATH = %SUBSTANCE_CFG_PATH%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_SUBSTANCE_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_VSCODE_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: vscode has either a user install or a system install
|
||||
:: https://code.visualstudio.com/#alt-downloads
|
||||
:: that will change the paths assumed in this launcher (assume system install)
|
||||
:: vscode envars: https://code.visualstudio.com/docs/editor/variables-reference
|
||||
|
||||
SET VSCODE_WRKSPC=%DCCSIG_PATH%\Solutions\.vscode\dccsi.code-workspace
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi WingIDE Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo VSCODE_WRKSPC = %VSCODE_WRKSPC%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_VSCODE_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
@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
|
||||
|
||||
:: Sets up environment for Lumberyard DCC tools and code access
|
||||
|
||||
:: Skip initialization if already completed
|
||||
IF "%DCCSI_ENV_WINGIDE_INIT%"=="1" GOTO :END_OF_FILE
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: WingIDE version Major
|
||||
IF "%DCCSI_WING_VERSION_MAJOR%"=="" (set DCCSI_WING_VERSION_MAJOR=7)
|
||||
:: WingIDE version Major
|
||||
IF "%DCCSI_WING_VERSION_MINOR%"=="" (set DCCSI_WING_VERSION_MINOR=1)
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_IDE%\python.exe
|
||||
|
||||
:: put project env variables/paths here
|
||||
set WINGHOME=%PROGRAMFILES(X86)%\Wing Pro %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR%
|
||||
SET WING_PROJ=%DCCSIG_PATH%\Solutions\.wing\DCCsi_%DCCSI_WING_VERSION_MAJOR%x.wpr
|
||||
|
||||
::SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi WingIDE Environment ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo DCCSI_WING_VERSION_MAJOR = %DCCSI_WING_VERSION_MAJOR%
|
||||
echo DCCSI_WING_VERSION_MINOR = %DCCSI_WING_VERSION_MINOR%
|
||||
echo WINGHOME = %WINGHOME%
|
||||
echo WING_PROJ = %WING_PROJ%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%WINGHOME%;%PATH%
|
||||
|
||||
::ENDLOCAL
|
||||
|
||||
:: Set flag so we don't initialize dccsi environment twice
|
||||
SET DCCSI_ENV_WINGIDE_INIT=1
|
||||
GOTO END_OF_FILE
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+20
-13
@@ -1,7 +1,4 @@
|
||||
:: Need to set up
|
||||
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
@@ -18,25 +15,35 @@ REM
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface Cmd
|
||||
TITLE O3DE DCC Scripting Interface Cmd
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ O3DE DCCsi CMD ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
CALL %~dp0\Env.bat
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
CALL %~dp0\Env_Substance.bat
|
||||
CALL %~dp0\Env_WingIDE.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCC Scripting Interface CMD ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
:: Create command prompt with environment
|
||||
CALL %windir%\system32\cmd.exe
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
@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 window
|
||||
TITLE O3DE DCCsi GEM PyCharm
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=True)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
:: Default level logger will handle
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=10)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_PyCharm.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_MAYA%
|
||||
|
||||
:: add prefered python to the PATH
|
||||
set PATH=%DCCSI_PY_DEFAULT%;%PATH%
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Launching DCCsi Project in PyCharm %PYCHARM_VER_YEAR%.%PYCHARM_VER_MAJOR%.%PYCHARM_VER_MINOR% ...
|
||||
echo ~ MayaPy.exe (default python interpreter)
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo LY_DEV = %LY_DEV%
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_BASE=%DCCSI_PY_IDE%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_MAYA%
|
||||
echo DCCSI_PY_DEFAULT = %DCCSI_PY_DEFAULT%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
IF EXIST "%PYCHARM_HOME%\bin\pycharm64.exe" (
|
||||
start "" "%PYCHARM_HOME%\bin\pycharm64.exe" "%PYCHARM_PROJ%"
|
||||
) ELSE (
|
||||
Where pycharm64.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo pycharm64.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" pycharm64.exe "%PYCHARM_PROJ%"
|
||||
)
|
||||
)
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+49
-46
@@ -1,8 +1,4 @@
|
||||
:: Launches maya wityh a bunch of local hooks for Lumberyard
|
||||
:: ToDo: move all of this to a .json data driven boostrapping system
|
||||
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
@@ -14,60 +10,67 @@ REM remove or modify any license notices. This file is distributed on an "AS IS"
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
echo ________________________________
|
||||
echo ~ calling DccScriptingInterface_Env.bat
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
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%
|
||||
|
||||
:: Default Maya Version
|
||||
:: Default Maya and Python version
|
||||
set MAYA_VERSION=2020
|
||||
set DCCSI_PY_VERSION_MAJOR=2
|
||||
set DCCSI_PY_VERSION_MINOR=7
|
||||
set DCCSI_PY_VERSION_RELEASE=11
|
||||
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_MAYA%
|
||||
|
||||
:: Default BASE DCCsi python 3.7 location
|
||||
:: Can be overridden (example, Launch_mayaPy_2020.bat :: MayaPy.exe)
|
||||
set DCCSI_PY_DCCSI=%DCCSI_LAUNCHERS_PATH%Launch_mayaPy_2020.bat
|
||||
echo DCCSI_PY_DCCSI = %DCCSI_PY_DCCSI%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo Launching Maya %MAYA_VERSION% for O3DE DCCsi...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE%
|
||||
echo MAYA_LOCATION = %MAYA_LOCATION%
|
||||
echo MAYA_BIN_PATH = %MAYA_BIN_PATH%
|
||||
|
||||
:: if a local customEnv.bat exists, run it
|
||||
IF EXIST "%~dp0Env.bat" CALL %~dp0Env.bat
|
||||
|
||||
echo ________________________________
|
||||
echo Launching Maya %MAYA_VERSION% for LY DCCsi...
|
||||
|
||||
::set MAYA_PATH="D:\Program Files\Autodesk\Maya2019"
|
||||
echo MAYA_PATH = %MAYA_PATH%
|
||||
|
||||
:::: 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
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
:: Default to the right version of Maya if we can detect it... and launch
|
||||
IF EXIST "C:\Program Files\Autodesk\Maya%MAYA_VERSION%\bin\maya.exe" (
|
||||
start "" "C:\Program Files\Autodesk\Maya%MAYA_VERSION%\bin\maya.exe" %*
|
||||
IF EXIST "%MAYA_BIN_PATH%\maya.exe" (
|
||||
start "" "%MAYA_BIN_PATH%\maya.exe" %*
|
||||
) ELSE (
|
||||
Where maya.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo Maya.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" Maya.exe %*
|
||||
)
|
||||
Where maya.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo Maya.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" Maya.exe %*
|
||||
)
|
||||
)
|
||||
|
||||
:: Return to starting directory
|
||||
::ENDLOCAL
|
||||
|
||||
:: Restore previous directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
|
||||
exit /b 0
|
||||
:END_OF_FILE
|
||||
+32
-52
@@ -10,61 +10,41 @@ REM remove or modify any license notices. This file is distributed on an "AS IS"
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
:: Launches Wing IDE and the DccScriptingInterface Project Files
|
||||
|
||||
:: version Major
|
||||
SET PYCHARM_VERSION_YEAR=2020
|
||||
echo PYCHARM_VERSION_YEAR = %PYCHARM_VERSION_YEAR%
|
||||
|
||||
:: version Major
|
||||
SET PYCHARM_VERSION_MAJOR=2
|
||||
echo PYCHARM_VERSION_MAJOR = %PYCHARM_VERSION_MAJOR%
|
||||
|
||||
@echo off
|
||||
|
||||
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface GEM PyCharm CE %PYCHARM_VERSION_YEAR%.%PYCHARM_VERSION_MAJOR%.%PYCHARM_VERSION_MINOR%
|
||||
TITLE O3DE DCCsi GEM PyCharm
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up LY DCC SIG PyCharm Dev Env...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
SET ABS_PATH=%~dp0
|
||||
echo ~ Current Dir, %ABS_PATH%
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=True)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
:: Default level logger will handle
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=10)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL
|
||||
CALL %~dp0\Env.bat
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_PyCharm.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up Env for PyCharm CE %PYCHARM_VERSION_YEAR%.%PYCHARM_VERSION_MAJOR%.%PYCHARM_VERSION_MINOR%
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
|
||||
::"C:\Program Files\JetBrains\PyCharm 2019.1.3\bin"
|
||||
::"C:\Program Files\JetBrains\PyCharm Community Edition 2018.3.5\bin\pycharm64.exe"
|
||||
|
||||
:: put project env variables/paths here
|
||||
set PYCHARM_HOME=%PROGRAMFILES%\JetBrains\PyCharm %PYCHARM_VERSION_YEAR%.%PYCHARM_VERSION_MAJOR%
|
||||
echo PYCHARM_HOME = %PYCHARM_HOME%
|
||||
|
||||
SET PYCHARM_PROJ=%DCCSIG_PATH%\Solutions
|
||||
echo PYCHARM_PROJ = %PYCHARM_PROJ%
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
@@ -74,15 +54,15 @@ echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
IF EXIST "%PYCHARM_HOME%\bin\pycharm64.exe" (
|
||||
start "" "%PYCHARM_HOME%\bin\pycharm64.exe" "%PYCHARM_PROJ%"
|
||||
start "" "%PYCHARM_HOME%\bin\pycharm64.exe" "%PYCHARM_PROJ%"
|
||||
) ELSE (
|
||||
Where pycharm64.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo pycharm64.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" pycharm64.exe "%PYCHARM_PROJ%"
|
||||
)
|
||||
Where pycharm64.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo pycharm64.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" pycharm64.exe "%PYCHARM_PROJ%"
|
||||
)
|
||||
)
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
+12
-21
@@ -16,42 +16,33 @@ REM
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface Cmd
|
||||
TITLE O3DE DCC Scripting Interface Py Min CMD
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
SETLOCAL enableDelayedExpansion
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..\..\..\)
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
|
||||
:: 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%
|
||||
|
||||
set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCC Scripting Interface, Py Min Env CMD ...
|
||||
echo ~ O3DE DCC Scripting Interface, Py Min Env CMD ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
:: Create command prompt with environment
|
||||
CALL %windir%\system32\cmd.exe
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
@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 LY_DEV environment
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCC Scripting Interface Py Qt Cmd
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ LY DCC Scripting Interface, Py Qt Env CMD ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
:: Create command prompt with environment
|
||||
CALL %windir%\system32\cmd.exe
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
@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
|
||||
|
||||
:: Launches VScode and the DccScriptingInterface Project Files
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCC Scripting Interface VScode
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Setting up O3DE DCCsi VScode Dev Env...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=True)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
:: Default level logger will handle
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=10)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
:: Initialize envCALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
CALL %~dp0\Env_Substance.bat
|
||||
CALL %~dp0\Env_VScode.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Launching DCCsi Project in VScode
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo LY_DEV = %LY_DEV%
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_BASE=%DCCSI_PY_IDE%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE%
|
||||
echo DCCSI_PY_DEFAULT = %DCCSI_PY_DEFAULT%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
|
||||
REM "C:\Program Files\Microsoft VS Code\Code.exe"
|
||||
|
||||
IF EXIST "%ProgramFiles%\Microsoft VS Code\Code.exe" (
|
||||
start "" "%ProgramFiles%\Microsoft VS Code\Code.exe" "%VSCODE_WRKSPC%"
|
||||
) ELSE (
|
||||
Where Code.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo Code.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" code.exe "%VSCODE_WRKSPC%"
|
||||
)
|
||||
)
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+40
-46
@@ -14,40 +14,15 @@ REM
|
||||
:: Launches Wing IDE and the DccScriptingInterface Project Files
|
||||
|
||||
:: Set up window
|
||||
TITLE Lumberyard DCC Scripting Interface GEM WingIDE 7x
|
||||
TITLE O3DE DCCsi Launch WingIDE 7x
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
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 ABS_PATH = %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%
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True)
|
||||
@@ -68,40 +43,59 @@ echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=10)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
CALL %~dp0\Env.bat
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
CALL %~dp0\Env_Substance.bat
|
||||
CALL %~dp0\Env_WingIDE.bat
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ WingIDE Version %WING_VERSION_MAJOR%.%WING_VERSION_MINOR%
|
||||
echo ~ WingIDE Version %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR%
|
||||
echo ~ Launching O3DE %LY_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
SET WING_PROJ=%DCCSIG_PATH%\Solutions\.wing\DCCsi_%WING_VERSION_MAJOR%x.wpr
|
||||
echo WING_PROJ = %WING_PROJ%
|
||||
echo LY_DEV = %LY_DEV%
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_BASE=%DCCSI_PY_IDE%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE%
|
||||
echo DCCSI_PY_DEFAULT = %DCCSI_PY_DEFAULT%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Launching %LY_PROJECT% project in WingIDE %WING_VERSION_MAJOR%.%WING_VERSION_MINOR% ...
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
IF EXIST "%WINGHOME%\bin\wing.exe" (
|
||||
start "" "%WINGHOME%\bin\wing.exe" "%WING_PROJ%"
|
||||
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%"
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
@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
|
||||
|
||||
:: Store current directory and change to environment directory so script works in any path.
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: 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%
|
||||
|
||||
:: Default Maya Version
|
||||
set MAYA_VERSION=2019
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env.bat
|
||||
|
||||
echo ~ Launching LY Dc cScripting Interfacw MayaPy (%MAYA_VERSION%) ...
|
||||
echo ________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Start Maya
|
||||
:: Default to the right version of Maya if we can detect it.
|
||||
Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11
|
||||
|
||||
IF EXIST "%DCCSI_PY_MAYA%" (
|
||||
CALL "%DCCSI_PY_MAYA%" %*
|
||||
) ELSE (
|
||||
Where mayapy.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo mayapy.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" mayapy.exe %*
|
||||
)
|
||||
)
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Restore previous directory
|
||||
POPD
|
||||
+46
-30
@@ -1,5 +1,4 @@
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
@@ -11,50 +10,67 @@ REM remove or modify any license notices. This file is distributed on an "AS IS"
|
||||
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
REM
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCCsi Launch MayaPy
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
:: Store current directory and change to environment directory so script works in any path.
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: 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%
|
||||
|
||||
:: Default Maya Version
|
||||
:: Default Maya and Python version
|
||||
set MAYA_VERSION=2020
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
set DCCSI_PY_VERSION_MAJOR=2
|
||||
set DCCSI_PY_VERSION_MINOR=7
|
||||
set DCCSI_PY_VERSION_RELEASE=11
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env.bat
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
echo ~ Launching LY Dc cScripting Interfacw MayaPy (%MAYA_VERSION%) ...
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_MAYA%
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ Launching O3DE DCCsi MayaPy (%MAYA_VERSION%) ...
|
||||
echo ________________________________________________________________
|
||||
echo.
|
||||
|
||||
:: Start Maya
|
||||
:: Default to the right version of Maya if we can detect it.
|
||||
Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11
|
||||
echo MAYA_VERSION = %MAYA_VERSION%
|
||||
echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR%
|
||||
echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR%
|
||||
echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE%
|
||||
echo MAYA_LOCATION = %MAYA_LOCATION%
|
||||
echo MAYA_BIN_PATH = %MAYA_BIN_PATH%
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
:: Default to the right version of Maya if we can detect it... and launch
|
||||
IF EXIST "%DCCSI_PY_MAYA%" (
|
||||
CALL "%DCCSI_PY_MAYA%" %*
|
||||
start "" "%DCCSI_PY_MAYA%" %*
|
||||
) ELSE (
|
||||
Where mayapy.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo mayapy.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" mayapy.exe %*
|
||||
)
|
||||
Where maya.exe 2> NUL
|
||||
IF ERRORLEVEL 1 (
|
||||
echo Maya.exe could not be found
|
||||
pause
|
||||
) ELSE (
|
||||
start "" Maya.exe %*
|
||||
)
|
||||
)
|
||||
|
||||
ENDLOCAL
|
||||
|
||||
:: Restore previous directory
|
||||
POPD
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
@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
|
||||
|
||||
:: Launches Wing IDE and the DccScriptingInterface Project Files
|
||||
|
||||
:: Set up window
|
||||
TITLE O3DE DCCsi Launch WingIDE 7x
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Constant Vars (Global)
|
||||
:: global debug (propogates)
|
||||
IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True)
|
||||
echo DCCSI_GDEBUG = %DCCSI_GDEBUG%
|
||||
:: initiates debugger connection
|
||||
IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=True)
|
||||
echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE%
|
||||
:: sets debugger, options: WING, PYCHARM
|
||||
IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING)
|
||||
echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER%
|
||||
:: Default level logger will handle
|
||||
:: CRITICAL:50
|
||||
:: ERROR:40
|
||||
:: WARNING:30
|
||||
:: INFO:20
|
||||
:: DEBUG:10
|
||||
:: NOTSET:0
|
||||
IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=10)
|
||||
echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL%
|
||||
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_WingIDE.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
echo.
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
echo ~ WingIDE Version %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR%
|
||||
echo ~ Launching O3DE %LY_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ...
|
||||
echo ~ MayaPy.exe (default python interpreter)
|
||||
echo _____________________________________________________________________
|
||||
echo.
|
||||
|
||||
echo LY_DEV = %LY_DEV%
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL%
|
||||
|
||||
:: Wing and other IDEs probably prefer access directly to the python.exe
|
||||
set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python
|
||||
echo DCCSI_PY_IDE = %DCCSI_PY_IDE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_BASE=%DCCSI_PY_IDE%\python.exe
|
||||
echo DCCSI_PY_BASE = %DCCSI_PY_BASE%
|
||||
|
||||
:: ide and debugger plug
|
||||
set DCCSI_PY_DEFAULT=%DCCSI_PY_MAYA%
|
||||
echo DCCSI_PY_DEFAULT = %DCCSI_PY_DEFAULT%
|
||||
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
echo.
|
||||
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
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%"
|
||||
)
|
||||
)
|
||||
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
:END_OF_FILE
|
||||
+15
-28
@@ -1,5 +1,4 @@
|
||||
@echo off
|
||||
|
||||
REM
|
||||
REM All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
REM its licensors.
|
||||
@@ -16,7 +15,7 @@ REM
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE DCCsi (miniconda3)
|
||||
TITLE O3DE DCCsi Launch pyBase
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
@@ -24,36 +23,24 @@ COLOR 8E
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
:: SETLOCAL enableDelayedExpansion
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
CALL %~dp0\Env.bat
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
:: These need to be ADDED to any env or launcher that is py3.7+ and needs PySide2 access
|
||||
:: This bootstraps LUMBERYARDS Qt binaraies and PySide2 packages (which likely won't work in other versions of python)
|
||||
:: If you set these in the env.bat it will cause some Qt apps like WingIDE from starting correctly
|
||||
:: Those apps provide their own Qt bins and Pyside packages (Wing, Substance, Maya, etc.)
|
||||
:: set up Qt/Pyside paths
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
:: set up PySide2/Shiboken
|
||||
set QTFORPYTHON_PATH=%LY_DEV%\Gems\QtForPython\3rdParty\pyside2\windows\release
|
||||
echo QTFORPYTHON_PATH = %QTFORPYTHON_PATH%
|
||||
echo Starting: %DCCSI_PY_BASE%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%QTFORPYTHON_PATH%;%PATH%
|
||||
SET PYTHONPATH=%QTFORPYTHON_PATH%;%PYTHONPATH%
|
||||
call %DCCSI_PY_BASE% %*
|
||||
|
||||
set QT_PLUGIN_PATH=%LY_BUILD_PATH%\bin\profile\EditorPlugins
|
||||
echo QT_PLUGIN_PATH = %QT_PLUGIN_PATH%
|
||||
ENDLOCAL
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%QT_PLUGIN_PATH%;%PATH%
|
||||
SET PYTHONPATH=%QT_PLUGIN_PATH%;%PYTHONPATH%
|
||||
:: Return to starting directory
|
||||
POPD
|
||||
|
||||
set LY_BIN_PATH=%LY_BUILD_PATH%\bin\profile
|
||||
echo LY_BIN_PATH = %LY_BIN_PATH%
|
||||
SET PATH=%LY_BIN_PATH%;%PATH%
|
||||
|
||||
echo Starting: %DCCSI_PYTHON_INSTALL%\python.exe
|
||||
|
||||
call %DCCSI_PYTHON_INSTALL%\python.exe %*
|
||||
:END_OF_FILE
|
||||
+11
-27
@@ -16,7 +16,7 @@ REM
|
||||
:: Puts you in the CMD within the dev environment
|
||||
|
||||
:: Set up window
|
||||
TITLE DCCsi (miniconda3)
|
||||
TITLE O3DE DCCsi pyBase CMD
|
||||
:: Use obvious color to prevent confusion (Grey with Yellow Text)
|
||||
COLOR 8E
|
||||
|
||||
@@ -24,35 +24,19 @@ COLOR 8E
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: Keep changes local
|
||||
:: SETLOCAL enableDelayedExpansion
|
||||
SETLOCAL ENABLEDELAYEDEXPANSION
|
||||
|
||||
CALL %~dp0\Env.bat
|
||||
:: Initialize env
|
||||
CALL %~dp0\Env_Core.bat
|
||||
CALL %~dp0\Env_Python.bat
|
||||
CALL %~dp0\Env_Qt.bat
|
||||
CALL %~dp0\Env_Maya.bat
|
||||
|
||||
:: These need to be ADDED to any env or launcher that is py3.7+ and needs PySide2 access
|
||||
:: This bootstraps LUMBERYARDS Qt binaraies and PySide2 packages (which likely won't work in other versions of python)
|
||||
:: If you set these in the env.bat it will cause some Qt apps like WingIDE from starting correctly
|
||||
:: Those apps provide their own Qt bins and Pyside packages (Wing, Substance, Maya, etc.)
|
||||
:: set up Qt/Pyside paths
|
||||
:: if the user has set up a custom env call it
|
||||
IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat
|
||||
|
||||
:: set up PySide2/Shiboken
|
||||
set QTFORPYTHON_PATH=%LY_DEV%\Gems\QtForPython\3rdParty\pyside2\windows\release
|
||||
echo QTFORPYTHON_PATH = %QTFORPYTHON_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%QTFORPYTHON_PATH%;%PATH%
|
||||
SET PYTHONPATH=%QTFORPYTHON_PATH%;%PYTHONPATH%
|
||||
|
||||
set QT_PLUGIN_PATH=%LY_BUILD_PATH%\bin\profile\EditorPlugins
|
||||
echo QT_PLUGIN_PATH = %QT_PLUGIN_PATH%
|
||||
|
||||
:: add to the PATH
|
||||
SET PATH=%QT_PLUGIN_PATH%;%PATH%
|
||||
SET PYTHONPATH=%QT_PLUGIN_PATH%;%PYTHONPATH%
|
||||
|
||||
set LY_BIN_PATH=%LY_BUILD_PATH%\bin\profile
|
||||
echo LY_BIN_PATH = %LY_BIN_PATH%
|
||||
SET PATH=%LY_BIN_PATH%;%PATH%
|
||||
:: Change to root dir
|
||||
CD /D %LY_PROJECT_PATH%
|
||||
|
||||
:: Create command prompt with environment
|
||||
CALL %windir%\system32\cmd.exe
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
@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
|
||||
|
||||
:: Store current dir
|
||||
%~d0
|
||||
cd %~dp0
|
||||
PUSHD %~dp0
|
||||
|
||||
:: This maps up to the \Dev folder
|
||||
set LY_DEV=..\..\..\..\..\..
|
||||
|
||||
:: shared location for default O3DE python location
|
||||
set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python
|
||||
|
||||
set PY_SITE=%DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev1-windows\python\Lib\site-packages
|
||||
|
||||
set PACKAGE_LOC=C:\Depot\3rdParty\packages\openimageio-2.1.16.0-rev1-windows\OpenImageIO\2.1.16.0\win_x64\bin
|
||||
|
||||
copy %PACKAGE_LOC%\OpenImageIO.pyd %PY_SITE%\OpenImageIO.pyd
|
||||
+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": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,3 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# !/usr/bin/python
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
@@ -9,6 +11,3 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Tests/DCCScriptingInterfaceTest.cpp
|
||||
)
|
||||
+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
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user