{LYN-4514} Re-factored Blast gem's python asset builder (#2143)

* {LYN-4514} Re-factored Blast gem's python asset builder

* Re-factored Blast gem's python asset builder so that the .blast file creates an asset info scene manifest
* Added a python script to act as a SceneAPI script + Python Asset Builder (blast_asset_builder.py)
* renaming types from "Slice" to "Chunk"

Tests: Re-enabled Gems/Blast/Code/Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* renaming from Slice to Chunks

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* updated the Copyright


Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* Removing StdAfx.h includes

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* null check added m_blastChunksAsset.Get()
removing 'slice' like EditorBlastSliceAssetHandlerTestFixture
delete old asset builder blast file


Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* adding source deps for FBX -> BLAST file

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* removing slice name

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>

* Adding error message and updates from PR

Signed-off-by: Jackson <23512001+jackalbe@users.noreply.github.com>
This commit is contained in:
jackalbe
2021-08-03 09:21:36 -05:00
committed by GitHub
parent fa52124f9c
commit be5a7f821c
21 changed files with 801 additions and 1284 deletions
@@ -0,0 +1,33 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Asset/BlastChunksAsset.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace Blast
{
void BlastChunksAsset::SetModelAssetIds(const AZStd::vector<AZ::Data::AssetId>& modelAssetIds)
{
m_modelAssetIds = modelAssetIds;
}
const AZStd::vector<AZ::Data::AssetId>& BlastChunksAsset::GetModelAssetIds() const
{
return m_modelAssetIds;
}
void BlastChunksAsset::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BlastChunksAsset, AZ::Data::AssetData>()
->Version(1)
->Field("modelAssetIds", &BlastChunksAsset::m_modelAssetIds);
}
}
} // namespace Blast
@@ -0,0 +1,32 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
namespace Blast
{
//! The product asset file from a .blast_chunks file product asset file
class BlastChunksAsset final
: public AZ::Data::AssetData
{
public:
AZ_RTTI(BlastChunksAsset, "{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BlastChunksAsset, AZ::SystemAllocator, 0);
BlastChunksAsset() = default;
~BlastChunksAsset() override = default;
void SetModelAssetIds(const AZStd::vector<AZ::Data::AssetId>& modelAssetIds);
const AZStd::vector<AZ::Data::AssetId>& GetModelAssetIds() const;
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::vector<AZ::Data::AssetId> m_modelAssetIds;
};
} // namespace Blast
@@ -1,62 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Asset/BlastSliceAsset.h>
#include <AzCore/RTTI/BehaviorContext.h>
namespace Blast
{
void BlastSliceAsset::SetMeshIdList(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList)
{
m_meshAssetIdList = meshAssetIdList;
}
const AZStd::vector<AZ::Data::AssetId>& BlastSliceAsset::GetMeshIdList() const
{
return m_meshAssetIdList;
}
void BlastSliceAsset::SetMaterialId(const AZ::Data::AssetId& materialAssetId)
{
m_materialAssetId = materialAssetId;
}
const AZ::Data::AssetId& BlastSliceAsset::GetMaterialId() const
{
return m_materialAssetId;
}
void BlastSliceAsset::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BlastSliceAsset, AZ::Data::AssetData>()
->Version(1)
->Field("meshAssetIdList", &BlastSliceAsset::m_meshAssetIdList)
->Field("materialAssetId", &BlastSliceAsset::m_materialAssetId);
}
if (AZ::BehaviorContext* behavior = azrtti_cast<AZ::BehaviorContext*>(context))
{
behavior->Class<BlastSliceAsset>("BlastSliceAsset")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "blast")
->Method("SetMeshIdList", &BlastSliceAsset::SetMeshIdList)
->Method("GetMeshIdList", &BlastSliceAsset::GetMeshIdList)
->Method("SetMaterialId", &BlastSliceAsset::SetMaterialId)
->Method("GetMaterialId", &BlastSliceAsset::GetMaterialId)
->Method(
"GetAssetTypeId",
[](BlastSliceAsset*)
{
return azrtti_typeid<BlastSliceAsset>();
});
}
}
} // namespace Blast
@@ -1,36 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
namespace Blast
{
//! The product asset file from a .blast_slice file product asset file
class BlastSliceAsset final : public AZ::Data::AssetData
{
public:
AZ_RTTI(BlastSliceAsset, "{D04AAF07-EB12-4E50-8964-114A9B9C1FD1}", AZ::Data::AssetData);
AZ_CLASS_ALLOCATOR(BlastSliceAsset, AZ::SystemAllocator, 0);
BlastSliceAsset() = default;
~BlastSliceAsset() override = default;
void SetMeshIdList(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList);
const AZStd::vector<AZ::Data::AssetId>& GetMeshIdList() const;
void SetMaterialId(const AZ::Data::AssetId& materialAssetId);
const AZ::Data::AssetId& GetMaterialId() const;
static void Reflect(AZ::ReflectContext* context);
private:
AZStd::vector<AZ::Data::AssetId> m_meshAssetIdList;
AZ::Data::AssetId m_materialAssetId;
};
} // namespace Blast
+1 -3
View File
@@ -16,7 +16,6 @@
#ifdef BLAST_EDITOR
#include <Editor/EditorBlastFamilyComponent.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorSystemComponent.h>
#endif
@@ -40,8 +39,7 @@ namespace Blast
#ifdef BLAST_EDITOR
EditorSystemComponent::CreateDescriptor(),
EditorBlastFamilyComponent::CreateDescriptor(),
EditorBlastMeshDataComponent::CreateDescriptor(),
BlastSliceAssetStorageComponent::CreateDescriptor(),
EditorBlastMeshDataComponent::CreateDescriptor()
#endif
});
}
@@ -0,0 +1,144 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastChunksAssetHandler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace Blast
{
//
// EditorBlastChunksAssetHandler
//
EditorBlastChunksAssetHandler::~EditorBlastChunksAssetHandler()
{
Unregister();
}
AZ::Data::AssetPtr EditorBlastChunksAssetHandler::CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
{
if (type != GetAssetType())
{
AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastChunksAsset'");
return {};
}
if (!CanHandleAsset(id))
{
return nullptr;
}
return aznew BlastChunksAsset;
}
AZ::Data::AssetHandler::LoadResult EditorBlastChunksAssetHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
BlastChunksAsset* blastChunksAsset = asset.GetAs<BlastChunksAsset>();
AZ_Error("blast", blastChunksAsset,
"This should be a BlastChunksAsset type, as this is the only type we process!");
if (!blastChunksAsset)
{
return LoadResult::Error;
}
// get all products from the source scene asset
bool found = false;
AZStd::vector<AZ::Data::AssetInfo> productsAssetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
found,
&AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID,
asset.Get()->GetId().m_guid,
productsAssetInfo);
if (!found)
{
AZ_Error("blast",
found,
"Could not find asset models produced by source asset ID %s, verify the output product model assets.",
asset.Get()->GetId().m_guid.ToString<AZStd::string>().c_str());
return LoadResult::Error;
}
// find all model assets
AZStd::vector<AZ::Data::AssetId> modelAssetIdList;
for (const AZ::Data::AssetInfo& assetInfo : productsAssetInfo)
{
if (azrtti_typeid<AZ::RPI::ModelAsset>() == assetInfo.m_assetType)
{
modelAssetIdList.push_back(assetInfo.m_assetId);
}
}
blastChunksAsset->SetModelAssetIds(modelAssetIdList);
return LoadResult::LoadComplete;
}
void EditorBlastChunksAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
delete ptr;
}
void EditorBlastChunksAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
assetTypes.push_back(azrtti_typeid<BlastChunksAsset>());
}
void EditorBlastChunksAssetHandler::Register()
{
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<BlastChunksAsset>());
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<BlastChunksAsset>());
}
void EditorBlastChunksAssetHandler::Unregister()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<BlastChunksAsset>());
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
AZ::Data::AssetType EditorBlastChunksAssetHandler::GetAssetType() const
{
return azrtti_typeid<BlastChunksAsset>();
}
const char* EditorBlastChunksAssetHandler::GetAssetTypeDisplayName() const
{
return "Blast Chunks Asset";
}
const char* EditorBlastChunksAssetHandler::GetGroup() const
{
return "Blast";
}
const char* EditorBlastChunksAssetHandler::GetBrowserIcon() const
{
return "Icons/Components/Box.png";
}
void EditorBlastChunksAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("blast_chunks");
}
} // namespace Blast
@@ -0,0 +1,46 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Asset/BlastChunksAsset.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace Blast
{
class EditorBlastChunksAssetHandler final
: public AZ::Data::AssetHandler
, public AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EditorBlastChunksAssetHandler, AZ::SystemAllocator, 0);
~EditorBlastChunksAssetHandler() override;
// AZ::Data::AssetHandler
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
void DestroyAsset(AZ::Data::AssetPtr ptr) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
// AZ::AssetTypeInfoBus::Handler
AZ::Data::AssetType GetAssetType() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
void Register();
void Unregister();
};
} // namespace Blast
@@ -45,10 +45,10 @@ namespace Blast
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<EditorBlastMeshDataComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(4)
->Version(5)
->Field("Show Mesh Assets", &EditorBlastMeshDataComponent::m_showMeshAssets)
->Field("Mesh Assets", &EditorBlastMeshDataComponent::m_meshAssets)
->Field("Blast Slice", &EditorBlastMeshDataComponent::m_blastSliceAsset);
->Field("Blast Chunks", &EditorBlastMeshDataComponent::m_blastChunksAsset);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
@@ -77,9 +77,9 @@ namespace Blast
->Attribute(AZ::Edit::Attributes::AutoExpand, false)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnMeshAssetsChanged)
->DataElement(
AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastSliceAsset, "Blast Slice",
"Slice override to fill out meshes and material")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnSliceAssetChanged);
AZ::Edit::UIHandlers::Default, &EditorBlastMeshDataComponent::m_blastChunksAsset, "Blast Chunks",
"Manifest override to fill out meshes and material")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorBlastMeshDataComponent::OnBlastChunksAssetChanged);
}
}
}
@@ -107,23 +107,27 @@ namespace Blast
UnregisterModel();
}
void EditorBlastMeshDataComponent::OnSliceAssetChanged()
void EditorBlastMeshDataComponent::OnBlastChunksAssetChanged()
{
if (!m_blastSliceAsset.GetId().IsValid())
if (!m_blastChunksAsset.GetId().IsValid())
{
return;
}
using namespace AZ::Data;
const AssetId blastAssetId = m_blastChunksAsset.GetId();
m_blastChunksAsset = AssetManager::Instance().GetAsset<BlastChunksAsset>(blastAssetId, AssetLoadBehavior::QueueLoad);
m_blastChunksAsset.BlockUntilLoadComplete();
const AssetId blastAssetId = m_blastSliceAsset.GetId();
m_blastSliceAsset =
AssetManager::Instance().GetAsset<BlastSliceAsset>(blastAssetId, AssetLoadBehavior::QueueLoad);
m_blastSliceAsset.BlockUntilLoadComplete();
if (!m_blastChunksAsset.Get() || m_blastChunksAsset.Get()->GetModelAssetIds().empty())
{
AZ_Warning("blast", false, "Blast Chunk Asset does not contain any models.")
return;
}
// load up the new mesh list
m_meshAssets.clear();
for (const auto& meshId : m_blastSliceAsset.Get()->GetMeshIdList())
for (const auto& meshId : m_blastChunksAsset.Get()->GetModelAssetIds())
{
auto meshAsset = AssetManager::Instance().GetAsset<AZ::RPI::ModelAsset>(meshId, AssetLoadBehavior::QueueLoad);
if (meshAsset)
@@ -135,8 +139,8 @@ namespace Blast
UnregisterModel();
RegisterModel();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
using namespace AzToolsFramework;
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::InvalidatePropertyDisplay, Refresh_EntireTree);
}
void EditorBlastMeshDataComponent::OnMeshAssetsChanged()
@@ -205,9 +209,9 @@ namespace Blast
gameEntity->CreateComponent<BlastMeshDataComponent>(m_meshAssets);
}
const AZ::Data::Asset<BlastSliceAsset>& EditorBlastMeshDataComponent::GetBlastSliceAsset() const
const AZ::Data::Asset<BlastChunksAsset>& EditorBlastMeshDataComponent::GetBlastChunksAsset() const
{
return m_blastSliceAsset;
return m_blastChunksAsset;
}
const AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>>& EditorBlastMeshDataComponent::GetMeshAssets() const
@@ -7,7 +7,7 @@
*/
#pragma once
#include <Asset/BlastSliceAsset.h>
#include <Asset/BlastChunksAsset.h>
#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
#include <Atom/RPI.Public/Model/Model.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
@@ -43,14 +43,14 @@ namespace Blast
// EditorComponentBase
void BuildGameEntity(AZ::Entity* gameEntity) override;
const AZ::Data::Asset<BlastSliceAsset>& GetBlastSliceAsset() const;
const AZ::Data::Asset<BlastChunksAsset>& GetBlastChunksAsset() const;
const AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>>& GetMeshAssets() const;
void OnMaterialsUpdated(const AZ::Render::MaterialAssignmentMap& materials) override;
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
private:
void OnSliceAssetChanged();
void OnBlastChunksAssetChanged();
void OnMeshAssetsChanged();
AZ::Crc32 GetMeshAssetsVisibility() const;
void OnMeshAssetsVisibilityChanged();
@@ -62,7 +62,7 @@ namespace Blast
//////////////////////////////////////////////////////////////////////////
// Reflected data
bool m_showMeshAssets = false;
AZ::Data::Asset<BlastSliceAsset> m_blastSliceAsset;
AZ::Data::Asset<BlastChunksAsset> m_blastChunksAsset;
AZStd::vector<AZ::Data::Asset<AZ::RPI::ModelAsset>> m_meshAssets;
//////////////////////////////////////////////////////////////////////////
@@ -1,345 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/Serialization/ObjectStream.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <SceneAPI/SceneCore/Containers/RuleContainer.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/DataTypes/Groups/IMeshGroup.h>
#include <SceneAPI/SceneData/Groups/MeshGroup.h>
#include <SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h>
#include <SceneAPI/SceneData/Rules/MaterialRule.h>
#include <GFxFramework/MaterialIO/Material.h>
namespace Blast
{
// BlastSliceAssetStorageComponent
void BlastSliceAssetStorageComponent::Reflect(AZ::ReflectContext* context)
{
using namespace AZ::Edit;
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<BlastSliceAssetStorageComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(2)
->Field("Mesh Data", &BlastSliceAssetStorageComponent::m_meshAssetIdList)
->Field("Mesh Path List", &BlastSliceAssetStorageComponent::m_meshAssetPathList);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<BlastSliceAssetStorageComponent>(
"Blast Slice Storage Component", "Used process blast slice data")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Physics")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Box.png")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Box.png")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::AddableByUser, false)
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetIdList, "Mesh Data",
"Slice data to fill out the mesh list")
->DataElement(
AZ::Edit::UIHandlers::Default, &BlastSliceAssetStorageComponent::m_meshAssetPathList,
"Mesh Paths", "The mesh path list");
}
}
if (AZ::BehaviorContext* behavior = azrtti_cast<AZ::BehaviorContext*>(context))
{
behavior->Class<BlastSliceAssetStorageComponent>("BlastSliceAssetStorageComponent")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Module, "blast")
->Method("GenerateAssetInfo", &BlastSliceAssetStorageComponent::GenerateAssetInfo)
->Method("WriteMaterialFile", &BlastSliceAssetStorageComponent::WriteMaterialFile);
}
}
bool BlastSliceAssetStorageComponent::GenerateAssetInfo(
const AZStd::vector<AZStd::string>& chunkNames, AZStd::string_view blastFilename,
AZStd::string_view assetinfoFilename)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(
serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (serializeContext == nullptr)
{
return false;
}
using namespace AZ::SceneAPI::Containers;
using namespace AZ::SceneAPI::SceneData;
AZStd::string filename;
AZ::StringFunc::Path::Split(blastFilename.data(), nullptr, nullptr, &filename, nullptr);
AZStd::any sceneManifestPointer(serializeContext->CreateAny(azrtti_typeid<SceneManifest>()));
SceneManifest* sceneManifest = AZStd::any_cast<SceneManifest>(&sceneManifestPointer);
AZStd::vector<AZStd::any> meshGroupData;
meshGroupData.reserve(chunkNames.size());
AZStd::vector<AZStd::any> materialRuleData;
materialRuleData.reserve(chunkNames.size());
for (const AZStd::string& chunkName : chunkNames)
{
meshGroupData.emplace_back(serializeContext->CreateAny(azrtti_typeid<MeshGroup>()));
AZStd::any& meshGroupPointer = meshGroupData.back();
MeshGroup* meshGroup = AZStd::any_cast<MeshGroup>(&meshGroupPointer);
// make selection list
meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode("RootNode");
for (const AZStd::string& node : chunkNames)
{
meshGroup->GetSceneNodeSelectionList().RemoveSelectedNode(
AZStd::string::format("RootNode.%s", node.c_str()));
}
meshGroup->GetSceneNodeSelectionList().AddSelectedNode(
AZStd::string::format("RootNode.%s", chunkName.c_str()));
// create a default material for the mesh group
materialRuleData.emplace_back(serializeContext->CreateAny(azrtti_typeid<MaterialRule>()));
AZStd::any& materialRulePointer = materialRuleData.back();
MaterialRule* materialRule = AZStd::any_cast<MaterialRule>(&materialRulePointer);
// override the deleter since the AZStd::any will clean up later on
AZStd::shared_ptr<MaterialRule> materialRuleEntry = AZStd::shared_ptr<MaterialRule>(
materialRule,
[](auto)
{
});
meshGroup->GetRuleContainer().AddRule(materialRuleEntry);
// construct the asset name for the chunk's mesh group
AZStd::string meshGroupName(filename);
meshGroupName.append("-");
meshGroupName.append(chunkName);
// TODO: Uncomment lines below as part of SPEC-3542
// meshGroup->OverrideId(AZ::Uuid::CreateName(meshGroupName.c_str()));
// meshGroup->SetName(AZStd::move(meshGroupName));
// override the deleter since the AZStd::any will clean up later on
AZStd::shared_ptr<MeshGroup> meshGroupEntry = AZStd::shared_ptr<MeshGroup>(
meshGroup,
[](auto)
{
});
sceneManifest->AddEntry(AZStd::move(meshGroupEntry));
}
return sceneManifest->SaveToFile(assetinfoFilename.data());
}
bool BlastSliceAssetStorageComponent::WriteMaterialFile(
AZStd::string_view materialGroupName, const AZStd::vector<AZStd::string>& materialNames,
AZStd::string_view materialFilename)
{
AZ::GFxFramework::MaterialGroup group;
for (const auto& texture : materialNames)
{
auto mat = AZStd::make_shared<AZ::GFxFramework::Material>();
mat->SetName(texture);
mat->SetTexture(AZ::GFxFramework::TextureMapType::Diffuse, "EngineAssets/Textures/white.dds");
group.AddMaterial(mat);
}
group.SetMtlName(materialGroupName);
return group.WriteMtlFile(materialFilename.data());
}
//
// EditorBlastSliceAssetHandler
//
EditorBlastSliceAssetHandler::~EditorBlastSliceAssetHandler()
{
Unregister();
}
AZ::Data::AssetPtr EditorBlastSliceAssetHandler::CreateAsset(
const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
{
if (type != GetAssetType())
{
AZ_Error("Blast", type == GetAssetType(), "Invalid asset type! We only handle 'BlastAsset'");
return {};
}
if (!CanHandleAsset(id))
{
return nullptr;
}
return aznew BlastSliceAsset;
}
AZ::Data::AssetHandler::LoadResult EditorBlastSliceAssetHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
BlastSliceAsset* blastSliceAssetData = asset.GetAs<BlastSliceAsset>();
AZ_Error(
"blast", blastSliceAssetData,
"This should be a BlastSliceAsset type, as this is the only type we process!");
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(
serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
if (blastSliceAssetData && serializeContext)
{
AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB);
AZStd::unique_ptr<AZ::Entity> baseEntity(
AZ::Utils::LoadObjectFromStream<AZ::Entity>(*stream, serializeContext, filter));
AZ_Error("Blast", baseEntity, "Could not load slice root entity {asset id}");
if (!baseEntity)
{
return LoadResult::Error;
}
auto&& sliceComponent = baseEntity->FindComponent<AZ::SliceComponent>();
AZ_Error("Blast", sliceComponent, "blast_slice entity missing SliceComponent!");
if (sliceComponent == nullptr)
{
return LoadResult::Error;
}
AZStd::vector<AZ::Entity*> enityList;
sliceComponent->GetEntities(enityList);
for (auto&& entity : enityList)
{
// the base element type to store Blast mesh data is the BlastSliceAssetStorageComponent
auto&& blastSliceAssetStorage = entity->FindComponent<BlastSliceAssetStorageComponent>();
if (blastSliceAssetStorage)
{
if (blastSliceAssetStorage->GetMeshData().empty() == false)
{
blastSliceAssetData->SetMeshIdList(blastSliceAssetStorage->GetMeshData());
return LoadResult::LoadComplete;
}
else if (blastSliceAssetStorage->GetMeshPathList().empty() == false)
{
AZStd::vector<AZ::Data::AssetId> meshAssetIdList;
meshAssetIdList.reserve(blastSliceAssetStorage->GetMeshPathList().size());
for (auto&& assetPath : blastSliceAssetStorage->GetMeshPathList())
{
AZ::Data::AssetId meshAssetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
meshAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath,
assetPath.c_str(), AZ::Data::s_invalidAssetType, false);
if (meshAssetId.IsValid())
{
meshAssetIdList.emplace_back(meshAssetId);
}
}
blastSliceAssetData->SetMeshIdList(meshAssetIdList);
return LoadResult::LoadComplete;
}
}
// back up logic to load blast data for the EditorBlastMeshDataComponent
auto&& meshDataComponent = entity->FindComponent<EditorBlastMeshDataComponent>();
if (meshDataComponent)
{
auto&& innerBlastSliceAsset = meshDataComponent->GetBlastSliceAsset();
if (innerBlastSliceAsset.IsReady())
{
blastSliceAssetData->SetMeshIdList(innerBlastSliceAsset.Get()->GetMeshIdList());
blastSliceAssetData->SetMaterialId(innerBlastSliceAsset.Get()->GetMaterialId());
return LoadResult::LoadComplete;
}
else
{
auto&& meshDataList = meshDataComponent->GetMeshAssets();
AZStd::vector<AZ::Data::AssetId> meshAssetIdList;
meshAssetIdList.reserve(meshDataList.size());
for (auto&& meshData : meshDataList)
{
AZ::RPI::ModelAsset* meshAsset = meshData.Get();
if (meshAsset)
{
meshAssetIdList.push_back(meshAsset->GetId());
}
}
blastSliceAssetData->SetMeshIdList(meshAssetIdList);
return LoadResult::LoadComplete;
}
}
}
AZ_Error(
"Blast", false, "blast_slice assetId:%s missing EditorBlastMeshDataComponent!",
asset->GetId().ToString<AZStd::string>().c_str());
}
return LoadResult::Error;
}
void EditorBlastSliceAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
delete ptr;
}
void EditorBlastSliceAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
{
assetTypes.push_back(azrtti_typeid<BlastSliceAsset>());
}
void EditorBlastSliceAssetHandler::Register()
{
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset manager isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<BlastSliceAsset>());
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<BlastSliceAsset>());
}
void EditorBlastSliceAssetHandler::Unregister()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<BlastSliceAsset>());
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
AZ::Data::AssetType EditorBlastSliceAssetHandler::GetAssetType() const
{
return azrtti_typeid<BlastSliceAsset>();
}
const char* EditorBlastSliceAssetHandler::GetAssetTypeDisplayName() const
{
return "Blast Slice Asset";
}
const char* EditorBlastSliceAssetHandler::GetGroup() const
{
return "Blast";
}
const char* EditorBlastSliceAssetHandler::GetBrowserIcon() const
{
return "Icons/Components/Box.png";
}
void EditorBlastSliceAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("blast_slice");
}
} // namespace Blast
@@ -1,101 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Asset/BlastSliceAsset.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
namespace Blast
{
//! Used to create store asset references (i.e. ids) to fill out the EditorBlastMeshDataComponent
class BlastSliceAssetStorageComponent final : public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_COMPONENT(
BlastSliceAssetStorageComponent, "{696C7E62-1EA4-41E2-B4F6-7BD0D30888DC}",
AzToolsFramework::Components::EditorComponentBase);
~BlastSliceAssetStorageComponent() override = default;
static void Reflect(AZ::ReflectContext* context);
const AZStd::vector<AZ::Data::AssetId>& GetMeshData() const
{
return m_meshAssetIdList;
}
void SetMeshData(const AZStd::vector<AZ::Data::AssetId>& meshAssetIdList)
{
m_meshAssetIdList = meshAssetIdList;
}
const AZStd::vector<AZStd::string>& GetMeshPathList() const
{
return m_meshAssetPathList;
}
void SetMeshPathList(const AZStd::vector<AZStd::string>& meshAssetPathList)
{
m_meshAssetPathList = meshAssetPathList;
}
private:
// AZ::Component interface implementation
void Activate() override {}
void Deactivate() override {}
// EditorComponentBase
void BuildGameEntity([[maybe_unused]] AZ::Entity* gameEntity) override {}
// Script API
bool GenerateAssetInfo(
const AZStd::vector<AZStd::string>& chunkNames,
AZStd::string_view blastFilename,
AZStd::string_view assetinfoFilename);
bool WriteMaterialFile(
AZStd::string_view materialGroupName,
const AZStd::vector<AZStd::string>& materialNames,
AZStd::string_view materialFilename);
AZStd::vector<AZ::Data::AssetId> m_meshAssetIdList;
AZStd::vector<AZStd::string> m_meshAssetPathList;
};
class EditorBlastSliceAssetHandler final
: public AZ::Data::AssetHandler
, public AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EditorBlastSliceAssetHandler, AZ::SystemAllocator, 0);
~EditorBlastSliceAssetHandler() override;
// AZ::Data::AssetHandler
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
void DestroyAsset(AZ::Data::AssetPtr ptr) override;
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
// AZ::AssetTypeInfoBus::Handler
AZ::Data::AssetType GetAssetType() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetGroup() const override;
const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
void Register();
void Unregister();
};
} // namespace Blast
@@ -6,7 +6,7 @@
*
*/
#include <Asset/BlastSliceAsset.h>
#include <Asset/BlastChunksAsset.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Editor/EditorSystemComponent.h>
#include <Editor/EditorWindow.h>
@@ -16,7 +16,7 @@ namespace Blast
{
void EditorSystemComponent::Reflect(AZ::ReflectContext* context)
{
BlastSliceAsset::Reflect(context);
BlastChunksAsset::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
@@ -26,14 +26,14 @@ namespace Blast
void EditorSystemComponent::Activate()
{
m_editorBlastSliceAssetHandler = AZStd::make_unique<EditorBlastSliceAssetHandler>();
m_editorBlastSliceAssetHandler->Register();
m_editorBlastChunksAssetHandler = AZStd::make_unique<EditorBlastChunksAssetHandler>();
m_editorBlastChunksAssetHandler->Register();
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (assetCatalog)
{
assetCatalog->EnableCatalogForAsset(azrtti_typeid<BlastSliceAsset>());
assetCatalog->AddExtension("blast_slice");
assetCatalog->EnableCatalogForAsset(azrtti_typeid<BlastChunksAsset>());
assetCatalog->AddExtension("blast_chunks");
}
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
@@ -46,7 +46,7 @@ namespace Blast
void EditorSystemComponent::Deactivate()
{
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
m_editorBlastSliceAssetHandler.reset();
m_editorBlastChunksAssetHandler.reset();
}
// This will be called when the IEditor instance is ready
@@ -11,7 +11,7 @@
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorBlastChunksAssetHandler.h>
namespace Blast
{
@@ -39,7 +39,7 @@ namespace Blast
required.push_back(AZ_CRC("BlastService", 0x75beae2d));
}
AZStd::unique_ptr<EditorBlastSliceAssetHandler> m_editorBlastSliceAssetHandler;
AZStd::unique_ptr<EditorBlastChunksAssetHandler> m_editorBlastChunksAssetHandler;
// AZ::Component
void Activate() override;
@@ -0,0 +1,207 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastChunksAssetHandler.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Asset/BlastChunksAsset.h>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <gmock/gmock.h>
#include <AzCore/UnitTest/MockComponentApplication.h>
namespace UnitTest
{
MockComponentApplication::MockComponentApplication()
{
AZ::ComponentApplicationBus::Handler::BusConnect();
AZ::Interface<AZ::ComponentApplicationRequests>::Register(this);
}
MockComponentApplication::~MockComponentApplication()
{
AZ::Interface<AZ::ComponentApplicationRequests>::Unregister(this);
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
class MockAssetCatalogRequestBusHandler final
: public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
virtual ~MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool));
MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&));
MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&));
MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(AddExtension, void(const char*));
MOCK_METHOD0(ClearCatalog, void());
MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector<AZStd::string>&, const AZStd::string&, int, const AZStd::vector<AZStd::string>&));
MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector<AZStd::string>&, const AZStd::string&));
MOCK_METHOD0(DisableCatalog, void());
MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&));
MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB));
MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*));
MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set<AZ::Data::AssetId>&, const AZStd::vector<AZStd::string>&));
MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&));
MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector<AZ::Data::AssetType>&));
MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector<AZStd::string>());
MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, size_t));
MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(LoadCatalog, bool(const char*));
MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&));
MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(SaveCatalog, bool(const char*));
MOCK_METHOD0(StartMonitoringAssets, void());
MOCK_METHOD0(StopMonitoringAssets, void());
MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&));
};
class MockAssetManager
: public AZ::Data::AssetManager
{
public:
MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) :
AssetManager(desc)
{
}
};
class EditorBlastChunkAssetHandlerTestFixture
: public AllocatorsTestFixture
{
public:
AZStd::unique_ptr<UnitTest::MockComponentApplication> m_mockComponentApplicationBusHandler;
AZStd::unique_ptr<MockAssetCatalogRequestBusHandler> m_mockAssetCatalogRequestBusHandler;
AZStd::unique_ptr<MockAssetManager> m_mockAssetManager;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
void SetUpChunkComponents()
{
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
AZ::Entity::Reflect(m_serializeContext.get());
AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get());
}
void TearDownChunkComponents()
{
m_serializeContext.reset();
}
void SetUp() override final
{
AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_mockComponentApplicationBusHandler = AZStd::make_unique<UnitTest::MockComponentApplication>();
m_mockAssetCatalogRequestBusHandler = AZStd::make_unique<MockAssetCatalogRequestBusHandler>();
m_mockAssetManager = AZStd::make_unique<MockAssetManager>(AZ::Data::AssetManager::Descriptor{});
AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get());
}
void TearDown() override final
{
m_mockAssetManager.release();
AZ::Data::AssetManager::Destroy();
m_mockAssetCatalogRequestBusHandler.reset();
m_mockComponentApplicationBusHandler.reset();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AllocatorsTestFixture::TearDown();
}
void SaveChunkAssetToStream(AZ::Entity* chunkAssetEntity, AZStd::vector<char>& buffer)
{
buffer.clear();
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML);
objStream->WriteClass(chunkAssetEntity);
EXPECT_TRUE(objStream->Finalize());
}
};
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetManager_Registered)
{
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Blast::BlastChunksAsset>()));
handler.Unregister();
}
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetTypeInfoBus_Responds)
{
auto assetId = azrtti_typeid<Blast::BlastChunksAsset>();
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
AZ::Data::AssetType assetType = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType);
EXPECT_NE(AZ::Uuid::CreateNull(), assetType);
const char* displayName = nullptr;
AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName);
EXPECT_STREQ("Blast Chunks Asset", displayName);
const char* group = nullptr;
AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup);
EXPECT_STREQ("Blast", group);
const char* icon = nullptr;
AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon);
EXPECT_STREQ("Icons/Components/Box.png", icon);
AZStd::vector<AZStd::string> extensions;
AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions);
ASSERT_EQ(1, extensions.size());
ASSERT_EQ("blast_chunks", extensions[0]);
handler.Unregister();
}
TEST_F(EditorBlastChunkAssetHandlerTestFixture, EditorBlastChunkAssetHandler_AssetHandler_Ready)
{
auto assetType = azrtti_typeid<Blast::BlastChunksAsset>();
auto&& assetManager = AZ::Data::AssetManager::Instance();
Blast::EditorBlastChunksAssetHandler handler;
handler.Register();
EXPECT_EQ(&handler, assetManager.GetHandler(assetType));
// create and release an instance of the BlastChunkAsset asset type
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto assetPtr = assetManager.CreateAsset<Blast::BlastChunksAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
EXPECT_NE(nullptr, assetPtr.Get());
EXPECT_EQ(azrtti_typeid<Blast::BlastChunksAsset>(), assetPtr.GetType());
}
handler.Unregister();
}
}
@@ -1,377 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/EditorBlastSliceAssetHandler.h>
#include <Editor/EditorBlastMeshDataComponent.h>
#include <Asset/BlastSliceAsset.h>
#include <AzCore/UnitTest/MockComponentApplication.h>
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <gmock/gmock.h>
namespace UnitTest
{
class MockComponentApplicationBusHandler final
//: public MockComponentApplication
: public AZ::ComponentApplicationBus::Handler
{
public:
MockComponentApplicationBusHandler()
{
AZ::ComponentApplicationBus::Handler::BusConnect();
}
virtual ~MockComponentApplicationBusHandler()
{
AZ::ComponentApplicationBus::Handler::BusDisconnect();
}
MOCK_METHOD0(Destroy, void());
MOCK_METHOD1(RegisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(UnregisterComponentDescriptor, void(const AZ::ComponentDescriptor*));
MOCK_METHOD1(RemoveEntity, bool(AZ::Entity*));
MOCK_METHOD1(DeleteEntity, bool(const AZ::EntityId&));
MOCK_METHOD1(GetEntityName, AZStd::string(const AZ::EntityId&));
MOCK_METHOD1(AddEntity, bool(AZ::Entity*));
MOCK_METHOD1(FindEntity, AZ::Entity*(const AZ::EntityId&));
MOCK_METHOD1(EnumerateEntities, void(const ComponentApplicationRequests::EntityCallback&));
MOCK_METHOD0(GetApplication, AZ::ComponentApplication* ());
MOCK_METHOD0(GetSerializeContext, AZ::SerializeContext* ());
MOCK_METHOD0(GetBehaviorContext, AZ::BehaviorContext* ());
MOCK_METHOD0(GetJsonRegistrationContext, AZ::JsonRegistrationContext* ());
MOCK_METHOD0(GetAppRoot, const char* ());
MOCK_CONST_METHOD0(GetExecutableFolder, const char* ());
MOCK_METHOD0(GetDrillerManager, AZ::Debug::DrillerManager* ());
MOCK_METHOD0(GetTickDeltaTime, float());
MOCK_METHOD1(Tick, void(float));
MOCK_METHOD0(TickSystem, void());
MOCK_CONST_METHOD0(GetRequiredSystemComponents, AZ::ComponentTypeList());
MOCK_METHOD1(ResolveModulePath, void(AZ::OSString&));
MOCK_METHOD0(CreateSerializeContext, void());
MOCK_METHOD0(DestroySerializeContext, void());
MOCK_METHOD0(CreateBehaviorContext, void());
MOCK_METHOD0(DestroyBehaviorContext, void());
MOCK_METHOD0(RegisterCoreComponents, void());
MOCK_METHOD1(AddSystemComponents, void(AZ::Entity*));
MOCK_METHOD0(ReflectSerialize, void());
MOCK_METHOD1(Reflect, void(AZ::ReflectContext*));
MOCK_CONST_METHOD0(GetBinFolder, const char* ());
};
class MockAssetCatalogRequestBusHandler final
: public AZ::Data::AssetCatalogRequestBus::Handler
{
public:
MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusConnect();
}
virtual ~MockAssetCatalogRequestBusHandler()
{
AZ::Data::AssetCatalogRequestBus::Handler::BusDisconnect();
}
MOCK_METHOD3(GetAssetIdByPath, AZ::Data::AssetId(const char*, const AZ::Data::AssetType&, bool));
MOCK_METHOD1(GetAssetInfoById, AZ::Data::AssetInfo(const AZ::Data::AssetId&));
MOCK_METHOD1(AddAssetType, void(const AZ::Data::AssetType&));
MOCK_METHOD1(AddDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(AddExtension, void(const char*));
MOCK_METHOD0(ClearCatalog, void());
MOCK_METHOD5(CreateBundleManifest, bool(const AZStd::string&, const AZStd::vector<AZStd::string>&, const AZStd::string&, int, const AZStd::vector<AZStd::string>&));
MOCK_METHOD2(CreateDeltaCatalog, bool(const AZStd::vector<AZStd::string>&, const AZStd::string&));
MOCK_METHOD0(DisableCatalog, void());
MOCK_METHOD1(EnableCatalogForAsset, void(const AZ::Data::AssetType&));
MOCK_METHOD3(EnumerateAssets, void(BeginAssetEnumerationCB, AssetEnumerationCB, EndAssetEnumerationCB));
MOCK_METHOD1(GenerateAssetIdTEMP, AZ::Data::AssetId(const char*));
MOCK_METHOD1(GetAllProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD3(GetAllProductDependenciesFilter, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&, const AZStd::unordered_set<AZ::Data::AssetId>&, const AZStd::vector<AZStd::string>&));
MOCK_METHOD1(GetAssetPathById, AZStd::string(const AZ::Data::AssetId&));
MOCK_METHOD1(GetDirectProductDependencies, AZ::Outcome<AZStd::vector<AZ::Data::ProductDependency>, AZStd::string>(const AZ::Data::AssetId&));
MOCK_METHOD1(GetHandledAssetTypes, void(AZStd::vector<AZ::Data::AssetType>&));
MOCK_METHOD0(GetRegisteredAssetPaths, AZStd::vector<AZStd::string>());
MOCK_METHOD2(InsertDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, size_t));
MOCK_METHOD2(InsertDeltaCatalogBefore, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>, AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(LoadCatalog, bool(const char*));
MOCK_METHOD2(RegisterAsset, void(const AZ::Data::AssetId&, AZ::Data::AssetInfo&));
MOCK_METHOD1(RemoveDeltaCatalog, bool(AZStd::shared_ptr<AzFramework::AssetRegistry>));
MOCK_METHOD1(SaveCatalog, bool(const char*));
MOCK_METHOD0(StartMonitoringAssets, void());
MOCK_METHOD0(StopMonitoringAssets, void());
MOCK_METHOD1(UnregisterAsset, void(const AZ::Data::AssetId&));
};
class MockAssetManager
: public AZ::Data::AssetManager
{
public:
MockAssetManager(const AZ::Data::AssetManager::Descriptor& desc) :
AssetManager(desc)
{
}
};
class EditorBlastSliceAssetHandlerTestFixture
: public AllocatorsTestFixture
{
public:
AZStd::unique_ptr<MockComponentApplicationBusHandler> m_mockComponentApplicationBusHandler;
//AZStd::unique_ptr<UnitTest::MockComponentApplication> m_mockComponentApplicationBusHandler;
AZStd::unique_ptr<MockAssetCatalogRequestBusHandler> m_mockAssetCatalogRequestBusHandler;
AZStd::unique_ptr<MockAssetManager> m_mockAssetManager;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
const AZ::ComponentDescriptor* m_sliceComponentDescriptor = nullptr;
void SetUpSliceComponents()
{
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
AZ::Entity::Reflect(m_serializeContext.get());
Blast::BlastSliceAssetStorageComponent::Reflect(m_serializeContext.get());
AzToolsFramework::Components::EditorComponentBase::Reflect(m_serializeContext.get());
m_sliceComponentDescriptor = AZ::SliceComponent::CreateDescriptor();
m_sliceComponentDescriptor->Reflect(m_serializeContext.get());
}
void TearDownSliceComponents()
{
delete m_sliceComponentDescriptor;
m_serializeContext.reset();
}
void SetUp() override final
{
AllocatorsTestFixture::SetUp();
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_mockComponentApplicationBusHandler = AZStd::make_unique<MockComponentApplicationBusHandler>();
m_mockAssetCatalogRequestBusHandler = AZStd::make_unique<MockAssetCatalogRequestBusHandler>();
m_mockAssetManager = AZStd::make_unique<MockAssetManager>(AZ::Data::AssetManager::Descriptor{});
AZ::Data::AssetManager::SetInstance(m_mockAssetManager.get());
}
void TearDown() override final
{
AZ::Data::AssetManager::SetInstance(nullptr);
m_mockAssetManager.reset();
m_mockAssetCatalogRequestBusHandler.reset();
m_mockComponentApplicationBusHandler.reset();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
AllocatorsTestFixture::TearDown();
}
void SaveSliceAssetToStream(AZ::Entity* sliceAssetEntity, AZStd::vector<char>& buffer)
{
buffer.clear();
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
AZ::ObjectStream* objStream = AZ::ObjectStream::Create(&stream, *m_serializeContext.get(), AZ::ObjectStream::ST_XML);
objStream->WriteClass(sliceAssetEntity);
EXPECT_TRUE(objStream->Finalize());
}
};
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetManager_Registered)
{
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
EXPECT_NE(nullptr, AZ::Data::AssetManager::Instance().GetHandler(azrtti_typeid<Blast::BlastSliceAsset>()));
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAssetStorageComponent_Behavior_Registered)
{
AZ::BehaviorContext behaviorContext;
Blast::BlastSliceAssetStorageComponent::Reflect(&behaviorContext);
auto classEntry = behaviorContext.m_classes.find("BlastSliceAssetStorageComponent");
EXPECT_NE(behaviorContext.m_classes.end(), classEntry);
AZ::BehaviorClass* behaviorClass = classEntry->second;
auto methodEntry = behaviorClass->m_methods.find("GenerateAssetInfo");
EXPECT_NE(behaviorClass->m_methods.end(), methodEntry);
AZ::BehaviorMethod* behaviorMethod = methodEntry->second;
EXPECT_EQ(4, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAssetStorageComponent>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZStd::vector<AZStd::string>>());
EXPECT_EQ(behaviorMethod->GetArgument(2)->m_typeId, azrtti_typeid<AZStd::string_view>());
EXPECT_EQ(behaviorMethod->GetArgument(3)->m_typeId, azrtti_typeid<AZStd::string_view>());
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, BlastSliceAsset_Behavior_Registered)
{
AZ::BehaviorContext behaviorContext;
Blast::BlastSliceAsset::Reflect(&behaviorContext);
auto classEntry = behaviorContext.m_classes.find("BlastSliceAsset");
EXPECT_NE(behaviorContext.m_classes.end(), classEntry);
AZ::BehaviorClass* behaviorClass = classEntry->second;
auto setMeshIdListEntry = behaviorClass->m_methods.find("SetMeshIdList");
EXPECT_NE(behaviorClass->m_methods.end(), setMeshIdListEntry);
{
AZ::BehaviorMethod* behaviorMethod = setMeshIdListEntry->second;
EXPECT_EQ(2, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZStd::vector<AZ::Data::AssetId>>());
}
auto getMeshIdListEntry = behaviorClass->m_methods.find("GetMeshIdList");
EXPECT_NE(behaviorClass->m_methods.end(), getMeshIdListEntry);
{
AZ::BehaviorMethod* behaviorMethod = getMeshIdListEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZStd::vector<AZ::Data::AssetId>>());
}
auto setMaterialIdEntry = behaviorClass->m_methods.find("SetMaterialId");
EXPECT_NE(behaviorClass->m_methods.end(), setMaterialIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = setMaterialIdEntry->second;
EXPECT_EQ(2, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetArgument(1)->m_typeId, azrtti_typeid<AZ::Data::AssetId>());
}
auto getMaterialIdEntry = behaviorClass->m_methods.find("GetMaterialId");
EXPECT_NE(behaviorClass->m_methods.end(), getMaterialIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = getMaterialIdEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZ::Data::AssetId>());
}
auto getAssetTypeIdEntry = behaviorClass->m_methods.find("GetAssetTypeId");
EXPECT_NE(behaviorClass->m_methods.end(), getAssetTypeIdEntry);
{
AZ::BehaviorMethod* behaviorMethod = getAssetTypeIdEntry->second;
EXPECT_EQ(1, behaviorMethod->GetNumArguments());
EXPECT_EQ(behaviorMethod->GetArgument(0)->m_typeId, azrtti_typeid<Blast::BlastSliceAsset>());
EXPECT_EQ(behaviorMethod->GetResult()->m_typeId, azrtti_typeid<AZ::TypeId>());
}
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetTypeInfoBus_Responds)
{
auto assetId = azrtti_typeid<Blast::BlastSliceAsset>();
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
AZ::Data::AssetType assetType = AZ::Uuid::CreateNull();
AZ::AssetTypeInfoBus::EventResult(assetType, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetType);
EXPECT_NE(AZ::Uuid::CreateNull(), assetType);
const char* displayName = nullptr;
AZ::AssetTypeInfoBus::EventResult(displayName, assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeDisplayName);
EXPECT_STREQ("Blast Slice Asset", displayName);
const char* group = nullptr;
AZ::AssetTypeInfoBus::EventResult(group, assetId, &AZ::AssetTypeInfoBus::Events::GetGroup);
EXPECT_STREQ("Blast", group);
const char* icon = nullptr;
AZ::AssetTypeInfoBus::EventResult(icon, assetId, &AZ::AssetTypeInfoBus::Events::GetBrowserIcon);
EXPECT_STREQ("Editor/Icons/Components/Box.png", icon);
AZStd::vector<AZStd::string> extensions;
AZ::AssetTypeInfoBus::Event(assetId, &AZ::AssetTypeInfoBus::Events::GetAssetTypeExtensions, extensions);
ASSERT_EQ(1, extensions.size());
ASSERT_EQ("blast_slice", extensions[0]);
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_Ready)
{
auto assetType = azrtti_typeid<Blast::BlastSliceAsset>();
auto&& assetManager = AZ::Data::AssetManager::Instance();
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
EXPECT_EQ(&handler, assetManager.GetHandler(assetType));
// create and release an instance of the BlastSliceAsset asset type
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(1)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto assetPtr = assetManager.CreateAsset<Blast::BlastSliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
EXPECT_NE(nullptr, assetPtr.Get());
EXPECT_EQ(azrtti_typeid<Blast::BlastSliceAsset>(), assetPtr.GetType());
}
handler.Unregister();
}
TEST_F(EditorBlastSliceAssetHandlerTestFixture, EditorBlastSliceAssetHandler_AssetHandler_LoadsAssetData)
{
SetUpSliceComponents();
AZStd::vector<AZStd::string> meshAssetPathList = { "/foo/path/thing.cgf", "/foo/path/that.cgf" };
AZ::Entity* storageEntity = aznew AZ::Entity();
auto* blastStorage = storageEntity->CreateComponent<Blast::BlastSliceAssetStorageComponent>();
blastStorage->SetMeshPathList(meshAssetPathList);
AZ::Entity sliceEntity;
AZ::SliceComponent* slice = sliceEntity.CreateComponent<AZ::SliceComponent>();
slice->AddEntity(storageEntity);
AZStd::vector<char> buffer;
SaveSliceAssetToStream(&sliceEntity, buffer);
// Load a slice with the BlastSliceAssetStorageComponent
Blast::EditorBlastSliceAssetHandler handler;
handler.Register();
{
using ::testing::Return;
using ::testing::_;
EXPECT_CALL(*m_mockComponentApplicationBusHandler, GetSerializeContext)
.Times(1)
.WillOnce(Return(m_serializeContext.get()));
EXPECT_CALL(*m_mockComponentApplicationBusHandler, FindEntity(_))
.Times(1)
.WillOnce(Return(&sliceEntity));
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetIdByPath(_,_,_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0)));
EXPECT_CALL(*m_mockAssetCatalogRequestBusHandler, GetAssetInfoById(_))
.Times(2)
.WillRepeatedly(Return(AZ::Data::AssetInfo{}));
auto&& assetManager = AZ::Data::AssetManager::Instance();
auto assetPtr = assetManager.CreateAsset<Blast::BlastSliceAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0));
AZ::IO::ByteContainerStream<AZStd::vector<char>> stream(&buffer);
stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN);
const AZ::Data::AssetFilterCB assetLoadFilterCB{};
bool loaded = handler.LoadAssetData(assetPtr, &stream, assetLoadFilterCB);
EXPECT_TRUE(loaded);
}
handler.Unregister();
TearDownSliceComponents();
}
}
+2 -2
View File
@@ -11,8 +11,8 @@ set(FILES
Source/Editor/EditorBlastFamilyComponent.cpp
Source/Editor/EditorBlastMeshDataComponent.cpp
Source/Editor/EditorBlastMeshDataComponent.h
Source/Editor/EditorBlastSliceAssetHandler.h
Source/Editor/EditorBlastSliceAssetHandler.cpp
Source/Editor/EditorBlastChunksAssetHandler.h
Source/Editor/EditorBlastChunksAssetHandler.cpp
Source/Editor/EditorSystemComponent.h
Source/Editor/EditorSystemComponent.cpp
Editor/ConfigurationWidget.h
@@ -7,6 +7,6 @@
#
set(FILES
# Tests/Editor/EditorBlastSliceAssetHandlerTest.cpp
Tests/Editor/EditorBlastChunksAssetHandlerTest.cpp
Tests/Editor/EditorTestMain.cpp
)
+2 -2
View File
@@ -26,8 +26,8 @@ set(FILES
Source/Asset/BlastAsset.cpp
Source/Asset/BlastAssetHandler.h
Source/Asset/BlastAssetHandler.cpp
Source/Asset/BlastSliceAsset.h
Source/Asset/BlastSliceAsset.cpp
Source/Asset/BlastChunksAsset.h
Source/Asset/BlastChunksAsset.cpp
Source/Components/BlastFamilyComponent.h
Source/Components/BlastFamilyComponent.cpp
Source/Components/BlastFamilyComponentNotificationBusHandler.h
@@ -1,323 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
def install_user_site():
import os
import sys
import azlmbr.paths
executableBinFolder = azlmbr.paths.executableFolder
# the PyAssImp module checks the Windows PATH for the assimp DLL file
if os.name == "nt":
os.environ['PATH'] = os.environ['PATH'] + os.pathsep + executableBinFolder
# PyAssImp module needs to find the shared library for assimp to load; "posix" handles Mac and Linux
if os.name == "posix":
if 'LD_LIBRARY_PATH' in os.environ:
os.environ['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] + os.pathsep + executableBinFolder
else:
os.environ['LD_LIBRARY_PATH'] = executableBinFolder
# add the user site packages folder to find the pyassimp egg link
import site
for item in sys.path:
if (item.find('site-packages') != -1):
site.addsitedir(item)
install_user_site()
import pyassimp
import azlmbr.asset
import azlmbr.asset.builder
import azlmbr.asset.entity
import azlmbr.blast
import azlmbr.bus as bus
import azlmbr.editor as editor
import azlmbr.entity
import azlmbr.math
import os
import traceback
import binascii
import sys
# the UUID must be unique amongst all the asset builders in Python or otherwise
# a collision of builders will happen preventing one from running
busIdString = '{CF5C74D1-9ED4-4851-85B1-9B15090DBEC7}'
busId = azlmbr.math.Uuid_CreateString(busIdString, 0)
handler = None
jobKeyName = 'Blast Chunk Assets'
sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
dccMaterialType = azlmbr.math.Uuid_CreateString('{C88469CF-21E7-41EB-96FD-BF14FBB05EDC}', 0)
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def get_source_fbx_filename(request):
fullPath = os.path.join(request.watchFolder, request.sourceFile)
basePath, filePart = os.path.split(fullPath)
filename = os.path.splitext(filePart)[0] + '.fbx'
filename = os.path.join(basePath, filename)
return filename
def raise_error(message):
raise RuntimeError(f'[ERROR]: {message}')
def generate_asset_info(chunkNames, request):
import azlmbr.blast
# write out an object stream with the extension of .fbx.assetinfo.generated
basePath, sceneFile = os.path.split(request.sourceFile)
assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated'
assetinfoFilename = os.path.join(basePath, assetinfoFilename)
assetinfoFilename = assetinfoFilename.replace('\\', '/').lower()
outputFilename = os.path.join(request.tempDirPath, assetinfoFilename)
storage = azlmbr.blast.BlastSliceAssetStorageComponent()
if (storage.GenerateAssetInfo(chunkNames, request.sourceFile, outputFilename)):
product = azlmbr.asset.builder.JobProduct(assetinfoFilename, sceneManifestType, 1)
product.dependenciesHandled = True
return product
raise_error('Failed to generate assetinfo.generated')
def export_fbx_manifest(request):
output = []
fbxFilename = get_source_fbx_filename(request)
sceneAsset = pyassimp.load(fbxFilename)
with sceneAsset as scene:
rootNode = scene.mRootNode.contents
for index in range(0, rootNode.mNumChildren):
child = rootNode.mChildren[index]
childNode = child.contents
childNodeName = bytes.decode(childNode.mName.data)
output.append(str(childNodeName))
return output
def convert_to_asset_paths(fbxFilename, gameRoot, chunkNameList):
realtivePath = fbxFilename[len(gameRoot) + 1:]
realtivePath = os.path.splitext(realtivePath)[0]
output = []
for chunk in chunkNameList:
assetPath = realtivePath + '-' + chunk + '.cgf'
assetPath = assetPath.replace('\\', '/')
assetPath = assetPath.lower()
output.append(assetPath)
return output
# creates a single job to compile for each platform
def create_jobs(request):
fbxSidecarFilename = get_source_fbx_filename(request)
if (os.path.exists(fbxSidecarFilename) is False):
print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile))
return azlmbr.asset.builder.CreateJobsResponse()
# see if the FBX file already has a .assetinfo source asset, if so then do not create a job
if (os.path.exists(f'{fbxSidecarFilename}.assetinfo')):
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
return response
# create job descriptor for each platform
jobDescriptorList = []
for platformInfo in request.enabledPlatforms:
jobDesc = azlmbr.asset.builder.JobDescriptor()
jobDesc.jobKey = jobKeyName
jobDesc.priority = 12 # higher than the 'Scene compilation' or 'fbx'
jobDesc.set_platform_identifier(platformInfo.identifier)
jobDescriptorList.append(jobDesc)
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
response.createJobOutputs = jobDescriptorList
return response
# handler to create jobs for a source asset
def on_create_jobs(args):
try:
request = args[0]
return create_jobs(request)
except:
log_exception_traceback()
return azlmbr.asset.builder.CreateJobsResponse()
def generate_blast_slice_asset(chunkNameList, request):
# get list of relative chunk paths
fbxFilename = get_source_fbx_filename(request)
assetPaths = convert_to_asset_paths(fbxFilename, request.watchFolder, chunkNameList)
outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'CreateEditorEntity', 'BlastData')
if (outcome.IsSuccess() is False):
raise_error('could not create an editor entity')
blastDataEntityId = outcome.GetValue()
# create a component for the editor entity
gameType = azlmbr.entity.EntityType().Game
blastMeshDataTypeIdList = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Blast Slice Storage Component"], gameType)
componentOutcome = editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentOfType', blastDataEntityId, blastMeshDataTypeIdList[0])
if (componentOutcome.IsSuccess() is False):
raise_error('failed to add component (Blast Slice Storage Component) to the blast_slice')
# build the blast slice using the chunk asset paths
blastMeshComponentId = componentOutcome.GetValue()[0]
outcome = editor.EditorComponentAPIBus(bus.Broadcast, 'BuildComponentPropertyTreeEditor', blastMeshComponentId)
if(outcome.IsSuccess() is False):
raise_error(f'failed to create Property Tree Editor for component ({blastMeshComponentId})')
pte = outcome.GetValue()
pte.set_visible_enforcement(True)
pte.set_value('Mesh Paths', assetPaths)
# write out an object stream with the extension of .blast_slice
basePath, sceneFile = os.path.split(request.sourceFile)
blastFilename = os.path.splitext(sceneFile)[0] + '.blast_slice'
blastFilename = os.path.join(basePath, blastFilename)
blastFilename = blastFilename.replace('\\', '/').lower()
tempFilename = os.path.join(request.tempDirPath, blastFilename)
entityList = [blastDataEntityId]
makeDynamic = False
outcome = azlmbr.asset.entity.PythonBuilderRequestBus(bus.Broadcast, 'WriteSliceFile', tempFilename, entityList, makeDynamic)
if (outcome.IsSuccess() is False):
raise_error(f'WriteSliceFile failed for blast_slice file ({blastFilename})')
# return a job product
blastSliceAsset = azlmbr.blast.BlastSliceAsset()
subId = binascii.crc32(blastFilename.encode('utf8'))
product = azlmbr.asset.builder.JobProduct(blastFilename, blastSliceAsset.GetAssetTypeId(), subId)
product.dependenciesHandled = True
return product
def read_in_string(data, dataLength):
stringData = ''
for idx in range(4, dataLength - 1):
char = bytes.decode(data[idx])
if (str.isascii(char)):
stringData += char
return stringData
def import_material_info(fbxFilename):
_, group_name = os.path.split(fbxFilename)
group_name = os.path.splitext(group_name)[0]
output = {}
output['group_name'] = group_name
output['material_name_list'] = []
sceneAsset = pyassimp.load(fbxFilename)
with sceneAsset as scene:
for materialIndex in range(0, scene.mNumMaterials):
material = scene.mMaterials[materialIndex].contents
for materialPropertyIdx in range(0, material.mNumProperties):
materialProperty = material.mProperties[materialPropertyIdx].contents
materialPropertyName = bytes.decode(materialProperty.mKey.data)
if (materialPropertyName.endswith('mat.name') and materialProperty.mType is 3):
stringData = read_in_string(materialProperty.mData, materialProperty.mDataLength)
output['material_name_list'].append(stringData)
return output
def write_material_file(sourceFile, destFolder):
# preserve source MTL files
rootPath, materialSourceFile = os.path.split(sourceFile)
materialSourceFile = os.path.splitext(materialSourceFile)[0] + '.mtl'
materialSourceFile = os.path.join(rootPath, materialSourceFile)
if (os.path.exists(materialSourceFile)):
print(f'{materialSourceFile} source already exists')
return None
# auto-generate a DCC material file
info = import_material_info(sourceFile)
materialGroupName = info['group_name']
materialNames = info['material_name_list']
materialFilename = materialGroupName + '.dccmtl.generated'
subId = binascii.crc32(materialFilename.encode('utf8'))
materialFilename = os.path.join(destFolder, materialFilename)
storage = azlmbr.blast.BlastSliceAssetStorageComponent()
storage.WriteMaterialFile(materialGroupName, materialNames, materialFilename)
product = azlmbr.asset.builder.JobProduct(materialFilename, dccMaterialType, subId)
product.dependenciesHandled = True
return product
def process_fbx_file(request):
# fill out response object
response = azlmbr.asset.builder.ProcessJobResponse()
productOutputs = []
# write out DCCMTL file as a product (if needed)
materialProduct = write_material_file(get_source_fbx_filename(request), request.tempDirPath)
if (materialProduct is not None):
productOutputs.append(materialProduct)
# prepare output folder
basePath, _ = os.path.split(request.sourceFile)
outputPath = os.path.join(request.tempDirPath, basePath)
os.makedirs(outputPath)
# parse FBX for chunk names
chunkNameList = export_fbx_manifest(request)
# create assetinfo generated (is product)
productOutputs.append(generate_asset_info(chunkNameList, request))
# write out the blast_slice object stream
productOutputs.append(generate_blast_slice_asset(chunkNameList, request))
response.outputProducts = productOutputs
response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success
response.dependenciesHandled = True
return response
# using the incoming 'request' find the type of job via 'jobKey' to determine what to do
def on_process_job(args):
try:
request = args[0]
if (request.jobDescription.jobKey.startswith(jobKeyName)):
return process_fbx_file(request)
return azlmbr.asset.builder.ProcessJobResponse()
except:
log_exception_traceback()
return azlmbr.asset.builder.ProcessJobResponse()
# register asset builder
def register_asset_builder():
assetPattern = azlmbr.asset.builder.AssetBuilderPattern()
assetPattern.pattern = '*.blast'
assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard
builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc()
builderDescriptor.name = "Blast Gem"
builderDescriptor.patterns = [assetPattern]
builderDescriptor.busId = busId
builderDescriptor.version = 5
outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor)
if outcome.IsSuccess():
# created the asset builder to hook into the notification bus
handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler()
handler.connect(busId)
handler.add_callback('OnCreateJobsRequest', on_create_jobs)
handler.add_callback('OnProcessJobRequest', on_process_job)
return handler
# create the asset builder handler
try:
handler = register_asset_builder()
except:
handler = None
log_exception_traceback()
@@ -0,0 +1,290 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
"""
This a Python Asset Builder script examines each .blast file to see if an
associated .fbx file needs to be processed by exporting all of its chunks
into a scene manifest
This is also a SceneAPI script that executes from a foo.fbx.assetinfo scene
manifest that writes out asset chunk data for .blast files
"""
import os, traceback, binascii, sys, json, pathlib
import azlmbr.math
import azlmbr.asset
import azlmbr.asset.entity
import azlmbr.asset.builder
import azlmbr.bus
#
# Python Asset Builder
#
busId = azlmbr.math.Uuid_CreateString('{D4FA20E3-8EF4-44A3-A045-AAE6C1CCAAAB}', 0)
jobKeyName = 'Blast Chunk Assets'
def log_exception_traceback():
exc_type, exc_value, exc_tb = sys.exc_info()
data = traceback.format_exception(exc_type, exc_value, exc_tb)
print(str(data))
def raise_error(message):
print (f'ERROR - {message}');
raise RuntimeError(f'[ERROR]: {message}');
# creates a single job to compile for each platform
def get_source_fbx_filename(request):
fullPath = os.path.join(request.watchFolder, request.sourceFile)
basePath, filePart = os.path.split(fullPath)
filename = os.path.splitext(filePart)[0] + '.fbx'
filename = os.path.join(basePath, filename)
return filename
def create_jobs(request):
fbxSidecarFilename = get_source_fbx_filename(request)
if (os.path.exists(fbxSidecarFilename) is False):
print('[WARN] Sidecar FBX file {} is missing for blast file {}'.format(fbxSidecarFilename, request.sourceFile))
return azlmbr.asset.builder.CreateJobsResponse()
# see if the FBX file already has a .assetinfo source asset, if so then do not create a job
establishedAssetInfo = f'{fbxSidecarFilename}.assetinfo';
if (os.path.exists(establishedAssetInfo)):
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
return response
# create job descriptor for each platform
jobDescriptorList = []
for platformInfo in request.enabledPlatforms:
sourceFileDependency = azlmbr.asset.builder.SourceFileDependency()
sourceFileDependency.sourceFileDependencyPath = fbxSidecarFilename
jobDependency = azlmbr.asset.builder.JobDependency()
jobDependency.sourceFile = sourceFileDependency
jobDependency.jobKey = jobKeyName
jobDependency.platformIdentifier = platformInfo.identifier
jobDesc = azlmbr.asset.builder.JobDescriptor()
jobDesc.jobKey = jobKeyName
jobDesc.set_platform_identifier(platformInfo.identifier)
jobDesc.jobDependencyList = [jobDependency]
jobDescriptorList.append(jobDesc)
response = azlmbr.asset.builder.CreateJobsResponse()
response.result = azlmbr.asset.builder.CreateJobsResponse_ResultSuccess
response.createJobOutputs = jobDescriptorList
return response
# to create jobs for a source asset
def on_create_jobs(args):
try:
request = args[0]
return create_jobs(request)
except:
log_exception_traceback()
return azlmbr.asset.builder.CreateJobsResponse()
def generate_assetinfo_product(request):
# write out a product asset file with the extension of .fbx.assetinfo.generated
basePath, sceneFile = os.path.split(request.sourceFile)
assetinfoFilename = os.path.splitext(sceneFile)[0] + '.fbx.assetinfo.generated'
assetinfoFilename = os.path.join(basePath, assetinfoFilename)
assetinfoFilename = assetinfoFilename.replace('\\', '/').lower()
outputFilename = os.path.join(request.tempDirPath, assetinfoFilename)
# the only rule in it is to run this file again as a scene processor
currentScript = pathlib.Path(__file__).resolve()
aDict = {"values": [{"$type": "ScriptProcessorRule", "scriptFilename": f"{currentScript}"}]}
jsonString = json.dumps(aDict)
jsonFile = open(outputFilename, "w")
jsonFile.write(jsonString)
jsonFile.close()
# return a job product for the generated assetinfo file
sceneManifestType = azlmbr.math.Uuid_CreateString('{9274AD17-3212-4651-9F3B-7DCCB080E467}', 0)
subId = 1
product = azlmbr.asset.builder.JobProduct(outputFilename, sceneManifestType, subId)
product.dependenciesHandled = True
return product
def process_fbx_file(request):
# fill out response object
response = azlmbr.asset.builder.ProcessJobResponse()
productOutputs = []
# prepare output folder
basePath, _ = os.path.split(request.sourceFile)
outputPath = os.path.join(request.tempDirPath, basePath)
os.makedirs(outputPath)
# create assetinfo generated file
productOutputs.append(generate_assetinfo_product(request))
response.outputProducts = productOutputs
response.resultCode = azlmbr.asset.builder.ProcessJobResponse_Success
response.dependenciesHandled = True
return response
# using the incoming 'request' find the type of job via 'jobKey' to determine what to do
def on_process_job(args):
try:
request = args[0]
if (request.jobDescription.jobKey.startswith(jobKeyName)):
return process_fbx_file(request)
return azlmbr.asset.builder.ProcessJobResponse()
except:
log_exception_traceback()
return azlmbr.asset.builder.ProcessJobResponse()
# register asset builder
def register_asset_builder():
assetPattern = azlmbr.asset.builder.AssetBuilderPattern()
assetPattern.pattern = '*.blast'
assetPattern.type = azlmbr.asset.builder.AssetBuilderPattern_Wildcard
builderDescriptor = azlmbr.asset.builder.AssetBuilderDesc()
builderDescriptor.name = "Blast Scene Builder"
builderDescriptor.patterns = [assetPattern]
builderDescriptor.busId = busId
builderDescriptor.version = 1
outcome = azlmbr.asset.builder.PythonAssetBuilderRequestBus(azlmbr.bus.Broadcast, 'RegisterAssetBuilder', builderDescriptor)
if outcome.IsSuccess():
# created the asset builder to hook into the notification bus
handler = azlmbr.asset.builder.PythonBuilderNotificationBusHandler()
handler.connect(busId)
handler.add_callback('OnCreateJobsRequest', on_create_jobs)
handler.add_callback('OnProcessJobRequest', on_process_job)
return handler
# create the asset builder handler
pythonAssetBuilderHandler = None
try:
if (pythonAssetBuilderHandler == None):
pythonAssetBuilderHandler = register_asset_builder()
except:
pythonAssetBuilderHandler = None
#
# SceneAPI Processor
#
blastChunksAssetType = azlmbr.math.Uuid_CreateString('{993F0B0F-37D9-48C6-9CC2-E27D3F3E343E}', 0)
def export_chunk_asset(scene, outputDirectory, platformIdentifier, productList):
import azlmbr.scene
import azlmbr.object
import azlmbr.paths
import json, os
jsonFilename = os.path.basename(scene.sourceFilename)
jsonFilename = os.path.join(outputDirectory, jsonFilename + '.blast_chunks')
# prepare output folder
basePath, _ = os.path.split(jsonFilename)
outputPath = os.path.join(outputDirectory, basePath)
if not os.path.exists(outputPath):
os.makedirs(outputPath, False)
# write out a JSON file with the chunk file info
with open(jsonFilename, "w") as jsonFile:
jsonFile.write(scene.manifest.ExportToJson())
exportProduct = azlmbr.scene.ExportProduct()
exportProduct.filename = jsonFilename
exportProduct.sourceId = scene.sourceGuid
exportProduct.assetType = blastChunksAssetType
exportProduct.subId = 101
exportProductList = azlmbr.scene.ExportProductList()
exportProductList.AddProduct(exportProduct)
return exportProductList
def on_prepare_for_export(args):
try:
scene = args[0] # azlmbr.scene.Scene
outputDirectory = args[1] # string
platformIdentifier = args[2] # string
productList = args[3] # azlmbr.scene.ExportProductList
return export_chunk_asset(scene, outputDirectory, platformIdentifier, productList)
except:
log_exception_traceback()
def get_mesh_node_names(sceneGraph):
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
meshDataList = []
node = sceneGraph.get_root()
children = []
while node.IsValid():
# store children to process after siblings
if sceneGraph.has_node_child(node):
children.append(sceneGraph.get_node_child(node))
# store any node that has mesh data content
nodeContent = sceneGraph.get_node_content(node)
if nodeContent is not None and nodeContent.CastWithTypeName('MeshData'):
if sceneGraph.is_node_end_point(node) is False:
nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
nodePath = nodeName.get_path()
if (len(nodeName.get_path())):
meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
# advance to next node
if sceneGraph.has_node_sibling(node):
node = sceneGraph.get_node_sibling(node)
elif children:
node = children.pop()
else:
node = azlmbr.scene.graph.NodeIndex()
return meshDataList
def update_manifest(scene):
import uuid, os
import azlmbr.scene as sceneApi
import azlmbr.scene.graph
from scene_api import scene_data as sceneData
graph = sceneData.SceneGraph(scene.graph)
meshNameList = get_mesh_node_names(graph)
sceneManifest = sceneData.SceneManifest()
sourceFilenameOnly = os.path.basename(scene.sourceFilename)
sourceFilenameOnly = sourceFilenameOnly.replace('.','_')
for activeMeshIndex in range(len(meshNameList)):
chunkName = meshNameList[activeMeshIndex]
chunkPath = chunkName.get_path()
meshGroupName = '{}_{}'.format(sourceFilenameOnly, chunkName.get_name())
meshGroup = sceneManifest.add_mesh_group(meshGroupName)
meshGroup['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, sourceFilenameOnly + chunkPath)) + '}'
sceneManifest.mesh_group_select_node(meshGroup, chunkPath)
return sceneManifest.export()
sceneJobHandler = None
def on_update_manifest(args):
try:
scene = args[0]
return update_manifest(scene)
except:
global sceneJobHandler
sceneJobHandler = None
log_exception_traceback()
# try to create SceneAPI handler for processing
try:
import azlmbr.scene as sceneApi
if (sceneJobHandler == None):
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
sceneJobHandler.connect()
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
sceneJobHandler.add_callback('OnPrepareForExport', on_prepare_for_export)
except:
sceneJobHandler = None
+10 -3
View File
@@ -4,6 +4,13 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
# LYN-652 to re-enable once the Blast gem tests are stable
# import asset_builder_blast
try:
import azlmbr.asset
import azlmbr.asset.entity
import azlmbr.asset.builder
import blast_asset_builder
except:
# this script only runs in an asset processing environment
# like the AssetProcessor or an AssetBuilder
# plus the Blast gem needs to be enabled for the project
pass