pythonassetbuildertests.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. """
  6. # Import builtin libraries
  7. import pytest
  8. import logging
  9. import os
  10. import json
  11. # Import fixtures
  12. from ..ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
  13. from ..ap_fixtures.ap_setup_fixture import ap_setup_fixture as ap_setup_fixture
  14. # Import LyShared
  15. import ly_test_tools.o3de.pipeline_utils as utils
  16. # Use the following logging pattern to hook all test logging together:
  17. logger = logging.getLogger(__name__)
  18. # Configuring the logging is done in ly_test_tools at the following location:
  19. # ~/dev/Tools/LyTestTools/ly_test_tools/log/py_logging_util.py
  20. @pytest.fixture
  21. def local_resources(request, workspace, ap_setup_fixture):
  22. ap_setup_fixture["tests_dir"] = os.path.dirname(os.path.realpath(__file__))
  23. @pytest.mark.usefixtures("asset_processor")
  24. @pytest.mark.usefixtures("ap_setup_fixture")
  25. @pytest.mark.usefixtures("local_resources")
  26. @pytest.mark.parametrize("project", ["AutomatedTesting"])
  27. @pytest.mark.SUITE_main
  28. class TestsPythonAssetProcessing_APBatch(object):
  29. @property
  30. def asset_processor_extra_params(self):
  31. return [
  32. # Disabling Atom assets disables most products, using the debugOutput flag ensures one product is output.
  33. "--debugOutput",
  34. # By default, if job priorities are equal, jobs run in an arbitrary order. This makes sure
  35. # jobs are run by sorting on the database source name, so they run in the same order each time
  36. # when this test is run.
  37. "--sortJobsByDBSourceName",
  38. # Disabling Atom products means this asset won't need a lot of source dependencies to be processed,
  39. # keeping the scope of this test down.
  40. "--regset=\"/O3DE/SceneAPI/AssetImporter/SkipAtomOutput=true\"",
  41. # The bug this regression test happened when the same builder processed FBX files with and without Python.
  42. # This flag ensures that only one builder is launched, so that situation can be replicated.
  43. "--regset=\"/Amazon/AssetProcessor/Settings/Jobs/maxJobs=1\""]
  44. def test_ProcessAssetWithoutScriptAfterAssetWithScript_ScriptOnlyRunsOnExpectedAsset(self, workspace, ap_setup_fixture, asset_processor):
  45. # This is a regression test. The situation it's testing is, the Python script to run
  46. # defined in scene manifest files was persisting in a single builder. So if
  47. # that builder processed file a.fbx, then b.fbx, and a.fbx has a Python script to run,
  48. # it was also running that Python script on b.fbx.
  49. asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "TwoSceneFiles_OneWithPythonOneWithout_PythonOnlyRunsOnFirstScene")
  50. result, _ = asset_processor.batch_process(extra_params=self.asset_processor_extra_params)
  51. assert result, "AP Batch failed"
  52. expected_product_list = [
  53. "a_simple_box_with_script.fbx.dbgsg",
  54. "b_simple_box_no_script.fbx.dbgsg"
  55. ]
  56. missing_assets, _ = utils.compare_assets_with_cache(expected_product_list,
  57. asset_processor.project_test_cache_folder())
  58. assert not missing_assets, f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}'
  59. # The Python script loaded in the scene manifest will write a log file with the source file's name
  60. # to the temp folder. This is the easiest way to have the internal Python there communicate with this test.
  61. expected_path = os.path.join(asset_processor.project_test_source_folder(), "a_simple_box_with_script_fbx.log")
  62. unexpected_path = os.path.join(asset_processor.project_test_source_folder(), "b_simple_box_no_script_fbx.log")
  63. # Simple check to make sure the Python script in the scene manifest ran on the file it should have ran on.
  64. assert os.path.exists(expected_path), f"Did not find expected output test asset {expected_path}"
  65. # If this test fails here, it means the Python script from the first processed FBX file is being run
  66. # on the second FBX file, when it should not be.
  67. assert not os.path.exists(unexpected_path), f"Found unexpected output test asset {unexpected_path}"
  68. def find_user_defined_property(self, filename: str, text: str):
  69. # find the user defined property pattern in a file
  70. with open(filename) as f:
  71. content = f.readlines()
  72. for line in content:
  73. if line.rstrip().endswith(text):
  74. return True
  75. return False
  76. def compute_udp_asset_debug_file(self, workspace, asset_processor, debug_filename, ap_setup_fixture):
  77. asset_folder_name = 'UserDefinedProperties'
  78. return self.compute_asset_debug_file(workspace, asset_processor, debug_filename, asset_folder_name, ap_setup_fixture)
  79. def compute_asset_debug_file(self, workspace, asset_processor, debug_filename, folder_name, ap_setup_fixture):
  80. # computes the file name of the debug file for a UserDefinedProperties test file
  81. asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], folder_name)
  82. result, _ = asset_processor.batch_process(extra_params=self.asset_processor_extra_params)
  83. assert result, "AP Batch failed"
  84. # compute the cache path
  85. cache_folder = asset_processor.project_test_cache_folder()
  86. # compute the file name to the debug file
  87. asset_debug_filename = os.path.join(cache_folder, debug_filename)
  88. if os.path.isfile(asset_debug_filename) == False:
  89. raise Exception(f"Missing file {asset_debug_filename}")
  90. return asset_debug_filename
  91. def test_ProcessSceneWithMetadata_SupportedMayaDataTypes_Work(self, workspace, ap_setup_fixture, asset_processor):
  92. # This test loads the debug output file for an FBX exported by Maya that has a few user defined properties
  93. asset_dbgsg = 'maya_with_attributes.fbx.dbgsg'
  94. dbgsg_file = self.compute_udp_asset_debug_file(workspace, asset_processor, asset_dbgsg, ap_setup_fixture)
  95. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_lod: false'), "Malformed o3de_atom_lod value"
  96. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_material: 0'), "Malformed o3de_atom_material value"
  97. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_lod: 0.000000'), "Malformed o3de_default_lod value"
  98. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_material: gem/sponza/assets/objects/sponza_mat_bricks.azmaterial'), "Malformed o3de_default_material value"
  99. def test_ProcessSceneWithMetadata_SupportedMaxDataTypes_Work(self, workspace, ap_setup_fixture, asset_processor):
  100. # This test loads the debug output file for an FBX exported by Max that has a few user defined properties
  101. asset_dbgsg = 'max_with_attributes.fbx.dbgsg'
  102. dbgsg_file = self.compute_udp_asset_debug_file(workspace, asset_processor, asset_dbgsg, ap_setup_fixture)
  103. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_material: 0'), "Malformed o3de_atom_material value"
  104. assert self.find_user_defined_property(dbgsg_file, 'o3de_phyx_lodY: 0.000000'), "Malformed o3de_phyx_lodY value"
  105. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_lod: 0.000000'), "Malformed o3de_default_lod value"
  106. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_lod: false'), "Malformed o3de_atom_lod value"
  107. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_material: gem/sponza/assets/objects/sponza_mat_bricks.azmaterial'), "Malformed o3de_default_material value"
  108. def test_ProcessSceneWithMetadata_SupportedBlenderDataTypes_Work(self, workspace, ap_setup_fixture, asset_processor):
  109. # This test loads the debug output file for an FBX exported by Blender that has a few user defined properties
  110. asset_dbgsg = 'blender_with_attributes.fbx.dbgsg'
  111. dbgsg_file = self.compute_udp_asset_debug_file(workspace, asset_processor, asset_dbgsg, ap_setup_fixture)
  112. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_material: 0'), "Malformed o3de_atom_material value"
  113. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_lod: 0.000000'), "Malformed o3de_default_lod value"
  114. assert self.find_user_defined_property(dbgsg_file, 'o3de_default_material: gem/sponza/assets/objects/sponza_mat_bricks.azmaterial'), "Malformed o3de_default_material value"
  115. assert self.find_user_defined_property(dbgsg_file, 'o3de_atom_lod: 0'), "Malformed o3de_atom_lod value"
  116. def test_ProcessSceneWithMetadata_DebugSceneManifest_Work(self, workspace, ap_setup_fixture, asset_processor):
  117. # This test detects the debug .assetinfo file in the cahce folder
  118. assetinfo_dbg = 'blender_with_attributes.fbx.assetinfo.dbg'
  119. assetinfo_dbg_file = self.compute_udp_asset_debug_file(workspace, asset_processor, assetinfo_dbg, ap_setup_fixture)
  120. assert assetinfo_dbg_file, "The debug assetinfo file is missing"
  121. def test_ProcessSceneWithCommonParent_DefaultMeshGroups_Work(self, workspace, ap_setup_fixture, asset_processor):
  122. # This test validates that the correct meshes are included and excluded in the default mesh groups
  123. # of a scene containing mesh nodes that have a common parent transform node
  124. assetinfo_dbg_list = {
  125. "blender_with_common_parent.fbx.assetinfo.dbg",
  126. "maya_with_common_parent.fbx.assetinfo.dbg"
  127. }
  128. expected_mesh_nodes = {
  129. "RootNode.AstroVAC v10.CPU:1.CPU1.CUPCase",
  130. "RootNode.AstroVAC v10.CPU:1.CPU1.CPUUserDoor",
  131. "RootNode.AstroVAC v10.CPU:1.CPU1.CPUCover"
  132. }
  133. for assetinfo_dbg in assetinfo_dbg_list:
  134. asset_folder_name = 'MeshGroups'
  135. assetinfo_dbg_file = self.compute_asset_debug_file(workspace, asset_processor, assetinfo_dbg, asset_folder_name, ap_setup_fixture)
  136. assert assetinfo_dbg_file, "The debug assetinfo file is missing"
  137. with open(assetinfo_dbg_file, 'r') as file :
  138. filedata = file.read()
  139. dgb_dict = json.loads(filedata)
  140. found_mesh_nodes = set()
  141. # Check that each default mesh group contains one of the expected mesh nodes in its selected
  142. # node list and has the rest of the expected mesh nodes in its unselected node list
  143. groups = dgb_dict['values']
  144. for group in groups:
  145. if 'MeshGroup' in group.get('$type', str()) and group.get('name', str()).startswith('default_'):
  146. selection_lists = group.get('nodeSelectionList')
  147. selected_node_list = selection_lists.get('selectedNodes', [])
  148. assert len(selected_node_list) == 1, "Selected node list does not contain exactly one mesh"
  149. current_set = expected_mesh_nodes.copy()
  150. current_set.remove(selected_node_list[0])
  151. unselected_node_list = selection_lists.get('unselectedNodes', [])
  152. assert current_set == set(unselected_node_list), "Unselected node list does not contain the correct meshes"
  153. found_mesh_nodes.add(selected_node_list[0])
  154. assert found_mesh_nodes == expected_mesh_nodes, "Not all expected mesh groups found"
  155. def test_ProcessAssetScript_SimpleFbx_HasExpectedProducts(self, workspace, ap_setup_fixture, asset_processor):
  156. # This tests all of the entry points a scene builder can script:
  157. # - OnUpdateManifest() returns a JSON string to use as the scene manifest; this must succeed to continue the scene pipe
  158. # - OnPrepareForExport() returns an export product list; this test should produce a simple.fbx.fake_asset export product
  159. asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "script_basics")
  160. result, _ = asset_processor.batch_process(extra_params=self.asset_processor_extra_params)
  161. assert result, "AP Batch failed"
  162. expected_product_list = [
  163. "simple.fbx.fake_asset"
  164. ]
  165. missing_assets, _ = utils.compare_assets_with_cache(expected_product_list, asset_processor.project_test_cache_folder())
  166. assert not missing_assets, f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}'