| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- """
- Copyright (c) Contributors to the Open 3D Engine Project.
- For complete copyright and license terms please see the LICENSE at the root of this distribution.
- SPDX-License-Identifier: Apache-2.0 OR MIT
- """
- import os
- import pathlib
- import shutil
- import sys
- import time
- import azlmbr.asset as azasset
- import azlmbr.atom
- import azlmbr.atomtools
- import azlmbr.bus as bus
- import azlmbr.math as azmath
- import azlmbr.paths
- import ly_test_tools.environment.file_system as fs
- from Atom.atom_utils.atom_constants import (
- AtomToolsDocumentRequestBusEvents, AtomToolsDocumentSystemRequestBusEvents, AtomToolsMainWindowRequestBusEvents,
- EntityPreviewViewportSettingsRequestBusEvents)
- MATERIAL_TYPES_PATH = pathlib.PurePath(azlmbr.paths.resolve_path(
- "@gemroot:Atom_Feature_Common@/Assets/Materials/Types"))
- MATERIALCANVAS_GRAPH_PATH = pathlib.PurePath(azlmbr.paths.resolve_path(
- "@gemroot:MaterialCanvas@/Assets/MaterialCanvas/TestData"))
- TEST_DATA_MATERIALS_PATH = pathlib.PurePath(azlmbr.paths.resolve_path(
- "@gemroot:Atom_TestData@/Assets/TestData/Materials"))
- VIEWPORT_LIGHTING_PRESETS_PATH = pathlib.PurePath(azlmbr.paths.resolve_path(
- "@gemroot:MaterialEditor@/Assets/MaterialEditor/LightingPresets"))
- VIEWPORT_MODELS_PRESETS_PATH = pathlib.PurePath(azlmbr.paths.resolve_path(
- "@gemroot:MaterialEditor@/Assets/MaterialEditor/ViewportModels"))
- def is_close(
- actual: int or float or object, expected: int or float or object, buffer: float = sys.float_info.min) -> bool:
- """
- Obtains the absolute value using an actual value and expected value then compares it to the buffer.
- The result of this comparison is returned as a boolean.
- :param actual: actual value
- :param expected: expected value
- :param buffer: acceptable variation from expected
- :return: bool
- """
- return abs(actual - expected) < buffer
- def compare_colors(color1: azlmbr.math.Color, color2: azlmbr.math.Color, buffer: float = 0.00001) -> bool:
- """
- Compares the red, green and blue properties of a color allowing a slight variance of buffer.
- :param color1: azlmbr.math.Color first value to compare.
- :param color2: azlmbr.math.Color second value to compare.
- :param buffer: allowed variance in individual color value.
- :return: boolean representing whether the colors are close (True) or too different (False).
- """
- return (
- is_close(color1.r, color2.r, buffer)
- and is_close(color1.g, color2.g, buffer)
- and is_close(color1.b, color2.b, buffer)
- )
- def verify_one_material_document_opened(
- material_document_ids_list: [azlmbr.math.Uuid], opened_document_index: int) -> bool:
- """
- Validation helper to verify if the document at opened_document_index value in the material_document_ids list
- is the only opened document.
- Returns True on success, False on failure.
- :param material_document_ids_list: List of material document IDs used for making the document opened check.
- :param opened_document_index: Index number of the one material document that should be open
- :return: bool
- """
- if not is_document_open(material_document_ids_list[opened_document_index]):
- return False
- material_document_ids_verification_list = material_document_ids_list.copy()
- material_document_ids_verification_list.pop(opened_document_index)
- for closed_material_document_id in material_document_ids_verification_list:
- if is_document_open(closed_material_document_id):
- return False
- return True
- def open_document(file_path: str) -> azlmbr.math.Uuid:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.OPEN_DOCUMENT, file_path)
- def is_document_open(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.IS_DOCUMENT_OPEN, document_id)
- def save_document(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.SAVE_DOCUMENT, document_id)
- def save_document_as_copy(document_id: azlmbr.math.Uuid, target_path: str) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.SAVE_DOCUMENT_AS_COPY, document_id, target_path)
- def save_document_as_child(document_id: azlmbr.math.Uuid, target_path: str) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.SAVE_DOCUMENT_AS_CHILD, document_id, target_path)
- def save_all_documents() -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.SAVE_ALL_DOCUMENTS)
- def close_document(document_id: azlmbr.math.Uuid):
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.CLOSE_DOCUMENT, document_id)
- def close_all_documents() -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.CLOSE_ALL_DOCUMENTS)
- def close_all_except_selected(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentSystemRequestBus(
- bus.Broadcast, AtomToolsDocumentSystemRequestBusEvents.CLOSE_ALL_DOCUMENTS_EXCEPT, document_id)
- def is_pane_visible(pane_name: str) -> bool:
- return azlmbr.atomtools.AtomToolsMainWindowRequestBus(
- bus.Broadcast, AtomToolsMainWindowRequestBusEvents.IS_DOCK_WIDGET_VISIBLE, pane_name)
- def set_pane_visibility(pane_name: str, value: bool) -> None:
- azlmbr.atomtools.AtomToolsMainWindowRequestBus(
- bus.Broadcast, AtomToolsMainWindowRequestBusEvents.SET_DOCK_WIDGET_VISIBLE, pane_name, value)
- def load_lighting_preset_by_asset_id(asset_id: azlmbr.math.Uuid) -> bool:
- """
- Takes in an asset ID and attempts to select the lighting background in the viewporet dropdown using that ID.
- Returns True if it successfully changes it, False otherwise.
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.LOAD_LIGHTING_PRESET_BY_ASSET_ID, asset_id)
- def load_lighting_preset_by_path(asset_path: str) -> bool:
- """
- Takes in an asset path and attempts to select the lighting background in the viewport dropdown using that path.
- Returns True if it successfully changes it, False otherwise.
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.LOAD_LIGHTING_PRESET, asset_path)
- def get_last_lighting_preset_asset_id() -> azlmbr.math.Uuid:
- """
- Returns the asset ID of the currently selected viewport lighting background.
- Example return values observed when selecting different lighting backgrounds from the viewport dropdown:
- {B9AA460C-622D-5F38-A02C-C0BCB0B65D96}:0
- {C4978B46-D509-5B71-86A1-09D8CC051DC4}:0
- {AB7FA1BA-7207-5333-BDD6-69C3F5B7A410}:0
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.GET_LAST_LIGHTING_PRESET_ASSET_ID)
- def get_last_lighting_preset_path() -> str:
- """
- Returns the full path to the currently selected viewport lighting background.
- Example return values observed when selecting different lighting backgrounds from the viewport dropdown:
- "C:/git/o3de/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/LightingPresets/neutral_urban.lightingpreset.azasset"
- "C:/git/o3de/Gems/Atom/Feature/Common/Assets/LightingPresets/LowContrast/artist_workshop.lightingpreset.azasset"
- "@gemroot:Atom_TestData@/Assets/TestData/LightingPresets/beach_parking.lightingpreset.azasset"
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast,
- EntityPreviewViewportSettingsRequestBusEvents.GET_LAST_LIGHTING_PRESET_PATH_WITHOUT_ALIAS)
- def get_last_model_preset_path() -> str:
- """
- Returns the full path to the currently selected viewport model.
- Example return values observed when selecting different models from the vioewport dropdown:
- "C:/git/o3de/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/Shaderball.modelpreset.azasset"
- "C:/git/o3de/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/Cone.modelpreset.azasset"
- "C:/git/o3de/Gems/Atom/Tools/MaterialEditor/Assets/MaterialEditor/ViewportModels/BeveledCone.modelpreset.azasset"
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.GET_LAST_MODEL_PRESET_PATH_WITHOUT_ALIAS)
- def get_last_model_preset_asset_id() -> azlmbr.math.Uuid:
- """
- Returns the asset ID of the currently selected viewport model.
- Example return values observed when selecting different models from the viewport dropdown:
- {9B61FAD7-2717-528C-9EBF-C1324D46ED56}:0
- {56C02199-7B4F-5896-A713-F70E6EBA0726}:0
- {46D4B53F-A900-591B-B4CD-75A79E47749B}:0
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.GET_LAST_MODEL_PRESET_ASSET_ID)
- def set_grid_enabled(value: bool) -> None:
- azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.SET_GRID_ENABLED, value)
- def get_grid_enabled() -> bool:
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.GET_GRID_ENABLED)
- def set_shadow_catcher_enabled(value: bool) -> None:
- azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.SET_SHADOW_CATCHER_ENABLED, value)
- def get_shadow_catcher_enabled() -> bool:
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.GET_SHADOW_CATCHER_ENABLED)
- def load_model_preset_by_asset_id(asset_id: azlmbr.math.Uuid) -> bool:
- """
- Takes in an asset ID and attempts to select the model in the viewport dropdown using that ID.
- Returns True if it successfully changes, False otherwise.
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.LOAD_MODEL_PRESET_BY_ASSET_ID, asset_id)
- def load_model_preset_by_path(asset_path: str) -> bool:
- """
- Takes in an asset path and attempts to select the model in the viewport dropdown using that path.
- Returns True if it successfully changes it, False otherwise.
- """
- return azlmbr.atomtools.EntityPreviewViewportSettingsRequestBus(
- azlmbr.bus.Broadcast, EntityPreviewViewportSettingsRequestBusEvents.LOAD_MODEL_PRESET, asset_path)
- def undo(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, AtomToolsDocumentRequestBusEvents.UNDO, document_id)
- def redo(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, AtomToolsDocumentRequestBusEvents.REDO, document_id)
- def begin_edit(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentRequestBus(
- azlmbr.bus.Event, AtomToolsDocumentRequestBusEvents.BEGIN_EDIT, document_id)
- def end_edit(document_id: azlmbr.math.Uuid) -> bool:
- return azlmbr.atomtools.AtomToolsDocumentRequestBus(
- azlmbr.bus.Event, AtomToolsDocumentRequestBusEvents.END_EDIT, document_id)
- def crash() -> None:
- azlmbr.atomtools.general.crash()
- def exit() -> None:
- """
- Closes the current Atom tools executable.
- :return: None
- """
- azlmbr.atomtools.general.exit()
- def disable_document_message_box_settings() -> None:
- """
- Modifies some registry settings to disable warning and error message boxes that block test progression.
- :return: None
- """
- azlmbr.atomtools.util.SetSettingsValue_bool("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayErrorMessageDialogs", False)
- azlmbr.atomtools.util.SetSettingsValue_bool("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayWarningMessageDialogs", False)
- def disable_graph_compiler_settings() -> None:
- """
- Modifies some registry settings to disable automatic graph compilation on open/edit/save.
- This is because some tests (i.e. main suite) need to avoid file writing/saving during the test run.
- :return: None
- """
- azlmbr.atomtools.util.SetSettingsValue_bool("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnOpen", False)
- azlmbr.atomtools.util.SetSettingsValue_bool("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnSave", False)
- azlmbr.atomtools.util.SetSettingsValue_bool("/O3DE/AtomToolsFramework/GraphCompiler/CompileOnEdit", False)
- def wait_for_condition(function: (), timeout_in_seconds: float = 1.0) -> bool or TypeError:
- """
- Function to run until it returns True or timeout is reached
- the function can have no parameters and
- waiting idle__wait_* is handled here not in the function
- :param function: a function that returns a boolean indicating a desired condition is achieved
- :param timeout_in_seconds: when reached, function execution is abandoned and False is returned
- :return: boolean representing success (True) or failure (False)
- """
- with Timeout(timeout_in_seconds) as t:
- while True:
- try:
- azlmbr.atomtools.general.idle_wait_frames(1)
- except Exception as e:
- print(f"WARNING: Couldn't wait for frame, got Exception : {e}")
- if t.timed_out:
- return False
- ret = function()
- if not isinstance(ret, bool):
- raise TypeError("return value for wait_for_condition function must be a bool")
- if ret:
- return True
- class ShaderAssetTestHelper:
- def copy_file(src_file, src_path, target_file, target_path):
- # type: (str, str, str, str) -> None
- """
- Copies the [src_file] located in [src_path] to the [target_file] located at [target_path].
- Leaves the [target_file] unlocked for reading and writing privileges
- :param src_file: The source file to copy (file name)
- :param src_path: The source file's path
- :param target_file: The target file to copy into (file name)
- :param target_path: The target file's path
- :return: None
- """
- target_file_path = os.path.join(target_path, target_file)
- src_file_path = os.path.join(src_path, src_file)
- if os.path.exists(target_file_path):
- fs.unlock_file(target_file_path)
- shutil.copyfile(src_file_path, target_file_path)
- def copy_tmp_files_in_order(src_directory, file_list, dst_directory, wait_time_in_between=0.0):
- import azlmbr.legacy.general as general
- # type: (str, list, str, float) -> None
- """
- This function assumes that for each file name listed in @file_list
- there's file named "@filename.txt" which the original source file
- but they will be copied with just the @filename (.txt removed).
- """
- for filename in file_list:
- src_name = f"{filename}.txt"
- ShaderAssetTestHelper.copy_file(src_name, src_directory, filename, dst_directory)
- if wait_time_in_between > 0.0:
- print(f"Created {filename} in {dst_directory}")
- general.idle_wait(wait_time_in_between)
- def remove_file(src_file, src_path):
- # type: (str, str) -> None
- """
- Removes the [src_file] located in [src_path].
- :param src_file: The source file to copy (file name)
- :param src_path: The source file's path
- :return: None
- """
- src_file_path = os.path.join(src_path, src_file)
- if os.path.exists(src_file_path):
- fs.unlock_file(src_file_path)
- os.remove(src_file_path)
- def remove_files(directory, file_list):
- for filename in file_list:
- ShaderAssetTestHelper.remove_file(filename, directory)
- def asset_exists(cache_relative_path):
- asset_id = azasset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", cache_relative_path, azmath.Uuid(), False)
- return asset_id.is_valid()
- class Timeout(object):
- """
- Contextual timeout
- """
- def __init__(self, seconds: float) -> None:
- """
- :param seconds: float seconds to allow before timed_out is True
- :return: None
- """
- self.seconds = seconds
- def __enter__(self) -> object:
- self.die_after = time.time() + self.seconds
- return self
- def __exit__(self, type: any, value: any, traceback: str) -> None:
- pass
- @property
- def timed_out(self) -> float:
- return time.time() > self.die_after
- class ScreenshotHelper(object):
- """
- A helper to capture screenshots and wait for them.
- """
- def __init__(self, idle_wait_frames_callback: ()) -> None:
- super().__init__()
- self.done = False
- self.captured_screenshot = False
- self.max_frames_to_wait = 60
- self.idle_wait_frames_callback = idle_wait_frames_callback
- def capture_screenshot_blocking(self, filename: str) -> bool:
- """
- Capture a screenshot and block the execution until the screenshot has been written to the disk.
- """
- self.done = False
- self.captured_screenshot = False
- outcome = azlmbr.atom.FrameCaptureRequestBus(azlmbr.bus.Broadcast, "CaptureScreenshot", filename)
- if outcome.IsSuccess():
- self.handler = azlmbr.atom.FrameCaptureNotificationBusHandler()
- self.handler.connect(outcome.GetValue())
- self.handler.add_callback("OnCaptureFinished", self.on_screenshot_captured)
- self.wait_until_screenshot()
- print("Screenshot taken.")
- else:
- print(f"Screenshot failed. {outcome.GetError().error_message}")
- return self.captured_screenshot
- def on_screenshot_captured(self, parameters: tuple[any, any]) -> None:
- """
- Sets the value for self.captured_screenshot based on the values in the parameters tuple.
- :param parameters: A tuple of 2 values that indicates if the capture was successful.
- """
- if parameters[0]:
- print(f"screenshot saved: {parameters[1]}")
- self.captured_screenshot = True
- else:
- print(f"screenshot failed: {parameters[1]}")
- self.done = True
- self.handler.disconnect()
- def wait_until_screenshot(self) -> None:
- frames_waited = 0
- while self.done is False:
- self.idle_wait_frames_callback(1)
- if frames_waited > self.max_frames_to_wait:
- print("timeout while waiting for the screenshot to be written")
- self.handler.disconnect()
- break
- else:
- frames_waited = frames_waited + 1
- print(f"(waited {frames_waited} frames)")
- def capture_screenshot(file_path: str) -> bool:
- return ScreenshotHelper(azlmbr.atomtools.general.idle_wait_frames).capture_screenshot_blocking(
- file_path
- )
|