You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AtomLyIntegration/CommonFeatures/Assets/Editor/Scripts/LegacyContentConversion/LegacyMaterialComponentConv...

263 lines
20 KiB
Python

"""
Copyright (c) Contributors to the Open 3D Engine Project
SPDX-License-Identifier: Apache-2.0 OR MIT
Lumberyard Legacy Mesh Component to Atom Mesh Component Conversion Script
"""
from LegacyConversionHelpers import *
class Material_Assignment_Info(object):
def __init__(self, slotAssetId, assignmentAssetId):
self.slotAssetId = slotAssetId
self.assignmentAssetId = assignmentAssetId
class Material_Component_Converter(object):
"""
Some material related functions. Since there is no material component in legacy, this doesn't inherit from Component_Converter like other similar classes
"""
def __init__(self, assetCatalogHelper):
self.assetCatalogHelper = assetCatalogHelper
def create_material_map_entry(self, slotAssetId, materialAssetId):
# <Class name="AZStd::pair" field="element" type="{F652A87A-0FDF-527C-B0ED-340C074A4874}">
# <Class name="AZ::Render::MaterialAssignmentId" field="value1" version="1" type="{EB603581-4654-4C17-B6DE-AE61E79EDA97}">
# <Class name="AZ::u64" field="lodIndex" value="18446744073709551615" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
# <Class name="AssetId" field="materialAssetId" version="1" type="{652ED536-3402-439B-AEBE-4A5DBC554085}">
# <Class name="AZ::Uuid" field="guid" value="{2A4E7DCF-F5D5-55B3-8D41-A4F89398D53C}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
# <Class name="unsigned int" field="subId" value="31953" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
# </Class>
# </Class>
# <Class name="AZ::Render::MaterialAssignment" field="value2" version="1" type="{C66E5214-A24B-4722-B7F0-5991E6F8F163}">
# <Class name="Asset" field="MaterialAsset" value="id={0BFD18DD-3A64-5240-A272-600301CE821C}:0,type={522C7BE0-501D-463E-92C6-15184A2B7AD8},hint={valena/valenaactor_jumpsuitmat.azmaterial}" version="1" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
# <Class name="AZStd::unordered_map" field="PropertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
# </Class>
# </Class>
pair = create_xml_element_from_string("<Class name=\"AZStd::pair\" field=\"element\" type=\"{F652A87A-0FDF-527C-B0ED-340C074A4874}\">")
isMapAssignment = True
pair.append(self.create_material_assignment_id(slotAssetId, isMapAssignment))
pair.append(self.create_material_assignment(materialAssetId))
return pair
def create_material_asset_string_from_assetid(self, assetId):
hint_path = ""
if assetId != "{00000000-0000-0000-0000-000000000000}:0":
hint_path = self.assetCatalogHelper.assetIdToRelativePathDict[assetId]
return "".join(("id=", assetId, ",type={522C7BE0-501D-463E-92C6-15184A2B7AD8},hint={", hint_path, "},loadBehavior=1"))
def create_material_assignment_id(self, slotAssetId, isMapAssignment):
# Material assignment ids are serialized differently for the map vs the EditorMaterialComponentSlot
field = ""
if isMapAssignment:
field = "value1"
else:
field = "id"
# <Class name="AZ::Render::MaterialAssignmentId" field="id" version="1" type="{EB603581-4654-4C17-B6DE-AE61E79EDA97}">
# <Class name="AZ::u64" field="lodIndex" value="18446744073709551615" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
# <Class name="AssetId" field="materialAssetId" version="1" type="{652ED536-3402-439B-AEBE-4A5DBC554085}">
# <Class name="AZ::Uuid" field="guid" value="{00000000-0000-0000-0000-000000000000}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
# <Class name="unsigned int" field="subId" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
# </Class>
# </Class>
materialAssignmentId = create_xml_element_from_string("".join(("<Class name=\"AZ::Render::MaterialAssignmentId\" field=\"", field, "\" version=\"1\" type=\"{EB603581-4654-4C17-B6DE-AE61E79EDA97}\">")))
# TODO - for now, always using the default lod index 18446744073709551615, which applies to all lods that don't have a specific override
materialAssignmentId_lodIndex = create_xml_element_from_string("<Class name=\"AZ::u64\" field=\"lodIndex\" value=\"18446744073709551615\" type=\"{D6597933-47CD-4FC8-B911-63F3E2B0993A}\"/>")
materialAssignmentId_AssetId = create_xml_element_from_string("<Class name=\"AssetId\" field=\"materialAssetId\" version=\"1\" type=\"{652ED536-3402-439B-AEBE-4A5DBC554085}\">")
uuid = get_uuid_from_assetId(slotAssetId)
materialAssignmentId_AssetId_Uuid = create_xml_element_from_string("".join(("<Class name=\"AZ::Uuid\" field=\"guid\" value=\"", uuid, "\" type=\"{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}\"/>")))
subId = get_subid_from_assetId(slotAssetId)
materialAssignmentId_AssetId_subid = xml.etree.ElementTree.Element("Class", {'name' : "unsigned int", 'field' : "subId", 'value' : subId, 'type' : "{43DA906B-7DEF-4CA8-9790-854106D3F983}"})
materialAssignmentId_AssetId.append(materialAssignmentId_AssetId_Uuid)
materialAssignmentId_AssetId.append(materialAssignmentId_AssetId_subid)
materialAssignmentId.append(materialAssignmentId_lodIndex)
materialAssignmentId.append(materialAssignmentId_AssetId)
return materialAssignmentId
def create_editor_material_assignment_slot(self, slotAssetId, materialAssetId, isDefaultSlot):
field = ""
if isDefaultSlot:
field = "defaultMaterialSlot"
else:
field = "element"
# <Class name="EditorMaterialComponentSlot" field="defaultMaterialSlot" version="2" type="{344066EB-7C3D-4E92-B53D-3C9EBD546488}">
# <Class name="AZ::Render::MaterialAssignmentId" ...
# <Class name="Asset" field="materialAsset" value="id={00000000-0000-0000-0000-000000000000}:0,type={522C7BE0-501D-463E-92C6-15184A2B7AD8},hint={}" version="1" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
# <Class name="AZStd::unordered_map" field="propertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
# </Class>
materialComponentSlot = create_xml_element_from_string("".join(("<Class name=\"EditorMaterialComponentSlot\" field=\"", field, "\" version=\"4\" type=\"{344066EB-7C3D-4E92-B53D-3C9EBD546488}\">")))
isMapAssignment = False
materialAssignmentId = self.create_material_assignment_id(slotAssetId, isMapAssignment)
materialAsset = self.create_material_asset(materialAssetId)
defaultPropertyOverrides = self.create_material_property_overrides()
materialComponentSlot.append(materialAssignmentId)
materialComponentSlot.append(materialAsset)
materialComponentSlot.append(defaultPropertyOverrides)
return materialComponentSlot
def create_material_assignment(self, materialAssetId):
# <Class name="AZ::Render::MaterialAssignment" field="value2" version="1" type="{C66E5214-A24B-4722-B7F0-5991E6F8F163}">
# <Class name="Asset" field="MaterialAsset" value="id={B175B5BF-E97C-52BD-9DC8-60A9CE05CCC8}:0,type={522C7BE0-501D-463E-92C6-15184A2B7AD8},hint={valena/valenaactor_glovesbootsmat.azmaterial}" version="1" type="{77A19D40-8731-4D3C-9041-1B43047366A4}"/>
# <Class name="AZStd::unordered_map" field="PropertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
# </Class>
materialAssignment = create_xml_element_from_string("<Class name=\"AZ::Render::MaterialAssignment\" field=\"value2\" version=\"1\" type=\"{C66E5214-A24B-4722-B7F0-5991E6F8F163}\">")
materialAssignment.append(self.create_material_asset(materialAssetId))
materialAssignment.append(self.create_material_property_overrides())
return materialAssignment
def create_material_asset(self, materialAssetId):
materialAssetString = self.create_material_asset_string_from_assetid(materialAssetId)
materialAsset = xml.etree.ElementTree.Element("Class", {'name' : "Asset", 'field' : "materialAsset", 'value' : materialAssetString, 'version' : "2", 'type' : "{77A19D40-8731-4D3C-9041-1B43047366A4}"})
return materialAsset
def create_material_property_overrides(self):
return create_xml_element_from_string("<Class name=\"AZStd::unordered_map\" field=\"propertyOverrides\" type=\"{6E6962E1-04C9-56F9-89C4-361031CC1384}\"/>")
def create_material_component_with_material_assignments(self, atomMaterialInDefaultSlotAssetId, materialAssignmentList):
# TODO - the relative path might not be in the same project/gem folder as the .slice
#<Class name="EditorMaterialComponent" field="element" version="3" type="{02B60E9D-470B-447D-A6EE-7D635B154183}">
# <Class name="EditorRenderComponentAdapter&lt;MaterialComponentController MaterialComponent MaterialComponentConfig &gt;" field="BaseClass1" type="{DF046B40-536D-5D59-96EF-7A40DA6191B2}">
# <Class name="EditorComponentAdapter&lt;MaterialComponentController MaterialComponent MaterialComponentConfig &gt;" field="BaseClass1" version="1" type="{6C4D4557-3728-56F8-A0FF-A309C3AAB853}">
# <Class name="EditorComponentBase" field="BaseClass1" version="1" type="{D5346BD4-7F20-444E-B370-327ACD03D4A0}">
# <Class name="AZ::Component" field="BaseClass1" type="{EDFCB2CF-F75D-43BE-B26B-F35821B29247}">
# <Class name="AZ::u64" field="Id" value="6456931760107146363" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
# </Class>
# </Class>
# <Class name="MaterialComponentController" field="Controller" version="1" type="{34AD7ED0-9866-44CD-93B6-E86840214B91}">
# <Class name="MaterialComponentConfig" field="Configuration" version="3" type="{3366C279-32AE-48F6-839B-7700AE117A54}">
# <Class name="ComponentConfig" field="BaseClass1" version="1" type="{0A7929DF-2932-40EA-B2B3-79BC1C3490D0}"/>
# <Class name="AZStd::unordered_map" field="materials" type="{50F6716F-698B-5A6C-AACD-940597FDEC24}">
# ... (map entries)
# </Class>
# </Class>
# </Class>
editorMaterialComponent = create_xml_element_from_string("<Class name=\"EditorMaterialComponent\" field=\"element\" version=\"5\" type=\"{02B60E9D-470B-447D-A6EE-7D635B154183}\">")
# can't use create_xml_element_from_string here because of the spaces in the class name
editorRenderComponentAdapter = xml.etree.ElementTree.Element("Class", {'name' : "EditorRenderComponentAdapter<MaterialComponentController MaterialComponent MaterialComponentConfig >", 'field' : "BaseClass1", 'type' : "{DF046B40-536D-5D59-96EF-7A40DA6191B2}"})
editorComponentAdapter = xml.etree.ElementTree.Element("Class", {'name' : "EditorComponentAdapter<MaterialComponentController MaterialComponent MaterialComponentConfig >", 'field' : "BaseClass1", 'version' : "1", 'type' : "{6C4D4557-3728-56F8-A0FF-A309C3AAB853}"})
editorComponentBase = create_xml_element_from_string("<Class name=\"EditorComponentBase\" field=\"BaseClass1\" version=\"1\" type=\"{D5346BD4-7F20-444E-B370-327ACD03D4A0}\">")
component = create_xml_element_from_string("<Class name=\"AZ::Component\" field=\"BaseClass1\" type=\"{EDFCB2CF-F75D-43BE-B26B-F35821B29247}\">")
u64 = create_xml_element_from_string("<Class name=\"AZ::u64\" field=\"Id\" value=\"6456931760107146363\" type=\"{D6597933-47CD-4FC8-B911-63F3E2B0993A}\"/>")
component.append(u64)
editorComponentBase.append(component)
editorComponentAdapter.append(editorComponentBase)
materialComponentController = create_xml_element_from_string("<Class name=\"MaterialComponentController\" field=\"Controller\" version=\"1\" type=\"{34AD7ED0-9866-44CD-93B6-E86840214B91}\">")
materialComponentConfig = create_xml_element_from_string("<Class name=\"MaterialComponentConfig\" field=\"Configuration\" version=\"3\" type=\"{3366C279-32AE-48F6-839B-7700AE117A54}\">")
componentConfig = create_xml_element_from_string("<Class name=\"ComponentConfig\" field=\"BaseClass1\" version=\"1\" type=\"{0A7929DF-2932-40EA-B2B3-79BC1C3490D0}\"/>")
materialMap = create_xml_element_from_string("<Class name=\"AZStd::unordered_map\" field=\"materials\" type=\"{50F6716F-698B-5A6C-AACD-940597FDEC24}\">")
# {00000000-0000-0000-0000-000000000000}:0 is the default slot
defaultMaterialAssignmentMapEntry = self.create_material_map_entry("{00000000-0000-0000-0000-000000000000}:0", atomMaterialInDefaultSlotAssetId)
materialMap.append(defaultMaterialAssignmentMapEntry)
for atomMaterial in materialAssignmentList:
if atomMaterial.assignmentAssetId:
materialMapElement = self.create_material_map_entry(atomMaterial.slotAssetId, atomMaterial.assignmentAssetId)
materialMap.append(materialMapElement)
materialComponentConfig.append(componentConfig)
materialComponentConfig.append(materialMap)
materialComponentController.append(materialComponentConfig)
editorComponentAdapter.append(materialComponentController)
editorRenderComponentAdapter.append(editorComponentAdapter)
isDefaultSlot = True
defaultMaterialComponentSlot = self.create_editor_material_assignment_slot("{00000000-0000-0000-0000-000000000000}:0", atomMaterialInDefaultSlotAssetId, isDefaultSlot)
# <Class name="AZStd::vector" field="materialSlots" type="{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}">
# ... (slots)
isDefaultSlot = False
materialsSlots = create_xml_element_from_string("Class name=\"AZStd::vector\" field=\"materialSlots\" type=\"{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}\"")
for atomMaterial in materialAssignmentList:
if atomMaterial.assignmentAssetId:
materialSlotElement = self.create_editor_material_assignment_slot(atomMaterial.slotAssetId, atomMaterial.assignmentAssetId, isDefaultSlot)
materialsSlots.append(materialSlotElement)
else:
# Use the default material if none was specified
materialSlotElement = self.create_editor_material_assignment_slot(atomMaterial.slotAssetId, self.get_default_material_assetid(), isDefaultSlot)
materialsSlots.append(materialSlotElement)
# <Class name="AZStd::vector" field="materialSlotsByLod" type="{22E4F3CF-29C1-54AC-89DD-6FD47A657229}">
# <Class name="AZStd::vector" field="element" type="{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}">
editorMaterialComponent.append(editorRenderComponentAdapter)
editorMaterialComponent.append(defaultMaterialComponentSlot)
return editorMaterialComponent
def convert_legacy_mtl_relative_path_to_atom_material_assetid(self, normalizedProjectDir, oldMaterialRelativePath, oldFbxRelativePathWithoutExtension):
if len(oldMaterialRelativePath) == 0:
# if no material was used, try to find one that matches the name of the fbx (in the event the fbx only has a single material)
cacheMaterialRelativePath = "".join((oldFbxRelativePathWithoutExtension, ".azmaterial"))
if cacheMaterialRelativePath in self.assetCatalogHelper.relativePathToAssetIdDict:
assetId = self.assetCatalogHelper.relativePathToAssetIdDict[cacheMaterialRelativePath]
return assetId
else:
return self.get_default_material_assetid()
# TODO - doesn't work if .mtl is in the path
atomRelativePath = oldMaterialRelativePath.replace('.mtl', '.azmaterial')
assetId = self.assetCatalogHelper.get_asset_id_from_relative_path(atomRelativePath)
if assetId:
return assetId
return self.get_default_material_assetid()
def get_default_material_assetid(self):
return "{00000000-0000-0000-0000-000000000000}:0"
#return "{2A83451E-0FE6-508E-BAA2-6142AAA53C42}:0" # AtomStarterGame\Materials\Magenta.material" - makes it obvious we couldn't find a material
def convert_legacy_mtl_relative_path_to_atom_material_list(self, normalizedProjectDir, oldMaterialRelativePath, oldFbxRelativePathWithoutExtension, isActor):
materialList = []
# Find all the materials produced by the fbx
cacheFbxPath = ""
if isActor:
cacheFbxPath = "".join((oldFbxRelativePathWithoutExtension, ".actor"))
else:
cacheFbxPath = "".join((oldFbxRelativePathWithoutExtension, ".azmodel"))
if cacheFbxPath in self.assetCatalogHelper.relativePathToAssetIdDict:
fbxAssetId = self.assetCatalogHelper.relativePathToAssetIdDict[cacheFbxPath]
# get the guid portion of the id
subIdSeparatorIndex = fbxAssetId.find(":")
fbxGuid = fbxAssetId[:subIdSeparatorIndex]
fbxProductList = self.assetCatalogHelper.assetUuidToAssetIdsDict[fbxGuid]
for productAssetId in fbxProductList:
relativePath = self.assetCatalogHelper.assetIdToRelativePathDict[productAssetId]
if relativePath.endswith(".azmaterial"):
# we found a product material.
slot = productAssetId
assignment = ""
# strip the _#### from it
extraCharactersIndex = relativePath.rfind("_")
convertedRelativePath = "".join((relativePath[:extraCharactersIndex], ".azmaterial"))
if convertedRelativePath in self.assetCatalogHelper.relativePathToAssetIdDict:
# try to find an atom material with the same name
assignment = self.assetCatalogHelper.relativePathToAssetIdDict[convertedRelativePath]
elif cacheFbxPath.replace(".azmodel", ".azmaterial") in self.assetCatalogHelper.relativePathToAssetIdDict:
# An fbx with only 1 submesh is going to produce an azmaterial that is fbxname_materialname
# even though this is often redundant like rivervista_01_RiverVista01MAT.azmaterial.
# Legacy .mtl files like this would end up with a rivervista_01.mtl that was a multi-material
# but only had a single sub-material called RiverVista01MAT.
# The legacy material converter treats this case as a single material, and instead of
# naming the file rivervista_01_RiverVista01MAT.material, it just calls it rivervista_01.material.
# However, the atom model builder still follows the fbxname_materialname convention, so in this case
# we should look and see if the material converter created an rivervist_01.material
assignment = self.assetCatalogHelper.relativePathToAssetIdDict[cacheFbxPath.replace(".azmodel", ".azmaterial")]
else:
assignment = self.get_default_material_assetid()
print("Could not match {0} to a corresponding source atom material".format(convertedRelativePath))
materialList.append(Material_Assignment_Info(slot, assignment))
return materialList