scene_mesh_to_prefab.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. import os, traceback, binascii, sys, json, pathlib
  9. import azlmbr.math
  10. import azlmbr.bus
  11. #
  12. # SceneAPI Processor
  13. #
  14. def log_exception_traceback():
  15. exc_type, exc_value, exc_tb = sys.exc_info()
  16. data = traceback.format_exception(exc_type, exc_value, exc_tb)
  17. print(str(data))
  18. def get_mesh_node_names(sceneGraph):
  19. import azlmbr.scene as sceneApi
  20. import azlmbr.scene.graph
  21. from scene_api import scene_data as sceneData
  22. meshDataList = []
  23. node = sceneGraph.get_root()
  24. children = []
  25. paths = []
  26. while node.IsValid():
  27. # store children to process after siblings
  28. if sceneGraph.has_node_child(node):
  29. children.append(sceneGraph.get_node_child(node))
  30. nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
  31. paths.append(nodeName.get_path())
  32. # store any node that has mesh data content
  33. nodeContent = sceneGraph.get_node_content(node)
  34. if nodeContent.CastWithTypeName('MeshData'):
  35. if sceneGraph.is_node_end_point(node) is False:
  36. if (len(nodeName.get_path())):
  37. meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
  38. # advance to next node
  39. if sceneGraph.has_node_sibling(node):
  40. node = sceneGraph.get_node_sibling(node)
  41. elif children:
  42. node = children.pop()
  43. else:
  44. node = azlmbr.scene.graph.NodeIndex()
  45. return meshDataList, paths
  46. def update_manifest(scene):
  47. import json
  48. import uuid, os
  49. import azlmbr.scene as sceneApi
  50. import azlmbr.scene.graph
  51. from scene_api import scene_data as sceneData
  52. graph = sceneData.SceneGraph(scene.graph)
  53. # Get a list of all the mesh nodes, as well as all the nodes
  54. mesh_name_list, all_node_paths = get_mesh_node_names(graph)
  55. scene_manifest = sceneData.SceneManifest()
  56. clean_filename = scene.sourceFilename.replace('.', '_')
  57. # Compute the filename of the scene file
  58. source_basepath = scene.watchFolder
  59. source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
  60. source_filename_only = os.path.basename(clean_filename)
  61. created_entities = []
  62. # Loop every mesh node in the scene
  63. for activeMeshIndex in range(len(mesh_name_list)):
  64. mesh_name = mesh_name_list[activeMeshIndex]
  65. mesh_path = mesh_name.get_path()
  66. # Create a unique mesh group name using the filename + node name
  67. mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name())
  68. # Remove forbidden filename characters from the name since this will become a file on disk later
  69. mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
  70. # Add the MeshGroup to the manifest and give it a unique ID
  71. mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
  72. mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
  73. # Set our current node as the only node that is included in this MeshGroup
  74. scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
  75. # Explicitly remove all other nodes to prevent implicit inclusions
  76. for node in all_node_paths:
  77. if node != mesh_path:
  78. scene_manifest.mesh_group_unselect_node(mesh_group, node)
  79. # Create an editor entity
  80. entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
  81. # Add an EditorMeshComponent to the entity
  82. editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
  83. # Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
  84. # The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
  85. # The assetHint will be converted to an AssetId later during prefab loading
  86. json_update = json.dumps({
  87. "Controller": { "Configuration": { "ModelAsset": {
  88. "assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
  89. });
  90. # Apply the JSON above to the component we created
  91. result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
  92. if not result:
  93. raise RuntimeError("UpdateComponentForEntity failed")
  94. # Keep track of the entity we set up, we'll add them all to the prefab we're creating later
  95. created_entities.append(entity_id)
  96. # Create a prefab with all our entities
  97. prefab_filename = source_filename_only + ".prefab"
  98. created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename)
  99. if created_template_id == azlmbr.prefab.InvalidTemplateId:
  100. raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
  101. # Convert the prefab to a JSON string
  102. output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
  103. if output.IsSuccess():
  104. jsonString = output.GetValue()
  105. uuid = azlmbr.math.Uuid_CreateRandom().ToString()
  106. jsonResult = json.loads(jsonString)
  107. # Add a PrefabGroup to the manifest and store the JSON on it
  108. scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult)
  109. else:
  110. raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
  111. # Convert the manifest to a JSON string and return it
  112. new_manifest = scene_manifest.export()
  113. return new_manifest
  114. sceneJobHandler = None
  115. def on_update_manifest(args):
  116. try:
  117. scene = args[0]
  118. return update_manifest(scene)
  119. except RuntimeError as err:
  120. print (f'ERROR - {err}')
  121. log_exception_traceback()
  122. global sceneJobHandler
  123. sceneJobHandler = None
  124. # try to create SceneAPI handler for processing
  125. try:
  126. import azlmbr.scene as sceneApi
  127. if (sceneJobHandler == None):
  128. sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
  129. sceneJobHandler.connect()
  130. sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
  131. except:
  132. sceneJobHandler = None