Tied up a few loose ends to support deeply nested property groups.

Simplified the call back for MaterialTypeSourceData::EnumeratePropertyGroups while also providing more data.
Made the Material Inspector join nested property group display names to be like "Layer 1 | Base Color", since the leaf property groups are shown as a flat list in the inspector.
Fixed CreateMaterialAssetFromSourceData to include the imported json files in the list of sourceDependencies. This triggers the Material Editor to hot-reload when one of these json files changes.
Updated a few places that were still assuming only one level of property group.
Updated EditorMaterialComponentInspector to apply the per-property-group material functors, before it was still only applying the top-level onces.
Moved some accessor function implementations to the cpp files, per feedback on another already-merged PR.

Testing:
Made changes to MinimalMultilayerPbr (in AtomSampleViewer) to use nested property groups, and saw the correct behavior in the Material Editor's property inspector.
Used MaterialComponent's property inspector to edit a StandardPbr material instance. Confrimed that functors were correctly controlling property visibility by enabling and disabling things like emissive and clear coat.
Used MaterialComponent's property inspector to edit a MinimalMultilayerPbr material instance. Saw all the expected groups and properties show up. Confirmed that per-group functors were correctly controlling property visibility.
Used MaterialComponent's property inspector to export a material instance and confirmed the .material file included the expected properties.

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
monroegm-disable-blank-issue-2
santorac 4 years ago
parent 2a3c5b38e5
commit 8fbd2aaaf5

@ -131,15 +131,17 @@ namespace AZ
PropertyGroup() = default; PropertyGroup() = default;
AZ_DISABLE_COPY(PropertyGroup) AZ_DISABLE_COPY(PropertyGroup)
const AZStd::string& GetName() const { return m_name; } const AZStd::string& GetName() const;
const AZStd::string& GetDisplayName() const { return m_displayName; } const AZStd::string& GetDisplayName() const;
const AZStd::string& GetDescription() const { return m_description; } const AZStd::string& GetDescription() const;
const PropertyList& GetProperties() const { return m_properties; } const PropertyList& GetProperties() const;
const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& GetPropertyGroups() const { return m_propertyGroups; } const AZStd::string& GetShaderInputsPrefix() const;
const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& GetFunctors() const { return m_materialFunctorSourceData; } const AZStd::string& GetShaderOptionsPrefix() const;
const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& GetPropertyGroups() const;
void SetDisplayName(AZStd::string_view displayName) { m_displayName = displayName; } const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& GetFunctors() const;
void SetDescription(AZStd::string_view description) { m_description = description; }
void SetDisplayName(AZStd::string_view displayName);
void SetDescription(AZStd::string_view description);
//! Add a new property to this PropertyGroup. //! Add a new property to this PropertyGroup.
//! @param name a unique for the property. Must be a C-style identifier. //! @param name a unique for the property. Must be a C-style identifier.
@ -281,13 +283,13 @@ namespace AZ
//! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"]. //! Splits an ID string like "itemA.itemB.itemC" into a vector like ["itemA.itemB", "itemC"].
static AZStd::vector<AZStd::string_view> SplitId(AZStd::string_view id); static AZStd::vector<AZStd::string_view> SplitId(AZStd::string_view id);
//! Describes a path in the hierarchy of property groups, with the top level group at the beginning and a leaf-most group at the end.
using PropertyGroupStack = AZStd::vector<const PropertyGroup*>;
//! Call back function type used with the enumeration functions. //! Call back function type used with the enumeration functions.
//! The PropertyGroupStack contains the stack of property groups at the current point in the traversal.
//! Return false to terminate the traversal. //! Return false to terminate the traversal.
using EnumeratePropertyGroupsCallback = AZStd::function<bool( using EnumeratePropertyGroupsCallback = AZStd::function<bool(const PropertyGroupStack&)>;
const PropertyGroup*, // the next property group in the tree
const MaterialNameContext&, // The name context defined by the PropertyGroup, used to scope the contents of the property group
const MaterialNameContext& // The name context that the PropertyGroup is in, used to scope the property group name
)>;
//! Recursively traverses all of the property groups contained in the material type, executing a callback function for each. //! Recursively traverses all of the property groups contained in the material type, executing a callback function for each.
//! @return false if the enumeration was terminated early by the callback returning false. //! @return false if the enumeration was terminated early by the callback returning false.
@ -304,6 +306,9 @@ namespace AZ
//! @return false if the enumeration was terminated early by the callback returning false. //! @return false if the enumeration was terminated early by the callback returning false.
bool EnumerateProperties(const EnumeratePropertiesCallback& callback) const; bool EnumerateProperties(const EnumeratePropertiesCallback& callback) const;
//! Returns a MaterialNameContext for a specific path through the property group hierarchy.
static MaterialNameContext MakeMaterialNameContext(const MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack);
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
//! If the data was loaded from an old format file (i.e. where "groups" and "properties" were separate sections), //! If the data was loaded from an old format file (i.e. where "groups" and "properties" were separate sections),
@ -319,10 +324,10 @@ namespace AZ
PropertyDefinition* FindProperty(AZStd::span<AZStd::string_view> parsedPropertyId, AZStd::span<AZStd::unique_ptr<PropertyGroup>> inPropertyGroupList); PropertyDefinition* FindProperty(AZStd::span<AZStd::string_view> parsedPropertyId, AZStd::span<AZStd::unique_ptr<PropertyGroup>> inPropertyGroupList);
// Function overloads for recursion, returns false to indicate that recursion should end. // Function overloads for recursion, returns false to indicate that recursion should end.
bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const; bool EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, PropertyGroupStack* propertyGroupStack, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const;
bool EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const; bool EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const;
static MaterialNameContext ExtendNameContext(MaterialNameContext nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup); static void ExtendNameContext(MaterialNameContext& nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup);
//! Recursively populates a material type asset with properties from the tree of material property groups. //! Recursively populates a material type asset with properties from the tree of material property groups.
//! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths //! @param materialTypeSourceFilePath path to the material type file that is being processed, used to look up relative paths

@ -45,9 +45,9 @@ namespace AZ
bool ContextualizeShaderOption(AZStd::string& shaderOptionName) const; bool ContextualizeShaderOption(AZStd::string& shaderOptionName) const;
//! Returns true if there is some non-default name context. //! Returns true if there is some non-default name context.
bool HasContextForProperties() const { return !m_propertyIdContext.empty(); } bool HasContextForProperties() const;
bool HasContextForSrgInputs() const { return !m_srgInputNameContext.empty(); } bool HasContextForSrgInputs() const;
bool HasContextForShaderOptions() const { return !m_shaderOptionNameContext.empty(); } bool HasContextForShaderOptions() const;
//! Returns true if the name context is empty. //! Returns true if the name context is empty.
bool IsDefault() const; bool IsDefault() const;

@ -262,7 +262,7 @@ namespace AZ
return Failure(); return Failure();
} }
auto materialTypeLoadOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourcePath); auto materialTypeLoadOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourcePath, nullptr, sourceDependencies);
if (!materialTypeLoadOutcome) if (!materialTypeLoadOutcome)
{ {
AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str()); AZ_Error("MaterialSourceData", false, "Failed to load MaterialTypeSourceData: '%s'.", materialTypeSourcePath.c_str());

@ -158,6 +158,56 @@ namespace AZ
return toPropertyGroupList.back().get(); return toPropertyGroupList.back().get();
} }
const AZStd::string& MaterialTypeSourceData::PropertyGroup::GetName() const
{
return m_name;
}
const AZStd::string& MaterialTypeSourceData::PropertyGroup::GetDisplayName() const
{
return m_displayName;
}
const AZStd::string& MaterialTypeSourceData::PropertyGroup::GetDescription() const
{
return m_description;
}
const MaterialTypeSourceData::PropertyList& MaterialTypeSourceData::PropertyGroup::GetProperties() const
{
return m_properties;
}
const AZStd::string& MaterialTypeSourceData::PropertyGroup::GetShaderInputsPrefix() const
{
return m_shaderInputsPrefix;
}
const AZStd::string& MaterialTypeSourceData::PropertyGroup::GetShaderOptionsPrefix() const
{
return m_shaderOptionsPrefix;
}
const AZStd::vector<AZStd::unique_ptr<MaterialTypeSourceData::PropertyGroup>>& MaterialTypeSourceData::PropertyGroup::GetPropertyGroups() const
{
return m_propertyGroups;
}
const AZStd::vector<Ptr<MaterialFunctorSourceDataHolder>>& MaterialTypeSourceData::PropertyGroup::GetFunctors() const
{
return m_materialFunctorSourceData;
}
void MaterialTypeSourceData::PropertyGroup::SetDisplayName(AZStd::string_view displayName)
{
m_displayName = displayName;
}
void MaterialTypeSourceData::PropertyGroup::SetDescription(AZStd::string_view description)
{
m_description = description;
}
MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertyGroup::AddProperty(AZStd::string_view name) MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::PropertyGroup::AddProperty(AZStd::string_view name)
{ {
if (!MaterialPropertyId::CheckIsValidName(name)) if (!MaterialPropertyId::CheckIsValidName(name))
@ -377,21 +427,23 @@ namespace AZ
return parts; return parts;
} }
bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const bool MaterialTypeSourceData::EnumeratePropertyGroups(const EnumeratePropertyGroupsCallback& callback, PropertyGroupStack* propertyGroupStack, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const
{ {
for (auto& propertyGroup : inPropertyGroupList) for (auto& propertyGroup : inPropertyGroupList)
{ {
MaterialNameContext groupNameContext = ExtendNameContext(nameContext, *propertyGroup); propertyGroupStack->push_back(propertyGroup.get());
if (!callback(propertyGroup.get(), groupNameContext, nameContext)) if (!callback(*propertyGroupStack))
{ {
return false; // Stop processing return false; // Stop processing
} }
if (!EnumeratePropertyGroups(callback, groupNameContext, propertyGroup->m_propertyGroups)) if (!EnumeratePropertyGroups(callback, propertyGroupStack, propertyGroup->m_propertyGroups))
{ {
return false; // Stop processing return false; // Stop processing
} }
propertyGroupStack->pop_back();
} }
return true; return true;
@ -404,14 +456,16 @@ namespace AZ
return false; return false;
} }
return EnumeratePropertyGroups(callback, {}, m_propertyLayout.m_propertyGroups); PropertyGroupStack propertyGroupStack;
return EnumeratePropertyGroups(callback, &propertyGroupStack, m_propertyLayout.m_propertyGroups);
} }
bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const bool MaterialTypeSourceData::EnumerateProperties(const EnumeratePropertiesCallback& callback, MaterialNameContext nameContext, const AZStd::vector<AZStd::unique_ptr<PropertyGroup>>& inPropertyGroupList) const
{ {
for (auto& propertyGroup : inPropertyGroupList) for (auto& propertyGroup : inPropertyGroupList)
{ {
MaterialNameContext groupNameContext = ExtendNameContext(nameContext, *propertyGroup); MaterialNameContext groupNameContext = nameContext;
ExtendNameContext(groupNameContext, *propertyGroup);
for (auto& property : propertyGroup->m_properties) for (auto& property : propertyGroup->m_properties)
{ {
@ -529,13 +583,21 @@ namespace AZ
return groupDefinitions; return groupDefinitions;
} }
MaterialNameContext MaterialTypeSourceData::ExtendNameContext(MaterialNameContext nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup) void MaterialTypeSourceData::ExtendNameContext(MaterialNameContext& nameContext, const MaterialTypeSourceData::PropertyGroup& propertyGroup)
{ {
MaterialNameContext materialNameContext2 = nameContext; nameContext.ExtendPropertyIdContext(propertyGroup.m_name);
materialNameContext2.ExtendPropertyIdContext(propertyGroup.m_name); nameContext.ExtendShaderOptionContext(propertyGroup.m_shaderOptionsPrefix);
materialNameContext2.ExtendShaderOptionContext(propertyGroup.m_shaderOptionsPrefix); nameContext.ExtendSrgInputContext(propertyGroup.m_shaderInputsPrefix);
materialNameContext2.ExtendSrgInputContext(propertyGroup.m_shaderInputsPrefix); }
return materialNameContext2;
/*static*/ MaterialNameContext MaterialTypeSourceData::MakeMaterialNameContext(const MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
{
MaterialNameContext nameContext;
for (auto& group : propertyGroupStack)
{
ExtendNameContext(nameContext, *group);
}
return nameContext;
} }
bool MaterialTypeSourceData::BuildPropertyList( bool MaterialTypeSourceData::BuildPropertyList(
@ -549,7 +611,7 @@ namespace AZ
return false; return false;
} }
materialNameContext = ExtendNameContext(materialNameContext, *propertyGroup); ExtendNameContext(materialNameContext, *propertyGroup);
for (const AZStd::unique_ptr<PropertyDefinition>& property : propertyGroup->m_properties) for (const AZStd::unique_ptr<PropertyDefinition>& property : propertyGroup->m_properties)
{ {

@ -118,5 +118,19 @@ namespace AZ
return true; return true;
} }
bool MaterialNameContext::HasContextForProperties() const
{
return !m_propertyIdContext.empty();
}
bool MaterialNameContext::HasContextForSrgInputs() const
{
return !m_srgInputNameContext.empty();
}
bool MaterialNameContext::HasContextForShaderOptions() const
{
return !m_shaderOptionNameContext.empty();
}
} // namespace RPI } // namespace RPI
} // namespace AZ } // namespace AZ

@ -354,9 +354,7 @@ namespace MaterialEditor
return false; return false;
} }
// TODO: Support populating the Material Editor with nested property groups, not just the top level. sourceData.SetPropertyValue(propertyId, propertyValue);
AZStd::string_view groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1);
sourceData.SetPropertyValue(AZ::RPI::MaterialPropertyId{groupName, propertyDefinition->GetName()}, propertyValue);
} }
} }
return true; return true;
@ -553,27 +551,38 @@ namespace MaterialEditor
// Assets must still be used for now because they contain the final accumulated value after all other materials // Assets must still be used for now because they contain the final accumulated value after all other materials
// in the hierarchy are applied // in the hierarchy are applied
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this, &parentPropertyValues]( [this, &parentPropertyValues](const AZ::RPI::MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup,
const AZ::RPI::MaterialNameContext& groupNameContext,
const AZ::RPI::MaterialNameContext& parentNameContext)
{ {
// Add any material functors that are located inside each property group. using namespace AZ::RPI;
const MaterialTypeSourceData::PropertyGroup* propertyGroup = propertyGroupStack.back();
MaterialNameContext groupNameContext = MaterialTypeSourceData::MakeMaterialNameContext(propertyGroupStack);
if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors(), groupNameContext)) if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors(), groupNameContext))
{ {
return false; return false;
} }
AZStd::vector<AZStd::string> groupNameVector;
AZStd::vector<AZStd::string> groupDisplayNameVector;
for (auto& group : propertyGroupStack)
{
groupNameVector.push_back(group->GetName());
groupDisplayNameVector.push_back(group->GetDisplayName());
}
m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup); m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup);
m_groups.back()->m_name = propertyGroup->GetName();
parentNameContext.ContextualizeProperty(m_groups.back()->m_name);
m_groups.back()->m_displayName = propertyGroup->GetDisplayName();
m_groups.back()->m_description = propertyGroup->GetDescription(); m_groups.back()->m_description = propertyGroup->GetDescription();
AzFramework::StringFunc::Join(m_groups.back()->m_name, groupNameVector.begin(), groupNameVector.end(), ".");
AzFramework::StringFunc::Join(m_groups.back()->m_displayName, groupDisplayNameVector.begin(), groupDisplayNameVector.end(), " | ");
for (const auto& propertyDefinition : propertyGroup->GetProperties()) for (const auto& propertyDefinition : propertyGroup->GetProperties())
{ {
// Assign id before conversion so it can be used in dynamic description
AtomToolsFramework::DynamicPropertyConfig propertyConfig; AtomToolsFramework::DynamicPropertyConfig propertyConfig;
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyDefinition->GetName(); propertyConfig.m_id = propertyDefinition->GetName();
groupNameContext.ContextualizeProperty(propertyConfig.m_id); groupNameContext.ContextualizeProperty(propertyConfig.m_id);
@ -588,8 +597,7 @@ namespace MaterialEditor
{ {
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition); 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_groupName? It doesn't seem to be used anywhere)
// (Does DynamicPropertyConfig really even need m_groupDisplayName?)
propertyConfig.m_groupName = m_groups.back()->m_name; propertyConfig.m_groupName = m_groups.back()->m_name;
propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName; propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName;
propertyConfig.m_showThumbnail = true; propertyConfig.m_showThumbnail = true;

@ -103,28 +103,9 @@ namespace AZ
return false; return false;
} }
// Get a list of all the editor functors to be used for property editor states // Add material functors that are in the top-level functors list. Other functors are also added per-property-group elsewhere.
auto propertyLayout = m_editData.m_materialAsset->GetMaterialPropertiesLayout(); AddEditorMaterialFunctors(m_editData.m_materialTypeSourceData.m_materialFunctorSourceData, AZ::RPI::MaterialNameContext{});
AZ::RPI::MaterialNameContext materialNameContext; // There is no name context for top-level functors, only functors inside PropertyGroups
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
AZ::RPI::MaterialFunctorSourceData::EditorContext(m_editData.m_materialTypeSourcePath, propertyLayout, &materialNameContext);
for (AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_editData.m_materialTypeSourceData.m_materialFunctorSourceData)
{
AZ::RPI::MaterialFunctorSourceData::FunctorResult createResult = functorData->CreateFunctor(editorContext);
if (createResult.IsSuccess())
{
AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>& functor = createResult.GetValue();
if (functor != nullptr)
{
m_editorFunctors.push_back(functor);
}
}
else
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material functors were not created: '%s'.", m_editData.m_materialTypeSourcePath.c_str());
}
}
Populate(); Populate();
LoadOverridesFromEntity(); LoadOverridesFromEntity();
@ -295,47 +276,73 @@ namespace AZ
void MaterialPropertyInspector::AddPropertiesGroup() void MaterialPropertyInspector::AddPropertiesGroup()
{ {
// Copy all of the properties from the material asset to the source data that will be exported // Copy all of the properties from the material asset to the source data that will be exported
// TODO: Support populating the Material Editor with nested property groups, not just the top level. m_editData.m_materialTypeSourceData.EnumeratePropertyGroups(
for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_editData.m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups) [this](const AZ::RPI::MaterialTypeSourceData::PropertyGroupStack& propertyGroupStack)
{
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::DynamicPropertyConfig propertyConfig; using namespace AZ::RPI;
// Assign id before conversion so it can be used in dynamic description const MaterialTypeSourceData::PropertyGroup* propertyGroupDefinition = propertyGroupStack.back();
propertyConfig.m_id = AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName());
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition.get()); MaterialNameContext groupNameContext = MaterialTypeSourceData::MakeMaterialNameContext(propertyGroupStack);
const auto& propertyIndex = AddEditorMaterialFunctors(propertyGroupDefinition->GetFunctors(), groupNameContext);
m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
propertyConfig.m_groupName = groupDisplayName; AZStd::vector<AZStd::string> groupNameVector;
propertyConfig.m_showThumbnail = true; AZStd::vector<AZStd::string> groupDisplayNameVector;
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
// There is no explicit parent material here. Material instance property overrides replace the values from the for (auto& group : propertyGroupStack)
// assigned material asset. Its values should be treated as parent, for comparison, in this case. {
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType( groupNameVector.push_back(group->GetName());
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]); groupDisplayNameVector.push_back(!group->GetDisplayName().empty() ? group->GetDisplayName() : group->GetName());
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType( }
m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
group.m_properties.emplace_back(propertyConfig);
}
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties AZStd::string groupId;
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( AzFramework::StringFunc::Join(groupId, groupNameVector.begin(), groupNameVector.end(), ".");
&group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0); auto& group = m_groups[groupId];
AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget); group.m_name = groupId;
} AzFramework::StringFunc::Join(group.m_displayName, groupDisplayNameVector.begin(), groupDisplayNameVector.end(), " | ");
group.m_description = !propertyGroupDefinition->GetDescription().empty() ? propertyGroupDefinition->GetDescription() : group.m_displayName;
group.m_properties.reserve(propertyGroupDefinition->GetProperties().size());
for (const auto& propertyDefinition : propertyGroupDefinition->GetProperties())
{
AtomToolsFramework::DynamicPropertyConfig propertyConfig;
// Assign id before conversion so it can be used in dynamic description
propertyConfig.m_id = propertyDefinition->GetName();
groupNameContext.ContextualizeProperty(propertyConfig.m_id);
AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition);
const auto& propertyIndex =
m_editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id);
// (Does DynamicPropertyConfig really even need m_groupName? It doesn't seem to be used anywhere)
propertyConfig.m_groupName = group.m_name;
propertyConfig.m_groupDisplayName = group.m_displayName;
propertyConfig.m_showThumbnail = true;
propertyConfig.m_defaultValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
// There is no explicit parent material here. Material instance property overrides replace the values from the
// assigned material asset. Its values should be treated as parent, for comparison, in this case.
propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialTypeAsset->GetDefaultPropertyValues()[propertyIndex.GetIndex()]);
propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(
m_editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]);
group.m_properties.emplace_back(propertyConfig);
}
// 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(group.m_name), {},
[this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0);
AddGroup(group.m_name, group.m_displayName, group.m_description, propertyGroupWidget);
return true;
});
} }
void MaterialPropertyInspector::Populate() void MaterialPropertyInspector::Populate()
@ -436,6 +443,37 @@ namespace AZ
// throttling // throttling
} }
bool MaterialPropertyInspector::AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders,
const AZ::RPI::MaterialNameContext& nameContext)
{
// Copied from MaterialDocument::AddEditorMaterialFunctors, should be refactored at some point
const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_editData.m_materialTypeSourcePath, m_editData.m_materialAsset->GetMaterialPropertiesLayout(), &nameContext);
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_editData.m_materialTypeSourcePath.c_str());
return false;
}
}
return true;
}
void MaterialPropertyInspector::RunEditorMaterialFunctors() void MaterialPropertyInspector::RunEditorMaterialFunctors()
{ {
if (!IsLoaded()) if (!IsLoaded())

@ -108,6 +108,10 @@ namespace AZ
void RunEditorMaterialFunctors(); void RunEditorMaterialFunctors();
void UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property); void UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property);
bool AddEditorMaterialFunctors(
const AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctorSourceDataHolder>>& functorSourceDataHolders,
const AZ::RPI::MaterialNameContext& nameContext);
AZ::Crc32 GetGroupSaveStateKey(const AZStd::string& groupName) const; AZ::Crc32 GetGroupSaveStateKey(const AZStd::string& groupName) const;
bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const; bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const;
const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const; const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const;

@ -151,9 +151,7 @@ namespace AZ
return true; return true;
} }
// TODO: Support populating the Material Editor with nested property groups, not just the top level. exportData.SetPropertyValue(propertyId, propertyValue);
AZStd::string_view groupName = propertyId.GetStringView().substr(0, propertyId.GetStringView().size() - propertyDefinition->GetName().size() - 1);
exportData.SetPropertyValue(RPI::MaterialPropertyId{groupName, propertyDefinition->GetName()}, propertyValue);
return true; return true;
}); });

Loading…
Cancel
Save