auto_lod.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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, logging
  9. import azlmbr.math
  10. import azlmbr.bus
  11. from scene_helpers import *
  12. #
  13. # SceneAPI Processor
  14. #
  15. def update_manifest(scene):
  16. import uuid
  17. import azlmbr.scene as sceneApi
  18. import azlmbr.scene.graph
  19. from scene_api import scene_data as sceneData
  20. graph = sceneData.SceneGraph(scene.graph)
  21. # Get a list of all the mesh nodes, as well as all the nodes
  22. mesh_name_list, all_node_paths = get_mesh_node_names(graph)
  23. mesh_name_list.sort(key=lambda node: str.casefold(node.get_path()))
  24. scene_manifest = sceneData.SceneManifest()
  25. clean_filename = scene.sourceFilename.replace('.', '_')
  26. # Compute the filename of the scene file
  27. source_basepath = scene.watchFolder
  28. source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
  29. source_filename_only = os.path.basename(clean_filename)
  30. created_entities = []
  31. previous_entity_id = azlmbr.entity.InvalidEntityId
  32. first_mesh = True
  33. # Make a list of mesh node paths
  34. mesh_path_list = list(map(lambda node: node.get_path(), mesh_name_list))
  35. # Assume the first mesh is the main mesh
  36. main_mesh = mesh_name_list[0]
  37. mesh_path = main_mesh.get_path()
  38. # Create a unique mesh group name using the filename + node name
  39. mesh_group_name = '{}_{}'.format(source_filename_only, main_mesh.get_name())
  40. # Remove forbidden filename characters from the name since this will become a file on disk later
  41. mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
  42. # Add the MeshGroup to the manifest and give it a unique ID
  43. mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
  44. mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
  45. # Set our current node as the only node that is included in this MeshGroup
  46. scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
  47. # Explicitly remove all other nodes to prevent implicit inclusions
  48. for node in mesh_path_list:
  49. if node != mesh_path:
  50. scene_manifest.mesh_group_unselect_node(mesh_group, node)
  51. # Create a LOD rule
  52. lod_rule = scene_manifest.mesh_group_add_lod_rule(mesh_group)
  53. # Loop all the mesh nodes after the first
  54. for x in mesh_path_list[1:]:
  55. # Add a new LOD level
  56. lod = scene_manifest.lod_rule_add_lod(lod_rule)
  57. # Select the current mesh for this LOD level
  58. scene_manifest.lod_select_node(lod, x)
  59. # Unselect every other mesh for this LOD level
  60. for y in mesh_path_list:
  61. if y != x:
  62. scene_manifest.lod_unselect_node(lod, y)
  63. # Create an editor entity
  64. entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
  65. # Add an EditorMeshComponent to the entity
  66. editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
  67. # Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
  68. # The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.fbx.azmodel
  69. # The assetHint will be converted to an AssetId later during prefab loading
  70. json_update = json.dumps({
  71. "Controller": { "Configuration": { "ModelAsset": {
  72. "assetHint": os.path.join(source_relative_path, mesh_group_name) + ".fbx.azmodel" }}}
  73. });
  74. # Apply the JSON above to the component we created
  75. result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
  76. if not result:
  77. raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
  78. create_prefab(scene_manifest, source_filename_only, [entity_id])
  79. # Convert the manifest to a JSON string and return it
  80. new_manifest = scene_manifest.export()
  81. return new_manifest
  82. sceneJobHandler = None
  83. def on_update_manifest(args):
  84. try:
  85. scene = args[0]
  86. return update_manifest(scene)
  87. except RuntimeError as err:
  88. print (f'ERROR - {err}')
  89. log_exception_traceback()
  90. except:
  91. log_exception_traceback()
  92. finally:
  93. global sceneJobHandler
  94. # do not delete or set sceneJobHandler to None, just disconnect from it.
  95. # this call is occuring while the scene Job Handler itself is in the callstack, so deleting it here
  96. # would cause a crash.
  97. sceneJobHandler.disconnect()
  98. # try to create SceneAPI handler for processing
  99. try:
  100. import azlmbr.scene as sceneApi
  101. sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
  102. sceneJobHandler.connect()
  103. sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
  104. except:
  105. sceneJobHandler = None