From ed4f7965da6f9459be64e7cac8523041696dea0d Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Sun, 31 Oct 2021 22:49:14 -0500 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFCreated=20function=20to=20get=20relati?= =?UTF-8?q?ve=20paths=20to=20referenced=20files=20that=20will=20fall=20bac?= =?UTF-8?q?k=20to=20asset=20folder=20relative=20paths=20under=20certain=20?= =?UTF-8?q?conditions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guthrie Adams --- .../Util/MaterialPropertyUtil.h | 14 ++++- .../Code/Source/Util/MaterialPropertyUtil.cpp | 46 ++++++++++++++++- .../Code/Source/Document/MaterialDocument.cpp | 51 ++++++++----------- .../Code/Source/Document/MaterialDocument.h | 4 +- .../Material/EditorMaterialComponentUtil.cpp | 11 ++-- 5 files changed, 84 insertions(+), 42 deletions(-) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h index d888c76553..b191e01bce 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Util/MaterialPropertyUtil.h @@ -45,10 +45,22 @@ namespace AtomToolsFramework //! Convert the property value into the format that will be stored in the source data //! This is primarily needed to support conversions of special types like enums and images bool ConvertToExportFormat( - const AZ::IO::BasicPath& exportFolder, + const AZStd::string& exportPath, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, AZ::RPI::MaterialPropertyValue& propertyValue); + //! Generate a file path from the exported file to the external reference. + //! This function is to support copying or moving a folder containing materials, models, and textures without modifying the files. The + //! general case returns a relative path from the export file to the reference file. If the reference path is too different or distant + //! from the export path then it might be more difficult to work with than an asset folder relative path. For example, material types + //! that Atom provides live in a folder that should be accessible from anywhere. When materials are created in arbitrary gems and + //! project folders, a relative path to the material type would need to be updated whenever the materials are copied or moved. The same + //! thing will happen with parent materials or textures if their paths can’t be resolved. To alleviate some of this, we use the asset + //! folder relative path if the export folder relative path is too complex. An alternate solution would be to only use export folder + //! relative paths if the referenced path is in the same folder or a sub folder the assets are not generally packaged like that. + AZStd::string GetExteralReferencePath( + const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth = 2); + //! Traverse up the instance data node hierarchy to find the containing dynamic property object const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode); } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp index b142977049..6ad9506fdd 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Util/MaterialPropertyUtil.cpp @@ -166,10 +166,13 @@ namespace AtomToolsFramework } bool ConvertToExportFormat( - const AZ::IO::BasicPath& exportFolder, + const AZStd::string& exportPath, const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition, AZ::RPI::MaterialPropertyValue& propertyValue) { + AZ::IO::BasicPath exportFolder(exportPath); + exportFolder.RemoveFilename(); + if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is()) { const uint32_t index = propertyValue.GetValue(); @@ -206,6 +209,47 @@ namespace AtomToolsFramework return true; } + AZStd::string GetExteralReferencePath(const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth) + { + if (referencePath.empty()) + { + return {}; + } + + AZ::IO::BasicPath exportFolder(exportPath); + exportFolder.RemoveFilename(); + + const AZStd::string relativePath = AZ::IO::PathView(referencePath).LexicallyRelative(exportFolder).StringAsPosix(); + + // Count the difference in depth between the export file path and the referenced file path. + uint32_t parentFolderCount = 0; + AZStd::string::size_type pos = 0; + const AZStd::string parentFolderToken = ".."; + while ((pos = relativePath.find(parentFolderToken, pos)) != AZStd::string::npos) + { + parentFolderCount++; + pos += parentFolderToken.length(); + } + + // If the difference in depth is too great then revert to using the asset folder relative path. + // We could change this to only use relative paths for references in subfolders. + if (parentFolderCount > maxPathDepth) + { + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, referencePath.c_str(), + assetInfo, watchFolder); + if (sourceInfoFound) + { + return assetInfo.m_relativePath; + } + } + + return relativePath; + } + const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode) { // Traverse up the hierarchy from the input node to search for an instance corresponding to material inspector property diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 1d3ada6457..2a0dcb36c2 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -228,18 +228,15 @@ namespace MaterialEditor return false; } - AZ::IO::BasicPath exportFolder(m_absolutePath); - exportFolder.RemoveFilename(); - // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; - sourceData.m_materialType = AZ::IO::PathView(m_materialSourceData.m_materialType).LexicallyRelative(exportFolder).StringAsPosix(); - sourceData.m_parentMaterial = AZ::IO::PathView(m_materialSourceData.m_parentMaterial).LexicallyRelative(exportFolder).StringAsPosix(); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType); + sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial); // populate sourceData with modified or overwritten properties const bool savedProperties = SavePropertiesToSourceData( - exportFolder, sourceData, + m_absolutePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue); @@ -302,18 +299,16 @@ namespace MaterialEditor return false; } - AZ::IO::BasicPath exportFolder(normalizedSavePath); - exportFolder.RemoveFilename(); - // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; - sourceData.m_materialType = AZ::IO::PathView(m_materialSourceData.m_materialType).LexicallyRelative(exportFolder).StringAsPosix(); - sourceData.m_parentMaterial = AZ::IO::PathView(m_materialSourceData.m_parentMaterial).LexicallyRelative(exportFolder).StringAsPosix(); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); + sourceData.m_parentMaterial = + AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial); // populate sourceData with modified or overwritten properties const bool savedProperties = SavePropertiesToSourceData( - exportFolder, sourceData, + normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue); @@ -375,27 +370,21 @@ namespace MaterialEditor return false; } - AZ::IO::BasicPath exportFolder(normalizedSavePath); - exportFolder.RemoveFilename(); - // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; - sourceData.m_materialType = AZ::IO::PathView(m_materialSourceData.m_materialType).LexicallyRelative(exportFolder).StringAsPosix(); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); + sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); // Only assign a parent path if the source was a .material if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension)) { - sourceData.m_parentMaterial = AZ::IO::PathView(m_absolutePath).LexicallyRelative(exportFolder).StringAsPosix(); + sourceData.m_parentMaterial = + AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial); } - // Force save data to store forward slashes - AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); - AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/"); - // populate sourceData with modified properties const bool savedProperties = SavePropertiesToSourceData( - exportFolder, sourceData, + normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) { return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue); @@ -595,9 +584,7 @@ namespace MaterialEditor } bool MaterialDocument::SavePropertiesToSourceData( - const AZ::IO::BasicPath& exportFolder, - AZ::RPI::MaterialSourceData& sourceData, - PropertyFilterFunction propertyFilter) const + const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const { using namespace AZ; using namespace RPI; @@ -615,7 +602,7 @@ namespace MaterialEditor MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); if (propertyValue.IsValid()) { - if (!AtomToolsFramework::ConvertToExportFormat(exportFolder, propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyDefinition, propertyValue)) { AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str()); result = false; @@ -699,6 +686,12 @@ namespace MaterialEditor return false; } m_materialTypeSourceData = materialTypeOutcome.GetValue(); + + if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == m_materialSourceData.ApplyVersionUpdates(m_absolutePath)) + { + AZ_Error("MaterialDocument", false, "Material source data could not be auto updated to the latest version of the material type: '%s'.", m_materialSourceData.m_materialType.c_str()); + return false; + } } else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension)) { diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index c4fd263014..ceb3190f26 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -105,9 +105,7 @@ namespace MaterialEditor ////////////////////////////////////////////////////////////////////////// bool SavePropertiesToSourceData( - const AZ::IO::BasicPath& exportFolder, - AZ::RPI::MaterialSourceData& sourceData, - PropertyFilterFunction propertyFilter) const; + const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const; bool OpenInternal(AZStd::string_view loadPath); diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index c775676e68..8dc6da077e 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -98,9 +98,6 @@ namespace AZ bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData) { - AZ::IO::BasicPath exportFolder(path); - exportFolder.RemoveFilename(); - // Construct the material source data object that will be exported AZ::RPI::MaterialSourceData exportData; @@ -121,8 +118,7 @@ namespace AZ return false; } - exportData.m_materialType = - AZ::IO::PathView(editData.m_materialTypeSourcePath).LexicallyRelative(exportFolder).StringAsPosix(); + exportData.m_materialType = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialTypeSourcePath); } if (!editData.m_materialParentSourcePath.empty()) @@ -141,8 +137,7 @@ namespace AZ return false; } - exportData.m_parentMaterial = - AZ::IO::PathView(editData.m_materialParentSourcePath).LexicallyRelative(exportFolder).StringAsPosix(); + exportData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialParentSourcePath); } // Copy all of the properties from the material asset to the source data that will be exported @@ -168,7 +163,7 @@ namespace AZ propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second); } - if (!AtomToolsFramework::ConvertToExportFormat(exportFolder, propertyDefinition, propertyValue)) + if (!AtomToolsFramework::ConvertToExportFormat(path, propertyDefinition, propertyValue)) { AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str()); result = false;