@ -25,213 +25,319 @@
# include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
# include <SceneAPI/SceneData/Rules/ScriptProcessorRule.h>
# include <SceneAPI/SceneCore/Utilities/Reporting.h>
# include <SceneAPI/SceneCore/Events/ExportProductList.h>
namespace AZ
namespace AZ : : SceneAPI : : Behaviors
{
namespace SceneAPI
class EditorPythonConsoleNotificationHandler final
: protected AzToolsFramework : : EditorPythonConsoleNotificationBus : : Handler
{
namespace Behaviors
public :
EditorPythonConsoleNotificationHandler ( )
{
class EditorPythonConsoleNotificationHandler final
: protected AzToolsFramework : : EditorPythonConsoleNotificationBus : : Handler
{
public :
EditorPythonConsoleNotificationHandler ( )
{
BusConnect ( ) ;
}
BusConnect ( ) ;
}
~ EditorPythonConsoleNotificationHandler ( )
{
BusDisconnect ( ) ;
}
~ EditorPythonConsoleNotificationHandler ( )
{
BusDisconnect ( ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorPythonConsoleNotifications
void OnTraceMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( LogWindow , " %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
////////////////////////////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorPythonConsoleNotifications
void OnTraceMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( LogWindow , " %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
void OnErrorMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( ErrorWindow , " [ERROR] %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
void OnErrorMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( ErrorWindow , " [ERROR] %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
void OnExceptionMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( ErrorWindow , " [EXCEPTION] %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
} ;
void OnExceptionMessage ( [[maybe_unused]] AZStd : : string_view message ) override
{
using namespace AZ : : SceneAPI : : Utilities ;
AZ_TracePrintf ( ErrorWindow , " [EXCEPTION] %.*s \n " , AZ_STRING_ARG ( message ) ) ;
}
} ;
// a event bus to signal during scene building
struct ScriptBuildingNotifications
: public AZ : : EBusTraits
{
virtual AZStd : : string OnUpdateManifest ( Containers : : Scene & scene ) = 0 ;
} ;
using ScriptBuildingNotificationBus = AZ : : EBus < ScriptBuildingNotifications > ;
using ExportProductList = AZ : : SceneAPI : : Events : : ExportProductList ;
// a event bus to signal during scene building
struct ScriptBuildingNotifications
: public AZ : : EBusTraits
{
virtual AZStd : : string OnUpdateManifest ( Containers : : Scene & scene ) = 0 ;
virtual ExportProductList OnPrepareForExport (
const Containers : : Scene & scene ,
AZStd : : string_view outputDirectory ,
AZStd : : string_view platformIdentifier ,
const ExportProductList & productList ) = 0 ;
} ;
using ScriptBuildingNotificationBus = AZ : : EBus < ScriptBuildingNotifications > ;
// a back end to handle scene builder events for a script
struct ScriptBuildingNotificationBusHandler final
: public ScriptBuildingNotificationBus : : Handler
, public AZ : : BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER (
ScriptBuildingNotificationBusHandler ,
" {DF2B51DE-A4D0-4139-B5D0-DF185832380D} " ,
AZ : : SystemAllocator ,
OnUpdateManifest ,
OnPrepareForExport ) ;
// a back end to handle scene builder events for a script
struct ScriptBuildingNotificationBusHandler final
: public ScriptBuildingNotificationBus : : Handler
, public AZ : : BehaviorEBusHandler
virtual ~ ScriptBuildingNotificationBusHandler ( ) = default ;
AZStd : : string OnUpdateManifest ( Containers : : Scene & scene ) override
{
AZStd : : string result ;
CallResult ( result , FN_OnUpdateManifest , scene ) ;
return result ;
}
ExportProductList OnPrepareForExport (
const Containers : : Scene & scene ,
AZStd : : string_view outputDirectory ,
AZStd : : string_view platformIdentifier ,
const ExportProductList & productList ) override
{
ExportProductList result ;
CallResult ( result , FN_OnPrepareForExport , scene , outputDirectory , platformIdentifier , productList ) ;
return result ;
}
static void Reflect ( AZ : : ReflectContext * context )
{
if ( AZ : : BehaviorContext * behaviorContext = azrtti_cast < AZ : : BehaviorContext * > ( context ) )
{
AZ_EBUS_BEHAVIOR_BINDER (
ScriptBuildingNotificationBusHandler ,
" {DF2B51DE-A4D0-4139-B5D0-DF185832380D} " ,
AZ : : SystemAllocator ,
OnUpdateManifest ) ;
behaviorContext - > EBus < ScriptBuildingNotificationBus > ( " ScriptBuildingNotificationBus " )
- > Attribute ( AZ : : Script : : Attributes : : Scope , AZ : : Script : : Attributes : : ScopeFlags : : Automation )
- > Attribute ( AZ : : Script : : Attributes : : Module , " scene " )
- > Handler < ScriptBuildingNotificationBusHandler > ( )
- > Event ( " OnUpdateManifest " , & ScriptBuildingNotificationBus : : Events : : OnUpdateManifest )
- > Event ( " OnPrepareForExport " , & ScriptBuildingNotificationBus : : Events : : OnPrepareForExport ) ;
}
}
} ;
virtual ~ ScriptBuildingNotificationBusHandler ( ) = default ;
struct ScriptProcessorRuleBehavior : : ExportEventHandler final
: public AZ : : SceneAPI : : SceneCore : : ExportingComponent
{
using PreExportEventContextFunction = AZStd : : function < bool ( Events : : PreExportEventContext & ) > ;
PreExportEventContextFunction m_preExportEventContextFunction ;
AZStd : : string OnUpdateManifest ( Containers : : Scene & scene ) override
{
AZStd : : string result ;
CallResult ( result , FN_OnUpdateManifest , scene ) ;
return result ;
}
ExportEventHandler ( PreExportEventContextFunction preExportEventContextFunction )
: m_preExportEventContextFunction ( preExportEventContextFunction )
{
BindToCall ( & ExportEventHandler : : PrepareForExport ) ;
AZ : : SceneAPI : : SceneCore : : ExportingComponent : : Activate ( ) ;
}
static void Reflect ( AZ : : ReflectContext * context )
{
if ( AZ : : BehaviorContext * behaviorContext = azrtti_cast < AZ : : BehaviorContext * > ( context ) )
{
behaviorContext - > EBus < ScriptBuildingNotificationBus > ( " ScriptBuildingNotificationBus " )
- > Attribute ( AZ : : Script : : Attributes : : Scope , AZ : : Script : : Attributes : : ScopeFlags : : Automation )
- > Attribute ( AZ : : Script : : Attributes : : Module , " scene " )
- > Handler < ScriptBuildingNotificationBusHandler > ( )
- > Event ( " OnUpdateManifest " , & ScriptBuildingNotificationBus : : Events : : OnUpdateManifest ) ;
}
}
} ;
~ ExportEventHandler ( )
{
AZ : : SceneAPI : : SceneCore : : ExportingComponent : : Deactivate ( ) ;
}
// this allows a Python script to add product assets on "scene export"
Events : : ProcessingResult PrepareForExport ( Events : : PreExportEventContext & context )
{
return m_preExportEventContextFunction ( context ) ? Events : : ProcessingResult : : Success : Events : : ProcessingResult : : Failure ;
}
} ;
void ScriptProcessorRuleBehavior : : Activate ( )
{
Events : : AssetImportRequestBus : : Handler : : BusConnect ( ) ;
m_exportEventHandler = AZStd : : make_shared < ExportEventHandler > ( [ this ] ( Events : : PreExportEventContext & context )
{
return this - > DoPrepareForExport ( context ) ;
} ) ;
}
void ScriptProcessorRuleBehavior : : Deactivate ( )
{
m_exportEventHandler . reset ( ) ;
Events : : AssetImportRequestBus : : Handler : : BusDisconnect ( ) ;
UnloadPython ( ) ;
}
bool ScriptProcessorRuleBehavior : : LoadPython ( const AZ : : SceneAPI : : Containers : : Scene & scene )
{
if ( m_editorPythonEventsInterface & & ! m_scriptFilename . empty ( ) )
{
return true ;
}
// get project folder
auto settingsRegistry = AZ : : SettingsRegistry : : Get ( ) ;
AZ : : IO : : FixedMaxPath projectPath ;
if ( ! settingsRegistry - > Get ( projectPath . Native ( ) , AZ : : SettingsRegistryMergeUtils : : FilePathKey_ProjectPath ) )
{
return false ;
}
void ScriptProcessorRuleBehavior : : Activate ( )
const AZ : : SceneAPI : : Containers : : SceneManifest & manifest = scene . GetManifest ( ) ;
auto view = Containers : : MakeDerivedFilterView < DataTypes : : IScriptProcessorRule > ( manifest . GetValueStorage ( ) ) ;
for ( const auto & scriptItem : view )
{
AZ : : IO : : FixedMaxPath scriptFilename ( scriptItem . GetScriptFilename ( ) ) ;
if ( scriptFilename . empty ( ) )
{
Events : : AssetImportRequestBus : : Handler : : BusConnect ( ) ;
AZ_Warning ( " scene " , false , " Skipping an empty script filename in (%s) " , scene . GetManifestFilename ( ) . c_str ( ) ) ;
continue ;
}
void ScriptProcessorRuleBehavior : : Deactivate ( )
// check for file exist via absolute path
if ( ! IO : : FileIOBase : : GetInstance ( ) - > Exists ( scriptFilename . c_str ( ) ) )
{
Events : : AssetImportRequestBus : : Handler : : BusDisconnect ( ) ;
if ( m_editorPythonEventsInterface )
// check for script in the project folder
AZ : : IO : : FixedMaxPath projectScriptPath = projectPath / scriptFilename ;
if ( ! IO : : FileIOBase : : GetInstance ( ) - > Exists ( projectScriptPath . c_str ( ) ) )
{
const bool silenceWarnings = true ;
m_editorPythonEventsInterface - > StopPython ( silenceWarnings ) ;
m_editorPythonEventsInterface = nullptr ;
AZ_Warning ( " scene " , false , " Skipping a missing script (%s) in manifest file (%s) " ,
scriptFilename . c_str ( ) ,
scene . GetManifestFilename ( ) . c_str ( ) ) ;
continue ;
}
scriptFilename = AZStd : : move ( projectScriptPath ) ;
}
void ScriptProcessorRuleBehavior : : Reflect ( ReflectContext * context )
// lazy load the Python interface
auto editorPythonEventsInterface = AZ : : Interface < AzToolsFramework : : EditorPythonEventsInterface > : : Get ( ) ;
if ( editorPythonEventsInterface - > IsPythonActive ( ) = = false )
{
ScriptBuildingNotificationBusHandler : : Reflect ( context ) ;
SerializeContext * serializeContext = azrtti_cast < SerializeContext * > ( context ) ;
if ( serializeContext )
const bool silenceWarnings = false ;
if ( editorPythonEventsInterface - > StartPython ( silenceWarnings ) = = false )
{
serializeContext - > Class < ScriptProcessorRuleBehavior , BehaviorComponent > ( ) - > Version ( 1 ) ;
editorPythonEventsInterface = nullptr ;
}
}
Events : : ProcessingResult ScriptProcessorRuleBehavior : : UpdateManifest (
Containers : : Scene & scene ,
Events : : AssetImportRequest : : ManifestAction action ,
[[maybe_unused]] Events : : AssetImportRequest : : RequestingApplication requester )
// both Python and the script need to be ready
if ( editorPythonEventsInterface = = nullptr | | scriptFilename . empty ( ) )
{
using namespace AzToolsFramework ;
AZ_Warning ( " scene " , false , " The scene manifest (%s) attempted to use script(%s) but Python is not enabled; "
" please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project. " ,
scene . GetManifestFilename ( ) . c_str ( ) , scriptFilename . c_str ( ) ) ;
if ( action ! = ManifestAction : : Update )
{
return Events : : ProcessingResult : : Ignored ;
}
return false ;
}
// get project folder
auto settingsRegistry = AZ : : SettingsRegistry : : Get ( ) ;
AZ : : IO : : FixedMaxPath projectPath ;
if ( ! settingsRegistry - > Get ( projectPath . Native ( ) , AZ : : SettingsRegistryMergeUtils : : FilePathKey_ProjectPath ) )
{
return Events : : ProcessingResult : : Ignored ;
}
m_editorPythonEventsInterface = editorPythonEventsInterface ;
m_scriptFilename = scriptFilename . c_str ( ) ;
return true ;
}
return false ;
}
void ScriptProcessorRuleBehavior : : UnloadPython ( )
{
if ( m_editorPythonEventsInterface )
{
const bool silenceWarnings = true ;
m_editorPythonEventsInterface - > StopPython ( silenceWarnings ) ;
m_editorPythonEventsInterface = nullptr ;
}
}
bool ScriptProcessorRuleBehavior : : DoPrepareForExport ( Events : : PreExportEventContext & context )
{
using namespace AzToolsFramework ;
auto executeCallback = [ this , & context ] ( )
{
// set up script's hook callback
EditorPythonRunnerRequestBus : : Broadcast ( & EditorPythonRunnerRequestBus : : Events : : ExecuteByFilename ,
m_scriptFilename . c_str ( ) ) ;
// call script's callback to allow extra products
ExportProductList extraProducts ;
ScriptBuildingNotificationBus : : BroadcastResult ( extraProducts , & ScriptBuildingNotificationBus : : Events : : OnPrepareForExport ,
context . GetScene ( ) ,
context . GetOutputDirectory ( ) ,
context . GetPlatformIdentifier ( ) ,
context . GetProductList ( )
) ;
auto & sceneManifest = scene . GetManifest ( ) ;
auto view = Containers : : MakeDerivedFilterView < DataTypes : : IScriptProcessorRule > ( sceneManifest . GetValueStorage ( ) ) ;
for ( const auto & scriptItem : view )
// add new products
for ( const auto & product : extraProducts . GetProducts ( ) )
{
context . GetProductList ( ) . AddProduct (
product . m_filename ,
product . m_id ,
product . m_assetType ,
product . m_lod ,
product . m_subId ,
product . m_dependencyFlags ) ;
}
} ;
if ( LoadPython ( context . GetScene ( ) ) )
{
EditorPythonConsoleNotificationHandler logger ;
m_editorPythonEventsInterface - > ExecuteWithLock ( executeCallback ) ;
}
return true ;
}
void ScriptProcessorRuleBehavior : : Reflect ( ReflectContext * context )
{
ScriptBuildingNotificationBusHandler : : Reflect ( context ) ;
SerializeContext * serializeContext = azrtti_cast < SerializeContext * > ( context ) ;
if ( serializeContext )
{
serializeContext - > Class < ScriptProcessorRuleBehavior , BehaviorComponent > ( ) - > Version ( 1 ) ;
}
}
Events : : ProcessingResult ScriptProcessorRuleBehavior : : UpdateManifest (
Containers : : Scene & scene ,
Events : : AssetImportRequest : : ManifestAction action ,
[[maybe_unused]] Events : : AssetImportRequest : : RequestingApplication requester )
{
using namespace AzToolsFramework ;
if ( action ! = ManifestAction : : Update )
{
return Events : : ProcessingResult : : Ignored ;
}
if ( LoadPython ( scene ) )
{
AZStd : : string manifestUpdate ;
auto executeCallback = [ this , & scene , & manifestUpdate ] ( )
{
EditorPythonRunnerRequestBus : : Broadcast ( & EditorPythonRunnerRequestBus : : Events : : ExecuteByFilename ,
m_scriptFilename . c_str ( ) ) ;
ScriptBuildingNotificationBus : : BroadcastResult ( manifestUpdate , & ScriptBuildingNotificationBus : : Events : : OnUpdateManifest ,
scene ) ;
} ;
EditorPythonConsoleNotificationHandler logger ;
m_editorPythonEventsInterface - > ExecuteWithLock ( executeCallback ) ;
// attempt to load the manifest string back to a JSON-scene-manifest
auto sceneManifestLoader = AZStd : : make_unique < AZ : : SceneAPI : : Containers : : SceneManifest > ( ) ;
auto loadOutcome = sceneManifestLoader - > LoadFromString ( manifestUpdate ) ;
if ( loadOutcome . IsSuccess ( ) )
{
scene . GetManifest ( ) . Clear ( ) ;
for ( size_t entryIndex = 0 ; entryIndex < sceneManifestLoader - > GetEntryCount ( ) ; + + entryIndex )
{
AZ : : IO : : FixedMaxPath scriptFilename ( scriptItem . GetScriptFilename ( ) ) ;
if ( scriptFilename . empty ( ) )
{
AZ_Warning ( " scene " , false , " Skipping an empty script filename in (%s) " , scene . GetManifestFilename ( ) . c_str ( ) ) ;
continue ;
}
// check for file exist via absolute path
if ( ! IO : : FileIOBase : : GetInstance ( ) - > Exists ( scriptFilename . c_str ( ) ) )
{
// check for script in the project folder
AZ : : IO : : FixedMaxPath projectScriptPath = projectPath / scriptFilename ;
if ( ! IO : : FileIOBase : : GetInstance ( ) - > Exists ( projectScriptPath . c_str ( ) ) )
{
AZ_Warning ( " scene " , false , " Skipping a missing script (%s) in manifest file (%s) " ,
scriptFilename . c_str ( ) ,
scene . GetManifestFilename ( ) . c_str ( ) ) ;
continue ;
}
scriptFilename = AZStd : : move ( projectScriptPath ) ;
}
// lazy load the Python interface
if ( ! m_editorPythonEventsInterface )
{
m_editorPythonEventsInterface = AZ : : Interface < AzToolsFramework : : EditorPythonEventsInterface > : : Get ( ) ;
const bool silenceWarnings = true ;
m_editorPythonEventsInterface - > StartPython ( silenceWarnings ) ;
}
if ( ! m_editorPythonEventsInterface & & ! scriptFilename . empty ( ) )
{
AZ_Warning ( " scene " , false ,
" The scene manifest (%s) attempted to use script(%s) but Python is not enabled; "
" please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project. " ,
scene . GetManifestFilename ( ) . c_str ( ) , scriptFilename . c_str ( ) ) ;
return Events : : ProcessingResult : : Ignored ;
}
AZStd : : string manifestUpdate ;
auto executeCallback = [ & scene , & scriptFilename , & manifestUpdate ] ( )
{
EditorPythonRunnerRequestBus : : Broadcast (
& EditorPythonRunnerRequestBus : : Events : : ExecuteByFilename ,
scriptFilename . c_str ( ) ) ;
ScriptBuildingNotificationBus : : BroadcastResult (
manifestUpdate ,
& ScriptBuildingNotificationBus : : Events : : OnUpdateManifest ,
scene ) ;
} ;
EditorPythonConsoleNotificationHandler logger ;
m_editorPythonEventsInterface - > ExecuteWithLock ( executeCallback ) ;
// attempt to load the manifest string back to a JSON-scene-manifest
auto sceneManifestLoader = AZStd : : make_unique < AZ : : SceneAPI : : Containers : : SceneManifest > ( ) ;
auto loadOutcome = sceneManifestLoader - > LoadFromString ( manifestUpdate ) ;
if ( loadOutcome . IsSuccess ( ) )
{
sceneManifest . Clear ( ) ;
for ( size_t entryIndex = 0 ; entryIndex < sceneManifestLoader - > GetEntryCount ( ) ; + + entryIndex )
{
sceneManifest . AddEntry ( sceneManifestLoader - > GetValue ( entryIndex ) ) ;
}
return Events : : ProcessingResult : : Success ;
}
scene . GetManifest ( ) . AddEntry ( sceneManifestLoader - > GetValue ( entryIndex ) ) ;
}
return Events : : ProcessingResult : : Ignored ;
return Events : : ProcessingResult : : Success ;
}
}
return Events : : ProcessingResult : : Ignored ;
}
} // namespace Behaviors
} // namespace SceneAPI
} // namespace AZ