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);
}
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>>())
{
result.m_value = Data::Asset<RPI::ImageAsset>(
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(),
azrtti_typeid<RPI::StreamingImageAsset>());
AZStd::any_cast<Data::Asset<StreamingImageAsset>>(value).GetId(), azrtti_typeid<RPI::StreamingImageAsset>());
}
else if (value.is<Data::Asset<ImageAsset>>())
{
@@ -129,7 +138,8 @@ namespace AZ
}
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());
}
@@ -187,5 +197,5 @@ namespace AZ
return result;
}
}
}
} // namespace RPI
} // namespace AZ
@@ -24,6 +24,12 @@ namespace AtomToolsFramework
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
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
virtual void Reset() = 0;
@@ -41,6 +41,10 @@ namespace AtomToolsFramework
~InspectorWidget() override;
// InspectorRequestBus::Handler overrides...
void AddHeading(QWidget* headingWidget) override;
void ClearHeading() override;
void Reset() override;
void AddGroupsBegin() override;
@@ -77,7 +81,6 @@ namespace AtomToolsFramework
virtual void OnHeaderClicked(const AZStd::string& groupNameId, QMouseEvent* event);
private:
QVBoxLayout* m_layout = nullptr;
QScopedPointer<Ui::InspectorWidget> m_ui;
struct GroupWidgetPair
@@ -38,7 +38,7 @@ namespace AtomToolsFramework
m_propertyEditor->Setup(context, instanceNotificationHandler, false);
m_propertyEditor->AddInstance(instance, instanceClassId, nullptr, instanceToCompare);
m_propertyEditor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_propertyEditor->QueueInvalidation(AzToolsFramework::PropertyModificationRefreshLevel::Refresh_EntireTree);
m_propertyEditor->InvalidateAll();
m_layout->addWidget(m_propertyEditor);
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()
{
qDeleteAll(m_ui->m_propertyContent->children());
m_layout = new QVBoxLayout(m_ui->m_propertyContent);
m_layout->setContentsMargins(0, 0, 0, 0);
m_layout->setSpacing(0);
qDeleteAll(m_ui->m_groupContents->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly));
qDeleteAll(m_ui->m_groupContentsLayout->children());
m_groups.clear();
}
void InspectorWidget::AddGroupsBegin()
{
setUpdatesEnabled(false);
setVisible(false);
Reset();
}
void InspectorWidget::AddGroupsEnd()
{
m_layout->addStretch();
m_ui->m_groupContentsLayout->addStretch();
// 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(
@@ -61,14 +71,14 @@ namespace AtomToolsFramework
const AZStd::string& groupDescription,
QWidget* groupWidget)
{
InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_propertyContent);
InspectorGroupHeaderWidget* groupHeader = new InspectorGroupHeaderWidget(m_ui->m_groupContents);
groupHeader->setText(groupDisplayName.c_str());
groupHeader->setToolTip(groupDescription.c_str());
m_layout->addWidget(groupHeader);
m_ui->m_groupContentsLayout->addWidget(groupHeader);
groupWidget->setObjectName(groupNameId.c_str());
groupWidget->setParent(m_ui->m_propertyContent);
m_layout->addWidget(groupWidget);
groupWidget->setParent(m_ui->m_groupContents);
m_ui->m_groupContentsLayout->addWidget(groupWidget);
m_groups[groupNameId] = {groupHeader, groupWidget};
@@ -101,28 +111,18 @@ namespace AtomToolsFramework
bool InspectorWidget::IsGroupVisible(const AZStd::string& groupNameId) const
{
auto groupItr = m_groups.find(groupNameId);
if (groupItr != m_groups.end())
{
return groupItr->second.m_header->isVisible();
}
return false;
return groupItr != m_groups.end() ? groupItr->second.m_header->isVisible() : false;
}
bool InspectorWidget::IsGroupHidden(const AZStd::string& groupNameId) const
{
auto groupItr = m_groups.find(groupNameId);
if (groupItr != m_groups.end())
{
return groupItr->second.m_header->isHidden();
}
return false;
return groupItr != m_groups.end() ? groupItr->second.m_header->isHidden() : false;
}
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();
}
@@ -130,7 +130,7 @@ namespace AtomToolsFramework
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();
}
@@ -138,7 +138,7 @@ namespace AtomToolsFramework
void InspectorWidget::RefreshAll()
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>())
for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>())
{
groupWidget->Refresh();
}
@@ -146,7 +146,7 @@ namespace AtomToolsFramework
void InspectorWidget::RebuildAll()
{
for (auto groupWidget : m_ui->m_propertyContent->findChildren<InspectorGroupWidget*>())
for (auto groupWidget : m_ui->m_groupContents->findChildren<InspectorGroupWidget*>())
{
groupWidget->Rebuild();
}
@@ -6,20 +6,14 @@
<rect>
<x>0</x>
<y>0</y>
<width>693</width>
<height>798</height>
<width>685</width>
<height>775</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Inspector</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QVBoxLayout" name="m_inspectorLayout">
<property name="spacing">
<number>0</number>
</property>
@@ -36,50 +30,103 @@
<number>0</number>
</property>
<item>
<widget class="QScrollArea" name="m_propertyScrollArea">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAsNeeded</enum>
</property>
<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>
<widget class="QWidget" name="m_sections">
<layout class="QVBoxLayout" name="m_sectionsLayout">
<property name="spacing">
<number>0</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<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="QFrame" name="m_propertyContent">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<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="QWidget" name="m_headingSection" native="true">
<layout class="QVBoxLayout" name="m_headingSectionLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
<property name="leftMargin">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
<property name="topMargin">
<number>0</number>
</property>
<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>
</item>
</layout>
@@ -7,6 +7,8 @@
*/
#pragma once
#include <Atom/Feature/Material/MaterialAssignmentId.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/std/string/string.h>
@@ -23,8 +25,12 @@ namespace AZ
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Open document in material editor
virtual void OpenInMaterialEditor(const AZStd::string& sourcePath) = 0;
//! Open source material in material editor
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>;
} // namespace Render
@@ -23,6 +23,10 @@ namespace AZ
virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0;
//! Get material assignment id matching lod and label substring
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
virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0;
//! Get material overrides
@@ -38,7 +42,7 @@ namespace AZ
//! Set material override
virtual void SetMaterialOverride(const MaterialAssignmentId& materialAssignmentId, const AZ::Data::AssetId& materialAssetId) = 0;
//! 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
virtual void ClearMaterialOverride(const MaterialAssignmentId& materialAssignmentId) = 0;
//! Set a material property override value wrapped by an AZStd::any
@@ -95,8 +99,16 @@ namespace AZ
virtual void ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) = 0;
//! Clear all property overrides
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
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>;
@@ -106,7 +118,7 @@ namespace AZ
{
public:
virtual void OnMaterialsUpdated([[maybe_unused]] const MaterialAssignmentMap& materials) {}
virtual void OnMaterialsEdited([[maybe_unused]] const MaterialAssignmentMap& materials) {}
virtual void OnMaterialsEdited() {}
};
using MaterialComponentNotificationBus = EBus<MaterialComponentNotifications>;
@@ -215,91 +215,21 @@ namespace AZ
void EditorMaterialComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId)
{
m_controller.SetDefaultMaterialOverride(assetId);
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
}
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;
}
void EditorMaterialComponent::OnMaterialAssignmentsChanged()
{
// [GFX TODO][ATOM-4604] remove flag after mesh component material handling is fixed to not recreate/reload mesh for material changes
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);
UpdateMaterialSlots();
}
void EditorMaterialComponent::UpdateMaterialSlots()
@@ -315,64 +245,18 @@ namespace AZ
MaterialAssignmentMap materialsFromSource;
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
for (const auto& materialPair : materialsFromSource)
{
// Setup the material slot entry
EditorMaterialComponentSlot slot;
slot.m_entityId = GetEntityId();
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
const MaterialAssignment& materialFromController = GetMaterialAssignmentFromMap(config.m_materials, slot.m_id);
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())
{
m_defaultMaterialSlot = slot;
@@ -388,7 +272,8 @@ namespace AZ
if (slot.m_id.IsLodAndSlotId())
{
// 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);
continue;
}
@@ -404,9 +289,10 @@ namespace AZ
[](const auto& a, const auto& b) { return a.GetLabel() < b.GetLabel(); });
}
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_EntireTree);
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
}
AZ::u32 EditorMaterialComponent::ResetMaterialSlots()
@@ -414,15 +300,15 @@ namespace AZ
AzToolsFramework::ScopedUndoBatch undoBatch("Resetting materials.");
SetDirty();
UpdateConfiguration(MaterialComponentConfig());
m_controller.SetMaterialOverrides(MaterialAssignmentMap());
UpdateMaterialSlots();
m_materialSlotsByLodEnabled = false;
// Forcing refresh in case triggered from context menu action
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_EntireTree);
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
@@ -431,26 +317,26 @@ namespace AZ
{
AzToolsFramework::ScopedUndoBatch undoBatch("Generating materials.");
SetDirty();
// 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;
auto materialSlots = GetMaterialSlots();
for (auto& materialSlotPair : materialSlots)
{
Data::AssetId defaultMaterialAssetId = materialSlotPair.second->m_defaultMaterialAsset.GetId();
Data::AssetId defaultMaterialAssetId = materialSlotPair.second->GetDefaultAssetId();
if (defaultMaterialAssetId.IsValid())
{
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
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
for (auto assetIdInfo : assetIdMap)
{
EditorMaterialComponentExporter::ExportItem exportItem{assetIdInfo.first, assetIdInfo.second};
EditorMaterialComponentExporter::ExportItem exportItem{ assetIdInfo.first, assetIdInfo.second };
exportItems.push_back(exportItem);
}
@@ -474,9 +360,9 @@ namespace AZ
if (editorMaterialSlot)
{
// 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
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_AttributesAndValues);
MaterialComponentNotificationBus::Event(GetEntityId(), &MaterialComponentNotifications::OnMaterialsEdited);
return OnConfigurationChanged();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
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;
}
@@ -541,11 +441,14 @@ namespace AZ
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 AZ
@@ -8,11 +8,11 @@
#pragma once
#include <Atom/Feature/Utils/EditorRenderComponentAdapter.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentConstants.h>
#include <Atom/Feature/Utils/EditorRenderComponentAdapter.h>
#include <Material/MaterialComponent.h>
#include <Material/EditorMaterialComponentSlot.h>
#include <Material/MaterialComponent.h>
namespace AZ
{
@@ -49,16 +49,6 @@ namespace AZ
//! MaterialReceiverNotificationBus::Handler overrides...
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
// LOD mapping from the model or other consumer of materials.
// If any corresponding material assignments are found in the component
@@ -101,8 +91,6 @@ namespace AZ
EditorMaterialComponentSlotsByLodContainer m_materialSlotsByLod;
bool m_materialSlotsByLodEnabled = false;
bool m_configurationChangeInProgress = false; // when true, model changes are ignored
static const char* GenerateMaterialsButtonText;
static const char* GenerateMaterialsToolTipText;
static const char* ResetMaterialsButtonText;
@@ -27,19 +27,16 @@
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/ThumbnailWidget.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.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
#include <QApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFileInfo>
#include <QHBoxLayout>
#include <QLabel>
#include <QMenu>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
AZ_POP_DISABLE_WARNING
@@ -49,26 +46,59 @@ namespace AZ
{
namespace EditorMaterialComponentInspector
{
MaterialPropertyInspector::MaterialPropertyInspector(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent)
MaterialPropertyInspector::MaterialPropertyInspector(QWidget* 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()
{
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.");
UnloadMaterial();
return false;
}
@@ -77,6 +107,7 @@ namespace AZ
if (!m_materialInstance)
{
AZ_Error("AZ::Render::EditorMaterialComponentInspector", false, "Material instance could not be created.");
UnloadMaterial();
return false;
}
@@ -102,15 +133,36 @@ namespace AZ
}
}
Populate();
m_messageLabel->setVisible(false);
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()
{
m_activeProperty = {};
m_groups = {};
m_dirtyPropertyFlags.set();
m_internalEditNotification = {};
AZ::TickBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect();
AtomToolsFramework::InspectorWidget::Reset();
}
@@ -150,9 +202,18 @@ namespace AZ
QFileInfo materialTypeSourceFileInfo(m_editData.m_materialTypeSourcePath.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;
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())
{
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
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(
&group, &group, group.TYPEINFO_Uuid(), this, this, saveStateKey,
[](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);
});
&group, nullptr, group.TYPEINFO_Uuid(), this, this, GetSaveStateKeyForGroup(groupNameId));
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
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(
&group, &group, group.TYPEINFO_Uuid(), this, this, saveStateKey,
[](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);
});
&group, nullptr, group.TYPEINFO_Uuid(), this, this, GetSaveStateKeyForGroup(groupNameId));
AddGroup(groupNameId, groupDisplayName, groupDescription, propertyGroupWidget);
}
AddGroupsEnd();
m_dirtyPropertyFlags.set();
RunEditorMaterialFunctors();
LoadOverridesFromEntity();
}
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()
{
if (!IsLoaded())
{
return;
}
AZStd::unordered_set<AZ::Name> changedPropertyNames;
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
for (auto& groupPair : m_groups)
{
AZ::Name groupName{groupPair.first};
AZ::Name groupName{ groupPair.first };
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)
@@ -355,12 +465,12 @@ namespace AZ
if (oldReadOnly != propertyConfig.m_readOnly)
{
RefreshAll();
RefreshGroup(groupPair.first);
}
if (oldVisible != propertyConfig.m_visible)
{
RebuildAll();
RebuildGroup(groupPair.first);
}
}
}
@@ -368,57 +478,37 @@ namespace AZ
void MaterialPropertyInspector::UpdateMaterialInstanceProperty(const AtomToolsFramework::DynamicProperty& property)
{
if (m_materialInstance)
if (!IsLoaded())
{
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
if (!propertyIndex.IsNull())
{
m_dirtyPropertyFlags.set(propertyIndex.GetIndex());
return;
}
const auto runtimeValue = AtomToolsFramework::ConvertToRuntimeType(property.GetValue());
if (runtimeValue.IsValid())
{
m_materialInstance->SetPropertyValue(propertyIndex, runtimeValue);
}
const auto propertyIndex = m_materialInstance->FindPropertyIndex(property.GetId());
if (!propertyIndex.IsNull())
{
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)
{
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();
RunPropertyChangedCallback();
RunEditorMaterialFunctors();
RebuildAll();
bool MaterialPropertyInspector::AreNodePropertyValuesEqual(
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);
}
bool MaterialPropertyInspector::SaveMaterial() const
@@ -446,7 +536,8 @@ namespace AZ
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())
{
return false;
@@ -463,13 +554,13 @@ namespace AZ
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);
}
bool MaterialPropertyInspector::HasMaterialParentSource() const
{
return !m_editData.m_materialParentSourcePath.empty() &&
return IsLoaded() && !m_editData.m_materialParentSourcePath.empty() &&
AZ::StringFunc::Path::IsExtension(
m_editData.m_materialParentSourcePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
}
@@ -479,7 +570,7 @@ namespace AZ
if (HasMaterialSource())
{
EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenInMaterialEditor, m_editData.m_materialSourcePath);
&EditorMaterialSystemComponentRequestBus::Events::OpenMaterialEditor, m_editData.m_materialSourcePath);
}
}
@@ -488,10 +579,42 @@ namespace AZ
if (HasMaterialParentSource())
{
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
{
return m_editData;
@@ -501,7 +624,8 @@ namespace AZ
{
// For some reason the reflected property editor notifications are not symmetrical
// This function is called continuously anytime a property changes until the edit has completed
// Because of that, we have to track whether or not we are continuing to edit the same property to know when editing has started and ended
// 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);
if (property)
{
@@ -521,15 +645,16 @@ namespace AZ
{
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback();
SaveOverridesToEntity(false);
}
}
}
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.
// when this function executes the changes to the property are ready to be committed or reverted
// As above, there are symmetrical functions on the notification interface for when editing begins and ends and has been
// completed but they are not being called following that pattern. when this function executes the changes to the property
// are ready to be committed or reverted
const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode);
if (property)
{
@@ -537,85 +662,92 @@ namespace AZ
{
m_editData.m_materialPropertyOverrideMap[m_activeProperty->GetId()] = m_activeProperty->GetValue();
UpdateMaterialInstanceProperty(*m_activeProperty);
RunPropertyChangedCallback();
SaveOverridesToEntity(true);
RunEditorMaterialFunctors();
m_activeProperty = nullptr;
}
}
}
bool OpenInspectorDialog(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback)
void MaterialPropertyInspector::OnEntityInitialized(const AZ::EntityId& entityId)
{
QWidget* activeWindow = nullptr;
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())
if (m_entityId == entityId)
{
return false;
UnloadMaterial();
}
}
inspector->Populate();
inspector->SetOverrides(propertyOverrideMap);
void MaterialPropertyInspector::OnEntityDestroyed(const AZ::EntityId& entityId)
{
if (m_entityId == entityId)
{
UnloadMaterial();
}
}
// Create the menu button
QToolButton* menuButton = new QToolButton(&dialog);
menuButton->setAutoRaise(true);
menuButton->setIcon(QIcon(":/Cards/img/UI20/Cards/menu_ico.svg"));
menuButton->setVisible(true);
QObject::connect(menuButton, &QToolButton::clicked, &dialog, [&]() {
QAction* action = nullptr;
void MaterialPropertyInspector::OnEntityActivated(const AZ::EntityId& entityId)
{
if (m_entityId == entityId)
{
QueueUpdateUI();
}
}
QMenu menu(&dialog);
action = menu.addAction("Clear Overrides", [&] { inspector->SetOverrides(MaterialPropertyOverrideMap()); });
action = menu.addAction("Revert Changes", [&] { inspector->SetOverrides(propertyOverrideMap); });
void MaterialPropertyInspector::OnEntityDeactivated(const AZ::EntityId& entityId)
{
if (m_entityId == entityId)
{
UnloadMaterial();
}
}
menu.addSeparator();
action = menu.addAction("Save Material", [&] { inspector->SaveMaterial(); });
action = menu.addAction("Save Material To Source", [&] { inspector->SaveMaterialToSource(); });
action->setEnabled(inspector->HasMaterialSource());
void MaterialPropertyInspector::OnEntityNameChanged(const AZ::EntityId& entityId, const AZStd::string& name)
{
AZ_UNUSED(name);
if (m_entityId == entityId)
{
QueueUpdateUI();
}
}
menu.addSeparator();
action = menu.addAction("Open Source Material In Editor", [&] { inspector->OpenMaterialSourceInEditor(); });
action->setEnabled(inspector->HasMaterialSource());
action = menu.addAction("Open Parent Material In Editor", [&] { inspector->OpenMaterialParentSourceInEditor(); });
action->setEnabled(inspector->HasMaterialParentSource());
menu.exec(QCursor::pos());
});
void MaterialPropertyInspector::OnTick(float deltaTime, ScriptTimePoint time)
{
AZ_UNUSED(time);
AZ_UNUSED(deltaTime);
UpdateUI();
AZ::TickBus::Handler::BusDisconnect();
}
QDialogButtonBox* buttonBox = new QDialogButtonBox(&dialog);
buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
void MaterialPropertyInspector::OnMaterialsEdited()
{
if (!m_internalEditNotification)
{
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);
dialogLayout->addWidget(menuButton);
dialogLayout->addWidget(inspector);
dialogLayout->addWidget(buttonBox);
dialog.setLayout(dialogLayout);
dialog.setModal(true);
if (IsLoaded() && m_editData.m_materialAssetId == assetId)
{
LoadOverridesFromEntity();
}
else
{
LoadMaterial(m_entityId, m_materialAssignmentId);
}
}
// Forcing the initial dialog size to accomodate typical content.
// 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.
// Resizing the dialog after show will not be centered and moving the dialog programatically doesn't m0ve the custmk frame.
dialog.setFixedSize(500, 800);
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;
void MaterialPropertyInspector::QueueUpdateUI()
{
if (!AZ::TickBus::Handler::BusIsConnected())
{
AZ::TickBus::Handler::BusConnect();
}
}
} // namespace EditorMaterialComponentInspector
} // namespace Render
@@ -9,17 +9,22 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h>
#include <AtomToolsFramework/Inspector/InspectorWidget.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/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/function/function_base.h>
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <Material/EditorMaterialComponentUtil.h>
#endif
class QLabel;
namespace AZ
{
namespace Render
@@ -31,31 +36,33 @@ namespace AZ
class MaterialPropertyInspector
: public AtomToolsFramework::InspectorWidget
, public AzToolsFramework::IPropertyEditorNotify
{
, public AZ::EntitySystemBus::Handler
, public AZ::TickBus::Handler
, public MaterialComponentNotificationBus::Handler
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(MaterialPropertyInspector, AZ::SystemAllocator, 0);
explicit MaterialPropertyInspector(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, PropertyChangedCallback propertyChangedCallback,
QWidget* parent = nullptr);
MaterialPropertyInspector(QWidget* parent = nullptr);
~MaterialPropertyInspector() override;
bool LoadMaterial();
bool LoadMaterial(const AZ::EntityId& entityId, const AZ::Render::MaterialAssignmentId& materialAssignmentId);
void UnloadMaterial();
bool IsLoaded() const;
// AtomToolsFramework::InspectorRequestBus::Handler overrides...
void Reset() override;
void Populate();
void SetOverrides(const MaterialPropertyOverrideMap& propertyOverrideMap);
bool SaveMaterial() const;
bool SaveMaterialToSource() const;
bool HasMaterialSource() const;
bool HasMaterialParentSource() const;
void OpenMaterialSourceInEditor() const;
void OpenMaterialParentSourceInEditor() const;
void OpenMenu();
const EditorMaterialComponentUtil::MaterialEditData& GetEditData() const;
private:
@@ -69,28 +76,47 @@ namespace AZ
void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode*, const QPoint&) 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 AddUvNamesGroup();
void RunPropertyChangedCallback();
void LoadOverridesFromEntity();
void SaveOverridesToEntity(bool commitChanges);
void RunEditorMaterialFunctors();
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
const AtomToolsFramework::DynamicProperty* m_activeProperty = {};
AZStd::string m_slotName;
AZ::Data::AssetId m_materialAssetId = {};
AZ::EntityId m_entityId;
AZ::Render::MaterialAssignmentId m_materialAssignmentId;
EditorMaterialComponentUtil::MaterialEditData m_editData;
PropertyChangedCallback m_propertyChangedCallback = {};
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance = {};
AZStd::vector<AZ::RPI::Ptr<AZ::RPI::MaterialFunctor>> m_editorFunctors = {};
AZ::RPI::MaterialPropertyFlags m_dirtyPropertyFlags = {};
AZStd::unordered_map<AZStd::string, AtomToolsFramework::DynamicPropertyGroup> m_groups = {};
};
bool OpenInspectorDialog(
const AZStd::string& slotName, const AZ::Data::AssetId& assetId, MaterialPropertyOverrideMap propertyOverrideMap,
PropertyChangedCallback propertyChangedCallback);
bool m_internalEditNotification = {};
QLabel* m_messageLabel = {};
};
} // namespace EditorMaterialComponentInspector
} // namespace Render
} // namespace AZ
@@ -80,10 +80,9 @@ namespace AZ
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorMaterialComponentSlot>()
->Version(6, &EditorMaterialComponentSlot::ConvertVersion)
->Version(7, &EditorMaterialComponentSlot::ConvertVersion)
->Field("id", &EditorMaterialComponentSlot::m_id)
->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset)
->Field("defaultMaterialAsset", &EditorMaterialComponentSlot::m_defaultMaterialAsset)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
@@ -114,20 +113,22 @@ namespace AZ
->Constructor<const EditorMaterialComponentSlot&>()
->Property("id", BehaviorValueProperty(&EditorMaterialComponentSlot::m_id))
->Property("materialAsset", BehaviorValueProperty(&EditorMaterialComponentSlot::m_materialAsset))
->Property("propertyOverrides", BehaviorValueProperty(&EditorMaterialComponentSlot::m_propertyOverrides))
->Property("matModUvOverrides", BehaviorValueProperty(&EditorMaterialComponentSlot::m_matModUvOverrides))
;
}
};
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
{
return m_label;
AZStd::string label;
MaterialComponentRequestBus::EventResult(label, m_entityId, &MaterialComponentRequestBus::Events::GetMaterialSlotLabel, m_id);
return label;
}
bool EditorMaterialComponentSlot::HasSourceData() const
@@ -137,50 +138,45 @@ namespace AZ
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_materialChangedCallback();
}
m_materialAsset.Create(assetId);
MaterialComponentRequestBus::Event(
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_propertyChangedCallback();
}
}
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);
}
m_materialAsset = asset;
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetMaterialOverride, m_id, m_materialAsset.GetId());
OnDataChanged();
}
void EditorMaterialComponentSlot::Clear()
{
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();
}
void EditorMaterialComponentSlot::ClearOverrides()
{
m_propertyOverrides = {};
m_matModUvOverrides = {};
OnMaterialChanged();
}
void EditorMaterialComponentSlot::ResetToDefaultAsset()
{
m_materialAsset = m_defaultMaterialAsset;
m_propertyOverrides = {};
m_matModUvOverrides = {};
OnMaterialChanged();
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetPropertyOverrides, m_id, MaterialPropertyOverrideMap());
MaterialComponentRequestBus::Event(
m_entityId, &MaterialComponentRequestBus::Events::SetModelUvOverrides, m_id, AZ::RPI::MaterialModelUvOverrideMap());
OnDataChanged();
}
void EditorMaterialComponentSlot::OpenMaterialExporter()
@@ -189,7 +185,7 @@ namespace AZ
// But we still need to allow the user to reconfigure it using the dialog
EditorMaterialComponentExporter::ExportItemsContainer exportItems;
{
EditorMaterialComponentExporter::ExportItem exportItem{m_defaultMaterialAsset.GetId(), m_label};
EditorMaterialComponentExporter::ExportItem exportItem{ GetDefaultAssetId(), GetLabel() };
exportItems.push_back(exportItem);
}
@@ -203,7 +199,8 @@ namespace AZ
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);
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()
{
MaterialPropertyOverrideMap initialPropertyOverrides = m_propertyOverrides;
auto applyPropertyChangedCallback = [this](const MaterialPropertyOverrideMap& propertyOverrides) {
m_propertyOverrides = propertyOverrides;
OnPropertyChanged();
};
if (m_materialAsset.GetId().IsValid())
{
if (EditorMaterialComponentInspector::OpenInspectorDialog(GetLabel(), m_materialAsset.GetId(), m_propertyOverrides, applyPropertyChangedCallback))
{
OnMaterialChanged();
}
}
EditorMaterialSystemComponentRequestBus::Broadcast(
&EditorMaterialSystemComponentRequestBus::Events::OpenMaterialInspector, m_entityId, m_id);
}
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 (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;
action = menu.addAction("Generate/Manage Source Material...", [this]() { OpenMaterialExporter(); });
action->setEnabled(m_defaultMaterialAsset.GetId().IsValid());
action->setEnabled(GetDefaultAssetId().IsValid());
menu.addSeparator();
@@ -276,10 +279,38 @@ namespace AZ
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->setEnabled(!m_propertyOverrides.empty() || !m_matModUvOverrides.empty());
action->setEnabled(!propertyOverrides.empty() || !matModUvOverrides.empty());
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 AZ
@@ -33,29 +33,26 @@ namespace AZ
AZ::Data::AssetId GetDefaultAssetId() const;
AZStd::string GetLabel() 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 ClearToDefaultAsset();
void ClearOverrides();
void OpenMaterialExporter();
void OpenMaterialEditor() const;
void OpenMaterialInspector();
void OpenUvNameMapInspector();
AZ::EntityId m_entityId;
MaterialAssignmentId m_id;
AZStd::string m_label;
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:
void OpenPopupMenu(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType& assetType);
void OnMaterialChanged() const;
void OnPropertyChanged() const;
void OnDataChanged() const;
};
// Vector of slots for assignable or overridable material data.
@@ -6,33 +6,31 @@
*
*/
#include <Material/EditorMaterialSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Atom/RHI/Factory.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Application/Application.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <Atom/RHI/Factory.h>
#include <AtomToolsFramework/Util/Util.h>
#include <Editor/LyViewPaneNames.h>
#include <Material/EditorMaterialComponentInspector.h>
#include <Material/EditorMaterialSystemComponent.h>
#include <Material/MaterialThumbnail.h>
// Disables warning messages triggered by the Qt library
// 4251: class needs to have dll-interface to be used by clients of class
// 4800: forcing value to bool 'true' or 'false' (performance warning)
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option")
#include <QApplication>
#include <QProcessEnvironment>
#include <QObject>
#include <QAction>
#include <QApplication>
#include <QDockWidget>
#include <QObject>
#include <QProcessEnvironment>
AZ_POP_DISABLE_WARNING
void InitMaterialEditorResources()
@@ -95,6 +93,7 @@ namespace AZ
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusConnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
SetupThumbnails();
m_materialBrowserInteractions.reset(aznew MaterialBrowserInteractions);
@@ -106,6 +105,7 @@ namespace AZ
AzFramework::ApplicationLifecycleEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorMenuNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
TeardownThumbnails();
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");
@@ -140,6 +140,20 @@ namespace AZ
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()
{
TeardownThumbnails();
@@ -157,11 +171,12 @@ namespace AZ
QObject::connect(
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()
{
using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider,
MAKE_TCACHE(Thumbnails::MaterialThumbnailCache),
ThumbnailerRequestsBus::Broadcast(
&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(Thumbnails::MaterialThumbnailCache),
ThumbnailContext::DefaultContext);
}
@@ -189,12 +216,13 @@ namespace AZ
using namespace AzToolsFramework::Thumbnailer;
using namespace LyIntegration;
ThumbnailerRequestsBus::Broadcast(&ThumbnailerRequests::UnregisterThumbnailProvider,
Thumbnails::MaterialThumbnailCache::ProviderName,
ThumbnailerRequestsBus::Broadcast(
&ThumbnailerRequests::UnregisterThumbnailProvider, Thumbnails::MaterialThumbnailCache::ProviderName,
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* MaterialTypeExtension = "materialtype";
@@ -8,11 +8,10 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzFramework/Application/Application.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/EditorMaterialSystemComponentRequestBus.h>
@@ -28,8 +27,9 @@ namespace AZ
: public AZ::Component
, private EditorMaterialSystemComponentRequestBus::Handler
, private AzFramework::ApplicationLifecycleEvents::Bus::Handler
, public AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
, public AzToolsFramework::EditorMenuNotificationBus::Handler
, private AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
, private AzToolsFramework::EditorMenuNotificationBus::Handler
, private AzToolsFramework::EditorEvents::Bus::Handler
{
public:
AZ_COMPONENT(EditorMaterialSystemComponent, "{96652157-DA0B-420F-B49C-0207C585144C}");
@@ -49,7 +49,8 @@ namespace AZ
private:
//! 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...
void OnApplicationAboutToStop() override;
@@ -61,6 +62,9 @@ namespace AZ
void OnPopulateToolMenuItems() override;
void OnResetToolMenuItems() override;
// AztoolsFramework::EditorEvents::Bus::Handler overrides...
void NotifyRegisterViews() override;
void SetupThumbnails();
void TeardownThumbnails();
@@ -29,16 +29,13 @@ namespace AZ
{
if (HandlesSource(fullSourceFileName))
{
openers.push_back(
{
"Material_Editor",
"Open in Material Editor...",
QIcon(),
openers.push_back({ "Material_Editor", "Open in Material Editor...", QIcon(),
[&](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")
->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments)
->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId)
->Event("GetDefaultMaterialAssetId", &MaterialComponentRequestBus::Events::GetDefaultMaterialAssetId)
->Event("GetMaterialSlotLabel", &MaterialComponentRequestBus::Events::GetMaterialSlotLabel)
->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides)
->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides)
->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides)
@@ -71,6 +73,7 @@ namespace AZ
->Event("ClearPropertyOverride", &MaterialComponentRequestBus::Events::ClearPropertyOverride)
->Event("ClearPropertyOverrides", &MaterialComponentRequestBus::Events::ClearPropertyOverrides)
->Event("ClearAllPropertyOverrides", &MaterialComponentRequestBus::Events::ClearAllPropertyOverrides)
->Event("SetPropertyOverrides", &MaterialComponentRequestBus::Events::SetPropertyOverrides)
->Event("GetPropertyOverrides", &MaterialComponentRequestBus::Events::GetPropertyOverrides)
;
}
@@ -168,19 +171,26 @@ namespace AZ
const auto& propertyOverrides2 = materialIt->second.m_propertyOverrides;
for (auto& propertyPair : propertyOverrides2)
{
const auto& materialPropertyIndex = materialInstance->FindPropertyIndex(propertyPair.first);
if (!materialPropertyIndex.IsNull())
if (propertyPair.second.empty())
{
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));
}
continue;
}
const auto& materialPropertyIndex = materialInstance->FindPropertyIndex(propertyPair.first);
if (materialPropertyIndex.IsNull())
{
continue;
}
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;
}
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)
{
// 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();
QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
}
}
@@ -334,12 +377,11 @@ namespace AZ
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);
if (materialIt == m_configuration.m_materials.end())
{
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {};
}
@@ -351,7 +393,6 @@ namespace AZ
if (m_configuration.m_materials.erase(materialAssignmentId) > 0)
{
QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
}
}
@@ -372,7 +413,6 @@ namespace AZ
}
QueuePropertyChanges(materialAssignmentId);
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
}
void MaterialComponentController::SetPropertyOverrideBool(
@@ -450,14 +490,12 @@ namespace AZ
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {};
}
const auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
if (propertyIt == materialIt->second.m_propertyOverrides.end())
{
AZ_Error("MaterialComponentController", false, "Property not found: %s.", propertyName.c_str());
return {};
}
@@ -546,14 +584,12 @@ namespace AZ
auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return;
}
auto propertyIt = materialIt->second.m_propertyOverrides.find(AZ::Name(propertyName));
if (propertyIt == materialIt->second.m_propertyOverrides.end())
{
AZ_Error("MaterialComponentController", false, "Property not found: %s.", propertyName.c_str());
return;
}
@@ -565,7 +601,6 @@ namespace AZ
}
QueuePropertyChanges(materialAssignmentId);
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
}
void MaterialComponentController::ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId)
@@ -573,7 +608,6 @@ namespace AZ
auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
AZ_Error("MaterialComponentController", false, "MaterialAssignmentId not found.");
return;
}
@@ -582,7 +616,6 @@ namespace AZ
materialIt->second.m_propertyOverrides = {};
materialIt->second.RebuildInstance();
QueueMaterialUpdateNotification();
MaterialComponentNotificationBus::Event(m_entityId, &MaterialComponentNotifications::OnMaterialsEdited, m_configuration.m_materials);
}
}
@@ -602,21 +635,62 @@ namespace AZ
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
{
const auto materialIt = m_configuration.m_materials.find(materialAssignmentId);
if (materialIt == m_configuration.m_materials.end())
{
AZ_Warning("MaterialComponentController", false, "MaterialAssignmentId not found.");
return {};
}
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)
{
m_queuedPropertyOverrides.emplace(materialAssignmentId);
@@ -47,6 +47,8 @@ namespace AZ
//! MaterialComponentRequestBus overrides...
MaterialAssignmentMap GetOriginalMaterialAssignments() 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;
const MaterialAssignmentMap& GetMaterialOverrides() const override;
void ClearAllMaterialOverrides() override;
@@ -54,7 +56,7 @@ namespace AZ
const AZ::Data::AssetId GetDefaultMaterialOverride() const override;
void ClearDefaultMaterialOverride() 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 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 ClearPropertyOverrides(const MaterialAssignmentId& materialAssignmentId) override;
void ClearAllPropertyOverrides() override;
void SetPropertyOverrides(
const MaterialAssignmentId& materialAssignmentId, const MaterialPropertyOverrideMap& propertyOverrides) 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: