atom_component_helper.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 datetime
  7. import os
  8. import zipfile
  9. class ImageComparisonTestFailure(Exception):
  10. """Custom test failure for failed image comparisons."""
  11. pass
  12. def create_screenshots_archive(screenshot_path):
  13. """
  14. Creates a new zip file archive at archive_path containing all files listed within archive_path.
  15. :param screenshot_path: location containing the files to archive, the zip archive file will also be saved here.
  16. :return: path to the created .zip file archive.
  17. """
  18. files_to_archive = []
  19. # Search for .png and .ppm files to add to the zip archive file.
  20. for (folder_name, sub_folders, file_names) in os.walk(screenshot_path):
  21. for file_name in file_names:
  22. if file_name.lower().endswith(".png") or file_name.lower().endswith(".ppm"):
  23. file_path = os.path.join(folder_name, file_name)
  24. files_to_archive.append(file_path)
  25. # Setup variables for naming the zip archive file.
  26. timestamp = datetime.datetime.now().timestamp()
  27. formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S")
  28. screenshots_zip_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
  29. # Write all of the valid .png and .ppm files to the archive file.
  30. with zipfile.ZipFile(screenshots_zip_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
  31. for file_path in files_to_archive:
  32. file_name = os.path.basename(file_path)
  33. zip_archive.write(file_path, file_name)
  34. return screenshots_zip_file
  35. def golden_images_directory():
  36. """
  37. Uses this file location to return the valid location for golden image files.
  38. :return: The path to the golden_images directory, but raises an IOError if the golden_images directory is missing.
  39. """
  40. current_file_directory = os.path.join(os.path.dirname(__file__))
  41. golden_images_dir = os.path.join(current_file_directory, '..', 'golden_images')
  42. if not os.path.exists(golden_images_dir):
  43. raise IOError(
  44. f'golden_images" directory was not found at path "{golden_images_dir}"'
  45. f'Please add a "golden_images" directory inside: "{current_file_directory}"'
  46. )
  47. return golden_images_dir
  48. def compare_screenshot_to_golden_image(screenshot_directory, test_screenshot, golden_image):
  49. """
  50. Compares the test_screenshot to the golden_images and return the comparison result.
  51. :param screenshot_directory: path to the directory containing screenshots.
  52. :param test_screenshot: the screenshot file name.
  53. :param golden_image: the golden image file name.
  54. """
  55. from Atom.atom_utils.screenshot_utils import compare_screenshots
  56. golden_images_dir = golden_images_directory()
  57. return compare_screenshots(f"{screenshot_directory}/{test_screenshot}", f"{golden_images_dir}/{golden_image}")
  58. def initial_viewport_setup(screen_width=1280, screen_height=720):
  59. """
  60. For setting up the initial viewport resolution to expected default values before running a screenshot test.
  61. Defaults to 1280 x 720 resolution (in pixels).
  62. :param screen_width: Width in pixels to set the viewport width size to.
  63. :param screen_height: Height in pixels to set the viewport height size to.
  64. :return: None
  65. """
  66. import azlmbr.legacy.general as general
  67. general.set_viewport_size(screen_width, screen_height)
  68. general.idle_wait_frames(1)
  69. general.update_viewport()
  70. general.idle_wait_frames(1)
  71. def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit_game_tuple, timeout_in_seconds=4):
  72. """
  73. Enters game mode, takes a screenshot named screenshot_name (must include file extension), and exits game mode.
  74. :param screenshot_name: string representing the name of the screenshot file, including file extension.
  75. :param enter_game_tuple: tuple where the 1st string is success & 2nd string is failure for entering the game.
  76. :param exit_game_tuple: tuple where the 1st string is success & 2nd string is failure for exiting the game.
  77. :param timeout_in_seconds: int or float seconds to wait for entering/exiting game mode.
  78. :return: None
  79. """
  80. import azlmbr.legacy.general as general
  81. from editor_python_test_tools.utils import TestHelper, Report
  82. from Atom.atom_utils.screenshot_utils import ScreenshotHelper
  83. screenshot_helper = ScreenshotHelper(general.idle_wait_frames)
  84. TestHelper.enter_game_mode(enter_game_tuple)
  85. TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
  86. screenshot_helper.prepare_viewport_for_screenshot(1920, 1080)
  87. success_screenshot = TestHelper.wait_for_condition(
  88. function=lambda: screenshot_helper.capture_screenshot_blocking(screenshot_name),
  89. timeout_in_seconds=timeout_in_seconds)
  90. Report.result(("Screenshot taken", "Screenshot failed to be taken"), success_screenshot)
  91. TestHelper.exit_game_mode(exit_game_tuple)
  92. TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
  93. def create_basic_atom_rendering_scene():
  94. """
  95. Sets up a new scene inside the Editor for testing Atom rendering GPU output.
  96. Setup: Deletes all existing entities before creating the scene.
  97. The created scene includes:
  98. 1. "Default Level" entity that holds all of the other entities.
  99. 2. "Grid" entity: Contains a Grid component.
  100. 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
  101. 4. "Ground Plane" entity: Contains Material & Mesh components.
  102. 5. "Directional Light" entity: Contains Directional Light component.
  103. 6. "Sphere" entity: Contains Material & Mesh components.
  104. 7. "Camera" entity: Contains Camera component.
  105. :return: None
  106. """
  107. import azlmbr.math as math
  108. import azlmbr.paths
  109. from editor_python_test_tools.asset_utils import Asset
  110. from editor_python_test_tools.editor_entity_utils import EditorEntity
  111. from Atom.atom_utils.atom_constants import AtomComponentProperties
  112. DEGREE_RADIAN_FACTOR = 0.0174533
  113. # Setup: Deletes all existing entities before creating the scene.
  114. search_filter = azlmbr.entity.SearchFilter()
  115. all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter)
  116. azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities)
  117. # 1. "Default Level" entity that holds all of the other entities.
  118. default_level_entity_name = "Default Level"
  119. default_level_entity = EditorEntity.create_editor_entity_at(math.Vector3(0.0, 0.0, 0.0), default_level_entity_name)
  120. # 2. "Grid" entity: Contains a Grid component.
  121. grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid(), default_level_entity.id)
  122. grid_component = grid_entity.add_component(AtomComponentProperties.grid())
  123. secondary_grid_spacing_value = 1.0
  124. grid_component.set_component_property_value(
  125. AtomComponentProperties.grid('Secondary Grid Spacing'), secondary_grid_spacing_value)
  126. # 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components.
  127. global_skylight_entity = EditorEntity.create_editor_entity(
  128. AtomComponentProperties.global_skylight(), default_level_entity.id)
  129. hdri_skybox_component = global_skylight_entity.add_component(AtomComponentProperties.hdri_skybox())
  130. global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight())
  131. global_skylight_image_asset_path = os.path.join("lightingpresets", "default_iblskyboxcm.exr.streamingimage")
  132. global_skylight_image_asset = Asset.find_asset_by_path(global_skylight_image_asset_path, False)
  133. hdri_skybox_component.set_component_property_value(
  134. AtomComponentProperties.hdri_skybox('Cubemap Texture'), global_skylight_image_asset.id)
  135. global_skylight_diffuse_image_asset_path = os.path.join(
  136. "lightingpresets", "default_iblskyboxcm_ibldiffuse.exr.streamingimage")
  137. global_skylight_diffuse_image_asset = Asset.find_asset_by_path(global_skylight_diffuse_image_asset_path, False)
  138. global_skylight_component.set_component_property_value(
  139. AtomComponentProperties.global_skylight('Diffuse Image'), global_skylight_diffuse_image_asset.id)
  140. global_skylight_specular_image_asset_path = os.path.join(
  141. "lightingpresets", "default_iblskyboxcm_iblspecular.exr.streamingimage")
  142. global_skylight_specular_image_asset = Asset.find_asset_by_path(
  143. global_skylight_specular_image_asset_path, False)
  144. global_skylight_component.set_component_property_value(
  145. AtomComponentProperties.global_skylight('Specular Image'), global_skylight_specular_image_asset.id)
  146. # 4. "Ground Plane" entity: Contains Material & Mesh components.
  147. ground_plane_name = "Ground Plane"
  148. ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id)
  149. ground_plane_material_component = ground_plane_entity.add_component(AtomComponentProperties.material())
  150. ground_plane_entity.set_local_uniform_scale(32.0)
  151. ground_plane_mesh_component = ground_plane_entity.add_component(AtomComponentProperties.mesh())
  152. ground_plane_mesh_asset_path = os.path.join("testdata", "objects", "plane.fbx.azmodel")
  153. ground_plane_mesh_asset = Asset.find_asset_by_path(ground_plane_mesh_asset_path, False)
  154. ground_plane_mesh_component.set_component_property_value(
  155. AtomComponentProperties.mesh('Model Asset'), ground_plane_mesh_asset.id)
  156. ground_plane_material_asset_path = os.path.join("materials", "presets", "pbr", "metal_chrome.azmaterial")
  157. ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False)
  158. ground_plane_material_component.set_component_property_value(
  159. AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id)
  160. # 5. "Directional Light" entity: Contains Directional Light component.
  161. directional_light_entity = EditorEntity.create_editor_entity_at(
  162. math.Vector3(0.0, 0.0, 10.0), AtomComponentProperties.directional_light(), default_level_entity.id)
  163. directional_light_entity.add_component(AtomComponentProperties.directional_light())
  164. directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
  165. directional_light_entity.set_local_rotation(directional_light_entity_rotation)
  166. # 6. "Sphere" entity: Contains Material & Mesh components.
  167. sphere_entity = EditorEntity.create_editor_entity_at(
  168. math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id)
  169. sphere_mesh_component = sphere_entity.add_component(AtomComponentProperties.mesh())
  170. sphere_mesh_asset_path = os.path.join("models", "sphere.fbx.azmodel")
  171. sphere_mesh_asset = Asset.find_asset_by_path(sphere_mesh_asset_path, False)
  172. sphere_mesh_component.set_component_property_value(
  173. AtomComponentProperties.mesh('Model Asset'), sphere_mesh_asset.id)
  174. sphere_material_component = sphere_entity.add_component(AtomComponentProperties.material())
  175. sphere_material_asset_path = os.path.join("materials", "presets", "pbr", "metal_brass_polished.azmaterial")
  176. sphere_material_asset = Asset.find_asset_by_path(sphere_material_asset_path, False)
  177. sphere_material_component.set_component_property_value(
  178. AtomComponentProperties.material('Material Asset'), sphere_material_asset.id)
  179. # 7. "Camera" entity: Contains Camera component.
  180. camera_entity = EditorEntity.create_editor_entity_at(
  181. math.Vector3(5.5, -12.0, 9.0), AtomComponentProperties.camera(), default_level_entity.id)
  182. camera_component = camera_entity.add_component(AtomComponentProperties.camera())
  183. camera_entity_rotation = math.Vector3(
  184. DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0)
  185. camera_entity.set_local_rotation(camera_entity_rotation)
  186. camera_fov_value = 60.0
  187. camera_component.set_component_property_value(AtomComponentProperties.camera('Field of view'), camera_fov_value)
  188. azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)