Merge pull request #7423 from aws-lumberyard-dev/Atom/guthadam/atom_tools_support_automatic_inspector_population_and_nested_groups
Atom Tools: Document system exposes reflected object data
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