Changing material component property inspector to dockable view pane

• Inspector is locked to a specific entity and material assignment ID
• All modifications are made via the material component request bus
• Removed complicated configuration management in editor material component
• Multiple material property inspectors can be opened
• Multiple materials across different entities can be edited simultaneously
• No longer blocks the viewport or other interactions
• Added functions to material component request bus for retrieving material slot labels, default materials, getting and setting property and UV overrides
• Added more asset related types to material property value conversion from any
• Added support for static heading widget on top of atom tools inspector, currently used for menus and messages

WIP: Still investigating intermittent crash because of corrupt asset property
Signed-off-by: Guthrie Adams <guthadam@amazon.com>
This commit is contained in:
Guthrie Adams
2021-09-14 19:11:11 -05:00
parent e69238d4de
commit f7d4b8e70f
19 changed files with 826 additions and 556 deletions
@@ -109,11 +109,20 @@ namespace AZ
{ {
result.m_value = AZStd::any_cast<Color>(value); result.m_value = AZStd::any_cast<Color>(value);
} }
else if (value.is<Data::AssetId>())
{
result.m_value = Data::Asset<RPI::ImageAsset>(
AZStd::any_cast<Data::AssetId>(value), azrtti_typeid<RPI::StreamingImageAsset>());
}
else if (value.is<Data::Asset<Data::AssetData>>())
{
result.m_value = Data::Asset<RPI::ImageAsset>(
AZStd::any_cast<Data::Asset<Data::AssetData>>(value).GetId(), azrtti_typeid<RPI::StreamingImageAsset>());
}
else if (value.is<Data::Asset<StreamingImageAsset>>()) else if (value.is<Data::Asset<StreamingImageAsset>>())
{ {
result.m_value = Data::Asset<RPI::ImageAsset>( result.m_value = Data::Asset<RPI::ImageAsset>(
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(), AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(), azrtti_typeid<RPI::StreamingImageAsset>());
azrtti_typeid<RPI::StreamingImageAsset>());
} }
else if (value.is<Data::Asset<ImageAsset>>()) else if (value.is<Data::Asset<ImageAsset>>())
{ {
@@ -129,7 +138,8 @@ namespace AZ
} }
else else
{ {
AZ_Warning("MaterialPropertyValue", false, "Cannot convert any to variant. Type in any is: %s.", AZ_Warning(
"MaterialPropertyValue", false, "Cannot convert any to variant. Type in any is: %s.",
value.get_type_info().m_id.ToString<AZStd::string>().data()); value.get_type_info().m_id.ToString<AZStd::string>().data());
} }
@@ -187,5 +197,5 @@ namespace AZ
return result; return result;
} }
} } // namespace RPI
} } // namespace AZ
@@ -24,6 +24,12 @@ namespace AtomToolsFramework
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::Uuid BusIdType; typedef AZ::Uuid BusIdType;
//! Add heading widget above scroll area
virtual void AddHeading(QWidget* headingWidget) = 0;
//! Clear heading widgets
virtual void ClearHeading() = 0;
//! Clear all inspector groups and content //! Clear all inspector groups and content
virtual void Reset() = 0; virtual void Reset() = 0;
@@ -41,6 +41,10 @@ namespace AtomToolsFramework
~InspectorWidget() override; ~InspectorWidget() override;
// InspectorRequestBus::Handler overrides... // InspectorRequestBus::Handler overrides...
void AddHeading(QWidget* headingWidget) override;
void ClearHeading() override;
void Reset() override; void Reset() override;
void AddGroupsBegin() override; void AddGroupsBegin() override;
@@ -77,7 +81,6 @@ namespace AtomToolsFramework
virtual void OnHeaderClicked(const AZStd::string& groupNameId, QMouseEvent* event); virtual void OnHeaderClicked(const AZStd::string& groupNameId, QMouseEvent* event);
private: private:
QVBoxLayout* m_layout = nullptr;
QScopedPointer<Ui::InspectorWidget> m_ui; QScopedPointer<Ui::InspectorWidget> m_ui;
struct GroupWidgetPair struct GroupWidgetPair
@@ -38,7 +38,7 @@ namespace AtomToolsFramework
m_propertyEditor->Setup(context, instanceNotificationHandler, false); m_propertyEditor->Setup(context, instanceNotificationHandler, false);
m_propertyEditor->AddInstance(instance, instanceClassId, nullptr, instanceToCompare); m_propertyEditor->AddInstance(instance, instanceClassId, nullptr, instanceToCompare);
m_propertyEditor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_propertyEditor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree); m_propertyEditor->InvalidateAll();
m_layout->addWidget(m_propertyEditor); m_layout->addWidget(m_propertyEditor);
setLayout(m_layout); setLayout(m_layout);
@@ -29,30 +29,40 @@ namespace AtomToolsFramework
{ {
} }
void InspectorWidget::AddHeading(QWidget* headingWidget)
{
headingWidget->setParent(m_ui->m_headingSection);
m_ui->m_headingSectionLayout->addWidget(headingWidget);
}
void InspectorWidget::ClearHeading()
{
qDeleteAll(m_ui->m_headingSection->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
qDeleteAll(m_ui->m_headingSectionLayout->children());
}
void InspectorWidget::Reset() void InspectorWidget::Reset()
{ {
qDeleteAll(m_ui->m_propertyContent->children()); qDeleteAll(m_ui->m_groupContents->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
m_layout = new QVBoxLayout(m_ui->m_propertyContent); qDeleteAll(m_ui->m_groupContentsLayout->children());
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
m_groups.clear(); m_groups.clear();
} }
void InspectorWidget::AddGroupsBegin() void InspectorWidget::AddGroupsBegin()
{ {
setUpdatesEnabled(false); setVisible(false);
Reset(); Reset();
} }
void InspectorWidget::AddGroupsEnd() void InspectorWidget::AddGroupsEnd()
{ {
m_layout->addStretch(); m_ui->m_groupContentsLayout->addStretch();
// Scroll to top whenever there is new content // Scroll to top whenever there is new content
m_ui->m_propertyScrollArea->verticalScrollBar()->setValue(m_ui->m_propertyScrollArea->verticalScrollBar()->minimum()); m_ui->m_groupScrollArea->verticalScrollBar()->setValue(m_ui->m_groupScrollArea->verticalScrollBar()->minimum());
setUpdatesEnabled(true); setVisible(true);
} }
void InspectorWidget::AddGroup( void InspectorWidget::AddGroup(
@@ -61,14 +71,14 @@ namespace AtomToolsFramework
const AZStd::string& groupDescription, const AZStd::string& groupDescription,
QWidget* groupWidget) QWidget* groupWidget)
{ {
InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_propertyContent); InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_groupContents);
groupHeader->setText(groupDisplayName.c_str()); groupHeader->setText(groupDisplayName.c_str());
groupHeader->setToolTip(groupDescription.c_str()); groupHeader->setToolTip(groupDescription.c_str());
m_layout->addWidget(groupHeader); m_ui->m_groupContentsLayout->addWidget(groupHeader);
groupWidget->setObjectName(groupNameId.c_str()); groupWidget->setObjectName(groupNameId.c_str());
groupWidget->setParent(m_ui->m_propertyContent); groupWidget->setParent(m_ui->m_groupContents);
m_layout->addWidget(groupWidget); m_ui->m_groupContentsLayout->addWidget(groupWidget);
m_groups[groupNameId] = {groupHeader, groupWidget}; m_groups[groupNameId] = {groupHeader, groupWidget};
@@ -101,28 +111,18 @@ namespace AtomToolsFramework
bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const
{ {
auto groupItr = m_groups.find(groupNameId); auto groupItr = m_groups.find(groupNameId);
if (groupItr != m_groups.end()) return groupItr != m_groups.end() ? groupItr->second.m_header->isVisible() : false;
{
return groupItr->second.m_header->isVisible();
}
return false;
} }
bool InspectorWidget::IsGroupHidden(const AZStd::string& groupNameId) const bool InspectorWidget::IsGroupHidden(const AZStd::string& groupNameId) const
{ {
auto groupItr = m_groups.find(groupNameId); auto groupItr = m_groups.find(groupNameId);
if (groupItr != m_groups.end()) return groupItr != m_groups.end() ? groupItr->second.m_header->isHidden() : false;
{
return groupItr->second.m_header->isHidden();
}
return false;
} }
void InspectorWidget::RefreshGroup(const AZStd::string& groupNameId) void InspectorWidget::RefreshGroup(const AZStd::string& groupNameId)
{ {
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>(groupNameId.c_str())) for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>(groupNameId.c_str()))
{ {
groupWidget->Refresh(); groupWidget->Refresh();
} }
@@ -130,7 +130,7 @@ namespace AtomToolsFramework
void InspectorWidget::RebuildGroup(const AZStd::string& groupNameId) void InspectorWidget::RebuildGroup(const AZStd::string& groupNameId)
{ {
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>(groupNameId.c_str())) for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>(groupNameId.c_str()))
{ {
groupWidget->Rebuild(); groupWidget->Rebuild();
} }
@@ -138,7 +138,7 @@ namespace AtomToolsFramework
void InspectorWidget::RefreshAll() void InspectorWidget::RefreshAll()
{ {
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>()) for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>())
{ {
groupWidget->Refresh(); groupWidget->Refresh();
} }
@@ -146,7 +146,7 @@ namespace AtomToolsFramework
void InspectorWidget::RebuildAll() void InspectorWidget::RebuildAll()
{ {
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>()) for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>())
{ {
groupWidget->Rebuild(); groupWidget->Rebuild();
} }
@@ -6,20 +6,14 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>693</width> <width>685</width>
<height>798</height> <height>775</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Inspector</string> <string>Inspector</string>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QVBoxLayout" name="m_inspectorLayout">
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
@@ -36,50 +30,103 @@
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
<widget class="QScrollArea" name="m_propertyScrollArea"> <widget class="QWidget" name="m_sections">
<property name="verticalScrollBarPolicy"> <layout class="QVBoxLayout" name="m_sectionsLayout">
<enum>Qt::ScrollBarAsNeeded</enum> <property name="spacing">
</property> <number>0</number>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>691</width>
<height>796</height>
</rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin">
<property name="spacing"> <number>0</number>
<number>0</number> </property>
</property> <property name="topMargin">
<property name="leftMargin"> <number>0</number>
<number>0</number> </property>
</property> <property name="rightMargin">
<property name="topMargin"> <number>0</number>
<number>0</number> </property>
</property> <property name="bottomMargin">
<property name="rightMargin"> <number>0</number>
<number>0</number> </property>
</property> <item>
<property name="bottomMargin"> <widget class="QWidget" name="m_headingSection" native="true">
<number>0</number> <layout class="QVBoxLayout" name="m_headingSectionLayout">
</property> <property name="spacing">
<item> <number>0</number>
<widget class="QFrame" name="m_propertyContent">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property> </property>
<property name="frameShadow"> <property name="leftMargin">
<enum>QFrame::Raised</enum> <number>0</number>
</property> </property>
</widget> <property name="topMargin">
</item> <number>0</number>
</layout> </property>
</widget> <property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="m_groupSection" native="true">
<layout class="QVBoxLayout" name="m_groupSectionLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="m_groupScrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="m_groupContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>683</width>
<height>763</height>
</rect>
</property>
<layout class="QVBoxLayout" name="m_groupContentsLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
@@ -7,6 +7,8 @@
*/ */
#pragma once #pragma once
#include <Atom/Feature/Material/MaterialAssignmentId.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h> #include <AzCore/std/string/string.h>
@@ -23,8 +25,12 @@ namespace AZ
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Open document in material editor //! Open source material in material editor
virtual void OpenInMaterialEditor(const AZStd::string& sourcePath) = 0; virtual void OpenMaterialEditor(const AZStd::string& sourcePath) = 0;
//! Open material instance editor
virtual void OpenMaterialInspector(
const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId) = 0;
}; };
using EditorMaterialSystemComponentRequestBus = AZ::EBus<EditorMaterialSystemComponentRequests>; using EditorMaterialSystemComponentRequestBus = AZ::EBus<EditorMaterialSystemComponentRequests>;
} // namespace Render } // namespace Render
@@ -23,6 +23,10 @@ namespace AZ
virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0; virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0;
//! Get material assignment id matching lod and label substring //! Get material assignment id matching lod and label substring
virtual MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0; virtual MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
//! Get default material asset
virtual AZ::Data::AssetId GetDefaultMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const = 0;
//! Get material slot label
virtual AZStd::string GetMaterialSlotLabel(const MaterialAssignmentId& materialAssignmentId) const = 0;
//! Set material overrides //! Set material overrides
virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0; virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0;
//! Get material overrides //! Get material overrides
@@ -38,7 +42,7 @@ namespace AZ
//! Set material override //! Set material override
virtual void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) = 0; virtual void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) = 0;
//! Get material override //! Get material override
virtual const AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const = 0; virtual AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const = 0;
//! Clear material override //! Clear material override
virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0; virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0;
//! Set a material property override value wrapped by an AZStd::any //! Set a material property override value wrapped by an AZStd::any
@@ -95,8 +99,16 @@ namespace AZ
virtual void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) = 0; virtual void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) = 0;
//! Clear all property overrides //! Clear all property overrides
virtual void ClearAllPropertyOverrides() = 0; virtual void ClearAllPropertyOverrides() = 0;
//! Set Property overrides for a specific material assignment
virtual void SetPropertyOverrides(
const MaterialAssignmentId& materialAssignmentId, const MaterialPropertyOverrideMap& propertyOverrides) = 0;
//! Get Property overrides for a specific material assignment //! Get Property overrides for a specific material assignment
virtual MaterialPropertyOverrideMap GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0; virtual MaterialPropertyOverrideMap GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0;
//! Set Model UV overrides for a specific material assignment
virtual void SetModelUvOverrides(
const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) = 0;
//! Get Model UV overrides for a specific material assignment
virtual AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const = 0;
}; };
using MaterialComponentRequestBus = EBus<MaterialComponentRequests>; using MaterialComponentRequestBus = EBus<MaterialComponentRequests>;
@@ -106,7 +118,7 @@ namespace AZ
{ {
public: public:
virtual void OnMaterialsUpdated([[maybe_unused]] const MaterialAssignmentMap& materials) {} virtual void OnMaterialsUpdated([[maybe_unused]] const MaterialAssignmentMap& materials) {}
virtual void OnMaterialsEdited([[maybe_unused]] const MaterialAssignmentMap& materials) {} virtual void OnMaterialsEdited() {}
}; };
using MaterialComponentNotificationBus = EBus<MaterialComponentNotifications>; using MaterialComponentNotificationBus = EBus<MaterialComponentNotifications>;
@@ -215,91 +215,21 @@ namespace AZ
void EditorMaterialComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId) void EditorMaterialComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId)
{ {
m_controller.SetDefaultMaterialOverride(assetId); m_controller.SetDefaultMaterialOverride(assetId);
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
} }
AZ::u32 EditorMaterialComponent::OnConfigurationChanged() AZ::u32 EditorMaterialComponent::OnConfigurationChanged()
{ {
// Whenever the user makes changes to the editor component data the controller configuration must be rebuilt
m_configurationChangeInProgress = true;
UpdateController();
m_configurationChangeInProgress = false;
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
} }
void EditorMaterialComponent::OnMaterialAssignmentsChanged() void EditorMaterialComponent::OnMaterialAssignmentsChanged()
{ {
// [GFX TODO][ATOM-4604] remove flag after mesh component material handling is fixed to not recreate/reload mesh for material changes UpdateMaterialSlots();
if (!m_configurationChangeInProgress)
{
UpdateMaterialSlots();
}
}
void EditorMaterialComponent::OnMaterialsEdited(const MaterialAssignmentMap& materials)
{
AzToolsFramework::ScopedUndoBatch undoBatch("Materials edited.");
SetDirty();
// The layout of the materials slots is already set.
// We just need to read the values from any edited overrides into the editor component
// and refresh.
for (auto& materialSlotPair : GetMaterialSlots())
{
EditorMaterialComponentSlot& slot = *materialSlotPair.second;
const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(materials, slot.m_id);
slot.m_materialAsset = materialFromController.m_materialAsset;
slot.m_propertyOverrides = materialFromController.m_propertyOverrides;
slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides;
}
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_AttributesAndValues);
}
void EditorMaterialComponent::UpdateConfiguration(const MaterialComponentConfig& config)
{
m_controller.SetMaterialOverrides(config.m_materials);
}
void EditorMaterialComponent::UpdateController()
{
SetDirty();
// Build the controller configuration from the editor configuration
MaterialComponentConfig config = m_controller.GetConfiguration();
config.m_materials.clear();
for (const auto& materialSlotPair : GetMaterialSlots())
{
const EditorMaterialComponentSlot* materialSlot = materialSlotPair.second;
// Do not apply materials for lods if they are disabled
if (materialSlot->m_id.m_lodIndex != MaterialAssignmentId::NonLodIndex && !m_materialSlotsByLodEnabled)
{
continue;
}
// Only material slots with a valid asset IDs or property overrides will be copied
// to minimize the amount of data stored in the controller and game component
if (materialSlot->m_materialAsset.GetId().IsValid())
{
MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id];
materialAssignment.m_materialAsset = materialSlot->m_materialAsset;
materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides;
materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides;
}
else if (!materialSlot->m_propertyOverrides.empty() || !materialSlot->m_matModUvOverrides.empty())
{
MaterialAssignment& materialAssignment = config.m_materials[materialSlot->m_id];
materialAssignment.m_materialAsset = materialSlot->m_defaultMaterialAsset;
materialAssignment.m_propertyOverrides = materialSlot->m_propertyOverrides;
materialAssignment.m_matModUvOverrides = materialSlot->m_matModUvOverrides;
}
}
UpdateConfiguration(config);
} }
void EditorMaterialComponent::UpdateMaterialSlots() void EditorMaterialComponent::UpdateMaterialSlots()
@@ -315,64 +245,18 @@ namespace AZ
MaterialAssignmentMap materialsFromSource; MaterialAssignmentMap materialsFromSource;
MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments); MaterialReceiverRequestBus::EventResult(materialsFromSource, GetEntityId(), &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
RPI::ModelMaterialSlotMap modelMaterialSlots;
MaterialReceiverRequestBus::EventResult(modelMaterialSlots, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
// Generate the table of editable materials using the source data to define number of groups, elements, and initial values // Generate the table of editable materials using the source data to define number of groups, elements, and initial values
for (const auto& materialPair : materialsFromSource) for (const auto& materialPair : materialsFromSource)
{ {
// Setup the material slot entry // Setup the material slot entry
EditorMaterialComponentSlot slot; EditorMaterialComponentSlot slot;
slot.m_entityId = GetEntityId();
slot.m_id = materialPair.first; slot.m_id = materialPair.first;
slot.m_materialChangedCallback = [this]() {
// This callback is triggered whenever an individual material slot changes outside of normal inspector interactions
// So we must manually handle undo, update configuration, and refresh the inspector to display the new values
AzToolsFramework::ScopedUndoBatch undoBatch("Material slot changed.");
SetDirty();
OnConfigurationChanged();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_AttributesAndValues);
};
slot.m_propertyChangedCallback = [this]() {
OnConfigurationChanged();
};
const char* UnknownSlotName = "<unknown>";
// If this is the default material assignment ID then it represents the default slot which is not contained in any other group
if (slot.m_id == DefaultMaterialAssignmentId)
{
slot.m_label = "Default Material";
}
else
{
auto slotIter = modelMaterialSlots.find(slot.m_id.m_materialSlotStableId);
if (slotIter != modelMaterialSlots.end())
{
const Name& displayName = slotIter->second.m_displayName;
slot.m_label = !displayName.IsEmpty() ? displayName.GetStringView() : UnknownSlotName;
slot.m_defaultMaterialAsset = slotIter->second.m_defaultMaterialAsset;
}
else
{
slot.m_label = UnknownSlotName;
}
}
// if material is present in controller configuration, assign its data // if material is present in controller configuration, assign its data
const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id); const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id);
slot.m_materialAsset = materialFromController.m_materialAsset; slot.m_materialAsset = materialFromController.m_materialAsset;
slot.m_propertyOverrides = materialFromController.m_propertyOverrides;
slot.m_matModUvOverrides = materialFromController.m_matModUvOverrides;
// Attempt to get the UV names from model meshes.
MaterialReceiverRequestBus::EventResult(slot.m_modelUvNames, GetEntityId(), &MaterialReceiverRequestBus::Events::GetModelUvNames);
if (slot.m_id.IsDefault()) if (slot.m_id.IsDefault())
{ {
m_defaultMaterialSlot = slot; m_defaultMaterialSlot = slot;
@@ -388,7 +272,8 @@ namespace AZ
if (slot.m_id.IsLodAndSlotId()) if (slot.m_id.IsLodAndSlotId())
{ {
// Resize the containers to fit all elements // Resize the containers to fit all elements
m_materialSlotsByLod.resize(AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1))); m_materialSlotsByLod.resize(
AZ::GetMax<size_t>(m_materialSlotsByLod.size(), aznumeric_cast<size_t>(slot.m_id.m_lodIndex + 1)));
m_materialSlotsByLod[slot.m_id.m_lodIndex].push_back(slot); m_materialSlotsByLod[slot.m_id.m_lodIndex].push_back(slot);
continue; continue;
} }
@@ -404,9 +289,10 @@ namespace AZ
[](const auto& a, const auto& b) { return a.GetLabel() < b.GetLabel(); }); [](const auto& a, const auto& b) { return a.GetLabel() < b.GetLabel(); });
} }
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
AzToolsFramework::Refresh_EntireTree);
} }
AZ::u32 EditorMaterialComponent::ResetMaterialSlots() AZ::u32 EditorMaterialComponent::ResetMaterialSlots()
@@ -414,15 +300,15 @@ namespace AZ
AzToolsFramework::ScopedUndoBatch undoBatch("Resetting materials."); AzToolsFramework::ScopedUndoBatch undoBatch("Resetting materials.");
SetDirty(); SetDirty();
UpdateConfiguration(MaterialComponentConfig()); m_controller.SetMaterialOverrides(MaterialAssignmentMap());
UpdateMaterialSlots(); UpdateMaterialSlots();
m_materialSlotsByLodEnabled = false; m_materialSlotsByLodEnabled = false;
// Forcing refresh in case triggered from context menu action MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
AzToolsFramework::Refresh_EntireTree);
return AZ::Edit::PropertyRefreshLevels::EntireTree; return AZ::Edit::PropertyRefreshLevels::EntireTree;
} }
@@ -431,26 +317,26 @@ namespace AZ
{ {
AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials."); AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials.");
SetDirty(); SetDirty();
// First generating a unique set of all material asset IDs that will be used for source data generation // First generating a unique set of all material asset IDs that will be used for source data generation
AZStd::unordered_map<AZ::Data::AssetId, AZStd::string /*slot name*/> assetIdMap; AZStd::unordered_map<AZ::Data::AssetId, AZStd::string /*slot name*/> assetIdMap;
auto materialSlots = GetMaterialSlots(); auto materialSlots = GetMaterialSlots();
for (auto& materialSlotPair : materialSlots) for (auto& materialSlotPair : materialSlots)
{ {
Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId(); Data::AssetId defaultMaterialAssetId = materialSlotPair.second->GetDefaultAssetId();
if (defaultMaterialAssetId.IsValid()) if (defaultMaterialAssetId.IsValid())
{ {
assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel(); assetIdMap[defaultMaterialAssetId] = materialSlotPair.second->GetLabel();
} }
} }
// Convert the unique set of asset IDs into export items that can be configured in the dialog // Convert the unique set of asset IDs into export items that can be configured in the dialog
// The order should not matter because the table in the dialog can sort itself for a specific row // The order should not matter because the table in the dialog can sort itself for a specific row
EditorMaterialComponentExporter::ExportItemsContainer exportItems; EditorMaterialComponentExporter::ExportItemsContainer exportItems;
for (auto assetIdInfo : assetIdMap) for (auto assetIdInfo : assetIdMap)
{ {
EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second}; EditorMaterialComponentExporter::ExportItem exportItem{ assetIdInfo.first, assetIdInfo.second };
exportItems.push_back(exportItem); exportItems.push_back(exportItem);
} }
@@ -474,9 +360,9 @@ namespace AZ
if (editorMaterialSlot) if (editorMaterialSlot)
{ {
// We need to check whether replaced material corresponds to this slot's default material. // We need to check whether replaced material corresponds to this slot's default material.
if (editorMaterialSlot->m_defaultMaterialAsset.GetId() == exportItem.GetOriginalAssetId()) if (editorMaterialSlot->GetDefaultAssetId() == exportItem.GetOriginalAssetId())
{ {
editorMaterialSlot->m_materialAsset.Create(assetIdOutcome.GetValue()); editorMaterialSlot->SetAsset(assetIdOutcome.GetValue());
} }
} }
} }
@@ -484,17 +370,31 @@ namespace AZ
} }
} }
// Forcing refresh in case triggered from context menu action MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_AttributesAndValues);
return OnConfigurationChanged(); AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
} }
AZ::u32 EditorMaterialComponent::OnLodsToggled() AZ::u32 EditorMaterialComponent::OnLodsToggled()
{ {
OnConfigurationChanged(); AzToolsFramework::ScopedUndoBatch undoBatch("Toggling LOD materials.");
SetDirty();
if (!m_materialSlotsByLodEnabled)
{
MaterialComponentConfig config = m_controller.GetConfiguration();
AZStd::erase_if(config.m_materials, [](const auto& item) {
const auto& [key, value] = item;
return key.m_lodIndex != MaterialAssignmentId::NonLodIndex;
});
m_controller.SetMaterialOverrides(config.m_materials);
}
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
return AZ::Edit::PropertyRefreshLevels::EntireTree; return AZ::Edit::PropertyRefreshLevels::EntireTree;
} }
@@ -541,11 +441,14 @@ namespace AZ
materialSlots[slot.m_id] = &slot; materialSlots[slot.m_id] = &slot;
} }
for (auto& slotsForLod : component.m_materialSlotsByLod) if (component.m_materialSlotsByLodEnabled)
{ {
for (auto& slot : slotsForLod) for (auto& slotsForLod : component.m_materialSlotsByLod)
{ {
materialSlots[slot.m_id] = &slot; for (auto& slot : slotsForLod)
{
materialSlots[slot.m_id] = &slot;
}
} }
} }
} }
@@ -565,4 +468,3 @@ namespace AZ
} }
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ
@@ -8,11 +8,11 @@
#pragma once #pragma once
#include <Atom/Feature/Utils/EditorRenderComponentAdapter.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h> #include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentConstants.h> #include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentConstants.h>
#include <Atom/Feature/Utils/EditorRenderComponentAdapter.h>
#include <Material/MaterialComponent.h>
#include <Material/EditorMaterialComponentSlot.h> #include <Material/EditorMaterialComponentSlot.h>
#include <Material/MaterialComponent.h>
namespace AZ namespace AZ
{ {
@@ -49,16 +49,6 @@ namespace AZ
//! MaterialReceiverNotificationBus::Handler overrides... //! MaterialReceiverNotificationBus::Handler overrides...
void OnMaterialAssignmentsChanged() override; void OnMaterialAssignmentsChanged() override;
//! MaterialComponentNotificationBus::Handler overrides...
void OnMaterialsEdited(const MaterialAssignmentMap& materials) override;
// Apply a material component configuration to the active controller
void UpdateConfiguration(const MaterialComponentConfig& config);
// Converts the editor components material slots to the material component
// configuration and updates the controller
void UpdateController();
// Regenerates the editor component material slots based on the material and // Regenerates the editor component material slots based on the material and
// LOD mapping from the model or other consumer of materials. // LOD mapping from the model or other consumer of materials.
// If any corresponding material assignments are found in the component // If any corresponding material assignments are found in the component
@@ -101,8 +91,6 @@ namespace AZ
EditorMaterialComponentSlotsByLodContainer m_materialSlotsByLod; EditorMaterialComponentSlotsByLodContainer m_materialSlotsByLod;
bool m_materialSlotsByLodEnabled = false; bool m_materialSlotsByLodEnabled = false;
bool m_configurationChangeInProgress = false; // when true, model changes are ignored
static const char* GenerateMaterialsButtonText; static const char* GenerateMaterialsButtonText;
static const char* GenerateMaterialsToolTipText; static const char* GenerateMaterialsToolTipText;
static const char* ResetMaterialsButtonText; static const char* ResetMaterialsButtonText;
@@ -27,19 +27,16 @@
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h> #include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/ThumbnailWidget.h> #include <AzToolsFramework/Thumbnails/ThumbnailWidget.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h> #include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h> #include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentConfig.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication> #include <QApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFileInfo> #include <QFileInfo>
#include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QMenu> #include <QMenu>
#include <QToolButton> #include <QToolButton>
#include <QVBoxLayout>
#include <QWidget> #include <QWidget>
AZ_POP_DISABLE_WARNING AZ_POP_DISABLE_WARNING
@@ -49,26 +46,59 @@ namespace AZ
{ {
namespace EditorMaterialComponentInspector namespace EditorMaterialComponentInspector
{ {
MaterialPropertyInspector::MaterialPropertyInspector( MaterialPropertyInspector::MaterialPropertyInspector(QWidget* parent)
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent)
: AtomToolsFramework::InspectorWidget(parent) : AtomToolsFramework::InspectorWidget(parent)
, m_slotName(slotName)
, m_materialAssetId(assetId)
, m_propertyChangedCallback(propertyChangedCallback)
{ {
// Create the menu button
QToolButton* menuButton = new QToolButton(this);
menuButton->setAutoRaise(true);
menuButton->setIcon(QIcon(":/Cards/img/UI20/Cards/menu_ico.svg"));
menuButton->setVisible(true);
QObject::connect(menuButton, &QToolButton::clicked, this, [this]() { OpenMenu(); });
AddHeading(menuButton);
m_messageLabel = new QLabel(this);
m_messageLabel->setWordWrap(true);
m_messageLabel->setVisible(true);
m_messageLabel->setAlignment(Qt::AlignCenter);
m_messageLabel->setText(tr("Material not available"));
AddHeading(m_messageLabel);
AZ::EntitySystemBus::Handler::BusConnect();
} }
MaterialPropertyInspector::~MaterialPropertyInspector() MaterialPropertyInspector::~MaterialPropertyInspector()
{ {
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect(); AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AZ::EntitySystemBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
MaterialComponentNotificationBus::Handler::BusDisconnect();
} }
bool MaterialPropertyInspector::LoadMaterial() bool MaterialPropertyInspector::LoadMaterial(
const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId)
{ {
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(m_materialAssetId, m_editData)) UnloadMaterial();
m_entityId = entityId;
m_materialAssignmentId = materialAssignmentId;
MaterialComponentNotificationBus::Handler::BusDisconnect();
MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
AZ::Data::AssetId materialAssetId = {};
MaterialComponentRequestBus::EventResult(
materialAssetId, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialOverride, m_materialAssignmentId);
if (!materialAssetId.IsValid())
{
UnloadMaterial();
return false;
}
if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(materialAssetId, m_editData))
{ {
AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to load material data."); AZ_Warning("AZ::Render::EditorMaterialComponentInspector", false, "Failed to load material data.");
UnloadMaterial();
return false; return false;
} }
@@ -77,6 +107,7 @@ namespace AZ
if (!m_materialInstance) if (!m_materialInstance)
{ {
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material instance could not be created."); AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material instance could not be created.");
UnloadMaterial();
return false; return false;
} }
@@ -102,15 +133,36 @@ namespace AZ
} }
} }
Populate();
m_messageLabel->setVisible(false);
return true; return true;
} }
void MaterialPropertyInspector::UnloadMaterial()
{
Reset();
m_editData = EditorMaterialComponentUtil::MaterialEditData();
m_materialInstance = {};
m_dirtyPropertyFlags.set();
m_editorFunctors = {};
m_internalEditNotification = {};
m_messageLabel->setVisible(true);
m_messageLabel->setText(tr("Material not available"));
}
bool MaterialPropertyInspector::IsLoaded() const
{
return m_entityId.IsValid() && m_materialInstance && m_editData.m_materialAsset.IsReady();
}
void MaterialPropertyInspector::Reset() void MaterialPropertyInspector::Reset()
{ {
m_activeProperty = {}; m_activeProperty = {};
m_groups = {}; m_groups = {};
m_dirtyPropertyFlags.set(); m_dirtyPropertyFlags.set();
m_internalEditNotification = {};
AZ::TickBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect(); AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset(); AtomToolsFramework::InspectorWidget::Reset();
} }
@@ -150,9 +202,18 @@ namespace AZ
QFileInfo materialTypeSourceFileInfo(m_editData.m_materialTypeSourcePath.c_str()); QFileInfo materialTypeSourceFileInfo(m_editData.m_materialTypeSourcePath.c_str());
QFileInfo materialParentSourceFileInfo(AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_editData.m_materialParentAsset.GetId()).c_str()); QFileInfo materialParentSourceFileInfo(AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_editData.m_materialParentAsset.GetId()).c_str());
AZStd::string entityName;
AZ::ComponentApplicationBus::BroadcastResult(
entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, m_entityId);
AZStd::string slotName;
MaterialComponentRequestBus::EventResult(
slotName, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialSlotLabel, m_materialAssignmentId);
QString materialInfo; QString materialInfo;
materialInfo += tr("<table>"); materialInfo += tr("<table>");
materialInfo += tr("<tr><td><b>Material Slot&emsp;</b></td><td>%1</td></tr>").arg(m_slotName.c_str()); materialInfo += tr("<tr><td><b>Entity&emsp;</b></td><td>%1</td></tr>").arg(entityName.c_str());
materialInfo += tr("<tr><td><b>Material Slot&emsp;</b></td><td>%1</td></tr>").arg(slotName.c_str());
if (!materialFileInfo.fileName().isEmpty()) if (!materialFileInfo.fileName().isEmpty())
{ {
materialInfo += tr("<tr><td><b>Material&emsp;</b></td><td>%1</td></tr>").arg(materialFileInfo.fileName()); materialInfo += tr("<tr><td><b>Material&emsp;</b></td><td>%1</td></tr>").arg(materialFileInfo.fileName());
@@ -209,16 +270,8 @@ namespace AZ
} }
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
const AZ::Crc32 saveStateKey(AZStd::string::format(
"MaterialPropertyInspector::PropertyGroup::%s::%s", m_materialAssetId.ToString<AZStd::string>().c_str(),
groupNameId.c_str()));
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, saveStateKey, &group, nullptr, group.TYPEINFO_Uuid(), this, this, GetSaveStateKeyForGroup(groupNameId));
[](const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target) {
AZ_UNUSED(source);
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(target);
return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue);
});
AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget); AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget);
} }
@@ -262,35 +315,90 @@ namespace AZ
} }
// Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties
const AZ::Crc32 saveStateKey(AZStd::string::format(
"MaterialPropertyInspector::PropertyGroup::%s::%s", m_materialAssetId.ToString<AZStd::string>().c_str(),
groupNameId.c_str()));
auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget(
&group, &group, group.TYPEINFO_Uuid(), this, this, saveStateKey, &group, nullptr, group.TYPEINFO_Uuid(), this, this, GetSaveStateKeyForGroup(groupNameId));
[](const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target) {
AZ_UNUSED(source);
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(target);
return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue);
});
AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget); AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget);
} }
AddGroupsEnd(); AddGroupsEnd();
m_dirtyPropertyFlags.set(); LoadOverridesFromEntity();
RunEditorMaterialFunctors();
} }
void MaterialPropertyInspector::RunPropertyChangedCallback() void MaterialPropertyInspector::LoadOverridesFromEntity()
{ {
if (m_propertyChangedCallback) if (!IsLoaded())
{ {
m_propertyChangedCallback(m_editData.m_materialPropertyOverrideMap); return;
}
m_editData.m_materialPropertyOverrideMap.clear();
MaterialComponentRequestBus::EventResult(
m_editData.m_materialPropertyOverrideMap, m_entityId, &MaterialComponentRequestBus::Events::GetPropertyOverrides,
m_materialAssignmentId);
for (auto& group : m_groups)
{
for (auto& property : group.second.m_properties)
{
const AtomToolsFramework::DynamicPropertyConfig& propertyConfig = property.GetConfig();
const auto overrideItr = m_editData.m_materialPropertyOverrideMap.find(propertyConfig.m_id);
const auto& editValue = overrideItr != m_editData.m_materialPropertyOverrideMap.end() ? overrideItr->second : propertyConfig.m_originalValue;
// This first converts to an acceptable runtime type in case the value came from script
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
if (!propertyIndex.IsNull())
{
const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(editValue);
if (runtimeValue.IsValid())
{
property.SetValue(AtomToolsFramework::ConvertToEditableType(runtimeValue));
}
}
else
{
property.SetValue(editValue);
}
UpdateMaterialInstanceProperty(property);
}
}
m_dirtyPropertyFlags.set();
RunEditorMaterialFunctors();
RefreshAll();
}
void MaterialPropertyInspector::SaveOverridesToEntity(bool commitChanges)
{
if (!IsLoaded())
{
return;
}
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetPropertyOverrides, m_materialAssignmentId,
m_editData.m_materialPropertyOverrideMap);
if (commitChanges)
{
AzToolsFramework::ScopedUndoBatch undoBatch("Material slot changed.");
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, m_entityId);
m_internalEditNotification = true;
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited);
m_internalEditNotification = false;
} }
} }
void MaterialPropertyInspector::RunEditorMaterialFunctors() void MaterialPropertyInspector::RunEditorMaterialFunctors()
{ {
if (!IsLoaded())
{
return;
}
AZStd::unordered_set<AZ::Name> changedPropertyNames; AZStd::unordered_set<AZ::Name> changedPropertyNames;
AZStd::unordered_set<AZ::Name> changedPropertyGroupNames; AZStd::unordered_set<AZ::Name> changedPropertyGroupNames;
@@ -337,11 +445,13 @@ namespace AZ
// Apply any changes to material property meta data back to the editor property configurations // Apply any changes to material property meta data back to the editor property configurations
for (auto& groupPair : m_groups) for (auto& groupPair : m_groups)
{ {
AZ::Name groupName{groupPair.first}; AZ::Name groupName{ groupPair.first };
if (changedPropertyGroupNames.find(groupName) != changedPropertyGroupNames.end()) if (changedPropertyGroupNames.find(groupName) != changedPropertyGroupNames.end())
{ {
SetGroupVisible(groupPair.first, propertyGroupDynamicMetadata[groupName].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled); SetGroupVisible(
groupPair.first,
propertyGroupDynamicMetadata[groupName].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled);
} }
for (auto& property : groupPair.second.m_properties) for (auto& property : groupPair.second.m_properties)
@@ -355,12 +465,12 @@ namespace AZ
if (oldReadOnly != propertyConfig.m_readOnly) if (oldReadOnly != propertyConfig.m_readOnly)
{ {
RefreshAll(); RefreshGroup(groupPair.first);
} }
if (oldVisible != propertyConfig.m_visible) if (oldVisible != propertyConfig.m_visible)
{ {
RebuildAll(); RebuildGroup(groupPair.first);
} }
} }
} }
@@ -368,57 +478,37 @@ namespace AZ
void MaterialPropertyInspector::UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property) void MaterialPropertyInspector::UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property)
{ {
if (m_materialInstance) if (!IsLoaded())
{ {
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId()); return;
if (!propertyIndex.IsNull()) }
{
m_dirtyPropertyFlags.set(propertyIndex.GetIndex());
const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(property.GetValue()); const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
if (runtimeValue.IsValid()) if (!propertyIndex.IsNull())
{ {
m_materialInstance->SetPropertyValue(propertyIndex, runtimeValue); m_dirtyPropertyFlags.set(propertyIndex.GetIndex());
}
const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(property.GetValue());
if (runtimeValue.IsValid())
{
m_materialInstance->SetPropertyValue(propertyIndex, runtimeValue);
} }
} }
} }
void MaterialPropertyInspector::SetOverrides(const MaterialPropertyOverrideMap& propertyOverrideMap) AZ::Crc32 MaterialPropertyInspector::GetSaveStateKeyForGroup(const AZStd::string& groupNameId) const
{ {
m_editData.m_materialPropertyOverrideMap = propertyOverrideMap; return AZ::Crc32(AZStd::string::format(
"MaterialPropertyInspector::PropertyGroup::%s::%s", m_editData.m_materialAssetId.ToString<AZStd::string>().c_str(),
groupNameId.c_str()));
}
for (auto& group : m_groups) bool MaterialPropertyInspector::AreNodePropertyValuesEqual(
{ const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target)
for (auto& property : group.second.m_properties) {
{ AZ_UNUSED(source);
const AtomToolsFramework::DynamicPropertyConfig& propertyConfig = property.GetConfig(); const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(target);
const auto overrideItr = m_editData.m_materialPropertyOverrideMap.find(propertyConfig.m_id); return property && AtomToolsFramework::ArePropertyValuesEqual(property->GetValue(), property->GetConfig().m_parentValue);
const auto& editValue = overrideItr != m_editData.m_materialPropertyOverrideMap.end() ? overrideItr->second : propertyConfig.m_originalValue;
// This first converts to an acceptable runtime type in case the value came from script
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
if (!propertyIndex.IsNull())
{
const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(editValue);
if (runtimeValue.IsValid())
{
property.SetValue(AtomToolsFramework::ConvertToEditableType(runtimeValue));
}
}
else
{
property.SetValue(editValue);
}
UpdateMaterialInstanceProperty(property);
}
}
m_dirtyPropertyFlags.set();
RunPropertyChangedCallback();
RunEditorMaterialFunctors();
RebuildAll();
} }
bool MaterialPropertyInspector::SaveMaterial() const bool MaterialPropertyInspector::SaveMaterial() const
@@ -446,7 +536,8 @@ namespace AZ
bool MaterialPropertyInspector::SaveMaterialToSource() const bool MaterialPropertyInspector::SaveMaterialToSource() const
{ {
const QString saveFilePath = AtomToolsFramework::GetSaveFileInfo(m_editData.m_materialSourcePath.c_str()).absoluteFilePath(); const QString saveFilePath =
AtomToolsFramework::GetSaveFileInfo(m_editData.m_materialSourcePath.c_str()).absoluteFilePath();
if (saveFilePath.isEmpty()) if (saveFilePath.isEmpty())
{ {
return false; return false;
@@ -463,13 +554,13 @@ namespace AZ
bool MaterialPropertyInspector::HasMaterialSource() const bool MaterialPropertyInspector::HasMaterialSource() const
{ {
return !m_editData.m_materialSourcePath.empty() && return IsLoaded() && !m_editData.m_materialSourcePath.empty() &&
AZ::StringFunc::Path::IsExtension(m_editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension); AZ::StringFunc::Path::IsExtension(m_editData.m_materialSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
} }
bool MaterialPropertyInspector::HasMaterialParentSource() const bool MaterialPropertyInspector::HasMaterialParentSource() const
{ {
return !m_editData.m_materialParentSourcePath.empty() && return IsLoaded() && !m_editData.m_materialParentSourcePath.empty() &&
AZ::StringFunc::Path::IsExtension( AZ::StringFunc::Path::IsExtension(
m_editData.m_materialParentSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension); m_editData.m_materialParentSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
} }
@@ -479,7 +570,7 @@ namespace AZ
if (HasMaterialSource()) if (HasMaterialSource())
{ {
EditorMaterialSystemComponentRequestBus::Broadcast( EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, m_editData.m_materialSourcePath); &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, m_editData.m_materialSourcePath);
} }
} }
@@ -488,10 +579,42 @@ namespace AZ
if (HasMaterialParentSource()) if (HasMaterialParentSource())
{ {
EditorMaterialSystemComponentRequestBus::Broadcast( EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, m_editData.m_materialParentSourcePath); &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, m_editData.m_materialParentSourcePath);
} }
} }
void MaterialPropertyInspector::OpenMenu()
{
QAction* action = nullptr;
QMenu menu(this);
action = menu.addAction("Clear Overrides", [this] {
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetPropertyOverrides, m_materialAssignmentId,
MaterialPropertyOverrideMap());
QueueUpdateUI();
});
action->setEnabled(IsLoaded());
menu.addSeparator();
action = menu.addAction("Save Material", [this] { SaveMaterial(); });
action->setEnabled(IsLoaded());
action = menu.addAction("Save Material To Source", [this] { SaveMaterialToSource(); });
action->setEnabled(HasMaterialSource());
menu.addSeparator();
action = menu.addAction("Open Source Material In Editor", [this] { OpenMaterialSourceInEditor(); });
action->setEnabled(HasMaterialSource());
action = menu.addAction("Open Parent Material In Editor", [this] { OpenMaterialParentSourceInEditor(); });
action->setEnabled(HasMaterialParentSource());
menu.exec(QCursor::pos());
}
const EditorMaterialComponentUtil::MaterialEditData& MaterialPropertyInspector::GetEditData() const const EditorMaterialComponentUtil::MaterialEditData& MaterialPropertyInspector::GetEditData() const
{ {
return m_editData; return m_editData;
@@ -501,7 +624,8 @@ namespace AZ
{ {
// For some reason the reflected property editor notifications are not symmetrical // 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 // 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 // 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
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property) if (property)
{ {
@@ -521,15 +645,16 @@ namespace AZ
{ {
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue(); m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty); UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback(); SaveOverridesToEntity(false);
} }
} }
} }
void MaterialPropertyInspector::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) void MaterialPropertyInspector::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. // As above, there are symmetrical functions on the notification interface for when editing begins and ends and has been
// when this function executes the changes to the property are ready to be committed or reverted // 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
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property) if (property)
{ {
@@ -537,85 +662,92 @@ namespace AZ
{ {
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue(); m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty); UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback(); SaveOverridesToEntity(true);
RunEditorMaterialFunctors(); RunEditorMaterialFunctors();
m_activeProperty = nullptr; m_activeProperty = nullptr;
} }
} }
} }
bool OpenInspectorDialog( void MaterialPropertyInspector::OnEntityInitialized(const AZ::EntityId& entityId)
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback)
{ {
QWidget* activeWindow = nullptr; if (m_entityId == entityId)
AzToolsFramework::EditorWindowRequestBus::BroadcastResult(activeWindow, &AzToolsFramework::EditorWindowRequests::GetAppMainWindow);
// Constructing a dialog with a table to display all configurable material export items
QDialog dialog(activeWindow);
dialog.setWindowTitle("Material Inspector");
MaterialPropertyInspector* inspector = new MaterialPropertyInspector(slotName, assetId, propertyChangedCallback, &dialog);
if (!inspector->LoadMaterial())
{ {
return false; UnloadMaterial();
} }
}
inspector->Populate(); void MaterialPropertyInspector::OnEntityDestroyed(const AZ::EntityId& entityId)
inspector->SetOverrides(propertyOverrideMap); {
if (m_entityId == entityId)
{
UnloadMaterial();
}
}
// Create the menu button void MaterialPropertyInspector::OnEntityActivated(const AZ::EntityId& entityId)
QToolButton* menuButton = new QToolButton(&dialog); {
menuButton->setAutoRaise(true); if (m_entityId == entityId)
menuButton->setIcon(QIcon(":/Cards/img/UI20/Cards/menu_ico.svg")); {
menuButton->setVisible(true); QueueUpdateUI();
QObject::connect(menuButton, &QToolButton::clicked, &dialog, [&]() { }
QAction* action = nullptr; }
QMenu menu(&dialog); void MaterialPropertyInspector::OnEntityDeactivated(const AZ::EntityId& entityId)
action = menu.addAction("Clear Overrides", [&] { inspector->SetOverrides(MaterialPropertyOverrideMap()); }); {
action = menu.addAction("Revert Changes", [&] { inspector->SetOverrides(propertyOverrideMap); }); if (m_entityId == entityId)
{
UnloadMaterial();
}
}
menu.addSeparator(); void MaterialPropertyInspector::OnEntityNameChanged(const AZ::EntityId& entityId, const AZStd::string& name)
action = menu.addAction("Save Material", [&] { inspector->SaveMaterial(); }); {
action = menu.addAction("Save Material To Source", [&] { inspector->SaveMaterialToSource(); }); AZ_UNUSED(name);
action->setEnabled(inspector->HasMaterialSource()); if (m_entityId == entityId)
{
QueueUpdateUI();
}
}
menu.addSeparator(); void MaterialPropertyInspector::OnTick(float deltaTime, ScriptTimePoint time)
action = menu.addAction("Open Source Material In Editor", [&] { inspector->OpenMaterialSourceInEditor(); }); {
action->setEnabled(inspector->HasMaterialSource()); AZ_UNUSED(time);
action = menu.addAction("Open Parent Material In Editor", [&] { inspector->OpenMaterialParentSourceInEditor(); }); AZ_UNUSED(deltaTime);
action->setEnabled(inspector->HasMaterialParentSource()); UpdateUI();
menu.exec(QCursor::pos()); AZ::TickBus::Handler::BusDisconnect();
}); }
QDialogButtonBox* buttonBox = new QDialogButtonBox(&dialog); void MaterialPropertyInspector::OnMaterialsEdited()
buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); {
QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); if (!m_internalEditNotification)
QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); {
QueueUpdateUI();
}
}
QObject::connect(&dialog, &QDialog::rejected, &dialog, [&] { inspector->SetOverrides(propertyOverrideMap); }); void MaterialPropertyInspector::UpdateUI()
{
AZ::Data::AssetId assetId;
MaterialComponentRequestBus::EventResult(
assetId, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialOverride, m_materialAssignmentId);
QVBoxLayout* dialogLayout = new QVBoxLayout(&dialog); if (IsLoaded() && m_editData.m_materialAssetId == assetId)
dialogLayout->addWidget(menuButton); {
dialogLayout->addWidget(inspector); LoadOverridesFromEntity();
dialogLayout->addWidget(buttonBox); }
dialog.setLayout(dialogLayout); else
dialog.setModal(true); {
LoadMaterial(m_entityId, m_materialAssignmentId);
}
}
// Forcing the initial dialog size to accomodate typical content. void MaterialPropertyInspector::QueueUpdateUI()
// Temporarily settng fixed size because dialog.show/exec invokes WindowDecorationWrapper::showEvent. {
// This forces the dialog to be centered and sized based on the layout of content. if (!AZ::TickBus::Handler::BusIsConnected())
// Resizing the dialog after show will not be centered and moving the dialog programatically doesn't m0ve the custmk frame. {
dialog.setFixedSize(500, 800); AZ::TickBus::Handler::BusConnect();
dialog.show(); }
// Removing fixed size to allow drag resizing
dialog.setMinimumSize(0, 0);
dialog.setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
// Return true if the user press the export button
return dialog.exec() == QDialog::Accepted;
} }
} // namespace EditorMaterialComponentInspector } // namespace EditorMaterialComponentInspector
} // namespace Render } // namespace Render
@@ -9,17 +9,22 @@
#pragma once #pragma once
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h> #include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.h> #include <AtomToolsFramework/Inspector/InspectorWidget.h>
#include <AzCore/Asset/AssetCommon.h> #include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/EntityBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/containers/unordered_map.h> #include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/containers/vector.h> #include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/function/function_base.h> #include <AzCore/std/function/function_base.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h> #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <Material/EditorMaterialComponentUtil.h> #include <Material/EditorMaterialComponentUtil.h>
#endif #endif
class QLabel;
namespace AZ namespace AZ
{ {
namespace Render namespace Render
@@ -31,31 +36,33 @@ namespace AZ
class MaterialPropertyInspector class MaterialPropertyInspector
: public AtomToolsFramework::InspectorWidget : public AtomToolsFramework::InspectorWidget
, public AzToolsFramework::IPropertyEditorNotify , public AzToolsFramework::IPropertyEditorNotify
{ , public AZ::EntitySystemBus::Handler
, public AZ::TickBus::Handler
, public MaterialComponentNotificationBus::Handler
{
Q_OBJECT Q_OBJECT
public: public:
AZ_CLASS_ALLOCATOR(MaterialPropertyInspector, AZ::SystemAllocator, 0); AZ_CLASS_ALLOCATOR(MaterialPropertyInspector, AZ::SystemAllocator, 0);
explicit MaterialPropertyInspector( MaterialPropertyInspector(QWidget* parent = nullptr);
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent = nullptr);
~MaterialPropertyInspector() override; ~MaterialPropertyInspector() override;
bool LoadMaterial(); bool LoadMaterial(const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId);
void UnloadMaterial();
bool IsLoaded() const;
// AtomToolsFramework::InspectorRequestBus::Handler overrides... // AtomToolsFramework::InspectorRequestBus::Handler overrides...
void Reset() override; void Reset() override;
void Populate(); void Populate();
void SetOverrides(const MaterialPropertyOverrideMap& propertyOverrideMap);
bool SaveMaterial() const; bool SaveMaterial() const;
bool SaveMaterialToSource() const; bool SaveMaterialToSource() const;
bool HasMaterialSource() const; bool HasMaterialSource() const;
bool HasMaterialParentSource() const; bool HasMaterialParentSource() const;
void OpenMaterialSourceInEditor() const; void OpenMaterialSourceInEditor() const;
void OpenMaterialParentSourceInEditor() const; void OpenMaterialParentSourceInEditor() const;
void OpenMenu();
const EditorMaterialComponentUtil::MaterialEditData& GetEditData() const; const EditorMaterialComponentUtil::MaterialEditData& GetEditData() const;
private: private:
@@ -69,28 +76,47 @@ namespace AZ
void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode*, const QPoint&) override {} void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode*, const QPoint&) override {}
void PropertySelectionChanged([[maybe_unused]] AzToolsFramework::InstanceDataNode*, bool) override {} void PropertySelectionChanged([[maybe_unused]] AzToolsFramework::InstanceDataNode*, bool) override {}
// AZ::EntitySystemBus::Handler overrides...
void OnEntityInitialized(const AZ::EntityId& entityId) override;
void OnEntityDestroyed(const AZ::EntityId& entityId) override;
void OnEntityActivated(const AZ::EntityId& entityId) override;
void OnEntityDeactivated(const AZ::EntityId& entityId) override;
void OnEntityNameChanged(const AZ::EntityId& entityId, const AZStd::string& name) override;
//! AZ::TickBus::Handler overrides...
void OnTick(float deltaTime, ScriptTimePoint time) override;
//! MaterialComponentNotificationBus::Handler overrides...
void OnMaterialsEdited() override;
void UpdateUI();
void QueueUpdateUI();
void AddDetailsGroup(); void AddDetailsGroup();
void AddUvNamesGroup(); void AddUvNamesGroup();
void RunPropertyChangedCallback();
void LoadOverridesFromEntity();
void SaveOverridesToEntity(bool commitChanges);
void RunEditorMaterialFunctors(); void RunEditorMaterialFunctors();
void UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property); void UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property);
AZ::Crc32 GetSaveStateKeyForGroup(const AZStd::string& groupNameId) const;
static bool AreNodePropertyValuesEqual(
const AzToolsFramework::InstanceDataNode* source, const AzToolsFramework::InstanceDataNode* target);
// Tracking the property that is actively being edited in the inspector // Tracking the property that is actively being edited in the inspector
const AtomToolsFramework::DynamicProperty* m_activeProperty = {}; const AtomToolsFramework::DynamicProperty* m_activeProperty = {};
AZStd::string m_slotName; AZ::EntityId m_entityId;
AZ::Data::AssetId m_materialAssetId = {}; AZ::Render::MaterialAssignmentId m_materialAssignmentId;
EditorMaterialComponentUtil::MaterialEditData m_editData; EditorMaterialComponentUtil::MaterialEditData m_editData;
PropertyChangedCallback m_propertyChangedCallback = {};
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance = {}; AZ::Data::Instance<AZ::RPI::Material> m_materialInstance = {};
AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors = {}; AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors = {};
AZ::RPI::MaterialPropertyFlags m_dirtyPropertyFlags = {}; AZ::RPI::MaterialPropertyFlags m_dirtyPropertyFlags = {};
AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups = {}; AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups = {};
}; bool m_internalEditNotification = {};
QLabel* m_messageLabel = {};
bool OpenInspectorDialog( };
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback);
} // namespace EditorMaterialComponentInspector } // namespace EditorMaterialComponentInspector
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ
@@ -80,10 +80,9 @@ namespace AZ
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context)) if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{ {
serializeContext->Class<EditorMaterialComponentSlot>() serializeContext->Class<EditorMaterialComponentSlot>()
->Version(6, &EditorMaterialComponentSlot::ConvertVersion) ->Version(7, &EditorMaterialComponentSlot::ConvertVersion)
->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("id", &EditorMaterialComponentSlot::m_id)
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset)
; ;
if (AZ::EditContext* editContext = serializeContext->GetEditContext()) if (AZ::EditContext* editContext = serializeContext->GetEditContext())
@@ -114,20 +113,22 @@ namespace AZ
->Constructor<const EditorMaterialComponentSlot&>() ->Constructor<const EditorMaterialComponentSlot&>()
->Property("id", BehaviorValueProperty(&EditorMaterialComponentSlot::m_id)) ->Property("id", BehaviorValueProperty(&EditorMaterialComponentSlot::m_id))
->Property("materialAsset", BehaviorValueProperty(&EditorMaterialComponentSlot::m_materialAsset)) ->Property("materialAsset", BehaviorValueProperty(&EditorMaterialComponentSlot::m_materialAsset))
->Property("propertyOverrides", BehaviorValueProperty(&EditorMaterialComponentSlot::m_propertyOverrides))
->Property("matModUvOverrides", BehaviorValueProperty(&EditorMaterialComponentSlot::m_matModUvOverrides))
; ;
} }
}; };
AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const AZ::Data::AssetId EditorMaterialComponentSlot::GetDefaultAssetId() const
{ {
return m_defaultMaterialAsset.GetId(); AZ::Data::AssetId assetId;
MaterialComponentRequestBus::EventResult(assetId, m_entityId, &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId, m_id);
return assetId;
} }
AZStd::string EditorMaterialComponentSlot::GetLabel() const AZStd::string EditorMaterialComponentSlot::GetLabel() const
{ {
return m_label; AZStd::string label;
MaterialComponentRequestBus::EventResult(label, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialSlotLabel, m_id);
return label;
} }
bool EditorMaterialComponentSlot::HasSourceData() const bool EditorMaterialComponentSlot::HasSourceData() const
@@ -137,50 +138,45 @@ namespace AZ
return !sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension); return !sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
} }
void EditorMaterialComponentSlot::OnMaterialChanged() const void EditorMaterialComponentSlot::SetAsset(const Data::AssetId& assetId)
{ {
if (m_materialChangedCallback) m_materialAsset.Create(assetId);
{ MaterialComponentRequestBus::Event(
m_materialChangedCallback(); m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
} OnDataChanged();
} }
void EditorMaterialComponentSlot::OnPropertyChanged() const void EditorMaterialComponentSlot::SetAsset(const Data::Asset<RPI::MaterialAsset>& asset)
{ {
if (m_propertyChangedCallback) m_materialAsset = asset;
{ MaterialComponentRequestBus::Event(
m_propertyChangedCallback(); m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
} OnDataChanged();
}
void EditorMaterialComponentSlot::OpenMaterialEditor() const
{
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_materialAsset.GetId());
if (!sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{
EditorMaterialSystemComponentRequestBus::Broadcast(&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, sourcePath);
}
} }
void EditorMaterialComponentSlot::Clear() void EditorMaterialComponentSlot::Clear()
{ {
m_materialAsset = {}; m_materialAsset = {};
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
ClearOverrides();
}
void EditorMaterialComponentSlot::ClearToDefaultAsset()
{
m_materialAsset.Create(GetDefaultAssetId());
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
ClearOverrides(); ClearOverrides();
} }
void EditorMaterialComponentSlot::ClearOverrides() void EditorMaterialComponentSlot::ClearOverrides()
{ {
m_propertyOverrides = {}; MaterialComponentRequestBus::Event(
m_matModUvOverrides = {}; m_entityId, &MaterialComponentRequestBus::Events::SetPropertyOverrides, m_id, MaterialPropertyOverrideMap());
OnMaterialChanged(); MaterialComponentRequestBus::Event(
} m_entityId, &MaterialComponentRequestBus::Events::SetModelUvOverrides, m_id, AZ::RPI::MaterialModelUvOverrideMap());
OnDataChanged();
void EditorMaterialComponentSlot::ResetToDefaultAsset()
{
m_materialAsset = m_defaultMaterialAsset;
m_propertyOverrides = {};
m_matModUvOverrides = {};
OnMaterialChanged();
} }
void EditorMaterialComponentSlot::OpenMaterialExporter() void EditorMaterialComponentSlot::OpenMaterialExporter()
@@ -189,7 +185,7 @@ namespace AZ
// But we still need to allow the user to reconfigure it using the dialog // But we still need to allow the user to reconfigure it using the dialog
EditorMaterialComponentExporter::ExportItemsContainer exportItems; EditorMaterialComponentExporter::ExportItemsContainer exportItems;
{ {
EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label}; EditorMaterialComponentExporter::ExportItem exportItem{ GetDefaultAssetId(), GetLabel() };
exportItems.push_back(exportItem); exportItems.push_back(exportItem);
} }
@@ -203,7 +199,8 @@ namespace AZ
continue; continue;
} }
// Generate a new asset ID utilizing the export file path so that we can update this material slot to reference the new asset // Generate a new asset ID utilizing the export file path so that we can update this material slot to reference the new
// asset
const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0); const auto& assetIdOutcome = AZ::RPI::AssetUtils::MakeAssetId(exportItem.GetExportPath(), 0);
if (assetIdOutcome) if (assetIdOutcome)
{ {
@@ -219,37 +216,43 @@ namespace AZ
} }
} }
void EditorMaterialComponentSlot::OpenMaterialEditor() const
{
const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(m_materialAsset.GetId());
if (!sourcePath.empty() && AZ::StringFunc::Path::IsExtension(sourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{
EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, sourcePath);
}
}
void EditorMaterialComponentSlot::OpenMaterialInspector() void EditorMaterialComponentSlot::OpenMaterialInspector()
{ {
MaterialPropertyOverrideMap initialPropertyOverrides = m_propertyOverrides; EditorMaterialSystemComponentRequestBus::Broadcast(
auto applyPropertyChangedCallback = [this](const MaterialPropertyOverrideMap& propertyOverrides) { &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialInspector, m_entityId, m_id);
m_propertyOverrides = propertyOverrides;
OnPropertyChanged();
};
if (m_materialAsset.GetId().IsValid())
{
if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback))
{
OnMaterialChanged();
}
}
} }
void EditorMaterialComponentSlot::OpenUvNameMapInspector() void EditorMaterialComponentSlot::OpenUvNameMapInspector()
{ {
RPI::MaterialModelUvOverrideMap initialUvOverrides = m_matModUvOverrides;
auto applyMatModUvOverrideChangedCallback = [this](const RPI::MaterialModelUvOverrideMap& matModUvOverrides) {
m_matModUvOverrides = matModUvOverrides;
// Treated as a special property. It will be updated together with properties.
OnPropertyChanged();
};
if (m_materialAsset.GetId().IsValid()) if (m_materialAsset.GetId().IsValid())
{ {
if (EditorMaterialComponentInspector::OpenInspectorDialog(m_materialAsset.GetId(), m_matModUvOverrides, m_modelUvNames, applyMatModUvOverrideChangedCallback)) AZStd::unordered_set<AZ::Name> modelUvNames;
MaterialReceiverRequestBus::EventResult(modelUvNames, m_entityId, &MaterialReceiverRequestBus::Events::GetModelUvNames);
RPI::MaterialModelUvOverrideMap matModUvOverrides;
MaterialComponentRequestBus::EventResult(
matModUvOverrides, m_entityId, &MaterialComponentRequestBus::Events::GetModelUvOverrides, m_id);
auto applyMatModUvOverrideChangedCallback = [this](const RPI::MaterialModelUvOverrideMap& matModUvOverrides)
{ {
OnMaterialChanged(); MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetModelUvOverrides, m_id, matModUvOverrides);
};
if (EditorMaterialComponentInspector::OpenInspectorDialog(
m_materialAsset.GetId(), matModUvOverrides, modelUvNames, applyMatModUvOverrideChangedCallback))
{
OnDataChanged();
} }
} }
} }
@@ -261,7 +264,7 @@ namespace AZ
QAction* action = nullptr; QAction* action = nullptr;
action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); }); action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); });
action->setEnabled(m_defaultMaterialAsset.GetId().IsValid()); action->setEnabled(GetDefaultAssetId().IsValid());
menu.addSeparator(); menu.addSeparator();
@@ -276,10 +279,38 @@ namespace AZ
menu.addSeparator(); menu.addSeparator();
MaterialPropertyOverrideMap propertyOverrides;
MaterialComponentRequestBus::EventResult(
propertyOverrides, m_entityId, &MaterialComponentRequestBus::Events::GetPropertyOverrides, m_id);
RPI::MaterialModelUvOverrideMap matModUvOverrides;
MaterialComponentRequestBus::EventResult(
matModUvOverrides, m_entityId, &MaterialComponentRequestBus::Events::GetModelUvOverrides, m_id);
action = menu.addAction("Clear Material Instance Overrides", [this]() { ClearOverrides(); }); action = menu.addAction("Clear Material Instance Overrides", [this]() { ClearOverrides(); });
action->setEnabled(!m_propertyOverrides.empty() || !m_matModUvOverrides.empty()); action->setEnabled(!propertyOverrides.empty() || !matModUvOverrides.empty());
menu.exec(QCursor::pos()); menu.exec(QCursor::pos());
} }
void EditorMaterialComponentSlot::OnMaterialChanged() const
{
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
OnDataChanged();
}
void EditorMaterialComponentSlot::OnDataChanged() const
{
// This is triggered whenever a material slot changes outside of normal inspector interactions
// Handle undo, update configuration, and refresh the inspector to display the new values
AzToolsFramework::ScopedUndoBatch undoBatch("Material slot changed.");
AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, m_entityId);
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
}
} // namespace Render } // namespace Render
} // namespace AZ } // namespace AZ
@@ -33,29 +33,26 @@ namespace AZ
AZ::Data::AssetId GetDefaultAssetId() const; AZ::Data::AssetId GetDefaultAssetId() const;
AZStd::string GetLabel() const; AZStd::string GetLabel() const;
bool HasSourceData() const; bool HasSourceData() const;
void OpenMaterialEditor() const;
void ResetToDefaultAsset(); void SetAsset(const Data::AssetId& assetId);
void SetAsset(const Data::Asset<RPI::MaterialAsset>& asset);
void Clear(); void Clear();
void ClearToDefaultAsset();
void ClearOverrides(); void ClearOverrides();
void OpenMaterialExporter(); void OpenMaterialExporter();
void OpenMaterialEditor() const;
void OpenMaterialInspector(); void OpenMaterialInspector();
void OpenUvNameMapInspector(); void OpenUvNameMapInspector();
AZ::EntityId m_entityId;
MaterialAssignmentId m_id; MaterialAssignmentId m_id;
AZStd::string m_label;
Data::Asset<RPI::MaterialAsset> m_materialAsset; Data::Asset<RPI::MaterialAsset> m_materialAsset;
Data::Asset<RPI::MaterialAsset> m_defaultMaterialAsset;
MaterialPropertyOverrideMap m_propertyOverrides;
AZStd::function<void()> m_materialChangedCallback;
AZStd::function<void()> m_propertyChangedCallback;
RPI::MaterialModelUvOverrideMap m_matModUvOverrides;
AZStd::unordered_set<AZ::Name> m_modelUvNames; // Cached for override options.
private: private:
void OpenPopupMenu(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType); void OpenPopupMenu(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType);
void OnMaterialChanged() const; void OnMaterialChanged() const;
void OnPropertyChanged() const; void OnDataChanged() const;
}; };
// Vector of slots for assignable or overridable material data. // Vector of slots for assignable or overridable material data.
@@ -6,33 +6,31 @@
* *
*/ */
#include <Material/EditorMaterialSystemComponent.h> #include <Atom/RHI/Factory.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl> #include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/StringFunc/StringFunc.h> #include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h> #include <AzCore/Utils/Utils.h>
#include <AzFramework/Application/Application.h> #include <AzFramework/Application/Application.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h> #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h> #include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <Editor/LyViewPaneNames.h>
#include <Atom/RHI/Factory.h> #include <Material/EditorMaterialComponentInspector.h>
#include <Material/EditorMaterialSystemComponent.h>
#include <AtomToolsFramework/Util/Util.h>
#include <Material/MaterialThumbnail.h> #include <Material/MaterialThumbnail.h>
// Disables warning messages triggered by the Qt library // Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class // 4251: class needs to have dll-interface to be used by clients of class
// 4800: forcing value to bool 'true' or 'false' (performance warning) // 4800: forcing value to bool 'true' or 'false' (performance warning)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QApplication>
#include <QProcessEnvironment>
#include <QObject>
#include <QAction> #include <QAction>
#include <QApplication>
#include <QDockWidget>
#include <QObject>
#include <QProcessEnvironment>
AZ_POP_DISABLE_WARNING AZ_POP_DISABLE_WARNING
void InitMaterialEditorResources() void InitMaterialEditorResources()
@@ -95,6 +93,7 @@ namespace AZ
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect(); AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusConnect(); AzToolsFramework::EditorMenuNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
SetupThumbnails(); SetupThumbnails();
m_materialBrowserInteractions.reset(aznew MaterialBrowserInteractions); m_materialBrowserInteractions.reset(aznew MaterialBrowserInteractions);
@@ -106,6 +105,7 @@ namespace AZ
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect(); AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect(); AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusDisconnect(); AzToolsFramework::EditorMenuNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
TeardownThumbnails(); TeardownThumbnails();
m_materialBrowserInteractions.reset(); m_materialBrowserInteractions.reset();
@@ -117,7 +117,7 @@ namespace AZ
} }
} }
void EditorMaterialSystemComponent::OpenInMaterialEditor(const AZStd::string& sourcePath) void EditorMaterialSystemComponent::OpenMaterialEditor(const AZStd::string& sourcePath)
{ {
AZ_TracePrintf("MaterialComponent", "Launching Material Editor"); AZ_TracePrintf("MaterialComponent", "Launching Material Editor");
@@ -140,6 +140,20 @@ namespace AZ
AtomToolsFramework::LaunchTool("MaterialEditor", ".exe", arguments); AtomToolsFramework::LaunchTool("MaterialEditor", ".exe", arguments);
} }
void EditorMaterialSystemComponent::OpenMaterialInspector(
const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId)
{
auto dockWidget = AzToolsFramework::InstanceViewPane("Material Property Inspector");
if (dockWidget)
{
auto inspector = static_cast<AZ::Render::EditorMaterialComponentInspector::MaterialPropertyInspector*>(dockWidget->widget());
if (inspector)
{
inspector->LoadMaterial(entityId, materialAssignmentId);
}
}
}
void EditorMaterialSystemComponent::OnApplicationAboutToStop() void EditorMaterialSystemComponent::OnApplicationAboutToStop()
{ {
TeardownThumbnails(); TeardownThumbnails();
@@ -157,11 +171,12 @@ namespace AZ
QObject::connect( QObject::connect(
m_openMaterialEditorAction, &QAction::triggered, m_openMaterialEditorAction, [this]() m_openMaterialEditorAction, &QAction::triggered, m_openMaterialEditorAction, [this]()
{ {
OpenInMaterialEditor(""); OpenMaterialEditor("");
} }
); );
AzToolsFramework::EditorMenuRequestBus::Broadcast(&AzToolsFramework::EditorMenuRequestBus::Handler::AddMenuAction, "ToolMenu", m_openMaterialEditorAction, true); AzToolsFramework::EditorMenuRequestBus::Broadcast(
&AzToolsFramework::EditorMenuRequestBus::Handler::AddMenuAction, "ToolMenu", m_openMaterialEditorAction, true);
} }
} }
@@ -174,13 +189,25 @@ namespace AZ
} }
} }
void EditorMaterialSystemComponent::NotifyRegisterViews()
{
AzToolsFramework::ViewPaneOptions inspectorOptions;
inspectorOptions.canHaveMultipleInstances = true;
inspectorOptions.preferedDockingArea = Qt::NoDockWidgetArea;
inspectorOptions.paneRect = QRect(50, 50, 400, 700);
inspectorOptions.showInMenu = false;
inspectorOptions.showOnToolsToolbar = false;
AzToolsFramework::RegisterViewPane<AZ::Render::EditorMaterialComponentInspector::MaterialPropertyInspector>(
"Material Property Inspector", LyViewPane::CategoryTools, inspectorOptions);
}
void EditorMaterialSystemComponent::SetupThumbnails() void EditorMaterialSystemComponent::SetupThumbnails()
{ {
using namespace AzToolsFramework::Thumbnailer; using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration; using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, ThumbnailerRequestsBus::Broadcast(
MAKE_TCACHE(Thumbnails::MaterialThumbnailCache), &ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(Thumbnails::MaterialThumbnailCache),
ThumbnailContext::DefaultContext); ThumbnailContext::DefaultContext);
} }
@@ -189,12 +216,13 @@ namespace AZ
using namespace AzToolsFramework::Thumbnailer; using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration; using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::UnregisterThumbnailProvider, ThumbnailerRequestsBus::Broadcast(
Thumbnails::MaterialThumbnailCache::ProviderName, &ThumbnailerRequests::UnregisterThumbnailProvider, Thumbnails::MaterialThumbnailCache::ProviderName,
ThumbnailContext::DefaultContext); ThumbnailContext::DefaultContext);
} }
AzToolsFramework::AssetBrowser::SourceFileDetails EditorMaterialSystemComponent::GetSourceFileDetails(const char* fullSourceFileName) AzToolsFramework::AssetBrowser::SourceFileDetails EditorMaterialSystemComponent::GetSourceFileDetails(
const char* fullSourceFileName)
{ {
static const char* MaterialTypeIconPath = ":/Icons/materialtype.svg"; static const char* MaterialTypeIconPath = ":/Icons/materialtype.svg";
static const char* MaterialTypeExtension = "materialtype"; static const char* MaterialTypeExtension = "materialtype";
@@ -8,11 +8,10 @@
#pragma once #pragma once
#include <AzCore/Component/Component.h> #include <AzCore/Component/Component.h>
#include <AzFramework/Application/Application.h> #include <AzFramework/Application/Application.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h> #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Viewport/ActionBus.h> #include <AzToolsFramework/Viewport/ActionBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h> #include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h>
@@ -28,8 +27,9 @@ namespace AZ
: public AZ::Component : public AZ::Component
, private EditorMaterialSystemComponentRequestBus::Handler , private EditorMaterialSystemComponentRequestBus::Handler
, private AzFramework::ApplicationLifecycleEvents::Bus::Handler , private AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler , private AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
, public AzToolsFramework::EditorMenuNotificationBus::Handler , private AzToolsFramework::EditorMenuNotificationBus::Handler
, private AzToolsFramework::EditorEvents::Bus::Handler
{ {
public: public:
AZ_COMPONENT(EditorMaterialSystemComponent, "{96652157-DA0B-420F-B49C-0207C585144C}"); AZ_COMPONENT(EditorMaterialSystemComponent, "{96652157-DA0B-420F-B49C-0207C585144C}");
@@ -49,7 +49,8 @@ namespace AZ
private: private:
//! EditorMaterialSystemComponentRequestBus::Handler overrides... //! EditorMaterialSystemComponentRequestBus::Handler overrides...
void OpenInMaterialEditor(const AZStd::string& sourcePath) override; void OpenMaterialEditor(const AZStd::string& sourcePath) override;
void OpenMaterialInspector(const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId) override;
// AzFramework::ApplicationLifecycleEvents overrides... // AzFramework::ApplicationLifecycleEvents overrides...
void OnApplicationAboutToStop() override; void OnApplicationAboutToStop() override;
@@ -61,6 +62,9 @@ namespace AZ
void OnPopulateToolMenuItems() override; void OnPopulateToolMenuItems() override;
void OnResetToolMenuItems() override; void OnResetToolMenuItems() override;
// AztoolsFramework::EditorEvents::Bus::Handler overrides...
void NotifyRegisterViews() override;
void SetupThumbnails(); void SetupThumbnails();
void TeardownThumbnails(); void TeardownThumbnails();
@@ -29,16 +29,13 @@ namespace AZ
{ {
if (HandlesSource(fullSourceFileName)) if (HandlesSource(fullSourceFileName))
{ {
openers.push_back( openers.push_back({ "Material_Editor", "Open in Material Editor...", QIcon(),
{
"Material_Editor",
"Open in Material Editor...",
QIcon(),
[&](const char* fullSourceFileNameInCallback, [[maybe_unused]] const AZ::Uuid& sourceUUID) [&](const char* fullSourceFileNameInCallback, [[maybe_unused]] const AZ::Uuid& sourceUUID)
{ {
EditorMaterialSystemComponentRequestBus::Broadcast(&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, fullSourceFileNameInCallback); EditorMaterialSystemComponentRequestBus::Broadcast(
} &EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor,
}); fullSourceFileNameInCallback);
} });
} }
} }
@@ -35,6 +35,8 @@ namespace AZ
->Attribute(AZ::Script::Attributes::Module, "render") ->Attribute(AZ::Script::Attributes::Module, "render")
->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments) ->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments)
->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId) ->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId)
->Event("GetDefaultMaterialAssetId", &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId)
->Event("GetMaterialSlotLabel", &MaterialComponentRequestBus::Events::GetMaterialSlotLabel)
->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides) ->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides)
->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides) ->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides)
->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides) ->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides)
@@ -71,6 +73,7 @@ namespace AZ
->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride) ->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride)
->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides) ->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides)
->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides) ->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides)
->Event("SetPropertyOverrides", &MaterialComponentRequestBus::Events::SetPropertyOverrides)
->Event("GetPropertyOverrides", &MaterialComponentRequestBus::Events::GetPropertyOverrides) ->Event("GetPropertyOverrides", &MaterialComponentRequestBus::Events::GetPropertyOverrides)
; ;
} }
@@ -168,19 +171,26 @@ namespace AZ
const auto& propertyOverrides2 = materialIt->second.m_propertyOverrides; const auto& propertyOverrides2 = materialIt->second.m_propertyOverrides;
for (auto& propertyPair : propertyOverrides2) for (auto& propertyPair : propertyOverrides2)
{ {
const auto& materialPropertyIndex = materialInstance->FindPropertyIndex(propertyPair.first); if (propertyPair.second.empty())
if (!materialPropertyIndex.IsNull())
{ {
if (propertyPair.second.is<Data::AssetId>()) continue;
{ }
const auto& assetId = *AZStd::any_cast<Data::AssetId>(&propertyPair.second);
Data::Asset<RPI::ImageAsset> imageAsset(assetId, azrtti_typeid<RPI::StreamingImageAsset>()); const auto& materialPropertyIndex = materialInstance->FindPropertyIndex(propertyPair.first);
materialInstance->SetPropertyValue(materialPropertyIndex, AZ::RPI::MaterialPropertyValue(imageAsset)); if (materialPropertyIndex.IsNull())
} {
else continue;
{ }
materialInstance->SetPropertyValue(materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second));
} if (propertyPair.second.is<Data::AssetId>())
{
const auto& assetId = *AZStd::any_cast<Data::AssetId>(&propertyPair.second);
Data::Asset<RPI::ImageAsset> imageAsset(assetId, azrtti_typeid<RPI::StreamingImageAsset>());
materialInstance->SetPropertyValue(materialPropertyIndex, AZ::RPI::MaterialPropertyValue(imageAsset));
}
else
{
materialInstance->SetPropertyValue(materialPropertyIndex, AZ::RPI::MaterialPropertyValue::FromAny(propertyPair.second));
} }
} }
@@ -288,6 +298,40 @@ namespace AZ
return materialAssignmentId; return materialAssignmentId;
} }
AZ::Data::AssetId MaterialComponentController::GetDefaultMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const
{
RPI::ModelMaterialSlotMap modelMaterialSlots;
MaterialReceiverRequestBus::EventResult(
modelMaterialSlots, m_entityId, &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
auto slotIter = modelMaterialSlots.find(materialAssignmentId.m_materialSlotStableId);
return slotIter != modelMaterialSlots.end() ? slotIter->second.m_defaultMaterialAsset.GetId() : AZ::Data::AssetId();
}
AZStd::string MaterialComponentController::GetMaterialSlotLabel(const MaterialAssignmentId& materialAssignmentId) const
{
if (materialAssignmentId == DefaultMaterialAssignmentId)
{
return "Default Material";
}
RPI::ModelMaterialSlotMap modelMaterialSlots;
MaterialReceiverRequestBus::EventResult(
modelMaterialSlots, m_entityId, &MaterialReceiverRequestBus::Events::GetModelMaterialSlots);
auto slotIter = modelMaterialSlots.find(materialAssignmentId.m_materialSlotStableId);
if (slotIter != modelMaterialSlots.end())
{
const Name& displayName = slotIter->second.m_displayName;
if (!displayName.IsEmpty())
{
return displayName.GetStringView();
}
}
return "<unknown>";
}
void MaterialComponentController::SetMaterialOverrides(const MaterialAssignmentMap& materials) void MaterialComponentController::SetMaterialOverrides(const MaterialAssignmentMap& materials)
{ {
// this function is called twice once material asset is changed, a temp variable is // this function is called twice once material asset is changed, a temp variable is
@@ -309,7 +353,6 @@ namespace AZ
{ {
m_configuration.m_materials.clear(); m_configuration.m_materials.clear();
QueueMaterialUpdateNotification(); QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
} }
@@ -334,12 +377,11 @@ namespace AZ
LoadMaterials(); LoadMaterials();
} }
const AZ::Data::AssetId MaterialComponentController::GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const AZ::Data::AssetId MaterialComponentController::GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const
{ {
auto materialIt = m_configuration.m_materials.find(materialAssignmentId); auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end()) if (materialIt == m_configuration.m_materials.end())
{ {
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {}; return {};
} }
@@ -351,7 +393,6 @@ namespace AZ
if (m_configuration.m_materials.erase(materialAssignmentId) > 0) if (m_configuration.m_materials.erase(materialAssignmentId) > 0)
{ {
QueueMaterialUpdateNotification(); QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
} }
@@ -372,7 +413,6 @@ namespace AZ
} }
QueuePropertyChanges(materialAssignmentId); QueuePropertyChanges(materialAssignmentId);
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
void MaterialComponentController::SetPropertyOverrideBool( void MaterialComponentController::SetPropertyOverrideBool(
@@ -450,14 +490,12 @@ namespace AZ
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId); const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end()) if (materialIt == m_configuration.m_materials.end())
{ {
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {}; return {};
} }
const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName)); const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
if (propertyIt == materialIt->second.m_propertyOverrides.end()) if (propertyIt == materialIt->second.m_propertyOverrides.end())
{ {
AZ_Error("MaterialComponentController", false, "Property not found: %s.", propertyName.c_str());
return {}; return {};
} }
@@ -546,14 +584,12 @@ namespace AZ
auto materialIt = m_configuration.m_materials.find(materialAssignmentId); auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end()) if (materialIt == m_configuration.m_materials.end())
{ {
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return; return;
} }
auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName)); auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
if (propertyIt == materialIt->second.m_propertyOverrides.end()) if (propertyIt == materialIt->second.m_propertyOverrides.end())
{ {
AZ_Error("MaterialComponentController", false, "Property not found: %s.", propertyName.c_str());
return; return;
} }
@@ -565,7 +601,6 @@ namespace AZ
} }
QueuePropertyChanges(materialAssignmentId); QueuePropertyChanges(materialAssignmentId);
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
void MaterialComponentController::ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) void MaterialComponentController::ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId)
@@ -573,7 +608,6 @@ namespace AZ
auto materialIt = m_configuration.m_materials.find(materialAssignmentId); auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end()) if (materialIt == m_configuration.m_materials.end())
{ {
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return; return;
} }
@@ -582,7 +616,6 @@ namespace AZ
materialIt->second.m_propertyOverrides = {}; materialIt->second.m_propertyOverrides = {};
materialIt->second.RebuildInstance(); materialIt->second.RebuildInstance();
QueueMaterialUpdateNotification(); QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
} }
@@ -602,21 +635,62 @@ namespace AZ
if (cleared) if (cleared)
{ {
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
} }
} }
void MaterialComponentController::SetPropertyOverrides(
const MaterialAssignmentId& materialAssignmentId, const MaterialPropertyOverrideMap& propertyOverrides)
{
auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
const bool wasEmpty = materialAssignment.m_propertyOverrides.empty();
materialAssignment.m_propertyOverrides = propertyOverrides;
if (wasEmpty != materialAssignment.m_propertyOverrides.empty())
{
materialAssignment.RebuildInstance();
QueueMaterialUpdateNotification();
}
QueuePropertyChanges(materialAssignmentId);
}
MaterialPropertyOverrideMap MaterialComponentController::GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const MaterialPropertyOverrideMap MaterialComponentController::GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const
{ {
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId); const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end()) if (materialIt == m_configuration.m_materials.end())
{ {
AZ_Warning("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {}; return {};
} }
return materialIt->second.m_propertyOverrides; return materialIt->second.m_propertyOverrides;
} }
void MaterialComponentController::SetModelUvOverrides(
const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides)
{
auto& materialAssignment = m_configuration.m_materials[materialAssignmentId];
const bool wasEmpty = materialAssignment.m_matModUvOverrides.empty();
materialAssignment.m_matModUvOverrides = modelUvOverrides;
if (wasEmpty != materialAssignment.m_matModUvOverrides.empty())
{
materialAssignment.RebuildInstance();
QueueMaterialUpdateNotification();
}
QueuePropertyChanges(materialAssignmentId);
}
AZ::RPI::MaterialModelUvOverrideMap MaterialComponentController::GetModelUvOverrides(
const MaterialAssignmentId& materialAssignmentId) const
{
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
return {};
}
return materialIt->second.m_matModUvOverrides;
}
void MaterialComponentController::QueuePropertyChanges(const MaterialAssignmentId& materialAssignmentId) void MaterialComponentController::QueuePropertyChanges(const MaterialAssignmentId& materialAssignmentId)
{ {
m_queuedPropertyOverrides.emplace(materialAssignmentId); m_queuedPropertyOverrides.emplace(materialAssignmentId);
@@ -47,6 +47,8 @@ namespace AZ
//! MaterialComponentRequestBus overrides... //! MaterialComponentRequestBus overrides...
MaterialAssignmentMap GetOriginalMaterialAssignments() const override; MaterialAssignmentMap GetOriginalMaterialAssignments() const override;
MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override; MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
AZ::Data::AssetId GetDefaultMaterialAssetId(const MaterialAssignmentId& materialAssignmentId) const override;
AZStd::string GetMaterialSlotLabel(const MaterialAssignmentId& materialAssignmentId) const override;
void SetMaterialOverrides(const MaterialAssignmentMap& materials) override; void SetMaterialOverrides(const MaterialAssignmentMap& materials) override;
const MaterialAssignmentMap& GetMaterialOverrides() const override; const MaterialAssignmentMap& GetMaterialOverrides() const override;
void ClearAllMaterialOverrides() override; void ClearAllMaterialOverrides() override;
@@ -54,7 +56,7 @@ namespace AZ
const AZ::Data::AssetId GetDefaultMaterialOverride() const override; const AZ::Data::AssetId GetDefaultMaterialOverride() const override;
void ClearDefaultMaterialOverride() override; void ClearDefaultMaterialOverride() override;
void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) override; void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) override;
const AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const override; AZ::Data::AssetId GetMaterialOverride(const MaterialAssignmentId& materialAssignmentId) const override;
void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) override; void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) override;
void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) override; void SetPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName, const AZStd::any& value) override;
@@ -86,7 +88,12 @@ namespace AZ
void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) override; void ClearPropertyOverride(const MaterialAssignmentId& materialAssignmentId, const AZStd::string& propertyName) override;
void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override; void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override;
void ClearAllPropertyOverrides() override; void ClearAllPropertyOverrides() override;
void SetPropertyOverrides(
const MaterialAssignmentId& materialAssignmentId, const MaterialPropertyOverrideMap& propertyOverrides) override;
MaterialPropertyOverrideMap GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const override; MaterialPropertyOverrideMap GetPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) const override;
void SetModelUvOverrides(
const MaterialAssignmentId& materialAssignmentId, const AZ::RPI::MaterialModelUvOverrideMap& modelUvOverrides) override;
AZ::RPI::MaterialModelUvOverrideMap GetModelUvOverrides(const MaterialAssignmentId& materialAssignmentId) const override;
private: private: