Atom Tools: Document system exposes reflected object data

• This removes a direct dependency on dynamic property groups and data from the document system.
• Added support for names, descriptions, and nesting to dynamic property groups.
• Moved property related functions from base document classes into material editor document classes because dynamic property groups are an implementation detail of the material editor document to support material type flexible data format.
• Change material document to use a table of dynamic property groups instead of a map of properties.
• Added functions to traverse groups and properties.
• This keeps groups and properties organized consistently with the material type file as well as what’s expected in the UI.
• Document data can now be maps directly to the inspector reflective property editors instead of copying one property at a time out of the document and keeping those synchronized.

Signed-off-by: Guthrie Adams <guthadam@amazon.com>
monroegm-disable-blank-issue-2
Guthrie Adams 4 years ago
parent 1e11df5d92
commit c9c172794e

@ -114,11 +114,11 @@ def get_property(document_id, property_name):
"""
:return: property value or invalid value if the document is not open or the property_name can't be found
"""
return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name)
return azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name)
def set_property(document_id, property_name, value):
azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value)
azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value)
def is_pane_visible(pane_name):

@ -32,10 +32,7 @@ namespace AtomToolsFramework
// AtomToolsDocumentRequestBus::Handler overrides...
AZStd::string_view GetAbsolutePath() const override;
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
AZStd::vector<DocumentObjectInfo> GetObjectInfo() const override;
bool Open(AZStd::string_view loadPath) override;
bool Reopen() override;
bool Save() override;
@ -78,10 +75,6 @@ namespace AtomToolsFramework
//! The normalized, absolute path where the document will be saved.
AZStd::string m_savePathNormalized;
AZStd::any m_invalidValue;
AtomToolsFramework::DynamicProperty m_invalidProperty;
//! This contains absolute paths of other source files that affect this document.
//! If any of the source files in this container are modified, the document system is notified to reload this document.
AZStd::unordered_set<AZStd::string> m_sourceDependencies;

@ -10,11 +10,10 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/any.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
namespace AtomToolsFramework
{
struct DocumentObjectInfo;
class AtomToolsDocumentNotifications
: public AZ::EBusTraits
{
@ -62,21 +61,11 @@ namespace AtomToolsFramework
//! @param documentId unique id of document for which the notification is sent
virtual void OnDocumentUndoStateChanged([[maybe_unused]] const AZ::Uuid& documentId) {}
//! Signal that a property changed
//! @param documentId unique id of document for which the notification is sent
//! @param property object containing the property value and configuration that was modified
virtual void OnDocumentPropertyValueModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {}
//! Signal that the property configuration has been changed.
//! @param documentId unique id of document for which the notification is sent
//! @param property object containing the property value and configuration that was modified
virtual void OnDocumentPropertyConfigModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {}
//! Signal that the property group visibility has been changed.
//! Signal that the group has been changed.
//! @param documentId unique id of document for which the notification is sent
//! @param groupId id of the group that changed
//! @param visible whether the property group is visible
virtual void OnDocumentPropertyGroupVisibilityChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AZ::Name& groupId, [[maybe_unused]] bool visible) {}
//! @param objectInfo description of the reflected object that's been modified
//! @param rebuilt signifies if it was a structural change that might require ui to be rebuilt
virtual void OnDocumentObjectInfoChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const DocumentObjectInfo& objectInfo, [[maybe_unused]] bool rebuilt) {}
};
using AtomToolsDocumentNotificationBus = AZ::EBus<AtomToolsDocumentNotifications>;

@ -14,6 +14,20 @@
namespace AtomToolsFramework
{
//! Structure used as an opaque description of objects reflected inside of a document.
//! An example use would be semi automatic handling of serialization or reflection to a property editor.
struct DocumentObjectInfo
{
bool m_visible = true;
AZStd::string m_name;
AZStd::string m_displayName;
AZStd::string m_description;
AZ::Uuid m_objectType = AZ::Uuid::CreateNull();
void* m_objectPtr = {};
};
//! This bus provides the most basic interface for implementing a document that works with the document system.
//! Any extensions or application specific functionality should be added using a domain specific buses.
class AtomToolsDocumentRequests
: public AZ::EBusTraits
{
@ -25,20 +39,8 @@ namespace AtomToolsFramework
//! Get absolute path of document
virtual AZStd::string_view GetAbsolutePath() const = 0;
//! Return property value
//! If the document is not open or the id can't be found, an invalid value is returned instead.
virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0;
//! Returns a property object
//! If the document is not open or the id can't be found, an invalid property is returned.
virtual const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const = 0;
//! Returns whether a property group is visible
//! If the document is not open or the id can't be found, returns false.
virtual bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const = 0;
//! Modify document property value
virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0;
//! Returns a container describing all reflected objects contained in a document
virtual AZStd::vector<DocumentObjectInfo> GetObjectInfo() const = 0;
//! Load document and related data
//! @param loadPath absolute path of document to load

@ -25,11 +25,11 @@ namespace AtomToolsFramework
//! Register a document factory function used to create specific document types
virtual void RegisterDocumentType(AZStd::function<AtomToolsDocument*()> documentCreator) = 0;
//! Create a document object
//! Create a document
//! @return Uuid of new document, or null Uuid if failed
virtual AZ::Uuid CreateDocument() = 0;
//! Destroy a document object with the specified id
//! Destroy a document with the specified id
//! @return true if Uuid was found and removed, otherwise false
virtual bool DestroyDocument(const AZ::Uuid& documentId) = 0;

@ -46,6 +46,7 @@ namespace AtomToolsFramework
AZStd::string m_name;
AZStd::string m_displayName;
AZStd::string m_groupName;
AZStd::string m_groupDisplayName;
AZStd::string m_description;
AZStd::any m_defaultValue;
AZStd::any m_parentValue;
@ -60,6 +61,7 @@ namespace AtomToolsFramework
bool m_visible = true;
bool m_readOnly = false;
bool m_showThumbnail = false;
AZStd::function<AZ::u32(const AZStd::any&)> m_dataChangeCallback;
};
//! Wraps an AZStd::any value and configuration so that it can be displayed and edited in a ReflectedPropertyEditor.
@ -106,7 +108,7 @@ namespace AtomToolsFramework
private:
// Functions used to configure edit data attributes.
AZStd::string GetDisplayName() const;
AZStd::string GetGroupName() const;
AZStd::string GetGroupDisplayName() const;
AZStd::string GetAssetPickerTitle() const;
AZStd::string GetDescription() const;
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const;

@ -9,6 +9,7 @@
#pragma once
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
namespace AtomToolsFramework
@ -22,6 +23,11 @@ namespace AtomToolsFramework
static void Reflect(AZ::ReflectContext* context);
bool m_visible = true;
AZStd::string m_name;
AZStd::string m_displayName;
AZStd::string m_description;
AZStd::vector<AtomToolsFramework::DynamicProperty> m_properties;
AZStd::vector<AZStd::shared_ptr<DynamicPropertyGroup>> m_groups;
};
} // namespace AtomToolsFramework

@ -36,32 +36,10 @@ namespace AtomToolsFramework
return m_absolutePath;
}
const AZStd::any& AtomToolsDocument::GetPropertyValue([[maybe_unused]] const AZ::Name& propertyId) const
AZStd::vector<DocumentObjectInfo> AtomToolsDocument::GetObjectInfo() const
{
AZ_UNUSED(propertyId);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return m_invalidValue;
}
const AtomToolsFramework::DynamicProperty& AtomToolsDocument::GetProperty([[maybe_unused]] const AZ::Name& propertyId) const
{
AZ_UNUSED(propertyId);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return m_invalidProperty;
}
bool AtomToolsDocument::IsPropertyGroupVisible([[maybe_unused]] const AZ::Name& propertyGroupFullName) const
{
AZ_UNUSED(propertyGroupFullName);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}
void AtomToolsDocument::SetPropertyValue([[maybe_unused]] const AZ::Name& propertyId, [[maybe_unused]] const AZStd::any& value)
{
AZ_UNUSED(propertyId);
AZ_UNUSED(value);
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return AZStd::vector<DocumentObjectInfo>();
}
bool AtomToolsDocument::Open(AZStd::string_view loadPath)
@ -256,13 +234,13 @@ namespace AtomToolsFramework
bool AtomToolsDocument::BeginEdit()
{
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}
bool AtomToolsDocument::EndEdit()
{
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}

@ -77,8 +77,6 @@ namespace AtomToolsFramework
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "atomtools")
->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath)
->Event("GetPropertyValue", &AtomToolsDocumentRequestBus::Events::GetPropertyValue)
->Event("SetPropertyValue", &AtomToolsDocumentRequestBus::Events::SetPropertyValue)
->Event("Open", &AtomToolsDocumentRequestBus::Events::Open)
->Event("Reopen", &AtomToolsDocumentRequestBus::Events::Reopen)
->Event("Close", &AtomToolsDocumentRequestBus::Events::Close)

@ -195,14 +195,14 @@ namespace AtomToolsFramework
return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name;
}
AZStd::string DynamicProperty::GetGroupName() const
AZStd::string DynamicProperty::GetGroupDisplayName() const
{
return m_config.m_groupName;
return m_config.m_groupDisplayName;
}
AZStd::string DynamicProperty::GetAssetPickerTitle() const
{
return GetGroupName().empty() ? GetDisplayName() : GetGroupName() + " " + GetDisplayName();
return GetGroupDisplayName().empty() ? GetDisplayName() : GetGroupDisplayName() + " " + GetDisplayName();
}
AZStd::string DynamicProperty::GetDescription() const
@ -235,6 +235,10 @@ namespace AtomToolsFramework
AZ::u32 DynamicProperty::OnDataChanged() const
{
if (m_config.m_dataChangeCallback)
{
return m_config.m_dataChangeCallback(GetValue());
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}

@ -17,7 +17,12 @@ namespace AtomToolsFramework
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<DynamicPropertyGroup>()
->Field("visible", &DynamicPropertyGroup::m_visible)
->Field("name", &DynamicPropertyGroup::m_name)
->Field("displayName", &DynamicPropertyGroup::m_displayName)
->Field("description", &DynamicPropertyGroup::m_description)
->Field("properties", &DynamicPropertyGroup::m_properties)
->Field("groups", &DynamicPropertyGroup::m_groups)
;
if (auto editContext = serializeContext->GetEditContext())
@ -28,6 +33,9 @@ namespace AtomToolsFramework
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_properties, "properties", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_properties row
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only
->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_groups, "groups", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_groups row
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only
;
}
}

@ -53,112 +53,93 @@ namespace MaterialEditor
return &m_materialTypeSourceData;
}
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value)
{
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Document is not open.");
return m_invalidValue;
return;
}
const auto it = m_properties.find(propertyId);
if (it == m_properties.end())
{
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidValue;
}
AtomToolsFramework::DynamicProperty* foundProperty = {};
TraverseGroups(m_groups, [&, this](auto& group) {
for (auto& property : group->m_properties)
{
if (property.GetId() == propertyId)
{
foundProperty = &property;
const AtomToolsFramework::DynamicProperty& property = it->second;
return property.GetValue();
}
// This first converts to an acceptable runtime type in case the value came from script
const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
const AtomToolsFramework::DynamicProperty& MaterialDocument::GetProperty(const AZ::Name& propertyId) const
{
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Document is not open.");
return m_invalidProperty;
}
property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue));
const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId);
if (!propertyIndex.IsNull())
{
if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue))
{
AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
Recompile();
RunEditorMaterialFunctors(dirtyFlags);
}
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id,
GetObjectInfoFromDynamicPropertyGroup(group.get()), false);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
return false;
}
}
return true;
});
const auto it = m_properties.find(propertyId);
if (it == m_properties.end())
if (!foundProperty)
{
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidProperty;
}
const AtomToolsFramework::DynamicProperty& property = it->second;
return property;
}
bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
{
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Document is not open.");
return false;
return m_invalidValue;
}
const auto it = m_propertyGroupVisibility.find(propertyGroupFullName);
if (it == m_propertyGroupVisibility.end())
auto property = FindProperty(propertyId);
if (!property)
{
AZ_Error("MaterialDocument", false, "Document property group could not be found: '%s'.", propertyGroupFullName.GetCStr());
return false;
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidValue;
}
return it->second;
return property->GetValue();
}
void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value)
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> MaterialDocument::GetObjectInfo() const
{
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Document is not open.");
return;
return {};
}
const auto it = m_properties.find(propertyId);
if (it == m_properties.end())
{
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return;
}
// This first converts to an acceptable runtime type in case the value came from script
const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
AtomToolsFramework::DynamicProperty& property = it->second;
property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue));
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
objects.reserve(m_groups.size());
const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId);
if (!propertyIndex.IsNull())
AtomToolsFramework::DocumentObjectInfo objectInfo;
for (const auto& group : m_groups)
{
if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue))
{
AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
Recompile();
EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags);
for (const AZ::Name& changedPropertyGroupName : result.m_updatedPropertyGroups)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id,
changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName));
}
for (const AZ::Name& changedPropertyName : result.m_updatedProperties)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id,
GetProperty(changedPropertyName));
}
}
objects.push_back(GetObjectInfoFromDynamicPropertyGroup(group.get()));
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
return objects;
}
bool MaterialDocument::Save()
@ -185,14 +166,15 @@ namespace MaterialEditor
}
// after saving, reset to a clean state
for (auto& propertyPair : m_properties)
{
AtomToolsFramework::DynamicProperty& property = propertyPair.second;
auto propertyConfig = property.GetConfig();
propertyConfig.m_originalValue = property.GetValue();
property.SetConfig(propertyConfig);
}
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
auto propertyConfig = property.GetConfig();
propertyConfig.m_originalValue = property.GetValue();
property.SetConfig(propertyConfig);
}
return true;
});
return SaveSucceeded();
}
@ -273,12 +255,19 @@ namespace MaterialEditor
bool MaterialDocument::IsModified() const
{
return AZStd::any_of(m_properties.begin(), m_properties.end(),
[](const auto& propertyPair)
{
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue);
bool result = false;
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue))
{
result = true;
return false;
}
}
return true;
});
return result;
}
bool MaterialDocument::IsSavable() const
@ -290,11 +279,13 @@ namespace MaterialEditor
{
// Save the current properties as a momento for undo before any changes are applied
m_propertyValuesBeforeEdit.clear();
for (const auto& propertyPair : m_properties)
{
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue();
}
TraverseGroups(m_groups, [this](auto& group) {
for (auto& property : group->m_properties)
{
m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue();
}
return true;
});
return true;
}
@ -348,10 +339,10 @@ namespace MaterialEditor
AZ::Name propertyId{propertyIdContext + propertyDefinition->GetName()};
const auto it = m_properties.find(propertyId);
if (it != m_properties.end() && propertyFilter(it->second))
const auto property = FindProperty(propertyId);
if (property && propertyFilter(*property))
{
AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue());
AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(property->GetValue());
if (propertyValue.IsValid())
{
if (!AtomToolsFramework::ConvertToExportFormat(m_savePathNormalized, propertyId, *propertyDefinition, propertyValue))
@ -394,52 +385,17 @@ namespace MaterialEditor
// The material document and inspector are constructed from source data
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{
// Load the material source data so that we can check properties and create a material asset from it
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData))
{
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
if (!m_materialSourceData.m_materialType.empty())
{
m_materialSourceData.m_materialType =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
if (!LoadMaterialSourceData())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return OpenFailed();
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
}
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialTypeSourceData::Extension))
{
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
if (!LoadMaterialTypeSourceData())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
}
else
{
@ -519,58 +475,21 @@ namespace MaterialEditor
// where such changes are supported at runtime.
m_materialInstance->SetPsoHandlingOverride(AZ::RPI::MaterialPropertyPsoHandling::Allowed);
// Populate the property map from a combination of source data and assets
// Assets must still be used for now because they contain the final accumulated value after all other materials
// in the hierarchy are applied
m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyIdContext + propertyGroup->GetName() + "." + propertyDefinition->GetName();
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size();
AZ_Warning("MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", propertyConfig.m_id.GetCStr(), m_absolutePath.c_str());
if (propertyIndexInBounds)
{
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
propertyConfig.m_showThumbnail = true;
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]);
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupName?)
propertyConfig.m_groupName = propertyGroup->GetDisplayName();
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
}
}
return true;
});
// Populate the property group visibility map
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups)
{
m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true;
}
// Adding properties for material type and parent as part of making dynamic properties and the inspector more general purpose. This
// allows the read only properties to appear in the inspector like any other property. This may change or be removed once support
// for changing the material parent is implemented.
m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = "overview";
m_groups.back()->m_displayName = "Overview";
m_groups.back()->m_description = m_materialSourceData.m_description;
// Adding properties for material type and parent as part of making dynamic
// properties and the inspector more general purpose.
// This allows the read only properties to appear in the inspector like any
// other property.
// This may change or be removed once support for changing the material parent
// is implemented.
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "overview.materialType";
propertyConfig.m_name = "materialType";
propertyConfig.m_displayName = "Material Type";
propertyConfig.m_groupName = "Overview";
propertyConfig.m_groupName = "overview";
propertyConfig.m_groupDisplayName = "Overview";
propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other "
"data needed to create and edit a derived material.";
propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset);
@ -578,14 +497,15 @@ namespace MaterialEditor
propertyConfig.m_parentValue = propertyConfig.m_defaultValue;
propertyConfig.m_readOnly = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
propertyConfig = {};
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset;
propertyConfig.m_id = "overview.parentMaterial";
propertyConfig.m_name = "parentMaterial";
propertyConfig.m_displayName = "Parent Material";
propertyConfig.m_groupName = "Overview";
propertyConfig.m_groupName = "overview";
propertyConfig.m_groupDisplayName = "Overview";
propertyConfig.m_description =
"The parent material provides an initial configuration whose properties are inherited and overriden by a derived material.";
propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset);
@ -594,9 +514,14 @@ namespace MaterialEditor
propertyConfig.m_readOnly = true;
propertyConfig.m_showThumbnail = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = UvGroupName;
m_groups.back()->m_displayName = "UV Sets";
m_groups.back()->m_description = "UV set names in this material, which can be renamed to match those in the model.";
//Add UV name customization properties
// Add UV name customization properties
const AZ::RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap();
for (const AZ::RPI::UvNamePair& uvNamePair : uvNameMap)
{
@ -608,61 +533,69 @@ namespace MaterialEditor
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(UvGroupName, shaderInput);
propertyConfig.m_name = shaderInput;
propertyConfig.m_displayName = shaderInput;
propertyConfig.m_groupName = "UV Sets";
propertyConfig.m_groupName = UvGroupName;
propertyConfig.m_groupDisplayName = "UV Sets";
propertyConfig.m_description = shaderInput;
propertyConfig.m_defaultValue = uvName;
propertyConfig.m_originalValue = uvName;
propertyConfig.m_parentValue = uvName;
propertyConfig.m_readOnly = true;
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
}
// Add material functors that are in the top-level functors list.
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext);
if (result2.IsSuccess())
// Populate the property map from a combination of source data and assets
// Assets must still be used for now because they contain the final accumulated value after all other materials
// in the hierarchy are applied
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this, &parentPropertyValues](
const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result2.GetValue();
if (functor != nullptr)
// Add any material functors that are located inside each property group.
if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors()))
{
m_editorFunctors.push_back(functor);
return false;
}
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
}
// Add any material functors that are located inside each property group.
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this](const AZStd::string&, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = propertyIdContext + propertyGroup->GetName();
m_groups.back()->m_displayName = propertyGroup->GetDisplayName();
m_groups.back()->m_description = propertyGroup->GetDescription();
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors())
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
// Assign id before conversion so it can be used in dynamic description
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
propertyConfig.m_id = m_groups.back()->m_name + "." + propertyDefinition->GetName();
const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
const bool propertyIndexInBounds =
propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size();
AZ_Warning(
"MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.",
propertyConfig.m_id.GetCStr(), m_absolutePath.c_str());
if (result.IsSuccess())
if (propertyIndexInBounds)
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue();
if (functor != nullptr)
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
// (Does DynamicPropertyConfig really even need m_groupDisplayName?)
propertyConfig.m_groupName = m_groups.back()->m_name;
propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName;
propertyConfig.m_showThumbnail = true;
propertyConfig.m_originalValue =
AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_parentValue =
AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]);
propertyConfig.m_dataChangeCallback = [documentId = m_id, propertyId = propertyConfig.m_id](const AZStd::any& value)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return false;
MaterialDocumentRequestBus::Event(
documentId, &MaterialDocumentRequestBus::Events::SetPropertyValue, propertyId, value);
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
};
m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig));
}
}
@ -674,6 +607,12 @@ namespace MaterialEditor
return OpenFailed();
}
// Add material functors that are in the top-level functors list.
if (!AddEditorMaterialFunctors(m_materialTypeSourceData.m_materialFunctorSourceData))
{
return OpenFailed();
}
AZ::RPI::MaterialPropertyFlags dirtyFlags;
dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
RunEditorMaterialFunctors(dirtyFlags);
@ -681,17 +620,35 @@ namespace MaterialEditor
return OpenSucceeded();
}
void MaterialDocument::Clear()
{
AtomToolsFramework::AtomToolsDocument::Clear();
AZ::TickBus::Handler::BusDisconnect();
m_materialAsset = {};
m_materialInstance = {};
m_compilePending = {};
m_groups.clear();
m_editorFunctors.clear();
m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData();
m_materialSourceData = AZ::RPI::MaterialSourceData();
m_propertyValuesBeforeEdit.clear();
}
bool MaterialDocument::ReopenRecordState()
{
m_propertyValuesBeforeReopen.clear();
for (const auto& propertyPair : m_properties)
{
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
TraverseGroups(m_groups, [this](auto& group) {
for (auto& property : group->m_properties)
{
m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue();
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
{
m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue();
}
}
}
return true;
});
return AtomToolsDocument::ReopenRecordState();
}
@ -711,20 +668,59 @@ namespace MaterialEditor
}
}
void MaterialDocument::Clear()
bool MaterialDocument::LoadMaterialSourceData()
{
AtomToolsFramework::AtomToolsDocument::Clear();
// Load the material source data so that we can check properties and create a material asset from it
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData))
{
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false;
}
AZ::TickBus::Handler::BusDisconnect();
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
m_materialAsset = {};
m_materialInstance = {};
m_compilePending = {};
m_properties.clear();
m_editorFunctors.clear();
m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData();
m_materialSourceData = AZ::RPI::MaterialSourceData();
m_propertyValuesBeforeEdit.clear();
if (!m_materialSourceData.m_materialType.empty())
{
m_materialSourceData.m_materialType =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
return true;
}
bool MaterialDocument::LoadMaterialTypeSourceData()
{
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.TakeValue();
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
return true;
}
void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues)
@ -737,60 +733,186 @@ namespace MaterialEditor
}
}
MaterialDocument::EditorMaterialFunctorResult MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags)
bool MaterialDocument::AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders)
{
EditorMaterialFunctorResult result;
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyDynamicMetadata> propertyDynamicMetadata;
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyGroupDynamicMetadata> propertyGroupDynamicMetadata;
for (auto& propertyPair : m_properties)
{
AtomToolsFramework::DynamicProperty& property = propertyPair.second;
AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig());
}
for (auto& groupPair : m_propertyGroupVisibility)
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : functorSourceDataHolders)
{
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}];
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
bool visible = groupPair.second;
metadata.m_visibility = visible ?
AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
if (result.IsSuccess())
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue();
if (functor != nullptr)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return false;
}
}
return true;
}
void MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags)
{
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyDynamicMetadata> propertyDynamicMetadata;
AZStd::unordered_map<AZ::Name, AZ::RPI::MaterialPropertyGroupDynamicMetadata> propertyGroupDynamicMetadata;
TraverseGroups(m_groups, [&](auto& group) {
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }];
metadata.m_visibility = group->m_visible ? AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
for (auto& property : group->m_properties)
{
AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig());
}
return true;
});
AZStd::unordered_set<AZ::Name> updatedProperties;
AZStd::unordered_set<AZ::Name> updatedPropertyGroups;
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor : m_editorFunctors)
{
const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies();
// None also covers case that the client code doesn't register material properties to dependencies,
// which will later get caught in Process() when trying to access a property.
if (materialPropertyDependencies.none() || functor->NeedsProcess(dirtyFlags))
{
AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext(
m_materialInstance->GetPropertyValues(),
m_materialInstance->GetMaterialPropertiesLayout(),
propertyDynamicMetadata,
propertyGroupDynamicMetadata,
result.m_updatedProperties,
result.m_updatedPropertyGroups,
&materialPropertyDependencies
);
m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata,
propertyGroupDynamicMetadata, updatedProperties, updatedPropertyGroups,
&materialPropertyDependencies);
functor->Process(context);
}
}
for (auto& propertyPair : m_properties)
TraverseGroups(m_groups, [&](auto& group) {
bool groupChange = false;
bool groupRebuilt = false;
if (updatedPropertyGroups.find(AZ::Name(group->m_name)) != updatedPropertyGroups.end())
{
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }];
group->m_visible = metadata.m_visibility != AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
groupChange = true;
}
for (auto& property : group->m_properties)
{
if (updatedProperties.find(AZ::Name(property.GetId())) != updatedProperties.end())
{
const bool visibleBefore = property.GetConfig().m_visible;
AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig();
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]);
property.SetConfig(propertyConfig);
groupChange = true;
groupRebuilt |= visibleBefore != property.GetConfig().m_visible;
}
}
if (groupChange || groupRebuilt)
{
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id,
GetObjectInfoFromDynamicPropertyGroup(group.get()), groupRebuilt);
}
return true;
});
}
AtomToolsFramework::DocumentObjectInfo MaterialDocument::GetObjectInfoFromDynamicPropertyGroup(
const AtomToolsFramework::DynamicPropertyGroup* group) const
{
AtomToolsFramework::DocumentObjectInfo objectInfo;
objectInfo.m_visible = group->m_visible;
objectInfo.m_name = group->m_name;
objectInfo.m_displayName = group->m_displayName;
objectInfo.m_description = group->m_description;
objectInfo.m_objectType = azrtti_typeid<AtomToolsFramework::DynamicPropertyGroup>();
objectInfo.m_objectPtr = const_cast<AtomToolsFramework::DynamicPropertyGroup*>(group);
return objectInfo;
}
bool MaterialDocument::TraverseGroups(
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback)
{
if (!callback)
{
AtomToolsFramework::DynamicProperty& property = propertyPair.second;
AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig();
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]);
property.SetConfig(propertyConfig);
return false;
}
for (auto& updatedPropertyGroup : result.m_updatedPropertyGroups)
for (auto& group : groups)
{
bool visible = propertyGroupDynamicMetadata[updatedPropertyGroup].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled;
m_propertyGroupVisibility[updatedPropertyGroup] = visible;
if (!callback(group) || !TraverseGroups(group->m_groups, callback))
{
return false;
}
}
return true;
}
bool MaterialDocument::TraverseGroups(
const AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(const AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback) const
{
if (!callback)
{
return false;
}
for (auto& group : groups)
{
if (!callback(group) || !TraverseGroups(group->m_groups, callback))
{
return false;
}
}
return true;
}
AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId)
{
AtomToolsFramework::DynamicProperty* result = nullptr;
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
if (property.GetId() == propertyId)
{
result = &property;
return false;
}
}
return true;
});
return result;
}
const AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId) const
{
AtomToolsFramework::DynamicProperty* result = nullptr;
TraverseGroups(m_groups, [&](auto& group) {
for (auto& property : group->m_properties)
{
if (property.GetId() == propertyId)
{
result = &property;
return false;
}
}
return true;
});
return result;
}
} // namespace MaterialEditor

@ -37,10 +37,7 @@ namespace MaterialEditor
virtual ~MaterialDocument();
// AtomToolsFramework::AtomToolsDocument overrides...
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> GetObjectInfo() const override;
bool Open(AZStd::string_view loadPath) override;
bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override;
@ -56,21 +53,17 @@ namespace MaterialEditor
AZ::Data::Instance<AZ::RPI::Material> GetInstance() const override;
const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override;
const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
private:
// Predicate for evaluating properties
using PropertyFilterFunction = AZStd::function<bool(const AtomToolsFramework::DynamicProperty&)>;
// Map of document's properties
using PropertyMap = AZStd::unordered_map<AZ::Name, AtomToolsFramework::DynamicProperty>;
// Map of raw property values for undo/redo comparison and storage
using PropertyValueMap = AZStd::unordered_map<AZ::Name, AZStd::any>;
// Map of document's property group visibility flags
using PropertyGroupVisibilityMap = AZStd::unordered_map<AZ::Name, bool>;
// AZ::TickBus overrides...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
@ -78,47 +71,60 @@ namespace MaterialEditor
// AtomToolsFramework::AtomToolsDocument overrides...
void Clear() override;
bool ReopenRecordState() override;
bool ReopenRestoreState() override;
void Recompile();
bool LoadMaterialSourceData();
bool LoadMaterialTypeSourceData();
void RestorePropertyValues(const PropertyValueMap& propertyValues);
struct EditorMaterialFunctorResult
{
AZStd::unordered_set<AZ::Name> m_updatedProperties;
AZStd::unordered_set<AZ::Name> m_updatedPropertyGroups;
};
bool AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders);
// Run editor material functor to update editor metadata.
// @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run.
// @return names for the set of properties and groups that have been changed or need update.
EditorMaterialFunctorResult RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags);
void RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags);
// Convert a dynamic property group pointer into generic document object info used to populate the inspector
AtomToolsFramework::DocumentObjectInfo GetObjectInfoFromDynamicPropertyGroup(
const AtomToolsFramework::DynamicPropertyGroup* group) const;
// Underlying material asset
// In order traversal of dynamic property groups
bool TraverseGroups(
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback);
// In order traversal of dynamic property groups
bool TraverseGroups(
const AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>>& groups,
AZStd::function<bool(const AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>&)> callback) const;
// Traverses dynamic property groups to find a property with a specific ID
AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId);
// Traverses dynamic property groups to find a property with a specific ID
const AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId) const;
// Material asset generated from source data, used to get the final values for properties to be assigned to the document
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
// Material instance being edited
// Material instance is only needed to run editor functors and is assigned directly to the viewport model to reflect real time
// changes to material property values
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
// If material instance value(s) were modified, do we need to recompile on next tick?
bool m_compilePending = false;
// Collection of all material's properties
PropertyMap m_properties;
// Collection of all material's property groups
PropertyGroupVisibilityMap m_propertyGroupVisibility;
// Material functors that run in editor. See MaterialFunctor.h for details.
AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors;
// Source data for material type
// Material type source data used to enumerate all properties and populate the document
AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData;
// Source data for material
// Material source data with property values that override the material type
AZ::RPI::MaterialSourceData m_materialSourceData;
// State of property values prior to an edit, used for restoration during undo
@ -126,5 +132,12 @@ namespace MaterialEditor
// State of property values prior to reopen
PropertyValueMap m_propertyValuesBeforeReopen;
// A container of root level dynamic property groups that represents the reflected, editable data within the document.
// These groups will be mapped to document object info so they can populate and be edited directly in the inspector.
AZStd::vector<AZStd::shared_ptr<AtomToolsFramework::DynamicPropertyGroup>> m_groups;
// Dummy default value returned whenever a property cannot be located
AZStd::any m_invalidValue;
};
} // namespace MaterialEditor

@ -45,7 +45,14 @@ namespace MaterialEditor
//! Get the internal material type source data
virtual const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const = 0;
};
//! Modify property value
virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0;
//! Return property value
//! If the document is not open or the id can't be found, an invalid value is returned instead.
virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0;
};
using MaterialDocumentRequestBus = AZ::EBus<MaterialDocumentRequests>;
} // namespace MaterialEditor

@ -65,7 +65,8 @@ namespace MaterialEditor
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "materialeditor")
;
->Event("SetPropertyValue", &MaterialDocumentRequestBus::Events::SetPropertyValue)
->Event("GetPropertyValue", &MaterialDocumentRequestBus::Events::GetPropertyValue);
}
}

@ -7,14 +7,10 @@
*/
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
#include <AtomToolsFramework/Inspector/InspectorPropertyGroupWidget.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <Document/MaterialDocumentRequestBus.h>
#include <Window/MaterialInspector/MaterialInspector.h>
namespace MaterialEditor
@ -38,7 +34,7 @@ namespace MaterialEditor
{
m_documentPath.clear();
m_documentId = AZ::Uuid::CreateNull();
m_groups = {};
m_activeProperty = {};
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset();
@ -67,18 +63,31 @@ namespace MaterialEditor
m_documentId = documentId;
bool isOpen = false;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen);
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen);
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
if (!m_documentId.IsNull() && isOpen)
{
// Create the top group for displaying overview info about the material
AddOverviewGroup();
// Create groups for displaying editable UV names
AddUvNamesGroup();
// Create groups for displaying editable properties
AddPropertiesGroup();
// This will automatically expose all document contents to an inspector with a collapsible group per object. In the case of the
// material editor, this will be one inspector group per property group.
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
objects, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetObjectInfo);
for (auto& objectInfo : objects)
{
// Passing in same main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
objectInfo.m_objectPtr, objectInfo.m_objectPtr, objectInfo.m_objectType, this, this,
GetGroupSaveStateKey(objectInfo.m_name), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(objectInfo.m_name, objectInfo.m_displayName, objectInfo.m_description, propertyGroupWidget);
SetGroupVisible(objectInfo.m_name, objectInfo.m_visible);
}
AtomToolsFramework::InspectorRequestBus::Handler::BusConnect(m_documentId);
}
@ -106,198 +115,42 @@ namespace MaterialEditor
return ":/Icons/blank.png";
}
void MaterialInspector::AddOverviewGroup()
{
const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr;
MaterialDocumentRequestBus::EventResult(
materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData);
const AZStd::string groupName = "overview";
const AZStd::string groupDisplayName = "Overview";
const AZStd::string groupDescription = materialTypeSourceData->m_description;
auto& group = m_groups[groupName];
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.materialType"));
group.m_properties.push_back(property);
property = {};
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.parentMaterial"));
group.m_properties.push_back(property);
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
}
void MaterialInspector::AddUvNamesGroup()
void MaterialInspector::OnDocumentObjectInfoChanged(
[[maybe_unused]] const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt)
{
AZ::Data::Asset<AZ::RPI::MaterialAsset> materialAsset;
MaterialDocumentRequestBus::EventResult(materialAsset, m_documentId, &MaterialDocumentRequestBus::Events::GetAsset);
const AZStd::string groupName = UvGroupName;
const AZStd::string groupDisplayName = "UV Sets";
const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model.";
auto& group = m_groups[groupName];
const auto& uvNameMap = materialAsset->GetMaterialTypeAsset()->GetUvNameMap();
group.m_properties.reserve(uvNameMap.size());
for (const auto& uvNamePair : uvNameMap)
SetGroupVisible(objectInfo.m_name, objectInfo.m_visible);
if (rebuilt)
{
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
AZ::RPI::MaterialPropertyId(groupName, uvNamePair.m_shaderInput.ToString()));
group.m_properties.push_back(property);
property.SetValue(property.GetConfig().m_parentValue);
RebuildGroup(objectInfo.m_name);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
}
void MaterialInspector::AddPropertiesGroup()
{
const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr;
MaterialDocumentRequestBus::EventResult(
materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData);
// TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : materialTypeSourceData->GetPropertyLayout().m_propertyGroups)
else
{
const AZStd::string& groupName = propertyGroup->GetName();
const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName;
const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName;
auto& group = m_groups[groupName];
group.m_properties.reserve(propertyGroup->GetProperties().size());
for (const auto& propertyDefinition : propertyGroup->GetProperties())
{
AtomToolsFramework::DynamicProperty property;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty,
AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName()));
group.m_properties.push_back(property);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget);
bool isGroupVisible = false;
AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(
isGroupVisible, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsPropertyGroupVisible, AZ::Name{groupName});
SetGroupVisible(groupName, isGroupVisible);
RefreshGroup(objectInfo.m_name);
}
}
void MaterialInspector::OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property)
{
for (auto& groupPair : m_groups)
{
for (auto& reflectedProperty : groupPair.second.m_properties)
{
if (reflectedProperty.GetId() == property.GetId())
{
if (!AtomToolsFramework::ArePropertyValuesEqual(reflectedProperty.GetValue(), property.GetValue()))
{
reflectedProperty.SetValue(property.GetValue());
AtomToolsFramework::InspectorRequestBus::Event(
documentId, &AtomToolsFramework::InspectorRequestBus::Events::RefreshGroup, groupPair.first);
}
return;
}
}
}
}
void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid&, const AtomToolsFramework::DynamicProperty& property)
{
for (auto& groupPair : m_groups)
{
for (auto& reflectedProperty : groupPair.second.m_properties)
{
if (reflectedProperty.GetId() == property.GetId())
{
// Visibility changes require the entire reflected property editor tree for this group to be rebuilt
if (reflectedProperty.GetVisibility() != property.GetVisibility())
{
reflectedProperty.SetConfig(property.GetConfig());
RebuildGroup(groupPair.first);
}
else
{
reflectedProperty.SetConfig(property.GetConfig());
RefreshGroup(groupPair.first);
}
return;
}
}
}
}
void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible)
{
SetGroupVisible(groupId.GetStringView(), visible);
}
void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
// For some reason the reflected property editor notifications are not symmetrical
// This function is called continuously anytime a property changes until the edit has completed
// Because of that, we have to track whether or not we are continuing to edit the same property to know when editing has started and
// ended
// This function is called before every single property change whether it's a button click or dragging a slider. We only want to
// begin tracking undo state for the first change in the sequence, when the user begins to drag the slider.
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property)
if (!m_activeProperty && property)
{
if (m_activeProperty != property)
{
m_activeProperty = property;
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
}
}
}
void MaterialInspector::AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property)
{
if (m_activeProperty == property)
{
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue());
}
m_activeProperty = property;
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit);
}
}
void MaterialInspector::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode)
{
// As above, there are symmetrical functions on the notification interface for when editing begins and ends and has been completed
// but they are not being called following that pattern. when this function executes the changes to the property are ready to be
// committed or reverted
// If tracking has started and editing has completed then we can stop tracking undue state for this sequence of changes.
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property)
if (m_activeProperty && m_activeProperty == property)
{
if (m_activeProperty == property)
{
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue());
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
m_activeProperty = nullptr;
}
AtomToolsFramework::AtomToolsDocumentRequestBus::Event(
m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit);
m_activeProperty = {};
}
}
} // namespace MaterialEditor

@ -45,31 +45,25 @@ namespace MaterialEditor
bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const;
const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const;
void AddOverviewGroup();
void AddUvNamesGroup();
void AddPropertiesGroup();
// AtomToolsDocumentNotificationBus::Handler implementation
void OnDocumentOpened(const AZ::Uuid& documentId) override;
void OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override;
void OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override;
void OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid& documentId, const AZ::Name& groupId, bool visible) override;
void OnDocumentObjectInfoChanged(
const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt) override;
// AzToolsFramework::IPropertyEditorNotify overrides...
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void AfterPropertyModified([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {}
void SetPropertyEditingActive([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {}
void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override;
void SealUndoStack() override {}
void RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode*, const QPoint&) override {}
void PropertySelectionChanged(AzToolsFramework::InstanceDataNode*, bool) override {}
void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, const QPoint&) override {}
void PropertySelectionChanged([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, bool) override {}
// Tracking the property that is activiley being edited in the inspector
const AtomToolsFramework::DynamicProperty* m_activeProperty = nullptr;
const AtomToolsFramework::DynamicProperty* m_activeProperty = {};
AZ::Uuid m_documentId = AZ::Uuid::CreateNull();
AZStd::string m_documentPath;
AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups;
AZStd::intrusive_ptr<MaterialEditorWindowSettings> m_windowSettings;
};
} // namespace MaterialEditor

@ -78,6 +78,28 @@ namespace ShaderManagementConsole
return m_invalidDescriptor;
}
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> ShaderManagementConsoleDocument::GetObjectInfo() const
{
if (!IsOpen())
{
AZ_Error("ShaderManagementConsoleDocument", false, "Document is not open.");
return {};
}
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
AtomToolsFramework::DocumentObjectInfo objectInfo;
objectInfo.m_visible = true;
objectInfo.m_name = "Shader Variant List";
objectInfo.m_displayName = "Shader Variant List";
objectInfo.m_description = "Shader Variant List";
objectInfo.m_objectType = azrtti_typeid<AZ::RPI::ShaderVariantListSourceData>();
objectInfo.m_objectPtr = const_cast<AZ::RPI::ShaderVariantListSourceData*>(&m_shaderVariantListSourceData);
objects.push_back(AZStd::move(objectInfo));
return objects;
}
bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath)
{
if (!AtomToolsDocument::Open(loadPath))

@ -32,6 +32,7 @@ namespace ShaderManagementConsole
~ShaderManagementConsoleDocument();
// AtomToolsFramework::AtomToolsDocument overrides...
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> GetObjectInfo() const override;
bool Open(AZStd::string_view loadPath) override;
bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override;

Loading…
Cancel
Save