LegacyMaterialComponentConverter.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. Lumberyard Legacy Mesh Component to Atom Mesh Component Conversion Script
  6. """
  7. from LegacyConversionHelpers import *
  8. class Material_Assignment_Info(object):
  9. def __init__(self, slotAssetId, assignmentAssetId):
  10. self.slotAssetId = slotAssetId
  11. self.assignmentAssetId = assignmentAssetId
  12. class Material_Component_Converter(object):
  13. """
  14. Some material related functions. Since there is no material component in legacy, this doesn't inherit from Component_Converter like other similar classes
  15. """
  16. def __init__(self, assetCatalogHelper):
  17. self.assetCatalogHelper = assetCatalogHelper
  18. def create_material_map_entry(self, slotAssetId, materialAssetId):
  19. # <Class name="AZStd::pair" field="element" type="{F652A87A-0FDF-527C-B0ED-340C074A4874}">
  20. # <Class name="AZ::Render::MaterialAssignmentId" field="value1" version="1" type="{EB603581-4654-4C17-B6DE-AE61E79EDA97}">
  21. # <Class name="AZ::u64" field="lodIndex" value="18446744073709551615" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
  22. # <Class name="AssetId" field="materialAssetId" version="1" type="{652ED536-3402-439B-AEBE-4A5DBC554085}">
  23. # <Class name="AZ::Uuid" field="guid" value="{2A4E7DCF-F5D5-55B3-8D41-A4F89398D53C}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
  24. # <Class name="unsigned int" field="subId" value="31953" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
  25. # </Class>
  26. # </Class>
  27. # <Class name="AZ::Render::MaterialAssignment" field="value2" version="1" type="{C66E5214-A24B-4722-B7F0-5991E6F8F163}">
  28. # <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}"/>
  29. # <Class name="AZStd::unordered_map" field="PropertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
  30. # </Class>
  31. # </Class>
  32. pair = create_xml_element_from_string("<Class name=\"AZStd::pair\" field=\"element\" type=\"{F652A87A-0FDF-527C-B0ED-340C074A4874}\">")
  33. isMapAssignment = True
  34. pair.append(self.create_material_assignment_id(slotAssetId, isMapAssignment))
  35. pair.append(self.create_material_assignment(materialAssetId))
  36. return pair
  37. def create_material_asset_string_from_assetid(self, assetId):
  38. hint_path = ""
  39. if assetId != "{00000000-0000-0000-0000-000000000000}:0":
  40. hint_path = self.assetCatalogHelper.assetIdToRelativePathDict[assetId]
  41. return "".join(("id=", assetId, ",type={522C7BE0-501D-463E-92C6-15184A2B7AD8},hint={", hint_path, "},loadBehavior=1"))
  42. def create_material_assignment_id(self, slotAssetId, isMapAssignment):
  43. # Material assignment ids are serialized differently for the map vs the EditorMaterialComponentSlot
  44. field = ""
  45. if isMapAssignment:
  46. field = "value1"
  47. else:
  48. field = "id"
  49. # <Class name="AZ::Render::MaterialAssignmentId" field="id" version="1" type="{EB603581-4654-4C17-B6DE-AE61E79EDA97}">
  50. # <Class name="AZ::u64" field="lodIndex" value="18446744073709551615" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
  51. # <Class name="AssetId" field="materialAssetId" version="1" type="{652ED536-3402-439B-AEBE-4A5DBC554085}">
  52. # <Class name="AZ::Uuid" field="guid" value="{00000000-0000-0000-0000-000000000000}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
  53. # <Class name="unsigned int" field="subId" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
  54. # </Class>
  55. # </Class>
  56. materialAssignmentId = create_xml_element_from_string("".join(("<Class name=\"AZ::Render::MaterialAssignmentId\" field=\"", field, "\" version=\"1\" type=\"{EB603581-4654-4C17-B6DE-AE61E79EDA97}\">")))
  57. # TODO - for now, always using the default lod index 18446744073709551615, which applies to all lods that don't have a specific override
  58. materialAssignmentId_lodIndex = create_xml_element_from_string("<Class name=\"AZ::u64\" field=\"lodIndex\" value=\"18446744073709551615\" type=\"{D6597933-47CD-4FC8-B911-63F3E2B0993A}\"/>")
  59. materialAssignmentId_AssetId = create_xml_element_from_string("<Class name=\"AssetId\" field=\"materialAssetId\" version=\"1\" type=\"{652ED536-3402-439B-AEBE-4A5DBC554085}\">")
  60. uuid = get_uuid_from_assetId(slotAssetId)
  61. materialAssignmentId_AssetId_Uuid = create_xml_element_from_string("".join(("<Class name=\"AZ::Uuid\" field=\"guid\" value=\"", uuid, "\" type=\"{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}\"/>")))
  62. subId = get_subid_from_assetId(slotAssetId)
  63. materialAssignmentId_AssetId_subid = xml.etree.ElementTree.Element("Class", {'name' : "unsigned int", 'field' : "subId", 'value' : subId, 'type' : "{43DA906B-7DEF-4CA8-9790-854106D3F983}"})
  64. materialAssignmentId_AssetId.append(materialAssignmentId_AssetId_Uuid)
  65. materialAssignmentId_AssetId.append(materialAssignmentId_AssetId_subid)
  66. materialAssignmentId.append(materialAssignmentId_lodIndex)
  67. materialAssignmentId.append(materialAssignmentId_AssetId)
  68. return materialAssignmentId
  69. def create_editor_material_assignment_slot(self, slotAssetId, materialAssetId, isDefaultSlot):
  70. field = ""
  71. if isDefaultSlot:
  72. field = "defaultMaterialSlot"
  73. else:
  74. field = "element"
  75. # <Class name="EditorMaterialComponentSlot" field="defaultMaterialSlot" version="2" type="{344066EB-7C3D-4E92-B53D-3C9EBD546488}">
  76. # <Class name="AZ::Render::MaterialAssignmentId" ...
  77. # <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}"/>
  78. # <Class name="AZStd::unordered_map" field="propertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
  79. # </Class>
  80. materialComponentSlot = create_xml_element_from_string("".join(("<Class name=\"EditorMaterialComponentSlot\" field=\"", field, "\" version=\"4\" type=\"{344066EB-7C3D-4E92-B53D-3C9EBD546488}\">")))
  81. isMapAssignment = False
  82. materialAssignmentId = self.create_material_assignment_id(slotAssetId, isMapAssignment)
  83. materialAsset = self.create_material_asset(materialAssetId)
  84. defaultPropertyOverrides = self.create_material_property_overrides()
  85. materialComponentSlot.append(materialAssignmentId)
  86. materialComponentSlot.append(materialAsset)
  87. materialComponentSlot.append(defaultPropertyOverrides)
  88. return materialComponentSlot
  89. def create_material_assignment(self, materialAssetId):
  90. # <Class name="AZ::Render::MaterialAssignment" field="value2" version="1" type="{C66E5214-A24B-4722-B7F0-5991E6F8F163}">
  91. # <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}"/>
  92. # <Class name="AZStd::unordered_map" field="PropertyOverrides" type="{6E6962E1-04C9-56F9-89C4-361031CC1384}"/>
  93. # </Class>
  94. materialAssignment = create_xml_element_from_string("<Class name=\"AZ::Render::MaterialAssignment\" field=\"value2\" version=\"1\" type=\"{C66E5214-A24B-4722-B7F0-5991E6F8F163}\">")
  95. materialAssignment.append(self.create_material_asset(materialAssetId))
  96. materialAssignment.append(self.create_material_property_overrides())
  97. return materialAssignment
  98. def create_material_asset(self, materialAssetId):
  99. materialAssetString = self.create_material_asset_string_from_assetid(materialAssetId)
  100. materialAsset = xml.etree.ElementTree.Element("Class", {'name' : "Asset", 'field' : "materialAsset", 'value' : materialAssetString, 'version' : "2", 'type' : "{77A19D40-8731-4D3C-9041-1B43047366A4}"})
  101. return materialAsset
  102. def create_material_property_overrides(self):
  103. return create_xml_element_from_string("<Class name=\"AZStd::unordered_map\" field=\"propertyOverrides\" type=\"{6E6962E1-04C9-56F9-89C4-361031CC1384}\"/>")
  104. def create_material_component_with_material_assignments(self, atomMaterialInDefaultSlotAssetId, materialAssignmentList):
  105. # TODO - the relative path might not be in the same project/gem folder as the .slice
  106. #<Class name="EditorMaterialComponent" field="element" version="3" type="{02B60E9D-470B-447D-A6EE-7D635B154183}">
  107. # <Class name="EditorRenderComponentAdapter&lt;MaterialComponentController MaterialComponent MaterialComponentConfig &gt;" field="BaseClass1" type="{DF046B40-536D-5D59-96EF-7A40DA6191B2}">
  108. # <Class name="EditorComponentAdapter&lt;MaterialComponentController MaterialComponent MaterialComponentConfig &gt;" field="BaseClass1" version="1" type="{6C4D4557-3728-56F8-A0FF-A309C3AAB853}">
  109. # <Class name="EditorComponentBase" field="BaseClass1" version="1" type="{D5346BD4-7F20-444E-B370-327ACD03D4A0}">
  110. # <Class name="AZ::Component" field="BaseClass1" type="{EDFCB2CF-F75D-43BE-B26B-F35821B29247}">
  111. # <Class name="AZ::u64" field="Id" value="6456931760107146363" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
  112. # </Class>
  113. # </Class>
  114. # <Class name="MaterialComponentController" field="Controller" version="1" type="{34AD7ED0-9866-44CD-93B6-E86840214B91}">
  115. # <Class name="MaterialComponentConfig" field="Configuration" version="3" type="{3366C279-32AE-48F6-839B-7700AE117A54}">
  116. # <Class name="ComponentConfig" field="BaseClass1" version="1" type="{0A7929DF-2932-40EA-B2B3-79BC1C3490D0}"/>
  117. # <Class name="AZStd::unordered_map" field="materials" type="{50F6716F-698B-5A6C-AACD-940597FDEC24}">
  118. # ... (map entries)
  119. # </Class>
  120. # </Class>
  121. # </Class>
  122. editorMaterialComponent = create_xml_element_from_string("<Class name=\"EditorMaterialComponent\" field=\"element\" version=\"5\" type=\"{02B60E9D-470B-447D-A6EE-7D635B154183}\">")
  123. # can't use create_xml_element_from_string here because of the spaces in the class name
  124. editorRenderComponentAdapter = xml.etree.ElementTree.Element("Class", {'name' : "EditorRenderComponentAdapter<MaterialComponentController MaterialComponent MaterialComponentConfig >", 'field' : "BaseClass1", 'type' : "{DF046B40-536D-5D59-96EF-7A40DA6191B2}"})
  125. editorComponentAdapter = xml.etree.ElementTree.Element("Class", {'name' : "EditorComponentAdapter<MaterialComponentController MaterialComponent MaterialComponentConfig >", 'field' : "BaseClass1", 'version' : "1", 'type' : "{6C4D4557-3728-56F8-A0FF-A309C3AAB853}"})
  126. editorComponentBase = create_xml_element_from_string("<Class name=\"EditorComponentBase\" field=\"BaseClass1\" version=\"1\" type=\"{D5346BD4-7F20-444E-B370-327ACD03D4A0}\">")
  127. component = create_xml_element_from_string("<Class name=\"AZ::Component\" field=\"BaseClass1\" type=\"{EDFCB2CF-F75D-43BE-B26B-F35821B29247}\">")
  128. u64 = create_xml_element_from_string("<Class name=\"AZ::u64\" field=\"Id\" value=\"6456931760107146363\" type=\"{D6597933-47CD-4FC8-B911-63F3E2B0993A}\"/>")
  129. component.append(u64)
  130. editorComponentBase.append(component)
  131. editorComponentAdapter.append(editorComponentBase)
  132. materialComponentController = create_xml_element_from_string("<Class name=\"MaterialComponentController\" field=\"Controller\" version=\"1\" type=\"{34AD7ED0-9866-44CD-93B6-E86840214B91}\">")
  133. materialComponentConfig = create_xml_element_from_string("<Class name=\"MaterialComponentConfig\" field=\"Configuration\" version=\"3\" type=\"{3366C279-32AE-48F6-839B-7700AE117A54}\">")
  134. componentConfig = create_xml_element_from_string("<Class name=\"ComponentConfig\" field=\"BaseClass1\" version=\"1\" type=\"{0A7929DF-2932-40EA-B2B3-79BC1C3490D0}\"/>")
  135. materialMap = create_xml_element_from_string("<Class name=\"AZStd::unordered_map\" field=\"materials\" type=\"{50F6716F-698B-5A6C-AACD-940597FDEC24}\">")
  136. defaultSlot = "{00000000-0000-0000-0000-000000000000}:0"
  137. for atomMaterial in materialAssignmentList:
  138. if atomMaterial.assignmentAssetId and atomMaterial.assignmentAssetId != defaultSlot:
  139. materialMapElement = self.create_material_map_entry(atomMaterial.slotAssetId, atomMaterial.assignmentAssetId)
  140. materialMap.append(materialMapElement)
  141. materialComponentConfig.append(componentConfig)
  142. materialComponentConfig.append(materialMap)
  143. materialComponentController.append(materialComponentConfig)
  144. editorComponentAdapter.append(materialComponentController)
  145. editorRenderComponentAdapter.append(editorComponentAdapter)
  146. isDefaultSlot = True
  147. defaultMaterialComponentSlot = self.create_editor_material_assignment_slot(defaultSlot, atomMaterialInDefaultSlotAssetId, isDefaultSlot)
  148. # <Class name="AZStd::vector" field="materialSlots" type="{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}">
  149. # ... (slots)
  150. isDefaultSlot = False
  151. materialsSlots = create_xml_element_from_string("Class name=\"AZStd::vector\" field=\"materialSlots\" type=\"{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}\"")
  152. for atomMaterial in materialAssignmentList:
  153. if atomMaterial.assignmentAssetId:
  154. materialSlotElement = self.create_editor_material_assignment_slot(atomMaterial.slotAssetId, atomMaterial.assignmentAssetId, isDefaultSlot)
  155. materialsSlots.append(materialSlotElement)
  156. else:
  157. # Use the default material if none was specified
  158. materialSlotElement = self.create_editor_material_assignment_slot(atomMaterial.slotAssetId, self.get_default_material_assetid(), isDefaultSlot)
  159. materialsSlots.append(materialSlotElement)
  160. # <Class name="AZStd::vector" field="materialSlotsByLod" type="{22E4F3CF-29C1-54AC-89DD-6FD47A657229}">
  161. # <Class name="AZStd::vector" field="element" type="{7FDDDE36-46C8-5DBC-8566-E792AA358BD9}">
  162. editorMaterialComponent.append(editorRenderComponentAdapter)
  163. editorMaterialComponent.append(defaultMaterialComponentSlot)
  164. return editorMaterialComponent
  165. def convert_legacy_mtl_relative_path_to_atom_material_assetid(self, normalizedProjectDir, oldMaterialRelativePath, oldFbxRelativePathWithoutExtension):
  166. if len(oldMaterialRelativePath) == 0:
  167. # 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)
  168. cacheMaterialRelativePath = "".join((oldFbxRelativePathWithoutExtension, ".azmaterial"))
  169. if cacheMaterialRelativePath in self.assetCatalogHelper.relativePathToAssetIdDict:
  170. assetId = self.assetCatalogHelper.relativePathToAssetIdDict[cacheMaterialRelativePath]
  171. return assetId
  172. else:
  173. return self.get_default_material_assetid()
  174. # TODO - doesn't work if .mtl is in the path
  175. atomRelativePath = oldMaterialRelativePath.replace('.mtl', '.azmaterial')
  176. assetId = self.assetCatalogHelper.get_asset_id_from_relative_path(atomRelativePath)
  177. if assetId:
  178. return assetId
  179. return self.get_default_material_assetid()
  180. def get_default_material_assetid(self):
  181. return "{00000000-0000-0000-0000-000000000000}:0"
  182. #return "{2A83451E-0FE6-508E-BAA2-6142AAA53C42}:0" # AtomStarterGame\Materials\Magenta.material" - makes it obvious we couldn't find a material
  183. def convert_legacy_mtl_relative_path_to_atom_material_list(self, normalizedProjectDir, oldMaterialRelativePath, oldFbxRelativePathWithoutExtension, isActor):
  184. materialList = []
  185. # Find all the materials produced by the fbx
  186. cacheFbxPath = ""
  187. if isActor:
  188. cacheFbxPath = "".join((oldFbxRelativePathWithoutExtension, ".actor"))
  189. else:
  190. cacheFbxPath = "".join((oldFbxRelativePathWithoutExtension, ".fbx.azmodel"))
  191. if cacheFbxPath in self.assetCatalogHelper.relativePathToAssetIdDict:
  192. fbxAssetId = self.assetCatalogHelper.relativePathToAssetIdDict[cacheFbxPath]
  193. # get the guid portion of the id
  194. subIdSeparatorIndex = fbxAssetId.find(":")
  195. fbxGuid = fbxAssetId[:subIdSeparatorIndex]
  196. fbxProductList = self.assetCatalogHelper.assetUuidToAssetIdsDict[fbxGuid]
  197. for productAssetId in fbxProductList:
  198. relativePath = self.assetCatalogHelper.assetIdToRelativePathDict[productAssetId]
  199. if relativePath.endswith(".azmaterial"):
  200. # we found a product material.
  201. slot = productAssetId
  202. assignment = ""
  203. # strip the _#### from it
  204. extraCharactersIndex = relativePath.rfind("_")
  205. convertedRelativePath = "".join((relativePath[:extraCharactersIndex], ".azmaterial"))
  206. if convertedRelativePath in self.assetCatalogHelper.relativePathToAssetIdDict:
  207. # try to find an atom material with the same name
  208. assignment = self.assetCatalogHelper.relativePathToAssetIdDict[convertedRelativePath]
  209. elif cacheFbxPath.replace(".azmodel", ".azmaterial") in self.assetCatalogHelper.relativePathToAssetIdDict:
  210. # An fbx with only 1 submesh is going to produce an azmaterial that is fbxname_materialname
  211. # even though this is often redundant like rivervista_01_RiverVista01MAT.azmaterial.
  212. # Legacy .mtl files like this would end up with a rivervista_01.mtl that was a multi-material
  213. # but only had a single sub-material called RiverVista01MAT.
  214. # The legacy material converter treats this case as a single material, and instead of
  215. # naming the file rivervista_01_RiverVista01MAT.material, it just calls it rivervista_01.material.
  216. # However, the atom model builder still follows the fbxname_materialname convention, so in this case
  217. # we should look and see if the material converter created an rivervist_01.material
  218. assignment = self.assetCatalogHelper.relativePathToAssetIdDict[cacheFbxPath.replace(".azmodel", ".azmaterial")]
  219. else:
  220. assignment = self.get_default_material_assetid()
  221. print("Could not match {0} to a corresponding source atom material".format(convertedRelativePath))
  222. materialList.append(Material_Assignment_Info(slot, assignment))
  223. return materialList