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>
This commit is contained in:
@@ -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):
|
||||
|
||||
+1
-8
@@ -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;
|
||||
|
||||
+6
-17
@@ -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
|
||||
{
|
||||
@@ -61,22 +60,12 @@ namespace AtomToolsFramework
|
||||
//! Signal that a document undo state was updated
|
||||
//! @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>;
|
||||
|
||||
+16
-14
@@ -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
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+6
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -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,62 +53,6 @@ namespace MaterialEditor
|
||||
return &m_materialTypeSourceData;
|
||||
}
|
||||
|
||||
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document is not open.");
|
||||
return m_invalidValue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const AtomToolsFramework::DynamicProperty& property = it->second;
|
||||
return property.GetValue();
|
||||
}
|
||||
|
||||
const AtomToolsFramework::DynamicProperty& MaterialDocument::GetProperty(const AZ::Name& propertyId) const
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document is not open.");
|
||||
return m_invalidProperty;
|
||||
}
|
||||
|
||||
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_invalidProperty;
|
||||
}
|
||||
|
||||
const AtomToolsFramework::DynamicProperty& property = it->second;
|
||||
return property;
|
||||
}
|
||||
|
||||
bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document is not open.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto it = m_propertyGroupVisibility.find(propertyGroupFullName);
|
||||
if (it == m_propertyGroupVisibility.end())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document property group could not be found: '%s'.", propertyGroupFullName.GetCStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value)
|
||||
{
|
||||
if (!IsOpen())
|
||||
@@ -117,48 +61,85 @@ namespace MaterialEditor
|
||||
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));
|
||||
|
||||
const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId);
|
||||
if (!propertyIndex.IsNull())
|
||||
{
|
||||
if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue))
|
||||
AtomToolsFramework::DynamicProperty* foundProperty = {};
|
||||
TraverseGroups(m_groups, [&, this](auto& group) {
|
||||
for (auto& property : group->m_properties)
|
||||
{
|
||||
AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
|
||||
|
||||
Recompile();
|
||||
|
||||
EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags);
|
||||
for (const AZ::Name& changedPropertyGroupName : result.m_updatedPropertyGroups)
|
||||
if (property.GetId() == propertyId)
|
||||
{
|
||||
foundProperty = &property;
|
||||
|
||||
// This first converts to an acceptable runtime type in case the value came from script
|
||||
const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
|
||||
|
||||
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::OnDocumentPropertyGroupVisibilityChanged, m_id,
|
||||
changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName));
|
||||
}
|
||||
for (const AZ::Name& changedPropertyName : result.m_updatedProperties)
|
||||
{
|
||||
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id,
|
||||
GetObjectInfoFromDynamicPropertyGroup(group.get()), false);
|
||||
|
||||
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
|
||||
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id,
|
||||
GetProperty(changedPropertyName));
|
||||
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!foundProperty)
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
|
||||
}
|
||||
}
|
||||
|
||||
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document is not open.");
|
||||
return m_invalidValue;
|
||||
}
|
||||
|
||||
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
|
||||
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property);
|
||||
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
|
||||
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
|
||||
auto property = FindProperty(propertyId);
|
||||
if (!property)
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
|
||||
return m_invalidValue;
|
||||
}
|
||||
|
||||
return property->GetValue();
|
||||
}
|
||||
|
||||
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> MaterialDocument::GetObjectInfo() const
|
||||
{
|
||||
if (!IsOpen())
|
||||
{
|
||||
AZ_Error("MaterialDocument", false, "Document is not open.");
|
||||
return {};
|
||||
}
|
||||
|
||||
AZStd::vector<AtomToolsFramework::DocumentObjectInfo> objects;
|
||||
objects.reserve(m_groups.size());
|
||||
|
||||
AtomToolsFramework::DocumentObjectInfo objectInfo;
|
||||
for (const auto& group : m_groups)
|
||||
{
|
||||
objects.push_back(GetObjectInfoFromDynamicPropertyGroup(group.get()));
|
||||
}
|
||||
|
||||
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))
|
||||
if (!LoadMaterialSourceData())
|
||||
{
|
||||
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())
|
||||
{
|
||||
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;
|
||||
// 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;
|
||||
|
||||
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.
|
||||
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));
|
||||
|
||||
//Add UV name customization properties
|
||||
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
|
||||
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())
|
||||
{
|
||||
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = result2.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 OpenFailed();
|
||||
}
|
||||
}
|
||||
|
||||
// Add any material functors that are located inside each property group.
|
||||
// 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](const AZStd::string&, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
|
||||
[this, &parentPropertyValues](
|
||||
const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
|
||||
{
|
||||
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 : propertyGroup->GetFunctors())
|
||||
// Add any material functors that are located inside each property group.
|
||||
if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors()))
|
||||
{
|
||||
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.IsSuccess())
|
||||
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 (const auto& propertyDefinition : propertyGroup->GetProperties())
|
||||
{
|
||||
// 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 (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());
|
||||
|
||||
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : functorSourceDataHolders)
|
||||
{
|
||||
AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
|
||||
|
||||
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;
|
||||
for (auto& propertyPair : m_properties)
|
||||
{
|
||||
AtomToolsFramework::DynamicProperty& property = propertyPair.second;
|
||||
AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig());
|
||||
}
|
||||
for (auto& groupPair : m_propertyGroupVisibility)
|
||||
{
|
||||
AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}];
|
||||
|
||||
bool visible = groupPair.second;
|
||||
metadata.m_visibility = visible ?
|
||||
AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden;
|
||||
}
|
||||
|
||||
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,20 +53,16 @@ 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);
|
||||
|
||||
// Underlying material asset
|
||||
// Convert a dynamic property group pointer into generic document object info used to populate the inspector
|
||||
AtomToolsFramework::DocumentObjectInfo GetObjectInfoFromDynamicPropertyGroup(
|
||||
const AtomToolsFramework::DynamicPropertyGroup* group) const;
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+40
-187
@@ -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()
|
||||
void MaterialInspector::OnDocumentObjectInfoChanged(
|
||||
[[maybe_unused]] const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt)
|
||||
{
|
||||
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()
|
||||
{
|
||||
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
|
||||
|
||||
+6
-12
@@ -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
|
||||
|
||||
+22
@@ -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))
|
||||
|
||||
+1
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user