diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py index 381f266fab..23fb249761 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py @@ -89,7 +89,7 @@ class TestAllComponentsIndepthTests(object): level_creation_expected_lines = [ "Viewport is set to the expected size: True", - "Basic level created" + "Exited game mode" ] unexpected_lines = [ "Trace::Assert", @@ -189,8 +189,8 @@ class TestPerformanceBenchmarkSuite(object): "Benchmark metadata captured.", "Pass timestamps captured.", "CPU frame time captured.", - "Capturing complete.", - "Captured data successfully." + "Captured data successfully.", + "Exited game mode" ] unexpected_lines = [ diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index 9bb7f9c50e..ad45e51080 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -14,10 +14,6 @@ import editor_python_test_tools.hydra_test_utils as hydra logger = logging.getLogger(__name__) TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") - -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) -@pytest.mark.parametrize("level", ["auto_test"]) class TestAtomEditorComponentsSandbox(object): # It requires at least one test diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py index fbd9f3459a..de7c9d2326 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_AtomFeatureIntegrationBenchmark.py @@ -90,7 +90,6 @@ def run(): benchmarker.capture_cpu_frame_time(i) general.exit_game_mode() helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0) - general.log("Capturing complete.") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py index 93e78a7c0a..9515712583 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py @@ -215,7 +215,6 @@ def run(): ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{'AtomBasicLevelSetup'}.ppm") general.exit_game_mode() helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0) - general.log("Basic level created") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index 2ca7f7ea2b..fd3222ba83 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -58,3 +58,6 @@ add_subdirectory(smoke) ## AWS ## add_subdirectory(AWS) + +## Integration tests for editor testing framework ## +add_subdirectory(editor_test_testing) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py index 2d8b124125..10a6ab1ef4 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py @@ -9,6 +9,7 @@ from __future__ import annotations from collections import Counter from collections import deque from os import path +from pathlib import Path from PySide2 import QtWidgets @@ -20,6 +21,10 @@ from editor_python_test_tools.utils import Report import azlmbr.entity as entity import azlmbr.bus as bus +import azlmbr.components as components +import azlmbr.editor as editor +import azlmbr.globals +import azlmbr.math as math import azlmbr.prefab as prefab import editor_python_test_tools.pyside_utils as pyside_utils @@ -57,26 +62,46 @@ class PrefabInstance: def __hash__(self): return hash(self.container_entity.id) - """ - See if this instance is valid to be used with other prefab operations. - :return: Whether the target instance is valid or not. - """ def is_valid(self) -> bool: + """ + See if this instance is valid to be used with other prefab operations. + :return: Whether the target instance is valid or not. + """ return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs - """ - Reparent this instance to target parent entity. - The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs. - :param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next. - """ + def has_editor_prefab_component(self) -> bool: + """ + Check if the instance's container entity contains EditorPrefabComponent. + :return: Whether the container entity of target instance has EditorPrefabComponent in it or not. + """ + return editor.EditorComponentAPIBus(bus.Broadcast, "HasComponentOfType", self.container_entity.id, azlmbr.globals.property.EditorPrefabComponentTypeId) + + def is_at_position(self, expected_position): + """ + Check if the instance's container entity is at expected position given. + :return: Whether the container entity of target instance is at expected position or not. + """ + actual_position = components.TransformBus(bus.Event, "GetWorldTranslation", self.container_entity.id) + is_at_position = actual_position.IsClose(expected_position) + + if not is_at_position: + Report.info(f"Prefab Instance Container Entity '{self.container_entity.id.ToString()}'\'s expected position: {expected_position.ToString()}, actual position: {actual_position.ToString()}") + + return is_at_position + async def ui_reparent_prefab_instance(self, parent_entity_id: EntityId): + """ + Reparent this instance to target parent entity. + The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs. + :param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next. + """ container_entity_id_before_reparent = self.container_entity.id original_parent = EditorEntity(self.container_entity.get_parent_id()) - original_parent_before_reparent_children_ids = set(original_parent.get_children_ids()) + original_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()} new_parent = EditorEntity(parent_entity_id) - new_parent_before_reparent_children_ids = set(new_parent.get_children_ids()) + new_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()} pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id)) pyside_utils.run_soon(lambda: wait_for_propagation()) @@ -90,23 +115,28 @@ class PrefabInstance: except pyside_utils.EventLoopTimeoutException: pass - original_parent_after_reparent_children_ids = set(original_parent.get_children_ids()) + original_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()} assert len(original_parent_after_reparent_children_ids) == len(original_parent_before_reparent_children_ids) - 1, \ "The children count of the Prefab Instance's original parent should be decreased by 1." assert not container_entity_id_before_reparent in original_parent_after_reparent_children_ids, \ "This Prefab Instance is still a child entity of its original parent entity." - new_parent_after_reparent_children_ids = set(new_parent.get_children_ids()) + new_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()} assert len(new_parent_after_reparent_children_ids) == len(new_parent_before_reparent_children_ids) + 1, \ "The children count of the Prefab Instance's new parent should be increased by 1." - container_entity_id_after_reparent = set(new_parent_after_reparent_children_ids).difference(new_parent_before_reparent_children_ids).pop() + after_before_diff = set(new_parent_after_reparent_children_ids.keys()).difference(set(new_parent_before_reparent_children_ids.keys())) + container_entity_id_after_reparent = new_parent_after_reparent_children_ids[after_before_diff.pop()] reparented_container_entity = EditorEntity(container_entity_id_after_reparent) reparented_container_entity_parent_id = reparented_container_entity.get_parent_id() has_correct_parent = reparented_container_entity_parent_id.ToString() == parent_entity_id.ToString() assert has_correct_parent, "Prefab Instance reparented is *not* under the expected parent entity" + current_instance_prefab = Prefab.get_prefab(self.prefab_file_name) + current_instance_prefab.instances.remove(self) + self.container_entity = reparented_container_entity + current_instance_prefab.instances.add(self) # This is a helper class which contains some of the useful information about a prefab template. class Prefab: @@ -117,31 +147,32 @@ class Prefab: self.file_path: str = get_prefab_file_path(file_path) self.instances: set[PrefabInstance] = set() - """ - Check if a prefab is ready to be used to generate its instances. - :param file_path: A unique file path of the target prefab. - :return: Whether the target prefab is loaded or not. - """ @classmethod def is_prefab_loaded(cls, file_path: str) -> bool: + """ + Check if a prefab is ready to be used to generate its instances. + :param file_path: A unique file path of the target prefab. + :return: Whether the target prefab is loaded or not. + """ return file_path in Prefab.existing_prefabs - """ - Check if a prefab exists in the directory for files of prefab tests. - :param file_name: A unique file name of the target prefab. - :return: Whether the target prefab exists or not. - """ + @classmethod def prefab_exists(cls, file_path: str) -> bool: + """ + Check if a prefab exists in the directory for files of prefab tests. + :param file_name: A unique file name of the target prefab. + :return: Whether the target prefab exists or not. + """ return path.exists(get_prefab_file_path(file_path)) - """ - Return a prefab which can be used immediately. - :param file_name: A unique file name of the target prefab. - :return: The prefab with given file name. - """ @classmethod def get_prefab(cls, file_name: str) -> Prefab: + """ + Return a prefab which can be used immediately. + :param file_name: A unique file name of the target prefab. + :return: The prefab with given file name. + """ assert file_name, "Received an empty file_name" if Prefab.is_prefab_loaded(file_name): return Prefab.existing_prefabs[file_name] @@ -151,15 +182,15 @@ class Prefab: Prefab.existing_prefabs[file_name] = Prefab(file_name) return new_prefab - """ - Create a prefab in memory and return it. The very first instance of this prefab will also be created. - :param entities: The entities that should form the new prefab (along with their descendants). - :param file_name: A unique file name of new prefab. - :param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name. - :return: Created Prefab object and the very first PrefabInstance object owned by the prefab. - """ @classmethod def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> tuple(Prefab, PrefabInstance): + """ + Create a prefab in memory and return it. The very first instance of this prefab will also be created. + :param entities: The entities that should form the new prefab (along with their descendants). + :param file_name: A unique file name of new prefab. + :param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name. + :return: Created Prefab object and the very first PrefabInstance object owned by the prefab. + """ assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists" new_prefab = Prefab(file_name) @@ -169,6 +200,9 @@ class Prefab: container_entity_id = create_prefab_result.GetValue() container_entity = EditorEntity(container_entity_id) + children_entity_ids = container_entity.get_children_ids() + + assert len(children_entity_ids) == len(entities), f"Entity count of created prefab instance does *not* match the count of given entities." if prefab_instance_name: container_entity.set_name(prefab_instance_name) @@ -180,12 +214,12 @@ class Prefab: Prefab.existing_prefabs[file_name] = new_prefab return new_prefab, new_prefab_instance - """ - Remove target prefab instances. - :param prefab_instances: Instances to be removed. - """ @classmethod def remove_prefabs(cls, prefab_instances: list[PrefabInstance]): + """ + Remove target prefab instances. + :param prefab_instances: Instances to be removed. + """ entity_ids_to_remove = [] entity_id_queue = [prefab_instance.container_entity for prefab_instance in prefab_instances] while entity_id_queue: @@ -212,15 +246,89 @@ class Prefab: instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name) instance_deleted_prefab.instances.remove(instance) instance = PrefabInstance() - - """ - Instantiate an instance of this prefab. - :param parent_entity: The entity the prefab should be a child of in the transform hierarchy. - :param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name. - :param prefab_position: The position in world space the prefab should be instantiated in. - :return: Instantiated PrefabInstance object owned by this prefab. - """ + + @classmethod + def duplicate_prefabs(cls, prefab_instances: list[PrefabInstance]): + """ + Duplicate target prefab instances. + :param prefab_instances: Instances to be duplicated. + :return: PrefabInstance objects of given prefab instances' duplicates. + """ + assert prefab_instances, "Input list of prefab instances should *not* be empty." + + common_parent = EditorEntity(prefab_instances[0].container_entity.get_parent_id()) + common_parent_children_ids_before_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()]) + + container_entity_ids = [prefab_instance.container_entity.id for prefab_instance in prefab_instances] + + duplicate_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DuplicateEntitiesInInstance', container_entity_ids) + assert duplicate_prefab_result.IsSuccess(), f"Prefab operation 'DuplicateEntitiesInInstance' failed. Error: {duplicate_prefab_result.GetError()}" + + wait_for_propagation() + + duplicate_container_entity_ids = duplicate_prefab_result.GetValue() + common_parent_children_ids_after_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()]) + + assert set([container_entity_id.ToString() for container_entity_id in container_entity_ids]).issubset(common_parent_children_ids_after_duplicate), \ + "Provided prefab instances are *not* the children of their common parent anymore after duplication." + assert common_parent_children_ids_before_duplicate.issubset(common_parent_children_ids_after_duplicate), \ + "Some children of provided entities' common parent before duplication are *not* the children of the common parent anymore after duplication." + assert len(common_parent_children_ids_after_duplicate) == len(common_parent_children_ids_before_duplicate) + len(prefab_instances), \ + "The children count of the given prefab instances' common parent entity is *not* increased to the expected number." + assert EditorEntity(duplicate_container_entity_ids[0]).get_parent_id().ToString() == common_parent.id.ToString(), \ + "Provided prefab instances' parent should be the same as duplicates' parent." + + duplicate_instances = [] + for duplicate_container_entity_id in duplicate_container_entity_ids: + prefab_file_path = prefab.PrefabPublicRequestBus(bus.Broadcast, 'GetOwningInstancePrefabPath', duplicate_container_entity_id) + assert prefab_file_path, "Returned file path should *not* be empty." + + prefab_file_name = Path(prefab_file_path).stem + duplicate_instance_prefab = Prefab.get_prefab(prefab_file_name) + duplicate_instance = PrefabInstance(prefab_file_path, EditorEntity(duplicate_container_entity_id)) + duplicate_instance_prefab.instances.add(duplicate_instance) + duplicate_instances.append(duplicate_instance) + + return duplicate_instances + + @classmethod + def detach_prefab(cls, prefab_instance: PrefabInstance): + """ + Detach target prefab instance. + :param prefab_instances: Instance to be detached. + """ + parent = EditorEntity(prefab_instance.container_entity.get_parent_id()) + parent_children_ids_before_detach = set([child_id.ToString() for child_id in parent.get_children_ids()]) + + assert prefab_instance.has_editor_prefab_component(), f"Container entity should have EditorPrefabComponent before detachment." + + detach_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DetachPrefab', prefab_instance.container_entity.id) + assert detach_prefab_result.IsSuccess(), f"Prefab operation 'DetachPrefab' failed. Error: {detach_prefab_result.GetError()}" + + assert not prefab_instance.has_editor_prefab_component(), f"Container entity should *not* have EditorPrefabComponent after detachment." + + parent_children_ids_after_detach = set([child_id.ToString() for child_id in parent.get_children_ids()]) + + assert prefab_instance.container_entity.id.ToString() in parent_children_ids_after_detach, \ + "Target prefab instance's container entity id should still exists after the detachment and before the propagation." + + assert len(parent_children_ids_after_detach) == len(parent_children_ids_before_detach), \ + "Parent entity should still keep the same amount of children entities." + + wait_for_propagation() + + instance_owner_prefab = Prefab.get_prefab(prefab_instance.prefab_file_name) + instance_owner_prefab.instances.remove(prefab_instance) + prefab_instance = PrefabInstance() + def instantiate(self, parent_entity: EditorEntity=None, name: str=None, prefab_position: Vector3=Vector3()) -> PrefabInstance: + """ + Instantiate an instance of this prefab. + :param parent_entity: The entity the prefab should be a child of in the transform hierarchy. + :param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name. + :param prefab_position: The position in world space the prefab should be instantiated in. + :return: Instantiated PrefabInstance object owned by this prefab. + """ parent_entity_id = parent_entity.id if parent_entity is not None else EntityId() instantiate_prefab_result = prefab.PrefabPublicRequestBus( @@ -240,4 +348,6 @@ class Prefab: assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation." self.instances.add(new_prefab_instance) + assert new_prefab_instance.is_at_position(prefab_position), "This prefab instance is *not* at expected position." + return new_prefab_instance diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py index a3fd5c741f..61c2816f50 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py @@ -9,20 +9,17 @@ import pytest import sys import ly_test_tools.environment.file_system as fs -from .FileManagement import FileManagement as fm +from .utils.FileManagement import FileManagement as fm sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') -from .base import TestAutomationBase +from base import TestAutomationBase - -@pytest.mark.parametrize("platform", ["win_x64_vs2017"]) -@pytest.mark.parametrize("configuration", ["profile"]) -@pytest.mark.parametrize("spec", ["all"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestUtils(TestAutomationBase): @fm.file_revert("UtilTest_Physmaterial_Editor_TestLibrary.physmaterial", r"AutomatedTesting\Levels\Physics\Physmaterial_Editor_Test") - def test_physmaterial_editor(self, request, workspace, editor): + def test_physmaterial_editor(self, request, workspace, launcher_platform, editor): """ Tests functionality of physmaterial editing utility :param workspace: Fixture containing platform and project detail @@ -35,11 +32,11 @@ class TestUtils(TestAutomationBase): unexpected_lines = ["Assert"] self._run_test(request, workspace, editor, physmaterial_editor_test_module, expected_lines, unexpected_lines) - def test_UtilTest_Tracer_PicksErrorsAndWarnings(self, request, workspace, editor): + def test_UtilTest_Tracer_PicksErrorsAndWarnings(self, request, workspace, launcher_platform, editor): from .utils import UtilTest_Tracer_PicksErrorsAndWarnings as testcase_module self._run_test(request, workspace, editor, testcase_module, [], []) - def test_FileManagement_FindingFiles(self, workspace): + def test_FileManagement_FindingFiles(self, workspace, launcher_platform): """ Tests the functionality of "searching for files" with FileManagement._find_files() :param workspace: ly_test_tools workspace fixture @@ -110,7 +107,7 @@ class TestUtils(TestAutomationBase): find_me_too_path, found_me["FindMeToo.txt"] ) - def test_FileManagement_FileBackup(self, workspace): + def test_FileManagement_FileBackup(self, workspace, launcher_platform): """ Tests the functionality of the file back up system via the FileManagement class :param workspace: ly_test_tools workspace fixture @@ -167,7 +164,7 @@ class TestUtils(TestAutomationBase): del file_map[target_file_path] fm._save_file_map(file_map) - def test_FileManagement_FileRestoration(self, workspace): + def test_FileManagement_FileRestoration(self, workspace, launcher_platform): """ Tests the restore file system via the FileManagement class :param workspace: ly_test_tools workspace fixture @@ -261,7 +258,7 @@ class TestUtils(TestAutomationBase): ["FindMe.txt", "FindMeToo.txt"], parent_path=r"AutomatedTesting\levels\Utils\Managed_files", search_subdirs=True ) @fm.file_override("default.physxconfiguration", "UtilTest_PhysxConfig_Override.physxconfiguration") - def test_UtilTest_Managed_Files(self, request, workspace, editor): + def test_UtilTest_Managed_Files(self, request, workspace, editor, launcher_platform): from .utils import UtilTest_Managed_Files as test_module expected_lines = [] diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py index 30a0055d5a..5337f0669c 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py @@ -47,3 +47,11 @@ class TestAutomation(TestAutomationBase): def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform): from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabBasicWorkflow_CreateReparentAndDetachPrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabBasicWorkflow_CreateReparentAndDetachPrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform): + from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py index 9ae4614d80..bbebd70e04 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py @@ -16,16 +16,15 @@ def PrefabBasicWorkflow_CreateAndDeletePrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Asserts if prefab creation doesn't succeeds + # Creates a prefab from the new entity _, car = Prefab.create_prefab( car_prefab_entities, CAR_PREFAB_FILE_NAME) - # Asserts if prefab deletion fails + # Deletes the prefab instance Prefab.remove_prefabs([car]) if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py new file mode 100644 index 0000000000..2479ae549e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py @@ -0,0 +1,32 @@ +""" +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 +""" + +def PrefabBasicWorkflow_CreateAndDuplicatePrefab(): + + CAR_PREFAB_FILE_NAME = 'car_prefab' + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new entity at the root level + car_entity = EditorEntity.create_editor_entity() + car_prefab_entities = [car_entity] + + # Creates a prefab from the new entity + _, car = Prefab.create_prefab( + car_prefab_entities, CAR_PREFAB_FILE_NAME) + + # Duplicates the prefab instance + Prefab.duplicate_prefabs([car]) + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabBasicWorkflow_CreateAndDuplicatePrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py index f78bbf483d..1cbc591c29 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py @@ -22,24 +22,23 @@ def PrefabBasicWorkflow_CreateAndReparentPrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new car entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Checks for prefab creation passed or not + # Creates a prefab from the car entity _, car = Prefab.create_prefab( car_prefab_entities, CAR_PREFAB_FILE_NAME) - # Creates another new Entity at the root level + # Creates another new wheel entity at the root level wheel_entity = EditorEntity.create_editor_entity() wheel_prefab_entities = [wheel_entity] - # Checks for wheel prefab creation passed or not + # Creates another prefab from the wheel entity _, wheel = Prefab.create_prefab( wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME) - # Checks for prefab reparenting passed or not + # Reparents the wheel prefab instance to the container entity of the car prefab instance await wheel.ui_reparent_prefab_instance(car.container_entity.id) run_test() diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py index 568a2c15b4..cae105a9a9 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py @@ -17,12 +17,11 @@ def PrefabBasicWorkflow_CreatePrefab(): prefab_test_utils.open_base_tests_level() - # Creates a new Entity at the root level - # Asserts if creation didn't succeed + # Creates a new entity at the root level car_entity = EditorEntity.create_editor_entity() car_prefab_entities = [car_entity] - # Checks for prefab creation passed or not + # Creates a prefab from the new entity Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME) if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py new file mode 100644 index 0000000000..bdf77c4bf3 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py @@ -0,0 +1,51 @@ +""" +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 +""" + +def PrefabBasicWorkflow_CreateReparentAndDetachPrefab(): + + CAR_PREFAB_FILE_NAME = 'car_prefab' + WHEEL_PREFAB_FILE_NAME = 'wheel_prefab' + + import editor_python_test_tools.pyside_utils as pyside_utils + + @pyside_utils.wrap_async + async def run_test(): + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new car entity at the root level + car_entity = EditorEntity.create_editor_entity() + car_prefab_entities = [car_entity] + + # Creates a prefab from the car entity + _, car = Prefab.create_prefab( + car_prefab_entities, CAR_PREFAB_FILE_NAME) + + # Creates another new wheel entity at the root level + wheel_entity = EditorEntity.create_editor_entity() + wheel_prefab_entities = [wheel_entity] + + # Creates another prefab from the wheel entity + _, wheel = Prefab.create_prefab( + wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME) + + # Reparents the wheel prefab instance to the container entity of the car prefab instance + await wheel.ui_reparent_prefab_instance(car.container_entity.id) + + # Detaches the wheel prefab instance + Prefab.detach_prefab(wheel) + + run_test() + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(PrefabBasicWorkflow_CreateReparentAndDetachPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py index 1b962d2ca7..a701802cd4 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py @@ -19,9 +19,8 @@ def PrefabBasicWorkflow_InstantiatePrefab(): prefab_test_utils.open_base_tests_level() - # Checks for prefab instantiation passed or not + # Instantiates a new car prefab instance test_prefab = Prefab.get_prefab(EXISTING_TEST_PREFAB_FILE_NAME) - test_instance = test_prefab.instantiate( prefab_position=INSTANTIATED_TEST_PREFAB_POSITION) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py index f82af23023..f865daf41a 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabTestUtils.py @@ -18,20 +18,6 @@ import azlmbr.components as components import azlmbr.entity as entity import azlmbr.legacy.general as general -def check_entity_at_position(entity_id, expected_entity_position): - entity_at_expected_position_result = ( - "entity is at expected position", - "entity is *not* at expected position") - - actual_entity_position = components.TransformBus(bus.Event, "GetWorldTranslation", entity_id) - is_at_position = actual_entity_position.IsClose(expected_entity_position) - Report.result(entity_at_expected_position_result, is_at_position) - - if not is_at_position: - Report.info(f"Entity '{entity_id.ToString()}'\'s expected position: {expected_entity_position.ToString()}, actual position: {actual_entity_position.ToString()}") - - return is_at_position - def check_entity_children_count(entity_id, expected_children_count): entity_children_count_matched_result = ( "Entity with a unique name found", @@ -47,19 +33,6 @@ def check_entity_children_count(entity_id, expected_children_count): return entity_children_count_matched -def get_children_ids_by_name(entity_id, entity_name): - entity = EditorEntity(entity_id) - children_entity_ids = entity.get_children_ids() - - result = [] - for child_entity_id in children_entity_ids: - child_entity = EditorEntity(child_entity_id) - child_entity_name = child_entity.get_name() - if child_entity_name == entity_name: - result.append(child_entity_id) - - return result - def open_base_tests_level(): helper.init_idle() helper.open_level("Prefab", "Base") diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor_test_testing/CMakeLists.txt new file mode 100644 index 0000000000..dbf26d66cd --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/editor_test_testing/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# 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 +# +# + +# This timeouts on jenkins, investigation is needed. Commment for now +# +#if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) +# ly_add_pytest( +# NAME AutomatedTesting::EditorTestTesting +# TEST_SUITE main +# TEST_SERIAL +# PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py +# RUNTIME_DEPENDENCIES +# Legacy::Editor +# AZ::AssetProcessor +# AutomatedTesting.Assets +# COMPONENT +# TestTools +# ) +#endif() diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py index 7c7e063951..15ba6690a1 100644 --- a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py @@ -35,10 +35,11 @@ class TestEditorTest: @classmethod def setup_class(cls): TestEditorTest.args = sys.argv.copy() - build_dir_arg_index = TestEditorTest.args.index("--build-directory") - if build_dir_arg_index < 0: - print("Error: Must pass --build-directory argument in order to run this test") - sys.exit(-2) + build_dir_arg_index = -1 + try: + build_dir_arg_index = TestEditorTest.args.index("--build-directory") + except ValueError as ex: + raise ValueError("Must pass --build-directory argument in order to run this test") TestEditorTest.args[build_dir_arg_index+1] = os.path.abspath(TestEditorTest.args[build_dir_arg_index+1]) TestEditorTest.args.append("-s") diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py index 0461ff2647..1c3652cae9 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py @@ -9,6 +9,7 @@ import os import pytest import ly_test_tools.environment.file_system as file_system +import ly_test_tools._internal.pytest_plugin as internal_plugin from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite @@ -79,6 +80,8 @@ class TestAutomation(EditorTestSuite): class test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(EditorSharedTest): from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module + @pytest.mark.skipif("debug" == os.path.basename(internal_plugin.build_directory), + reason="https://github.com/o3de/o3de/issues/4872") class test_LandscapeCanvas_GraphUpdates_UpdateComponents(EditorSharedTest): from .EditorScripts import GraphUpdates_UpdateComponents as test_module diff --git a/Code/Editor/Controls/ColorGradientCtrl.cpp b/Code/Editor/Controls/ColorGradientCtrl.cpp deleted file mode 100644 index 446e5810c5..0000000000 --- a/Code/Editor/Controls/ColorGradientCtrl.cpp +++ /dev/null @@ -1,923 +0,0 @@ -/* - * 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 - * - */ - - -#include "EditorDefs.h" - -#include "ColorGradientCtrl.h" - -// Qt -#include -#include - -// AzQtComponents -#include - - -#define MIN_TIME_EPSILON 0.01f - -////////////////////////////////////////////////////////////////////////// -CColorGradientCtrl::CColorGradientCtrl(QWidget* parent) - : QWidget(parent) -{ - m_nActiveKey = -1; - m_nHitKeyIndex = -1; - m_nKeyDrawRadius = 3; - m_bTracking = false; - m_pSpline = nullptr; - m_fMinTime = -1; - m_fMaxTime = 1; - m_fMinValue = -1; - m_fMaxValue = 1; - m_fTooltipScaleX = 1; - m_fTooltipScaleY = 1; - m_bNoTimeMarker = true; - m_bLockFirstLastKey = false; - m_bNoZoom = true; - - ClearSelection(); - - m_bSelectedKeys.reserve(0); - - m_fTimeMarker = -10; - - m_grid.zoom.x = 100; - - setMouseTracking(true); -} - -CColorGradientCtrl::~CColorGradientCtrl() -{ -} - - -///////////////////////////////////////////////////////////////////////////// -// QColorGradientCtrl message handlers - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); - - QRect rc(QPoint(0, 0), event->size()); - m_rcGradient = rc; - m_rcGradient.setHeight(m_rcGradient.height() - 11); - //m_rcGradient.DeflateRect(4,4); - - m_grid.rect = m_rcGradient; - if (m_bNoZoom) - { - m_grid.zoom.x = static_cast(m_grid.rect.width()); - } - - m_rcKeys = rc; - m_rcKeys.setTop(m_rcKeys.bottom() - 10); -} - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetZoom(float fZoom) -{ - m_grid.zoom.x = fZoom; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetOrigin(float fOffset) -{ - m_grid.origin.x = fOffset; -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::KeyToPoint(int nKey) -{ - if (nKey >= 0) - { - return TimeToPoint(m_pSpline->GetKeyTime(nKey)); - } - return QPoint(0, 0); -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::TimeToPoint(float time) -{ - return QPoint(m_grid.WorldToClient(Vec2(time, 0)).x(), m_rcGradient.height() / 2); -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::TimeToColor(float time) -{ - ISplineInterpolator::ValueType val; - m_pSpline->Interpolate(time, val); - const AZ::Color col = ValueToColor(val); - return col; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val) -{ - time = XOfsToTime(point.x()); - ColorToValue(TimeToColor(time), val); -} - -////////////////////////////////////////////////////////////////////////// -float CColorGradientCtrl::XOfsToTime(int x) -{ - return m_grid.ClientToWorld(QPoint(x, 0)).x; -} - -////////////////////////////////////////////////////////////////////////// -QPoint CColorGradientCtrl::XOfsToPoint(int x) -{ - return TimeToPoint(XOfsToTime(x)); -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::XOfsToColor(int x) -{ - return TimeToColor(XOfsToTime(x)); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::paintEvent(QPaintEvent* e) -{ - QPainter painter(this); - - QRect rcClient = rect(); - - if (m_pSpline) - { - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - } - { - if (!isEnabled()) - { - painter.setBrush(palette().button()); - painter.drawRect(rcClient); - return; - } - - ////////////////////////////////////////////////////////////////////////// - // Fill keys backgound. - ////////////////////////////////////////////////////////////////////////// - QRect rcKeys = m_rcKeys.intersected(e->rect()); - painter.setBrush(palette().button()); - painter.drawRect(rcKeys); - ////////////////////////////////////////////////////////////////////////// - - //Draw Keys and Curve - if (m_pSpline) - { - DrawGradient(e, &painter); - DrawKeys(e, &painter); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::DrawGradient(QPaintEvent* e, QPainter* painter) -{ - //Draw Curve - // create and select a thick, white pen - painter->setPen(QPen(QColor(128, 255, 128), 1, Qt::SolidLine)); - - const QRect rcClip = e->rect().intersected(m_rcGradient); - const int right = rcClip.left() + rcClip.width(); - for (int x = rcClip.left(); x < right; x++) - { - const AZ::Color col = XOfsToColor(x); - QPen pen(QColor(col.GetR8(), col.GetG8(), col.GetR8(), col.GetA8()), 1, Qt::SolidLine); - painter->setPen(pen); - painter->drawLine(x, m_rcGradient.top(), x, m_rcGradient.top() + m_rcGradient.height()); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::DrawKeys(QPaintEvent* e, QPainter* painter) -{ - if (!m_pSpline) - { - return; - } - - // create and select a white pen - painter->setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); - - QRect rcClip = e->rect(); - - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - - for (int i = 0; i < m_pSpline->GetKeyCount(); i++) - { - float time = m_pSpline->GetKeyTime(i); - QPoint pt = TimeToPoint(time); - - if (pt.x() < rcClip.left() - 8 || pt.x() > rcClip.left() + rcClip.width() + 8) - { - continue; - } - - const AZ::Color clr = TimeToColor(time); - QBrush brush(QColor(clr.GetR8(), clr.GetG8(), clr.GetB8(), clr.GetA8())); - painter->setBrush(brush); - - // Find the midpoints of the top, right, left, and bottom - // of the client area. They will be the vertices of our polygon. - QPoint pts[3]; - pts[0].rx() = pt.x(); - pts[0].ry() = m_rcKeys.top() + 1; - pts[1].rx() = pt.x() - 5; - pts[1].ry() = m_rcKeys.top() + 8; - pts[2].rx() = pt.x() + 5; - pts[2].ry() = m_rcKeys.top() + 8; - painter->drawPolygon(pts, 3); - - if (m_bSelectedKeys[i]) - { - QPen pen(QColor(200, 0, 0), 1, Qt::SolidLine); - QPen oldPen = painter->pen(); - painter->setPen(pen); - painter->drawPolygon(pts, 3); - painter->setPen(oldPen); - } - } - - if (!m_bNoTimeMarker) - { - QPen timePen(QColor(255, 0, 255), 1, Qt::SolidLine); - painter->setPen(timePen); - QPoint pt = TimeToPoint(m_fTimeMarker); - painter->drawLine(pt.x(), m_rcGradient.top() + 1, pt.x(), m_rcGradient.bottom() - 1); - } -} - -void CColorGradientCtrl::UpdateTooltip(QPoint pos) -{ - if (m_nHitKeyIndex >= 0) - { - float time = m_pSpline->GetKeyTime(m_nHitKeyIndex); - ISplineInterpolator::ValueType val; - m_pSpline->GetKeyValue(m_nHitKeyIndex, val); - - AZ::Color col = TimeToColor(time); - int cont_s = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_IN_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2; - int cont_d = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_OUT_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2; - - QString tipText(tr("%1 : %2,%3,%4 [%5,%6]").arg(time * m_fTooltipScaleX, 0, 'f', 2).arg(col.GetR8()).arg(col.GetG8()).arg(col.GetB8()).arg(cont_s).arg(cont_d)); - const QPoint globalPos = mapToGlobal(pos); - QToolTip::showText(mapToGlobal(pos), tipText, this, QRect(globalPos, QSize(1, 1))); - } -} - -///////////////////////////////////////////////////////////////////////////// -//Mouse Message Handlers -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mousePressEvent(QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - OnLButtonDown(event); - } - else if (event->button() == Qt::RightButton) - { - OnRButtonDown(event); - } -} - -void CColorGradientCtrl::OnLButtonDown([[maybe_unused]] QMouseEvent* event) -{ - if (m_bTracking) - { - return; - } - if (!m_pSpline) - { - return; - } - - setFocus(); - - switch (m_hitCode) - { - case HIT_KEY: - StartTracking(); - SetActiveKey(m_nHitKeyIndex); - break; - - /* - case HIT_SPLINE: - { - // Cycle the spline slope of the nearest key. - int flags = m_pSpline->GetKeyFlags(m_nHitKeyIndex); - if (m_nHitKeyDist < 0) - // Toggle left side. - flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT; - if (m_nHitKeyDist > 0) - // Toggle right side. - flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT; - m_pSpline->SetKeyFlags(m_nHitKeyIndex, flags); - m_pSpline->Update(); - - SetActiveKey(-1); - SendNotifyEvent( CLRGRDN_CHANGE ); - if (m_updateCallback) - m_updateCallback(this); - break; - } - */ - - case HIT_NOTHING: - SetActiveKey(-1); - break; - } - update(); -} - - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnRButtonDown([[maybe_unused]] QMouseEvent* event) -{ -} - - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mouseDoubleClickEvent(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (event->button() != Qt::LeftButton) - { - return; - } - - switch (m_hitCode) - { - case HIT_SPLINE: - { - int iIndex = InsertKey(event->pos()); - SetActiveKey(iIndex); - EditKey(iIndex); - - update(); - } - break; - case HIT_KEY: - { - EditKey(m_nHitKeyIndex); - } - break; - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::mouseMoveEvent(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (!m_bTracking) - { - switch (HitTest(event->pos())) - { - case HIT_SPLINE: - { - setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE)); - } break; - case HIT_KEY: - { - setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK)); - } break; - default: - break; - } - } - - if (m_bTracking) - { - TrackKey(event->pos()); - } - - if (m_bTracking || m_nHitKeyIndex >= 0) - { - UpdateTooltip(event->pos()); - } - else - { - QToolTip::hideText(); - } -} - -void CColorGradientCtrl::mouseReleaseEvent(QMouseEvent* event) -{ - if (event->button() == Qt::LeftButton) - { - OnLButtonUp(event); - } - else if (event->button() == Qt::RightButton) - { - OnRButtonUp(event); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnLButtonUp(QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } - - if (m_bTracking) - { - StopTracking(event->pos()); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnRButtonUp([[maybe_unused]] QMouseEvent* event) -{ - if (!m_pSpline) - { - return; - } -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetActiveKey(int nIndex) -{ - ClearSelection(); - - //Activate New Key - if (nIndex >= 0) - { - m_bSelectedKeys[nIndex] = true; - } - m_nActiveKey = nIndex; - update(); - - SendNotifyEvent(CLRGRDN_ACTIVE_KEY_CHANGE); -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetSpline(ISplineInterpolator* pSpline, bool bRedraw) -{ - if (pSpline != m_pSpline) - { - //if (pSpline && pSpline->GetNumDimensions() != 3) - //return; - m_pSpline = pSpline; - m_nActiveKey = -1; - } - - ClearSelection(); - - if (bRedraw) - { - update(); - } -} - -////////////////////////////////////////////////////////////////////////// -ISplineInterpolator* CColorGradientCtrl::GetSpline() -{ - return m_pSpline; -} - -///////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::keyPressEvent(QKeyEvent* event) -{ - bool bProcessed = false; - - if (m_nActiveKey != -1 && m_pSpline) - { - switch (event->key()) - { - case Qt::Key_Delete: - { - RemoveKey(m_nActiveKey); - bProcessed = true; - } break; - case Qt::Key_Up: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() -= 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Down: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() += 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Left: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() -= 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - case Qt::Key_Right: - { - CUndo undo("Move Spline Key"); - QPoint point = KeyToPoint(m_nActiveKey); - point.rx() += 1; - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - TrackKey(point); - bProcessed = true; - } break; - - default: - break; //do nothing - } - - update(); - } - - event->setAccepted(bProcessed); -} - -////////////////////////////////////////////////////////////////////////////// -CColorGradientCtrl::EHitCode CColorGradientCtrl::HitTest(QPoint point) -{ - if (!m_pSpline) - { - return HIT_NOTHING; - } - - ISplineInterpolator::ValueType val; - float time; - PointToTimeValue(point, time, val); - - QRect rc = rect(); - - m_nHitKeyIndex = -1; - - if (rc.contains(point)) - { - m_nHitKeyDist = 0xFFFF; - m_hitCode = HIT_SPLINE; - - for (int i = 0; i < m_pSpline->GetKeyCount(); i++) - { - QPoint splinePt = TimeToPoint(m_pSpline->GetKeyTime(i)); - if (abs(point.x() - splinePt.x()) < abs(m_nHitKeyDist)) - { - m_nHitKeyIndex = i; - m_nHitKeyDist = point.x() - splinePt.x(); - } - } - if (abs(m_nHitKeyDist) < 4) - { - m_hitCode = HIT_KEY; - } - } - else - { - m_hitCode = HIT_NOTHING; - } - - return m_hitCode; -} - -/////////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::StartTracking() -{ - m_bTracking = true; - - GetIEditor()->BeginUndo(); - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCKCROSS)); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::TrackKey(QPoint point) -{ - if (point.x() < m_rcGradient.left() || point.y() > m_rcGradient.right()) - { - return; - } - - int nKey = m_nHitKeyIndex; - - if (nKey >= 0) - { - ISplineInterpolator::ValueType val; - float time; - PointToTimeValue(point, time, val); - - // Clamp to min/max time. - if (time < m_fMinTime || time > m_fMaxTime) - { - return; - } - - int i; - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Switch to next key. - if ((m_pSpline->GetKeyTime(i) < time && i > nKey) || - (m_pSpline->GetKeyTime(i) > time && i < nKey)) - { - m_pSpline->SetKeyTime(nKey, time); - m_pSpline->Update(); - SetActiveKey(i); - m_nHitKeyIndex = i; - return; - } - } - - if (!m_bLockFirstLastKey || (nKey != 0 && nKey != m_pSpline->GetKeyCount() - 1)) - { - m_pSpline->SetKeyTime(nKey, time); - m_pSpline->Update(); - } - - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - update(); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::StopTracking(QPoint point) -{ - if (!m_bTracking) - { - return; - } - - GetIEditor()->AcceptUndo("Spline Move"); - - if (m_nHitKeyIndex >= 0) - { - QRect rc = rect(); - rc = rc.marginsAdded(QMargins(100, 100, 100, 100)); - if (!rc.contains(point)) - { - RemoveKey(m_nHitKeyIndex); - } - } - - m_bTracking = false; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::EditKey(int nKey) -{ - if (!m_pSpline) - { - return; - } - - if (nKey < 0 || nKey >= m_pSpline->GetKeyCount()) - { - return; - } - - SetActiveKey(nKey); - - ISplineInterpolator::ValueType val; - m_pSpline->GetKeyValue(nKey, val); - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - AzQtComponents::ColorPicker dlg(AzQtComponents::ColorPicker::Configuration::RGB); - dlg.setCurrentColor(ValueToColor(val)); - dlg.setSelectedColor(ValueToColor(val)); - connect(&dlg, &AzQtComponents::ColorPicker::currentColorChanged, this, &CColorGradientCtrl::OnKeyColorChanged); - if (dlg.exec() == QDialog::Accepted) - { - CUndo undo("Modify Gradient Color"); - OnKeyColorChanged(dlg.selectedColor()); - } - else - { - OnKeyColorChanged(ValueToColor(val)); - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::OnKeyColorChanged(const AZ::Color& color) -{ - int nKey = m_nActiveKey; - if (!m_pSpline) - { - return; - } - if (nKey < 0 || nKey >= m_pSpline->GetKeyCount()) - { - return; - } - - ISplineInterpolator::ValueType val; - ColorToValue(color, val); - m_pSpline->SetKeyValue(nKey, val); - update(); - - if (m_bLockFirstLastKey) - { - if (nKey == 0) - { - m_pSpline->SetKeyValue(m_pSpline->GetKeyCount() - 1, val); - } - else if (nKey == m_pSpline->GetKeyCount() - 1) - { - m_pSpline->SetKeyValue(0, val); - } - } - m_pSpline->Update(); - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - GetIEditor()->UpdateViews(eRedrawViewports); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::RemoveKey(int nKey) -{ - if (!m_pSpline) - { - return; - } - if (m_bLockFirstLastKey) - { - if (nKey == 0 || nKey == m_pSpline->GetKeyCount() - 1) - { - return; - } - } - - CUndo undo("Remove Spline Key"); - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - m_nActiveKey = -1; - m_nHitKeyIndex = -1; - if (m_pSpline) - { - m_pSpline->RemoveKey(nKey); - m_pSpline->Update(); - } - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - update(); -} - -////////////////////////////////////////////////////////////////////////// -int CColorGradientCtrl::InsertKey(QPoint point) -{ - CUndo undo("Spline Insert Key"); - - ISplineInterpolator::ValueType val; - - float time; - PointToTimeValue(point, time, val); - - if (time < m_fMinTime || time > m_fMaxTime) - { - return -1; - } - - int i; - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Skip if any key already have time that is very close. - if (fabs(m_pSpline->GetKeyTime(i) - time) < MIN_TIME_EPSILON) - { - return i; - } - } - - SendNotifyEvent(CLRGRDN_BEFORE_CHANGE); - - m_pSpline->InsertKey(time, val); - m_pSpline->Interpolate(time, val); - ClearSelection(); - update(); - - SendNotifyEvent(CLRGRDN_CHANGE); - if (m_updateCallback) - { - m_updateCallback(this); - } - - for (i = 0; i < m_pSpline->GetKeyCount(); i++) - { - // Find key with added time. - if (m_pSpline->GetKeyTime(i) == time) - { - return i; - } - } - - return -1; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::ClearSelection() -{ - m_nActiveKey = -1; - if (m_pSpline) - { - m_bSelectedKeys.resize(m_pSpline->GetKeyCount()); - } - for (int i = 0; i < (int)m_bSelectedKeys.size(); i++) - { - m_bSelectedKeys[i] = false; - } -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SetTimeMarker(float fTime) -{ - if (!m_pSpline) - { - return; - } - - { - QPoint pt = TimeToPoint(m_fTimeMarker); - QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized(); - rc += QMargins(1, 0, 1, 0); - update(rc); - } - { - QPoint pt = TimeToPoint(fTime); - QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized(); - rc += QMargins(1, 0, 1, 0); - update(rc); - } - m_fTimeMarker = fTime; -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::SendNotifyEvent(int nEvent) -{ - switch (nEvent) - { - case CLRGRDN_BEFORE_CHANGE: - emit beforeChange(); - break; - case CLRGRDN_CHANGE: - emit change(); - break; - case CLRGRDN_ACTIVE_KEY_CHANGE: - emit activeKeyChange(); - break; - } -} - -////////////////////////////////////////////////////////////////////////// -AZ::Color CColorGradientCtrl::ValueToColor(ISplineInterpolator::ValueType val) -{ - const AZ::Color color(val[0], val[1], val[2], 1.0); - return color.LinearToGamma(); -} - -////////////////////////////////////////////////////////////////////////// -void CColorGradientCtrl::ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val) -{ - const AZ::Color colLin = col.GammaToLinear(); - val[0] = colLin.GetR(); - val[1] = colLin.GetG(); - val[2] = colLin.GetB(); - val[3] = 0; -} - -void CColorGradientCtrl::SetNoTimeMarker(bool noTimeMarker) -{ - m_bNoTimeMarker = noTimeMarker; - update(); -} - - -#include diff --git a/Code/Editor/Controls/ColorGradientCtrl.h b/Code/Editor/Controls/ColorGradientCtrl.h deleted file mode 100644 index bb1a83b0c1..0000000000 --- a/Code/Editor/Controls/ColorGradientCtrl.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - * 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 - * - */ - - -#ifndef CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H -#define CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H -#pragma once - -#if !defined(Q_MOC_RUN) -#include -#include -#include "Controls/WndGridHelper.h" -#endif - -namespace AZ -{ - class Color; -} - -// Notify event sent when spline is being modified. -#define CLRGRDN_CHANGE (0x0001) -// Notify event sent just before when spline is modified. -#define CLRGRDN_BEFORE_CHANGE (0x0002) -// Notify event sent when the active key changes -#define CLRGRDN_ACTIVE_KEY_CHANGE (0x0003) - -////////////////////////////////////////////////////////////////////////// -// Spline control. -////////////////////////////////////////////////////////////////////////// -class CColorGradientCtrl - : public QWidget -{ - Q_OBJECT -public: - CColorGradientCtrl(QWidget* parent = nullptr); - virtual ~CColorGradientCtrl(); - - //Key functions - int GetActiveKey() { return m_nActiveKey; }; - void SetActiveKey(int nIndex); - int InsertKey(QPoint point); - - // Turns on/off zooming and scroll support. - void SetNoZoom([[maybe_unused]] bool bNoZoom) { m_bNoZoom = false; }; - - void SetTimeRange(float tmin, float tmax) { m_fMinTime = tmin; m_fMaxTime = tmax; } - void SetValueRange(float tmin, float tmax) { m_fMinValue = tmin; m_fMaxValue = tmax; } - void SetTooltipValueScale(float x, float y) { m_fTooltipScaleX = x; m_fTooltipScaleY = y; }; - // Lock value of first and last key to be the same. - void LockFirstAndLastKeys(bool bLock) { m_bLockFirstLastKey = bLock; } - - void SetSpline(ISplineInterpolator* pSpline, bool bRedraw = false); - ISplineInterpolator* GetSpline(); - - void SetTimeMarker(float fTime); - - // Zoom in pixels per time unit. - void SetZoom(float fZoom); - void SetOrigin(float fOffset); - - typedef AZStd::function UpdateCallback; - void SetUpdateCallback(const UpdateCallback& cb) { m_updateCallback = cb; }; - - void SetNoTimeMarker(bool noTimeMarker); - -signals: - void change(); - void beforeChange(); - void activeKeyChange(); - -protected: - enum EHitCode - { - HIT_NOTHING, - HIT_KEY, - HIT_SPLINE, - }; - - void paintEvent(QPaintEvent* e) override; - void resizeEvent(QResizeEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void OnLButtonDown(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event) override; - void OnLButtonUp(QMouseEvent* event); - void OnRButtonUp(QMouseEvent* event); - void mouseDoubleClickEvent(QMouseEvent* event) override; - void OnRButtonDown(QMouseEvent* event); - void keyPressEvent(QKeyEvent* event) override; - - // Drawing functions - void DrawGradient(QPaintEvent* e, QPainter* painter); - void DrawKeys(QPaintEvent* e, QPainter* painter); - void UpdateTooltip(QPoint pos); - - EHitCode HitTest(QPoint point); - - //Tracking support helper functions - void StartTracking(); - void TrackKey(QPoint point); - void StopTracking(QPoint point); - void RemoveKey(int nKey); - void EditKey(int nKey); - - QPoint KeyToPoint(int nKey); - QPoint TimeToPoint(float time); - void PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val); - float XOfsToTime(int x); - QPoint XOfsToPoint(int x); - - AZ::Color XOfsToColor(int x); - AZ::Color TimeToColor(float time); - - void ClearSelection(); - - void SendNotifyEvent(int nEvent); - - AZ::Color ValueToColor(ISplineInterpolator::ValueType val); - void ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val); - - -private: - void OnKeyColorChanged(const AZ::Color& color); - -private: - ISplineInterpolator* m_pSpline; - - bool m_bNoZoom; - - QRect m_rcClipRect; - QRect m_rcGradient; - QRect m_rcKeys; - - QPoint m_hitPoint; - EHitCode m_hitCode; - int m_nHitKeyIndex; - int m_nHitKeyDist; - QPoint m_curvePoint; - - float m_fTimeMarker; - - int m_nActiveKey; - int m_nKeyDrawRadius; - - bool m_bTracking; - - float m_fMinTime, m_fMaxTime; - float m_fMinValue, m_fMaxValue; - float m_fTooltipScaleX, m_fTooltipScaleY; - - bool m_bLockFirstLastKey; - - bool m_bNoTimeMarker; - - std::vector m_bSelectedKeys; - - UpdateCallback m_updateCallback; - - CWndGridHelper m_grid; -}; - -#endif // CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp index c228fbda09..68c4acb95e 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyCtrl.cpp @@ -27,7 +27,6 @@ void RegisterReflectedVarHandlers() EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LightAnimationPropertyHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew UserPopupWidgetHandler()); - EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew ColorCurveHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FloatCurveHandler()); EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew MotionPropertyWidgetHandler()); } diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp index b9f7e12e95..2278f55d95 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.cpp @@ -170,30 +170,3 @@ bool FloatCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CSpline GUI->SetSpline(reinterpret_cast(instance.m_spline)); return false; } - - -QWidget* ColorCurveHandler::CreateGUI(QWidget *pParent) -{ - CColorGradientCtrl* gradientCtrl = new CColorGradientCtrl(pParent); - //connect(gradientCtrl, &CColorGradientCtrl::change, [gradientCtrl]() - //{ - // EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, gradientCtrl); - //}); - gradientCtrl->SetTimeRange(0, 1); - gradientCtrl->setFixedHeight(36); - return gradientCtrl; - -} - -void ColorCurveHandler::ConsumeAttribute(CColorGradientCtrl*, AZ::u32, AzToolsFramework::PropertyAttributeReader*, const char*) -{} - -void ColorCurveHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, [[maybe_unused]] CColorGradientCtrl* GUI, [[maybe_unused]] property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) -{} - -bool ColorCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CColorGradientCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node) -{ - GUI->SetSpline(reinterpret_cast(instance.m_spline)); - return false; -} - diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h index e63a974c91..5ec24b679d 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h @@ -16,7 +16,6 @@ #include #include "ReflectedVar.h" #include "Util/VariablePropertyType.h" -#include "Controls/ColorGradientCtrl.h" #include "Controls/SplineCtrl.h" #include #endif @@ -82,17 +81,4 @@ public: void OnSplineChange(CSplineCtrl*); }; -class ColorCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CColorGradientCtrl> -{ -public: - AZ_CLASS_ALLOCATOR(ColorCurveHandler, AZ::SystemAllocator, 0); - bool IsDefaultHandler() const override { return false; } - QWidget* CreateGUI(QWidget *pParent) override; - - AZ::u32 GetHandlerName(void) const override { return AZ_CRC("ePropertyColorCurve", 0xa30da4ec); } - - void ConsumeAttribute(CColorGradientCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override; - void WriteGUIValuesIntoProperty(size_t index, CColorGradientCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override; - bool ReadValuesIntoGUI(size_t index, CColorGradientCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override; -}; #endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H diff --git a/Code/Editor/editor_lib_files.cmake b/Code/Editor/editor_lib_files.cmake index 031ad26d76..47b69765ba 100644 --- a/Code/Editor/editor_lib_files.cmake +++ b/Code/Editor/editor_lib_files.cmake @@ -330,8 +330,6 @@ set(FILES Commands/CommandManager.h Controls/BitmapToolTip.cpp Controls/BitmapToolTip.h - Controls/ColorGradientCtrl.cpp - Controls/ColorGradientCtrl.h Controls/ConsoleSCB.cpp Controls/ConsoleSCB.h Controls/ConsoleSCB.ui diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp new file mode 100644 index 0000000000..2c856fb4b0 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.cpp @@ -0,0 +1,254 @@ +/* + * 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 + * + */ + +#include +#include +#include + +namespace AZ +{ + JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element) + { + using namespace JsonSerializationResult; + + for (auto& path : importPathStack) + { + if (importPath == path) + { + return settings.m_reporting( + AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()), + ResultCode(Tasks::Import, Outcomes::Catastrophic), element); + } + } + + importPathStack.push_back(importPath); + AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer); + JsonImportSettings nestedImportSettings; + nestedImportSettings.m_importer = settings.m_importer; + nestedImportSettings.m_reporting = settings.m_reporting; + nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies; + ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement); + importPathStack.pop_back(); + + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, StackedString& element) + { + using namespace JsonSerializationResult; + + if (jsonDoc.IsObject()) + { + for (auto& field : jsonDoc.GetObject()) + { + if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0) + { + const rapidjson::Value& importDirective = field.value; + AZ::IO::FixedMaxPath importAbsPath = importPathStack.back(); + importAbsPath.RemoveFilename(); + AZStd::string importName; + if (importDirective.IsObject()) + { + auto filenameField = importDirective.FindMember("filename"); + if (filenameField != importDirective.MemberEnd()) + { + importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength()); + } + } + else + { + importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength()); + } + importAbsPath.Append(importName); + + rapidjson::Value patch; + ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator); + if (resolveResult.GetOutcome() == Outcomes::Catastrophic) + { + return resolveResult; + } + + if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports) + { + rapidjson::Pointer path(element.Get().data(), element.Get().size()); + settings.m_importer->AddImportDirective(path, importName); + } + if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies) + { + settings.m_importer->AddImportedFile(importAbsPath.String()); + } + + ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + settings.m_importer->ApplyPatch(jsonDoc, patch, allocator); + } + else if (field.value.IsObject() || field.value.IsArray()) + { + ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength())); + ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + } + } + } + else if(jsonDoc.IsArray()) + { + int index = 0; + for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index) + { + if (!elem->IsObject() && !elem->IsArray()) + { + continue; + } + ScopedStackedString entryName(element, index); + ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element); + if (result.GetOutcome() == Outcomes::Catastrophic) + { + return result; + } + } + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (jsonDoc.IsObject() || jsonDoc.IsArray()) + { + const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives(); + for (auto& import : importDirectives) + { + rapidjson::Pointer importPtr = import.first; + rapidjson::Value* currentValue = importPtr.Get(jsonDoc); + + rapidjson::Value importedValue(rapidjson::kObjectType); + importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator); + ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings); + if (resolveResult.GetOutcome() == Outcomes::Catastrophic) + { + return resolveResult; + } + + rapidjson::Value patch; + settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator); + settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second); + } + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) + { + using namespace JsonSerializationResult; + + auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native()); + if (importedObject.IsSuccess()) + { + rapidjson::Value& importedDoc = importedObject.GetValue(); + + if (importDirective.IsObject()) + { + auto patchField = importDirective.FindMember("patch"); + if (patchField != importDirective.MemberEnd()) + { + patch.CopyFrom(patchField->value, allocator); + } + } + + importPtr->CopyFrom(importedDoc, allocator); + } + else + { + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename) + { + using namespace JsonSerializationResult; + + importPtr->SetObject(); + if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty())) + { + rapidjson::Value importDirective(rapidjson::kObjectType); + importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator); + importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator); + importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator); + } + else + { + importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target, + const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator) + { + using namespace JsonSerializationResult; + + if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty())) + { + return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch); + } + + return ResultCode(Tasks::Import, Outcomes::Success); + } + + JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch, + const rapidjson::Value& source, const rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator) + { + return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch); + } + + void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile) + { + m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile)); + } + + void BaseJsonImporter::AddImportedFile(AZStd::string importedFile) + { + m_importedFiles.insert(AZStd::move(importedFile)); + } + + const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives() + { + return m_importDirectives; + } + + const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles() + { + return m_importedFiles; + } +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h new file mode 100644 index 0000000000..ef53e265d8 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonImporter.h @@ -0,0 +1,108 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AZ +{ + struct JsonImportSettings; + + class BaseJsonImporter + { + public: + AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}"); + + using ImportDirectivesList = AZStd::vector>; + using ImportedFilesList = AZStd::unordered_set; + + virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator); + + virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, + const AZStd::string& importFilename); + + virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target, + const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator); + + virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch, + const rapidjson::Value& source, const rapidjson::Value& target, + rapidjson::Document::AllocatorType& allocator); + + void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile); + const ImportDirectivesList& GetImportDirectives(); + + void AddImportedFile(AZStd::string importedFile); + const ImportedFilesList& GetImportedFiles(); + + virtual ~BaseJsonImporter() = default; + + protected: + + ImportDirectivesList m_importDirectives; + ImportedFilesList m_importedFiles; + }; + + enum class ImportTracking : AZ::u8 + { + None = 0, + Dependencies = (1<<0), + Imports = (1<<1), + All = (Dependencies | Imports) + }; + AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking); + + class JsonImportResolver final + { + public: + + using ImportPathStack = AZStd::vector; + + JsonImportResolver() = delete; + JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete; + JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete; + JsonImportResolver(const JsonImportResolver& rhs) = delete; + JsonImportResolver(JsonImportResolver&& rhs) = delete; + ~JsonImportResolver() = delete; + + static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, StackedString& element); + + static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + + private: + + static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc, + rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack, + JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element); + }; + + + struct JsonImportSettings final + { + JsonSerializationResult::JsonIssueCallback m_reporting; + + BaseJsonImporter* m_importer = nullptr; + + ImportTracking m_resolveFlags = ImportTracking::All; + + AZ::IO::FixedMaxPath m_loadedJsonPath; + }; +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp index 45549b8078..a9b2d2fefa 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonMerger.cpp @@ -706,7 +706,7 @@ namespace AZ rapidjson::Value(rapidjson::kNullType), field.value, element, settings); } - if (result.GetOutcome() == Outcomes::Success) + if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults) { rapidjson::Value name; name.CopyFrom(field.name, allocator, true); @@ -717,6 +717,10 @@ namespace AZ { return result; } + else + { + resultCode.Combine(result); + } } // Do an extra pass to find all the fields that are removed. @@ -751,7 +755,7 @@ namespace AZ rapidjson::Value value; ResultCode result = CreateMergePatchInternal(value, allocator, rapidjson::Value(rapidjson::kNullType), field.value, element, settings); - if (result.GetOutcome() == Outcomes::Success) + if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults) { rapidjson::Value name; name.CopyFrom(field.name, allocator, true); @@ -762,11 +766,20 @@ namespace AZ { return result; } + else + { + resultCode.Combine(result); + } + } + + if (target.MemberCount() == 0) + { + resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.", + ResultCode(Tasks::CreatePatch, Outcomes::Success), element)); } } patch = AZStd::move(resultValue); - resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success)); return resultCode; } else diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp index bc07f684f6..db76e46f2b 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -19,11 +20,6 @@ namespace AZ { - const char* JsonSerialization::TypeIdFieldIdentifier = "$type"; - const char* JsonSerialization::DefaultStringIdentifier = "{}"; - const char* JsonSerialization::KeyFieldIdentifier = "Key"; - const char* JsonSerialization::ValueFieldIdentifier = "Value"; - namespace JsonSerializationInternal { template @@ -394,6 +390,60 @@ namespace AZ } } + JsonSerializationResult::ResultCode JsonSerialization::ResolveImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (settings.m_importer == nullptr) + { + AZ_Assert(false, "Importer object needs to be provided"); + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + AZStd::string scratchBuffer; + auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode + { + return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target); + }; + if (!settings.m_reporting) + { + settings.m_reporting = issueReportingCallback; + } + + JsonImportResolver::ImportPathStack importPathStack; + importPathStack.push_back(settings.m_loadedJsonPath); + StackedString element(StackedString::Format::JsonPointer); + + return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element); + } + + JsonSerializationResult::ResultCode JsonSerialization::RestoreImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings) + { + using namespace JsonSerializationResult; + + if (settings.m_importer == nullptr) + { + AZ_Assert(false, "Importer object needs to be provided"); + return ResultCode(Tasks::Import, Outcomes::Catastrophic); + } + + AZStd::string scratchBuffer; + auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode + { + return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target); + }; + if (!settings.m_reporting) + { + settings.m_reporting = issueReportingCallback; + } + + settings.m_resolveFlags = ImportTracking::None; + + return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings); + } + JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer, AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path) { diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h index c85847ac78..d961953a1d 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerialization.h @@ -18,6 +18,8 @@ namespace AZ { class BaseJsonSerializer; + + struct JsonImportSettings; enum class JsonMergeApproach { @@ -51,10 +53,11 @@ namespace AZ class JsonSerialization final { public: - static const char* TypeIdFieldIdentifier; - static const char* DefaultStringIdentifier; - static const char* KeyFieldIdentifier; - static const char* ValueFieldIdentifier; + static constexpr const char* TypeIdFieldIdentifier = "$type"; + static constexpr const char* DefaultStringIdentifier = "{}"; + static constexpr const char* KeyFieldIdentifier = "Key"; + static constexpr const char* ValueFieldIdentifier = "Value"; + static constexpr const char* ImportDirectiveIdentifier = "$import"; //! Merges two json values together by applying "patch" to "target" using the selected merge algorithm. //! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will @@ -284,6 +287,22 @@ namespace AZ //! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error". static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs); + //! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed + //! in through the settings. + //! @param jsonDoc The json document in which to resolve imports. + //! @param allocator The allocator associated with the json document. + //! @param settings Additional settings that control the way the imports are resolved. + static JsonSerializationResult::ResultCode ResolveImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + + //! Restores all import directives that were present in the json document. The same importer object that was + //! passed into ResolveImports through the settings needs to be passed here through settings as well. + //! @param jsonDoc The json document in which to restore imports. + //! @param allocator The allocator associated with the json document. + //! @param settings Additional settings that control the way the imports are restored. + static JsonSerializationResult::ResultCode RestoreImports( + rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings); + private: JsonSerialization() = delete; ~JsonSerialization() = delete; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp index 7e84aced7b..822c1c43d5 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp @@ -69,6 +69,9 @@ namespace AZ case Tasks::CreatePatch: target.append("a create patch operation "); break; + case Tasks::Import: + target.append("an import operation"); + break; default: target.append("an unknown operation "); break; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h index 8590971a1c..204c40b8ca 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.h @@ -32,7 +32,8 @@ namespace AZ ReadField, //!< Task to read a field from JSON to a value. WriteValue, //!< Task to write a value to a JSON field. Merge, //!< Task to merge two JSON values/documents together. - CreatePatch //!< Task to create a patch to transform one value/document to another. + CreatePatch, //!< Task to create a patch to transform one value/document to another. + Import //!< Task to import a JSON document. }; //! Describes how the task was processed. diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp index 2031d14d08..e6bfd78806 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp @@ -120,7 +120,7 @@ namespace AZ::Utils AZ::Outcome WriteFile(AZStd::string_view content, AZStd::string_view filePath) { AZ::IO::FixedMaxPath filePathFixed = filePath; // Because FileIOStream requires a null-terminated string - AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite); + AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath); bool success = false; diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 4d95ddf098..41229429f2 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -522,6 +522,8 @@ set(FILES Serialization/Json/IntSerializer.cpp Serialization/Json/JsonDeserializer.h Serialization/Json/JsonDeserializer.cpp + Serialization/Json/JsonImporter.cpp + Serialization/Json/JsonImporter.h Serialization/Json/JsonMerger.h Serialization/Json/JsonMerger.cpp Serialization/Json/JsonSerialization.h diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp new file mode 100644 index 0000000000..5121efa5e3 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Serialization/Json/TestCases_Importing.cpp @@ -0,0 +1,413 @@ +/* + * 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 + * + */ + +#include +#include +#include + +namespace JsonSerializationTests +{ + class JsonImportingTests; + + class JsonImporterCustom + : public AZ::BaseJsonImporter + { + public: + AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}"); + + + AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, + const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override; + + JsonImporterCustom(JsonImportingTests* tests) + { + testClass = tests; + } + + private: + JsonImportingTests* testClass; + }; + + class JsonImportingTests + : public BaseJsonSerializerFixture + { + public: + void SetUp() override + { + BaseJsonSerializerFixture::SetUp(); + } + + void TearDown() override + { + BaseJsonSerializerFixture::TearDown(); + } + + void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out) + { + const char *objectJson = R"({ + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + })"; + + const char *arrayJson = R"([ + { "element_1" : "value_1" }, + { "element_2" : "value_2" }, + { "element_3" : "value_3" } + ])"; + + const char *nestedImportJson = R"({ + "desc" : "Nested Import", + "obj" : {"$import" : "object.json"} + })"; + + const char *nestedImportCycle1Json = R"({ + "desc" : "Nested Import Cycle 1", + "obj" : {"$import" : "nested_import_c2.json"} + })"; + + const char *nestedImportCycle2Json = R"({ + "desc" : "Nested Import Cycle 2", + "obj" : {"$import" : "nested_import_c1.json"} + })"; + + if (docName.compare("object.json") == 0) + { + out.Parse(objectJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("array.json") == 0) + { + out.Parse(arrayJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import.json") == 0) + { + out.Parse(nestedImportJson); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import_c1.json") == 0) + { + out.Parse(nestedImportCycle1Json); + ASSERT_FALSE(out.HasParseError()); + } + else if (docName.compare("nested_import_c2.json") == 0) + { + out.Parse(nestedImportCycle2Json); + ASSERT_FALSE(out.HasParseError()); + } + } + + protected: + void TestImportLoadStore(const char* input, const char* expectedImportedValue) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + rapidjson::Document expectedOutcome; + expectedOutcome.Parse(expectedImportedValue); + ASSERT_FALSE(expectedOutcome.HasParseError()); + + TestResolveImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject()); + + rapidjson::Document originalInput; + originalInput.Parse(input); + ASSERT_FALSE(originalInput.HasParseError()); + + TestRestoreImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject()); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + void TestImportCycle(const char* input) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj); + + EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + void TestInsertNewImport(const char* input, const char* expectedRestoredValue) + { + m_jsonDocument->Parse(input); + ASSERT_FALSE(m_jsonDocument->HasParseError()); + + JsonImporterCustom* importerObj = new JsonImporterCustom(this); + + TestResolveImports(importerObj); + + importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json"); + + rapidjson::Document expectedOutput; + expectedOutput.Parse(expectedRestoredValue); + ASSERT_FALSE(expectedOutput.HasParseError()); + + TestRestoreImports(importerObj); + + Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject()); + + m_jsonDocument->SetObject(); + delete importerObj; + } + + AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj) + { + AZ::JsonImportSettings settings; + settings.m_importer = importerObj; + + return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings); + } + + AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj) + { + AZ::JsonImportSettings settings; + settings.m_importer = importerObj; + + return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings); + } + }; + + AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr, + rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath, + rapidjson::Document::AllocatorType& allocator) + { + AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import); + + rapidjson::Document importedDoc; + testClass->GetTestDocument(importedFilePath.String(), importedDoc); + + if (importDirective.IsObject()) + { + auto patchField = importDirective.FindMember("patch"); + if (patchField != importDirective.MemberEnd()) + { + patch.CopyFrom(patchField->value, allocator); + } + } + + importPtr->CopyFrom(importedDoc, allocator); + + return resultCode; + } + + // Test Cases + + TEST_F(JsonImportingTests, ImportSimpleObjectTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object": {"$import" : "object.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object": { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object": { + "$import" : { + "filename" : "object.json", + "patch" : { "field_2" : "patched_value" } + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object": { + "field_1" : "value_1", + "field_2" : "patched_value", + "field_3" : "value_3" + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleArrayTest) + { + const char* inputFile = R"( + { + "name" : "simple_array_import", + "object": {"$import" : "array.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_array_import", + "object": [ + { "element_1" : "value_1" }, + { "element_2" : "value_2" }, + { "element_3" : "value_3" } + ] + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest) + { + const char* inputFile = R"( + { + "name" : "simple_array_import", + "object": { + "$import" : { + "filename" : "array.json", + "patch" : [ { "element_1" : "patched_value" } ] + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_array_import", + "object": [ + { "element_1" : "patched_value" } + ] + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportTest) + { + const char* inputFile = R"( + { + "name" : "nested_import", + "object": {"$import" : "nested_import.json"} + } + )"; + + const char* expectedOutput = R"( + { + "name" : "nested_import", + "object": { + "desc" : "Nested Import", + "obj" : { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportPatchTest) + { + const char* inputFile = R"( + { + "name" : "nested_import", + "object": { + "$import" : { + "filename" : "nested_import.json", + "patch" : { "obj" : { "field_3" : "patched_value" } } + } + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "nested_import", + "object": { + "desc" : "Nested Import", + "obj" : { + "field_1" : "value_1", + "field_2" : "value_2", + "field_3" : "patched_value" + } + } + } + )"; + + TestImportLoadStore(inputFile, expectedOutput); + } + + TEST_F(JsonImportingTests, NestedImportCycleTest) + { + const char* inputFile = R"( + { + "name" : "nested_import_cycle", + "object": {"$import" : "nested_import_c1.json"} + } + )"; + + TestImportCycle(inputFile); + } + + TEST_F(JsonImportingTests, InsertNewImportTest) + { + const char* inputFile = R"( + { + "name" : "simple_object_import", + "object_1": {"$import" : "object.json"}, + "object_2": { + "field_1" : "other_value", + "field_2" : "value_2", + "field_3" : "value_3" + } + } + )"; + + const char* expectedOutput = R"( + { + "name" : "simple_object_import", + "object_1": {"$import" : "object.json"}, + "object_2": { + "$import" : { + "filename" : "object.json", + "patch" : { "field_1" : "other_value" } + } + } + } + )"; + + TestInsertNewImport(inputFile, expectedOutput); + } +} diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index cc6000209f..d39595c45e 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -121,6 +121,7 @@ set(FILES Serialization/Json/TestCases_Classes.cpp Serialization/Json/TestCases_Compare.cpp Serialization/Json/TestCases_Enum.cpp + Serialization/Json/TestCases_Importing.cpp Serialization/Json/TestCases_Patching.cpp Serialization/Json/TestCases_Pointers.h Serialization/Json/TestCases_Pointers.cpp diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp index 3ba7ed7223..798b1fe99f 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp @@ -22,7 +22,7 @@ namespace AzFramework AZStd::scoped_ptr pWatcher(LaunchProcess(processLaunchInfo, communicationType)); if (!pWatcher) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); return false; } else @@ -31,7 +31,7 @@ namespace AzFramework ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator(); if (!pCommunicator || !pCommunicator->IsValid()) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); return false; } else diff --git a/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp index b4cad8511f..cd52393df9 100644 --- a/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Visibility/OctreeSystemComponent.cpp @@ -14,9 +14,8 @@ namespace AzFramework { AZ_CVAR(bool, bg_octreeUseQuadtree, false, nullptr, AZ::ConsoleFunctorFlags::ReadOnly, "If set to true, the visibility octrees will degenerate to a quadtree split along the X/Y plane"); AZ_CVAR(float, bg_octreeMaxWorldExtents, 16384.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum supported world size by the world octreeSystemComponent"); - AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split"); - AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation"); - + AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split"); + AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation"); static uint32_t GetChildNodeCount() { @@ -25,14 +24,12 @@ namespace AzFramework return (bg_octreeUseQuadtree) ? QuadtreeNodeChildCount : OctreeNodeChildCount; } - OctreeNode::OctreeNode(const AZ::Aabb& bounds) : m_bounds(bounds) { ; } - OctreeNode::OctreeNode(OctreeNode&& rhs) : m_bounds(rhs.m_bounds) , m_parent(rhs.m_parent) @@ -46,7 +43,6 @@ namespace AzFramework } } - OctreeNode& OctreeNode::operator=(OctreeNode&& rhs) { m_bounds = rhs.m_bounds; @@ -63,7 +59,6 @@ namespace AzFramework return *this; } - void OctreeNode::Insert(OctreeScene& octreeScene, VisibilityEntry* entry) { AZ_Assert(entry->m_internalNode == nullptr, "Double-insertion: Insert invoked for an entry already bound to the OctreeScene"); @@ -98,7 +93,6 @@ namespace AzFramework } } - void OctreeNode::Update(OctreeScene& octreeScene, VisibilityEntry* entry) { AZ_Assert(entry->m_internalNode == this, "Update invoked for an entry bound to a different OctreeNode"); @@ -129,7 +123,6 @@ namespace AzFramework } } - void OctreeNode::Remove(OctreeScene& octreeScene, VisibilityEntry* entry) { AZ_Assert(entry->m_internalNode == this, "Remove invoked for an entry bound to a different OctreeNode"); @@ -152,25 +145,30 @@ namespace AzFramework } } - void OctreeNode::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const { - EnumerateHelper(aabb, callback); + if (AZ::ShapeIntersection::Overlaps(aabb, m_bounds)) + { + EnumerateHelper(aabb, callback); + } } - void OctreeNode::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const { - EnumerateHelper(sphere, callback); + if (AZ::ShapeIntersection::Overlaps(sphere, m_bounds)) + { + EnumerateHelper(sphere, callback); + } } - void OctreeNode::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const { - EnumerateHelper(frustum, callback); + if (AZ::ShapeIntersection::Overlaps(frustum, m_bounds)) + { + EnumerateHelper(frustum, callback); + } } - void OctreeNode::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const { // Invoke the callback for the current node @@ -190,25 +188,21 @@ namespace AzFramework } } - const AZStd::vector& OctreeNode::GetEntries() const { return m_entries; } - OctreeNode* OctreeNode::GetChildren() const { return m_children; } - bool OctreeNode::IsLeaf() const { return m_children == nullptr; } - void OctreeNode::TryMerge(OctreeScene& octreeScene) { if (IsLeaf()) @@ -236,7 +230,6 @@ namespace AzFramework } } - template void OctreeNode::EnumerateHelper(const T& boundingVolume, const IVisibilityScene::EnumerateCallback& callback) const { @@ -262,7 +255,6 @@ namespace AzFramework } } - void OctreeNode::Split(OctreeScene& octreeScene) { AZ_Assert(m_children == nullptr, "Split invoked on an octreeScene node that has already been split"); @@ -312,7 +304,6 @@ namespace AzFramework } } - void OctreeNode::Merge(OctreeScene& octreeScene) { AZ_Assert(m_children != nullptr, "Merge invoked on an octreeScene node that does not have children"); @@ -371,7 +362,6 @@ namespace AzFramework } } - void OctreeScene::RemoveEntry(VisibilityEntry& entry) { AZStd::lock_guard lock(m_sharedMutex); @@ -382,35 +372,30 @@ namespace AzFramework } } - void OctreeScene::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const { AZStd::shared_lock lock(m_sharedMutex); m_root.Enumerate(aabb, callback); } - void OctreeScene::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const { AZStd::shared_lock lock(m_sharedMutex); m_root.Enumerate(sphere, callback); } - void OctreeScene::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const { AZStd::shared_lock lock(m_sharedMutex); m_root.Enumerate(frustum, callback); } - void OctreeScene::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const { AZStd::shared_lock lock(m_sharedMutex); m_root.EnumerateNoCull(callback); } - uint32_t OctreeScene::GetEntryCount() const { return m_entryCount; @@ -421,26 +406,22 @@ namespace AzFramework return m_nodeCount; } - uint32_t OctreeScene::GetFreeNodeCount() const { // Each entry represents GetChildNodeCount() nodes return aznumeric_cast(m_freeOctreeNodes.size() * GetChildNodeCount()); } - uint32_t OctreeScene::GetPageCount() const { return aznumeric_cast(m_nodeCache.size()); } - uint32_t OctreeScene::GetChildNodeCount() const { return AzFramework::GetChildNodeCount(); } - void OctreeScene::DumpStats() { AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::EntryCount = %u", GetName().GetCStr(), GetEntryCount()); @@ -450,21 +431,18 @@ namespace AzFramework AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::ChildNodeCount = %u", GetName().GetCStr(), GetChildNodeCount()); } - static inline uint32_t CreateNodeIndex(uint32_t page, uint32_t offset) { AZ_Assert(page <= 0xFFFF && offset <= 0xFFFF, "Out of range values passed to CreateNodeIndex"); return (page << 16) | offset; } - static inline void ExtractPageAndOffsetFromIndex(uint32_t index, uint32_t& page, uint32_t& offset) { offset = index & 0x0000FFFF; page = index >> 16; } - uint32_t OctreeScene::AllocateChildNodes() { const uint32_t childCount = GetChildNodeCount(); @@ -508,14 +486,12 @@ namespace AzFramework return CreateNodeIndex(nextChildPage, nextChildOffset); } - void OctreeScene::ReleaseChildNodes(uint32_t nodeIndex) { m_nodeCount -= GetChildNodeCount(); m_freeOctreeNodes.push(nodeIndex); } - OctreeNode* OctreeScene::GetChildNodesAtIndex(uint32_t nodeIndex) const { uint32_t childPage; @@ -524,7 +500,6 @@ namespace AzFramework return &(*m_nodeCache[childPage])[childOffset]; } - void OctreeSystemComponent::Reflect(AZ::ReflectContext* context) { if (auto* serializeContext = azrtti_cast(context)) @@ -534,19 +509,16 @@ namespace AzFramework } } - void OctreeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC("OctreeService")); } - void OctreeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) { incompatible.push_back(AZ_CRC("OctreeService")); } - OctreeSystemComponent::OctreeSystemComponent() { AZ::Interface::Register(this); @@ -555,7 +527,6 @@ namespace AzFramework m_defaultScene = aznew OctreeScene(AZ::Name("DefaultVisibilityScene")); } - OctreeSystemComponent::~OctreeSystemComponent() { AZ_Assert(m_scenes.empty(), "All IVisibilityScenes must be destroyed before shutdown"); @@ -566,13 +537,11 @@ namespace AzFramework AZ::Interface::Unregister(this); } - void OctreeSystemComponent::Activate() { ; } - void OctreeSystemComponent::Deactivate() { ; @@ -591,7 +560,6 @@ namespace AzFramework return newScene; } - void OctreeSystemComponent::DestroyVisibilityScene(IVisibilityScene* visScene) { for (auto iter = m_scenes.begin(); iter != m_scenes.end(); ++iter) @@ -606,7 +574,6 @@ namespace AzFramework AZ_Assert(false, "visScene[\"%s\"] not found in the OctreeSystemComponent", visScene->GetName().GetCStr()); } - IVisibilityScene* OctreeSystemComponent::FindVisibilityScene(const AZ::Name& sceneName) { for (OctreeScene* scene : m_scenes) @@ -619,7 +586,6 @@ namespace AzFramework return nullptr; } - void OctreeSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { for (OctreeScene* scene : m_scenes) diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp index 158240210e..85669e093c 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Application/Application_Linux.cpp @@ -7,17 +7,35 @@ */ #include +#include #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB #include #endif +constexpr rlim_t g_minimumOpenFileHandles = 65536L; + //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { //////////////////////////////////////////////////////////////////////////////////////////////// Application::Implementation* Application::Implementation::Create() { + // The default open file limit for processes may not be enough for O3DE applications. + // We will need to increase to the recommended value if the current open file limit + // is not sufficient. + rlimit currentLimit; + int get_limit_result = getrlimit(RLIMIT_NOFILE, ¤tLimit); + AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits"); + if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles)) + { + rlimit newLimit; + newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit + newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit + [[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit); + AZ_Assert(set_limit_result == 0, "Unable to update open file limits"); + } + #if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB return aznew XcbApplication(); #elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp index d2cd681012..51a8545443 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp @@ -91,31 +91,42 @@ namespace AzFramework return processId == 0; } - /*! Executes a command in the child process after the fork operation has been executed. - * This function will never return. If the execvp command fails this will call _exit with - * the errno value as the return value since continuing execution after a execvp command - * is invalid (it will be running the parent's code and in its address space and will - * cause many issues). + /*! Executes a command in the child process after the fork operation + * has been executed. This function will never return. If the execvpe + * command fails this will call _exit since continuing execution after + * a execvpe command is invalid (it will be running the parent's code + * and in its address space and will cause many issues). + * + * This function runs after a `fork()` call. `fork()` creates a copy of + * the current process, including the current state of the process's + * memory, at the time the call is made. However, it only creates a + * copy of the one thread that called `fork()`. This means that if any + * mutexes are locked by other threads at the time of `fork()`, those + * mutexes will remain locked in the child process, with no way to + * unlock them. So this function needs to ensure that it does as little + * work as possible. * * \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer. - * \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer. + * \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer. * \param processLaunchInfo - struct containing information about luanching the command * \param startupInfo - struct containing information needed to startup the command + * \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process */ - void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo) + [[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array& errorPipe) { + close(errorPipe[0]); + if (!processLaunchInfo.m_workingDirectory.empty()) { int res = chdir(processLaunchInfo.m_workingDirectory.c_str()); if (res != 0) { - std::cerr << strerror(errno) << std::endl; - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str()); + write(errorPipe[1], &errno, sizeof(int)); // We *have* to _exit as we are the child process and simply // returning at this point would mean we would start running // the code from our parent process and that will just wreck // havoc. - _exit(errno); + _exit(0); } } @@ -135,15 +146,17 @@ namespace AzFramework startupInfo.SetupHandlesForChildProcess(); - execve(commandAndArgs[0], commandAndArgs, environmentVariables); + execvpe(commandAndArgs[0], commandAndArgs, environmentVariables); + const int errval = errno; - // If we get here then execve failed to run the requested program and + // If we get here then execvpe failed to run the requested program and // we have an error. In this case we need to exit the child process - // to stop it from continuing to run as a clone of the parent - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno)); - std::cerr << strerror(errno) << std::endl; + // to stop it from continuing to run as a clone of the parent. + // Communicate the error code back to the parent via a pipe for the + // parent to read. + write(errorPipe[1], &errval, sizeof(errval)); - _exit(errno); + _exit(0); } } @@ -212,9 +225,8 @@ namespace AzFramework AZStd::string outputString; bool inQuotes = false; - for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos) + for (const char currentChar : processLaunchInfo.m_commandlineParameters) { - char currentChar = processLaunchInfo.m_commandlineParameters[pos]; if (currentChar == '"') { inQuotes = !inQuotes; @@ -231,7 +243,7 @@ namespace AzFramework outputString.push_back(currentChar); } } - + if (!outputString.empty()) { commandTokens.push_back(outputString); @@ -249,10 +261,10 @@ namespace AzFramework return false; } - // Because of the way execve is defined we need to copy the strings from + // Because of the way execvpe is defined we need to copy the strings from // AZ::string (using c_str() returns a const char*) into a non-const char* - // Need to add one more as exec requires the array's last element to be a null pointer + // Need to add one more as execvpe requires the array's last element to be a null pointer char** commandAndArgs = new char*[commandTokens.size() + 1]; for (int i = 0; i < commandTokens.size(); ++i) { @@ -275,7 +287,7 @@ namespace AzFramework azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str()); environmentVariablesVector.emplace_back(environmentVariable.get()); } - // Adding one more as exec expects the array to have a nullptr as the last element + // Adding one more as execvpe expects the array to have a nullptr as the last element environmentVariablesVector.emplace_back(nullptr); environmentVariables = environmentVariablesVector.data(); } @@ -288,15 +300,50 @@ namespace AzFramework AZ_Assert(environmentVariables, "Environment variables for current process not available\n"); } + // Set up a pipe to communicate the error code from the subprocess's execvpe call + AZStd::array childErrorPipeFds{}; + pipe(childErrorPipeFds.data()); + + // This configures the write end of the pipe to close on calls to `exec` + fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC); + pid_t child_pid = fork(); if (IsIdChildProcess(child_pid)) { - ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo); + ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds); } - processData.m_childProcessId = child_pid; // Close these handles as they are only to be used by the child process processData.m_startupInfo.CloseAllHandles(); + close(childErrorPipeFds[1]); + + { + int errorCodeFromChild = 0; + int count = 0; + // Read from the error pipe. + // * If the child's call to execvpe succeeded, then the pipe will + // be closed due to setting FD_CLOEXEC on the write end of the + // pipe. `read()` will return 0. + // * If the child's call to execvpe failed, the child will have + // written the error code to the pipe. `read()` will return >0, and + // the data to be read is the error code from execvpe. + while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1) + { + if (errno != EAGAIN && errno != EINTR) + { + break; + } + } + if (count) + { + AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild)); + processData.m_childProcessIsDone = true; + child_pid = -1; + } + } + close(childErrorPipeFds[0]); + + processData.m_childProcessId = child_pid; for (int i = 0; i < commandTokens.size(); i++) { diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp index 88392c98c0..b0f316cf50 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp @@ -53,7 +53,7 @@ namespace AzNetworking m_timeoutItemMap.erase(timeoutId); } - void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts) + void TimeoutQueue::UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts) { int32_t numTimeouts = 0; if (maxTimeouts < 0) @@ -103,7 +103,7 @@ namespace AzNetworking // By this point, the item is definitely timed out // Invoke the timeout function to see how to proceed - const TimeoutResult result = timeoutHandler.HandleTimeout(mapItem); + const TimeoutResult result = timeoutHandler(mapItem); if (result == TimeoutResult::Refresh) { @@ -122,4 +122,10 @@ namespace AzNetworking m_timeoutItemMap.erase(itemTimeoutId); } } + + void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts) + { + TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); }); + UpdateTimeouts(handler, maxTimeouts); + } } diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h index 239af5be0e..63417ea36f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h @@ -64,6 +64,12 @@ namespace AzNetworking //! @param timeoutId the identifier of the item to remove void RemoveItem(TimeoutId timeoutId); + //! Updates timeouts for all items, invokes the provided timeout functor if required. + //! @param timeoutHandler lambda to invoke for all timeouts + //! @param maxTimeouts the maximum number of timeouts to process before breaking iteration + using TimeoutHandler = AZStd::function; + void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1); + //! Updates timeouts for all items, invokes timeout handlers if required. //! @param timeoutHandler listener instance to call back on for timeouts //! @param maxTimeouts the maximum number of timeouts to process before breaking iteration diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_close.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_close.svg new file mode 100644 index 0000000000..3fccf0e716 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open.svg b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open.svg new file mode 100644 index 0000000000..55704ec230 --- /dev/null +++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/Entity/prefab_edit_open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc index 0c8fedc79d..15049aeb69 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc +++ b/Code/Framework/AzQtComponents/AzQtComponents/Images/resources.qrc @@ -6,6 +6,8 @@ Entity/layer.svg Entity/prefab.svg Entity/prefab_edit.svg + Entity/prefab_edit_open.svg + Entity/prefab_edit_close.svg Level/level.svg diff --git a/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp b/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp index 864bc3f52a..7581986950 100644 --- a/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp +++ b/Code/Framework/AzTest/AzTest/Platform/Mac/Platform_Mac.cpp @@ -7,6 +7,7 @@ */ #include #include +#include #include #include @@ -20,11 +21,16 @@ public: explicit ModuleHandle(const std::string& lib) : m_libHandle(nullptr) { - std::string libext = lib; - if (!AZ::Test::EndsWith(libext, ".dylib")) + AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() }; + if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX)) { - libext += ".dylib"; + libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native(); } + if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION) + { + libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION; + } + m_libHandle = dlopen(libext.c_str(), RTLD_NOW); const char* error = dlerror(); if (error) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp index d95a704e84..e9a5bee87a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.cpp @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -38,6 +40,13 @@ namespace AzToolsFramework AZ::Edit::SliceFlags::DontGatherReference); } } + + if (auto behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty( + "EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId))) + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation); + } } void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h index 8873787bd3..aa15b63ac2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/EditorPrefabComponent.h @@ -16,7 +16,9 @@ namespace AzToolsFramework class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase { public: - AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase); + static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}"; + + AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase); static void Reflect(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 09b5745a90..8bc258fef9 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -206,6 +206,34 @@ namespace AzToolsFramework::Prefab return instance.has_value() && (&instance->get() == &m_focusedInstance->get()); } + bool PrefabFocusHandler::IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const + { + if (!m_focusedInstance.has_value()) + { + // PrefabFocusHandler has not been initialized yet. + return false; + } + + if (!entityId.IsValid()) + { + return false; + } + + InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); + + while (instance.has_value()) + { + if (&instance->get() == &m_focusedInstance->get()) + { + return true; + } + + instance = instance->get().GetParentInstance(); + } + + return false; + } + const AZ::IO::Path& PrefabFocusHandler::GetPrefabFocusPath([[maybe_unused]] AzFramework::EntityContextId entityContextId) const { return m_instanceFocusPath; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 6a71365d8e..cb8165e7f0 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -53,6 +53,7 @@ namespace AzToolsFramework::Prefab PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override; bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override; + bool IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const override; const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override; const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h index 86e476b56f..5bd4c6b0f6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h @@ -37,10 +37,15 @@ namespace AzToolsFramework::Prefab //! Returns the entity id of the container entity for the instance the prefab system is focusing on. virtual AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const = 0; + //! Returns whether the entity belongs to the instance that is being focused on. + //! @param entityId The entityId of the queried entity. + //! @return true if the entity belongs to the focused instance, false otherwise. + virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0; + //! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants. //! @param entityId The entityId of the queried entity. //! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise. - virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0; + virtual bool IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const = 0; //! Returns the path from the root instance to the currently focused instance. //! @return A path composed from the names of the container entities for the instance path. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index ee34b628bb..d976c91c3e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -974,7 +974,7 @@ namespace AzToolsFramework return DeleteFromInstance(entityIds, true); } - PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) + DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) { if (entityIds.empty()) { @@ -1021,6 +1021,7 @@ namespace AzToolsFramework ScopedUndoBatch undoBatch("Duplicate Entities"); + EntityIdList duplicatedEntityAndInstanceIds; { AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities"); @@ -1033,7 +1034,7 @@ namespace AzToolsFramework if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { - return AZStd::move(retrieveEntitiesAndInstancesOutcome); + return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError()); } // Take a snapshot of the instance DOM before we manipulate it @@ -1044,8 +1045,6 @@ namespace AzToolsFramework PrefabDom instanceDomAfter; instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator()); - EntityIdList duplicatedEntityAndInstanceIds; - // Duplicate any nested entities and instances as requested AZStd::unordered_map newInstanceAliasToOldInstanceMap; AZStd::unordered_map duplicateEntityAliasMap; @@ -1114,7 +1113,7 @@ namespace AzToolsFramework ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds); } - return AZ::Success(); + return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds)); } PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index a9dadc3336..4961be9d77 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -63,7 +63,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 528d4f6d1b..ede857dd2b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -26,6 +26,7 @@ namespace AzToolsFramework { typedef AZ::Outcome CreatePrefabResult; typedef AZ::Outcome InstantiatePrefabResult; + typedef AZ::Outcome DuplicatePrefabResult; typedef AZ::Outcome PrefabOperationResult; typedef AZ::Outcome PrefabRequestResult; typedef AZ::Outcome PrefabEntityResult; @@ -160,14 +161,15 @@ namespace AzToolsFramework /** * Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance. * @param entities The entities to duplicate. - * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. + * @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded; + * on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; /** * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting * the container entity into a regular entity and putting it under the parent prefab, removing the link between this - * instance and the parent, removing links between this instance and it's nested instances, adding entities directly + * instance and the parent, removing links between this instance and its nested instances, and adding entities directly * owned by this instance under the parent instance. * Bails if the entity is not a container entity or belongs to the level prefab instance. * @param containerEntityId The container entity id of the instance to detach. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h index 7b23fffb7f..fd4b8a5f17 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h @@ -25,6 +25,7 @@ namespace AzToolsFramework { using CreatePrefabResult = AZ::Outcome; using InstantiatePrefabResult = AZ::Outcome; + using DuplicatePrefabResult = AZ::Outcome; using PrefabOperationResult = AZ::Outcome; /** @@ -69,6 +70,29 @@ namespace AzToolsFramework * Return an outcome object; on failure, it comes with an error message detailing the cause of the error. */ virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0; + + /** + * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting + * the container entity into a regular entity and putting it under the parent prefab, removing the link between this + * instance and the parent, removing links between this instance and its nested instances, and adding entities directly + * owned by this instance under the parent instance. + * Bails if the entity is not a container entity or belongs to the level prefab instance. + * Return an outcome object; on failure, it comes with an error message detailing the cause of the error. + */ + virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0; + + /** + * Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance. + * Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded; + * on failure, it comes with an error message detailing the cause of the error. + */ + virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + + /** + * Get the file path to the prefab file for the prefab instance owning the entity provided. + * Returns the path to the prefab, or an empty path if the entity is owned by the level. + */ + virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0; }; using PrefabPublicRequestBus = AZ::EBus; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp index 0aaf81c4c9..3b69dcdfe4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.cpp @@ -28,6 +28,9 @@ namespace AzToolsFramework ->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory) ->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab) ->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance) + ->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab) + ->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance) + ->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath) ; } } @@ -62,5 +65,19 @@ namespace AzToolsFramework return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds); } + PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId) + { + return m_prefabPublicInterface->DetachPrefab(containerEntityId); + } + + DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) + { + return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds); + } + + AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const + { + return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native(); + } } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h index ae0ed2a5d1..b24ea7ec2a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestHandler.h @@ -34,6 +34,9 @@ namespace AzToolsFramework CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override; InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override; PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; + DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override; private: PrefabPublicInterface* m_prefabPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp index 6c96209d56..1c2230fa83 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp @@ -23,7 +23,7 @@ namespace AzToolsFramework } //PrefabInstanceUndo - PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation) + PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation) : PrefabUndoBase(undoOperationName) { m_useImmediatePropagation = useImmediatePropagation; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h index 0946a36951..bc0b86a8c6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h @@ -45,7 +45,7 @@ namespace AzToolsFramework : public PrefabUndoBase { public: - explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true); + explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true); void Capture( const PrefabDom& initialState, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp index aca667b9aa..c13f7dd848 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Slice/SliceUtilities.cpp @@ -2648,16 +2648,15 @@ namespace AzToolsFramework } else { - QString cleanSaveAs(QDir::cleanPath(slicePath)); + AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(slicePath.toUtf8().constData()).LexicallyNormal(); bool isPathSafeForAssets = false; - for (AZStd::string assetSafeFolder : assetSafeFolders) + for (const AZStd::string& assetSafeFolder : assetSafeFolders) { - QString cleanAssetSafeFolder(QDir::cleanPath(assetSafeFolder.c_str())); - // Compare using clean paths so slash direction does not matter. - // Note that this comparison is case sensitive because some file systems - // Open 3D Engine supports are case sensitive. - if (cleanSaveAs.startsWith(cleanAssetSafeFolder)) + AZ::IO::PathView assetSafeFolderView(assetSafeFolder); + // Check if the slice path is relative to the safe asset directory. + // The Path classes are being used to make this check case insensitive. + if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView)) { isPathSafeForAssets = true; break; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp index 3cdcade1b0..866080a83f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.cpp @@ -101,8 +101,25 @@ namespace AzToolsFramework { } - void EditorEntityUiHandlerBase::OnDoubleClick([[maybe_unused]] AZ::EntityId entityId) const + bool EditorEntityUiHandlerBase::OnOutlinerItemClick( + [[maybe_unused]] const QPoint& position, + [[maybe_unused]] const QStyleOptionViewItem& option, + [[maybe_unused]] const QModelIndex& index) const + { + return false; + } + + void EditorEntityUiHandlerBase::OnOutlinerItemExpand([[maybe_unused]] const QModelIndex& index) const + { + } + + void EditorEntityUiHandlerBase::OnOutlinerItemCollapse([[maybe_unused]] const QModelIndex& index) const { } + bool EditorEntityUiHandlerBase::OnEntityDoubleClick([[maybe_unused]] AZ::EntityId entityId) const + { + return false; + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h index 9554ad01fc..96d393efa1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h @@ -61,8 +61,17 @@ namespace AzToolsFramework virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const; - //! Triggered when the entity is double clicked in the Outliner. - virtual void OnDoubleClick(AZ::EntityId entityId) const; + //! Triggered when the entity is clicked in the Outliner. + //! @return True if the click has been handled and should not be propagated, false otherwise. + virtual bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const; + //! Triggered when an entity's children are expanded in the Outliner. + virtual void OnOutlinerItemExpand(const QModelIndex& index) const; + //! Triggered when an entity's children are collapsed in the Outliner. + virtual void OnOutlinerItemCollapse(const QModelIndex& index) const; + + //! Triggered when the entity is double clicked in the Outliner or in the Viewport. + //! @return True if the double click has been handled and should not be propagated, false otherwise. + virtual bool OnEntityDoubleClick(AZ::EntityId entityId) const; private: EditorEntityUiHandlerId m_handlerId = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp index 3f8023c1e3..a5f1e29942 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerListModel.cpp @@ -11,10 +11,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -2287,7 +2289,14 @@ namespace AzToolsFramework // Now we setup a Text Document so it can draw the rich text QTextDocument textDoc; textDoc.setDefaultFont(optionV4.font); - textDoc.setDefaultStyleSheet("body {color: white}"); + if (option.state & QStyle::State_Enabled) + { + textDoc.setDefaultStyleSheet("body {color: white}"); + } + else + { + textDoc.setDefaultStyleSheet("body {color: #7C7C7C}"); + } textDoc.setHtml("" + entityNameRichText + ""); painter->translate(textRect.topLeft()); textDoc.setTextWidth(textRect.width()); @@ -2326,6 +2335,23 @@ namespace AzToolsFramework return true; } + if (event->type() == QEvent::MouseButtonPress) + { + AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + + if (auto editorEntityUiInterface = AZ::Interface::Get(); editorEntityUiInterface != nullptr) + { + auto mouseEvent = static_cast(event); + + auto entityUiHandler = editorEntityUiInterface->GetHandler(entityId); + + if (entityUiHandler && entityUiHandler->OnOutlinerItemClick(mouseEvent->pos(), option, index)) + { + return true; + } + } + } + return QStyledItemDelegate::editorEvent(event, model, option, index); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp index d3138f5139..d94ced392c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.cpp @@ -73,6 +73,8 @@ namespace AzToolsFramework void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event) { m_mousePosition = QPoint(); + m_currentHoveredIndex = QModelIndex(); + update(); } void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event) @@ -129,6 +131,11 @@ namespace AzToolsFramework } m_mousePosition = event->pos(); + if (QModelIndex hoveredIndex = indexAt(m_mousePosition); m_currentHoveredIndex != indexAt(m_mousePosition)) + { + m_currentHoveredIndex = hoveredIndex; + update(); + } //process mouse movement as normal, potentially triggering drag and drop QTreeView::mouseMoveEvent(event); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx index 5d76ec6db2..42b42a59b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerTreeView.hxx @@ -90,6 +90,8 @@ namespace AzToolsFramework const QColor m_selectedColor = QColor(255, 255, 255, 45); const QColor m_hoverColor = QColor(255, 255, 255, 30); + QModelIndex m_currentHoveredIndex; + EditorEntityUiInterface* m_editorEntityFrameworkInterface; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp index 4d53102edb..3e97f967fc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Outliner/EntityOutlinerWidget.cpp @@ -902,6 +902,7 @@ namespace AzToolsFramework EditorPickModeRequestBus::Broadcast( &EditorPickModeRequests::StopEntityPickMode); + return; } switch (index.column()) @@ -918,18 +919,30 @@ namespace AzToolsFramework { if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId)) { - entityUiHandler->OnDoubleClick(entityId); + entityUiHandler->OnEntityDoubleClick(entityId); } } void EntityOutlinerWidget::OnTreeItemExpanded(const QModelIndex& index) { - m_listModel->OnEntityExpanded(GetEntityIdFromIndex(index)); + AZ::EntityId entityId = GetEntityIdFromIndex(index); + if (auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId)) + { + entityUiHandler->OnOutlinerItemExpand(index); + } + + m_listModel->OnEntityExpanded(entityId); } void EntityOutlinerWidget::OnTreeItemCollapsed(const QModelIndex& index) { - m_listModel->OnEntityCollapsed(GetEntityIdFromIndex(index)); + AZ::EntityId entityId = GetEntityIdFromIndex(index); + if (auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId)) + { + entityUiHandler->OnOutlinerItemCollapse(index); + } + + m_listModel->OnEntityCollapsed(entityId); } void EntityOutlinerWidget::OnExpandEntity(const AZ::EntityId& entityId, bool expand) @@ -1163,7 +1176,7 @@ namespace AzToolsFramework { QTimer::singleShot(1, this, [this]() { m_gui->m_objectTree->setUpdatesEnabled(true); - m_gui->m_objectTree->expandToDepth(0); + m_gui->m_objectTree->expand(m_proxyModel->index(0,0)); }); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 65f7b0cdfb..ae0d18b077 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -805,16 +805,15 @@ namespace AzToolsFramework } else { - QString cleanSaveAs(QDir::cleanPath(prefabPath)); + AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(prefabPath.toUtf8().constData()).LexicallyNormal(); bool isPathSafeForAssets = false; - for (AZStd::string assetSafeFolder : assetSafeFolders) + for (const AZStd::string& assetSafeFolder : assetSafeFolders) { - QString cleanAssetSafeFolder(QDir::cleanPath(assetSafeFolder.c_str())); - // Compare using clean paths so slash direction does not matter. - // Note that this comparison is case sensitive because some file systems - // Open 3D Engine supports are case sensitive. - if (cleanSaveAs.startsWith(cleanAssetSafeFolder)) + AZ::IO::PathView assetSafeFolderView(assetSafeFolder); + // Check if the prefabPath is relative to the safe asset directory. + // The Path classes are being used to make this check case insensitive. + if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView)) { isPathSafeForAssets = true; break; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index 7c2c65d204..447f94fc15 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -21,10 +21,16 @@ namespace AzToolsFramework { + const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444"); + const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A"); + const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565"); const QColor PrefabUiHandler::m_prefabCapsuleColor = QColor("#1E252F"); + const QColor PrefabUiHandler::m_prefabCapsuleDisabledColor = QColor("#35383C"); const QColor PrefabUiHandler::m_prefabCapsuleEditColor = QColor("#4A90E2"); const QString PrefabUiHandler::m_prefabIconPath = QString(":/Entity/prefab.svg"); const QString PrefabUiHandler::m_prefabEditIconPath = QString(":/Entity/prefab_edit.svg"); + const QString PrefabUiHandler::m_prefabEditOpenIconPath = QString(":/Entity/prefab_edit_open.svg"); + const QString PrefabUiHandler::m_prefabEditCloseIconPath = QString(":/Entity/prefab_edit_close.svg"); PrefabUiHandler::PrefabUiHandler() { @@ -75,7 +81,7 @@ namespace AzToolsFramework if (!path.empty()) { - tooltip = QObject::tr("%1").arg(path.Native().data()); + tooltip = QObject::tr("Double click to edit.\n%1").arg(path.Native().data()); } return tooltip; @@ -102,13 +108,20 @@ namespace AzToolsFramework AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName; const bool isLastColumn = index.column() == EntityOutlinerListModel::ColumnLockToggle; - const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value() && index.model()->hasChildren(index); + QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName); + const bool hasVisibleChildren = + firstColumnIndex.data(EntityOutlinerListModel::ExpandedRole).value() && + firstColumnIndex.model()->hasChildren(firstColumnIndex); QColor backgroundColor = m_prefabCapsuleColor; if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { backgroundColor = m_prefabCapsuleEditColor; } + else if (!(option.state & QStyle::State_Enabled)) + { + backgroundColor = m_prefabCapsuleDisabledColor; + } QPainterPath backgroundPath; backgroundPath.setFillRule(Qt::WindingFill); @@ -184,7 +197,8 @@ namespace AzToolsFramework const bool isFirstColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnName; const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle; - QColor borderColor = m_prefabCapsuleColor; + // There is no legal way of opening prefabs in their default state, so default to disabled. + QColor borderColor = m_prefabCapsuleDisabledColor; if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { borderColor = m_prefabCapsuleEditColor; @@ -273,6 +287,71 @@ namespace AzToolsFramework painter->restore(); } + void PrefabUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const + { + AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + const QPoint offset = QPoint(-18, 3); + QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName); + const int iconSize = 16; + const bool isHovered = (option.state & QStyle::State_MouseOver); + const bool isSelected = index.data(EntityOutlinerListModel::SelectedRole).template value(); + const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName; + const bool isExpanded = + firstColumnIndex.data(EntityOutlinerListModel::ExpandedRole).value() && + firstColumnIndex.model()->hasChildren(firstColumnIndex); + + if (!isFirstColumn || !(option.state & QStyle::State_Enabled)) + { + return; + } + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + + if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + // Only show the close icon if the prefab is expanded. + // This allows the prefab container to be opened if it was collapsed during propagation. + if (!isExpanded) + { + return; + } + + // Use the same color as the background. + QColor backgroundColor = m_backgroundColor; + if (isSelected) + { + backgroundColor = m_backgroundSelectedColor; + } + else if (isHovered) + { + backgroundColor = m_backgroundHoverColor; + } + + // Paint a rect to cover up the expander. + QRect rect = QRect(0, 0, 16, 16); + rect.translate(option.rect.topLeft() + offset); + painter->fillRect(rect, backgroundColor); + + // Paint the icon. + QIcon closeIcon = QIcon(m_prefabEditCloseIconPath); + painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize)); + } + else + { + // Only show the edit icon on hover. + if (!isHovered) + { + return; + } + + QIcon openIcon = QIcon(m_prefabEditOpenIconPath); + painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize)); + } + + painter->restore(); + } + bool PrefabUiHandler::IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child) { QModelIndex lastVisibleItemIndex = GetLastVisibleChild(parent); @@ -314,9 +393,53 @@ namespace AzToolsFramework return Internal_GetLastVisibleChild(model, lastChild); } - void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const + bool PrefabUiHandler::OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const + { + AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + const QPoint offset = QPoint(-18, 3); + + if (m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId)) + { + QRect iconRect = QRect(0, 0, 16, 16); + iconRect.translate(option.rect.topLeft() + offset); + + if (iconRect.contains(position)) + { + if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + // Focus on this prefab. + m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + } + + // Don't propagate event. + return true; + } + } + + return false; + } + + void PrefabUiHandler::OnOutlinerItemCollapse(const QModelIndex& index) const + { + AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value()); + + if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + + // Go one level up. + int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(editorEntityContextId); + m_prefabFocusPublicInterface->FocusOnPathIndex(editorEntityContextId, length - 2); + } + } + + bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const { // Focus on this prefab m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + + // Don't propagate event. + return true; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index 7c68d9fd95..6c78afc5b7 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -36,7 +36,10 @@ namespace AzToolsFramework void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QModelIndex& descendantIndex) const override; - void OnDoubleClick(AZ::EntityId entityId) const override; + void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + void OnOutlinerItemCollapse(const QModelIndex& index) const override; + bool OnEntityDoubleClick(AZ::EntityId entityId) const override; private: Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr; @@ -48,9 +51,15 @@ namespace AzToolsFramework static constexpr int m_prefabCapsuleRadius = 6; static constexpr int m_prefabBorderThickness = 2; + static const QColor m_backgroundColor; + static const QColor m_backgroundHoverColor; + static const QColor m_backgroundSelectedColor; static const QColor m_prefabCapsuleColor; + static const QColor m_prefabCapsuleDisabledColor; static const QColor m_prefabCapsuleEditColor; static const QString m_prefabIconPath; static const QString m_prefabEditIconPath; + static const QString m_prefabEditOpenIconPath; + static const QString m_prefabEditCloseIconPath; }; } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index a289d914d6..43e2a6793f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -20,7 +20,7 @@ namespace AzToolsFramework::ViewportUi::Internal { const static int HighlightBorderSize = 5; const static int TopHighlightBorderSize = 25; - const static char* HighlightBorderColor = "#44B2F8"; + const static char* HighlightBorderColor = "#4A90E2"; static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup) { diff --git a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp index ea458ba826..2d3e671999 100644 --- a/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp +++ b/Code/Tools/AssetProcessor/Platform/Linux/native/FileWatcher/FileWatcher_linux.cpp @@ -72,7 +72,7 @@ struct FolderRootWatch::PlatformImplementation // Add the folder to watch and track it int watchHandle = inotify_add_watch(m_iNotifyHandle, cleanPath.toUtf8().constData(), - IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY); + IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE); if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout)) { @@ -95,7 +95,7 @@ struct FolderRootWatch::PlatformImplementation int watchHandle = inotify_add_watch(m_iNotifyHandle, dirName.toUtf8().constData(), - IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY); + IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE); if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout)) { diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 5f7826dbac..298d2a749a 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -218,6 +218,10 @@ QTabBar::tab:focus { color: #666666; } +#verticalSeparatingLine { + color: #888888; +} + /************** Project Settings **************/ #projectSettings { margin-top:42px; @@ -481,6 +485,26 @@ QProgressBar::chunk { font-weight: 600; } +#gemCatalogMenuButton { + qproperty-flat: true; + max-width:36px; + min-width:36px; + max-height:24px; + min-height:24px; +} + +#GemCatalogCartOverlayGemDownloadHeader { + margin:0; + padding: 0px; + background-color: #333333; +} + +#GemCatalogCartOverlayGemDownloadBG { + margin:0; + padding: 0px; + background-color: #444444; +} + #GemCatalogHeaderLabel { font-size: 12px; color: #FFFFFF; diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 84b931cb30..65e01803aa 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -49,6 +49,8 @@ namespace O3DE::ProjectManager m_stack->addWidget(m_gemCatalogScreen); vLayout->addWidget(m_stack); + connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest); + // When there are multiple project templates present, we re-gather the gems when changing the selected the project template. connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex) { @@ -133,7 +135,7 @@ namespace O3DE::ProjectManager } else { - emit GotoPreviousScreenRequest(); + emit GoToPreviousScreenRequest(); } } diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp new file mode 100644 index 0000000000..fa3fdb10d1 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -0,0 +1,84 @@ +/* + * 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 + * + */ + +#include +#include + +#include + + +namespace O3DE::ProjectManager +{ + DownloadController::DownloadController(QWidget* parent) + : QObject() + , m_lastProgress(0) + , m_parent(parent) + { + m_worker = new DownloadWorker(); + m_worker->moveToThread(&m_workerThread); + + connect(&m_workerThread, &QThread::started, m_worker, &DownloadWorker::StartDownload); + connect(m_worker, &DownloadWorker::Done, this, &DownloadController::HandleResults); + connect(m_worker, &DownloadWorker::UpdateProgress, this, &DownloadController::UpdateUIProgress); + connect(this, &DownloadController::StartGemDownload, m_worker, &DownloadWorker::StartDownload); + } + + DownloadController::~DownloadController() + { + connect(&m_workerThread, &QThread::finished, m_worker, &DownloadController::deleteLater); + m_workerThread.requestInterruption(); + m_workerThread.quit(); + m_workerThread.wait(); + } + + void DownloadController::AddGemDownload(const QString& gemName) + { + m_gemNames.push_back(gemName); + if (m_gemNames.size() == 1) + { + m_worker->SetGemToDownload(m_gemNames[0], false); + m_workerThread.start(); + } + } + + void DownloadController::UpdateUIProgress(int progress) + { + m_lastProgress = progress; + emit GemDownloadProgress(progress); + } + + void DownloadController::HandleResults(const QString& result) + { + bool succeeded = true; + + if (!result.isEmpty()) + { + QMessageBox::critical(nullptr, tr("Gem download"), result); + succeeded = false; + } + + m_gemNames.erase(m_gemNames.begin()); + emit Done(succeeded); + + if (!m_gemNames.empty()) + { + emit StartGemDownload(m_gemNames[0]); + } + else + { + m_workerThread.quit(); + m_workerThread.wait(); + } + } + + void DownloadController::HandleCancel() + { + m_workerThread.quit(); + emit Done(false); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h new file mode 100644 index 0000000000..608d9b1a2b --- /dev/null +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -0,0 +1,72 @@ +/* + * 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 + * + */ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QProcess) + +namespace O3DE::ProjectManager +{ + QT_FORWARD_DECLARE_CLASS(DownloadWorker) + + class DownloadController : public QObject + { + Q_OBJECT + + public: + explicit DownloadController(QWidget* parent = nullptr); + ~DownloadController(); + + void AddGemDownload(const QString& m_gemName); + + bool IsDownloadQueueEmpty() + { + return m_gemNames.empty(); + } + + const AZStd::vector& GetDownloadQueue() const + { + return m_gemNames; + } + + const QString& GetCurrentDownloadingGem() const + { + if (!m_gemNames.empty()) + { + return m_gemNames[0]; + } + else + { + static const QString emptyString; + return emptyString; + } + } + public slots: + void UpdateUIProgress(int progress); + void HandleResults(const QString& result); + void HandleCancel(); + + signals: + void StartGemDownload(const QString& gemName); + void Done(bool success = true); + void GemDownloadProgress(int percentage); + + private: + DownloadWorker* m_worker; + QThread m_workerThread; + QWidget* m_parent; + AZStd::vector m_gemNames; + + int m_lastProgress; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp new file mode 100644 index 0000000000..9bda1b34cc --- /dev/null +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -0,0 +1,48 @@ +/* + * 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 + * + */ + +#include +#include +#include + + +namespace O3DE::ProjectManager +{ + DownloadWorker::DownloadWorker() + : QObject() + { + } + + void DownloadWorker::StartDownload() + { + auto gemDownloadProgress = [=](int downloadProgress) + { + m_downloadProgress = downloadProgress; + emit UpdateProgress(downloadProgress); + }; + AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress); + if (gemInfoResult.IsSuccess()) + { + emit Done(""); + } + else + { + emit Done(tr("Gem download failed")); + } + } + + void DownloadWorker::SetGemToDownload(const QString& gemName, bool downloadNow) + { + m_gemName = gemName; + if (downloadNow) + { + StartDownload(); + } + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.h b/Code/Tools/ProjectManager/Source/DownloadWorker.h new file mode 100644 index 0000000000..316a730a78 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.h @@ -0,0 +1,42 @@ +/* + * 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 + * + */ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QProcess) + +namespace O3DE::ProjectManager +{ + class DownloadWorker : public QObject + { + // Download was cancelled + inline static const QString DownloadCancelled = QObject::tr("Download Cancelled."); + + Q_OBJECT + + public: + explicit DownloadWorker(); + ~DownloadWorker() = default; + + public slots: + void StartDownload(); + void SetGemToDownload(const QString& gemName, bool downloadNow = true); + + signals: + void UpdateProgress(int progress); + void Done(QString result = ""); + + private: + + QString m_gemName; + int m_downloadProgress; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp index c78a9426db..f30a8e0daa 100644 --- a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.cpp @@ -29,17 +29,17 @@ namespace O3DE::ProjectManager topBarFrameWidget->setLayout(topBarHLayout); - QTabWidget* tabWidget = new QTabWidget(); - tabWidget->setObjectName("engineTab"); - tabWidget->tabBar()->setObjectName("engineTabBar"); - tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus); + m_tabWidget = new QTabWidget(); + m_tabWidget->setObjectName("engineTab"); + m_tabWidget->tabBar()->setObjectName("engineTabBar"); + m_tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus); m_engineSettingsScreen = new EngineSettingsScreen(); m_gemRepoScreen = new GemRepoScreen(); - tabWidget->addTab(m_engineSettingsScreen, tr("General")); - tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories")); - topBarHLayout->addWidget(tabWidget); + m_tabWidget->addTab(m_engineSettingsScreen, tr("General")); + m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories")); + topBarHLayout->addWidget(m_tabWidget); vLayout->addWidget(topBarFrameWidget); @@ -61,4 +61,28 @@ namespace O3DE::ProjectManager return true; } + bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen) + { + if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum()) + { + return true; + } + + return false; + } + + void EngineScreenCtrl::GoToScreen(ProjectManagerScreen screen) + { + if (screen == m_engineSettingsScreen->GetScreenEnum()) + { + m_tabWidget->setCurrentWidget(m_engineSettingsScreen); + m_engineSettingsScreen->NotifyCurrentScreen(); + } + else if (screen == m_gemRepoScreen->GetScreenEnum()) + { + m_tabWidget->setCurrentWidget(m_gemRepoScreen); + m_gemRepoScreen->NotifyCurrentScreen(); + } + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h index 9e799f13e7..b7142ba226 100644 --- a/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h +++ b/Code/Tools/ProjectManager/Source/EngineScreenCtrl.h @@ -11,6 +11,8 @@ #include #endif +QT_FORWARD_DECLARE_CLASS(QTabWidget) + namespace O3DE::ProjectManager { QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen) @@ -26,7 +28,10 @@ namespace O3DE::ProjectManager QString GetTabText() override; bool IsTab() override; + bool ContainsScreen(ProjectManagerScreen screen) override; + void GoToScreen(ProjectManagerScreen screen) override; + QTabWidget* m_tabWidget = nullptr; EngineSettingsScreen* m_engineSettingsScreen = nullptr; GemRepoScreen* m_gemRepoScreen = nullptr; }; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 9fca6040d4..0ecea215bf 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -8,17 +8,21 @@ #include #include +#include + #include #include #include #include -#include +#include +#include namespace O3DE::ProjectManager { - CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, QWidget* parent) + CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent) : QWidget(parent) , m_gemModel(gemModel) + , m_downloadController(downloadController) { setObjectName("GemCatalogCart"); @@ -42,6 +46,9 @@ namespace O3DE::ProjectManager hLayout->addWidget(closeButton); m_layout->addLayout(hLayout); + // downloading gems + CreateDownloadSection(); + // added CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=] { @@ -149,6 +156,109 @@ namespace O3DE::ProjectManager update(); } + void CartOverlayWidget::CreateDownloadSection() + { + QWidget* widget = new QWidget(); + widget->setFixedWidth(s_width); + m_layout->addWidget(widget); + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setAlignment(Qt::AlignTop); + widget->setLayout(layout); + + QLabel* titleLabel = new QLabel(); + titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel"); + layout->addWidget(titleLabel); + + titleLabel->setText(tr("Gems to be installed")); + + // Create header section + QWidget* downloadingGemsWidget = new QWidget(); + downloadingGemsWidget->setObjectName("GemCatalogCartOverlayGemDownloadHeader"); + layout->addWidget(downloadingGemsWidget); + QVBoxLayout* gemDownloadLayout = new QVBoxLayout(); + gemDownloadLayout->setMargin(0); + gemDownloadLayout->setAlignment(Qt::AlignTop); + downloadingGemsWidget->setLayout(gemDownloadLayout); + QLabel* processingQueueLabel = new QLabel("Processing Queue"); + gemDownloadLayout->addWidget(processingQueueLabel); + + QWidget* downloadingItemWidget = new QWidget(); + downloadingItemWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG"); + gemDownloadLayout->addWidget(downloadingItemWidget); + QVBoxLayout* downloadingItemLayout = new QVBoxLayout(); + downloadingItemLayout->setAlignment(Qt::AlignTop); + downloadingItemWidget->setLayout(downloadingItemLayout); + + auto update = [=](int downloadProgress) + { + if (m_downloadController->IsDownloadQueueEmpty()) + { + widget->hide(); + } + else + { + widget->setUpdatesEnabled(false); + // remove items + QLayoutItem* layoutItem = nullptr; + while ((layoutItem = downloadingItemLayout->takeAt(0)) != nullptr) + { + if (layoutItem->layout()) + { + // Gem info row + QLayoutItem* rowLayoutItem = nullptr; + while ((rowLayoutItem = layoutItem->layout()->takeAt(0)) != nullptr) + { + rowLayoutItem->widget()->deleteLater(); + } + layoutItem->layout()->deleteLater(); + } + if (layoutItem->widget()) + { + layoutItem->widget()->deleteLater(); + } + } + + // Setup gem download rows + const AZStd::vector& downloadQueue = m_downloadController->GetDownloadQueue(); + + QLabel* downloadsInProgessLabel = new QLabel(""); + downloadsInProgessLabel->setText( + QString("%1 %2").arg(downloadQueue.size()).arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress..."))); + downloadingItemLayout->addWidget(downloadsInProgessLabel); + + for (int downloadingGemNumber = 0; downloadingGemNumber < downloadQueue.size(); ++downloadingGemNumber) + { + QHBoxLayout* nameProgressLayout = new QHBoxLayout(); + TagWidget* newTag = new TagWidget(downloadQueue[downloadingGemNumber]); + nameProgressLayout->addWidget(newTag); + QLabel* progress = new QLabel(downloadingGemNumber == 0? QString("%1%").arg(downloadProgress) : tr("Queued")); + nameProgressLayout->addWidget(progress); + QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); + nameProgressLayout->addSpacerItem(spacer); + QLabel* cancelText = new QLabel(tr("Cancel")); + nameProgressLayout->addWidget(cancelText); + downloadingItemLayout->addLayout(nameProgressLayout); + QProgressBar* downloadProgessBar = new QProgressBar(); + downloadingItemLayout->addWidget(downloadProgessBar); + downloadProgessBar->setValue(downloadingGemNumber == 0 ? downloadProgress : 0); + } + + widget->setUpdatesEnabled(true); + widget->show(); + } + }; + + auto downloadEnded = [=](bool /*success*/) + { + update(0); // update the list to remove the gem that has finished + }; + // connect to download controller data changed + connect(m_downloadController, &DownloadController::GemDownloadProgress, this, update); + connect(m_downloadController, &DownloadController::Done, this, downloadEnded); + update(0); + } + QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector& gems) const { QStringList gemNames; @@ -160,9 +270,10 @@ namespace O3DE::ProjectManager return gemNames; } - CartButton::CartButton(GemModel* gemModel, QWidget* parent) + CartButton::CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent) : QWidget(parent) , m_gemModel(gemModel) + , m_downloadController(downloadController) { m_layout = new QHBoxLayout(); m_layout->setMargin(0); @@ -239,7 +350,7 @@ namespace O3DE::ProjectManager delete m_cartOverlay; } - m_cartOverlay = new CartOverlayWidget(m_gemModel, this); + m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this); connect(m_cartOverlay, &QWidget::destroyed, this, [=] { // Reset the overlay pointer on destruction to prevent dangling pointers. @@ -265,7 +376,7 @@ namespace O3DE::ProjectManager } } - GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent) + GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent) : QFrame(parent) { QHBoxLayout* hLayout = new QHBoxLayout(); @@ -293,8 +404,30 @@ namespace O3DE::ProjectManager hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed)); - CartButton* cartButton = new CartButton(gemModel); + CartButton* cartButton = new CartButton(gemModel, downloadController); hLayout->addWidget(cartButton); + + hLayout->addSpacing(16); + + // Separating line + QFrame* vLine = new QFrame(); + vLine->setFrameShape(QFrame::VLine); + vLine->setObjectName("verticalSeparatingLine"); + hLayout->addWidget(vLine); + + hLayout->addSpacing(16); + + QMenu* gemMenu = new QMenu(this); + m_openGemReposAction = gemMenu->addAction(tr("Show Gem Repos")); + + connect(m_openGemReposAction, &QAction::triggered, this,[this](){ emit OpenGemsRepo(); }); + + QPushButton* gemMenuButton = new QPushButton(this); + gemMenuButton->setObjectName("gemCatalogMenuButton"); + gemMenuButton->setMenu(gemMenu); + gemMenuButton->setIcon(QIcon(":/menu.svg")); + gemMenuButton->setIconSize(QSize(36, 24)); + hLayout->addWidget(gemMenuButton); } void GemCatalogHeaderWidget::ReinitForProject() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 2cfda4c790..fa381e54ae 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -15,12 +15,15 @@ #include #include #include +#include + #include #include #include #include #include #include +#include #endif namespace O3DE::ProjectManager @@ -31,16 +34,18 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr); + CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); private: QStringList ConvertFromModelIndices(const QVector& gems) const; using GetTagIndicesCallback = AZStd::function()>; void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); + void CreateDownloadSection(); QVBoxLayout* m_layout = nullptr; GemModel* m_gemModel = nullptr; + DownloadController* m_downloadController = nullptr; inline constexpr static int s_width = 240; }; @@ -51,7 +56,7 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - CartButton(GemModel* gemModel, QWidget* parent = nullptr); + CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr); ~CartButton(); void ShowOverlay(); @@ -64,6 +69,7 @@ namespace O3DE::ProjectManager QLabel* m_countLabel = nullptr; QPushButton* m_dropDownButton = nullptr; CartOverlayWidget* m_cartOverlay = nullptr; + DownloadController* m_downloadController = nullptr; inline constexpr static int s_iconSize = 24; inline constexpr static int s_arrowDownIconSize = 8; @@ -75,13 +81,18 @@ namespace O3DE::ProjectManager Q_OBJECT // AUTOMOC public: - explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr); + explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent = nullptr); ~GemCatalogHeaderWidget() = default; void ReinitForProject(); + signals: + void OpenGemsRepo(); + private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; inline constexpr static int s_height = 60; + + QAction* m_openGemReposAction = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 945878768d..cbe36400cf 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -32,9 +33,13 @@ namespace O3DE::ProjectManager vLayout->setSpacing(0); setLayout(vLayout); - m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel); + m_downloadController = new DownloadController(); + + m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController); vLayout->addWidget(m_headerWidget); + connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); + QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); vLayout->addLayout(hLayout); @@ -191,6 +196,27 @@ namespace O3DE::ProjectManager return EnableDisableGemsResult::Success; } + void GemCatalogScreen::HandleOpenGemRepo() + { + QVector gemsToBeAdded = m_gemModel->GatherGemsToBeAdded(true); + QVector gemsToBeRemoved = m_gemModel->GatherGemsToBeRemoved(true); + + if (!gemsToBeAdded.empty() || !gemsToBeRemoved.empty()) + { + QMessageBox::StandardButton warningResult = QMessageBox::warning( + nullptr, "Pending Changes", + "There are some unsaved changes to the gem selection,
they will be lost if you change screens.
Are you sure?", + QMessageBox::No | QMessageBox::Yes); + + if (warningResult != QMessageBox::Yes) + { + return; + } + } + + emit ChangeScreenRequest(ProjectManagerScreen::GemRepos); + } + ProjectManagerScreen GemCatalogScreen::GetScreenEnum() { return ProjectManagerScreen::GemCatalog; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 72e8d44f65..456a5fe91c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -39,8 +39,13 @@ namespace O3DE::ProjectManager EnableDisableGemsResult EnableDisableGemsForProject(const QString& projectPath); GemModel* GetGemModel() const { return m_gemModel; } + DownloadController* GetDownloadController() const { return m_downloadController; } + + private slots: + void HandleOpenGemRepo(); private: + void FillModel(const QString& projectPath); GemListView* m_gemListView = nullptr; @@ -50,5 +55,6 @@ namespace O3DE::ProjectManager GemSortFilterProxyModel* m_proxModel = nullptr; QVBoxLayout* m_filterWidgetLayout = nullptr; GemFilterWidget* m_filterWidget = nullptr; + DownloadController* m_downloadController = nullptr; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 5973fb2eda..99563f5f05 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -301,6 +301,7 @@ namespace O3DE::ProjectManager m_enableGemProject = pybind11::module::import("o3de.enable_gem"); m_disableGemProject = pybind11::module::import("o3de.disable_gem"); m_editProjectProperties = pybind11::module::import("o3de.project_properties"); + m_download = pybind11::module::import("o3de.download"); m_repo = pybind11::module::import("o3de.repo"); m_pathlib = pybind11::module::import("pathlib"); @@ -1116,4 +1117,30 @@ namespace O3DE::ProjectManager std::sort(gemRepos.begin(), gemRepos.end()); return AZ::Success(AZStd::move(gemRepos)); } + + AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) + { + bool downloadSucceeded = false; + auto result = ExecuteWithLockErrorHandling( + [&] + { + auto downloadResult = m_download.attr("download_gem")( + QString_To_Py_String(gemName), // gem name + pybind11::none(), // destination path + false// skip auto register + ); + downloadSucceeded = (downloadResult.cast() == 0); + }); + + if (!result.IsSuccess()) + { + return result; + } + else if (!downloadSucceeded) + { + return AZ::Failure("Failed to download gem."); + } + + return AZ::Success(); + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 2a36372809..43bec29945 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -63,6 +63,7 @@ namespace O3DE::ProjectManager bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; + AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); @@ -89,6 +90,7 @@ namespace O3DE::ProjectManager pybind11::handle m_enableGemProject; pybind11::handle m_disableGemProject; pybind11::handle m_editProjectProperties; + pybind11::handle m_download; pybind11::handle m_repo; pybind11::handle m_pathlib; }; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 2d04147770..132633ece6 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -200,6 +200,8 @@ namespace O3DE::ProjectManager * @return A list of gem repo infos. */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; + + virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/Source/ScreenWidget.h b/Code/Tools/ProjectManager/Source/ScreenWidget.h index 9563fc2f6a..148dcdb8c8 100644 --- a/Code/Tools/ProjectManager/Source/ScreenWidget.h +++ b/Code/Tools/ProjectManager/Source/ScreenWidget.h @@ -47,6 +47,14 @@ namespace O3DE::ProjectManager return tr("Missing"); } + virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen) + { + return false; + } + virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen) + { + } + //! Notify this screen it is the current screen virtual void NotifyCurrentScreen() { @@ -55,7 +63,7 @@ namespace O3DE::ProjectManager signals: void ChangeScreenRequest(ProjectManagerScreen screen); - void GotoPreviousScreenRequest(); + void GoToPreviousScreenRequest(); void ResetScreenRequest(ProjectManagerScreen screen); void NotifyCurrentProject(const QString& projectPath); void NotifyBuildProject(const ProjectInfo& projectInfo); diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index df0bdb29f4..314765def0 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -83,11 +83,28 @@ namespace O3DE::ProjectManager bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit) { + ScreenWidget* newScreen = nullptr; + const auto iterator = m_screenMap.find(screen); if (iterator != m_screenMap.end()) + { + newScreen = iterator.value(); + } + else + { + // Check if screen is contained by another screen + for (ScreenWidget* checkingScreen : m_screenMap) + { + if (checkingScreen->ContainsScreen(screen)) + { + newScreen = checkingScreen; + break; + } + } + } + if (newScreen) { ScreenWidget* currentScreen = GetCurrentScreen(); - ScreenWidget* newScreen = iterator.value(); if (currentScreen != newScreen) { @@ -109,6 +126,11 @@ namespace O3DE::ProjectManager newScreen->NotifyCurrentScreen(); + if (iterator == m_screenMap.end()) + { + newScreen->GoToScreen(screen); + } + return true; } } @@ -116,7 +138,7 @@ namespace O3DE::ProjectManager return false; } - bool ScreensCtrl::GotoPreviousScreen() + bool ScreensCtrl::GoToPreviousScreen() { if (!m_screenVisitOrder.isEmpty()) { @@ -171,7 +193,7 @@ namespace O3DE::ProjectManager m_screenMap.insert(screen, newScreen); connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen); - connect(newScreen, &ScreenWidget::GotoPreviousScreenRequest, this, &ScreensCtrl::GotoPreviousScreen); + connect(newScreen, &ScreenWidget::GoToPreviousScreenRequest, this, &ScreensCtrl::GoToPreviousScreen); connect(newScreen, &ScreenWidget::ResetScreenRequest, this, &ScreensCtrl::ResetScreen); connect(newScreen, &ScreenWidget::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject); connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject); diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.h b/Code/Tools/ProjectManager/Source/ScreensCtrl.h index 7132d64dd0..ab69a09ea3 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.h +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.h @@ -41,7 +41,7 @@ namespace O3DE::ProjectManager public slots: bool ChangeToScreen(ProjectManagerScreen screen); bool ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit = true); - bool GotoPreviousScreen(); + bool GoToPreviousScreen(); void ResetScreen(ProjectManagerScreen screen); void ResetAllScreens(); void DeleteScreen(ProjectManagerScreen screen); diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index e952ada57a..1c8f9a6931 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -40,6 +40,10 @@ namespace O3DE::ProjectManager m_updateSettingsScreen = new UpdateProjectSettingsScreen(); m_gemCatalogScreen = new GemCatalogScreen(); + connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, [this](ProjectManagerScreen screen){ + emit ChangeScreenRequest(screen); + }); + m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); @@ -118,7 +122,7 @@ namespace O3DE::ProjectManager { if (UpdateProjectSettings(true)) { - emit GotoPreviousScreenRequest(); + emit GoToPreviousScreenRequest(); } } } @@ -136,6 +140,11 @@ namespace O3DE::ProjectManager } else if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen) { + if (!m_gemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty()) + { + QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing.")); + return; + } // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. const GemCatalogScreen::EnableDisableGemsResult result = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); if (result == GemCatalogScreen::EnableDisableGemsResult::Failed) diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index fd8389ca4f..e2e35717f6 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -29,6 +29,10 @@ set(FILES Source/FormImageBrowseEditWidget.cpp Source/GemsSubWidget.h Source/GemsSubWidget.cpp + Source/DownloadController.h + Source/DownloadController.cpp + Source/DownloadWorker.h + Source/DownloadWorker.cpp Source/PathValidator.h Source/PathValidator.cpp Source/ProjectManagerWindow.h diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp index 79a39ff793..3edc7bbe67 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslCompiler.cpp @@ -404,18 +404,18 @@ namespace AZ bool AzslCompiler::ParseSrgPopulateRootConstantData(const rapidjson::Document& input, RootConstantData& rootConstantData) const { - if (input.HasMember("InlineConstantBuffer")) + if (input.HasMember("RootConstantBuffer")) { - const rapidjson::Value& rootConstantBufferValue = input["InlineConstantBuffer"]; - AZ_Assert(rootConstantBufferValue.IsObject(), "InlineConstantBuffer is not an object"); + const rapidjson::Value& rootConstantBufferValue = input["RootConstantBuffer"]; + AZ_Assert(rootConstantBufferValue.IsObject(), "RootConstantBuffer is not an object"); for (rapidjson::Value::ConstMemberIterator itr = rootConstantBufferValue.MemberBegin(); itr != rootConstantBufferValue.MemberEnd(); ++itr) { AZStd::string_view rootConstantBufferMemberName = itr->name.GetString(); const rapidjson::Value& rootConstantBufferMemberValue = itr->value; - if (rootConstantBufferMemberName == "bufferForInlineConstants") + if (rootConstantBufferMemberName == "bufferForRootConstants") { - AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForInlineConstants is not an object"); + AZ_Assert(rootConstantBufferMemberValue.IsObject(), "bufferForRootConstants is not an object"); for (rapidjson::Value::ConstMemberIterator itr2 = rootConstantBufferMemberValue.MemberBegin(); itr2 != rootConstantBufferMemberValue.MemberEnd(); ++itr2) { @@ -442,14 +442,14 @@ namespace AZ } } } - else if (rootConstantBufferMemberName == "inputsForInlineConstants") + else if (rootConstantBufferMemberName == "inputsForRootConstants") { - AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForInlineConstants is not an array"); + AZ_Assert(rootConstantBufferMemberValue.IsArray(), "inputsForRootConstants is not an array"); for (rapidjson::Value::ConstValueIterator itr2 = rootConstantBufferMemberValue.Begin(); itr2 != rootConstantBufferMemberValue.End(); ++itr2) { const rapidjson::Value& rootConstantBufferValue2 = *itr2; - AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForInlineConstants is not an object"); + AZ_Assert(rootConstantBufferValue2.IsObject(), "Entry in inputsForRootConstants is not an object"); SrgConstantData rootConstantInputs; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp index cacb310918..ffc5f40ef4 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/AzslShaderBuilderSystemComponent.cpp @@ -81,7 +81,7 @@ namespace AZ // Register Shader Asset Builder AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor; shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder"; - shaderAssetBuilderDescriptor.m_version = 104; // ATOM-15871 + shaderAssetBuilderDescriptor.m_version = 105; // [AZSL] Changing inlineConstant to rootConstant keyword work. // .shader file changes trigger rebuilds shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderAssetBuilderDescriptor.m_busId = azrtti_typeid(); @@ -96,7 +96,7 @@ namespace AZ shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder"; // Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update // ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder". - shaderVariantAssetBuilderDescriptor.m_version = 25; // ATOM-15871 + shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work. shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid(); shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2); diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp index e9ac18a432..2c87a79068 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderBuilderUtility.cpp @@ -183,7 +183,7 @@ namespace AZ // access the root constants reflection if (!azslc.ParseSrgPopulateRootConstantData( outcomes[AzslSubProducts::srg].GetValue(), - rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) + rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section) { AZ_Error(builderName, false, "Failed to obtain root constant data reflection"); return AssetBuilderSDK::ProcessJobResult_Failed; diff --git a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp index 27ae90dcdc..bb40baca7d 100644 --- a/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp +++ b/Gems/Atom/Asset/Shader/Code/Source/Editor/ShaderVariantAssetBuilder.cpp @@ -561,7 +561,7 @@ namespace AZ // Access the root constants reflection if (!azslCompiler.ParseSrgPopulateRootConstantData( jsonOutcome.GetValue(), - rootConstantData)) // consuming data from --srg ("InlineConstantBuffer" subjson section) + rootConstantData)) // consuming data from --srg ("RootConstantBuffer" subjson section) { AZ_Error(ShaderVariantAssetBuilderName, false, "Failed to obtain root constant data reflection"); return false; diff --git a/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype index 17209771e5..30062205a8 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/ReflectionProbe/ReflectionProbeVisualization.materialtype @@ -1,7 +1,7 @@ { "description": "Base material for the reflection probe visualization model.", + "version": 1, "propertyLayout": { - "version": 1, "properties": { "general": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype index 74246f85db..d05f03c9a9 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.materialtype @@ -1,7 +1,7 @@ { "description": "Base material for the reflection probe visualization model.", + "version": 1, "propertyLayout": { - "version": 1, "properties": { "settings": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype index bed3b69c4c..f4bcfb2673 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/EnhancedPBR.materialtype @@ -1,7 +1,7 @@ { "description": "Material Type with properties used to define Enhanced PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model, with advanced features like subsurface scattering, transmission, and anisotropy.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "baseColor", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype index e4da9c6022..e2a05aa916 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/Skin.materialtype @@ -1,7 +1,7 @@ { "description": "Material Type tailored for rendering skin, with support for blended wrinkle maps that work with animated vertex blend shapes.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "baseColor", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype index 5fa0dcb217..107b525ae4 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR.materialtype @@ -1,7 +1,7 @@ { "description": "Similar to StandardPBR but supports multiple layers blended together.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "blend", diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.shader index 28322d68ed..00efca6056 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass.shader @@ -48,6 +48,14 @@ } ] }, + + "Supervariants": + [ + { + "Name": "", + "PlusArguments": "--no-alignment-validation" + } + ], "DrawList" : "forward" } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader index 42366d6067..983245ffb0 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardMultilayerPBR_ForwardPass_EDS.shader @@ -49,5 +49,13 @@ ] }, + "Supervariants": + [ + { + "Name": "", + "PlusArguments": "--no-alignment-validation" + } + ], + "DrawList" : "forward" } diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype index 6eb82b85ae..7527a7658a 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype +++ b/Gems/Atom/Feature/Common/Assets/Materials/Types/StandardPBR.materialtype @@ -1,7 +1,7 @@ { "description": "Material Type with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "baseColor", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset index 7770b326a6..81b7d36484 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset +++ b/Gems/Atom/Feature/Common/Assets/Passes/PassTemplates.azasset @@ -469,12 +469,12 @@ "Path": "Passes/OpaqueParent.pass" }, { - "Name": "ThumbnailPipeline", - "Path": "Passes/ThumbnailPipeline.pass" + "Name": "ToolsPipeline", + "Path": "Passes/ToolsPipeline.pass" }, { - "Name": "ThumbnailPipelineRenderToTexture", - "Path": "Passes/ThumbnailPipelineRenderToTexture.pass" + "Name": "ToolsPipelineRenderToTexture", + "Path": "Passes/ToolsPipelineRenderToTexture.pass" }, { "Name": "TransparentParentTemplate", diff --git a/Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipeline.pass b/Gems/Atom/Feature/Common/Assets/Passes/ToolsPipeline.pass similarity index 99% rename from Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipeline.pass rename to Gems/Atom/Feature/Common/Assets/Passes/ToolsPipeline.pass index 932b6ac435..51c169348b 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipeline.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/ToolsPipeline.pass @@ -4,7 +4,7 @@ "ClassName": "PassAsset", "ClassData": { "PassTemplate": { - "Name": "ThumbnailPipeline", + "Name": "ToolsPipeline", "PassClass": "ParentPass", "Slots": [ { diff --git a/Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipelineRenderToTexture.pass b/Gems/Atom/Feature/Common/Assets/Passes/ToolsPipelineRenderToTexture.pass similarity index 88% rename from Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipelineRenderToTexture.pass rename to Gems/Atom/Feature/Common/Assets/Passes/ToolsPipelineRenderToTexture.pass index 11e2cb717a..b98ec46d0e 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/ThumbnailPipelineRenderToTexture.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/ToolsPipelineRenderToTexture.pass @@ -4,7 +4,7 @@ "ClassName": "PassAsset", "ClassData": { "PassTemplate": { - "Name": "ThumbnailPipelineRenderToTexture", + "Name": "ToolsPipelineRenderToTexture", "PassClass": "RenderToTexturePass", "PassData": { "$type": "RenderToTexturePassData", @@ -15,7 +15,7 @@ "PassRequests": [ { "Name": "Pipeline", - "TemplateName": "ThumbnailPipeline", + "TemplateName": "ToolsPipeline", "Connections": [ { "LocalSlot": "SwapChainOutput", diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl index 099a6394d4..a380a0a547 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl @@ -138,7 +138,7 @@ float3 ColorGrade(float3 frameColor) PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight); frameColor = max(frameColor, 0.0); frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation), PassSrg::m_colorAdjustmentWeight); - + frameColor = max(frameColor, 0.0); frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight, PassSrg::m_splitToneShadowsColor, PassSrg::m_splitToneHighlightsColor); frameColor = ColorGradeChannelMixer(frameColor, PassSrg::m_channelMixingRed, PassSrg::m_channelMixingGreen, PassSrg::m_channelMixingBlue); @@ -147,8 +147,7 @@ float3 ColorGrade(float3 frameColor) PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight, PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor); - - frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight); frameColor = lerp(frameColor, ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift), PassSrg::m_finalAdjustmentWeight); + frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight); return max(frameColor.rgb, 0.0); } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.shader b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.shader index 5512b1ad4d..475a1b1b54 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.shader +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/RayTracing/RayTracingSrgs.shader @@ -37,7 +37,7 @@ [ { "Name": "", - "PlusArguments": "", + "PlusArguments": "--no-alignment-validation", "MinusArguments": "--strip-unused-srgs" } ] diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 123e5da7ba..e13465307a 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -161,8 +161,8 @@ set(FILES Passes/LutGeneration.pass Passes/MainPipeline.pass Passes/MainPipelineRenderToTexture.pass - Passes/ThumbnailPipeline.pass - Passes/ThumbnailPipelineRenderToTexture.pass + Passes/ToolsPipeline.pass + Passes/ToolsPipelineRenderToTexture.pass Passes/MeshMotionVector.pass Passes/ModulateTexture.pass Passes/MorphTarget.pass diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h index befbb7c990..29371618cf 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialPropertyValueSerializer.h @@ -24,6 +24,13 @@ namespace AZ AZ_RTTI(AZ::RPI::JsonMaterialPropertyValueSerializer, "{A52B1ED8-C849-4269-9AA7-9D0814D2EC59}", BaseJsonSerializer); AZ_CLASS_ALLOCATOR_DECL; + //! A LoadContext object must be passed down to the serializer via JsonDeserializerContext::GetMetadata().Add(...) + struct LoadContext + { + AZ_TYPE_INFO(JsonMaterialPropertyValueSerializer::LoadContext, "{5E0A891A-27F6-4AD7-88A5-B9EA50F88B45}"); + uint32_t m_materialTypeVersion; //!< The version number from the .materialtype file + }; + JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, JsonDeserializerContext& context) override; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h index 02607a7954..a67477f061 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialSourceData.h @@ -50,7 +50,7 @@ namespace AZ AZStd::string m_parentMaterial; //!< The immediate parent of this material - uint32_t m_propertyLayoutVersion = 0; //!< The version of the property layout, defined in the material type, which was used to configure this material + uint32_t m_materialTypeVersion = 0; //!< The version of the material type that was used to configure this material struct Property { @@ -64,6 +64,18 @@ namespace AZ PropertyGroupMap m_properties; + enum class ApplyVersionUpdatesResult + { + Failed, + NoUpdates, + UpdatesApplied + }; + + //! Checks the material type version and potentially applies a series of property changes (most common are simple property renames) + //! based on the MaterialTypeAsset's version update procedure. + //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths. + ApplyVersionUpdatesResult ApplyVersionUpdates(AZStd::string_view materialSourceFilePath = ""); + //! Creates a MaterialAsset from the MaterialSourceData content. //! @param assetId ID for the MaterialAsset //! @param materialSourceFilePath Indicates the path of the .material file that the MaterialSourceData represents. Used for resolving file-relative paths. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h index 04b6222404..1234b15f95 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Edit/Material/MaterialTypeSourceData.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace AZ { @@ -119,12 +120,36 @@ namespace AZ using PropertyList = AZStd::vector; + struct VersionUpdatesRenameOperationDefinition + { + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::VersionUpdatesRenameOperationDefinition, "{F2295489-E15A-46CC-929F-8D42DEDBCF14}"); + + AZStd::string m_operation; + + AZStd::string m_renameFrom; + AZStd::string m_renameTo; + }; + + // TODO: Support script operations--At that point, we'll likely need to replace VersionUpdatesRenameOperationDefinition with a more generic + // data structure that has a custom JSON serialize. We will only be supporting rename for now. + using VersionUpdateActions = AZStd::vector; + + struct VersionUpdateDefinition + { + AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::VersionUpdateDefinition, "{2C9D3B91-0585-4BC9-91D2-4CF0C71BC4B7}"); + + uint32_t m_toVersion; + VersionUpdateActions m_actions; + }; + + using VersionUpdates = AZStd::vector; + struct PropertyLayout { AZ_TYPE_INFO(AZ::RPI::MaterialTypeSourceData::PropertyLayout, "{AE53CF3F-5C3B-44F5-B2FB-306F0EB06393}"); - - //! Indicates the version of the set of available properties. Can be used to detect materials that might need to be updated. - uint32_t m_version = 0; + + //! This field is unused, and has been replaced by MaterialTypeSourceData::m_version below. It is kept for legacy file compatibility to suppress warnings and errors. + uint32_t m_versionOld = 0; //! List of groups that will contain the available properties AZStd::vector m_groups; @@ -135,6 +160,11 @@ namespace AZ AZStd::string m_description; + //! Version 1 is the default and should not contain any version update. + uint32_t m_version = 1; + + VersionUpdates m_versionUpdates; + PropertyLayout m_propertyLayout; //! A list of shader variants that are always used at runtime; they cannot be turned off @@ -153,7 +183,12 @@ namespace AZ const GroupDefinition* FindGroup(AZStd::string_view groupName) const; - const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const; + //! Searches for a specific property. + //! Note this function can find properties using old versions of the property name; in that case, + //! the name in the returned PropertyDefinition* will not match the @propertyName that was searched for. + //! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied. + //! @return the requested property, or null if it could not be found + const PropertyDefinition* FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion = 0) const; //! Construct a complete list of group definitions, including implicit groups, arranged in the same order as the source data //! Groups with the same name will be consolidated into a single entry @@ -179,6 +214,11 @@ namespace AZ bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const; Outcome> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const; + + //! Possibly renames @propertyId based on the material version update steps. + //! @param materialTypeVersion indicates the version number of the property name being passed in. Only renames above this version number will be applied. + //! @return true if the property was renamed + bool ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion = 0) const; }; //! The wrapper class for derived material functors. diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h index 6ca3bca652..2a1de6debd 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialAsset.h @@ -22,6 +22,7 @@ namespace UnitTest { class MaterialTests; + class MaterialAssetTests; } namespace AZ @@ -42,10 +43,12 @@ namespace AZ , public MaterialReloadNotificationBus::Handler , public AssetInitBus::Handler { + friend class MaterialVersionUpdate; friend class MaterialAssetCreator; friend class MaterialAssetHandler; friend class MaterialAssetCreatorCommon; friend class UnitTest::MaterialTests; + friend class UnitTest::MaterialAssetTests; public: AZ_RTTI(MaterialAsset, "{522C7BE0-501D-463E-92C6-15184A2B7AD8}", AZ::Data::AssetData); @@ -119,6 +122,10 @@ namespace AZ //! from m_materialTypeAsset. void RealignPropertyValuesAndNames(); + //! Checks the material type version and potentially applies a series of property changes (most common are simple property renames) + //! based on the MaterialTypeAsset's version update procedure. + void ApplyVersionUpdates(); + //! Called by asset creators to assign the asset to a ready state. void SetReady(); @@ -143,6 +150,10 @@ namespace AZ //! If empty, this implies that m_propertyValues is aligned with the entries in m_materialPropertiesLayout. AZStd::vector m_propertyNames; + //! The materialTypeVersion this materialAsset was based of. If the versions do not match at runtime when a + //! materialTypeAsset is loaded, an update will be performed on m_propertyNames if populated. + uint32_t m_materialTypeVersion = 1; + //! A flag to determine if m_propertyValues needs to be aligned with MaterialPropertiesLayout. Set to true whenever //! m_materialTypeAsset is reinitializing. bool m_isDirty = true; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h index e705a8041b..9bc0e018d3 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAsset.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace AZ { @@ -123,6 +124,11 @@ namespace AZ //! Returns a map from the UV shader inputs to a custom name. MaterialUvNameMap GetUvNameMap() const; + //! Returns the version of the MaterialTypeAsset. + uint32_t GetVersion() const; + + const AZStd::vector& GetMaterialVersionUpdateList() const { return m_materialVersionUpdates; } + private: bool PostLoadInit() override; @@ -162,6 +168,12 @@ namespace AZ //! Index in @m_shaderCollection of the shader asset that contains the ObjectSrg. uint32_t m_objectSrgShaderIndex = InvalidShaderIndex; + //! The version of this MaterialTypeAsset. If the version is greater than 1, actions performed + //! to update this MaterialTypeAsset will be in m_materialVersionUpdateMap + uint32_t m_version = 1; + + //! Contains actions to perform for each material update version. + AZStd::vector m_materialVersionUpdates; }; class MaterialTypeAssetHandler : public AssetHandler diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h index 70488245a2..5e5f94da6d 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h @@ -38,6 +38,11 @@ namespace AZ void AddShader(const AZ::Data::Asset& shaderAsset, const ShaderVariantId& shaderVaraintId = ShaderVariantId{}, const AZ::Name& shaderTag = Uuid::CreateRandom().ToString()); void AddShader(const AZ::Data::Asset& shaderAsset, const AZ::Name& shaderTag); + //! Sets the version of the MaterialTypeAsset + void SetVersion(uint32_t version); + //! Adds a version update object into the MaterialTypeAsset + void AddVersionUpdate(const MaterialVersionUpdate& materialVersionUpdate); + //! Indicates that this MaterialType will own the specified shader option. //! Material-owned shader options can be connected to material properties (either directly or through functors). //! They cannot be accessed externally (for example, through the Material::SetSystemShaderOption() function). @@ -112,6 +117,7 @@ namespace AZ //! Saves the per-material SRG layout in m_shaderResourceGroupLayout for easier access void CacheMaterialSrgLayout(); + bool ValidateMaterialVersion(); bool ValidateBeginMaterialProperty(); bool ValidateEndMaterialProperty(); diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h new file mode 100644 index 0000000000..eb71ad9cb7 --- /dev/null +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h @@ -0,0 +1,62 @@ +/* + * 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 + * + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + class MaterialAsset; + + // This class contains a toVersion and a list of actions to specify what operations were performed to upgrade a materialType. + class MaterialVersionUpdate + { + public: + AZ_TYPE_INFO(AZ::RPI::MaterialVersionUpdate, "{B36E7712-AED8-46AA-AFE0-01F8F884C44A}"); + + static void Reflect(ReflectContext* context); + + // At this time, the only supported operation is rename. If/when we add more actions in the future, + // we'll need to improve this, possibly with some virtual interface or union data. + struct RenamePropertyAction + { + AZ_TYPE_INFO(AZ::RPI::MaterialVersionUpdate::RenameAction, "{A1FBEB19-EA05-40F0-9700-57D048DF572B}"); + + static void Reflect(ReflectContext* context); + + AZ::Name m_fromPropertyId; + AZ::Name m_toPropertyId; + }; + + explicit MaterialVersionUpdate() = default; + explicit MaterialVersionUpdate(uint32_t toVersion); + + uint32_t GetVersion() const; + void SetVersion(uint32_t toVersion); + + //! Apply version updates to the given material asset. + //! @return true if any changes were made + bool ApplyVersionUpdates(MaterialAsset& materialAsset) const; + + using Actions = AZStd::vector; + const Actions& GetActions() const; + void AddAction(const RenamePropertyAction& action); + + private: + uint32_t m_toVersion; + Actions m_actions; + }; + + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp index 86ea8ab688..e9cebf29c7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Builders/Material/MaterialBuilder.cpp @@ -47,7 +47,7 @@ namespace AZ { AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor; materialBuilderDescriptor.m_name = JobKey; - materialBuilderDescriptor.m_version = 109; // Changed "id" to "name" in serialization + materialBuilderDescriptor.m_version = 110; // Material version auto update feature materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); materialBuilderDescriptor.m_busId = azrtti_typeid(); @@ -287,6 +287,11 @@ namespace AZ return {}; } + if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == material.GetValue().ApplyVersionUpdates(materialSourceFilePath)) + { + return {}; + } + auto materialAssetOutcome = material.GetValue().CreateMaterialAsset(Uuid::CreateRandom(), materialSourceFilePath, true); if (!materialAssetOutcome.IsSuccess()) { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp index 3b2d36451a..5e04365ffb 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialPropertyValueSerializer.cpp @@ -62,6 +62,8 @@ namespace AZ return context.Report(JsonSerializationResult::Tasks::ReadField, JsonSerializationResult::Outcomes::Catastrophic, "Material type reference not found."); } + const JsonMaterialPropertyValueSerializer::LoadContext* loadContext = context.GetMetadata().Find(); + // Construct the full property name (groupName.propertyName) by parsing it from the JSON path string. size_t startPropertyName = context.GetPath().Get().rfind('/'); size_t startGroupName = context.GetPath().Get().rfind('/', startPropertyName-1); @@ -70,7 +72,7 @@ namespace AZ JSR::ResultCode result(JSR::Tasks::ReadField); - auto propertyDefinition = materialType->FindProperty(groupName, propertyName); + auto propertyDefinition = materialType->FindProperty(groupName, propertyName, loadContext->m_materialTypeVersion); if (!propertyDefinition) { AZStd::string message = AZStd::string::format("Property '%.*s.%.*s' not found in material type.", AZ_STRING_ARG(groupName), AZ_STRING_ARG(propertyName)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp index f697f33a3f..5aed6b2993 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceData.cpp @@ -72,6 +72,62 @@ namespace AZ materialAssetCreator.SetPropertyValue(propertyId, entry.second); } } + + MaterialSourceData::ApplyVersionUpdatesResult MaterialSourceData::ApplyVersionUpdates(AZStd::string_view materialSourceFilePath) + { + AZStd::string materialTypeFullPath = AssetUtils::ResolvePathReference(materialSourceFilePath, m_materialType); + auto materialTypeSourceDataOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeFullPath); + if (!materialTypeSourceDataOutcome.IsSuccess()) + { + return ApplyVersionUpdatesResult::Failed; + } + + MaterialTypeSourceData materialTypeSourceData = materialTypeSourceDataOutcome.TakeValue(); + + if (m_materialTypeVersion == materialTypeSourceData.m_version) + { + return ApplyVersionUpdatesResult::NoUpdates; + } + + bool changesWereApplied = false; + + // Note that the only kind of property update currently supported is rename... + + for (auto& groupPair : m_properties) + { + PropertyMap& propertyMap = groupPair.second; + + PropertyMap newPropertyMap; + + for (auto& propertyPair : propertyMap) + { + MaterialPropertyId propertyId{groupPair.first, propertyPair.first}; + if (materialTypeSourceData.ApplyPropertyRenames(propertyId, m_materialTypeVersion)) + { + newPropertyMap[propertyId.GetPropertyName().GetStringView()] = propertyPair.second; + changesWereApplied = true; + } + else + { + newPropertyMap[propertyPair.first] = propertyPair.second; + } + } + + propertyMap = newPropertyMap; + } + + if (changesWereApplied) + { + AZ_Warning("MaterialSourceData", false, + "This material is based on version '%u' of '%s', but the material type is now at version '%u'. " + "Automatic updates are available. Consider updating the .material source file.", + m_materialTypeVersion, m_materialType.c_str(), materialTypeSourceData.m_version); + } + + m_materialTypeVersion = materialTypeSourceData.m_version; + + return changesWereApplied ? ApplyVersionUpdatesResult::UpdatesApplied : ApplyVersionUpdatesResult::NoUpdates; + } Outcome > MaterialSourceData::CreateMaterialAsset(Data::AssetId assetId, AZStd::string_view materialSourceFilePath, bool elevateWarnings, bool includeMaterialPropertyNames) const { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp index 5e4af07aae..2a504fc345 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialSourceDataSerializer.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -45,9 +46,9 @@ namespace AZ } result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_description, azrtti_typeid(), inputValue, "description", context)); - result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialType, azrtti_typeid(), inputValue, "materialType", context)); result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_parentMaterial, azrtti_typeid(), inputValue, "parentMaterial", context)); - result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_propertyLayoutVersion, azrtti_typeid(), inputValue, "propertyLayoutVersion", context)); + result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialType, azrtti_typeid(), inputValue, "materialType", context)); + result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_materialTypeVersion, azrtti_typeid(), inputValue, "materialTypeVersion", context)); if (materialSourceData->m_materialType.empty()) { @@ -118,6 +119,10 @@ namespace AZ context.GetMetadata().Add(AZStd::move(materialTypeData)); + JsonMaterialPropertyValueSerializer::LoadContext materialPropertyValueLoadContext; + materialPropertyValueLoadContext.m_materialTypeVersion = materialSourceData->m_materialTypeVersion; + context.GetMetadata().Add(materialPropertyValueLoadContext); + result.Combine(ContinueLoadingFromJsonObjectField(&materialSourceData->m_properties, azrtti_typeid(), inputValue, "properties", context)); if (result.GetProcessing() == JsonSerializationResult::Processing::Completed) @@ -146,9 +151,9 @@ namespace AZ JSR::ResultCode resultCode(JSR::Tasks::ReadField); resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "description", &materialSourceData->m_description, nullptr, azrtti_typeid(), context)); - resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialType", &materialSourceData->m_materialType, nullptr, azrtti_typeid(), context)); resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "parentMaterial", &materialSourceData->m_parentMaterial, nullptr, azrtti_typeid(), context)); - resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "propertyLayoutVersion", &materialSourceData->m_propertyLayoutVersion, nullptr, azrtti_typeid(), context)); + resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialType", &materialSourceData->m_materialType, nullptr, azrtti_typeid(), context)); + resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "materialTypeVersion", &materialSourceData->m_materialTypeVersion, nullptr, azrtti_typeid(), context)); resultCode.Combine(ContinueStoringToJsonObjectField(outputValue, "properties", &materialSourceData->m_properties, nullptr, azrtti_typeid(), context)); return context.Report(resultCode, "Processed material."); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp index 1cc57f4d47..74250647c3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Edit/Material/MaterialTypeSourceData.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,23 @@ namespace AZ serializeContext->RegisterGenericType(); + serializeContext->Class() + ->Version(1) + ->Field("op", &VersionUpdatesRenameOperationDefinition::m_operation) + ->Field("from", &VersionUpdatesRenameOperationDefinition::m_renameFrom) + ->Field("to", &VersionUpdatesRenameOperationDefinition::m_renameTo) + ; + + serializeContext->RegisterGenericType(); + + serializeContext->Class() + ->Version(1) + ->Field("toVersion", &VersionUpdateDefinition::m_toVersion) + ->Field("actions", &VersionUpdateDefinition::m_actions) + ; + + serializeContext->RegisterGenericType(); + serializeContext->Class() ->Version(2) ->Field("file", &ShaderVariantReferenceData::m_shaderFilePath) @@ -67,8 +85,8 @@ namespace AZ ; serializeContext->Class() - ->Version(1) - ->Field("version", &PropertyLayout::m_version) + ->Version(2) // Material Version Update + ->Field("version", &PropertyLayout::m_versionOld) ->Field("groups", &PropertyLayout::m_groups) ->Field("properties", &PropertyLayout::m_properties) ; @@ -76,8 +94,10 @@ namespace AZ serializeContext->RegisterGenericType(); serializeContext->Class() - ->Version(3) + ->Version(4) // Material Version Update ->Field("description", &MaterialTypeSourceData::m_description) + ->Field("version", &MaterialTypeSourceData::m_version) + ->Field("versionUpdates", &MaterialTypeSourceData::m_versionUpdates) ->Field("propertyLayout", &MaterialTypeSourceData::m_propertyLayout) ->Field("shaders", &MaterialTypeSourceData::m_shaderCollection) ->Field("functors", &MaterialTypeSourceData::m_materialFunctorSourceData) @@ -110,7 +130,38 @@ namespace AZ return nullptr; } - const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName) const + bool MaterialTypeSourceData::ApplyPropertyRenames(MaterialPropertyId& propertyId, uint32_t materialTypeVersion) const + { + bool renamed = false; + + for (const VersionUpdateDefinition& versionUpdate : m_versionUpdates) + { + if (materialTypeVersion >= versionUpdate.m_toVersion) + { + continue; + } + + for (const VersionUpdatesRenameOperationDefinition& action : versionUpdate.m_actions) + { + if (action.m_operation == "rename") + { + if (action.m_renameFrom == propertyId.GetFullName().GetStringView()) + { + propertyId = MaterialPropertyId::Parse(action.m_renameTo); + renamed = true; + } + } + else + { + AZ_Warning("Material source data", false, "Unsupported material version update operation '%s'", action.m_operation.c_str()); + } + } + } + + return renamed; + } + + const MaterialTypeSourceData::PropertyDefinition* MaterialTypeSourceData::FindProperty(AZStd::string_view groupName, AZStd::string_view propertyName, uint32_t materialTypeVersion) const { auto groupIter = m_propertyLayout.m_properties.find(groupName); if (groupIter == m_propertyLayout.m_properties.end()) @@ -126,6 +177,27 @@ namespace AZ } } + // Property has not been found, try looking for renames in the version history + + MaterialPropertyId propertyId = MaterialPropertyId{groupName, propertyName}; + ApplyPropertyRenames(propertyId, materialTypeVersion); + + // Do the search again with the new names + + groupIter = m_propertyLayout.m_properties.find(propertyId.GetGroupName().GetStringView()); + if (groupIter == m_propertyLayout.m_properties.end()) + { + return nullptr; + } + + for (const PropertyDefinition& property : groupIter->second) + { + if (property.m_name == propertyId.GetPropertyName().GetStringView()) + { + return &property; + } + } + return nullptr; } @@ -280,6 +352,41 @@ namespace AZ materialTypeAssetCreator.SetElevateWarnings(elevateWarnings); materialTypeAssetCreator.Begin(assetId); + if (m_propertyLayout.m_versionOld != 0) + { + materialTypeAssetCreator.ReportError( + "The field '/propertyLayout/version' is deprecated and moved to '/version'. " + "Please edit this material type source file and move the '\"version\": %u' setting up one level.", + m_propertyLayout.m_versionOld); + return Failure(); + } + + // Set materialtype version and add each version update object into MaterialTypeAsset. + materialTypeAssetCreator.SetVersion(m_version); + { + const AZ::Name rename = AZ::Name{ "rename" }; + + for (const auto& versionUpdate : m_versionUpdates) + { + MaterialVersionUpdate materialVersionUpdate{versionUpdate.m_toVersion}; + for (const auto& action : versionUpdate.m_actions) + { + if (action.m_operation == rename.GetStringView()) + { + materialVersionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction{ + AZ::Name{ action.m_renameFrom }, + AZ::Name{ action.m_renameTo } + }); + } + else + { + materialTypeAssetCreator.ReportWarning("Unsupported material version update operation '%s'", action.m_operation.c_str()); + } + } + materialTypeAssetCreator.AddVersionUpdate(materialVersionUpdate); + } + } + // Used to gather all the UV streams used in this material type from its shaders in alphabetical order. auto semanticComp = [](const RHI::ShaderSemantic& lhs, const RHI::ShaderSemantic& rhs) -> bool { diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp index 5c7af601fb..e9d8a42641 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAsset.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,9 @@ namespace AZ if (auto* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(10) + ->Version(11) // Material version update ->Field("materialTypeAsset", &MaterialAsset::m_materialTypeAsset) + ->Field("materialTypeVersion", &MaterialAsset::m_materialTypeVersion) ->Field("propertyValues", &MaterialAsset::m_propertyValues) ->Field("propertyNames", &MaterialAsset::m_propertyNames) ; @@ -103,9 +105,25 @@ namespace AZ AZStd::array_view MaterialAsset::GetPropertyValues() const { - if (!m_propertyNames.empty() && m_isDirty) + // If property names are included, they are used to re-arrange the property value list to align with the + // MaterialPropertiesLayout. This realignment would be necessary if the material type is updated with + // a new property layout, and a corresponding material is not reprocessed by the AP and continues using the + // old property layout. + if (!m_propertyNames.empty()) { - const_cast(this)->RealignPropertyValuesAndNames(); + const uint32_t materialTypeVersion = m_materialTypeAsset->GetVersion(); + if (m_materialTypeVersion < materialTypeVersion) + { + // It is possible that the material type has had some properties renamed. If that's the case, and this material + // is still referencing the old property layout, we need to apply any auto updates to rename those properties + // before using them to realign the property values. + const_cast(this)->ApplyVersionUpdates(); + } + + if (m_isDirty) + { + const_cast(this)->RealignPropertyValuesAndNames(); + } } return m_propertyValues; @@ -183,6 +201,40 @@ namespace AZ m_isDirty = false; } + void MaterialAsset::ApplyVersionUpdates() + { + if (m_materialTypeVersion == m_materialTypeAsset->GetVersion()) + { + return; + } + + const uint32_t originalVersion = m_materialTypeVersion; + + bool changesWereApplied = false; + + for (const MaterialVersionUpdate& versionUpdate : m_materialTypeAsset->GetMaterialVersionUpdateList()) + { + if (m_materialTypeVersion < versionUpdate.GetVersion()) + { + if (versionUpdate.ApplyVersionUpdates(*this)) + { + changesWereApplied = true; + m_materialTypeVersion = versionUpdate.GetVersion(); + } + } + } + + if (changesWereApplied) + { + AZ_Warning("MaterialAsset", false, + "This material is based on version '%u' of %s, but the material type is now at version '%u'. " + "Automatic updates are available. Consider updating the .material source file.", + originalVersion, m_materialTypeAsset.ToString().c_str(), m_materialTypeAsset->GetVersion()); + } + + m_materialTypeVersion = m_materialTypeAsset->GetVersion(); + } + void MaterialAsset::ReinitializeMaterialTypeAsset(Data::Asset asset) { Data::Asset newMaterialTypeAsset = { asset.GetAs(), AZ::Data::AssetLoadBehavior::PreLoad }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp index 79d19c15a2..b62a91a98d 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialAssetCreator.cpp @@ -23,6 +23,7 @@ namespace AZ if (ValidateIsReady()) { m_asset->m_materialTypeAsset = parentMaterial.m_materialTypeAsset; + m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion(); if (!m_asset->m_materialTypeAsset) { @@ -69,6 +70,7 @@ namespace AZ ReportError("MaterialTypeAsset is null"); return; } + m_asset->m_materialTypeVersion = m_asset->m_materialTypeAsset->GetVersion(); m_materialPropertiesLayout = m_asset->GetMaterialPropertiesLayout(); if (includeMaterialPropertyNames) diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp index 913c3206fb..522ba74119 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAsset.cpp @@ -37,6 +37,7 @@ namespace AZ void MaterialTypeAsset::Reflect(ReflectContext* context) { + MaterialVersionUpdate::Reflect(context); UvNamePair::Reflect(context); if (auto* serializeContext = azrtti_cast(context)) @@ -44,7 +45,9 @@ namespace AZ serializeContext->RegisterGenericType(); serializeContext->Class() - ->Version(4) // ATOM-15472 + ->Version(5) // Material version update + ->Field("Version", &MaterialTypeAsset::m_version) + ->Field("VersionUpdates", &MaterialTypeAsset::m_materialVersionUpdates) ->Field("ShaderCollection", &MaterialTypeAsset::m_shaderCollection) ->Field("MaterialFunctors", &MaterialTypeAsset::m_materialFunctors) ->Field("MaterialSrgShaderIndex", &MaterialTypeAsset::m_materialSrgShaderIndex) @@ -161,6 +164,11 @@ namespace AZ return m_uvNameMap; } + uint32_t MaterialTypeAsset::GetVersion() const + { + return m_version; + } + void MaterialTypeAsset::SetReady() { m_status = AssetStatus::Ready; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp index 4405e835d6..46086dfecc 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp @@ -38,7 +38,7 @@ namespace AZ bool MaterialTypeAssetCreator::End(Data::Asset& result) { - if (!ValidateIsReady() || !ValidateEndMaterialProperty()) + if (!ValidateIsReady() || !ValidateEndMaterialProperty() || !ValidateMaterialVersion()) { return false; } @@ -100,6 +100,48 @@ namespace AZ } } + bool MaterialTypeAssetCreator::ValidateMaterialVersion() + { + if (m_asset->m_materialVersionUpdates.empty()) + { + return true; + } + + uint32_t prevVersion = 0; + for(const MaterialVersionUpdate& versionUpdate : m_asset->m_materialVersionUpdates) + { + if (versionUpdate.GetVersion() <= prevVersion) + { + ReportError("Version updates are not sequential. See version update '%u'.", versionUpdate.GetVersion()); + return false; + } + + if (versionUpdate.GetVersion() > m_asset->m_version) + { + ReportError("Version updates go beyond the current material type version. See version update '%u'.", versionUpdate.GetVersion()); + return false; + } + + prevVersion = versionUpdate.GetVersion(); + } + + const auto& lastMaterialVersionUpdate = m_asset->m_materialVersionUpdates.back(); + for (const auto& action : lastMaterialVersionUpdate.GetActions()) + { + const auto propertyIndex = m_asset->m_materialPropertiesLayout->FindPropertyIndex(AZ::Name{ action.m_toPropertyId }); + if (!propertyIndex.IsValid()) + { + ReportError("Renamed property '%s' not found in material property layout. Check that the property name has been " + "upgraded to the correct version", + action.m_toPropertyId.GetCStr()); + return false; + } + + } + + return true; + } + void MaterialTypeAssetCreator::AddShader(const AZ::Data::Asset& shaderAsset, const ShaderVariantId& shaderVaraintId, const AZ::Name& shaderTag) { if (ValidateIsReady() && ValidateNotNull(shaderAsset, "ShaderAsset")) @@ -123,6 +165,16 @@ namespace AZ AddShader(shaderAsset, ShaderVariantId{}, shaderTag); } + void MaterialTypeAssetCreator::SetVersion(uint32_t version) + { + m_asset->m_version = version; + } + + void MaterialTypeAssetCreator::AddVersionUpdate(const MaterialVersionUpdate& materialVersionUpdate) + { + m_asset->m_materialVersionUpdates.push_back(materialVersionUpdate); + } + void MaterialTypeAssetCreator::ClaimShaderOptionOwnership(const Name& shaderOptionName) { bool optionFound = false; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp new file mode 100644 index 0000000000..b387e502e2 --- /dev/null +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp @@ -0,0 +1,89 @@ +/* + * 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 + * + */ + +#include +#include +#include + +namespace AZ +{ + namespace RPI + { + void MaterialVersionUpdate::RenamePropertyAction::Reflect(ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("From", &RenamePropertyAction::m_fromPropertyId) + ->Field("To", &RenamePropertyAction::m_toPropertyId) + ; + } + } + + void MaterialVersionUpdate::Reflect(ReflectContext* context) + { + MaterialVersionUpdate::RenamePropertyAction::Reflect(context); + + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->RegisterGenericType(); + + serializeContext->Class() + ->Version(1) + ->Field("ToVersion", &MaterialVersionUpdate::m_toVersion) + ->Field("Actions", &MaterialVersionUpdate::m_actions) + ; + } + } + + MaterialVersionUpdate::MaterialVersionUpdate(uint32_t toVersion) + : m_toVersion(toVersion) + { + } + + uint32_t MaterialVersionUpdate::GetVersion() const + { + return m_toVersion; + } + + void MaterialVersionUpdate::SetVersion(uint32_t toVersion) + { + m_toVersion = toVersion; + } + + bool MaterialVersionUpdate::ApplyVersionUpdates(MaterialAsset& materialAsset) const + { + bool changesWereApplied = false; + + for (auto& propertyName : materialAsset.m_propertyNames) + { + for (const auto& action : m_actions) + { + if (propertyName == action.m_fromPropertyId) + { + propertyName = action.m_toPropertyId; + changesWereApplied = true; + } + } + } + + return changesWereApplied; + } + + const AZ::RPI::MaterialVersionUpdate::Actions& MaterialVersionUpdate::GetActions() const + { + return m_actions; + } + + void MaterialVersionUpdate::AddAction(const RenamePropertyAction& action) + { + m_actions.push_back(action); + } + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp b/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp index a5511edea1..31a46e9a6f 100644 --- a/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp +++ b/Gems/Atom/RPI/Code/Tests/Common/AssetSystemStub.cpp @@ -7,6 +7,7 @@ */ #include +#include namespace UnitTest { @@ -36,12 +37,17 @@ namespace UnitTest // Because GetSourceInfoBySourcePath should always return 0 for the sub-id, since it's about the source file not product file. sourceInfo.m_assetInfo.m_assetId.m_subId = 0; - m_sourceInfoMap.emplace(sourcePath, sourceInfo); + AZStd::string normalizedSourcePath = sourcePath; + AzFramework::StringFunc::Path::Normalize(normalizedSourcePath); + m_sourceInfoMap.emplace(normalizedSourcePath, sourceInfo); } bool AssetSystemStub::GetSourceInfoBySourcePath(const char* sourcePath, AZ::Data::AssetInfo& assetInfo, AZStd::string& watchFolder) { - auto iter = m_sourceInfoMap.find(sourcePath); + AZStd::string normalizedSourcePath = sourcePath; + AzFramework::StringFunc::Path::Normalize(normalizedSourcePath); + + auto iter = m_sourceInfoMap.find(normalizedSourcePath); if (iter != m_sourceInfoMap.end()) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialAssetTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialAssetTests.cpp index ce223ceb35..58a852f176 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialAssetTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialAssetTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,11 @@ namespace UnitTest RPITestFixture::TearDown(); } + + void ReplaceMaterialType(Data::Asset materialAsset, Data::Asset upgradedMaterialTypeAsset) + { + materialAsset->m_materialTypeAsset = upgradedMaterialTypeAsset; + } }; TEST_F(MaterialAssetTests, Basic) @@ -202,6 +208,81 @@ namespace UnitTest EXPECT_EQ(serializedAsset->GetPropertyValues()[8].GetValue>(), streamingImageAsset); } + TEST_F(MaterialAssetTests, UpgradeMaterialAsset) + { + // Here we test the main way that a material asset upgrade would be applied at runtime: A material type is updated to + // both rename a property *and* change the order in which properties appear in the layout. In this case, the new name + // must be identified and then that new name is used to find the appropriate index in the property layout. + + auto materialSrgLayout = CreateCommonTestMaterialSrgLayout(); + + auto shaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), materialSrgLayout); + + Data::Asset testMaterialTypeAssetV1; + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(Uuid::CreateRandom()); + materialTypeCreator.AddShader(shaderAsset); + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyInt" }, MaterialPropertyDataType::Int, Name{ "m_int" }); + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyUInt" }, MaterialPropertyDataType::UInt, Name{ "m_uint" }); + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyFloat" }, MaterialPropertyDataType::Float, Name{ "m_float" }); + EXPECT_TRUE(materialTypeCreator.End(testMaterialTypeAssetV1)); + + // Construct the material asset with materialTypeAsset version 1 + Data::AssetId assetId(Uuid::CreateRandom()); + + MaterialAssetCreator creator; + const bool includePropertyNames = true; + creator.Begin(assetId, *testMaterialTypeAssetV1, includePropertyNames); + creator.SetPropertyValue(Name{ "MyInt" }, 7); + creator.SetPropertyValue(Name{ "MyUInt" }, 8u); + creator.SetPropertyValue(Name{ "MyFloat" }, 9.0f); + Data::Asset materialAsset; + EXPECT_TRUE(creator.End(materialAsset)); + + // Prepare material type asset version 2 with the update actions + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction( + { + Name{ "MyInt" }, + Name{ "MyIntRenamed" } + })); + + Data::Asset testMaterialTypeAssetV2; + materialTypeCreator.Begin(Uuid::CreateRandom()); + materialTypeCreator.SetVersion(versionUpdate.GetVersion()); + materialTypeCreator.AddVersionUpdate(versionUpdate); + materialTypeCreator.AddShader(shaderAsset); + // Now we add the properties in a different order from before, and use the new name for MyInt. + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyUInt" }, MaterialPropertyDataType::UInt, Name{ "m_uint" }); + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyFloat" }, MaterialPropertyDataType::Float, Name{ "m_float" }); + AddMaterialPropertyForSrg(materialTypeCreator, Name{ "MyIntRenamed" }, MaterialPropertyDataType::Int, Name{ "m_int" }); + EXPECT_TRUE(materialTypeCreator.End(testMaterialTypeAssetV2)); + + // This is our way of faking the idea that an old version of the MaterialAsset could be loaded with a new version of the MaterialTypeAsset. + ReplaceMaterialType(materialAsset, testMaterialTypeAssetV2); + + // This can find errors and warnings, we are looking for a warning when the version update is applied + ErrorMessageFinder warningFinder; + warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file"); + warningFinder.AddExpectedErrorMessage("This material is based on version '1'"); + warningFinder.AddExpectedErrorMessage("material type is now at version '2'"); + + // Even though this material was created using the old version of the material type, it's property values should get automatically + // updated to align with the new property layout in the latest MaterialTypeAsset. + MaterialPropertyIndex myIntIndex = materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"MyIntRenamed"}); + EXPECT_EQ(2, myIntIndex.GetIndex()); + EXPECT_EQ(7, materialAsset->GetPropertyValues()[myIntIndex.GetIndex()].GetValue()); + + warningFinder.CheckExpectedErrorsFound(); + + // Since the MaterialAsset has already been updated, and the warning reported once, we should not see the "consider updating" + // warning reported again on subsequent property accesses. + warningFinder.Reset(); + myIntIndex = materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{"MyIntRenamed"}); + EXPECT_EQ(2, myIntIndex.GetIndex()); + EXPECT_EQ(7, materialAsset->GetPropertyValues()[myIntIndex.GetIndex()].GetValue()); + } + TEST_F(MaterialAssetTests, Error_NoBegin) { Data::AssetId assetId(Uuid::CreateRandom()); diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp index dd3b3b2711..acce52ae8e 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialSourceDataTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include @@ -60,22 +62,72 @@ namespace UnitTest localFileIO->SetAlias("@exefolder@", rootPath); m_testMaterialSrgLayout = CreateCommonTestMaterialSrgLayout(); - m_testShaderAsset = CreateTestShaderAsset(Uuid::CreateRandom(), m_testMaterialSrgLayout); + m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.shader", m_testShaderAsset.GetId()); - MaterialTypeAssetCreator materialTypeCreator; - materialTypeCreator.Begin(Uuid::CreateRandom()); - materialTypeCreator.AddShader(m_testShaderAsset); - AddCommonTestMaterialProperties(materialTypeCreator, "general."); - materialTypeCreator.End(m_testMaterialTypeAsset); + // The MaterialSourceData relies on both MaterialTypeSourceData and MaterialTypeAsset. We have to make sure the + // .materialtype file is present on disk, and that the MaterialTypeAsset is available through the asset database stub... + + const char* materialTypeJson = R"( + { + "version": 10, + "propertyLayout": { + "properties": { + "general": [ + {"name": "MyBool", "type": "bool"}, + {"name": "MyInt", "type": "Int"}, + {"name": "MyUInt", "type": "UInt"}, + {"name": "MyFloat", "type": "Float"}, + {"name": "MyFloat2", "type": "Vector2"}, + {"name": "MyFloat3", "type": "Vector3"}, + {"name": "MyFloat4", "type": "Vector4"}, + {"name": "MyColor", "type": "Color"}, + {"name": "MyImage", "type": "Image"}, + {"name": "MyEnum", "type": "Enum", "enumValues": ["Enum0", "Enum1", "Enum2"], "defaultValue": "Enum0"} + ] + } + }, + "shaders": [ + { + "file": "@exefolder@/Temp/test.shader" + } + ], + "versionUpdates": [ + { + "toVersion": 2, + "actions": [ + {"op": "rename", "from": "general.testColorNameA", "to": "general.testColorNameB"} + ] + }, + { + "toVersion": 4, + "actions": [ + {"op": "rename", "from": "general.testColorNameB", "to": "general.testColorNameC"} + ] + }, + { + "toVersion": 10, + "actions": [ + {"op": "rename", "from": "general.testColorNameC", "to": "general.MyColor"} + ] + } + ] + } + )"; + + AZ::Utils::WriteFile(materialTypeJson, "@exefolder@/Temp/test.materialtype"); + + MaterialTypeSourceData materialTypeSourceData; + LoadTestDataFromJson(materialTypeSourceData, materialTypeJson); + m_testMaterialTypeAsset = materialTypeSourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()).TakeValue(); // Since this test doesn't actually instantiate a Material, it won't need to instantiate this ImageAsset, so all we // need is an asset reference with a valid ID. m_testImageAsset = Data::Asset{ Data::AssetId{Uuid::CreateRandom(), StreamingImageAsset::GetImageAssetSubId()}, azrtti_typeid() }; // Register the test assets with the AssetSystemStub so CreateMaterialAsset() can use AssetUtils. - m_assetSystemStub.RegisterSourceInfo("test.materialtype", m_testMaterialTypeAsset.GetId()); - m_assetSystemStub.RegisterSourceInfo("test.streamingimage", m_testImageAsset.GetId()); + m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.materialtype", m_testMaterialTypeAsset.GetId()); + m_assetSystemStub.RegisterSourceInfo("@exefolder@/Temp/test.streamingimage", m_testImageAsset.GetId()); } void TearDown() override @@ -88,12 +140,12 @@ namespace UnitTest RPITestFixture::TearDown(); } }; - + void AddPropertyGroup(MaterialSourceData& material, AZStd::string_view groupName) { material.m_properties.insert(groupName); } - + void AddProperty(MaterialSourceData& material, AZStd::string_view groupName, AZStd::string_view propertyName, const MaterialPropertyValue& anyValue) { material.m_properties[groupName][propertyName].m_value = anyValue; @@ -103,7 +155,7 @@ namespace UnitTest { MaterialSourceData sourceData; - sourceData.m_materialType = "test.materialtype"; + sourceData.m_materialType = "@exefolder@/Temp/test.materialtype"; AddPropertyGroup(sourceData, "general"); AddProperty(sourceData, "general", "MyBool", true); AddProperty(sourceData, "general", "MyInt", -10); @@ -113,7 +165,7 @@ namespace UnitTest AddProperty(sourceData, "general", "MyFloat2", AZ::Vector2(2.1f, 2.2f)); AddProperty(sourceData, "general", "MyFloat3", AZ::Vector3(3.1f, 3.2f, 3.3f)); AddProperty(sourceData, "general", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f)); - AddProperty(sourceData, "general", "MyImage", AZStd::string("test.streamingimage")); + AddProperty(sourceData, "general", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage")); AddProperty(sourceData, "general", "MyEnum", AZStd::string("Enum1")); auto materialAssetOutcome = sourceData.CreateMaterialAsset(Uuid::CreateRandom(), "", true); @@ -139,7 +191,7 @@ namespace UnitTest EXPECT_STREQ(a.m_materialType.data(), b.m_materialType.data()); EXPECT_STREQ(a.m_description.data(), b.m_description.data()); EXPECT_STREQ(a.m_parentMaterial.data(), b.m_parentMaterial.data()); - EXPECT_EQ(a.m_propertyLayoutVersion, b.m_propertyLayoutVersion); + EXPECT_EQ(a.m_materialTypeVersion, b.m_materialTypeVersion); EXPECT_EQ(a.m_properties.size(), b.m_properties.size()); for (auto& groupA : a.m_properties) @@ -170,7 +222,7 @@ namespace UnitTest auto& propertyA = propertyIterA.second; auto& propertyB = propertyIterB->second; - + bool typesMatch = propertyA.m_value.GetTypeId() == propertyB.m_value.GetTypeId(); EXPECT_TRUE(typesMatch); if (typesMatch) @@ -229,8 +281,8 @@ namespace UnitTest " } \n" "} \n"; - const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/roundTripTest.materialtype"; - + const char* materialTypeFilePath = "@exefolder@/Temp/roundTripTest.materialtype"; + AZ::IO::FileIOStream file; EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath)); file.Write(strlen(materialTypeJson), materialTypeJson); @@ -240,7 +292,7 @@ namespace UnitTest sourceDataOriginal.m_materialType = materialTypeFilePath; sourceDataOriginal.m_parentMaterial = materialTypeFilePath; sourceDataOriginal.m_description = "This is a description"; - sourceDataOriginal.m_propertyLayoutVersion = 7; + sourceDataOriginal.m_materialTypeVersion = 7; AddPropertyGroup(sourceDataOriginal, "groupA"); AddProperty(sourceDataOriginal, "groupA", "MyBool", true); AddProperty(sourceDataOriginal, "groupA", "MyInt", -10); @@ -252,14 +304,14 @@ namespace UnitTest AddPropertyGroup(sourceDataOriginal, "groupC"); AddProperty(sourceDataOriginal, "groupC", "MyFloat4", AZ::Vector4(4.1f, 4.2f, 4.3f, 4.4f)); AddProperty(sourceDataOriginal, "groupC", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f}); - AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("test.streamingimage")); + AddProperty(sourceDataOriginal, "groupC", "MyImage", AZStd::string("@exefolder@/Temp/test.streamingimage")); AZStd::string sourceDataSerialized; JsonTestResult storeResult = StoreTestDataToJson(sourceDataOriginal, sourceDataSerialized); MaterialSourceData sourceDataCopy; JsonTestResult loadResult = LoadTestDataFromJson(sourceDataCopy, sourceDataSerialized); - + CheckEqual(sourceDataOriginal, sourceDataCopy); } @@ -277,10 +329,10 @@ namespace UnitTest ] } } - } + } )"; - const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; + const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype"; AZ::IO::FileIOStream file; EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath)); @@ -296,7 +348,7 @@ namespace UnitTest "testColor": [0.1,0.2,0.3] } }, - "materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype" + "materialType": "@exefolder@/Temp/simpleMaterialType.materialtype" } )"; @@ -330,7 +382,7 @@ namespace UnitTest { const AZStd::string inputJson = R"( { - "propertyLayoutVersion": 1, + "materialTypeVersion": 1, "properties": { "baseColor": { "color": [1.0,1.0,1.0] @@ -354,7 +406,7 @@ namespace UnitTest const AZStd::string inputJson = R"( { "materialType": "DoesNotExist.materialtype", - "propertyLayoutVersion": 1, + "materialTypeVersion": 1, "properties": { "baseColor": { "color": [1.0,1.0,1.0] @@ -387,10 +439,10 @@ namespace UnitTest ] } } - } + } )"; - const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; + const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype"; AZ::IO::FileIOStream file; EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath)); @@ -399,8 +451,8 @@ namespace UnitTest const AZStd::string inputJson = R"( { - "materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype", - "propertyLayoutVersion": 1, + "materialType": "@exefolder@/Temp/simpleMaterialType.materialtype", + "materialTypeVersion": 1, "properties": { "general": { "testColor": [1.0,1.0,1.0] @@ -433,10 +485,10 @@ namespace UnitTest ] } } - } + } )"; - const char* materialTypeFilePath = "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype"; + const char* materialTypeFilePath = "@exefolder@/Temp/simpleMaterialType.materialtype"; AZ::IO::FileIOStream file; EXPECT_TRUE(file.Open(materialTypeFilePath, AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath)); @@ -445,8 +497,8 @@ namespace UnitTest const AZStd::string inputJson = R"( { - "materialType": "@exefolder@/Gems/Atom/RPI/Code/Tests/Material/Temp/simpleMaterialType.materialtype", - "propertyLayoutVersion": 1, + "materialType": "@exefolder@/Temp/simpleMaterialType.materialtype", + "materialTypeVersion": 1, "properties": { "general": { "doesNotExist": [1.0,1.0,1.0] @@ -467,20 +519,20 @@ namespace UnitTest TEST_F(MaterialSourceDataTests, CreateMaterialAsset_MultiLevelDataInheritance) { MaterialSourceData sourceDataLevel1; - sourceDataLevel1.m_materialType = "test.materialtype"; + sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype"; AddPropertyGroup(sourceDataLevel1, "general"); AddProperty(sourceDataLevel1, "general", "MyFloat", 1.5f); AddProperty(sourceDataLevel1, "general", "MyColor", AZ::Color{0.1f, 0.2f, 0.3f, 0.4f}); MaterialSourceData sourceDataLevel2; - sourceDataLevel2.m_materialType = "test.materialtype"; + sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype"; sourceDataLevel2.m_parentMaterial = "level1.material"; AddPropertyGroup(sourceDataLevel2, "general"); AddProperty(sourceDataLevel2, "general", "MyColor", AZ::Color{0.15f, 0.25f, 0.35f, 0.45f}); AddProperty(sourceDataLevel2, "general", "MyFloat2", AZ::Vector2{4.1f, 4.2f}); MaterialSourceData sourceDataLevel3; - sourceDataLevel3.m_materialType = "test.materialtype"; + sourceDataLevel3.m_materialType = "@exefolder@/Temp/test.materialtype"; sourceDataLevel3.m_parentMaterial = "level2.material"; AddPropertyGroup(sourceDataLevel3, "general"); AddProperty(sourceDataLevel3, "general", "MyFloat", 3.5f); @@ -497,7 +549,7 @@ namespace UnitTest auto materialAssetLevel3 = sourceDataLevel3.CreateMaterialAsset(Uuid::CreateRandom(), "", true); EXPECT_TRUE(materialAssetLevel3.IsSuccess()); - + auto layout = m_testMaterialTypeAsset->GetMaterialPropertiesLayout(); MaterialPropertyIndex myFloat = layout->FindPropertyIndex(Name("general.MyFloat")); MaterialPropertyIndex myFloat2 = layout->FindPropertyIndex(Name("general.MyFloat2")); @@ -535,14 +587,14 @@ namespace UnitTest m_assetSystemStub.RegisterSourceInfo("otherBase.materialtype", otherMaterialType.GetId()); MaterialSourceData sourceDataLevel1; - sourceDataLevel1.m_materialType = "test.materialtype"; + sourceDataLevel1.m_materialType = "@exefolder@/Temp/test.materialtype"; MaterialSourceData sourceDataLevel2; - sourceDataLevel2.m_materialType = "test.materialtype"; + sourceDataLevel2.m_materialType = "@exefolder@/Temp/test.materialtype"; sourceDataLevel2.m_parentMaterial = "level1.material"; MaterialSourceData sourceDataLevel3; - sourceDataLevel3.m_materialType = "otherBase.materialtype"; + sourceDataLevel3.m_materialType = "@exefolder@/Temp/otherBase.materialtype"; sourceDataLevel3.m_parentMaterial = "level2.material"; auto materialAssetLevel1 = sourceDataLevel1.CreateMaterialAsset(Uuid::CreateRandom(), "", true); @@ -570,7 +622,7 @@ namespace UnitTest { MaterialSourceData sourceData; - sourceData.m_materialType = "test.materialtype"; + sourceData.m_materialType = "@exefolder@/Temp/test.materialtype"; AddPropertyGroup(sourceData, "general"); @@ -587,7 +639,7 @@ namespace UnitTest { MaterialSourceData sourceData; - sourceData.m_materialType = "test.materialtype"; + sourceData.m_materialType = "@exefolder@/Temp/test.materialtype"; AddPropertyGroup(sourceData, "general"); @@ -629,7 +681,7 @@ namespace UnitTest expectWarning([](MaterialSourceData& materialSourceData) { - AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("test.streamingimage")); + AddProperty(materialSourceData, "general", "DoesNotExist", AZStd::string("@exefolder@/Temp/test.streamingimage")); }); // Missing image reference @@ -638,6 +690,124 @@ namespace UnitTest AddProperty(materialSourceData, "general", "MyImage", AZStd::string("doesNotExist.streamingimage")); }, 3); // Expect a 3rd error because AssetUtils reports its own assertion failure } + + + TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionUpdate) + { + const AZStd::string inputJson = R"( + { + "materialType": "@exefolder@/Temp/test.materialtype", + "materialTypeVersion": 1, + "properties": { + "general": { + "testColorNameA": [0.1, 0.2, 0.3] + } + } + } + )"; + + MaterialSourceData material; + JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); + + EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); + EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); + + // Initially, the loaded material data will match the .material file exactly. This gives us the accurate representation of + // what's actually saved on disk. + + EXPECT_NE(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end()); + + AZ::Color testColor = material.m_properties["general"]["testColorNameA"].m_value.GetValue(); + EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01)); + + EXPECT_EQ(1, material.m_materialTypeVersion); + + // Then we force the material data to update to the latest material type version specification + ErrorMessageFinder warningFinder; // Note this finds errors and warnings, and we're looking for a warning. + warningFinder.AddExpectedErrorMessage("Automatic updates are available. Consider updating the .material source file"); + warningFinder.AddExpectedErrorMessage("This material is based on version '1'"); + warningFinder.AddExpectedErrorMessage("material type is now at version '10'"); + material.ApplyVersionUpdates(); + warningFinder.CheckExpectedErrorsFound(); + + // Now the material data should match the latest material type. + // Look for the property under the latest name in the material type, not the name used in the .material file. + + EXPECT_EQ(material.m_properties["general"].find("testColorNameA"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("testColorNameB"), material.m_properties["general"].end()); + EXPECT_EQ(material.m_properties["general"].find("testColorNameC"), material.m_properties["general"].end()); + EXPECT_NE(material.m_properties["general"].find("MyColor"), material.m_properties["general"].end()); + + testColor = material.m_properties["general"]["MyColor"].m_value.GetValue(); + EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01)); + + EXPECT_EQ(10, material.m_materialTypeVersion); + + // Calling ApplyVersionUpdates() again should not report the warning again, since the material has already been updated. + warningFinder.Reset(); + material.ApplyVersionUpdates(); + } + + TEST_F(MaterialSourceDataTests, Load_MaterialTypeVersionPartialUpdate) + { + // This case is similar to Load_MaterialTypeVersionUpdate but we start at a later + // version so only some of the version updates are applied. + + const AZStd::string inputJson = R"( + { + "materialType": "@exefolder@/Temp/test.materialtype", + "materialTypeVersion": 3, + "properties": { + "general": { + "testColorNameB": [0.1, 0.2, 0.3] + } + } + } + )"; + + MaterialSourceData material; + JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); + + EXPECT_EQ(AZ::JsonSerializationResult::Tasks::ReadField, loadResult.m_jsonResultCode.GetTask()); + EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, loadResult.m_jsonResultCode.GetProcessing()); + + material.ApplyVersionUpdates(); + + AZ::Color testColor = material.m_properties["general"]["MyColor"].m_value.GetValue(); + EXPECT_TRUE(AZ::Color(0.1f, 0.2f, 0.3f, 1.0f).IsClose(testColor, 0.01)); + + EXPECT_EQ(10, material.m_materialTypeVersion); + } + + TEST_F(MaterialSourceDataTests, Load_Error_MaterialTypeVersionUpdateWithMismatchedVersion) + { + const AZStd::string inputJson = R"( + { + "materialType": "@exefolder@/Temp/test.materialtype", + "materialTypeVersion": 3, // At this version, the property should be testColorNameB not testColorNameA + "properties": { + "general": { + "testColorNameA": [0.1, 0.2, 0.3] + } + } + } + )"; + + MaterialSourceData material; + JsonTestResult loadResult = LoadTestDataFromJson(material, inputJson); + + loadResult.ContainsMessage("/properties/general/testColorNameA", "Property 'general.testColorNameA' not found in material type."); + + EXPECT_FALSE(material.m_properties["general"]["testColorNameA"].m_value.IsValid()); + + material.ApplyVersionUpdates(); + + EXPECT_FALSE(material.m_properties["general"]["MyColor"].m_value.IsValid()); + } + } diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp index b9774c84d9..81b723ec04 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeAssetTests.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -153,6 +154,16 @@ namespace UnitTest MaterialTypeAssetCreator materialTypeCreator; materialTypeCreator.Begin(assetId); + // Version updates + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction( + { + Name{ "EnableSpecialPassPrev" }, + Name{ "EnableSpecialPass" } + })); + materialTypeCreator.SetVersion(versionUpdate.GetVersion()); + materialTypeCreator.AddVersionUpdate(versionUpdate); + // Built-in shader materialTypeCreator.AddShader(m_testShaderAsset); @@ -198,7 +209,7 @@ namespace UnitTest { EXPECT_EQ(m_testMaterialSrgLayout, materialTypeAsset->GetMaterialSrgLayout()); EXPECT_EQ(5, materialTypeAsset->GetMaterialPropertiesLayout()->GetPropertyCount()); - + EXPECT_EQ(2, materialTypeAsset->GetVersion()); // Check aliased properties const MaterialPropertyIndex colorIndex = materialTypeAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(Name{ "MyColor" }); @@ -490,6 +501,106 @@ namespace UnitTest }); } + TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_WrongName) + { + Data::Asset materialTypeAsset; + + Data::AssetId assetId(Uuid::CreateRandom()); + + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(assetId); + + // Invalid version updates + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction( + { + Name{ "EnableSpecialPassPrev" }, + Name{ "InvalidPropertyName" } + })); + materialTypeCreator.SetVersion(versionUpdate.GetVersion()); + materialTypeCreator.AddVersionUpdate(versionUpdate); + materialTypeCreator.AddShader(m_testShaderAsset); + + materialTypeCreator.BeginMaterialProperty(Name{ "EnableSpecialPass" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + + AZ_TEST_START_ASSERTTEST; + EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset)); + AZ_TEST_STOP_ASSERTTEST(1); + EXPECT_EQ(1, materialTypeCreator.GetErrorCount()); + } + + TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_WrongOrder) + { + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(Uuid::CreateRandom()); + + materialTypeCreator.SetVersion(4); + materialTypeCreator.AddShader(m_testShaderAsset); + materialTypeCreator.BeginMaterialProperty(Name{ "d" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("Version updates are not sequential. See version update '3'"); + + { + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "a" },Name{ "b" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + } + + { + MaterialVersionUpdate versionUpdate(4); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "b" },Name{ "c" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + } + + { + MaterialVersionUpdate versionUpdate(3); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "c" },Name{ "d" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + } + + Data::Asset materialTypeAsset; + EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset)); + + errorMessageFinder.CheckExpectedErrorsFound(); + + EXPECT_EQ(1, materialTypeCreator.GetErrorCount()); + } + + TEST_F(MaterialTypeAssetTests, Error_InvalidMaterialVersionUpdate_GoesTooFar) + { + MaterialTypeAssetCreator materialTypeCreator; + materialTypeCreator.Begin(Uuid::CreateRandom()); + + materialTypeCreator.SetVersion(3); + materialTypeCreator.AddShader(m_testShaderAsset); + materialTypeCreator.BeginMaterialProperty(Name{ "d" }, MaterialPropertyDataType::Bool); + materialTypeCreator.EndMaterialProperty(); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("Version updates go beyond the current material type version. See version update '4'"); + + { + MaterialVersionUpdate versionUpdate(2); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "a" },Name{ "b" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + } + + { + MaterialVersionUpdate versionUpdate(4); + versionUpdate.AddAction(MaterialVersionUpdate::RenamePropertyAction({Name{ "b" },Name{ "c" }})); + materialTypeCreator.AddVersionUpdate(versionUpdate); + } + + Data::Asset materialTypeAsset; + EXPECT_FALSE(materialTypeCreator.End(materialTypeAsset)); + + errorMessageFinder.CheckExpectedErrorsFound(); + + EXPECT_EQ(1, materialTypeCreator.GetErrorCount()); + } TEST_F(MaterialTypeAssetTests, MaterialTypeWithNoSRGOrProperties) { diff --git a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp index ba4a58b9ff..b811e630d0 100644 --- a/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Material/MaterialTypeSourceDataTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -978,8 +979,16 @@ namespace UnitTest const AZStd::string inputJson = R"( { "description": "This is a general description about the material", + "version": 2, + "versionUpdates": [ + { + "toVersion": 2, + "actions": [ + { "op": "rename", "from": "groupA.fooPrev", "to": "groupA.foo" } + ] + } + ], "propertyLayout": { - "version": 2, "groups": [ { "name": "groupA", @@ -1062,7 +1071,12 @@ namespace UnitTest EXPECT_EQ(material.m_description, "This is a general description about the material"); - EXPECT_EQ(material.m_propertyLayout.m_version, 2); + EXPECT_EQ(material.m_version, 2); + EXPECT_EQ(material.m_versionUpdates.size(), 1); + EXPECT_EQ(material.m_versionUpdates[0].m_toVersion, 2); + EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_operation, "rename"); + EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_renameFrom, "groupA.fooPrev"); + EXPECT_EQ(material.m_versionUpdates[0].m_actions[0].m_renameTo, "groupA.foo"); EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2); EXPECT_TRUE(material.FindGroup("groupA") != nullptr); @@ -1208,8 +1222,6 @@ namespace UnitTest EXPECT_EQ(material.m_description, "This is a general description about the material"); - EXPECT_EQ(material.m_propertyLayout.m_version, 2); - EXPECT_EQ(material.m_propertyLayout.m_groups.size(), 2); EXPECT_TRUE(material.FindGroup("groupA") != nullptr); EXPECT_TRUE(material.FindGroup("groupB") != nullptr); @@ -1266,7 +1278,6 @@ namespace UnitTest { "description": "", "propertyLayout": { - "version": 2, "groups": [ { "name": "general", @@ -1305,4 +1316,191 @@ namespace UnitTest CheckPropertyValue>(materialTypeAsset, Name{ "general.absolute" }, m_testImageAsset2); CheckPropertyValue>(materialTypeAsset, Name{ "general.relative" }, m_testImageAsset2); } + + + TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName) + { + const AZStd::string inputJson = R"( + { + "version": 10, + "versionUpdates": [ + { + "toVersion": 2, + "actions": [ + { "op": "rename", "from": "general.fooA", "to": "general.fooB" } + ] + }, + { + "toVersion": 4, + "actions": [ + { "op": "rename", "from": "general.barA", "to": "general.barB" } + ] + }, + { + "toVersion": 6, + "actions": [ + { "op": "rename", "from": "general.fooB", "to": "general.fooC" }, + { "op": "rename", "from": "general.barB", "to": "general.barC" } + ] + }, + { + "toVersion": 7, + "actions": [ + { "op": "rename", "from": "general.bazA", "to": "otherGroup.bazB" } + ] + } + ], + "propertyLayout": { + "properties": { + "general": [ + { + "name": "fooC", + "type": "Bool" + }, + { + "name": "barC", + "type": "Float" + } + ], + "otherGroup": [ + { + "name": "dontMindMe", + "type": "Bool" + }, + { + "name": "bazB", + "type": "Float" + } + ] + } + } + } + )"; + + MaterialTypeSourceData materialType; + JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson); + + EXPECT_EQ(materialType.m_version, 10); + + // First find the properties using their correct current names + const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooC"); + const MaterialTypeSourceData::PropertyDefinition* bar = materialType.FindProperty("general", "barC"); + const MaterialTypeSourceData::PropertyDefinition* baz = materialType.FindProperty("otherGroup", "bazB"); + + EXPECT_TRUE(foo); + EXPECT_TRUE(bar); + EXPECT_TRUE(baz); + EXPECT_EQ(foo->m_name, "fooC"); + EXPECT_EQ(bar->m_name, "barC"); + EXPECT_EQ(baz->m_name, "bazB"); + + // Now try doing the property lookup using old versions of the name and make sure the same property can be found + + EXPECT_EQ(foo, materialType.FindProperty("general", "fooA")); + EXPECT_EQ(foo, materialType.FindProperty("general", "fooB")); + EXPECT_EQ(bar, materialType.FindProperty("general", "barA")); + EXPECT_EQ(bar, materialType.FindProperty("general", "barB")); + EXPECT_EQ(baz, materialType.FindProperty("general", "bazA")); + + EXPECT_EQ(nullptr, materialType.FindProperty("general", "fooX")); + EXPECT_EQ(nullptr, materialType.FindProperty("general", "barX")); + EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazX")); + EXPECT_EQ(nullptr, materialType.FindProperty("general", "bazB")); + EXPECT_EQ(nullptr, materialType.FindProperty("otherGroup", "bazA")); + } + + TEST_F(MaterialTypeSourceDataTests, FindPropertyUsingOldName_Error_UnsupportedVersionUpdate) + { + const AZStd::string inputJson = R"( + { + "version": 10, + "versionUpdates": [ + { + "toVersion": 2, + "actions": [ + { "op": "notRename", "from": "general.fooA", "to": "general.fooB" } + ] + } + ], + "propertyLayout": { + "properties": { + "general": [ + { + "name": "fooB", + "type": "Bool" + } + ] + } + } + } + )"; + + MaterialTypeSourceData materialType; + JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("Unsupported material version update operation 'notRename'"); + + + const MaterialTypeSourceData::PropertyDefinition* foo = materialType.FindProperty("general", "fooA"); + + EXPECT_EQ(nullptr, foo); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_UnsupportedVersionUpdate) + { + MaterialTypeSourceData sourceData; + + MaterialTypeSourceData::PropertyDefinition propertySource; + propertySource.m_name = "a"; + propertySource.m_dataType = MaterialPropertyDataType::Int; + propertySource.m_value = 0; + sourceData.m_propertyLayout.m_properties["general"].push_back(propertySource); + + sourceData.m_version = 2; + + MaterialTypeSourceData::VersionUpdateDefinition versionUpdate; + versionUpdate.m_toVersion = 2; + MaterialTypeSourceData::VersionUpdatesRenameOperationDefinition updateAction; + updateAction.m_operation = "operationNotKnown"; + versionUpdate.m_actions.push_back(updateAction); + sourceData.m_versionUpdates.push_back(versionUpdate); + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("Unsupported material version update operation 'operationNotKnown'"); + errorMessageFinder.AddIgnoredErrorMessage("Failed to build MaterialTypeAsset", true); + + auto materialTypeOutcome = sourceData.CreateMaterialTypeAsset(Uuid::CreateRandom()); + EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } + + TEST_F(MaterialTypeSourceDataTests, CreateMaterialTypeAsset_Error_VersionInWrongLocation) + { + // The version field used to be under the propertyLayout section, but it has been moved up to the top level. + // If any users have their own custom .materialtype with an older format that has the version in the wrong place + // then we will report an error with instructions to move it to the correct location. + + ErrorMessageFinder errorMessageFinder; + errorMessageFinder.AddExpectedErrorMessage("The field '/propertyLayout/version' is deprecated and moved to '/version'. Please edit this material type source file and move the '\"version\": 4' setting up one level"); + + const AZStd::string inputJson = R"( + { + "propertyLayout": { + "version": 4 + } + } + )"; + + MaterialTypeSourceData materialType; + JsonTestResult loadResult = LoadTestDataFromJson(materialType, inputJson); + + auto materialTypeOutcome = materialType.CreateMaterialTypeAsset(Uuid::CreateRandom()); + EXPECT_FALSE(materialTypeOutcome.IsSuccess()); + + errorMessageFinder.CheckExpectedErrorsFound(); + } } diff --git a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake index 49c7231fed..4f0e432511 100644 --- a/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake +++ b/Gems/Atom/RPI/Code/atom_rpi_reflect_files.cmake @@ -61,6 +61,7 @@ set(FILES Include/Atom/RPI.Reflect/Material/MaterialTypeAssetCreator.h Include/Atom/RPI.Reflect/Material/ShaderCollection.h Include/Atom/RPI.Reflect/Material/MaterialFunctor.h + Include/Atom/RPI.Reflect/Material/MaterialVersionUpdate.h Include/Atom/RPI.Reflect/Pass/ComputePassData.h Include/Atom/RPI.Reflect/Pass/CopyPassData.h Include/Atom/RPI.Reflect/Pass/DownsampleMipChainPassData.h @@ -141,6 +142,7 @@ set(FILES Source/RPI.Reflect/Material/MaterialTypeAssetCreator.cpp Source/RPI.Reflect/Material/ShaderCollection.cpp Source/RPI.Reflect/Material/MaterialFunctor.cpp + Source/RPI.Reflect/Material/MaterialVersionUpdate.cpp Source/RPI.Reflect/Pass/PassAsset.cpp Source/RPI.Reflect/Pass/PassAttachmentReflect.cpp Source/RPI.Reflect/Pass/PassRequest.cpp diff --git a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype index 00f11663f7..cf0bffa058 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/AutoBrick.materialtype @@ -1,7 +1,7 @@ { "description": "This is an example of a custom material type using Atom's PBR shading model: procedurally generated brick or tile.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "shape", diff --git a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype index 81ebd63c28..5d99737576 100644 --- a/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype +++ b/Gems/Atom/TestData/TestData/Materials/Types/MinimalPBR.materialtype @@ -1,7 +1,7 @@ { "description": "Base Material with properties used to define Standard PBR, a metallic-roughness Physically-Based Rendering (PBR) material shading model.", + "version": 3, "propertyLayout": { - "version": 3, "groups": [ { "name": "settings", diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp index a0d2034082..2b39a87623 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/PreviewRenderer/PreviewRenderer.cpp @@ -61,7 +61,7 @@ namespace AtomToolsFramework AZ::RPI::RenderPipelineDescriptor pipelineDesc; pipelineDesc.m_mainViewTagName = "MainCamera"; pipelineDesc.m_name = pipelineName; - pipelineDesc.m_rootPassTemplate = "MainPipelineRenderToTexture"; + pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture"; // We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue // [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 17e292ac16..98af749261 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -230,9 +230,11 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; + + AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); // Force save data to store forward slashes AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); @@ -302,9 +304,11 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial; + + AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); // Force save data to store forward slashes AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/"); @@ -373,8 +377,10 @@ namespace MaterialEditor // create source data from properties MaterialSourceData sourceData; - sourceData.m_propertyLayoutVersion = m_materialTypeSourceData.m_propertyLayout.m_version; sourceData.m_materialType = m_materialSourceData.m_materialType; + + AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null."); + sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); // Only assign a parent path if the source was a .material if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension)) @@ -679,6 +685,12 @@ namespace MaterialEditor return false; } m_materialTypeSourceData = materialTypeOutcome.GetValue(); + + if (MaterialSourceData::ApplyVersionUpdatesResult::Failed == m_materialSourceData.ApplyVersionUpdates(m_absolutePath)) + { + AZ_Error("MaterialDocument", false, "Material source data could not be auto updated to the latest version of the material type: '%s'.", m_materialSourceData.m_materialType.c_str()); + return false; + } } else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension)) { diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp index 070c42fd7c..d15db886d6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentUtil.cpp @@ -99,7 +99,6 @@ namespace AZ { // Construct the material source data object that will be exported AZ::RPI::MaterialSourceData exportData; - exportData.m_propertyLayoutVersion = editData.m_materialTypeSourceData.m_propertyLayout.m_version; // Converting absolute material paths to relative paths bool result = false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp index 2d8f83a32a..e32b26f0c5 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/PostProcess/ColorGrading/EditorHDRColorGradingComponent.cpp @@ -131,16 +131,20 @@ namespace AZ ->Attribute(Edit::Attributes::Max, 1.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhShadowsStart, "Shadows Start", "SMH Shadows Start Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhShadowsEnd, "Shadows End", "SMH Shadows End Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhHighlightsStart, "Highlights Start", "SMH Highlights Start Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Slider, &HDRColorGradingComponentConfig::m_smhHighlightsEnd, "Highlights End", "SMH Highlights End Value") ->Attribute(Edit::Attributes::Min, 0.0f) - ->Attribute(Edit::Attributes::Max, 1.0f) + ->Attribute(Edit::Attributes::Max, 16.0f) + ->Attribute(Edit::Attributes::SoftMax, 2.0f) ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhShadowsColor, "Shadows Color", "SMH Shadows Color") ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhMidtonesColor, "Midtones Color", "SMH Midtones Color") ->DataElement(AZ::Edit::UIHandlers::Color, &HDRColorGradingComponentConfig::m_smhHighlightsColor, "Highlights Color", "SMH Highlights Color") diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env.example similarity index 63% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env.example index 080b4e92f7..29c1739992 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.env.example @@ -4,18 +4,18 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # -# -- This line is 75 characters ------------------------------------------- +# ------------------------------------------------------------------------- # Sets up the project environment for python scripting using the export DYNACONF_COMPANY=Amazon -# if a lumberyard project isn't set use this gem -export DYNACONF_LY_PROJECT=DccScriptingInterface -export DYNACONF_LY_PROJECT_PATH=`pwd` -export DYNACONF_LY_DEV=${LY_PROJECT_PATH}\..\..\..\.. +# if a O3DE project isn't set use this gem +export DYNACONF_O3DE_PROJECT=DccScriptingInterface +export DYNACONF_O3DE_PROJECT_PATH=`pwd` +export DYNACONF_O3DE_DEV=${O3DE_PROJECT_PATH}\..\..\..\.. # LY build folder -export DYNACONF_LY_BUILD_PATH=${LY_DEV}\build -export DYNACONF_LY_BIN_PATH=${LY_BUILD_PATH}\bin\profile +export DYNACONF_O3DE_BUILD_PATH=${O3DE_DEV}\build +export DYNACONF_O3DE_BIN_PATH=${O3DE_BUILD_PATH}\bin\profile # default IDE and debug settings #export DYNACONF_DCCSI_GDEBUG=false @@ -24,9 +24,9 @@ export DYNACONF_DCCSI_GDEBUGGER=WING export DYNACONF_DCCSI_LOGLEVEL=20 # defaults for DccScriptingInterface (DCCsi) -export DYNACONF_DCCSIG_PATH=${LY_DEV}\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface +export DYNACONF_DCCSIG_PATH=${O3DE_DEV}\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface -# set up default python interpreter (Lumberyard) +# set up default python interpreter (O3DE) # we may want to entirely remove these and rely on config.py to dynamically set up # however VScode can be configured with a .env so might be valueable to keep export DYNACONF_DCCSI_PY_VERSION_MAJOR=3 @@ -40,13 +40,13 @@ export DYNACONF_DCCSI_PYTHON_PATH=${DCCSIG_PATH}\3rdParty\Python export DYNACONF_DCCSI_PYTHON_LIB_PATH=${DCCSI_PYTHON_PATH}\Lib\${DCCSI_PY_VERSION_MAJOR}.x\${DCCSI_PY_VERSION_MAJOR}.${DCCSI_PY_VERSION_MINOR}.x\site-packages # TO DO: figure out how to best deal with OS folder (i.e. 'windows') -export DYNACONF_DCCSI_PYTHON_INSTALL=${LY_DEV}\python -export DYNACONF_DDCCSI_PY_BASE=${DCCSI_PYTHON_INSTALL}\python.cmd +export DYNACONF_O3DE_PYTHON_INSTALL=${O3DE_DEV}\python +export DYNACONF_DCCSI_PY_BASE=${O3DE_PYTHON_INSTALL}\python.cmd # set up Qt / PySide2 # TO DO: These should NOT be set in the global env as they will cause conflicts # with other Qt apps (like DCC tools), only set in local.env, or modify config.py # for utils/tools/apps that need them ( see config.init_ly_pyside() ) -#export DYNACONF_QTFORPYTHON_PATH=${LY_DEV}\Gems\QtForPython\3rdParty\pyside2\windows\release -#export DYNACONF_QT_PLUGIN_PATH=${LY_BUILD_PATH}\bin\profile\EditorPlugins -#export DYNACONF_QT_QPA_PLATFORM_PLUGIN_PATH=${LY_BUILD_PATH}\bin\profile\EditorPlugins\platforms +#export DYNACONF_QTFORPYTHON_PATH=${O3DE_DEV}\Gems\QtForPython\3rdParty\pyside2\windows\release +#export DYNACONF_QT_PLUGIN_PATH=${O3DE_BUILD_PATH}\bin\profile\EditorPlugins +#export DYNACONF_QT_QPA_PLATFORM_PLUGIN_PATH=${O3DE_BUILD_PATH}\bin\profile\EditorPlugins\platforms diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore index 9ad4717873..086d189eba 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.gitignore @@ -8,4 +8,6 @@ workspace.xml # Ignore dynaconf secret files .secrets.* settings.local.json -azpy/_sample_package_/* \ No newline at end of file +azpy/_sample_package_/* +.env +settings_export.json.tmp \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.p4ignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.p4ignore index dedde06bc4..2ff9b6b614 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.p4ignore +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/.p4ignore @@ -19,3 +19,4 @@ __WIP__/* !.gitignore .secrets.* settings.local.json +.env \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/README.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/README.txt new file mode 100644 index 0000000000..7f5a72fb1c --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/README.txt @@ -0,0 +1,36 @@ +DccScriptingInterface (DCCsi) + +This location can be extended with additional 3rdParty Python Utils, Tools, Packages, etc. + +These are not installed or distributed with O3DE + +However, there is some stubbed scaffolding in place. + +This is a bootstrapped sandbox for installing python libs (version bootstrapped procedurally): +C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\Lib\2.x\2.7.x\site-packages +C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\Lib\3.x\3.7.x\site-packages + +Any libs installed to this location will be accessible (use at your own risk) + +For instance, if you want to add py2.7 compatible libs, for apps like Maya2020: +"C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Tools\DCC\Maya\readme.txt" + +These pyside2-tools can be useful, and they are not pip installed, nor distributed. +C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\pyside2-tools + +pyside2-tools instructions: + +1. clone the repo in this location: C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python + >git clone https://github.com/pyside/pyside2-tools + +2. to use as a python package ... + find this and copy: + "< local DCCsi >\3rdParty\Python\pyside2-tools\pyside2uic\__init__.py.in" + + and rename to this: + "< local DCCsi >\3rdParty\Python\pyside2-tools\pyside2uic\__init__.py" + +3. add to PYTHONPATH: < local DCCsi >\3rdParty\Python + in .py something like: site.addsitedir(DCCSI_PYSIDE2_TOOLS) + +See: "< local DCCsi >\config.py" \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Editor/Scripts/bootstrap.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Editor/Scripts/bootstrap.py index d417c572e9..406e2b04ff 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Editor/Scripts/bootstrap.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Editor/Scripts/bootstrap.py @@ -7,9 +7,9 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # -# -- This line is 75 characters ------------------------------------------- +# ------------------------------------------------------------------------- """This module is for use in boostrapping the DccScriptingInterface Gem -with Lumberyard. Note: this boostrap is only designed fo be py3 compatible. +with O3DE. Note: this boostrap is only designed fo be py3 compatible. If you need DCCsi access in py27 (Autodesk Maya for instance) you may need to implement your own boostrapper module. Currently this is boostrapped from add_dccsi.py, as a temporty measure related to this Jira: @@ -24,40 +24,64 @@ import logging as _logging # ------------------------------------------------------------------------- +# ------------------------------------------------------------------------- +_O3DE_RUNNING=None +try: + import azlmbr + _O3DE_RUNNING=True +except: + _O3DE_RUNNING=False +# ------------------------------------------------------------------------- + + # ------------------------------------------------------------------------- # we don't use dynaconf setting here as we might not yet have access # to that site-dir. -_MODULE = 'DCCsi.bootstrap' +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'O3DE.DCCsi.bootstrap' + +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_MODULENAME) # we need to set up basic access to the DCCsi _MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen? -_DCCSIG_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../../..')) -_DCCSIG_PATH = os.getenv('DCCSIG_PATH', _DCCSIG_PATH) -site.addsitedir(_DCCSIG_PATH) +_DCCSI_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../../..')) +_DCCSI_PATH = os.getenv('DCCSI_PATH', _DCCSI_PATH) +site.addsitedir(_DCCSI_PATH) -# we can get basic access to the DCCsi.azpy api now -import azpy +# now we have azpy api access +from azpy.env_bool import env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import ENVAR_DCCSI_LOGLEVEL +from azpy.constants import FRMT_LOG_LONG -# early attach WingIDE debugger (can refactor to include other IDEs later) -while 0: # flag on to attemp to connect wingIDE debugger - from azpy.env_bool import env_bool - if not env_bool('DCCSI_DEBUGGER_ATTACHED', False): - # if not already attached lets do it here - from azpy.test.entry_test import connect_wing - foo = connect_wing() +# set up global space, logging etc. +# set these true if you want them set globally for debugging +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_LOGLEVEL = int(env_bool(ENVAR_DCCSI_LOGLEVEL, int(20))) +if _DCCSI_GDEBUG: + _DCCSI_LOGLEVEL = int(10) + +_logging.basicConfig(format=FRMT_LOG_LONG, level=_DCCSI_LOGLEVEL) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -# settings.setenv() # doing this will add the additional DYNACONF_ envars -def get_dccsi_config(DCCSIG_PATH=_DCCSIG_PATH): +# _settings.setenv() # doing this will add the additional DYNACONF_ envars +def get_dccsi_config(DCCSI_PATH=_DCCSI_PATH): """Convenience method to set and retreive settings directly from module.""" # we can go ahead and just make sure the the DCCsi env is set - # config is SO generic this ensures we are importing a specific one - _spec_dccsi_config = importlib.util.spec_from_file_location("dccsi.config", - Path(DCCSIG_PATH, + # _config is SO generic this ensures we are importing a specific one + _spec_dccsi_config = importlib.util.spec_from_file_location("dccsi._config", + Path(DCCSI_PATH, "config.py")) _dccsi_config = importlib.util.module_from_spec(_spec_dccsi_config) _spec_dccsi_config.loader.exec_module(_dccsi_config) @@ -65,9 +89,12 @@ def get_dccsi_config(DCCSIG_PATH=_DCCSIG_PATH): return _dccsi_config # ------------------------------------------------------------------------- -# set and retreive the base settings on import -config = get_dccsi_config() -settings = config.get_config_settings() +# set and retreive the base env context/_settings on import +_config = get_dccsi_config() +_settings = _config.get_config_settings() + +if _DCCSI_DEV_MODE: + _config.attach_debugger() # attempts to start debugger # done with basic setup # --- END ----------------------------------------------------------------- @@ -77,50 +104,99 @@ settings = config.get_config_settings() # ------------------------------------------------------------------------- if __name__ == '__main__': """Run this file as main""" + + # ------------------------------------------------------------------------- + _O3DE_RUNNING=None + try: + import azlmbr + _O3DE_RUNNING=True + except: + _O3DE_RUNNING=False + # ------------------------------------------------------------------------- - _G_DEBUG = False - _G_TEST_PYSIDE = False + _MODULENAME = __name__ + if _MODULENAME is '__main__': + _MODULENAME = 'O3DE.DCCsi.bootstrap' + + from azpy.constants import STR_CROSSBAR + + # module internal debugging flags + while 0: # temp internal debug flag + _DCCSI_GDEBUG = True + break + + # overide logger for standalone to be more verbose and log to file + import azpy + _LOGGER = azpy.initialize_logger(_MODULENAME, + log_to_file=_DCCSI_GDEBUG, + default_log_level=_DCCSI_LOGLEVEL) + # happy print + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('~ constants.py ... Running script as __main__') + _LOGGER.info(STR_CROSSBAR) + + # parse the command line args + import argparse + parser = argparse.ArgumentParser( + description='O3DE DCCsi Boostrap (Test)', + epilog="Will externally test the DCCsi boostrap") _config = get_dccsi_config() - _settings = config.get_config_settings() - - _log_level = int(_settings.DCCSI_LOGLEVEL) - if _G_DEBUG: - _log_level = int(10) # force debug level - _LOGGER = azpy.initialize_logger(_MODULE, - log_to_file=True, - default_log_level=_log_level) + _settings = _config.get_config_settings(enable_o3de_python=True, + enable_o3de_pyside2=True) + parser.add_argument('-gd', '--global-debug', + type=bool, + required=False, + help='Enables global debug flag.') + parser.add_argument('-dm', '--developer-mode', + type=bool, + required=False, + help='Enables dev mode for early auto attaching debugger.') + parser.add_argument('-tp', '--test-pyside2', + type=bool, + required=False, + help='Runs Qt/PySide2 tests and reports.') + args = parser.parse_args() - # we can now grab values from the DCCsi.config.py dynamic env settings - # the rest of this block is basic debug testing the dynamic settings at boot - _LOGGER.info(f'Running module: {_MODULE}') - _LOGGER.info(f'DCCSIG_PATH: {_settings.DCCSIG_PATH}') - _LOGGER.info(f'DCCSI_G_DEBUG: {_settings.DCCSI_GDEBUG}') - _LOGGER.info(f'DCCSI_DEV_MODE: {_settings.DCCSI_DEV_MODE}') - - _LOGGER.info(f'OS_FOLDER: {_settings.OS_FOLDER}') - _LOGGER.info(f'LY_PROJECT: {_settings.LY_PROJECT}') - _LOGGER.info(f'LY_PROJECT_PATH: {_settings.LY_PROJECT_PATH}') - _LOGGER.info(f'LY_DEV: {_settings.LY_DEV}') - _LOGGER.info(f'LY_BUILD_PATH: {_settings.LY_BUILD_PATH}') - _LOGGER.info(f'LY_BIN_PATH: {_settings.LY_BIN_PATH}') + # easy overrides + if args.global_debug: + _DCCSI_GDEBUG = True + if args.developer_mode: + _DCCSI_DEV_MODE = True + _config.attach_debugger() # attempts to start debugger - _LOGGER.info(f'DCCSIG_PATH: {_settings.DCCSIG_PATH}') - _LOGGER.info(f'DCCSI_PYTHON_LIB_PATH: {_settings.DCCSI_PYTHON_LIB_PATH}') - _LOGGER.info(f'DDCCSI_PY_BASE: {_settings.DDCCSI_PY_BASE}') + if _DCCSI_GDEBUG: + _LOGGER.info(f'DCCSI_PATH: {_settings.DCCSI_PATH}') + _LOGGER.info(f'DCCSI_G_DEBUG: {_settings.DCCSI_GDEBUG}') + _LOGGER.info(f'DCCSI_DEV_MODE: {_settings.DCCSI_DEV_MODE}') + + _LOGGER.info(f'DCCSI_OS_FOLDER: {_settings.DCCSI_OS_FOLDER}') + _LOGGER.info(f'O3DE_PROJECT: {_settings.O3DE_PROJECT}') + _LOGGER.info(f'O3DE_PROJECT_PATH: {_settings.O3DE_PROJECT_PATH}') + _LOGGER.info(f'O3DE_DEV: {_settings.O3DE_DEV}') + _LOGGER.info(f'O3DE_BUILD_PATH: {_settings.O3DE_BUILD_PATH}') + _LOGGER.info(f'O3DE_BIN_PATH: {_settings.O3DE_BIN_PATH}') + + _LOGGER.info(f'DCCSI_PATH: {_settings.DCCSI_PATH}') + _LOGGER.info(f'DCCSI_PYTHON_LIB_PATH: {_settings.DCCSI_PYTHON_LIB_PATH}') + _LOGGER.info(f'DCCSI_PY_BASE: {_settings.DCCSI_PY_BASE}') - if _G_TEST_PYSIDE: + if _DCCSI_GDEBUG or args.test_pyside2: try: import PySide2 except: # set up Qt/PySide2 access and test - _settings = _config.get_config_settings(setup_ly_pyside=True) + _settings = _config.get_config_settings(enable_o3de_pyside2=True) import PySide2 _LOGGER.info(f'PySide2: {PySide2}') - _LOGGER.info(f'LY_BIN_PATH: {_settings.LY_BIN_PATH}') + _LOGGER.info(f'O3DE_BIN_PATH: {_settings.O3DE_BIN_PATH}') _LOGGER.info(f'QT_PLUGIN_PATH: {_settings.QT_PLUGIN_PATH}') _LOGGER.info(f'QT_QPA_PLATFORM_PLUGIN_PATH: {_settings.QT_QPA_PLATFORM_PLUGIN_PATH}') _config.test_pyside2() + + if not _O3DE_RUNNING: + # return + sys.exit() # --- END ----------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/README.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/README.txt new file mode 100644 index 0000000000..9a3efe589a --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/README.txt @@ -0,0 +1,87 @@ +""" +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 +""" +# ------------------------------------------------------------------------- + +This folder contains the DccScriptingInterface (DCCsi) for O3DE + +Notice: The old \\SDK folder is being replaced with \\Tools +The scripts in \\SDK may be out of data (and not run) +When scripts are finished being updated and refactored into \\Tools the \\SDK will be removed + +What is the DCCsi? + +- A shared development environment for technical art oriented to working with Python across a number of DCC tools. +- Leverage the existing python ecosystem for technical art. +- Integrate a DCC app like Substance (or Substance SAT api) from the Python driven VFX and Games ecosystem. +- Extend O3DE and unlock its potential for content creators, and the Technical Artists that service them. + +Tenets: +(1) Interoperability: Design DCC-agnostic modules and DCC-bespoke modules + to work together efficiently and intuitively. + +(2) Encapsulation: Define a module in terms of its essential features and interface to other components, + to facilitate logical layered design and easy maintenance. + +(3) Extensibility: Design the tool set to be easily extensible with new functionality and new tools. + Individual pieces should have a generic communication mechanism to allow newly written tools to slot cleanly and transparently into the tool chain. + +What is provided (High Level): +- DCC-Agnostic Python Framework (as a modular Gem) related to multiple integrations for: + O3DE Editor (python scripting, utils and PySide2 tools) + DCC applications and their Python APIs/SDKs + Custom standalone tools and utils (python based) + external from cmd line + external standalone + integrated to run within O3DE Editor + +What is provided (by folder): + +\3rdParty: Allows third party libs/packages to be integrated outside of O3DE + Example: O3DE is py3, Maya 2020 (and earlier) is py27 + O3DE provides a patterns for Gems to provide a requirements.txt + See: + DccScriptingInterface\reqiurements.txt + ^ These packages will be fetched and installed into O3DE python at build time + + This means for some applications like Maya we need another way to add the same packages + See: + DccScriptingInterface\SDK\Maya\readme.txt + DccScriptingInterface\SDK\Maya\requirements.txt + DccScriptingInterface\3rdParty\Python\Lib\2.x\2.7.x\site-packages\* + + Packages that reside in 3rdParty are never commited to the repo (only fetched+installed) + +\Assets: All O3DE Gems can maintain an asset folder + If a Gem contains an \Asset folder, these assets are folded into the projects asset data + These assets are processed by the Asset Processor for use in the Editor and Runtime + In the DCCsi the \Assets folder primarily contains TestData + +\azpy Core (shared) API, A pure python Package and Modules + +\Code Contains the bare bones C++ scaffold to build and integrate the Gem with O3DE + Notes: portions of the DCCsi can be utilized outside of O3DE + thus this Gem doens't have to be enabled and built for some use cases + +\Editor This folder provides an entry point pattern for extending O3DE Editor with python + When a Gem is enabled ... + If the following if found, it will be executed when the Editor boots: + "Editor\Scripts\bootstrap.py" + + This can be used to initialize code access, extend the editor (PySide2), etc. + +\Tools This is where the following is maintained: + +\Tools\DCC Integration for DCC tools: + configuration of tool (managed env, etc.) + bootstrapping, such as providing the tool access to azpy api code + extensibility, such as adding new functionality or tool to the app + +\Tools\DCC\Maya An example of adding a integration for Autodesk Maya + +\Tools\Env\Windows This provides a .bat file managed env to configure and bootsrap windows apps +\Tools\Launchers\windows Provides .bat files based tool launchers for windows (accesses env) + diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Atom/Scripts/Python/minspect.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Atom/Scripts/Python/minspect.py deleted file mode 100644 index 760807042c..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Atom/Scripts/Python/minspect.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -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 pymel.core as pmc -import sys -import types - - -def syspath(): - print 'sys.path:' - for p in sys.path: - print ' ' + p - - -def info(obj): - """Prints information about the object.""" - - lines = ['Info for %s' % obj.name(), - 'Attributes:'] - # Get the name of all attributes - for a in obj.listAttr(): - lines.append(' ' + a.name()) - lines.append('MEL type: %s' % obj.type()) - lines.append('MRO:') - lines.extend([' ' + t.__name__ for t in type(obj).__mro__]) - result = '\n'.join(lines) - print result - - -def _is_pymel(obj): - try: # (1) - module = obj.__module__ # (2) - except AttributeError: # (3) - try: - module = obj.__name__ # (4) - except AttributeError: - return None # (5) - return module.startswith('pymel') # (6) - - -def _py_to_helpstr(obj): - if isinstance(obj, basestring): - return 'search.html?q=%s' % (obj.replace(' ', '+')) - if not _is_pymel(obj): - return None - if isinstance(obj, types.ModuleType): - return ('generated/%(module)s.html#module-%(module)s' % - dict(module=obj.__name__)) - if isinstance(obj, types.MethodType): - return ('generated/classes/%(module)s/' - '%(module)s.%(typename)s.html' - '#%(module)s.%(typename)s.%(methname)s' % dict( - module=obj.__module__, - typename=obj.im_class.__name__, - methname=obj.__name__)) - if isinstance(obj, types.FunctionType): - return ('generated/functions/%(module)s/' - '%(module)s.%(funcname)s.html' - '#%(module)s.%(funcname)s' % dict( - module=obj.__module__, - funcname=obj.__name__)) - if not isinstance(obj, type): - obj = type(obj) - return ('generated/classes/%(module)s/' - '%(module)s.%(typename)s.html' - '#%(module)s.%(typename)s' % dict( - module=obj.__module__, - typename=obj.__name__)) - - -def test_py_to_helpstr(): - def dotest(obj, ideal): - result = _py_to_helpstr(obj) - assert result == ideal, '%s != %s' % (result, ideal) - dotest('maya rocks', 'search.html?q=maya+rocks') - dotest(pmc.nodetypes, - 'generated/pymel.core.nodetypes.html' - '#module-pymel.core.nodetypes') - dotest(pmc.nodetypes.Joint, - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint') - dotest(pmc.nodetypes.Joint(), - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint') - dotest(pmc.nodetypes.Joint().getTranslation, - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint.getTranslation') - dotest(pmc.joint, - 'generated/functions/pymel.core.animation/' - 'pymel.core.animation.joint.html' - '#pymel.core.animation.joint') - dotest(object(), None) - dotest(10, None) - dotest([], None) - dotest(sys, None) - - -def test_py_to_helpstrFAIL(): - assert 1 == 2, '1 != 2' - - -import webbrowser # (1) -HELP_ROOT_URL = ('http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/PyMel/')# (2) - - -def pmhelp(obj): # (3) - """Gives help for a pymel or python object. - - If obj is not a PyMEL object, use Python's built-in - `help` function. - If obj is a string, open a web browser to a search in the - PyMEL help for the string. - Otherwise, open a web browser to the page for the object. - """ - tail = _py_to_helpstr(obj) - if tail is None: - help(obj) # (4) - else: - webbrowser.open(HELP_ROOT_URL + tail) # (5) - - -if __name__ == '__main__': - test_py_to_helpstr() - print 'Tests ran successfully.' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/launcher.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/launcher.bat deleted file mode 100644 index fb07178344..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/launcher.bat +++ /dev/null @@ -1,85 +0,0 @@ -@echo off - -REM -REM Copyright (c) Contributors to the Open 3D Engine Project. -REM For complete copyright and license terms please see the LICENSE at the root of this distribution. -REM -REM SPDX-License-Identifier: Apache-2.0 OR MIT -REM -REM - -:: Set up and run LY Python CMD prompt -:: Sets up the DccScriptingInterface_Env, -:: Puts you in the CMD within the dev environment - -:: Set up window -TITLE Lumberyard DCC Scripting Interface Cmd -:: Use obvious color to prevent confusion (Grey with Yellow Text) -COLOR 8E - -%~d0 -cd %~dp0 - -:: Keep changes local -SETLOCAL enableDelayedExpansion - -:: This maps up to the \Dev folder -IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..\..\..\..\..\..) - -:: Change to root Lumberyard dev dir -:: Don't use the LY_DEV so we can test that ENVAR!!! -CD /d %DEV_REL_PATH% -set Rel_Dev=%CD% -echo Rel_Dev = %Rel_Dev% -:: Restore original directory -popd - -set DCCSI_PYTHON_INSTALL=%Rel_Dev%\Tools\Python\3.7.5\windows - -:: add to the PATH -SET PATH=%DCCSI_PYTHON_INSTALL%;%PATH% - -:: dcc scripting interface gem path -set DCCSIG_PATH=%Rel_Dev%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface -echo DCCSIG_PATH = %DCCSIG_PATH% - -:: add to the PATH -SET PATH=%DCCSIG_PATH%;%PATH% - -:: Constant Vars (Global) -:: global debug (propogates) -IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false) -echo DCCSI_GDEBUG = %DCCSI_GDEBUG% -:: initiates debugger connection -IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=false) -echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE% -:: sets debugger, options: WING, PYCHARM -IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING) -echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER% - -echo. -echo _____________________________________________________________________ -echo. -echo ~ LY DCCsi, DCC Material Converter -echo _____________________________________________________________________ -echo. - -:: Change to root dir -CD /D %DCCSIG_PATH% - -:: add to the PATH -SET PATH=%DCCSIG_PATH%;%PATH% - -set PYTHONPATH=%DCCSIG_PATH%;%PYTHONPATH% - -CALL %DCCSI_PYTHON_INSTALL%\python.exe "%DCCSIG_PATH%\SDK\Maya\Scripts\Python\kitbash_converter\standalone.py" - - -ENDLOCAL - -:: Return to starting directory -POPD - -:END_OF_FILE - -exit /b 0 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/main.py index e554b1ca68..d0e9759208 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/main.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/kitbash_converter/main.py @@ -51,8 +51,8 @@ module_name = 'kitbash_converter.main' log_file_path = os.path.join(settings.DCCSI_LOG_PATH, f'{module_name}.log') _log_level = int(20) -_G_DEBUG = True -if _G_DEBUG: +_DCCSI_GDEBUG = True +if _DCCSI_GDEBUG: _log_level = int(10) from azpy.constants import FRMT_LOG_LONG diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Cmd.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Cmd.bat deleted file mode 100644 index ce118d7710..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Cmd.bat +++ /dev/null @@ -1,45 +0,0 @@ -:: coding:utf-8 -:: !/usr/bin/python -:: -:: 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 -:: -:: - -@echo off -:: Set up and run LY Python CMD prompt -:: Sets up the DccScriptingInterface_Env, -:: Puts you in the CMD within the dev environment - -:: Set up window -TITLE Lumberyard DCC Scripting Interface Cmd -:: Use obvious color to prevent confusion (Grey with Yellow Text) -COLOR 8E - -%~d0 -cd %~dp0 -PUSHD %~dp0 - -:: Keep changes local -SETLOCAL enableDelayedExpansion - -CALL %~dp0\Project_Env.bat - -echo. -echo _____________________________________________________________________ -echo. -echo ~ LY DCC Scripting Interface CMD ... -echo _____________________________________________________________________ -echo. - -:: Create command prompt with environment -CALL %windir%\system32\cmd.exe - -ENDLOCAL - -:: Return to starting directory -POPD - -:END_OF_FILE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Maya_2020.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Maya_2020.bat deleted file mode 100644 index 32e910790e..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_Maya_2020.bat +++ /dev/null @@ -1,68 +0,0 @@ -:: coding:utf-8 -:: !/usr/bin/python -:: -:: 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 -:: -:: - -@echo off -:: Launches maya with a bunch of local hooks for Lumberyard -:: ToDo: move all of this to a .json data driven boostrapping system - -%~d0 -cd %~dp0 -PUSHD %~dp0 - -echo ________________________________ -echo ~ calling PROJ_Env.bat - -:: Keep changes local -SETLOCAL enableDelayedExpansion - -:: PY version Major -set DCCSI_PY_VERSION_MAJOR=2 -echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR% - -:: PY version Major -set DCCSI_PY_VERSION_MINOR=7 -echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR% - -:: Maya Version -set MAYA_VERSION=2020 -echo MAYA_VERSION = %MAYA_VERSION% - -:: if a local customEnv.bat exists, run it -IF EXIST "%~dp0Project_Env.bat" CALL %~dp0Project_Env.bat - -echo ________________________________ -echo Launching Maya %MAYA_VERSION% for Lumberyard... - -:::: Set Maya native project acess to this project -::set MAYA_PROJECT=%LY_PROJECT% -::echo MAYA_PROJECT = %MAYA_PROJECT% - -:: DX11 Viewport -Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11 - -:: Default to the right version of Maya if we can detect it... and launch -IF EXIST "%MAYA_LOCATION%\bin\Maya.exe" ( - start "" "%MAYA_LOCATION%\bin\Maya.exe" %* -) ELSE ( - Where maya.exe 2> NUL - IF ERRORLEVEL 1 ( - echo Maya.exe could not be found - pause - ) ELSE ( - start "" Maya.exe %* - ) -) - -:: Return to starting directory -POPD - -:END_OF_FILE - -exit /b 0 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_WingIDE-7-1.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_WingIDE-7-1.bat deleted file mode 100644 index 4d82bd3e16..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Launch_WingIDE-7-1.bat +++ /dev/null @@ -1,81 +0,0 @@ -:: coding:utf-8 -:: !/usr/bin/python -:: -:: 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 -:: -:: - -@echo off -:: Launches Wing IDE and the DccScriptingInterface Project Files - -echo. -echo _____________________________________________________________________ -echo. -echo ~ Setting up LY DCCsi WingIDE Dev Env... -echo _____________________________________________________________________ -echo. - -:: Store current dir -%~d0 -cd %~dp0 -PUSHD %~dp0 - -:: Keep changes local -SETLOCAL enableDelayedExpansion - -SET ABS_PATH=%~dp0 -echo Current Dir, %ABS_PATH% - -:: WingIDE version Major -SET WING_VERSION_MAJOR=7 -echo WING_VERSION_MAJOR = %WING_VERSION_MAJOR% - -:: WingIDE version Major -SET WING_VERSION_MINOR=1 -echo WING_VERSION_MINOR = %WING_VERSION_MINOR% - -:: note the changed path from IDE to Pro -set WINGHOME=%PROGRAMFILES(X86)%\Wing Pro %WING_VERSION_MAJOR%.%WING_VERSION_MINOR% -echo WINGHOME = %WINGHOME% - -CALL %~dp0\Project_Env.bat - -echo. -echo _____________________________________________________________________ -echo. -echo ~ WingIDE Version %WING_VERSION_MAJOR%.%WING_VERSION_MINOR% -echo _____________________________________________________________________ -echo. - -SET WING_PROJ=%DCCSIG_PATH%\Solutions\.wing\DCCsi_%WING_VERSION_MAJOR%x.wpr -echo WING_PROJ = %WING_PROJ% - -echo. -echo _____________________________________________________________________ -echo. -echo ~ Launching %LY_PROJECT% project in WingIDE %WING_VERSION_MAJOR%.%WING_VERSION_MINOR% ... -echo _____________________________________________________________________ -echo. - - -IF EXIST "%WINGHOME%\bin\wing.exe" ( - start "" "%WINGHOME%\bin\wing.exe" "%WING_PROJ%" -) ELSE ( - Where wing.exe 2> NUL - IF ERRORLEVEL 1 ( - echo wing.exe could not be found - pause - ) ELSE ( - start "" wing.exe "%WING_PROJ%" - ) -) - -ENDLOCAL - -:: Return to starting directory -POPD - -:END_OF_FILE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Project_Env.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Project_Env.bat deleted file mode 100644 index f958d98616..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/Project_Env.bat +++ /dev/null @@ -1,72 +0,0 @@ -:: coding:utf-8 -:: !/usr/bin/python -:: -:: 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 -:: -:: - -@echo off -:: Sets up environment for Lumberyard DCC tools and code access - -:: Store current dir -%~d0 -cd %~dp0 -PUSHD %~dp0 - -for %%a in (.) do set LY_PROJECT=%%~na - -echo. -echo _____________________________________________________________________ -echo. -echo ~ Setting up LY DSI PROJECT Environment ... -echo _____________________________________________________________________ -echo. - -echo LY_PROJECT = %LY_PROJECT% - -:: Put you project env vars and overrides here - -:: chanhe the relative path up to dev -set DEV_REL_PATH=../../.. -set ABS_PATH=%~dp0 - -:: Override the default maya version -set MAYA_VERSION=2020 -echo MAYA_VERSION = %MAYA_VERSION% - -set LY_PROJECT_PATH=%ABS_PATH% -echo LY_PROJECT_PATH = %LY_PROJECT_PATH% - -:: Change to root Lumberyard dev dir -CD /d %LY_PROJECT_PATH%\%DEV_REL_PATH% -set LY_DEV=%CD% -echo LY_DEV = %LY_DEV% - -CALL %LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Launchers\Windows\Env.bat - -rem :: Constant Vars (Global) -rem SET LYPY_GDEBUG=0 -rem echo LYPY_GDEBUG = %LYPY_GDEBUG% -rem SET LYPY_DEV_MODE=0 -rem echo LYPY_DEV_MODE = %LYPY_DEV_MODE% -rem SET LYPY_DEBUGGER=WING -rem echo LYPY_DEBUGGER = %LYPY_DEBUGGER% - -:: Restore original directory -popd - -:: Change to root dir -CD /D %ABS_PATH% - -:: if the user has set up a custom env call it -IF EXIST "%~dp0User_Env.bat" CALL %~dp0User_Env.bat - -GOTO END_OF_FILE - -:: Return to starting directory -POPD - -:END_OF_FILE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/main.py index 3b985199bb..09caef836f 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/main.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/legacy_asset_converter/main.py @@ -80,8 +80,8 @@ module_name = 'legacy_asset_converter.main' log_file_path = os.path.join(settings.DCCSI_LOG_PATH, f'{module_name}.log') _log_level = int(20) -_G_DEBUG = True -if _G_DEBUG: +_DCCSI_GDEBUG = True +if _DCCSI_GDEBUG: _log_level = int(10) from azpy.constants import FRMT_LOG_LONG diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/maya_dcc_materials/minspect.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/maya_dcc_materials/minspect.py deleted file mode 100644 index 668382480c..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/maya_dcc_materials/minspect.py +++ /dev/null @@ -1,135 +0,0 @@ -# -*- coding: utf-8 -*- -# !/usr/bin/python -# -# 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 pymel.core as pmc -import sys -import types - - -def syspath(): - print 'sys.path:' - for p in sys.path: - print ' ' + p - - -def info(obj): - """Prints information about the object.""" - - lines = ['Info for %s' % obj.name(), - 'Attributes:'] - # Get the name of all attributes - for a in obj.listAttr(): - lines.append(' ' + a.name()) - lines.append('MEL type: %s' % obj.type()) - lines.append('MRO:') - lines.extend([' ' + t.__name__ for t in type(obj).__mro__]) - result = '\n'.join(lines) - print result - - -def _is_pymel(obj): - try: # (1) - module = obj.__module__ # (2) - except AttributeError: # (3) - try: - module = obj.__name__ # (4) - except AttributeError: - return None # (5) - return module.startswith('pymel') # (6) - - -def _py_to_helpstr(obj): - if isinstance(obj, basestring): - return 'search.html?q=%s' % (obj.replace(' ', '+')) - if not _is_pymel(obj): - return None - if isinstance(obj, types.ModuleType): - return ('generated/%(module)s.html#module-%(module)s' % - dict(module=obj.__name__)) - if isinstance(obj, types.MethodType): - return ('generated/classes/%(module)s/' - '%(module)s.%(typename)s.html' - '#%(module)s.%(typename)s.%(methname)s' % dict( - module=obj.__module__, - typename=obj.im_class.__name__, - methname=obj.__name__)) - if isinstance(obj, types.FunctionType): - return ('generated/functions/%(module)s/' - '%(module)s.%(funcname)s.html' - '#%(module)s.%(funcname)s' % dict( - module=obj.__module__, - funcname=obj.__name__)) - if not isinstance(obj, type): - obj = type(obj) - return ('generated/classes/%(module)s/' - '%(module)s.%(typename)s.html' - '#%(module)s.%(typename)s' % dict( - module=obj.__module__, - typename=obj.__name__)) - - -def test_py_to_helpstr(): - def dotest(obj, ideal): - result = _py_to_helpstr(obj) - assert result == ideal, '%s != %s' % (result, ideal) - dotest('maya rocks', 'search.html?q=maya+rocks') - dotest(pmc.nodetypes, - 'generated/pymel.core.nodetypes.html' - '#module-pymel.core.nodetypes') - dotest(pmc.nodetypes.Joint, - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint') - dotest(pmc.nodetypes.Joint(), - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint') - dotest(pmc.nodetypes.Joint().getTranslation, - 'generated/classes/pymel.core.nodetypes/' - 'pymel.core.nodetypes.Joint.html' - '#pymel.core.nodetypes.Joint.getTranslation') - dotest(pmc.joint, - 'generated/functions/pymel.core.animation/' - 'pymel.core.animation.joint.html' - '#pymel.core.animation.joint') - dotest(object(), None) - dotest(10, None) - dotest([], None) - dotest(sys, None) - - -def test_py_to_helpstrFAIL(): - assert 1 == 2, '1 != 2' - - -import webbrowser # (1) -HELP_ROOT_URL = ('http://help.autodesk.com/cloudhelp/2018/ENU/Maya-Tech-Docs/PyMel/')# (2) - - -def pmhelp(obj): # (3) - """Gives help for a pymel or python object. - - If obj is not a PyMEL object, use Python's built-in - `help` function. - If obj is a string, open a web browser to a search in the - PyMEL help for the string. - Otherwise, open a web browser to the page for the object. - """ - tail = _py_to_helpstr(obj) - if tail is None: - help(obj) # (4) - else: - webbrowser.open(HELP_ROOT_URL + tail) # (5) - - -if __name__ == '__main__': - test_py_to_helpstr() - print 'Tests ran successfully.' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter.py index 10cdac813a..4c24a61b59 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter.py @@ -23,7 +23,7 @@ def returnStubDir(stub): break if (len(tail) == 0): path = "" - if _G_DEBUG: + if _DCCSI_GDEBUG: print('~ Debug Message: I was not able to find the ' 'path to that file (stub) in a walk-up from currnet path') break diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter_maya.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter_maya.py index 3ea7b1e5e7..3a7ec49e74 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter_maya.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/Python/stingraypbs_converter/stingrayPBS_converter_maya.py @@ -24,7 +24,7 @@ def returnStubDir(stub, start_path): break if (len(tail) == 0): path = "" - if _G_DEBUG: + if _DCCSI_GDEBUG: print('~ Debug Message: I was not able to find the ' 'path to that file (stub) in a walk-up from currnet path') break diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_callbacks.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_callbacks.py index 4dda18c403..8c3b0f6a64 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_callbacks.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_callbacks.py @@ -42,7 +42,7 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, True) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, True) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, True) _MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_callbacks' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_defaults.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_defaults.py index d6db5e42f4..639b03c297 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_defaults.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_defaults.py @@ -37,7 +37,7 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_defaults' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_menu.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_menu.py index 4e85a448c1..e417324d0b 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_menu.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/set_menu.py @@ -37,7 +37,7 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_menu' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/userSetup.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/userSetup.py index aee5563c3d..04eb027142 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/userSetup.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Maya/Scripts/userSetup.py @@ -56,7 +56,7 @@ import maya.mel as mel # ------------------------------------------------------------------------- # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) #_DCCSI_DEV_MODE = True # force true for debugger testing @@ -69,7 +69,7 @@ _MODULENAME = str('{0}.{1}'.format(_APP_TAG, _TOOL_TAG)) _LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=int(20)) _LOGGER.info('Initializing: {0}.'.format({_MODULENAME})) -_LOGGER.info('DCCSI_GDEBUG: {0}.'.format({_G_DEBUG})) +_LOGGER.info('DCCSI_GDEBUG: {0}.'.format({_DCCSI_GDEBUG})) _LOGGER.info('DCCSI_DEV_MODE: {0}.'.format({_DCCSI_DEV_MODE})) # flag to turn off setting up callbacks, until they are fully implemented @@ -175,17 +175,17 @@ try: except Exception as e: _LOGGER.critical(_STR_ERROR_ENVAR.format(_BASE_ENVVAR_DICT[ENVAR_DCCSI_SDK_PATH])) -_LY_PROJECT_PATH = None +_O3DE_PROJECT_PATH = None try: - _LY_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_LY_PROJECT_PATH] + _O3DE_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH] except Exception as e: - _LOGGER.critical(_STR_ERROR_ENVAR.format(_BASE_ENVVAR_DICT[ENVAR_LY_PROJECT_PATH])) + _LOGGER.critical(_STR_ERROR_ENVAR.format(_BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH])) # check some env var tags (fail if no, likely means no proper code access) -_LY_DEV = _BASE_ENVVAR_DICT[ENVAR_LY_DEV] -_LY_DCCSIG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSIG_PATH] -_LY_DCCSI_LOG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_LOG_PATH] -_LY_AZPY_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_AZPY_PATH] +_O3DE_DEV = _BASE_ENVVAR_DICT[ENVAR_O3DE_DEV] +_O3DE_DCCSIG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSIG_PATH] +_O3DE_DCCSI_LOG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_LOG_PATH] +_O3DE_AZPY_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_AZPY_PATH] # ------------------------------------------------------------------------- @@ -270,18 +270,18 @@ def post_startup(): install_fix_paths() # set the project workspace - #_LY_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_LY_PROJECT_PATH] - _project_workspace = os.path.join(_LY_PROJECT_PATH, TAG_MAYA_WORKSPACE) + #_O3DE_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH] + _project_workspace = os.path.join(_O3DE_PROJECT_PATH, TAG_MAYA_WORKSPACE) if os.path.isfile(_project_workspace): try: # load workspace - maya.cmds.workspace(_LY_PROJECT_PATH, openWorkspace=True) + maya.cmds.workspace(_O3DE_PROJECT_PATH, openWorkspace=True) _LOGGER.info('Loaded workspace file: {0}'.format(_project_workspace)) - maya.cmds.workspace(_LY_PROJECT_PATH, update=True) + maya.cmds.workspace(_O3DE_PROJECT_PATH, update=True) except Exception as e: _LOGGER.error(e) else: - _LOGGER.warning('Workspace file not found: {1}'.format(_LY_PROJECT_PATH)) + _LOGGER.warning('Workspace file not found: {1}'.format(_O3DE_PROJECT_PATH)) # Set up Lumberyard, maya default setting from set_defaults import set_defaults @@ -292,7 +292,7 @@ def post_startup(): _LOGGER.info('Add UI dependent tools') # wrap in a try, because we haven't implmented it yet try: - mel.eval(str(r'source "{}"'.format(TAG_LY_DCC_MAYA_MEL))) + mel.eval(str(r'source "{}"'.format(TAG_O3DE_DCC_MAYA_MEL))) except Exception as e: _LOGGER.error(e) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/blender_materials.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/blender_materials.py deleted file mode 100755 index 6d7bd10f89..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/blender_materials.py +++ /dev/null @@ -1,85 +0,0 @@ -# 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 bpy -import collections -import json - - -def get_shader_information(): - """ - Queries all materials and corresponding material attributes and file textures in the Blender scene. - - :return: - """ - # TODO - link file texture location to PBR material plugs- finding it difficult to track down how this is achieved - # in the Blender Python API documentation and/or in forums - - materials_count = 1 - shader_types = get_blender_shader_types() - materials_dictionary = {} - for target_mesh in [o for o in bpy.data.objects if type(o.data) is bpy.types.Mesh]: - material_information = collections.OrderedDict(DccApplication='Blender', AppliedMesh=target_mesh, - SceneName=bpy.data.filepath, MaterialAttributes={}, - FileConnections={}) - for target_material in target_mesh.data.materials: - material_information['MaterialName'] = target_material.name - shader_attributes = {} - shader_file_connections = {} - - for node in target_material.node_tree.nodes: - socket = node.inputs[0] - print('NODE: {}'.format(node)) - print('Socket: {}'.format(socket)) - - for material_input in node.inputs: - attribute_name = material_input.name - try: - attribute_value = material_input.default_value - print('Name: [{}] [{}] ValueType ::::::> {}'.format(attribute_name, attribute_value, - type(attribute_value))) - material_information['MaterialAttributes'].update({attribute_name: str(attribute_value)}) - except Exception as e: - pass - print('\n') - if node.type == 'TEX_IMAGE': - material_information['FileConnections'].update({str(node): str(node.image.filepath)}) - if node.name in shader_types.keys(): - material_information['MaterialType'] = shader_types[node.name] - - -# material_information['MaterialAttributes'] = shader_attributes - materials_dictionary['Material_{}'.format(materials_count)] = material_information - materials_count += 1 - print('_________________________________________________________________\n') - - return materials_dictionary - - -def get_blender_shader_types(): - """ - This returns all the material types present in the Blender scene - :return: - """ - shader_types = {} - ddir = lambda data, filter_str: [i for i in dir(data) if i.startswith(filter_str)] - get_nodes = lambda cat: [i for i in getattr(bpy.types, cat).category.items(None)] - cycles_categories = ddir(bpy.types, "NODE_MT_category_SH_NEW") - for cat in cycles_categories: - if cat == 'NODE_MT_category_SH_NEW_SHADER': - for node in get_nodes(cat): - shader_types[node.label] = node.nodetype - return shader_types - - -materials_dictionary = get_shader_information() -#print('Materials Dictionary:') -#print(materials_dictionary) -#parsed = json.loads(str(materials_dictionary)) -#print(json.dumps(parsed, indent=4, sort_keys=True)) - - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/cli_control.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/cli_control.py deleted file mode 100755 index 1d91275545..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/cli_control.py +++ /dev/null @@ -1,52 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# -# -# -- This line is 75 characters ------------------------------------------- - -import click -import os -import main as app_main - - -@click.version_option('1.0.0') -@click.option('--output', default='PBR', help='Lumberyard material type. Current options: [pbr_basic]') -@click.argument('operands', type=click.STRING, nargs=-1) -@click.command(context_settings=dict(ignore_unknown_options=True)) -def main(output, operands): - target_files = [] - for index, operand in enumerate(operands): - entry_path = os.path.abspath(str(operand)) - if os.path.isdir(entry_path): - for directory_path, directory_names, file_names in os.walk(entry_path): - for file_name in file_names: - if is_valid_file(file_name): - target_files.append(os.path.join(entry_path, file_name)) - else: - if is_valid_file(operand): - target_files.append(operand) - - if len(target_files): - app_main.launch_material_converter('standalone', output, target_files) - - -def is_valid_file(file_name): - """ - Allows only supported DCC application files by extensions - :param file_name: The name of the file. - :return: - """ - target_extensions = 'ma mb fbx blend max'.split(' ') - if file_name.split('.')[-1] in target_extensions: - return True - return False - - -if __name__ == '__main__': - main() - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/dcc_material_mapping.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/dcc_material_mapping.py deleted file mode 100755 index 6369cb5658..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/dcc_material_mapping.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# -# -# -- This line is 75 characters ------------------------------------------- - -import logging - -logging.basicConfig(level=logging.DEBUG) - -def get_maya_material_mapping(name, material_type, file_connections): - """ - Helps map found material DCC attribute values/file connections with Lumberyard materials. - - :param name: Material name from within Maya - :param material_type: Maya Material type to match values to (i.e. Stingray PBS, aiStandardSurface(Arnold) - :param file_connections: List of all connected texture files from Maya - :return: Key value pairs for attributes/file textures assigned as Lumberyard material values - """ - material_properties = {} - if material_type == 'StingrayPBS': - logging.debug('Mapping StingrayPBS') - maps = 'color, metallic, roughness, normal, emissive, ao, opacity'.split(', ') - naming_exceptions = {'color': 'baseColor', 'ao': 'ambientOcclusion'} - for m in maps: - texture_attribute = 'TEX_{}_map'.format(m) - for tex in file_connections.keys(): - if tex.find(texture_attribute) != -1: - key = m if m not in naming_exceptions else naming_exceptions.get(m) - logging.debug('Key, Value: {} {}.{}'.format(key, name, texture_attribute)) - material_properties[key] = {'useTexture': 'true', - 'textureMap': file_connections.get( - '{}.{}'.format(name, texture_attribute))} - elif material_type == 'aiStandardSurface': - logging.debug('Mapping AiStandardSurface') - # TODO- Occlusion is based on a more difficult setup- there is no standard channel. Set this up as time permits - maps = 'baseColor, metalness, specularRoughness, normal, emissionColor, opacity'.split(', ') - naming_exceptions = {'metalness': 'metallic', 'specularRoughness': 'roughness', 'emissionColor': 'emissive'} - for m in maps: - key = m if m not in naming_exceptions.keys() else naming_exceptions.get(m) - texture_attribute = m - for tex in file_connections.keys(): - if tex.find(texture_attribute) != -1: - logging.debug('Key, Value: {} {}.{}'.format(key, name, texture_attribute)) - material_properties[key] = {'useTexture': 'true', - 'textureMap': file_connections.get( - '{}.{}'.format(name, texture_attribute))} - else: - pass - - return material_properties - - -def get_blender_material_mapping(name, material_type, file_connections): - pass - - -def get_max_material_mapping(name, material_type, file_connections): - pass - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/drag_and_drop.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/drag_and_drop.py deleted file mode 100755 index d02104b12c..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/drag_and_drop.py +++ /dev/null @@ -1,65 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# -# -# -- This line is 75 characters ------------------------------------------- - -from PySide2 import QtWidgets, QtCore -from PySide2.QtCore import Signal - -class DragAndDrop(QtWidgets.QWidget): - drop_update = QtCore.Signal(list) - drop_over = QtCore.Signal(bool) - - def __init__(self, frame_color=None, highlight=None, parent=None): - super(DragAndDrop, self).__init__(parent) - - self.urls = [] - self.frame_color = frame_color - self.frame_highlight = highlight - self.setContentsMargins(0, 0, 0, 0) - self.setAcceptDrops(True) - - self.drag_and_drop_frame = QtWidgets.QFrame(self) - self.drag_and_drop_frame.setGeometry(0, 0, 5000, 5000) - self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color)) - - def dragEnterEvent(self, e): - if e.mimeData().hasUrls: - e.accept() - self.drop_over.emit(True) - if self.frame_highlight: - self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_highlight)) - else: - e.ignore() - - def dragLeaveEvent(self, e): - self.drop_over.emit(False) - - if self.frame_highlight: - self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color)) - - def dragMoveEvent(self, e): - if e.mimeData().hasUrls: - e.accept() - else: - e.ignore() - - def dropEvent(self, e): - if e.mimeData().hasUrls: - e.setDropAction(QtCore.Qt.CopyAction) - e.accept() - - for url in e.mimeData().urls(): - file_name = str(url.toLocalFile()) - self.urls.append(file_name) - self.drop_update.emit(self.urls) - if self.frame_highlight: - self.drag_and_drop_frame.setStyleSheet('background-color:rgb({});'.format(self.frame_color)) - else: - e.ignore() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/launcher.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/launcher.bat deleted file mode 100644 index d609c26897..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/launcher.bat +++ /dev/null @@ -1,85 +0,0 @@ -@echo off - -REM -REM Copyright (c) Contributors to the Open 3D Engine Project. -REM For complete copyright and license terms please see the LICENSE at the root of this distribution. -REM -REM SPDX-License-Identifier: Apache-2.0 OR MIT -REM -REM - -:: Set up and run LY Python CMD prompt -:: Sets up the DccScriptingInterface_Env, -:: Puts you in the CMD within the dev environment - -:: Set up window -TITLE Lumberyard DCC Scripting Interface Cmd -:: Use obvious color to prevent confusion (Grey with Yellow Text) -COLOR 8E - -%~d0 -cd %~dp0 - -:: Keep changes local -SETLOCAL enableDelayedExpansion - -:: This maps up to the \Dev folder -IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..\..\..\..) - -:: Change to root Lumberyard dev dir -:: Don't use the LY_DEV so we can test that ENVAR!!! -CD /d %DEV_REL_PATH% -set Rel_Dev=%CD% -echo Rel_Dev = %Rel_Dev% -:: Restore original directory -popd - -set DCCSI_PYTHON_INSTALL=%Rel_Dev%\Tools\Python\3.7.5\windows - -:: add to the PATH -SET PATH=%DCCSI_PYTHON_INSTALL%;%PATH% - -:: dcc scripting interface gem path -set DCCSIG_PATH=%Rel_Dev%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface -echo DCCSIG_PATH = %DCCSIG_PATH% - -:: add to the PATH -SET PATH=%DCCSIG_PATH%;%PATH% - -:: Constant Vars (Global) -:: global debug (propogates) -IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false) -echo DCCSI_GDEBUG = %DCCSI_GDEBUG% -:: initiates debugger connection -IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=false) -echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE% -:: sets debugger, options: WING, PYCHARM -IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING) -echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER% - -echo. -echo _____________________________________________________________________ -echo. -echo ~ LY DCCsi, DCC Material Converter -echo _____________________________________________________________________ -echo. - -:: Change to root dir -CD /D %DCCSIG_PATH% - -:: add to the PATH -SET PATH=%DCCSIG_PATH%;%PATH% - -set PYTHONPATH=%DCCSIG_PATH%;%PYTHONPATH% - -CALL %DCCSI_PYTHON_INSTALL%\python.exe "%DCCSIG_PATH%\SDK\PythonTools\DCC_Material_Converter\standalone.py" - - -ENDLOCAL - -:: Return to starting directory -POPD - -:END_OF_FILE - -exit /b 0 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/main.py deleted file mode 100755 index f9359d7d82..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/main.py +++ /dev/null @@ -1,1026 +0,0 @@ -# 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 -# -# - -""" -Usage -===== -Put usage instructions here. - -Output -====== -Put output information here. - -Notes: -In order to run this, you'll need to verify that the "mayapy_path" class attribute corresponds to the location on -your machine. Currently I've just included mapping instructions for Maya StingrayPBS materials, although most of -the needed elements are in place to carry out additional materials inside of Maya pretty quickly moving forward. -I've marked areas that still need refinement (or to be added altogether) with TODO comments - -TODO- Docstrings need work... wanted to get descriptions in but they need to be set for Sphinx -TODO- Add 3ds Max interoperability -Links: -https://blender.stackexchange.com/questions/100497/use-blenders-bpy-in-projects-outside-blender -https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2019/ENU/3DSMax-Batch/files/ -GUID-0968FF0A-5ADD-454D-B8F6-1983E76A4AF9-htm.html - -TODO- Look at dynaconf and wire in a solid means for configuration settings -TODO- This hasn't been "designed"- might be worth it to consider the visual design to ensure the most effective and - attractive UI -TODO- Allow revisions to Model - -Reading FBX file information (might come in handy later) --- Materials information can be extracted from ASCII fbx pretty easily, binary is possible but more difficult --- FBX files could be exported as ASCII files and I could use regex there to extract material information --- I couldn't get pyfbx_i42 to work, but purportedly it can extract information from binary files. You may just have -to use the specified python versions -""" -# built-ins -import collections -import logging -import subprocess -import json -import sys -import os -import re - -# should give access to Lumberyard Qt dlls and PySide2 -from PySide2 import QtWidgets, QtCore, QtGui -from PySide2.QtCore import Slot -from PySide2.QtWidgets import QApplication -import shiboken2 -from shiboken2 import wrapInstance - -# local imports -from model import MaterialsModel -from drag_and_drop import DragAndDrop -import dcc_material_mapping as dcc_map - -# global space -main_window_pointer = None -main_app_window = None - - -class MaterialsToLumberyard(QtWidgets.QWidget): - def __init__(self, output_material_type='PBR', cli_values=None, parent=None): - super(MaterialsToLumberyard, self).__init__(parent) - - self.app = QtWidgets.QApplication.instance() - self.setWindowFlags(QtCore.Qt.Window) - self.setGeometry(50, 50, 800, 520) - self.setObjectName('MaterialsToLumberyard') - self.setWindowTitle(' ') - self.setWindowFlags(self.windowFlags() & ~QtCore.Qt.WindowMinMaxButtonsHint) - self.isTopLevel() - - self.cli_enabled = cli_values - self.output_material_type = output_material_type - self.desktop_location = os.path.join(os.path.expanduser('~'), 'Desktop') - self.directory_path = os.path.dirname(os.path.abspath(__file__)) - self.mayapy_path = os.path.abspath("C:/Program Files/Autodesk/Maya2020/bin/mayapy.exe") - self.blender_path = self.get_blender_path() - self.bold_font_large = QtGui.QFont('Helvetica', 7, QtGui.QFont.Bold) - self.medium_font = QtGui.QFont('Helvetica', 7, QtGui.QFont.Normal) - self.blessed_file_extensions = 'ma mb fbx max blend'.split(' ') - - self.dcc_materials_dictionary = {} - self.lumberyard_materials_dictionary = {} - self.lumberyard_material_nodes = [] - self.target_file_list = [] - self.current_scene = None - self.model = None - self.total_materials = 0 - - self.main_container = QtWidgets.QVBoxLayout(self) - self.main_container.setContentsMargins(0, 0, 0, 0) - self.main_container.setAlignment(QtCore.Qt.AlignTop) - self.setLayout(self.main_container) - self.content_layout = QtWidgets.QVBoxLayout() - self.content_layout.setAlignment(QtCore.Qt.AlignTop) - self.content_layout.setContentsMargins(10, 3, 10, 5) - self.main_container.addLayout(self.content_layout) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> Header Bar - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - self.header_bar_layout = QtWidgets.QHBoxLayout() - self.lumberyard_logo_layout = QtWidgets.QHBoxLayout() - self.lumberyard_logo_layout.setAlignment(QtCore.Qt.AlignLeft) - logo_path = os.path.join(self.directory_path, 'resources', 'lumberyard_logo.png') - logo_pixmap = QtGui.QPixmap(logo_path) - self.lumberyard_logo = QtWidgets.QLabel() - self.lumberyard_logo.setPixmap(logo_pixmap) - self.lumberyard_logo_layout.addWidget(self.lumberyard_logo) - self.header_bar_layout.addLayout(self.lumberyard_logo_layout) - - self.switch_combobox_layout = QtWidgets.QHBoxLayout() - self.switch_combobox_layout.setAlignment(QtCore.Qt.AlignRight) - self.switch_layout_combobox = QtWidgets.QComboBox() - self.set_combobox_items_accessibility() - self.switch_layout_combobox.setFixedSize(250, 30) - self.combobox_items = ['Add Source Files', 'Source File List', 'DCC Material Values', 'Export Materials'] - self.switch_layout_combobox.setStyleSheet('QComboBox {padding-left:6px;}') - self.switch_layout_combobox.addItems(self.combobox_items) - self.switch_combobox_layout.addWidget(self.switch_layout_combobox) - self.header_bar_layout.addLayout(self.switch_combobox_layout) - - self.content_layout.addSpacing(5) - self.content_layout.addLayout(self.header_bar_layout) - - # ++++++++++++++++++++++++++++++++++++++++++++++++# - # File Source Table / Attributes (Stacked Layout) # - # ++++++++++++++++++++++++++++++++++++++++++++++++# - - self.content_stacked_layout = QtWidgets.QStackedLayout() - self.content_layout.addLayout(self.content_stacked_layout) - self.switch_layout_combobox.currentIndexChanged.connect(self.layout_combobox_changed) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> Add Source Files - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - frame_color_value = '75,75,75' - highlight_color_value = '20,106,30' - self.drag_and_drop_widget = DragAndDrop(frame_color_value, highlight_color_value) - self.drag_and_drop_widget.drop_update.connect(self.drag_and_drop_file_update) - self.drag_and_drop_widget.drop_over.connect(self.drag_and_drop_over) - self.drag_and_drop_layout = QtWidgets.QVBoxLayout() - self.drag_and_drop_layout.setContentsMargins(0, 0, 0, 0) - self.drag_and_drop_layout.setAlignment(QtCore.Qt.AlignCenter) - self.drag_and_drop_widget.setLayout(self.drag_and_drop_layout) - - start_message = 'Drag source files here, or use file browser button below to get started.' - self.drag_and_drop_label = QtWidgets.QLabel(start_message) - self.drag_and_drop_label.setStyleSheet('color: white;') - self.drag_and_drop_layout.addWidget(self.drag_and_drop_label) - self.drag_and_drop_layout.addSpacing(10) - - self.select_files_button_layout = QtWidgets.QHBoxLayout() - self.select_files_button_layout.setAlignment(QtCore.Qt.AlignCenter) - self.select_files_button = QtWidgets.QPushButton('Select Files') - self.select_files_button_layout.addWidget(self.select_files_button) - self.select_files_button.clicked.connect(self.select_files_button_clicked) - self.select_files_button.setFixedSize(80, 35) - self.drag_and_drop_layout.addLayout(self.select_files_button_layout) - self.content_stacked_layout.addWidget(self.drag_and_drop_widget) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> Files Table - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - self.target_files_table = QtWidgets.QTableWidget() - self.target_files_table.setFocusPolicy(QtCore.Qt.NoFocus) - self.target_files_table.setColumnCount(2) - self.target_files_table.setAlternatingRowColors(True) - self.target_files_table.setHorizontalHeaderLabels(['File List', '']) - self.target_files_table.horizontalHeader().setStyleSheet('QHeaderView::section ' - '{background-color: rgb(220, 220, 220); ' - 'padding-top:7px; padding-left:5px;}') - self.target_files_table.verticalHeader().hide() - files_header = self.target_files_table.horizontalHeader() - files_header.setFixedHeight(30) - files_header.setDefaultAlignment(QtCore.Qt.AlignLeft) - files_header.setContentsMargins(10, 10, 0, 0) - files_header.setDefaultSectionSize(60) - files_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) - files_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed) - self.target_files_table.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) - self.content_stacked_layout.addWidget(self.target_files_table) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> Scene Information Table - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - self.material_tree_view = QtWidgets.QTreeView() - self.headers = ['Key', 'Value'] - self.material_tree_view.setStyleSheet('QTreeView::item {height:25px;} QHeaderView::section ' - '{background-color: rgb(220, 220, 220); height:30px; padding-left:10px}') - self.material_tree_view.setFocusPolicy(QtCore.Qt.NoFocus) - self.material_tree_view.setAlternatingRowColors(True) - self.material_tree_view.setUniformRowHeights(True) - self.content_stacked_layout.addWidget(self.material_tree_view) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> LY Material Definitions - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - self.lumberyard_material_definitions_widget = QtWidgets.QWidget() - self.lumberyard_material_definitions_layout = QtWidgets.QHBoxLayout(self.lumberyard_material_definitions_widget) - self.lumberyard_material_definitions_layout.setSpacing(0) - self.lumberyard_material_definitions_layout.setContentsMargins(0, 0, 0, 0) - self.lumberyard_material_definitions_frame = QtWidgets.QFrame(self.lumberyard_material_definitions_widget) - self.lumberyard_material_definitions_frame.setGeometry(0, 0, 5000, 5000) - self.lumberyard_material_definitions_frame.setStyleSheet('background-color:rgb(75,75,75);') - self.lumberyard_material_definitions_scroller = QtWidgets.QScrollArea() - self.scroller_widget = QtWidgets.QWidget() - self.scroller_layout = QtWidgets.QVBoxLayout() - self.scroller_widget.setLayout(self.scroller_layout) - self.lumberyard_material_definitions_scroller.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - self.lumberyard_material_definitions_scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.lumberyard_material_definitions_scroller.setWidgetResizable(True) - self.lumberyard_material_definitions_scroller.setWidget(self.scroller_widget) - self.lumberyard_material_definitions_layout.addWidget(self.lumberyard_material_definitions_scroller) - self.content_stacked_layout.addWidget(self.lumberyard_material_definitions_widget) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> File processing buttons - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - self.process_files_layout = QtWidgets.QHBoxLayout() - self.content_layout.addLayout(self.process_files_layout) - self.process_files_button = QtWidgets.QPushButton('Process Added Files') - self.process_files_button.setFixedHeight(50) - self.process_files_button.clicked.connect(self.process_listed_files_clicked) - self.process_files_layout.addWidget(self.process_files_button) - - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - # ---->> Status bar / Loader - # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - # TODO- Move all processing of files to another thread and display progress with loader - - self.status_bar = QtWidgets.QStatusBar() - self.status_bar.setStyleSheet('background-color: rgb(220, 220, 220);') - self.status_bar.setContentsMargins(0, 0, 0, 0) - self.status_bar.setSizeGripEnabled(False) - self.message_readout_label = QtWidgets.QLabel('Ready.') - self.message_readout_label.setStyleSheet('padding-left: 10px') - self.status_bar.addWidget(self.message_readout_label) - - self.progress_bar = QtWidgets.QProgressBar() - self.progress_bar_widget = QtWidgets.QWidget() - self.progress_bar_widget_layout = QtWidgets.QHBoxLayout() - self.progress_bar_widget_layout.setContentsMargins(0, 0, 0, 0) - self.progress_bar_widget_layout.setAlignment(QtCore.Qt.AlignRight) - self.progress_bar_widget.setLayout(self.progress_bar_widget_layout) - self.status_bar.addPermanentWidget(self.progress_bar_widget) - self.progress_bar_widget_layout.addWidget(self.progress_bar) - self.progress_bar.setFixedSize(180, 20) - self.main_container.addWidget(self.status_bar) - self.initialize() - - ############################ - # UI Display Layers ######## - ############################ - - def initialize(self): - if self.cli_enabled: - print('CLI ACCESS:::::::::::\nValues passed: {}'.format(self.cli_enabled)) - self.target_file_list = self.cli_enabled - self.process_file_list() - self.export_selected_materials() - - def populate_source_files_table(self): - """ - Adds selected files from the 'Source Files' section of the UI. This creates each item listing in the table - as well as adds a 'Remove' button that will clear corresponding item from the table. Processed files will - get color coded, based on whether or not the materials in the file could be successfully processed. Subsequent - searches will not clear items from the table currently, as each item acts as a register of materials that have - and have not yet been processed. - :return: - """ - self.target_files_table.setRowCount(0) - for index, entry in enumerate(self.target_file_list): - entry = entry[1] if type(entry) == list else entry - self.target_files_table.insertRow(index) - item = QtWidgets.QTableWidgetItem(' {}'.format(entry)) - self.target_files_table.setRowHeight(index, 45) - remove_button = QtWidgets.QPushButton('Remove') - remove_button.setFixedWidth(60) - remove_button.clicked.connect(self.remove_source_file_clicked) - self.target_files_table.setItem(index, 0, item) - self.target_files_table.setCellWidget(index, 1, remove_button) - - def populate_dcc_material_values_tree(self): - """ - Sets the materials model class to the file attribute tree. - :return: - """ - # TODO- Create mechanism for collapsing previously gathered materials, and or pushing them further down the list - self.material_tree_view.setModel(self.model) - self.material_tree_view.expandAll() - self.material_tree_view.resizeColumnToContents(0) - - def populate_export_materials_list(self): - """ - Once all materials have been analyzed inside of DCC applications, the 'Export Materials' view lists all - materials presented as their Lumberyard counterparts. Each listing displays a representation of the material - file based on its corresponding DCC material values and file connections. - :return: - """ - self.reset_export_materials_description() - for count, value in enumerate(self.lumberyard_materials_dictionary): - material_definition_node = MaterialNode([value, self.lumberyard_materials_dictionary[value]], count) - self.lumberyard_material_nodes.append(material_definition_node) - self.scroller_layout.addWidget(material_definition_node) - self.scroller_layout.addLayout(self.create_separator_line()) - - ############################ - # TBD ######## - ############################ - - def process_file_list(self): - """ - The entry point for reading DCC files and extracting values. Files are filtered and separated - by DCC app (based on file extensions) before processing is done. - - Supported DCC applications: - Maya (.ma, .mb, .fbx), 3dsMax(.max), Blender(.blend) - :return: - """ - files_dict = {'maya': [], 'max': [], 'blender': [], 'na': []} - for file_location in self.target_file_list: - file_name = os.path.basename(str(file_location)) - file_extension = os.path.splitext(file_name)[1] - target_application = self.get_target_application(file_extension) - if target_application in files_dict.keys(): - files_dict[target_application].append(file_location) - - for key, values in files_dict.items(): - try: - if key == 'maya' and len(values): - self.get_maya_material_values(values) - elif key == 'max' and len(values): - self.get_max_material_values(values) - elif key == 'blender' and len(values): - self.get_blender_material_values(values) - else: - pass - except Exception as e: - # TODO- Allow corrective actions or some display of errors if this fails? - logging.warning('Could not process files. Error: {}'.format(e)) - - if self.dcc_materials_dictionary: - self.set_transfer_status(self.dcc_materials_dictionary) - # Create Model with extracted values from file list - self.set_material_model() - # Setup Lumberyard Material File Values - self.set_export_materials_description() - # Update UI Layout - self.populate_export_materials_list() - self.switch_layout_combobox.setCurrentIndex(3) - self.set_ui_buttons() - self.message_readout_label.setText('Ready.') - - def reset_export_materials_description(self): - pass - - def reset_all_values(self): - pass - - def create_separator_line(self): - """ Convenience function for adding separation line to the UI. """ - layout = QtWidgets.QHBoxLayout() - line = QtWidgets.QLabel() - line.setFrameStyle(QtWidgets.QFrame.HLine | QtWidgets.QFrame.Sunken) - line.setLineWidth(1) - line.setFixedHeight(10) - layout.addWidget(line) - layout.setContentsMargins(8, 0, 8, 0) - return layout - - def export_selected_materials(self): - """ - This will eventually be revised to save material definitions in the proper place in the user's project folder, - but for now material definitions will be saved to the desktop. - :return: - """ - for node in self.lumberyard_material_nodes: - if node.material_name_checkbox.isChecked(): - output_path = os.path.dirname(node.material_info['sourceFile']) - node.material_info.pop('sourceFile') - output = os.path.join(output_path, '{}.material'.format(node.material_name)) - with open(output, 'w', encoding='utf-8') as material_file: - json.dump(node.material_info, material_file, ensure_ascii=False, indent=4) - - ############################ - # Getters/Setters ########## - ############################ - - @staticmethod - def get_target_application(file_extension): - """ - Searches compatible file extensions and returns one of three Application names- Maya, 3dsMax, or Blender. - :param file_extension: Passed file extension used to determine DCC Application it originated from. - :return: Returns the application corresponding to the extension if found- otherwise returns a Boolean None - """ - app_extensions = {'maya': ['.ma', '.mb', '.fbx'], 'max': ['.max'], 'blender': ['.blend']} - target_dcc_application = [key for key, values in app_extensions.items() if file_extension in values] - if target_dcc_application: - return target_dcc_application[0] - return None - - @staticmethod - def get_lumberyard_material_template(shader_type): - """ - Loads material descriptions from the Lumberyard installation, providing a template to compare and convert DCC - shaders to Lumberyard material definitions. This is the first step in the comparison. The second step is to - compare these values with specific mapping instructions for DCC Application and DCC material type to arrive at - a converted material. - :param shader_type: The type of Lumberyard shader to pair material attributes to (i.e. PBR Shader) - :return: File dictionary of the available boilerplate Lumberyard shader settings. - """ - definitions = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'resources', - '{}.template.material'.format(shader_type)) - if os.path.exists(definitions): - with open(definitions) as f: - return json.load(f) - - @staticmethod - def get_lumberyard_material_properties(name, dcc_app, material_type, file_connections): - """ - This system will probably need rethinking if DCCs and compatible materials grow. I've tried to keep this - flexible so that it can be expanded with more apps and materials. - - :param name: Material name from within the DCC application - :param dcc_app: The application that the material was sourced from - :param material_type: DCC material type - :param file_connections: Texture files found attached to the materials - """ - - material_properties = {} - if dcc_app == 'Maya': - material_properties = dcc_map.get_maya_material_mapping(name, material_type, file_connections) - elif dcc_app == 'Blender': - material_properties = dcc_map.get_blender_material_mapping(name, material_type, file_connections) - elif dcc_app == '3dsMax': - material_properties = dcc_map.get_max_material_mapping(name, material_type, file_connections) - else: - pass - return material_properties - - @staticmethod - def get_filename_increment(name): - """ - Convenience function that assists in ensuring that if any materials are encountered with the same name, an - underscore and number is appended to it to prevent overwrites. - :param name: The name of the material. The function searches the string for increment numbers, and either adds - one to any encountered, or adds an "_1" if passed name is the first duplicate encountered. - :return: The adjusted name with a unique incremental value. - """ - last_number = re.compile(r'(?:[^\d]*(\d+)[^\d]*)+') - number_found = last_number.search(name) - if number_found: - next_number = str(int(number_found.group(1)) + 1) - start, end = number_found.span(1) - name = name[:max(end - len(next_number), start)] + next_number + name[end:] - return name - - def get_maya_material_values(self, target_files): - """ - Launches Maya Standalone and processes list of materials for each scene passed to the 'target_files' argument. - Also sets the environment paths needed for an instance of Maya's Python distribution. After files are processed - a single dictionary of scene materials is returned, and added to the "materials_dictionary" scene attribute. - :param target_files: List of files filtered from total list of files requested for processing that have a - Maya file extension - :return: - """ - - # TODO- Set load process to a separate thread and wire load progress bar up - - try: - script_path = str(os.path.join(self.directory_path, 'maya_materials.py')) - target_files.append(self.total_materials) - runtime_env = os.environ.copy() - runtime_env['MAYA_LOCATION'] = os.path.dirname(self.mayapy_path) - runtime_env['PYTHONPATH'] = os.path.dirname(self.mayapy_path) - command = f'{self.mayapy_path} "{script_path}"' - for file in target_files: - command += f' "{file}"' - p = subprocess.Popen(command, shell=False, env=runtime_env, stdout=subprocess.PIPE) - output = p.communicate()[0] - self.set_material_dictionary(json.loads(output)) - except Exception as e: - logging.warning('maya error: {}'.format(e)) - - def get_max_material_values(self, target_files): - """ - This has not been implemented yet. - - :param target_files: List of files filtered from total list of files requested for processing that have a - .max file extension - :return: - """ - logging.debug('Max Target file: {}'.format(target_files)) - - def get_blender_material_values(self, target_files): - """ - This has not been implemented yet. - - :param target_files: List of files filtered from total list of files requested for processing that have a - .blend file extension - :return: - """ - logging.debug('Blender Target file: {}'.format(target_files)) - script_path = str(os.path.join(self.directory_path, 'blender_materials.py')) - target_files.append(self.total_materials) - p = subprocess.Popen([self.blender_path, '--background', '--python', script_path, '--', target_files]) - output = p.communicate()[0] - self.set_material_dictionary(json.loads(output)) - - def get_blender_path(self): - """ - Finds latest Blender version installed on the user machine for command line file processing. - - :return: Most current version available (or none) - """ - blender_base_directory = os.path.join(os.path.join('C:\\', 'Program Files', 'Blender Foundation')) - blender_versions_found = [] - for (directory_path, directory_name, filenames) in os.walk(blender_base_directory): - for filename in filenames: - if filename == 'blender.exe': - blender_versions_found.append(os.path.join(directory_path, filename)) - - if blender_versions_found: - return max(blender_versions_found, key=os.path.getctime) - else: - return None - - def set_combobox_items_accessibility(self): - """ - Locks items from within the combobox until the sections they connect to have content - :return: - """ - # TODO- Add this functionality - pass - - def set_transfer_status(self, transfer_info): - """ - Colorizes listings in the 'Source Files' view of the UI after processing to green or red, indicating whether or - not scene analysis successfully returned compatible materials and their values. - :param transfer_info: Each file the scripts attempt to process return a receipt of the success or failure of - the analysis. - :return: - """ - - # TODO- Include some way to get error information if analysis fails, and potentially offer the means to - # repair values as they map to intended Lumberyard shader type - - for row in range(self.target_files_table.rowCount()): - for key, values in transfer_info.items(): - row_path = self.target_files_table.item(row, 0).text().strip() - scene_processed = {x for x in transfer_info if values['SceneName'].replace('\\', '/') == row_path} - if scene_processed: - self.target_files_table.item(row, 0).setBackground(QtGui.QColor(192, 255, 171)) - break - else: - self.target_files_table.item(row, 0).setBackground(QtGui.QColor(255, 177, 171)) - - def set_export_materials_description(self): - root = self.model.rootItem - for row in range(self.model.rowCount()): - source_file = self.model.get_attribute_value('SceneName', root.child(row)) - name = self.model.get_attribute_value('MaterialName', root.child(row)) - material_type = self.model.get_attribute_value('MaterialType', root.child(row)) - dcc_app = self.model.get_attribute_value('DccApplication', root.child(row)) - file_connections = {} - shader_attributes = {} - - for childIndex in range(root.child(row).childCount()): - child_item = root.child(row).child(childIndex) - child_value = child_item.itemData - if child_item.childCount(): - target_dict = file_connections if child_value[0] == 'FileConnections' else shader_attributes - for subChildIndex in range(child_item.childCount()): - sub_child_data = child_item.child(subChildIndex).itemData - target_dict[sub_child_data[0]] = sub_child_data[1] - self.set_material_description(source_file, name, dcc_app, material_type, file_connections) - - def set_material_dictionary(self, dcc_dictionary): - """ - Adds all material descriptions pulled from each DCC file analyzed to the "materials_dictionary" class attribute. - This function runs each time a subprocess is launched to gather DCC application material values. - :param dcc_dictionary: The dictionary of values for each material analyzed by each specific DCC file list - return analyzed values - :return: - """ - logging.debug('DCC Dictionary: {}'.format(json.dumps(dcc_dictionary, indent=4))) - self.total_materials += len(dcc_dictionary) - self.dcc_materials_dictionary.update(dcc_dictionary) - - def set_material_model(self, initialize=True): - """ - Once all materials have been gathered across a selected file set query, this organizes the values into a - QT Model Class - :param initialize: Default is set to boolean True. If a model has already been established in the current - session, the initialize parameter would be set to false, and the values added to the Model. All changes to - the model would then be redistributed to other informational views in the UI. - :return: - """ - if initialize: - self.model = MaterialsModel(self.headers, self.dcc_materials_dictionary) - else: - self.model.update() - self.dcc_materials_dictionary.clear() - self.populate_dcc_material_values_tree() - - def set_ui_buttons(self): - """ - Handles UI buttons for each of the three stacked layout views (Source Files, DCC Material Values, - Export Materials) - :return: - """ - display_index = self.content_stacked_layout.currentIndex() - self.switch_layout_combobox.setEnabled(True) - self.process_files_button.setText('Process Listed Files') - # Add Source Files Layout ------------------------------->> - if display_index == 0: - self.process_files_button.setEnabled(True) - - # Source File List -------------------------------------->> - elif display_index == 1: - self.process_files_button.setEnabled(True) - - # DCC Material Values Layout ---------------------------->> - elif display_index == 2: - self.process_files_button.setEnabled(False) - - # Export Materials Layout ------------------------------->> - else: - self.process_files_button.setText('Export Selected Materials') - if self.lumberyard_materials_dictionary: - self.process_files_button.setEnabled(True) - - def set_material_description(self, source_file, name, dcc_app, material_type, file_connections): - """ - Build dictionary for material description based on extracted values - - :param source_file: The file that the material was extracted from - :param name: Name of material - :param dcc_app: Source file type of material (Maya, Blender or 3ds Max) - :param material_type: Material type within app (i.e. Stingray PBS) - :param file_connections: Texture files found connected to the shader - :return: - """ - - default_settings = self.get_lumberyard_material_template('standardPBR') - material = collections.OrderedDict(sourceFile=source_file, description=name, - materialType=default_settings.get('materialType'), - parentMaterial=default_settings.get('parentMaterial'), - propertyLayoutVersion=default_settings.get('propertyLayoutVersion'), - properties=self.get_lumberyard_material_properties(name, dcc_app, - material_type, - file_connections)) - name += self.output_material_type - self.lumberyard_materials_dictionary[name if name not in self.lumberyard_materials_dictionary.keys() else - self.get_filename_increment(name)] = material - - ############################ - # Button Actions ########### - ############################ - - def remove_source_file_clicked(self): - """ - In the Source File view of the UI layout, this will remove the listed file in its respective row. If files - have not been processed yet, it prevents that file from being analyzed. If the files have already been - analyzed, this will remove the materials from stored values. - :return: - """ - file_index = self.target_files_table.indexAt(self.sender().pos()) - del self.target_file_list[file_index.row()] - self.populate_files_table() - - def process_listed_files_clicked(self): - """ - The button serves a dual purpose, depending on the current layout of the window. 'Process listed files' - initiates the DCC file analysis that extracts material information. In the "Export Materials" layout, this - button (for now) will export material files corresponding to each analyzed material. Exported material files - are routed to the directories of the respective files processed. - :return: - """ - - if self.sender().text() == 'Process Added Files': - self.message_readout_label.setText('Gathering Material Information...') - self.app.processEvents() - self.process_file_list() - else: - self.export_selected_materials() - - def select_files_button_clicked(self): - """ - This dialog allows user to select DCC files to be processed for the materials present for conversion. - :return: - """ - - # TODO- Eventually it might be worth it to allow files from multiple locations to be selected. Currently - # this only allows single/multiple files from a single directory to be selected, although drag and drop - # allows multiple locations - - dialog = QtWidgets.QFileDialog(self, 'Shift-Select Target Files', self.desktop_location) - dialog.setFileMode(QtWidgets.QFileDialog.ExistingFile) - dialog.setNameFilter('Compatible Files (*.ma *.mb *.fbx *.max *.blend)') - dialog.setOption(QtWidgets.QFileDialog.DontUseNativeDialog, True) - file_view = dialog.findChild(QtWidgets.QListView, 'listView') - - # Workaround for selecting multiple files with File Dialog - if file_view: - file_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - f_tree_view = dialog.findChild(QtWidgets.QTreeView) - if f_tree_view: - f_tree_view.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) - - if dialog.exec_() == QtWidgets.QDialog.Accepted: - self.target_file_list += dialog.selectedFiles() - if self.target_file_list: - self.populate_source_files_table() - self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list))) - self.process_files_button.setEnabled(True) - - def layout_combobox_changed(self): - """ - Handles main window layout combobox index change. - :return: - """ - self.content_stacked_layout.setCurrentIndex(self.switch_layout_combobox.currentIndex()) - self.set_ui_buttons() - - def reset_clicked(self): - """ - Brings the application and all variables back to their initial state. - :return: - """ - self.reset_all_values() - - ############################ - # Slots #################### - ############################ - - @Slot(list) - def drag_and_drop_file_update(self, file_list): - for file in file_list: - if os.path.basename(file).split('.')[-1] in self.blessed_file_extensions: - self.target_file_list.append(file) - self.drag_and_drop_widget.urls.clear() - self.populate_source_files_table() - self.message_readout_label.setText('Source files added: {}'.format(len(self.target_file_list))) - self.drag_and_drop_label.setStyleSheet('color: white;') - - @Slot(bool) - def drag_and_drop_over(self, is_over): - if is_over: - self.drag_and_drop_label.setStyleSheet('color: rgb(0, 255, 0);') - else: - self.drag_and_drop_label.setStyleSheet('color: white;') - - -class MaterialNode(QtWidgets.QWidget): - def __init__(self, material_info, current_position, parent=None): - super(MaterialNode, self).__init__(parent) - - self.material_name = material_info[0] - self.material_info = material_info[1] - self.current_position = current_position - self.property_settings = {} - - self.small_font = QtGui.QFont("Helvetica", 7, QtGui.QFont.Bold) - self.bold_font = QtGui.QFont("Helvetica", 8, QtGui.QFont.Bold) - self.main_layout = QtWidgets.QVBoxLayout() - self.main_layout.setContentsMargins(0, 0, 0, 0) - self.setLayout(self.main_layout) - - self.background_frame = QtWidgets.QFrame(self) - self.background_frame.setGeometry(0, 0, 5000, 5000) - self.background_frame.setStyleSheet('background-color:rgb(220, 220, 220);') - - # ######################## - # Title Bar - # ######################## - - self.title_bar_widget = QtWidgets.QWidget() - self.title_bar_layout = QtWidgets.QHBoxLayout(self.title_bar_widget) - self.title_bar_layout.setContentsMargins(10, 0, 10, 0) - self.title_bar_layout.setAlignment(QtCore.Qt.AlignTop) - self.title_bar_frame = QtWidgets.QFrame(self.title_bar_widget) - self.title_bar_frame.setGeometry(0, 0, 5000, 40) - self.title_bar_frame.setStyleSheet('background-color:rgb(193,154,255);') - self.main_layout.addWidget(self.title_bar_widget) - self.material_name_checkbox = QtWidgets.QCheckBox(self.material_name) - self.material_name_checkbox.setFixedHeight(35) - self.material_name_checkbox.setStyleSheet('spacing:10px; color:white') - self.material_name_checkbox.setFont(self.bold_font) - self.material_name_checkbox.setChecked(True) - self.title_bar_layout.addWidget(self.material_name_checkbox) - - self.material_file_layout = QtWidgets.QHBoxLayout() - self.material_file_layout.setAlignment(QtCore.Qt.AlignRight) - self.source_file = QtWidgets.QLabel(os.path.basename(self.material_info['sourceFile'])) - self.source_file.setStyleSheet('color:white;') - self.source_file.setFont(self.small_font) - self.material_file_layout.addWidget(self.source_file) - self.material_file_layout.addSpacing(10) - - self.edit_button = QtWidgets.QPushButton('Edit') - self.edit_button.clicked.connect(self.edit_button_clicked) - self.edit_button.setFixedWidth(55) - self.material_file_layout.addWidget(self.edit_button) - self.title_bar_layout.addLayout(self.material_file_layout) - - self.information_layout = QtWidgets.QHBoxLayout() - self.information_layout.setContentsMargins(10, 0, 10, 10) - self.main_layout.addLayout(self.information_layout) - - # ######################## - # Details layout - # ######################## - - self.details_layout = QtWidgets.QVBoxLayout() - self.details_layout.setAlignment(QtCore.Qt.AlignTop) - self.details_groupbox = QtWidgets.QGroupBox("Details") - self.details_groupbox.setFixedWidth(200) - self.details_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; " - "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); " - "subcontrol-position: top left;}") - self.details_layout.addSpacing(15) - self.material_type_label = QtWidgets.QLabel('Material Type') - self.material_type_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);') - self.material_type_label.setFixedHeight(25) - self.material_type_label.setFont(self.bold_font) - self.details_layout.addWidget(self.material_type_label) - - self.material_type_combobox = QtWidgets.QComboBox() - self.material_type_combobox.setFixedHeight(30) - self.material_type_combobox.setStyleSheet('QCombobox QAbstractItemView { padding-left: 15px; }') - material_type_items = [' Standard PBR'] - self.material_type_combobox.addItems(material_type_items) - self.details_layout.addWidget(self.material_type_combobox) - self.details_layout.addSpacing(10) - - self.description_label = QtWidgets.QLabel('Description') - self.description_label.setStyleSheet('padding-left: 6px; color: white; background-color:rgb(175, 175, 175);') - self.description_label.setFixedHeight(25) - self.description_label.setFont(self.bold_font) - self.details_layout.addWidget(self.description_label) - - self.description_box = QtWidgets.QTextEdit('This space is reserved for additional information.') - self.details_layout.addWidget(self.description_box) - self.information_layout.addWidget(self.details_groupbox) - self.details_groupbox.setLayout(self.details_layout) - - # ######################## - # Properties layout - # ######################## - - self.properties_layout = QtWidgets.QVBoxLayout() - self.properties_layout.setAlignment(QtCore.Qt.AlignTop) - self.properties_groupbox = QtWidgets.QGroupBox("Properties") - self.properties_groupbox.setFixedWidth(150) - self.properties_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; " - "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); " - "subcontrol-position: top left;}") - self.properties_list_widget = QtWidgets.QListWidget() - self.material_properties = ['ambientOcclusion', 'baseColor', 'emissive', 'metallic', 'roughness', 'specularF0', - 'normal', 'opacity'] - self.properties_list_widget.addItems(self.material_properties) - self.properties_list_widget.itemSelectionChanged.connect(self.property_selection_changed) - self.properties_layout.addSpacing(15) - self.properties_layout.addWidget(self.properties_list_widget) - self.information_layout.addWidget(self.properties_groupbox) - self.properties_groupbox.setLayout(self.properties_layout) - - # ######################## - # Attributes layout - # ######################## - - self.attributes_layout = QtWidgets.QVBoxLayout() - self.attributes_layout.setAlignment(QtCore.Qt.AlignTop) - self.attributes_groupbox = QtWidgets.QGroupBox("Attributes") - self.attributes_groupbox.setStyleSheet("QGroupBox {font:bold; border: 1px solid silver; " - "margin-top: 6px;} QGroupBox::title { color: rgb(150, 150, 150); " - "subcontrol-position: top left;}") - self.information_layout.addWidget(self.attributes_groupbox) - self.attributes_layout.addSpacing(15) - self.attributes_table = QtWidgets.QTableWidget() - self.attributes_table.setFocusPolicy(QtCore.Qt.NoFocus) - self.attributes_table.setColumnCount(2) - self.attributes_table.setAlternatingRowColors(True) - self.attributes_table.setHorizontalHeaderLabels(['Attribute', 'Value']) - self.attributes_table.verticalHeader().hide() - attributes_table_header = self.attributes_table.horizontalHeader() - attributes_table_header.setStyleSheet('QHeaderView::section {background-color: rgb(220, 220, 220);}') - attributes_table_header.setDefaultAlignment(QtCore.Qt.AlignLeft) - attributes_table_header.setContentsMargins(10, 10, 0, 0) - attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) - attributes_table_header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - attributes_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.Interactive) - self.attributes_layout.addWidget(self.attributes_table) - self.attributes_groupbox.setLayout(self.attributes_layout) - self.initialize_display_values() - - def initialize_display_values(self): - """ - Initializes all of the widget item information for material based on the DCC application info the class has - been passed. - :return: - """ - for material_property in self.material_properties: - if material_property in self.material_info.get('properties'): - self.property_settings[material_property] = self.material_info['properties'].get(material_property) - current_row = self.material_properties.index(material_property) - current_item = self.properties_list_widget.takeItem(current_row) - self.properties_list_widget.insertItem(0, current_item) - else: - self.property_settings[material_property] = 'inactive' - current_row = self.material_properties.index(material_property) - item = self.properties_list_widget.item(current_row) - item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEnabled) - item.setFlags(item.flags() & ~QtCore.Qt.ItemIsSelectable) - - self.properties_list_widget.setCurrentRow(0) - self.set_attributes_table(self.get_selected_property()) - - def set_attributes_table(self, selected_property): - """ - Displays the key, value pairs for the item selected in the Properties list widget - :param selected_property: The item in the Properties list widget that is currently selected. Only active - values are displayed. - :return: - """ - self.attributes_table.setRowCount(0) - row_count = 0 - for key, value in self.property_settings[selected_property].items(): - self.attributes_table.insertRow(row_count) - key_item = QtWidgets.QTableWidgetItem(key) - self.attributes_table.setItem(row_count, 0, key_item) - value_item = QtWidgets.QTableWidgetItem(value) - self.attributes_table.setItem(row_count, 1, value_item) - row_count += 1 - - def get_selected_property(self): - """ - Convenience function to get current value selected in the Properties list widget. - :return: - """ - return self.properties_list_widget.currentItem().text() - - def update_model(self): - """ - Not sure if this will go away, but if desired, I could make attribute values able to be revised after - materials have been scraped from the DCC materials - :return: - """ - pass - - def edit_button_clicked(self): - """ - This is in place in the event that we want to allow material revisions for properties to be made after - DCC processing step has already been executed. The idea would basically be to surface an editable - table where values can be added, removed or changed within the final material definition. - :return: - """ - logging.debug('Edit button clicked') - - def property_selection_changed(self): - """ - Fired when index of list view selected property selection has changed. - :return: - """ - self.set_attributes_table(self.get_selected_property()) - - -def is_valid_file(file_name): - """ - The acts as a clearinghouse for DCC file types supported by the script - :param file_name: Reads the extension of the filename for filtering - :return: - """ - target_extensions = 'ma mb fbx blend max'.split(' ') - if file_name.split('.')[-1] in target_extensions: - return True - return False - - -def launch_material_converter(window_type='standalone', material_type='PBR', target_files=None): - """ - The setup for this will be revised once this is fully integrated into the DCCsi system. Currently only the - standalone (default) and command line entry points work as intended. - :param window_type: The method of access for material conversion (standalone, command_line, maya_native, max_native) - :param material_type: Type of output material desired for import into Lumberyard. Currently only PBR is supported - :param target_files: DCC app files to process for converted Lumberyard materials - :return: - """ - if window_type == 'command_line': - MaterialsToLumberyard(material_type, target_files) - elif window_type == 'maya_native': - from maya import OpenMayaUI as omui - main_window_pointer = omui.MQtUtil.mainWindow() - main_app_window = wrapInstance(long(main_window_pointer), QtWidgets.QWidget) - MaterialsToLumberyard(material_type, None, main_app_window) - elif window_type == 'max_native': - from pymxs import runtime as rt - main_window_pointer = QtWidgets.QWidget.find(rt.windows.getMAXHWND()) - main_app_window = shiboken2.wrapInstance(shiboken2.getCppPointer(main_window_pointer)[0], QtWidgets.QMainWindow) - MaterialsToLumberyard(material_type, None, main_app_window) - else: - app = QApplication(sys.argv) - app_ui = MaterialsToLumberyard() - app_ui.show() - sys.exit(app.exec_()) - - -if __name__ == '__main__': - launch_material_converter() - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/maya_materials.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/maya_materials.py deleted file mode 100755 index 4e66f83661..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/maya_materials.py +++ /dev/null @@ -1,169 +0,0 @@ -# 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 -# - -from PySide2 import QtCore -import maya.standalone -maya.standalone.initialize(name='python') -import maya.cmds as mc -import collections -import logging -import json -import sys -import os - - -for handler in logging.root.handlers[:]: - logging.root.removeHandler(handler) - -logging.basicConfig(level=logging.INFO, - format='%(name)s - %(levelname)s - %(message)s', - datefmt='%m-%d %H:%M', - filename='output.log', - filemode='w') - - -class MayaMaterials(QtCore.QObject): - def __init__(self, files_list, materials_count, parent=None): - super(MayaMaterials, self).__init__(parent) - - self.files_list = files_list - self.current_scene = None - self.materials_dictionary = {} - self.materials_count = int(materials_count) - self.get_material_information() - - def get_material_information(self): - """ - Main entry point for the material information extraction. Because this class is run - in Standalone mode as a subprocess, the list is passed as a string- some parsing/measures - need to be taken in order to separate values that originated as a list before passed. - - :return: A dictionary of all of the materials gathered. Sent back to main UI through stdout - """ - for target_file in file_list: - self.current_scene = os.path.abspath(target_file.replace('\'', '')) - mc.file(self.current_scene, open=True, force=True) - self.set_material_descriptions() - json.dump(self.materials_dictionary, sys.stdout) - - @staticmethod - def get_materials(target_mesh): - """ - Gathers a list of all materials attached to each mesh's shader - - :param target_mesh: The target mesh to pull attached material information from. - :return: List of unique material values attached to the mesh passed as an argument. - """ - shading_group = mc.listConnections(target_mesh, type='shadingEngine') - materials = mc.ls(mc.listConnections(shading_group), materials=1) - return list(set(materials)) - - @staticmethod - def get_shader(material_name): - """ - Convenience function for obtaining the shader that the specified material (as an argument) - is attached to. - - :param material_name: Takes the material name as an argument to get associated shader object - :return: - """ - connections = mc.listConnections(material_name, type='shadingEngine')[0] - shader_name = '{}.surfaceShader'.format(connections) - shader = mc.listConnections(shader_name)[0] - return shader - - def get_shader_information(self, shader, material_mesh): - """ - Helper function for extracting shader/material attributes used to form the DCC specific dictionary - of found material values for conversion. - - :param shader: The target shader object to analyze - :param material_mesh: The material mesh needs to be passed to search for textures attached to it. - :return: Complete set (in the form of two dictionaries) of file connections and material attribute values - """ - shader_file_connections = {} - materials = self.get_materials(material_mesh) - for material in materials: - material_files = [x for x in mc.listConnections(material, plugs=1, source=1) if x.startswith('file')] - for file_name in material_files: - file_texture = mc.getAttr('{}.fileTextureName'.format(file_name.split('.')[0])) - if os.path.basename(file_texture).split('.')[-1] != 'dds': - key_name = mc.listConnections(file_name, plugs=1, source=1)[0] - shader_file_connections[key_name] = file_texture - - shader_attributes = {} - for shader_attribute in mc.listAttr(shader, s=True, iu=True): - try: - shader_attributes[str(shader_attribute)] = str(mc.getAttr('{}.{}'.format(shader, shader_attribute))) - except Exception as e: - logging.error('MayaAttributeError: {}'.format(e)) - - return shader_file_connections, shader_attributes - - def set_material_dictionary(self, material_name, material_type, material_mesh): - """ - When a unique material has been found, this creates a dictionary entry with all relevant material values. This - includes material attributes as well as attached file textures. Later in the process this information is - leveraged when creating the Lumberyard material definition. - - :param material_name: The name attached to the material - :param material_type: Specific type of material (Arnold, Stingray, etc.) - :param material_mesh: Mesh that the material is applied to - :return: - """ - self.materials_count += 1 - shader = self.get_shader(material_name) - shader_file_connections, shader_attributes = self.get_shader_information(shader, material_mesh) - material_dictionary = collections.OrderedDict(MaterialName=material_name, MaterialType=material_type, - DccApplication='Maya', AppliedMesh=material_mesh, - FileConnections=shader_file_connections, - SceneName=str(self.current_scene), - MaterialAttributes=shader_attributes) - material_name = 'Material_{}'.format(self.materials_count) - self.materials_dictionary[material_name] = material_dictionary - logging.info('\n\n:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n' - 'MATERIAL DEFINITION: {} \n' - ':::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::\n{}'.format( - self.materials_dictionary[material_name]['MaterialType'], - json.dumps(self.materials_dictionary[material_name], indent=4))) - - def set_material_descriptions(self): - """ - This function serves as the clearinghouse for all analyzed materials passing through the system. - It will determine whether or not the found material has already been processed, or if it needs to - be added to the final material dictionary. In the event that an encountered material has already - been processed, this function creates a register of all meshes it is applied to in the 'AppliedMesh' - attribute. - :return: - """ - scene_geo = mc.ls(v=True, geometry=True) - for target_mesh in scene_geo: - material_list = self.get_materials(target_mesh) - for material_name in material_list: - material_type = mc.nodeType(material_name) - - if material_type != 'lambert': - material_listed = [x for x in self.materials_dictionary - if self.materials_dictionary[x]['MaterialName'] == material_name] - - if not material_listed: - self.set_material_dictionary(str(material_name), str(material_type), str(target_mesh)) - else: - mesh_list = self.materials_dictionary[material_name].get('AppliedMesh') - if not isinstance(mesh_list, list): - self.materials_dictionary[str(material_name)]['AppliedMesh'] = [mesh_list, target_mesh] - else: - mesh_list.append(target_mesh) - - -# ++++++++++++++++++++++++++++++++++++++++++++++++# -# Maya Specific Shader Mapping # -# ++++++++++++++++++++++++++++++++++++++++++++++++# - -file_list = sys.argv[1:-1] -count = sys.argv[-1] -instance = MayaMaterials(file_list, count) - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/model.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/model.py deleted file mode 100755 index 3c23635438..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/model.py +++ /dev/null @@ -1,154 +0,0 @@ -# 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 -# -# - -from PySide2.QtCore import QAbstractItemModel, QModelIndex, Qt - - -class MaterialsModel(QAbstractItemModel): - def __init__(self, headers, data, parent=None): - super(MaterialsModel, self).__init__(parent) - - self.rootItem = TreeNode(headers) - self.parents = [self.rootItem] - self.indentations = [0] - self.create_data(data) - - def create_data(self, data, indent=-1): - """ - Recursive loop that structures Model data into tree form. - :param data: Row information. - :param indent: Column information. This helps to facilitate the creation of nested rows. - :return: - """ - if type(data) == dict: - indent += 1 - position = 4 * indent - for key, value in data.items(): - if position > self.indentations[-1]: - if self.parents[-1].childCount() > 0: - self.parents.append(self.parents[-1].child(self.parents[-1].childCount() - 1)) - self.indentations.append(position) - else: - while position < self.indentations[-1] and len(self.parents) > 0: - self.parents.pop() - self.indentations.pop() - parent = self.parents[-1] - parent.insertChildren(parent.childCount(), 1, parent.columnCount()) - parent.child(parent.childCount() - 1).setData(0, key) - value_string = str(value) if type(value) != dict else str('') - parent.child(parent.childCount() - 1).setData(1, value_string) - try: - self.create_data(value, indent) - except RuntimeError: - pass - - @staticmethod - def get_attribute_value(search_string, search_column): - """ Convenience function for quickly accessing row information based on attribute keys. """ - for childIndex in range(search_column.childCount()): - child_item = search_column.child(childIndex) - child_value = child_item.itemData - if child_value[0] == search_string: - return child_value[1] - return None - - def index(self, row, column, index=QModelIndex()): - """ Returns the index of the item in the model specified by the given row, column and parent index """ - if not self.hasIndex(row, column, index): - return QModelIndex() - if not index.isValid(): - item = self.rootItem - else: - item = index.internalPointer() - - child = item.child(row) - if child: - return self.createIndex(row, column, child) - return QModelIndex() - - def parent(self, index): - """ - Returns the parent of the model item with the given index If the item has no parent, - an invalid QModelIndex is returned - """ - if not index.isValid(): - return QModelIndex() - item = index.internalPointer() - if not item: - return QModelIndex() - - parent = item.parentItem - if parent == self.rootItem: - return QModelIndex() - else: - return self.createIndex(parent.childNumber(), 0, parent) - - def rowCount(self, index=QModelIndex()): - """ - Returns the number of rows under the given parent. When the parent is valid it means that - rowCount is returning the number of children of parent - """ - if index.isValid(): - parent = index.internalPointer() - else: - parent = self.rootItem - return parent.childCount() - - def columnCount(self, index=QModelIndex()): - """ Returns the number of columns for the children of the given parent """ - return self.rootItem.columnCount() - - def data(self, index, role=Qt.DisplayRole): - """ Returns the data stored under the given role for the item referred to by the index """ - if index.isValid() and role == Qt.DisplayRole: - return index.internalPointer().data(index.column()) - elif not index.isValid(): - return self.rootItem.data(index.column()) - - def headerData(self, section, orientation, role=Qt.DisplayRole): - """ Returns the data for the given role and section in the header with the specified orientation """ - if orientation == Qt.Horizontal and role == Qt.DisplayRole: - return self.rootItem.data(section) - - -class TreeNode(object): - def __init__(self, data, parent=None): - self.parentItem = parent - self.itemData = data - self.children = [] - - def child(self, row): - return self.children[row] - - def childCount(self): - return len(self.children) - - def childNumber(self): - if self.parentItem is not None: - return self.parentItem.children.index(self) - - def columnCount(self): - return len(self.itemData) - - def data(self, column): - return self.itemData[column] - - def insertChildren(self, position, count, columns): - if position < 0 or position > len(self.children): - return False - for row in range(count): - data = [v for v in range(columns)] - item = TreeNode(data, self) - self.children.insert(position, item) - - def parent(self): - return self.parentItem - - def setData(self, column, value): - if column < 0 or column >= len(self.itemData): - return False - self.itemData[column] = value diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/standalone.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/standalone.py deleted file mode 100644 index e7a8445953..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/standalone.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# - -"""Boostraps and Starts Standalone DCC Material Converter utility""" - -# built in's -import os -import site - -# ------------------------------------------------------------------------- -# \dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\PythonTools\DCC_Material_Converter\standalone.py -_MODULE_PATH = os.path.abspath(__file__) - -_DCCSIG_REL_PATH = "../../../.." -_DCCSIG_PATH = os.path.join(_MODULE_PATH, _DCCSIG_REL_PATH) -_DCCSIG_PATH = os.path.normpath(_DCCSIG_PATH) - -_DCCSIG_PATH = os.getenv('DCCSIG_PATH', - os.path.abspath(_DCCSIG_PATH)) - -# we don't have access yet to the DCCsi Lib\site-packages -site.addsitedir(_DCCSIG_PATH) # PYTHONPATH - -# azpy bootstrapping and extensions -import azpy.config_utils -_config = azpy.config_utils.get_dccsi_config() -settings = _config.get_config_settings(setup_ly_pyside=True) - -from main import launch_material_converter - -launch_material_converter() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/Launcher/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/Launcher/main.py deleted file mode 100644 index 5812755035..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/Launcher/main.py +++ /dev/null @@ -1,74 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# -# -# -- This line is 75 characters ------------------------------------------- -# built-ins -import os -import sys -import logging as _logging - -# azpy extensions -import azpy.config_utils -_config = azpy.config_utils.get_dccsi_config() -settings = _config.get_config_settings(setup_ly_pyside=True) - -# 3rd Party (we may or do provide) -from pathlib import Path -from pathlib import PurePath - -# Lumberyard extensions -from azpy.env_bool import env_bool -from azpy.constants import ENVAR_DCCSI_GDEBUG -from azpy.constants import ENVAR_DCCSI_DEV_MODE - -# ------------------------------------------------------------------------- -# set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_GDEBUG) - -for handler in _logging.root.handlers[:]: - _logging.root.removeHandler(handler) - -_MODULENAME = 'DCCsi.SDK.pythontools.launcher.main' - -_log_level = _logging.INFO -if _G_DEBUG: - _log_level = _logging.DEBUG - -_LOGGER = azpy.initialize_logger(name=_MODULENAME, - log_to_file=True, - default_log_level=_log_level) - -_LOGGER.debug('Starting up: {0}.'.format({_MODULENAME})) -# ------------------------------------------------------------------------- - - -# ------------------------------------------------------------------------- -def main(): - from PySide2.QtWidgets import QApplication, QPushButton - - app = QApplication(sys.argv) -# ------------------------------------------------------------------------- - - -# -------------------------------------------------------------------------- -if __name__ == '__main__': - """Run this file as main""" - -app = QApplication([]) # Start an application. -window = QWidget() # Create a window. -layout = QVBoxLayout() # Create a layout. -button = QPushButton("I'm just a Button man") # Define a button -layout.addWidget(QLabel('Hello World!')) # Add a label -layout.addWidget(button) # Add the button man -window.setLayout(layout) # Pass the layout to the window -window.show() # Show window -app.exec_() # Execute the App diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/atom_material.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/atom_material.py index 4c35861b9c..b7a6bfa3a1 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/atom_material.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/atom_material.py @@ -29,7 +29,7 @@ from pathlib import Path # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/bootstrap.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/bootstrap.py index 50a4d3fdda..f3e91b59ce 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/bootstrap.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/bootstrap.py @@ -34,20 +34,20 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # these are for module debugging, set to false on submit -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = 'DCCsi.SDK.substance.builder.bootstrap' _log_level = int(20) -if _G_DEBUG: +if _DCCSI_GDEBUG: _log_level = int(10) _LOGGER = azpy.initialize_logger(_PACKAGENAME, log_to_file=True, default_log_level=_log_level) _LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME})) _LOGGER.debug('_DCCSIG_PATH: {}'.format(_DCCSIG_PATH)) -_LOGGER.debug('_G_DEBUG: {}'.format(_G_DEBUG)) +_LOGGER.debug('_G_DEBUG: {}'.format(_DCCSI_GDEBUG)) _LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE)) if _DCCSI_DEV_MODE: @@ -69,7 +69,7 @@ from dynaconf import settings try: from PySide2.QtWidgets import QApplication except: - _dccsi_config.init_ly_pyside(settings.LY_DEV) # init for standalone + _dccsi_config.init_o3de_pyside(settings.O3DE_DEV) # init for standalone # running in the editor if the QtForPython Gem is enabled # you should already have access and shouldn't need to set up @@ -92,20 +92,20 @@ os.environ["PYSBS_DIR_PATH"] = str(_PYSBS_DIR_PATH) # standard paths we may use downstream # To Do: move these into a dynaconf config extension specific to this tool? -from azpy.constants import ENVAR_LY_DEV -_LY_DEV = Path(os.getenv(ENVAR_LY_DEV, - settings.LY_DEV)).resolve() +from azpy.constants import ENVAR_O3DE_DEV +_O3DE_DEV = Path(os.getenv(ENVAR_O3DE_DEV, + settings.O3DE_DEV)).resolve() -from azpy.constants import ENVAR_LY_PROJECT_PATH -_LY_PROJECT_PATH = Path(os.getenv(ENVAR_LY_PROJECT_PATH, - settings.LY_PROJECT_PATH)).resolve() +from azpy.constants import ENVAR_O3DE_PROJECT_PATH +_O3DE_PROJECT_PATH = Path(os.getenv(ENVAR_O3DE_PROJECT_PATH, + settings.O3DE_PROJECT_PATH)).resolve() from azpy.constants import ENVAR_DCCSI_SDK_PATH _DCCSI_SDK_PATH = Path(os.getenv(ENVAR_DCCSI_SDK_PATH, settings.DCCSIG_SDK_PATH)).resolve() # build some reuseable path parts for the substance builder -_PROJECT_ASSETS_PATH = Path(_LY_PROJECT_PATH, 'Assets').resolve() +_PROJECT_ASSETS_PATH = Path(_O3DE_PROJECT_PATH, 'Assets').resolve() _PROJECT_MATERIALS_PATH = Path(_PROJECT_ASSETS_PATH, 'Materials').resolve() # ------------------------------------------------------------------------- @@ -116,15 +116,15 @@ _PROJECT_MATERIALS_PATH = Path(_PROJECT_ASSETS_PATH, 'Materials').resolve() if __name__ == "__main__": """Run this file as main""" - _LOGGER.info('_LY_DEV: {}'.format(_LY_DEV)) - _LOGGER.info('_LY_PROJECT_PATH: {}'.format(_LY_PROJECT_PATH)) + _LOGGER.info('_O3DE_DEV: {}'.format(_O3DE_DEV)) + _LOGGER.info('_O3DE_PROJECT_PATH: {}'.format(_O3DE_PROJECT_PATH)) _LOGGER.info('_DCCSI_SDK_PATH: {}'.format(_DCCSI_SDK_PATH)) _LOGGER.info('_PYSBS_DIR_PATH: {}'.format(_PYSBS_DIR_PATH)) _LOGGER.info('_PROJECT_ASSETS_PATH: {}'.format(_PROJECT_ASSETS_PATH)) _LOGGER.info('_PROJECT_MATERIALS_PATH: {}'.format(_PROJECT_MATERIALS_PATH)) - if _G_DEBUG: + if _DCCSI_GDEBUG: _dccsi_config.test_pyside2() # runs a small PySdie2 test # remove the logger diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sb_gui_main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sb_gui_main.py index cc501fe872..9408b94919 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sb_gui_main.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sb_gui_main.py @@ -35,7 +35,7 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_GDEBUG) for handler in _logging.root.handlers[:]: @@ -44,7 +44,7 @@ for handler in _logging.root.handlers[:]: _MODULENAME = 'DCCsi.SDK.substance.builder.sb_gui_main' _log_level = _logging.INFO -if _G_DEBUG: +if _DCCSI_GDEBUG: _log_level = _logging.DEBUG _LOGGER = azpy.initialize_logger(name=_MODULENAME, @@ -71,12 +71,12 @@ import config _LOGGER.debug('config.py is: {}'.format(config)) # initialize the Lumberyard Qt / PySide2 -config.init_ly_pyside(settings.LY_DEV) # for standalone +config.init_o3de_pyside(settings.O3DE_DEV) # for standalone settings.setenv() # for standalone # log debug info about Qt/PySide2 _LOGGER.debug('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH)) -_LOGGER.debug('LY_BIN_PATH: {}'.format(settings.LY_BIN_PATH)) +_LOGGER.debug('O3DE_BIN_PATH: {}'.format(settings.O3DE_BIN_PATH)) _LOGGER.debug('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH)) _LOGGER.debug('QT_QPA_PLATFORM_PLUGIN_PATH: {}'.format(settings.QT_QPA_PLATFORM_PLUGIN_PATH)) # ------------------------------------------------------------------------- @@ -123,26 +123,26 @@ from atom_material import AtomMaterial # ------------------------------------------------------------------------- # To Do: still should manage via dynaconf (dynamic config and settings) -from azpy.constants import ENVAR_LY_DEV -_LY_DEV = Path(os.getenv(ENVAR_LY_DEV, None)).resolve() +from azpy.constants import ENVAR_O3DE_DEV +_O3DE_DEV = Path(os.getenv(ENVAR_O3DE_DEV, None)).resolve() -from azpy.constants import ENVAR_LY_PROJECT -_LY_PROJECT = os.getenv(ENVAR_LY_PROJECT, None) +from azpy.constants import ENVAR_O3DE_PROJECT +_O3DE_PROJECT = os.getenv(ENVAR_O3DE_PROJECT, None) -from azpy.constants import ENVAR_LY_PROJECT_PATH -_LY_PROJECT_PATH = Path(os.getenv(ENVAR_LY_PROJECT_PATH, None)).resolve() +from azpy.constants import ENVAR_O3DE_PROJECT_PATH +_O3DE_PROJECT_PATH = Path(os.getenv(ENVAR_O3DE_PROJECT_PATH, None)).resolve() from azpy.constants import ENVAR_DCCSI_SDK_PATH _DCCSI_SDK_PATH = Path(os.getenv(ENVAR_DCCSI_SDK_PATH, None)).resolve() # build some reuseable path parts -_PROJECT_ASSET_PATH = Path(_LY_PROJECT_PATH).resolve() -_PROJECT_ASSETS_PATH = Path(_LY_PROJECT_PATH, 'Materials').resolve() +_PROJECT_ASSET_PATH = Path(_O3DE_PROJECT_PATH).resolve() +_PROJECT_ASSETS_PATH = Path(_O3DE_PROJECT_PATH, 'Materials').resolve() # To Do: figure out a proper way to deal with Lumberyard game projects -_GEM_MATPLAY_PATH = Path(_LY_DEV, 'Gems', 'AtomContent', 'AtomMaterialPlayground').resolve() -_GEM_ROYALTYFREE = Path(_LY_DEV, 'Gems', 'AtomContent', 'RoyaltyFreeAssets').resolve() -_GEM_SUBSOURCELIBRARY = Path(_LY_DEV, 'Gems', 'AtomContent', 'SubstanceSourceLibrary').resolve() +_GEM_MATPLAY_PATH = Path(_O3DE_DEV, 'Gems', 'AtomContent', 'AtomMaterialPlayground').resolve() +_GEM_ROYALTYFREE = Path(_O3DE_DEV, 'Gems', 'AtomContent', 'RoyaltyFreeAssets').resolve() +_GEM_SUBSOURCELIBRARY = Path(_O3DE_DEV, 'Gems', 'AtomContent', 'SubstanceSourceLibrary').resolve() _SUB_LIBRARY_PATH = Path(_GEM_SUBSOURCELIBRARY, 'Assets', 'SubstanceSource', 'Library').resolve() # ^ This hard codes a bunch of known asset gems, again bad # To Do: figure out a proper way to scrap the gem registry from project @@ -150,9 +150,9 @@ _SUB_LIBRARY_PATH = Path(_GEM_SUBSOURCELIBRARY, 'Assets', 'SubstanceSource', 'Li # path to watcher script _WATCHER_SCRIPT_PATH = Path(_DCCSI_SDK_PATH, 'substance', 'builder', 'watchdog', '__init__.py').resolve() -_TEX_RNDR_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve() -_MAT_OUTPUT_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve() -_SBSAR_COOK_PATH = Path(_LY_PROJECT_PATH, 'Materials', 'Substance').resolve() +_TEX_RNDR_PATH = Path(_O3DE_PROJECT_PATH, 'Materials', 'Substance').resolve() +_MAT_OUTPUT_PATH = Path(_O3DE_PROJECT_PATH, 'Materials', 'Substance').resolve() +_SBSAR_COOK_PATH = Path(_O3DE_PROJECT_PATH, 'Materials', 'Substance').resolve() # ------------------------------------------------------------------------- @@ -171,7 +171,7 @@ class Window(QtWidgets.QDialog): # we should really init non-Qt stuff and set things up as properties if project_path is None: - self.project_path = str(_LY_PROJECT_PATH) + self.project_path = str(_O3DE_PROJECT_PATH) else: self.project_path = Path(project_path) @@ -213,7 +213,7 @@ class Window(QtWidgets.QDialog): self.matOutputPathComboBox = self.createComboBox(str(_MAT_OUTPUT_PATH)) # self.directoryComboBox = self.createComboBox(QtCore.QDir.currentPath()) - # I changed this to scan the _LY_PROJECT + # I changed this to scan the _O3DE_PROJECT # self.sbsarDirectory = self.return_1st_sbsar(Path(self.project_path, 'Assets')).resolve().parent self.sbsarDirectory = QtCore.QDir() self.sbsarDirectory.setCurrent(str(_PROJECT_ASSET_PATH)) @@ -672,13 +672,13 @@ class Window(QtWidgets.QDialog): # if you want relative paths here is a better way # first of all, assume we know the project we are in - #_LY_PROJECT_PATH + #_O3DE_PROJECT_PATH texture_output_path = Path(self.texRenderPathComboBox.currentText()).resolve() rel_tex_path = None for p in texture_output_path.parts: - if _LY_PROJECT == p: - index = texture_output_path.parts.index(_LY_PROJECT) + if _O3DE_PROJECT == p: + index = texture_output_path.parts.index(_O3DE_PROJECT) rel_tuple = texture_output_path.parts[index + 1:] rel_tex_path = Path(*list(rel_tuple)) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbs_to_sbsar.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbs_to_sbsar.py index 4763bf430c..441110b8d4 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbs_to_sbsar.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbs_to_sbsar.py @@ -47,7 +47,7 @@ import pysbs.context as pysbs_context # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ @@ -68,10 +68,10 @@ _SYNTH_ENV_DICT = OrderedDict() _SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT) # grab a specific path from the base_env _PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] -_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] +_O3DE_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] # build some reuseable path parts -_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm() +_PATH_MOCK_ASSETS = Path(_O3DE_PROJECT_PATH, 'Assets').norm() _PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm() _PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_info.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_info.py index 2a26f13be2..9652b8990a 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_info.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_info.py @@ -47,7 +47,7 @@ import pysbs.context as pysbs_context # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ @@ -62,7 +62,7 @@ _LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = os.getenv(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = os.getenv(ENVAR_DCCSI_GDEBUG, False) # global space debug flag _DCCSI_DEV_MODE = os.getenv(ENVAR_DCCSI_DEV_MODE, False) @@ -88,10 +88,10 @@ _SYNTH_ENV_DICT = OrderedDict() _SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT) # grab a specific path from the base_env _PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] -_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] +_O3DE_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] # build some reuseable path parts -_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm() +_PATH_MOCK_ASSETS = Path(_O3DE_PROJECT_PATH, 'Assets').norm() _PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm() _PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_render.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_render.py index 31e2c81a57..6285ea0335 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_render.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_render.py @@ -45,7 +45,7 @@ import pysbs.context as pysbs_context # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ @@ -66,10 +66,10 @@ _SYNTH_ENV_DICT = OrderedDict() _SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT) # grab a specific path from the base_env _PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] -_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] +_O3DE_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] # build some reuseable path parts -_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm() +_PATH_MOCK_ASSETS = Path(_O3DE_PROJECT_PATH, 'Assets').norm() _PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm() _PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_utils.py index 74293a8d77..8ffdf30e35 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/sbsar_utils.py @@ -28,7 +28,7 @@ from azpy.constants import ENVAR_DCCSI_DEV_MODE from dynaconf import settings from pathlib import Path -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, settings.DCCSI_GDEBUG) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, settings.DCCSI_DEV_MODE) _MODULENAME = 'DCCsi.SDK.substance.builder.sbsar_utils' @@ -190,15 +190,15 @@ if __name__ == "__main__": _SYNTH_ENV_DICT = synthetic_env.stash_env() from azpy.constants import ENVAR_DCCSIG_PATH - from azpy.constants import ENVAR_LY_PROJECT_PATH + from azpy.constants import ENVAR_O3DE_PROJECT_PATH # grab a specific path from the base_env _PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] # use DCCsi as the project path for this test - _LY_PROJECT_PATH = _PATH_DCCSI + _O3DE_PROJECT_PATH = _PATH_DCCSI - _PROJECT_ASSETS_PATH = Path(_LY_PROJECT_PATH, 'Assets').resolve() + _PROJECT_ASSETS_PATH = Path(_O3DE_PROJECT_PATH, 'Assets').resolve() _PROJECT_MATERIALS_PATH = Path(_PROJECT_ASSETS_PATH, 'Materials').resolve() # this will combine two parts into a single path (object) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/substance_tools.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/substance_tools.py index af001902ed..0407a1db08 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/substance_tools.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/substance_tools.py @@ -46,7 +46,7 @@ import pysbs.context as pysbs_context # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ @@ -67,10 +67,10 @@ _SYNTH_ENV_DICT = OrderedDict() _SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT) # grab a specific path from the base_env _PATH_DCCSI = _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] -_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] +_O3DE_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] # build some reuseable path parts -_PATH_MOCK_ASSETS = Path(_LY_PROJECT_PATH, 'Assets').norm() +_PATH_MOCK_ASSETS = Path(_O3DE_PROJECT_PATH, 'Assets').norm() _PATH_MOCK_SUBLIB = Path(_PATH_MOCK_ASSETS, 'SubstanceSource').norm() _PATH_MOCK_SBS = Path(_PATH_MOCK_SUBLIB, 'sbs').norm() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/watchdog/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/watchdog/__init__.py index d37a9cb5ec..35c970e711 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/watchdog/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/Substance/builder/watchdog/__init__.py @@ -53,7 +53,7 @@ import pysbs.context as pysbs_context # ------------------------------------------------------------------------- # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ @@ -71,8 +71,8 @@ _LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME})) from collections import OrderedDict _SYNTH_ENV_DICT = OrderedDict() _SYNTH_ENV_DICT = azpy.synthetic_env.stash_env(_SYNTH_ENV_DICT) -_LY_DEV = _SYNTH_ENV_DICT[ENVAR_LY_DEV] -_LY_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] +_O3DE_DEV = _SYNTH_ENV_DICT[ENVAR_O3DE_DEV] +_O3DE_PROJECT_PATH = _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] # ------------------------------------------------------------------------- @@ -90,7 +90,7 @@ class MyHandler(PatternMatchingEventHandler): """ self.outputName = event.src_path.split(".sbsar")[0].split("/")[-1] self.outputCookPath = event.src_path.split(self.outputName) - self.outputRenderPath = Path(_LY_PROJECT_PATH, 'Assets', 'Textures', 'Substance').norm() + self.outputRenderPath = Path(_O3DE_PROJECT_PATH, 'Assets', 'Textures', 'Substance').norm() _LOGGER.debug(self.outputCookPath, self.outputName, self.outputRenderPath) pysbs_batch.sbsrender_info(input=event.src_path) diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/conftest.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/3dsMax/stub similarity index 65% rename from AutomatedTesting/Gem/PythonTests/editor_test_testing/conftest.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/3dsMax/stub index 1f49f7111b..d365a5f2c9 100644 --- a/AutomatedTesting/Gem/PythonTests/editor_test_testing/conftest.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/3dsMax/stub @@ -1,8 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python """ 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 """ - -pytest_plugins = ["pytester"] +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/_init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/_init__.py new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/_init__.py @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/main.py new file mode 100644 index 0000000000..b08991f63e --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/AddOns/MaterialExporter/main.py @@ -0,0 +1,21 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- +# DCCsi\\Tools\\Blender\\AddOns\\MaterialExporter\\main.py + +""" A in Blender tool for exporting BRDF materials as O3DE Atom StandardPBR +""" + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """Run this file as main""" + + print('MaterialExporter.main() not implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/config.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/config.py new file mode 100644 index 0000000000..b0f67b1034 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/config.py @@ -0,0 +1,11 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- + +print('Not Implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.dev/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/settings.json similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.dev/stub rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/settings.json diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/start.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/start.py new file mode 100644 index 0000000000..b0f67b1034 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Blender/start.py @@ -0,0 +1,11 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- + +print('Not Implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Houdini/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Houdini/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Houdini/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Marmoset/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Marmoset/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Marmoset/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/Prefs/icons/MayaStartupImage.png b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/Prefs/icons/MayaStartupImage.png new file mode 100644 index 0000000000..8f5d8290a8 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/Prefs/icons/MayaStartupImage.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:083ab199e273431963fc5f80bb80b7d1b1d428f7b12a5180d7199a7431291982 +size 311865 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/plugins/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/plugins/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/scripts/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/scripts/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/siteDir/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/2020/siteDir/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Help/HelpStub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Help/HelpStub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Projects/default/workspace.mel b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Projects/default/workspace.mel new file mode 100644 index 0000000000..c6474c4414 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Projects/default/workspace.mel @@ -0,0 +1,36 @@ +//Maya 2016 Project Definition + +workspace -fr "fluidCache" "cache/nCache/fluid"; +workspace -fr "images" "images"; +workspace -fr "offlineEdit" "scenes/edits"; +workspace -fr "furShadowMap" "renderData/fur/furShadowMap"; +workspace -fr "iprImages" "renderData/iprImages"; +workspace -fr "renderData" "renderData"; +workspace -fr "scripts" "scripts"; +workspace -fr "fileCache" "cache/nCache"; +workspace -fr "eps" "data"; +workspace -fr "shaders" "renderData/shaders"; +workspace -fr "3dPaintTextures" "sourceimages/3dPaintTextures"; +workspace -fr "translatorData" "data"; +workspace -fr "mel" "scripts"; +workspace -fr "furFiles" "renderData/fur/furFiles"; +workspace -fr "OBJ" "data"; +workspace -fr "particles" "cache/particles"; +workspace -fr "scene" "scenes"; +workspace -fr "furEqualMap" "renderData/fur/furEqualMap"; +workspace -fr "sourceImages" "sourceimages"; +workspace -fr "furImages" "renderData/fur/furImages"; +workspace -fr "clips" "clips"; +workspace -fr "depth" "renderData/depth"; +workspace -fr "movie" "movies"; +workspace -fr "audio" "sound"; +workspace -fr "bifrostCache" "cache/bifrost"; +workspace -fr "autoSave" "autosave"; +workspace -fr "mayaAscii" "scenes"; +workspace -fr "move" "data"; +workspace -fr "sound" "sound"; +workspace -fr "diskCache" "data"; +workspace -fr "illustrator" "data"; +workspace -fr "mayaBinary" "scenes"; +workspace -fr "templates" "assets"; +workspace -fr "furAttrMap" "renderData/fur/furAttrMap"; diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/IBLbaker_brdf_lut.dds b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/IBLbaker_brdf_lut.dds new file mode 100644 index 0000000000..81e62781b4 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/IBLbaker_brdf_lut.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:646b6d93b2c672bbbbcb46af1bfcaf26ca37c8a0c2b218989b145417bb6b7c93 +size 262272 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_diffuse.dds b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_diffuse.dds new file mode 100644 index 0000000000..980477af4b --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_diffuse.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86209db0389b152c709d529e9d5705219b44bfbf712a26788a5c2ab0adc0c373 +size 98452 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_specular.dds b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_specular.dds new file mode 100644 index 0000000000..fd6e1f99fc --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_lighting_specular.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd95898a04b81b80b095dbef34523f3b70a8c14bc9f82116f732ab648f25658b +size 2096788 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_skybox.dds b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_skybox.dds new file mode 100644 index 0000000000..304aaa0a1f --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/PBR/ly_cubempas/artist_workshop_4k_skybox.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a85a15d5c60102f414b4ad03604dbd1c78e3d1c1fe445f60db158b2c069f792d +size 25165972 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/SourceImages/MayaStartupImage.psd b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/SourceImages/MayaStartupImage.psd new file mode 100644 index 0000000000..8d630620ca --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/SourceImages/MayaStartupImage.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1318f73ca32ec56dfeb0233679504f6fc723081f0cafa8e7e2d0517b878defd1 +size 2000893 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/SourceImages/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/SourceImages/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/workspace.mel b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/workspace.mel new file mode 100644 index 0000000000..2cbf02f1aa --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Resources/workspace.mel @@ -0,0 +1,69 @@ +//Maya 2016 Project Definition + +workspace -fr "fluidCache" "mayaData/cache/nCache/fluid"; +workspace -fr "JT_DC" "mayaData/Trans/JT"; +workspace -fr "CATIAV4_DC" "mayaData/Trans/CATIAV4"; +workspace -fr "images" "mayaData/Images"; +workspace -fr "offlineEdit" "ArtSource/Maya"; +workspace -fr "STEP_DC" "mayaData/Trans/STEP"; +workspace -fr "furShadowMap" "mayaData/renderData/fur/furShadowMap"; +workspace -fr "SPF_DCE" "mayaData/Trans/SPF"; +workspace -fr "scripts" "mayaData/Scripts"; +workspace -fr "CATIAV5_DC" "mayaData/Trans/CATIAV5"; +workspace -fr "DAE_FBX" "mayaData/Trans/DAE_FBX"; +workspace -fr "shaders" "mayaData/renderData/shaders"; +workspace -fr "furFiles" "mayaData/renderData/fur/furFiles"; +workspace -fr "OBJ" "mayaData/OBJ"; +workspace -fr "FBX export" "mayaData/Trans/FBX_export"; +workspace -fr "furEqualMap" "mayaData/renderData/fur/furEqualMap"; +workspace -fr "Autodesk Packet File" "mayaData/Trans"; +workspace -fr "DAE_FBX export" "mayaData/Trans/DAE_FBX_export"; +workspace -fr "SPF_DC" "mayaData/Trans/SPF"; +workspace -fr "movie" "mayaData/movies"; +workspace -fr "DXF_DCE" "mayaData/Trans/DXF"; +workspace -fr "move" "mayaData/move"; +workspace -fr "mayaAscii" "ArtSource"; +workspace -fr "autoSave" "mayaData"; +workspace -fr "sound" "mayaData/Sounds"; +workspace -fr "mayaBinary" "ArtSource"; +workspace -fr "ZPR_DCE" "mayaData/Trans/ZPR"; +workspace -fr "STL_DCE" "mayaData/Trans/STL"; +workspace -fr "iprImages" "mayaData/renderData/iprImages"; +workspace -fr "PhysX" "mayaData/Trans/Physx"; +workspace -fr "DXF_DC" "mayaData/Trans/DXF"; +workspace -fr "FBX" "mayaData/Trans/FBX"; +workspace -fr "studioImport" "mayaData/Trans"; +workspace -fr "UG_DCE" "mayaData/Trans/UG"; +workspace -fr "renderData" "mayaData/renderData"; +workspace -fr "fileCache" "mayaData/cache/nCache"; +workspace -fr "eps" "mayaData/EPS"; +workspace -fr "Fbx" "Objects"; +workspace -fr "3dPaintTextures" "mayaData/images/3dPaintTextures"; +workspace -fr "translatorData" "mayaData"; +workspace -fr "mel" "mayaData/Scripts/Mel"; +workspace -fr "particles" "mayaData/cache/particles"; +workspace -fr "IV_DC" "mayaData/Trans/IV"; +workspace -fr "scene" "ArtSource"; +workspace -fr "DWG_DCE" "mayaData/Trans/DWG"; +workspace -fr "MayaCryExport" "Objects"; +workspace -fr "sourceImages" "ArtSource/Textures"; +workspace -fr "furImages" "mayaData/renderData/fur/furImages"; +workspace -fr "clips" "mayaData/clips"; +workspace -fr "PTC_DC" "mayaData/Trans/PTC"; +workspace -fr "STL_DC" "mayaData/Trans/STL"; +workspace -fr "IPT_DC" "mayaData/Trans/IPT"; +workspace -fr "CSB_DC" "mayaData/Trans/CSB"; +workspace -fr "SW_DC" "mayaData/Trans/SW"; +workspace -fr "depth" "mayaData/renderData/depth"; +workspace -fr "audio" "mayaData/Sounds"; +workspace -fr "DWG_DC" "mayaData/Trans/DWG"; +workspace -fr "bifrostCache" "mayaData/cache/bifrost"; +workspace -fr "IGES_DCE" "mayaData/Trans/IGES"; +workspace -fr "Alembic" "mayaData/Trans/Alembic"; +workspace -fr "illustrator" "mayaData/AI"; +workspace -fr "diskCache" "mayaData"; +workspace -fr "UG_DC" "mayaData/Trans/UG"; +workspace -fr "templates" "mayaData/assets"; +workspace -fr "OBJexport" "mayaData/Trans/Obj"; +workspace -fr "furAttrMap" "mayaData/renderData/fur/furAttrMap"; +workspace -fr "IGES_DC" "mayaData/Trans/IGES"; diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Mel/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Mel/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub_util.py similarity index 61% rename from Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub_util.py index 5597c28d04..1dc43d8485 100644 --- a/Gems/AudioEngineWwise/Code/audioenginewwise_stub_files.cmake +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/Python/stub_util.py @@ -1,3 +1,5 @@ +# coding:utf-8 +#!/usr/bin/python # # 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. @@ -5,7 +7,6 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # +# ------------------------------------------------------------------------- -set(FILES - Source/AudioEngineWwiseModule_Stub.cpp -) +print('Not Implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/constants.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/constants.py new file mode 100644 index 0000000000..6ed8297ab9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/constants.py @@ -0,0 +1,32 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- + +""" +Module Documentation: + DccScriptingInterface:: Tools//maya//scripts//constants.py + +This module is mainly a bunch of commony used constants, and default strings +So we can make an update here once that is used elsewhere +""" +# ------------------------------------------------------------------------- +# built-ins +# none + +# -- External Python modules + +# -- DCCsi Extension Modules +#import azpy + +# -- maya imports +# none +# ------------------------------------------------------------------------- +OBJ_DCCSI_MAINMENU = 'O3deDCCsiMainMenu' +TAG_DCCSI_MAINMENU = 'DCCsi (O3DE:Atom)' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_callbacks.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_callbacks.py new file mode 100644 index 0000000000..4a2b756a5e --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_callbacks.py @@ -0,0 +1,201 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +""" +Module Documentation: + DccScriptingInterface:: SDK//maya//scripts//set_callbacks.py + +This module manages a set of predefined callbacks for maya +""" +# ------------------------------------------------------------------------- +# -- Standard Python modules +import os +import sys +import logging as _logging +# -- External Python modules +from box import Box +# maya imports +import maya.cmds as mc +import maya.api.OpenMaya as om +# -- DCCsi Extension Modules +from azpy.constants import * +import azpy.dcc.maya +azpy.dcc.maya.init() # <-- should have already run? +import azpy.dcc.maya.callbacks.event_callback_handler as azEvCbH +import azpy.dcc.maya.callbacks.node_message_callback_handler as azNdMsH +# Node Message Callback Setup +import azpy.dcc.maya.callbacks.on_shader_rename as oSR +from set_defaults import set_defaults +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +from azpy.env_bool import env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE + +# global space +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, True) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, True) + +_MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_callbacks' + +_LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=int(20)) +_LOGGER.debug('Invoking:: {0}.'.format({_MODULENAME})) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# global scope callbacks, set up set and initialize all to None +# To Do: should callback initialization use data-driven settings? +# To Do: should we move callback initialization to a sub-module? +# To Do: move the callback key like 'NewSceneOpened' here (instead of None) +# ^ this would provide ability to loop through and replace key with CB object + +_G_CALLBACKS = Box(box_dots=True) # global scope container +_G_PRIMEKEY = 'DCCsi_callbacks' +_G_CALLBACKS[_G_PRIMEKEY] = True # required prime key + + +# ------------------------------------------------------------------------- +def init_callbacks(_callbacks=_G_CALLBACKS): + # store as a dict (Box is a fancy dict) + _callbacks[_G_PRIMEKEY] = True # required prime key + + # signature dict['callback key'] = ('CallBack'(type), func, callbackObj) + _callbacks['on_new_file'] = ['NewSceneOpened', set_defaults, None] + _callbacks['new_scene_fix_paths'] = ['NewSceneOpened', install_fix_paths, None] + _callbacks['post_scene_fix_paths'] = ['PostSceneRead', install_fix_paths, None] + _callbacks['workspace_changed'] = ['workspaceChanged', update_workspace, None] + _callbacks['quit_app'] = ['quitApplication', uninstall_callbacks, None] + + # nodeMessage style callbacks + # fire a function + _func_00 = oSR.on_shader_rename_rename_shading_group + # using a nodeMessage callback trigger + _cb_00 = om.MNodeMessage.addNameChangedCallback + # all nodeMessage type callbacks can use 'nodeMessageType' key + _callbacks['shader_rename'] = ['nodeMessageType', (_func_00, _cb_00), None] + + return _callbacks +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def uninstall_callbacks(): + """Bulk uninstalls hte globally defined set of callbacks: + _G_callbacks""" + + global _G_CALLBACKS + + _LOGGER.debug('uninstall_callbacks() fired') + + for key, value in _G_CALLBACKS: + if value[2] is not None: # have a cb + value[2].uninstall() # so uninstall it + else: + _LOGGER.warning('No callback in: key {0}, value:{1}' + ''.format(key, value)) + _G_CALLBACKS = None + _LOGGER.info('DCCSI CALLBACKS UNINSTALLED ... EXITING') + return _G_CALLBACKS +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def install_callbacks(_callbacks=_G_CALLBACKS): + """Bulk installs the globally defined set of callbacks: + _G_callbacks""" + + _LOGGER.debug('install_callback_set() fired') + + _callbacks = init_callbacks(_callbacks) + + # we initialized the box with this so pop it + if 'box_dots' in _callbacks: + _callbacks.pop('box_dots') + + # don't pass anything but carefully considered dict + if _G_PRIMEKEY in _callbacks: + _primekey = _callbacks.pop(_G_PRIMEKEY) + else: + _LOGGER.error('No prime key, use a correct dictionary') + #To Do: implement error handling and return codes + return _callbacks[None] + + for key, value in _G_CALLBACKS.items(): + # we popped the prime key should the rest should be safe + if value[0] != 'nodeMessageType': + # set callback up + _cb = azEvCbH.EventCallbackHandler(value[0], + value[1]) + # ^ installs by default + # stash it back into managed dict + value[2] = _cb + # value[2].install() + else: + # set up callback, value[1] should be tupple(func, trigger) + _cb = azNdMsH.NodeMessageCallbackHandler(value[1][0], + value[1][1]) + # ^ installs by default + # stash it back into managed dict + value[2] = _cb + # value[2].install() + + return _callbacks +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def install_fix_paths(foo=None): + """Installs and triggers a fix paths module. + This can repair broken reference paths in shaders""" + global _fix_paths + _fix_paths = None + + _LOGGER.debug('install_fix_paths() fired') + + # if we don't have it already, this function is potentially triggered + # by a callback, so we don't need to keep importing it. + try: + _fix_paths + reload(_fix_paths) + except Exception as e: + try: + import fixPaths as _fix_paths + except Exception as e: + # To Do: not implemented yet + _LOGGER.warning('NOT IMPLEMENTED: {0}'.format(e)) + + # if we have it, use it + if _fix_paths: + return _fix_paths.main() + else: + # To Do: implement error handling and return codes + return 1 +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def update_workspace(foo=None): + """Forces and update of the workspace (workspace.mel)""" + _LOGGER.debug('update_workspace() fired') + result = mc.workspace(update=True) + return result +# ------------------------------------------------------------------------- + +# install and init callbacks on an import obj +_G_CALLBACKS = install_callbacks(_G_CALLBACKS) + +# ========================================================================== +# Module Tests +#========================================================================== +if __name__ == '__main__': + _G_CALLBACKS = install_callbacks(_G_CALLBACKS) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_defaults.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_defaults.py new file mode 100644 index 0000000000..639b03c297 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_defaults.py @@ -0,0 +1,93 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +""" +Module Documentation: + DccScriptingInterface:: SDK//maya//scripts//set_pref_defaults.py + +This module manages a predefined set of prefs for maya +""" +# ------------------------------------------------------------------------- +# -- Standard Python modules +import os +import sys +# -- External Python modules + +# -- DCCsi Extension Modules +import azpy +from azpy.constants import * + +# -- maya imports +import maya.cmds as mc +import maya.mel as mm + +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +from azpy.env_bool import env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE + +# global space +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) + +_MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_defaults' + +_LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=int(20)) +_LOGGER.debug('Invoking:: {0}.'.format({_MODULENAME})) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def set_defaults(units='meter'): + """This method will make defined settings changes to Maya prefs, + to better configure maya to work with Lumberyard""" + # To Do: make this data-driven env/settings, game teams should be able + # to opt out and/or set their prefered configuration. + + _LOGGER.debug('set_defaults_lumberyard() fired') + + # set up default units ... this should be moved to bootstrap config + _LOGGER.info('Default, 1 Linear Game Unit in Lumberyard == 1 Meter' + ' in Maya content. Setting default linear units to Meters' + ' (user can change to other units in the preferences)') + + result = mc.currentUnit(linear=units) + + # set up grid defaults + _LOGGER.info('Setting Grid defaults, to match default unit scale.' + '(user can change grid config manually') + try: + mc.grid(size=32, spacing=1, divisions=10) + except Exception as e: + _LOGGER.warning('{0}'.format(e)) + + # viewFit + _LOGGER.info('Changing default mc.viewFit') + try: + mc.viewFit() + except Exception as e: + _LOGGER.warning('{0}'.format(e)) + + # some mel commands + _LOGGER.info('Changing sersp camera clipping planes') + try: + mm.eval(str(r'setAttr "perspShape.nearClipPlane" 0.01;')) + mm.eval(str(r'setAttr "perspShape.farClipPlane" 1000;')) + except Exception as e: + _LOGGER.warning('{0}'.format(e)) + + # set up fixPaths + _LOGGER.info('~ Setting up fixPaths in default scene') + + return 0 +# ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_menu.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_menu.py new file mode 100644 index 0000000000..e417324d0b --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_menu.py @@ -0,0 +1,88 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +""" +Module Documentation: + DccScriptingInterface:: SDK//maya//scripts//set_menu.py + +This module creates and manages a DCCsi mainmenu +""" +# ------------------------------------------------------------------------- +# -- Standard Python modules +# none + +# -- External Python modules +# none + +# -- DCCsi Extension Modules +import azpy +from constants import OBJ_DCCSI_MAINMENU +from constants import TAG_DCCSI_MAINMENU + +# -- maya imports +import pymel.core as pm +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +from azpy.env_bool import env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE + +# global space +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) + +_MODULENAME = r'DCCsi.SDK.Maya.Scripts.set_menu' + +_LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=int(20)) +_LOGGER.debug('Invoking:: {0}.'.format({_MODULENAME})) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def menu_cmd_test(): + _LOGGER.info('test_func(), is TESTING main menu') + return +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +def set_main_menu(obj_name=OBJ_DCCSI_MAINMENU, label=TAG_DCCSI_MAINMENU): + _main_window = pm.language.melGlobals['gMainWindow'] + + _menu_obj = obj_name + _menu_label = label + + # check if it already exists and remove (so we don't duplicate) + if pm.menu(_menu_obj, label=_menu_label, exists=True, parent=_main_window): + pm.deleteUI(pm.menu(_menu_obj, e=True, deleteAllItems=True)) + + # create the main menu object + _custom_tools_menu = pm.menu(_menu_obj, + label=_menu_label, + parent=_main_window, + tearOff=True) + + # make a dummpy sub-menu + pm.menuItem(label='Menu Item Stub', + subMenu=True, + parent=_custom_tools_menu, + tearOff=True) + + # make a dummy menu item to test + pm.menuItem(label='Test', command=pm.Callback(menu_cmd_test)) + return _custom_tools_menu + +# ========================================================================== +# Run as LICENSE +#========================================================================== +if __name__ == '__main__': + + _custom_menu = set_main_menu() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_shelf.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_shelf.py new file mode 100644 index 0000000000..1bee05afe8 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/set_shelf.py @@ -0,0 +1,130 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +""" +Module Documentation: + DccScriptingInterface:: SDK//maya//scripts//set_shelf.py + +This module manages a custom shelf in maya for the DCCsi +Reference: https://gist.github.com/vshotarov/1c3176fe9e38dcaadd1e56c2f15c95d9 +""" +# ------------------------------------------------------------------------- +# -- Standard Python modules +# none + +# -- External Python modules +# none + +# -- DCCsi Extension Modules +# none + +# -- Maya Extension Modules +import maya.cmds as mc +# ------------------------------------------------------------------------- + +def _null(*args): + pass + +# ------------------------------------------------------------------------- +class customShelf(_Custom_Shelf): + '''This is an example shelf.''' + + def build(self): + self.add_button(label="button1") + self.add_button("button2") + self.add_button("popup") + p = mc.popupMenu(b=1) + self.add_menu_item(p, "popupMenuItem1") + self.add_menu_item(p, "popupMenuItem2") + sub = self.add_submenu(p, "subMenuLevel1") + self.add_menu_item(sub, "subMenuLevel1Item1") + sub2 = self.add_submenu(sub, "subMenuLevel2") + self.add_menu_item(sub2, "subMenuLevel2Item1") + self.add_menu_item(sub2, "subMenuLevel2Item2") + self.add_menu_item(sub, "subMenuLevel1Item2") + self.add_menu_item(p, "popupMenuItem3") + self.add_button("button3") +# ------------------------------------------------------------------------- + + + +class _Custom_Shelf(): + '''A simple class to build custom shelves in maya. + The build method is empty and an inheriting class should override''' + + def __init__(self, name="DCCsi", icon_path=""): + self._name = name + + self._icon_path = icon_path + + self._label_background_color = (0, 0, 0, 0) + self._label_colour = (.9, .9, .9) + + self._clean_old_shlef() + + mc.setParent(self._name) + + self.build() + + def build(self): + '''Override this method in custom class. + Otherwise, nothing is added to the shelf.''' + pass + + def add_button(self, + label='', + icon="commandButton.png", + command=_null, + doubleCommand=_null): + '''Adds a shelf button with the specified label, + command, double click command and image.''' + mc.setParent(self._name) + if icon: + icon = self._icon_path + icon + mc.shelfButton(width=37, height=37, + image=icon, + label=label, + command=command, + doubleClickCommand=doubleCommand, + imageOverlayLabel=label, + overlayLabelBackColor=self._label_background_color, + overlayLabelColor=self._label_colour) + + def add_menu_item(self, parent, label, command=_null, icon=""): + '''Adds a shelf button with the specified label, + command, double click command and image.''' + if icon: + icon = self._icon_path + icon + return mc.menuItem(p=parent, l=label, c=command, i="") + + def add_submenu(self, parent, label, icon=None): + '''Adds a sub menu item with the specified label and icon + to the specified parent popup menu.''' + if icon: + icon = self._icon_path + icon + return mc.menuItem(p=parent, l=label, i=icon, subMenu=1) + + def _clean_old_shlef(self): + '''Checks if the shelf exists and empties it if it does or + creates it if it does not.''' + if mc.shelfLayout(self._name, ex=1): + if mc.shelfLayout(self._name, q=1, ca=1): + for each in mc.shelfLayout(self._name, q=1, ca=1): + mc.deleteUI(each) + else: + mc.shelfLayout(self._name, p="ShelfLayout") + + +# ========================================================================== +# Module Tests +# ========================================================================== +if __name__ == '__main__': + customShelf() + pass diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/userSetup.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/userSetup.py new file mode 100644 index 0000000000..c68eb8d256 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Scripts/userSetup.py @@ -0,0 +1,325 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +from __future__ import unicode_literals + +""" +This module fullfils the maya bootstrap pattern as described in their docs +https://tinyurl.com/y2aoz8es + +Pattern is similar to Lumberyard Editor\\Scripts\\bootstrap.py + +For now the proper way to initiate Maya boostrapping the DCCsi, is to use +the provided env and launcher bat files. + +If you are developing for the DCCsi you can use this launcher to start Maya: +DccScriptingInterface\\Launchers\\Windows\\Launch_Maya_2020.bat" + +To Do: ATOM-5861 +""" +__project__ = 'DccScriptingInterface' + +# it is really hard to debug userSetup bootstrapping +# this enables some rudimentary logging for debugging +_BOOT_INFO = True + +# ------------------------------------------------------------------------- +# built in's +import os +import sys +import site +import inspect +import traceback +import logging as _logging + +# -- DCCsi Extension Modules +import azpy +from azpy.constants import * +from azpy.env_bool import env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE + +# To Do: needs to be updated to use dynaconf and config.py +from azpy.env_base import _BASE_ENVVAR_DICT + +# -- maya imports +import maya.cmds as cmds +import maya.mel as mel +#from pymel.all import * +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# global space +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_DEV_MODE = True # force true for debugger testing + +_ORG_TAG = r'Amazon::Lumberyard' +_APP_TAG = r'DCCsi' +_TOOL_TAG = r'SDK.Maya.Scripts.userSetup' +_TYPE_TAG = r'entrypoint' # bootstrap + +_MODULENAME = str('{0}.{1}'.format(_APP_TAG, _TOOL_TAG)) + +_LOGGER = azpy.initialize_logger(_MODULENAME, default_log_level=int(20)) +_LOGGER.info('Initializing: {0}.'.format({_MODULENAME})) +_LOGGER.info('DCCSI_GDEBUG: {0}.'.format({_DCCSI_GDEBUG})) +_LOGGER.info('DCCSI_DEV_MODE: {0}.'.format({_DCCSI_DEV_MODE})) + +# flag to turn off setting up callbacks, until they are fully implemented +# To Do: consider making it a settings option to define and enable/disable +_G_LOAD_CALLBACKS = True # couple bugs, couple NOT IMPLEMENTED +_LOGGER.info('DCCSI_MAYA_SET_CALLBACKS: {0}.'.format({_G_LOAD_CALLBACKS})) + +# early attach WingIDE debugger (can refactor to include other IDEs later) +if _DCCSI_DEV_MODE: + from azpy.test.entry_test import connect_wing + foo = connect_wing() +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# To Do REMOVE this block and replace with dev module +# debug prints, To Do: this should be moved to bootstrap config +#_G_DEBUGGER = os.getenv(ENVAR_DCCSI_GDEBUGGER, "WING") + +#if _DCCSI_DEV_MODE: + #if _G_DEBUGGER == "WING": + #_LOGGER.info('{0}'.format('-' * 74)) + #_LOGGER.info('Developer Debug Mode: {0}, Basic debugger: {1}'.format(_G_DEBUG, _G_DEBUGGER)) + #try: + #_LOGGER.info('Attempting to start basic WING debugger') + #import azpy.lmbr.test + + #_LOGGER.info('Package Imported: azpy.test') + #ouput = azpy.entry_test.main(verbose=False, + #connectDebugger=True, + #returnOuput=_G_DEBUG) + #_LOGGER.info(ouput) + #pass + #except Exception as e: + #_LOGGER.info("Error: azpy.test, entry_test (didn't perform)") + #_LOGGER.info("Exception: {0}".format(e)) + #pass + #elif _G_DEBUGGER == "PYCHARM": + ## https://github.com/juggernate/PyCharm-Maya-Debugging + #_LOGGER.info('{0}'.format('-' * 74)) + #_LOGGER.info('Developer Debug Mode: {0}, Basic debugger: {1}'.format(_G_DEBUG, _G_DEBUGGER)) + #sys.path.append('C:\Program Files\JetBrains\PyCharm 2019.1.3\debug-eggs\pydevd-pycharm.egg') + #try: + #_LOGGER.info('Attempting to start basic PYCHARM debugger') + ## Inside Maya Python Console (Tip: add to a shelf button for quick access) + #import pydevd + + #_LOGGER.info('Package Imported: pydevd') + #pydevd.settrace('localhost', port=7720, suspend=False) + #_LOGGER.info('PYCHARM Debugger Attach Success!!!') + ## To disconnect run: + ## pydevd.stoptrace() + #pass + #except Exception as e: + #_LOGGER.info("Error: pydevd.settrace (didn't perform)") + #_LOGGER.info("Exception: {0}".format(e)) + #pass + #else: + #pass +## ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# validate access to the DCCsi and it's Lib site-packages +# bootstrap site-packages by version +from azpy.constants import PATH_DCCSI_PYTHON_LIB_PATH + +try: + os.path.exists(PATH_DCCSI_PYTHON_LIB_PATH) + site.addsitedir(PATH_DCCSI_PYTHON_LIB_PATH) + _LOGGER.info('azpy 3rdPary site-packages: is: {0}'.format(PATH_DCCSI_PYTHON_LIB_PATH)) +except Exception as e: + _LOGGER.error('ERROR: {0}, {1}'.format(e, PATH_DCCSI_PYTHON_LIB_PATH)) + raise e + +# 3rdparty +from unipath import Path +from box import Box +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +# Maya is frozen +#_MODULE_PATH = Path(__file__) +# https://tinyurl.com/y49t3zzn +# module path when frozen +_MODULE_FILEPATH = os.path.abspath(inspect.getfile(inspect.currentframe())) +_MODULE_PATH = os.path.dirname(_MODULE_FILEPATH) +if _BOOT_INFO: + _LOGGER.debug('Boot: CWD: {}'.format(os.getcwd())) + _LOGGER.debug('Frozen: _MODULE_FILEPATH: {}'.format(_MODULE_FILEPATH)) + _LOGGER.debug('Frozen: _MODULE_PATH: {}'.format(_MODULE_PATH)) + _LOGGER.debug('Module __name__: {}'.format(__name__)) +# root: INFO: Module __name__: __main__ + +_LOGGER.info('_MODULENAME: {}'.format(_MODULENAME)) + +# ------------------------------------------------------------------------- +# check some env var tags (fail if no, likely means no proper code access) +_STR_ERROR_ENVAR = "Envar 'key' does not exist in base_env: {0}" +_DCCSI_TOOLS_PATH = None +# To Do: needs to be updated to use dynaconf and config.py +try: + _DCCSI_TOOLS_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_TOOLS_PATH] +except Exception as e: + _LOGGER.critical(_STR_ERROR_ENVAR.format(_BASE_ENVVAR_DICT[ENVAR_DCCSI_TOOLS_PATH])) + +_O3DE_PROJECT_PATH = None +try: + _O3DE_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH] +except Exception as e: + _LOGGER.critical(_STR_ERROR_ENVAR.format(_BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH])) + +# check some env var tags (fail if no, likely means no proper code access) +_O3DE_DEV = _BASE_ENVVAR_DICT[ENVAR_O3DE_DEV] +_O3DE_DCCSIG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSIG_PATH] +_O3DE_DCCSI_LOG_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_LOG_PATH] +_O3DE_AZPY_PATH = _BASE_ENVVAR_DICT[ENVAR_DCCSI_AZPY_PATH] +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# To Do: implement data driven config +# Currently not used, but will be where we store the ordered dict +# which is parsed from the project bootstrapping config files. +_G_app_config = {} + +# global scope maya callbacks container +_G_callbacks = Box(box_dots=True) # global scope container + +# used to store fixPaths in the global scope +_fix_paths = None +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# add appropriate common tools paths to the maya environment variables +def startup(): + """Early starup execution before mayautils.executeDeferred(). + Some things like UI and plugins should be defered to avoid failure""" + _LOGGER.info('startup() fired') + + # get known paths + _KNOWN_PATHS = site._init_pathinfo() + + if os.path.isdir(_DCCSI_TOOLS_PATH): + site.addsitedir(_DCCSI_TOOLS_PATH, _KNOWN_PATHS) + try: + import azpy.test + _LOGGER.info('SUCCESS, import azpy.test') + except Exception as e: + _LOGGER.warning('startup(), could not import azpy.test') + + _LOGGER.info('startup(), COMPLETE') + return 0 +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# verify Shared\Python exists and add it as a site dir. Begin imports and config. +def post_startup(): + """Allows for a defered execution startup sequence""" + + _LOGGER.info('post_startup() fired') + + # plugins, To Do: these should be moved to bootstrapping config + try: + maya.cmds.loadPlugin("dx11Shader") + except Exception as e: + _LOGGER.error(e) # not a hard failure + + # Lumberyard DCCsi environment ready or error out. + try: + import azpy.dcc.maya + _LOGGER.info('Python module imported: azpy.dcc.maya') + except Exception as e: + _LOGGER.error(e) + _LOGGER.error(traceback.print_exc()) + return 1 + + # Dccsi azpy maya ready or error out. + try: + azpy.dcc.maya.init() + _LOGGER.info('SUCCESS, azpy.dcc.maya.init(), code accessible.') + except Exception as e: + _LOGGER.error(e) + _LOGGER.error(traceback.print_exc()) + return 1 + + # callbacks, To Do: these should also be moved to the bootstrapping config + # Defered startup after the Ui is running. + _G_CALLBACKS = Box(box_dots=True) # this just ensures a global scope container + if _G_LOAD_CALLBACKS: + from set_callbacks import _G_CALLBACKS + # ^ need to hold on to this as the install repopulate set + + # this ensures the fixPaths callback is loaded + # even when the other global callbacks are disabled + from set_callbacks import install_fix_paths + install_fix_paths() + + # set the project workspace + #_O3DE_PROJECT_PATH = _BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH] + _project_workspace = os.path.join(_O3DE_PROJECT_PATH, TAG_MAYA_WORKSPACE) + if os.path.isfile(_project_workspace): + try: + # load workspace + maya.cmds.workspace(_O3DE_PROJECT_PATH, openWorkspace=True) + _LOGGER.info('Loaded workspace file: {0}'.format(_project_workspace)) + maya.cmds.workspace(_O3DE_PROJECT_PATH, update=True) + except Exception as e: + _LOGGER.error(e) + else: + _LOGGER.warning('Workspace file not found: {1}'.format(_O3DE_PROJECT_PATH)) + + # Set up Lumberyard, maya default setting + from set_defaults import set_defaults + set_defaults() + + # Setup UI tools + if not maya.cmds.about(batch=True): + _LOGGER.info('Add UI dependent tools') + # wrap in a try, because we haven't implmented it yet + try: + mel.eval(str(r'source "{}"'.format(TAG_O3DE_DCC_MAYA_MEL))) + except Exception as e: + _LOGGER.error(e) + + # manage custom menu in a sub-module + from set_menu import set_main_menu + set_main_menu() + + # To Do: manage custom shelf in a sub-module + + _LOGGER.info('post_startup(), COMPLETE') + _LOGGER.info('DCCsi Bootstrap, COMPLETE') + return 0 +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +if __name__ == '__main__': + try: + # Early startup config. + startup() + + # This allows defered action post boot (atfer UI is active) + from maya.utils import executeDeferred + post = executeDeferred(post_startup) + + except Exception as e: + traceback.print_exc() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Shaders/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Shaders/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Tools/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Tools/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/Tools/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/config.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/config.py new file mode 100644 index 0000000000..50d6de8de4 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/config.py @@ -0,0 +1,23 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +# DCCsi\\Tools\\DCC\\Maya\\config.py + +"""DccScriptingInterface (DCCsi) +This is the dynamic config (dynaconf) for O3DE Maya DCCsi interface +""" + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """Run this file as main""" + + print('Maya.config() not implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/constants.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/constants.py new file mode 100644 index 0000000000..6695b6185c --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/constants.py @@ -0,0 +1,16 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +# DCCsi\\Tools\\DCC\\Maya\\constsants.py + +"""DccScriptingInterface (DCCsi) +This module contains constants for the O3DE Maya DCCsi interface +""" + diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/readme.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/readme.txt new file mode 100644 index 0000000000..57c8317737 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/readme.txt @@ -0,0 +1,78 @@ +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 +------------------------------------------------------------------------------- + +"DccScriptingInterface" aka DCCsi is a Gem for O3DE to extend and interface with dcc tools +in the python ecosystem. Each dcc tool may have it's own specific version of python. +Most are some version of py3+. O3DE provides an install of py3+ and manages package +dependancies with requirements.txt files and the cmake build system. + +However Autodesk Maya still uses a version of py2.7 and so we need an alternate way +to deal with package management for this DCC tool. + +Maya ships with it's own python interpreter called mayapy.exe + +Generally it is located here: +C:\Program Files\Autodesk\Maya2020\bin\mayapy.exe + +The python install and site-packages are here: +C:\Program Files\Autodesk\Maya2020\Python\Lib\site-packages + +A general goal of the DCCsi is be self-maintained, and to not taint the users installed applications of environment. + +So we boostrap additional access to site-packages in our userSetup.py: +"C:\Depot\Lumberyard\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\Maya\Scripts\userSetup.py" + +We don't want users to have to install or use Python2.7 although with maya and possibly other dcc tools we don't have that control. Maya 2020 and earlier versions are still on Python2.7, so instead of forcing another install of python we can just use mayapy to manage extensions. + +Pip may already be installed, you can check like so (your maya install path may be different): + +C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip --version + +If pip is not available yet for your mayapy. + +First find out where th site-packages is located + +C:\Program Files\Autodesk\Maya2020\bin>mayapy -m site +sys.path = [ + 'C:\\Program Files\\Autodesk\\Maya2020\\bin', + 'C:\\Program Files\\Autodesk\\Maya2020\\bin\\python27.zip', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python\\DLLs', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\plat-win', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\lib-tk', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python', + 'C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages', +] +USER_BASE: 'C:\\Users\\gallowj\\AppData\\Roaming\\Python' (exists) +USER_SITE: 'C:\\Users\\gallowj\\AppData\\Roaming\\Python\\Python27\\site-packages' (doesn't exist) +ENABLE_USER_SITE: True + +This is the location we are looking for: +C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages + +download get-pip.py and put into the above ^ directory: +https://bootstrap.pypa.io/pip/2.7/get-pip.py + +Put that in the root of site-packages: +C:\\Program Files\\Autodesk\\Maya2020\\Python\\lib\\site-packages\\get-pip.py + +With get-pip module ready, we run it to install pip: +C:\Program Files\Autodesk\Maya2020\bin>mayapy -m get-pip + +Now you should be able to run the following command and verify pip: +C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip --version +pip 20.3.4 from C:\Users\< you >\AppData\Roaming\Python\Python27\site-packages\pip (python 2.7) + +Now your local maya install is all set up with pip so you can install additional python packages to use in maya. (note: not all packages are compatible with maya) + +Now you will want to run the following file to finish setup... +We have a requirements.txt file with the extension packages we use in the DCCsi. +You'll need the repo/branch path of your O3DE (aka Lumberyard) install. +And you'll need to know where the DCCsi is located, we will install package dependancies there. + +Note: you may need to update the paths below to match your local o3de engine install! + +C:\Program Files\Autodesk\Maya2020\bin>mayapy -m pip install -r C:\Depot\o3de\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\Maya\requirements.txt -t C:\Depot\o3de\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\3rdParty\Python\Lib\2.x\2.7.x\site-packages diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/requirements.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/requirements.txt new file mode 100644 index 0000000000..ceb5be4dea --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/requirements.txt @@ -0,0 +1,84 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# pip-compile --generate-hashes requirements.txt +# +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 + # via -r requirements.txt +cachetools==3.1.1 \ + --hash=sha256:428266a1c0d36dc5aca63a2d7c5942e88c2c898d72139fca0e97fdd2380517ae \ + --hash=sha256:8ea2d3ce97850f31e4a08b0e2b5e6c34997d7216a9d2c98e0f3978630d4da69a + # via -r requirements.txt +click==7.1.2 \ + --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a \ + --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc + # via + # -r requirements.txt + # pip-tools +dynaconf==3.1.4 \ + --hash=sha256:b2f472d83052f809c5925565b8a2ba76a103d5dc1dbb9748b693ed67212781b9 \ + --hash=sha256:e6f383b84150b70fc439c8b2757581a38a58d07962aa14517292dcce1a77e160 + # via -r requirements.txt +hashids==1.3.1 \ + --hash=sha256:6c3dc775e65efc2ce2c157a65acb776d634cb814598f406469abef00ae3f635c \ + --hash=sha256:8bddd1acba501bfc9306e7e5a99a1667f4f2cacdc20cbd70bcc5ddfa5147c94c + # via -r requirements.txt +pathlib2==2.3.5 \ + --hash=sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db \ + --hash=sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868 + # via -r requirements.txt +pathlib==1.0.1 \ + --hash=sha256:6940718dfc3eff4258203ad5021090933e5c04707d5ca8cc9e73c94a7894ea9f + # via -r requirements.txt +python-box==3.4.6 \ + --hash=sha256:694a7555e3ff9fbbce734bbaef3aad92b8e4ed0659d3ed04d56b6a0a0eff26a9 \ + --hash=sha256:a71d3dc9dbaa34c8597d3517c89a8041bd62fa875f23c0f3dad55e1958e3ce10 + # via -r requirements.txt +scandir==1.10.0 \ + --hash=sha256:2586c94e907d99617887daed6c1d102b5ca28f1085f90446554abf1faf73123e \ + --hash=sha256:2ae41f43797ca0c11591c0c35f2f5875fa99f8797cb1a1fd440497ec0ae4b022 \ + --hash=sha256:2b8e3888b11abb2217a32af0766bc06b65cc4a928d8727828ee68af5a967fa6f \ + --hash=sha256:2c712840c2e2ee8dfaf36034080108d30060d759c7b73a01a52251cc8989f11f \ + --hash=sha256:4d4631f6062e658e9007ab3149a9b914f3548cb38bfb021c64f39a025ce578ae \ + --hash=sha256:67f15b6f83e6507fdc6fca22fedf6ef8b334b399ca27c6b568cbfaa82a364173 \ + --hash=sha256:7d2d7a06a252764061a020407b997dd036f7bd6a175a5ba2b345f0a357f0b3f4 \ + --hash=sha256:8c5922863e44ffc00c5c693190648daa6d15e7c1207ed02d6f46a8dcc2869d32 \ + --hash=sha256:92c85ac42f41ffdc35b6da57ed991575bdbe69db895507af88b9f499b701c188 \ + --hash=sha256:b24086f2375c4a094a6b51e78b4cf7ca16c721dcee2eddd7aa6494b42d6d519d \ + --hash=sha256:cb925555f43060a1745d0a321cca94bcea927c50114b623d73179189a4e100ac + # via + # -r requirements.txt + # pathlib2 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced + # via + # -r requirements.txt + # pathlib2 +typing==3.7.4.3 \ + --hash=sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9 \ + --hash=sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5 + # via + # -r requirements.txt + # dynaconf +unipath==1.1 \ + --hash=sha256:09839adcc72e8a24d4f76d63656f30b5a1f721fc40c9bcd79d8c67bdd8b47dae \ + --hash=sha256:e6257e508d8abbfb6ddd8ec357e33589f1f48b1599127f23b017124d90b0fff7 + # via -r requirements.txt +qdarkstyle==3.0.2 \ + --hash=sha256:55d149cf5f40ee297397f1818e091118cefb855a4a9c5c38566c47acd2d8c7ae \ + --hash=sha256:7c791535cc20b3cc1e8e1bf6b88dabe53cb0615983df702be83597e73ada2558 + # via -r c:\temp\requirements.txt +qtpy==1.9.0 \ + --hash=sha256:2db72c44b55d0fe1407be8fba35c838ad0d6d3bb81f23007886dc1fc0f459c8d \ + --hash=sha256:fa0b8363b363e89b2a6f49eddc162a04c0699ae95e109a6be3bb145a913190ea + # via + # -r c:\temp\requirements.txt + # qdarkstyle +wincertstore==0.2 \ + --hash=sha256:22d5eebb52df88a8d4014d5cf6d1b6c3a5d469e6c3b2e2854f3a003e48872356 \ + --hash=sha256:780bd1557c9185c15d9f4221ea7f905cb20b93f7151ca8ccaed9714dce4b327a + # via -r requirements.txt diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/settings.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/settings.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/start.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/start.py new file mode 100644 index 0000000000..4d965aec28 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Maya/start.py @@ -0,0 +1,24 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +# DCCsi\\Tools\\DCC\\Maya\\start.py + +"""DccScriptingInterface (DCCsi) +The DCCsi bootstraps tools like Maya with additional code access and extensions. +This module starts up maya in the DCCsi managed synthetic environment context. +""" + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """Run this file as main""" + + print('Maya.Start() not implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/custom_shaders/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/custom_shaders/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/ly_presets/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/ly_presets/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/templates/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/resources/templates/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/scripts/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/scripts/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/scripts/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/stub new file mode 100644 index 0000000000..d365a5f2c9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/DCC/Substance/stub @@ -0,0 +1,9 @@ +# coding:utf-8 +#!/usr/bin/python +""" +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 +""" +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/.gitignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/.gitignore similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/.gitignore rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/.gitignore diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Core.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Core.bat similarity index 67% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Core.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Core.bat index 5146d5f3e8..b37170d605 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Core.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Core.bat @@ -31,11 +31,11 @@ echo ~ O3DE DCC Scripting Interface Environment ... echo _____________________________________________________________________ echo. -IF "%DCCSI_LAUNCHERS_PATH%"=="" (set DCCSI_LAUNCHERS_PATH=%~dp0) -echo DCCSI_LAUNCHERS_PATH = %DCCSI_LAUNCHERS_PATH% +IF "%DCCSI_DEV_ENV%"=="" (set DCCSI_DEV_ENV=%~dp0) +echo DCCSI_DEV_ENV = %DCCSI_DEV_ENV% :: add to the PATH -SET PATH=%DCCSI_LAUNCHERS_PATH%;%PATH% +SET PATH=%DCCSI_DEV_ENV%;%PATH% :: Constant Vars (Global) :: global debug flag (propogates) @@ -61,24 +61,21 @@ IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=20) echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL% :: This maps up to the \Dev folder -IF "%DEV_REL_PATH%"=="" (set DEV_REL_PATH=..\..\..\..) -echo DEV_REL_PATH = %DEV_REL_PATH% +IF "%O3DE_REL_PATH%"=="" (set O3DE_REL_PATH=..\..\..\..) +echo O3DE_REL_PATH = %O3DE_REL_PATH% :: You can define the project name -IF "%LY_PROJECT_NAME%"=="" ( - for %%a in (%CD%..\..\..) do set LY_PROJECT_NAME=%%~na +IF "%O3DE_PROJECT%"=="" ( + for %%a in (%CD%..\..\..\..) do set O3DE_PROJECT=%%~na ) -echo LY_PROJECT_NAME = %LY_PROJECT_NAME% - -:: if not defined we just use the DCCsi path as stand-in -IF "%LY_PROJECT%"=="" (set LY_PROJECT=%CD%) -echo LY_PROJECT = %LY_PROJECT% +echo O3DE_PROJECT = %O3DE_PROJECT% :: set up the default project path (dccsi) :: if not set we also use the DCCsi path as stand-in -CD /D ..\..\ -IF "%LY_PROJECT_PATH%"=="" (set LY_PROJECT_PATH=%CD%) -echo LY_PROJECT_PATH = %LY_PROJECT_PATH% +CD /D ..\..\..\ +:: To Do: remove one of these +IF "%O3DE_PROJECT_PATH%"=="" (set O3DE_PROJECT_PATH=%CD%) +echo O3DE_PROJECT_PATH = %O3DE_PROJECT_PATH% IF "%ABS_PATH%"=="" (set ABS_PATH=%CD%) echo ABS_PATH = %ABS_PATH% @@ -87,37 +84,40 @@ echo ABS_PATH = %ABS_PATH% pushd %ABS_PATH% :: Change to root Lumberyard dev dir -CD /d %LY_PROJECT_PATH%\%DEV_REL_PATH% -IF "%LY_DEV%"=="" (set LY_DEV=%CD%) -echo LY_DEV = %LY_DEV% +CD /d %O3DE_PROJECT_PATH%\%O3DE_REL_PATH% +IF "%O3DE_DEV%"=="" (set O3DE_DEV=%CD%) +echo O3DE_DEV = %O3DE_DEV% :: Restore original directory popd :: dcc scripting interface gem path :: currently know relative path to this gem -set DCCSIG_PATH=%LY_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface +set DCCSIG_PATH=%O3DE_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface echo DCCSIG_PATH = %DCCSIG_PATH% :: Change to DCCsi root dir CD /D %DCCSIG_PATH% :: per-dcc sdk path -set DCCSI_SDK_PATH=%DCCSIG_PATH%\SDK -echo DCCSI_SDK_PATH = %DCCSI_SDK_PATH% +set DCCSI_TOOLS_PATH=%DCCSIG_PATH%\Tools +echo DCCSI_TOOLS_PATH = %DCCSI_TOOLS_PATH% :: temp log location specific to this gem -set DCCSI_LOG_PATH=%DCCSIG_PATH%\.temp\logs +set DCCSI_LOG_PATH=%O3DE_PROJECT_PATH%\.temp\logs echo DCCSI_LOG_PATH = %DCCSI_LOG_PATH% :: O3DE build path -IF "%TAG_LY_BUILD_PATH%"=="" (set TAG_LY_BUILD_PATH=build) -echo TAG_LY_BUILD_PATH = %TAG_LY_BUILD_PATH% +IF "%O3DE_BUILD_FOLDER%"=="" (set O3DE_BUILD_FOLDER=build) +echo O3DE_BUILD_FOLDER = %O3DE_BUILD_FOLDER% -IF "%LY_BUILD_PATH%"=="" (set LY_BUILD_PATH=%LY_DEV%\%TAG_LY_BUILD_PATH%\bin\profile) -echo LY_BUILD_PATH = %LY_BUILD_PATH% +IF "%O3DE_BUILD_PATH%"=="" (set O3DE_BUILD_PATH=%O3DE_DEV%\%O3DE_BUILD_FOLDER%) +echo O3DE_BUILD_PATH = %O3DE_BUILD_PATH% + +IF "%O3DE_BIN_PATH%"=="" (set O3DE_BIN_PATH=%O3DE_BUILD_PATH%\bin\profile) +echo O3DE_BIN_PATH = %O3DE_BIN_PATH% :: add to the PATH -SET PATH=%LY_BUILD_PATH%;%DCCSIG_PATH%;%DCCSI_AZPY_PATH%;%PATH% +SET PATH=%O3DE_BIN_PATH%;%DCCSIG_PATH%;%PATH% ::ENDLOCAL diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Maya.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat similarity index 87% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Maya.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat index cceaa80b75..5e3c600124 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Maya.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Maya.bat @@ -27,14 +27,12 @@ IF "%DCCSI_PY_VERSION_MINOR%"=="" (set DCCSI_PY_VERSION_MINOR=7) IF "%DCCSI_PY_VERSION_RELEASE%"=="" (set DCCSI_PY_VERSION_RELEASE=11) :: Default Maya Version -IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=%MAYA_VERSION%) +IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020) :: Initialize env CALL %~dp0\Env_Core.bat CALL %~dp0\Env_Python.bat -::SETLOCAL ENABLEDELAYEDEXPANSION - echo. echo _____________________________________________________________________ echo. @@ -48,14 +46,14 @@ echo DCCSI_PY_VERSION_RELEASE = %DCCSI_PY_VERSION_RELEASE% echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% :::: Set Maya native project acess to this project -IF "%MAYA_PROJECT%"=="" (set MAYA_PROJECT=%LY_PROJECT%) +IF "%MAYA_PROJECT%"=="" (set MAYA_PROJECT=%O3DE_PROJECT%) echo MAYA_PROJECT = %MAYA_PROJECT% :: maya sdk path -set DCCSI_SDK_MAYA_PATH=%DCCSI_SDK_PATH%\Maya -echo DCCSI_SDK_MAYA_PATH = %DCCSI_SDK_MAYA_PATH% +set DCCSI_TOOLS_MAYA_PATH=%DCCSI_TOOLS_PATH%\DCC\Maya +echo DCCSI_TOOLS_MAYA_PATH = %DCCSI_TOOLS_MAYA_PATH% -set MAYA_MODULE_PATH=%DCCSI_SDK_MAYA_PATH%;%MAYA_MODULE_PATH% +set MAYA_MODULE_PATH=%DCCSI_TOOLS_MAYA_PATH%;%MAYA_MODULE_PATH% echo MAYA_MODULE_PATH = %MAYA_MODULE_PATH% :: Maya File Paths, etc @@ -93,36 +91,36 @@ echo DCCSI_PY_MAYA = %DCCSI_PY_MAYA% SET PATH=%MAYA_BIN_PATH%;%PATH% :: Local DCCsi Maya plugins access (ours) -set DCCSI_MAYA_PLUG_IN_PATH=%DCCSI_SDK_MAYA_PATH%\plugins +set DCCSI_MAYA_PLUG_IN_PATH=%DCCSI_TOOLS_MAYA_PATH%\plugins :: also attached to maya's built-it env var set MAYA_PLUG_IN_PATH=%DCCSI_MAYA_PLUG_IN_PATH%;MAYA_PLUG_IN_PATH echo DCCSI_MAYA_PLUG_IN_PATH = %DCCSI_MAYA_PLUG_IN_PATH% :: Local DCCsi Maya shelves (ours) -set DCCSI_MAYA_SHELF_PATH=%DCCSI_SDK_MAYA_PATH%\Prefs\Shelves +set DCCSI_MAYA_SHELF_PATH=%DCCSI_TOOLS_MAYA_PATH%\Prefs\Shelves set MAYA_SHELF_PATH=%DCCSI_MAYA_SHELF_PATH% echo DCCSI_MAYA_SHELF_PATH = %DCCSI_MAYA_SHELF_PATH% :: Local DCCsi Maya icons path (ours) -set DCCSI_MAYA_XBMLANGPATH=%DCCSI_SDK_MAYA_PATH%\Prefs\icons +set DCCSI_MAYA_XBMLANGPATH=%DCCSI_TOOLS_MAYA_PATH%\Prefs\icons :: also attached to maya's built-it env var set XBMLANGPATH=%DCCSI_MAYA_XBMLANGPATH%;%XBMLANGPATH% echo DCCSI_MAYA_XBMLANGPATH = %DCCSI_MAYA_XBMLANGPATH% :: Local DCCsi Maya Mel scripts (ours) -set DCCSI_MAYA_SCRIPT_MEL_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Mel +set DCCSI_MAYA_SCRIPT_MEL_PATH=%DCCSI_TOOLS_MAYA_PATH%\Scripts\Mel :: also attached to maya's built-it env var set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_MEL_PATH%;%MAYA_SCRIPT_PATH% echo DCCSI_MAYA_SCRIPT_MEL_PATH = %DCCSI_MAYA_SCRIPT_MEL_PATH% :: Local DCCsi Maya Python scripts (ours) -set DCCSI_MAYA_SCRIPT_PY_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts\Python +set DCCSI_MAYA_SCRIPT_PY_PATH=%DCCSI_TOOLS_MAYA_PATH%\Scripts\Python :: also attached to maya's built-it env var set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PY_PATH%;%MAYA_SCRIPT_PATH% echo DCCSI_MAYA_SCRIPT_PY_PATH = %DCCSI_MAYA_SCRIPT_PY_PATH% :: DCCsi Maya boostrap, userSetup.py access (ours) -set DCCSI_MAYA_SCRIPT_PATH=%DCCSI_SDK_MAYA_PATH%\Scripts +set DCCSI_MAYA_SCRIPT_PATH=%DCCSI_TOOLS_MAYA_PATH%\Scripts :: also attached to maya's built-it env var set MAYA_SCRIPT_PATH=%DCCSI_MAYA_SCRIPT_PATH%;%MAYA_SCRIPT_PATH% echo DCCSI_MAYA_SCRIPT_PATH = %DCCSI_MAYA_SCRIPT_PATH% diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_PyCharm.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_PyCharm.bat similarity index 95% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_PyCharm.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_PyCharm.bat index 67f2c3d81e..7be9154e6d 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_PyCharm.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_PyCharm.bat @@ -34,7 +34,7 @@ CALL %~dp0\Env_Python.bat CALL %~dp0\Env_Qt.bat :: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python +set DCCSI_PY_IDE = %O3DE_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python echo DCCSI_PY_IDE = %DCCSI_PY_IDE% :: ide and debugger plug diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Python.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Python.bat similarity index 76% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Python.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Python.bat index 196d7b09bb..388ab531df 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Python.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Python.bat @@ -19,8 +19,6 @@ PUSHD %~dp0 CALL %~dp0\Env_Core.bat -::SETLOCAL ENABLEDELAYEDEXPANSION - echo. echo _____________________________________________________________________ echo. @@ -56,32 +54,29 @@ echo DCCSI_PYTHON_LIB_PATH = %DCCSI_PYTHON_LIB_PATH% SET PATH=%DCCSI_PYTHON_LIB_PATH%;%PATH% :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python -echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL% +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\python +echo O3DE_PYTHON_INSTALL = %O3DE_PYTHON_INSTALL% :: location for O3DE python 3.7 location -set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.cmd +:: Note, many DCC tools (like Maya) include thier own python interpretter +:: Some DCC apps may not operate correctly if PYTHONHOME is set (this is definitely the case with Maya) +:: Be aware the python.cmd below does set PYTHONHOME +set DCCSI_PY_BASE=%O3DE_PYTHON_INSTALL%\python.cmd echo DCCSI_PY_BASE = %DCCSI_PY_BASE% -:: ide and debugger plug -set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE% +CALL %O3DE_PYTHON_INSTALL%\get_python_path.bat -:: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE=%DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python +:: Some IDEs like Wing, may in some cases need acess directly to the exe to operate correctly +IF "%DCCSI_PY_IDE%"=="" (set DCCSI_PY_IDE=%O3DE_PYTHONHOME%\python.exe) echo DCCSI_PY_IDE = %DCCSI_PY_IDE% -set DCCSI_PY_IDE_PACKAGES=%DCCSI_PY_IDE%\Lib\site-packages -echo DCCSI_PY_IDE_PACKAGES = %DCCSI_PY_IDE_PACKAGES% - :: add to the PATH -SET PATH=%DCCSI_PYTHON_INSTALL%;%DCCSI_PY_IDE%;%DCCSI_PY_IDE_PACKAGES%;%PATH% +SET PATH=%O3DE_PYTHON_INSTALL%;%O3DE_PYTHONHOME%;%DCCSI_PY_IDE%;%PATH% :: add all python related paths to PYTHONPATH for package imports -set PYTHONPATH=%DCCSIG_PATH%;%DCCSI_PYTHON_LIB_PATH%;%LY_BUILD_PATH%;%PYTHONPATH% +set PYTHONPATH=%DCCSIG_PATH%;%DCCSI_PYTHON_LIB_PATH%;%O3DE_BUILD_PATH%;%PYTHONPATH% echo PYTHONPATH = %PYTHONPATH% -::ENDLOCAL - :: Set flag so we don't initialize dccsi environment twice SET DCCSI_ENV_PY_INIT=1 GOTO END_OF_FILE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Qt.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Qt.bat similarity index 85% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Qt.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Qt.bat index 2ad49b4416..6202ead2f2 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Qt.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Qt.bat @@ -34,23 +34,23 @@ echo. :: set up Qt/Pyside paths :: set up PySide2/Shiboken -set QTFORPYTHON_PATH=%LY_DEV%\Gems\QtForPython\3rdParty\pyside2\windows\release +set QTFORPYTHON_PATH=%O3DE_DEV%\Gems\QtForPython\3rdParty\pyside2\windows\release echo QTFORPYTHON_PATH = %QTFORPYTHON_PATH% :: add to the PATH SET PATH=%QTFORPYTHON_PATH%;%PATH% SET PYTHONPATH=%QTFORPYTHON_PATH%;%PYTHONPATH% -set QT_PLUGIN_PATH=%LY_BUILD_PATH%\bin\profile\EditorPlugins +set QT_PLUGIN_PATH=%O3DE_BUILD_PATH%\bin\profile\EditorPlugins echo QT_PLUGIN_PATH = %QT_PLUGIN_PATH% :: add to the PATH SET PATH=%QT_PLUGIN_PATH%;%PATH% SET PYTHONPATH=%QT_PLUGIN_PATH%;%PYTHONPATH% -set LY_BIN_PATH=%LY_BUILD_PATH%\bin\profile -echo LY_BIN_PATH = %LY_BIN_PATH% -SET PATH=%LY_BIN_PATH%;%PATH% +set O3DE_BIN_PATH=%O3DE_BUILD_PATH%\bin\profile +echo O3DE_BIN_PATH = %O3DE_BIN_PATH% +SET PATH=%O3DE_BIN_PATH%;%PATH% ::ENDLOCAL diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Substance.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Substance.bat similarity index 92% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Substance.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Substance.bat index 3c92b306ca..d44ed985da 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_Substance.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_Substance.bat @@ -31,7 +31,7 @@ echo. : Substance Designer :: maya sdk path -set DCCSI_SUBSTANCE_PATH=%DCCSI_SDK_PATH%\Substance +set DCCSI_SUBSTANCE_PATH=%DCCSI_TOOLS_PATH%\Substance echo DCCSI_SUBSTANCE_PATH = %DCCSI_SUBSTANCE_PATH% :: https://docs.substance3d.com/sddoc/project-preferences-107118596.html#ProjectPreferences-ConfigurationFile :: Path to .exe, "C:\Program Files\Allegorithmic\Substance Designer\Substance Designer.exe" @@ -39,7 +39,7 @@ set SUBSTANCE_PATH="%ProgramFiles%\Allegorithmic\Substance Designer" echo SUBSTANCE_PATH = %SUBSTANCE_PATH% :: default config -set SUBSTANCE_CFG_PATH=%LY_PROJECT_PATH%\DCCsi_default.sbscfg +set SUBSTANCE_CFG_PATH=%O3DE_PROJECT_PATH%\DCCsi_default.sbscfg echo SUBSTANCE_CFG_PATH = %SUBSTANCE_CFG_PATH% ::ENDLOCAL diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_VScode.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_VScode.bat similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_VScode.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_VScode.bat diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_WingIDE.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_WingIDE.bat similarity index 78% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_WingIDE.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_WingIDE.bat index 56209ca5aa..a8c3b3d073 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Env_WingIDE.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Env_WingIDE.bat @@ -27,18 +27,9 @@ CALL %~dp0\Env_Core.bat CALL %~dp0\Env_Python.bat CALL %~dp0\Env_Qt.bat -:: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python -echo DCCSI_PY_IDE = %DCCSI_PY_IDE% - -:: ide and debugger plug -set DCCSI_PY_DEFAULT=%DCCSI_PY_IDE%\python.exe - :: put project env variables/paths here set WINGHOME=%PROGRAMFILES(X86)%\Wing Pro %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% -SET WING_PROJ=%DCCSIG_PATH%\Solutions\.wing\DCCsi_%DCCSI_WING_VERSION_MAJOR%x.wpr - -::SETLOCAL ENABLEDELAYEDEXPANSION +SET WING_PROJ=%DCCSIG_PATH%\Tools\Dev\Windows\Solutions\.wing\DCCsi_%DCCSI_WING_VERSION_MAJOR%x.wpr echo. echo _____________________________________________________________________ @@ -55,8 +46,6 @@ echo WING_PROJ = %WING_PROJ% :: add to the PATH SET PATH=%WINGHOME%;%PATH% -::ENDLOCAL - :: Set flag so we don't initialize dccsi environment twice SET DCCSI_ENV_WINGIDE_INIT=1 GOTO END_OF_FILE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Env_Cmd.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Env_Cmd.bat similarity index 97% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Env_Cmd.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Env_Cmd.bat index a8476ccc22..5b76de1401 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Env_Cmd.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Env_Cmd.bat @@ -40,7 +40,7 @@ CALL %~dp0\Env_WingIDE.bat IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% :: Create command prompt with environment CALL %windir%\system32\cmd.exe diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_MayaPy_PyCharmPro.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_MayaPy_PyCharmPro.bat similarity index 91% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_MayaPy_PyCharmPro.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_MayaPy_PyCharmPro.bat index d98b8d81e7..e451050ab8 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_MayaPy_PyCharmPro.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_MayaPy_PyCharmPro.bat @@ -56,14 +56,14 @@ echo ~ MayaPy.exe (default python interpreter) echo _____________________________________________________________________ echo. -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python -echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL% +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\Python +echo O3DE_PYTHON_INSTALL = %O3DE_PYTHON_INSTALL% :: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python +set DCCSI_PY_IDE = %O3DE_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python echo DCCSI_PY_IDE = %DCCSI_PY_IDE% :: ide and debugger plug @@ -80,7 +80,7 @@ IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat echo. :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% IF EXIST "%PYCHARM_HOME%\bin\pycharm64.exe" ( start "" "%PYCHARM_HOME%\bin\pycharm64.exe" "%PYCHARM_PROJ%" diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Maya_2020.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Maya_2020.bat similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Maya_2020.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Maya_2020.bat index 2c4e1cc941..ee2ea5bf1f 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Maya_2020.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Maya_2020.bat @@ -50,7 +50,7 @@ echo MAYA_LOCATION = %MAYA_LOCATION% echo MAYA_BIN_PATH = %MAYA_BIN_PATH% :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% :: Default to the right version of Maya if we can detect it... and launch IF EXIST "%MAYA_BIN_PATH%\maya.exe" ( diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_PyCharmPro.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_PyCharmPro.bat similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_PyCharmPro.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_PyCharmPro.bat diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_PyMin_Cmd.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_PyMin_Cmd.bat similarity index 97% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_PyMin_Cmd.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_PyMin_Cmd.bat index d001d8a75e..193cb40de8 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_PyMin_Cmd.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_PyMin_Cmd.bat @@ -38,7 +38,7 @@ echo _____________________________________________________________________ echo. :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% :: Create command prompt with environment CALL %windir%\system32\cmd.exe diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Qt_PyMin_Cmd.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Qt_PyMin_Cmd.bat similarity index 93% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Qt_PyMin_Cmd.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Qt_PyMin_Cmd.bat index d50989004b..75d1ff8cc4 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_Qt_PyMin_Cmd.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_Qt_PyMin_Cmd.bat @@ -10,7 +10,7 @@ REM :: Set up and run LY Python CMD prompt :: Sets up the DccScriptingInterface_Env, -:: Puts you in the CMD within the LY_DEV environment +:: Puts you in the CMD within the O3DE_DEV environment :: Set up window TITLE O3DE DCC Scripting Interface Py Qt Cmd @@ -39,7 +39,7 @@ echo _____________________________________________________________________ echo. :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% :: Create command prompt with environment CALL %windir%\system32\cmd.exe diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_VScode.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_VScode.bat similarity index 92% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_VScode.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_VScode.bat index 120584ed6a..2dc5b06032 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_VScode.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_VScode.bat @@ -61,14 +61,14 @@ echo ~ Launching DCCsi Project in VScode echo _____________________________________________________________________ echo. -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python -echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL% +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\Python +echo O3DE_PYTHON_INSTALL = %O3DE_PYTHON_INSTALL% :: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python +set DCCSI_PY_IDE = %O3DE_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python echo DCCSI_PY_IDE = %DCCSI_PY_IDE% :: ide and debugger plug diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_WingIDE-7-1.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_WingIDE-7-1.bat similarity index 74% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_WingIDE-7-1.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_WingIDE-7-1.bat index c933670bab..56e278da08 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_WingIDE-7-1.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_WingIDE-7-1.bat @@ -20,6 +20,9 @@ COLOR 8E cd %~dp0 PUSHD %~dp0 +:: if the user has set up a custom env call it +IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat + :: Constant Vars (Global) :: global debug (propogates) IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=True) @@ -51,35 +54,20 @@ echo. echo _____________________________________________________________________ echo. echo ~ WingIDE Version %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% -echo ~ Launching O3DE %LY_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ... +echo ~ Launching O3DE %O3DE_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ... echo _____________________________________________________________________ echo. -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python -echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL% - -:: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python -echo DCCSI_PY_IDE = %DCCSI_PY_IDE% - -:: ide and debugger plug -set DCCSI_PY_BASE=%DCCSI_PY_IDE%\python.exe -echo DCCSI_PY_BASE = %DCCSI_PY_BASE% - -:: ide and debugger plug -set DCCSI_PY_DEFAULT=%DCCSI_PY_BASE% -echo DCCSI_PY_DEFAULT = %DCCSI_PY_DEFAULT% - -:: if the user has set up a custom env call it -IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\Python +echo O3DE_PYTHON_INSTALL = %O3DE_PYTHON_INSTALL% echo. :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% IF EXIST "%WINGHOME%\bin\wing.exe" ( start "" "%WINGHOME%\bin\wing.exe" "%WING_PROJ%" diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayaPy_2020.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayaPy_2020.bat similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayaPy_2020.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayaPy_2020.bat index f07ec093cb..33b8bc6392 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayaPy_2020.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayaPy_2020.bat @@ -48,7 +48,7 @@ echo MAYA_LOCATION = %MAYA_LOCATION% echo MAYA_BIN_PATH = %MAYA_BIN_PATH% :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% SETLOCAL ENABLEDELAYEDEXPANSION diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayapy_WingIDE-7-1.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayapy_WingIDE-7-1.bat similarity index 87% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayapy_WingIDE-7-1.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayapy_WingIDE-7-1.bat index 631250d06b..ca5b175c39 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_mayapy_WingIDE-7-1.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_mayapy_WingIDE-7-1.bat @@ -50,19 +50,19 @@ echo. echo _____________________________________________________________________ echo. echo ~ WingIDE Version %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% -echo ~ Launching O3DE %LY_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ... +echo ~ Launching O3DE %O3DE_PROJECT% project in WingIDE %DCCSI_WING_VERSION_MAJOR%.%DCCSI_WING_VERSION_MINOR% ... echo ~ MayaPy.exe (default python interpreter) echo _____________________________________________________________________ echo. -echo LY_DEV = %LY_DEV% +echo O3DE_DEV = %O3DE_DEV% :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python -echo DCCSI_PYTHON_INSTALL = %DCCSI_PYTHON_INSTALL% +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\Python +echo O3DE_PYTHON_INSTALL = %O3DE_PYTHON_INSTALL% :: Wing and other IDEs probably prefer access directly to the python.exe -set DCCSI_PY_IDE = %DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python +set DCCSI_PY_IDE = %O3DE_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python echo DCCSI_PY_IDE = %DCCSI_PY_IDE% :: ide and debugger plug @@ -79,7 +79,7 @@ IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat echo. :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% IF EXIST "%WINGHOME%\bin\wing.exe" ( start "" "%WINGHOME%\bin\wing.exe" "%WING_PROJ%" diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_pyBASE.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_pyBASE.bat similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_pyBASE.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_pyBASE.bat diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_pyBASE_Cmd.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_pyBASE_Cmd.bat similarity index 97% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_pyBASE_Cmd.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_pyBASE_Cmd.bat index 9f8950f59f..e1b075e065 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Launch_pyBASE_Cmd.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Launch_pyBASE_Cmd.bat @@ -33,7 +33,7 @@ CALL %~dp0\Env_Maya.bat IF EXIST "%~dp0Env_Dev.bat" CALL %~dp0Env_Dev.bat :: Change to root dir -CD /D %LY_PROJECT_PATH% +CD /D %O3DE_PROJECT_PATH% :: Create command prompt with environment CALL %windir%\system32\cmd.exe diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/README.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/README.txt new file mode 100644 index 0000000000..e5b7946276 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/README.txt @@ -0,0 +1,78 @@ +""" +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 +""" +# ------------------------------------------------------------------------- + +DccScriptingInterface (DCCsi) is a framework for O3DE extensions, for example: +- Lightweight python integrations with DCC tools like Maya +- O3DE configuration, customization and extensions of tools +- Standalone PySide2/Qt tools +- ^ These might utilize a mix of O3DE and DDC python APIs + +The DccScriptingInterface\config.py, procedurally provides a synthetic env context. +This env is a data-driven approach to configuring layered and managed env settings. + +This env provides the hooks for DDC apps and/or standalone tools, +to configure acess to O3DE code (for boostrapping), safely retreive known paths, set/get developer flags, etc. + +DccScriptingInterface\Tools\Dev\Windows\ + +This is a .bat file based version of the default env context for development on windows. +This is what we use to boot the default env context such that it is available, when launching a development tool such as a IDE. + +This allows a developer to troubleshoot/debug code, like config.py + +Other tools, can use config.py to stand up the env context. + +What is in this folder ... + +Core env modules +--------------------- +Env_Core.bat : core access to O3DE and DCCsi +Env_Python.bat : access to O3DE python and general py configuration +Env_Qt.bat : access to O3DE Qt .dll files and PySide2 + +DCC add on envars +--------------------- +Env_Maya.bat : configures Maya with code O3DE/DCCsi access +Env_Substance.bat : Configures Substance Designer + +IDE env +--------------------- +Env_WingIDE.bat : configures WingIDE for DCCsi development +Env_VScode.bat : configures VScode for DCCsi development +Env_PyCharm.bat : configures PyCharm for DCCsi development + +Launchers +--------------------- +Launch_env_Cmd.bat : Starts a cmd with entire managed env context + : ^ allows use to validate env + : ^ display all default ENVAR plugs + : ^ allows user to test O3DE python + scripts from cmd +Launch_PyMin_Cmd.bat : Starts minimal cmd with O3DE python access only + : ^ for instance, test Dccsi\config.py like this: + : {DCCsi prommpt}>python config.py +Launch_Maya_2020.bat : Starts Maya2020 within managed env context +Launch_WindIDE-7-1.bat : Starts WingIDE within managed env context + +--------------------- +Instructions: How to test the synthetic environment and settings externally + +1. Run the cmd: Launch_PyMin_Cmd.bat + + C:\Depot\o3de-engine\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface> + +2. Run command:>python config.py -dm=True -py=True -qt=True + +What this does? +- runs the O3DE python exe +- starts the config.py which begins to procedurally create synthetic/dynamic environment (hooks) +- ^ this starts with DCCsi hooks +- enters 'dev mode'(-dm) and attempts to attach debugger (Wing IDE only for now, others planned) +- enables additional O3DE python hooks and code access +- ^ great for standalone tools, but you don't want that functionality to interfer with other DCC tools python environments (like Maya!) +- enables access to O3DE Qt .dlls and PySide2 python package support(-qt) +- ^ great for standalone PySide2 which can operate outside of the O3DE editor \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Setuo_copy_oiio.bat b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Setuo_copy_oiio.bat similarity index 76% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Setuo_copy_oiio.bat rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Setuo_copy_oiio.bat index 590e634d54..18a58e1371 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Launchers/Windows/Setuo_copy_oiio.bat +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Setuo_copy_oiio.bat @@ -13,12 +13,12 @@ cd %~dp0 PUSHD %~dp0 :: This maps up to the \Dev folder -set LY_DEV=..\..\..\..\..\.. +set O3DE_DEV=..\..\..\..\..\.. :: shared location for default O3DE python location -set DCCSI_PYTHON_INSTALL=%LY_DEV%\Python +set O3DE_PYTHON_INSTALL=%O3DE_DEV%\Python -set PY_SITE=%DCCSI_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python\Lib\site-packages +set PY_SITE=%O3DE_PYTHON_INSTALL%\runtime\python-3.7.10-rev2-windows\python\Lib\site-packages set PACKAGE_LOC=C:\Depot\3rdParty\packages\openimageio-2.1.16.0-rev1-windows\OpenImageIO\2.1.16.0\win_x64\bin diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.dev/readme.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.dev/readme.txt similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.dev/readme.txt rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.dev/readme.txt diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.dev/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.dev/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.gitignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.gitignore similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.gitignore rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.gitignore diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/.gitignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/.gitignore similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/.gitignore rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/.gitignore diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/.p4ignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/.p4ignore similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/.p4ignore rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/.p4ignore diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/DccScriptingInterface.iml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/DccScriptingInterface.iml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/DccScriptingInterface.iml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/DccScriptingInterface.iml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/encodings.xml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/encodings.xml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/encodings.xml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/encodings.xml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/main.py similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/main.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/main.py diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/misc.xml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/misc.xml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/misc.xml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/misc.xml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/modules.xml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/modules.xml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/modules.xml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/modules.xml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/vcs.xml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/vcs.xml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/vcs.xml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/vcs.xml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/webResources.xml b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/webResources.xml similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.idea/webResources.xml rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.idea/webResources.xml diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/dccsi.code-workspace b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/dccsi.code-workspace similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/dccsi.code-workspace rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/dccsi.code-workspace diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/launch.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/launch.json similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/launch.json rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/launch.json diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/settings.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/settings.json similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.vscode/settings.json rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.vscode/settings.json diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.wing/.gitignore b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.wing/.gitignore similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.wing/.gitignore rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.wing/.gitignore diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.wing/DCCsi_7x.wpr b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.wing/DCCsi_7x.wpr similarity index 57% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.wing/DCCsi_7x.wpr rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.wing/DCCsi_7x.wpr index aa7a8c4bd9..18af1298db 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/.wing/DCCsi_7x.wpr +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/.wing/DCCsi_7x.wpr @@ -5,7 +5,22 @@ ################################################################## [project attributes] debug.launch-configs = (2, - {'launch-GeaM41WYMGA1sEfm': ({'shared': True}, + {'launch-0L6se5pxC9AWvpY0': ({'shared': True}, + {'buildcmd': ('project', + None), + 'env': ('project', + [u'']), + 'name': 'DCCSI_PY_IDE', + 'pyexec': ('custom', + u'${DCCSI_PY_IDE}'), + 'pypath': ('project', + []), + 'pyrunargs': ('project', + '-u'), + 'runargs': u'', + 'rundir': ('project', + u'')}), + 'launch-GeaM41WYMGA1sEfm': ({'shared': True}, {'buildcmd': ('project', None), 'env': ('custom', @@ -33,52 +48,30 @@ debug.launch-configs = (2, 'pyrunargs': ('project', '-u'), 'runargs': u'', - 'rundir': ('project', - u'')}), - 'launch-oobMrvXFf1SwtYBg': ({'shared': True}, - {'buildcmd': ('project', - None), - 'env': ('custom', - [u'']), - 'name': u'DCCSI_PY_DEFAULT', - 'pyexec': ('custom', - u'${DCCSI_PY_DEFAULT}'), - 'pypath': ('project', - []), - 'pyrunargs': ('project', - '-u'), - 'runargs': u'', 'rundir': ('project', u'')})}) -proj.directory-list = [{'dirloc': loc('../..'), +proj.directory-list = [{'dirloc': loc('../../../../..'), 'excludes': (), 'filter': u'*', - 'include_hidden': True, + 'include_hidden': False, + 'recursive': True, + 'watch_for_changes': True}, + {'dirloc': loc('../../../../../../../../Atom/Feature/Common/Editor/Scripts/ColorGrading'), + 'excludes': (), + 'filter': u'*', + 'include_hidden': False, + 'recursive': True, + 'watch_for_changes': True}, + {'dirloc': loc('../../../../../../../../../python'), + 'excludes': (), + 'filter': u'*', + 'include_hidden': False, 'recursive': True, 'watch_for_changes': True}] proj.file-type = 'shared' -proj.home-dir = loc('../..') -proj.launch-config = {loc('../../SDK/Atom/Scripts/Python/DCC_Materials/maya_materials_export.py'): ('c'\ - 'ustom', +proj.launch-config = {loc('../../../../../azpy/core/py2/utils.py'): ('custom', (u'', - 'launch-GeaM41WYMGA1sEfm')), - loc('../../SDK/Maya/Scripts/Python/legacy_asset_converter/main.py'): ('c'\ - 'ustom', + 'launch-0L6se5pxC9AWvpY0')), + loc('../../../../../azpy/core/py3/utils.py'): ('custom', (u'', - 'launch-WUN9lgYK6qYU7qE9')), - loc('../../azpy/__init__.py'): ('custom', - (u'', - 'launch-oobMrvXFf1SwtYBg')), - loc('../../azpy/constants.py'): ('custom', - (u'', - 'launch-GeaM41WYMGA1sEfm')), - loc('../../azpy/env_base.py'): ('project', - (u'', - 'launch-GeaM41WYMGA1sEfm')), - loc('../../azpy/maya/callbacks/node_message_callback_handler.py'): ('c'\ - 'ustom', - (u'', - 'launch-GeaM41WYMGA1sEfm')), - loc('../../config.py'): ('custom', - (u'', - 'launch-GeaM41WYMGA1sEfm'))} + 'launch-0L6se5pxC9AWvpY0'))} diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/readme.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/readme.txt similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Solutions/readme.txt rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Dev/Windows/Solutions/readme.txt diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/README.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/README.txt new file mode 100644 index 0000000000..05aa948943 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/README.txt @@ -0,0 +1,9 @@ +""" +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 +""" +# ------------------------------------------------------------------------- + +This folder resprents a Mock Tool - currently just a scaffold (not implemented) \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/Resources/resource.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/Resources/resource.txt new file mode 100644 index 0000000000..30df2e0eb9 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/Resources/resource.txt @@ -0,0 +1,9 @@ +""" +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 +""" +# ------------------------------------------------------------------------- + +This folder is for tool resources - this file is a resource stub. \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/max_materials.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/config.py old mode 100755 new mode 100644 similarity index 51% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/max_materials.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/config.py index a67c39910e..1dc43d8485 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/SDK/PythonTools/DCC_Material_Converter/max_materials.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/config.py @@ -1,18 +1,12 @@ +# coding:utf-8 +#!/usr/bin/python +# # 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 MaxPlus -import sys - - -def get_material_information(): - for mesh_object in MaxPlus.Core.GetRootNode().Children: - print('Object---> {}'.format(mesh_object)) - - -get_material_information() +print('Not Implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/main.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/main.py new file mode 100644 index 0000000000..1dc43d8485 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/main.py @@ -0,0 +1,12 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- + +print('Not Implemented') \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/settings.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/settings.json new file mode 100644 index 0000000000..d58fc7e10c --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/MockTool/settings.json @@ -0,0 +1,17 @@ +{ + "default": { + "DCCSI_GDEBUG": true, + "SPDX-LICENSE-IDENTIFIER": "Apache-2.0 OR MIT", + "COPYWRITE":"Copyright (c) Contributors to the Open 3D Engine Project.", + "COPYWRITE_MSG":"For complete copyright and license terms please see the LICENSE at the root of this distribution." + }, + "development": { + "DCCSI_GDEBUG": true, + "TEST_RULE": "/dccsi_with_json", + "MESSAGE": "The O3DE DCCsi manages dynamic configuration and settings with dynaconf", + "DYNACONF_HELP": "https://dynaconf.readthedocs.io/" + }, + "production": { + "DCCSI_GDEBUG": false + } +} \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/AA_Gun_01_ddna.tif b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/AA_Gun_01_ddna.tif new file mode 100644 index 0000000000..0cefbe9c25 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/AA_Gun_01_ddna.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c34bbeafae5c5b62d3571ac9eee295fff12ac0744f81375f53a3faf2c5095e4 +size 286400 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/test_oiio.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/test_oiio.py new file mode 100644 index 0000000000..b33fa8ef56 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/Tests/OpenImageIO/test_oiio.py @@ -0,0 +1,66 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- + +import OpenImageIO as oiio + +def convert(src): + try: + rgba = oiio.ImageBuf(src) + spec = get_image_spec(rgba) + + # Normal output ------> + rgb = oiio.ImageBufAlgo.channels(rgba, (0, 1, 2)) + normal_output = src.replace('ddna', 'Normal') + + # Roughness output ------> + alpha = oiio.ImageBufAlgo.channels(rgba, (3,)) + roughness = oiio.ImageBufAlgo.invert(alpha) + roughness_output = normal_output.replace('Normal', 'Roughness') + + write_image(rgba, normal_output, spec['format']) + write_image(roughness, roughness_output, spec['format']) + # logging.info('Output Normal Map: {}'.format(normal_output)) + # logging.info('Output Roughness Map: {}'.format(roughness_output)) + + except Exception as e: + logging.info('OIIO error in image conversion: {}'.format(e)) + return None + +def get_image_spec(target_image): + spec = target_image.spec() + info = {'resolution': (spec.width, spec.height, spec.x, spec.y), 'channels': spec.channelnames, + 'format': str(spec.format)} + if spec.channelformats : + info['channelformats'] = str(spec.channelformats) + info['alpha channel'] = str(spec.alpha_channel) + info['z channel'] = str(spec.z_channel) + info['deep'] = str(spec.deep) + for i in range(len(spec.extra_attribs)): + if type(spec.extra_attribs[i].value) == str: + info[spec.extra_attribs[i].name] = spec.extra_attribs[i].value + else: + info[spec.extra_attribs[i].name] = spec.extra_attribs[i].value + return info + +def write_image(image, filename, image_format): + if not image.has_error: + image.set_write_format(image_format) + image.write(filename) + if image.has_error: + print("Error writing", filename, ":", image.geterror()) + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + # run a test + convert('AA_Gun_01_ddna.tif') + # validate() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/stub b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Python/stub new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Resources/Atom/StandardPBR_AllProperties.material b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Resources/Atom/StandardPBR_AllProperties.material new file mode 100644 index 0000000000..7d38d1a1a3 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/Resources/Atom/StandardPBR_AllProperties.material @@ -0,0 +1,47 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "general": { + "texcoord": 0 + }, + "baseColor": { + "colorLinear": [ 1.0, 1.0, 1.0 ], + "factor": 1.0, + "useTexture": true, + "textureMap": "EngineAssets/TextureMsg/DefaultNoUVs.tif" + }, + "metallic": { + "factor": 0.0, + "useTexture": false, + "textureMap": "" + }, + "roughness": { + "factor": 1.0, + "useTexture": true, + "textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_spec.tif" + }, + "specularF0": { + "factor": 0.5, + "useTexture": false, + "textureMap": "" + }, + "normal": { + "factor": 1.0, + "useTexture": true, + "textureMap": "EngineAssets/TextureMsg/DefaultNoUVs_ddn.tif" + }, + "opacity": { + "doubleSided": false, + "factor": 1.0, + "cutoutAlpha": false, + "cutoutThreshold": 0.5, + "useBaseColorTextureAlpha": false, + "useTexture": false, + "textureMap": "" + } + } +} + diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/start_service.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/start_service.py new file mode 100644 index 0000000000..ac9d02cf5d --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Tools/start_service.py @@ -0,0 +1,21 @@ +""" +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 +""" +# ------------------------------------------------------------------------- +"""DCCsi Tool and Application service launcher""" + +def start_service(): + """Not Implemented""" + print('DCCsi.Tools.DCC.start_service() not implemented') + return None + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """Run this file as main""" + + start_service() \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/__init__.py index 0577c50474..958508c227 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/__init__.py @@ -26,26 +26,18 @@ import logging as _logging # ------------------------------------------------------------------------- -_ORG_TAG = 'Amazon_Lumberyard' -_APP_TAG = 'DCCsi' -_TOOL_TAG = 'azpy' -_TYPE_TAG = 'module' +# global scope +_PACKAGENAME = 'azpy' -_PACKAGENAME = _TOOL_TAG +__all__ = ['config_utils', + 'constants', + 'env_bool', + 'return_stub', + 'core', + 'dcc', + 'dev', + 'test'] -__all__ = ['config_utils', 'render', - 'constants', 'return_stub', 'synthetic_env', - 'env_base', 'env_bool', 'test', 'dev', - 'lumberyard', 'marmoset'] # 'blender', 'maya', 'substance', 'houdini'] -# ------------------------------------------------------------------------- - - -# ------------------------------------------------------------------------- -# _ROOT_LOGGER = _logging.getLogger() # only use this if debugging -# https://stackoverflow.com/questions/56733085/how-to-know-the-current-file-path-after-being-frozen-into-an-executable-using-cx/56748839 -#os.chdir(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))) -# ------------------------------------------------------------------------- -# global space # we need to set up basic access to the DCCsi _MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen? _DCCSIG_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../..')) @@ -58,8 +50,11 @@ import azpy.env_bool as env_bool import azpy.constants as constants import azpy.config_utils as config_utils -_G_DEBUG = env_bool.env_bool(constants.ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool.env_bool(constants.ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool.env_bool(constants.ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_LOGLEVEL = int(env_bool.env_bool(constants.ENVAR_DCCSI_LOGLEVEL, int(20))) +if _DCCSI_GDEBUG: + _DCCSI_LOGLEVEL = int(10) # for py2.7 (Maya) we provide this, so we must assume some bootstrapping # has occured, see DccScriptingInterface\\config.py (_DCCSI_PYTHON_LIB_PATH) @@ -69,38 +64,44 @@ try: except: import pathlib2 as pathlib from pathlib import Path -if _G_DEBUG: - print('DCCsi debug breadcrumb, pathlib is: {}'.format(pathlib)) +if _DCCSI_GDEBUG: + print('[DCCsi][AZPY] DEBUG BREADCRUMB, pathlib is: {}'.format(pathlib)) +# ------------------------------------------------------------------------- -# to be continued... +# ------------------------------------------------------------------------- +# set up module logging +#for handler in _logging.root.handlers[:]: + #_logging.root.removeHandler(handler) +_logging.basicConfig(format=constants.FRMT_LOG_LONG, level=_DCCSI_LOGLEVEL) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- # get/set the project name -_LY_DEV = os.getenv(constants.ENVAR_LY_DEV, +_O3DE_DEV = Path(os.getenv(constants.ENVAR_O3DE_DEV, config_utils.get_stub_check_path(in_path=os.getcwd(), - check_stub='engine.json')) + check_stub='engine.json'))) +_LOGGER.debug('_O3DE_DEV" {}'.format(_O3DE_DEV.resolve())) + +_O3DE_PROJECT_PATH = Path(os.getenv(constants.ENVAR_O3DE_PROJECT_PATH, + config_utils.get_o3de_project_path())) +_LOGGER.debug('_O3DE_PROJECT_PATH" {}'.format(_O3DE_PROJECT_PATH.resolve())) # get/set the project name -_LY_PROJECT_NAME = os.getenv(constants.ENVAR_LY_PROJECT, - config_utils.get_current_project().name) +if _O3DE_PROJECT_PATH: + _O3DE_PROJECT = str(os.getenv(constants.ENVAR_O3DE_PROJECT, + _O3DE_PROJECT_PATH.name)) +else: + _O3DE_PROJECT='o3de' # project cache log dir path -_DCCSI_LOG_PATH = Path(os.getenv(constants.ENVAR_DCCSI_LOG_PATH, - Path(_LY_DEV, - _LY_PROJECT_NAME, - 'Cache', - 'pc', 'user', 'log', 'logs'))) - - -for handler in _logging.root.handlers[:]: - _logging.root.removeHandler(handler) - -# very basic root logger for early debugging, flip to while 1: -while 0: - _logging.basicConfig(level=_logging.DEBUG, - format=constants.FRMT_LOG_LONG, - datefmt='%m-%d %H:%M') - - _logging.debug('azpy.rootlogger> root logger set up for debugging') # root logger +from azpy.constants import TAG_DCCSI_NICKNAME +from azpy.constants import PATH_DCCSI_LOG_PATH +_DCCSI_LOG_PATH = Path(PATH_DCCSI_LOG_PATH.format(O3DE_PROJECT_PATH=_O3DE_PROJECT_PATH.resolve(), + TAG_DCCSI_NICKNAME=TAG_DCCSI_NICKNAME)) # ------------------------------------------------------------------------- @@ -145,14 +146,15 @@ if sys.version_info.major < 3: # ------------------------------------------------------------------------- def initialize_logger(name, log_to_file=False, - default_log_level=_logging.NOTSET): + default_log_level=_logging.NOTSET, + propogate=False): """Start a azpy logger""" _logger = _logging.getLogger(name) - _logger.propagate = False + _logger.propagate = propogate if not _logger.handlers: _log_level = int(os.getenv('DCCSI_LOGLEVEL', default_log_level)) - if _G_DEBUG: + if _DCCSI_GDEBUG: _log_level = int(10) # force when debugging print('_log_level: {}'.format(_log_level)) @@ -201,27 +203,21 @@ def initialize_logger(name, return _logger # ------------------------------------------------------------------------- + # ------------------------------------------------------------------------- -# set up logger with both console and file _logging -if _G_DEBUG: - _LOGGER = initialize_logger(_PACKAGENAME, log_to_file=True) -else: - _LOGGER = initialize_logger(_PACKAGENAME, log_to_file=False) - -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) - # some simple logger tests # evoke the filehandlers and test writting to the log file -if _G_DEBUG: +if _DCCSI_GDEBUG: _LOGGER.info('Forced Info! for {0}.'.format({_PACKAGENAME})) _LOGGER.error('Forced ERROR! for {0}.'.format({_PACKAGENAME})) # debug breadcrumbs to check this module and used paths _LOGGER.debug('MODULE_PATH: {}'.format(_MODULE_PATH)) -_LOGGER.debug('LY_DEV_PATH: {}'.format(_LY_DEV)) +_LOGGER.debug('O3DE_DEV_PATH: {}'.format(_O3DE_DEV)) _LOGGER.debug('DCCSI_PATH: {}'.format(_DCCSIG_PATH)) -_LOGGER.debug('LY_PROJECT_TAG: {}'.format(_LY_PROJECT_NAME)) +_LOGGER.debug('O3DE_PROJECT_TAG: {}'.format(_O3DE_PROJECT)) _LOGGER.debug('DCCSI_LOG_PATH: {}'.format(_DCCSI_LOG_PATH)) +# ------------------------------------------------------------------------- # ------------------------------------------------------------------------- @@ -258,9 +254,9 @@ del _LOGGER # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- if __name__ == '__main__': - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True - if _G_DEBUG: + if _DCCSI_GDEBUG: print(_DCCSIG_PATH) test_imports() diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/config_utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/config_utils.py index 2df85cbfaa..f6b71a7d97 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/config_utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/config_utils.py @@ -18,14 +18,31 @@ import logging as _logging # -------------------------------------------------------------------------- -_PACKAGENAME = 'azpy.config_utils' +# note: this module is called in other root modules +# must avoid cyclical imports +# global scope +# normally would pull the constant envar string +# but avoiding cyclical imports here FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)" -_logging.basicConfig(level=_logging.INFO, - format=FRMT_LOG_LONG, - datefmt='%m-%d %H:%M') -_LOGGER = _logging.getLogger(_PACKAGENAME) -_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) +from azpy.env_bool import env_bool +_DCCSI_GDEBUG = env_bool('DCCSI_GDEBUG', False) +_DCCSI_LOGLEVEL = env_bool('DCCSI_LOGLEVEL', False) +_DCCSI_LOGLEVEL = int(env_bool('DCCSI_LOGLEVEL', int(20))) +if _DCCSI_GDEBUG: + _DCCSI_LOGLEVEL = int(10) + +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'azpy.config_utils' + +# set up module logging +#for handler in _logging.root.handlers[:]: + #_logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_MODULENAME) +#_logging.basicConfig(format=FRMT_LOG_LONG, level=_DCCSI_LOGLEVEL) +_LOGGER.propagate = False +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) __all__ = ['get_os', 'return_stub', 'get_stub_check_path', 'get_dccsi_config', 'get_current_project'] @@ -34,13 +51,15 @@ __all__ = ['get_os', 'return_stub', 'get_stub_check_path', # ------------------------------------------------------------------------- # just a quick check to ensure what paths have code access -_G_DEBUG = False # enable for debug prints -if _G_DEBUG: +if _DCCSI_GDEBUG: known_paths = list() for p in sys.path: known_paths.append(p) _LOGGER.debug(known_paths) +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- # this import can fail in Maya 2020 (and earlier) stuck on py2.7 # wrapped in a try, to trap and providing messaging to help user correct try: @@ -76,6 +95,15 @@ def get_os(): # ------------------------------------------------------------------------- +# ------------------------------------------------------------------------- +from azpy.core import get_datadir +# there was a method here refactored out to add py2.7 support for Maya 2020 +#"DccScriptingInterface\azpy\core\py2\utils.py get_datadir()" +#"DccScriptingInterface\azpy\core\py3\utils.py get_datadir()" +# Warning: planning to deprecate py2 support +# ------------------------------------------------------------------------- + + # ------------------------------------------------------------------------- def return_stub_dir(stub_file='dccsi_stub'): _dir_to_last_file = None @@ -126,6 +154,30 @@ def get_stub_check_path(in_path=os.getcwd(), check_stub='engine.json'): # ------------------------------------------------------------------------- +# ------------------------------------------------------------------------- +def get_o3de_engine_root(check_stub='engine.json'): + # get the O3DE engine root folder + # if we are running within O3DE we can ensure which engine is running + _O3DE_DEV = None + try: + import azlmbr # this file will fail outside of O3DE + except ImportError as e: + # if that fails, we can search up + # search up to get \dev + _O3DE_DEV = get_stub_check_path(check_stub='engine.json') + # To Do: What if engine.json doesn't exist? + else: + # execute if no exception + # allow for external ENVAR override + from azpy.constants import ENVAR_O3DE_DEV + _O3DE_DEV = Path(os.getenv(ENVAR_O3DE_DEV, azlmbr.paths.engroot)) + finally: + # note: can't use fstrings as this module gets called with py2.7 in maya + _LOGGER.info('O3DE engine root: {}'.format(_O3DE_DEV.resolve())) + return _O3DE_DEV +# ------------------------------------------------------------------------- + + # ------------------------------------------------------------------------- # settings.setenv() # doing this will add the additional DYNACONF_ envars def get_dccsi_config(dccsi_dirpath=return_stub_dir()): @@ -176,46 +228,75 @@ def get_current_project_cfg(dev_folder=get_stub_check_path()): # ------------------------------------------------------------------------- -def get_current_project(): +def get_check_global_project(): """Gets o3de project via .o3de data in user directory""" from azpy.constants import PATH_USER_O3DE_BOOTSTRAP from collections import OrderedDict from box import Box + from azpy.core import get_datadir bootstrap_box = None - - try: - bootstrap_box = Box.from_json(filename=str(Path(PATH_USER_O3DE_BOOTSTRAP).resolve()), - encoding="utf-8", - errors="strict", - object_pairs_hook=OrderedDict) - except Exception as e: - # this file runs in py2.7 for Maya 2020, FileExistsError is not defined - _LOGGER.error('FileExistsError: {}'.format(PATH_USER_O3DE_BOOTSTRAP)) - _LOGGER.error('exception is: {}'.format(e)) - + json_file_path = Path(PATH_USER_O3DE_BOOTSTRAP) + if json_file_path.exists(): + try: + bootstrap_box = Box.from_json(filename=str(json_file_path.resolve()), + encoding="utf-8", + errors="strict", + object_pairs_hook=OrderedDict) + except IOError as e: + # this file runs in py2.7 for Maya 2020, FileExistsError is not defined + _LOGGER.error('Bad file interaction: {}'.format(json_file_path.resolve())) + _LOGGER.error('Exception is: {}'.format(e)) + pass if bootstrap_box: # this seems fairly hard coded - what if the data changes? project_path=Path(bootstrap_box.Amazon.AzCore.Bootstrap.project_path) - return project_path.resolve() + return project_path else: return None # ------------------------------------------------------------------------- +# ------------------------------------------------------------------------- +def get_o3de_project_path(): + """figures out the o3de project path + if not found defaults to the engine folder""" + _O3DE_PROJECT_PATH = None + try: + import azlmbr # this file will fail outside of O3DE + except ImportError as e: + # (fallback 1) this checks if a global project is set + # This check user home for .o3de data + _O3DE_PROJECT_PATH = get_check_global_project() + else: + # execute if no exception, this would indicate we are in O3DE land + # allow for external ENVAR override + from azpy.constants import ENVAR_O3DE_PROJECT_PATH + _O3DE_PROJECT_PATH = Path(os.getenv(ENVAR_O3DE_PROJECT_PATH, azlmbr.paths.projectroot)) + finally: + # (fallback 2) if None, fallback to engine folder + if not _O3DE_PROJECT_PATH: + _O3DE_PROJECT_PATH = get_o3de_engine_root() + # note: can't use fstrings as this module gets called with py2.7 in maya + _LOGGER.info('O3DE project root: {}'.format(_O3DE_PROJECT_PATH.resolve())) + return _O3DE_PROJECT_PATH +# ------------------------------------------------------------------------- + + # ------------------------------------------------------------------------- def bootstrap_dccsi_py_libs(dccsi_dirpath=return_stub_dir()): """Builds and adds local site dir libs based on py version""" from azpy.constants import STR_DCCSI_PYTHON_LIB_PATH # a path string constructor - _DCCSI_PYTHON_LIB_PATH = STR_DCCSI_PYTHON_LIB_PATH.format(dccsi_dirpath, - sys.version_info[0], - sys.version_info[1]) + _DCCSI_PYTHON_LIB_PATH = Path(STR_DCCSI_PYTHON_LIB_PATH.format(dccsi_dirpath, + sys.version_info[0], + sys.version_info[1])) - if os.path.exists(_DCCSI_PYTHON_LIB_PATH): - _LOGGER.debug('Performed site.addsitedir({})'.format(_DCCSI_PYTHON_LIB_PATH)) - site.addsitedir(_DCCSI_PYTHON_LIB_PATH) # PYTHONPATH + if _DCCSI_PYTHON_LIB_PATH.exists(): + site.addsitedir(_DCCSI_PYTHON_LIB_PATH.resolve()) # PYTHONPATH + _LOGGER.debug('Performed site.addsitedir({})' + ''.format(_DCCSI_PYTHON_LIB_PATH.resolve())) return _DCCSI_PYTHON_LIB_PATH else: message = "Doesn't exist: {}".format(_DCCSI_PYTHON_LIB_PATH) @@ -243,13 +324,10 @@ if __name__ == '__main__': _config = get_dccsi_config() _LOGGER.info('DCCSI_CONFIG_PATH: {}'.format(_config)) - _LOGGER.info('LY_DEV: {}'.format(get_stub_check_path('engine.json'))) - - # this will be deprecated and shouldn't work soon (returns None) - _LOGGER.info('LY_PROJECT: {}'.format(get_current_project_cfg(get_stub_check_path('bootstrap.cfg')))) + _LOGGER.info('O3DE_DEV: {}'.format(get_o3de_engine_root(check_stub='engine.json'))) # new o3de version - _LOGGER.info('LY_PROJECT: {}'.format(get_current_project())) + _LOGGER.info('O3DE_PROJECT: {}'.format(get_check_global_project())) _LOGGER.info('DCCSI_PYTHON_LIB_PATH: {}'.format(bootstrap_dccsi_py_libs(return_stub_dir('dccsi_stub')))) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/constants.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/constants.py index b9619fa584..d23601a33d 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/constants.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/constants.py @@ -25,7 +25,16 @@ import sys import site from os.path import expanduser import logging as _logging +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +# global scope +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'azpy.constants' + +os.environ['PYTHONINSPECT'] = 'True' # for this module to perform standalone # we need to set up basic access to the DCCsi _MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen? @@ -33,10 +42,10 @@ _DCCSIG_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../..')) _DCCSIG_PATH = os.getenv('DCCSIG_PATH', _DCCSIG_PATH) site.addsitedir(_DCCSIG_PATH) -# azpy module -#import azpy.constants as cnst +# now we have azpy api access +import azpy +from azpy.env_bool import env_bool from azpy.config_utils import return_stub_dir -import azpy.env_bool as env_bool # ------------------------------------------------------------------------- @@ -49,24 +58,26 @@ ENVAR_DCCSI_LOGLEVEL = str('DCCSI_LOGLEVEL') # Log formating FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)" FRMT_LOG_SHRT = "[%(asctime)s][%(name)s][%(levelname)s] >> %(message)s" +# ------------------------------------------------------------------------- -# global space -_G_DEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) +# ------------------------------------------------------------------------- +# global debug stuff +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_LOGLEVEL = int(env_bool(ENVAR_DCCSI_LOGLEVEL, int(20))) +if _DCCSI_GDEBUG: + _DCCSI_LOGLEVEL = int(10) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# set up module logging for handler in _logging.root.handlers[:]: _logging.root.removeHandler(handler) - -_PACKAGENAME = 'azpy.constants' - -_LOG_LEVEL = int(20) -if _G_DEBUG: - _LOG_LEVEL = int(10) -_logging.basicConfig(level=_LOG_LEVEL, - format=FRMT_LOG_LONG, - datefmt='%m-%d %H:%M') -_LOGGER = _logging.getLogger(_PACKAGENAME) -_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) +_LOGGER = _logging.getLogger(_MODULENAME) +_logging.basicConfig(format=FRMT_LOG_LONG, level=_DCCSI_LOGLEVEL) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- @@ -80,24 +91,25 @@ STR_CROSSBAR_RL = str('{0}\r'.format(STR_CROSSBAR)) STR_CROSSBAR_NL = str('{0}\n'.format(STR_CROSSBAR)) # some common str tags -TAG_DEFAULT_COMPANY = str('Amazon.Lumberyard') +TAG_DEFAULT_COMPANY = str('Amazon.O3DE') TAG_DEFAULT_PROJECT = str('DccScriptingInterface') +TAG_DCCSI_NICKNAME = str('DCCsi') TAG_MOCK_PROJECT = str('MockProject') -TAG_DIR_LY_DEV = str('dev') +TAG_DIR_O3DE_DEV = str('dev') TAG_DIR_DCCSI_AZPY = str('azpy') -TAG_DIR_DCCSI_SDK = str('SDK') -TAG_DIR_LY_BUILD = str('build') +TAG_DIR_DCCSI_TOOLS = str('Tools') +TAG_DIR_O3DE_BUILD_FOLDER = str('build') TAG_QT_PLUGIN_PATH = str('QT_PLUGIN_PATH') TAG_O3DE_FOLDER = str('.o3de') TAG_O3DE_BOOTSTRAP = str('bootstrap.setreg') +TAG_DCCSI_CONFIG = str('dccsiconfiguration.setreg') # filesystem markers, stub file names. -STUB_LY_DEV = str('engine.json') -STUB_LY_ROOT_PROJECT = str('ly_project_stub') -STUB_LY_ROOT_DCCSI = str('dccsi_stub') -STUB_LY_DCCSI_AZPY = str('dccsi_azpy_stub') -STUB_LY_DCCSI_SDK = str('dccsi_sdk_stub') +STUB_O3DE_DEV = str('engine.json') +STUB_O3DE_ROOT_DCCSI = str('dccsi_stub') +STUB_O3DE_DCCSI_AZPY = str('dccsi_azpy_stub') +STUB_O3DE_DCCSI_TOOLS = str('dccsi_tools_stub') # config string consts, Meta Qualifiers QUALIFIER_COMMENT = str('_meta_COMMENT') @@ -135,18 +147,18 @@ PATH_PROGRAMFILES_X64 = str(os.environ['PROGRAMFILES']) # base env var key as str ENVAR_COMPANY = str('COMPANY') -ENVAR_LY_PROJECT = str('LY_PROJECT') -ENVAR_LY_PROJECT_PATH = str('LY_PROJECT_PATH') -ENVAR_LY_DEV = str('LY_DEV') +ENVAR_O3DE_PROJECT = str('O3DE_PROJECT') +ENVAR_O3DE_PROJECT_PATH = str('O3DE_PROJECT_PATH') +ENVAR_O3DE_DEV = str('O3DE_DEV') ENVAR_DCCSIG_PATH = str('DCCSIG_PATH') ENVAR_DCCSI_AZPY_PATH = str('DCCSI_AZPY_PATH') -ENVAR_DCCSI_SDK_PATH = str('DCCSI_SDK_PATH') -ENVAR_LY_BUILD_DIR_NAME = str('LY_BUILD_DIR_NAME') +ENVAR_DCCSI_TOOLS_PATH = str('DCCSI_TOOLS_PATH') +ENVAR_O3DE_BUILD_DIR_NAME = str('O3DE_BUILD_DIR_NAME') -ENVAR_LY_BUILD_PATH = str('LY_BUILD_PATH') +ENVAR_O3DE_BUILD_PATH = str('O3DE_BUILD_PATH') ENVAR_QT_PLUGIN_PATH = TAG_QT_PLUGIN_PATH ENVAR_QTFORPYTHON_PATH = str('QTFORPYTHON_PATH') -ENVAR_LY_BIN_PATH = str('LY_BIN_PATH') +ENVAR_O3DE_BIN_PATH = str('O3DE_BIN_PATH') ENVAR_DCCSI_LOG_PATH = str('DCCSI_LOG_PATH') ENVAR_DCCSI_LAUNCHERS_PATH = str('DCCSI_LAUNCHERS_PATH') @@ -155,7 +167,7 @@ ENVAR_DCCSI_PY_VERSION_MAJOR = str('DCCSI_PY_VERSION_MAJOR') ENVAR_DCCSI_PY_VERSION_MINOR = str('DCCSI_PY_VERSION_MINOR') ENVAR_DCCSI_PYTHON_PATH = str('DCCSI_PYTHON_PATH') ENVAR_DCCSI_PYTHON_LIB_PATH = str('DCCSI_PYTHON_LIB_PATH') -ENVAR_DCCSI_PYTHON_INSTALL = str('DCCSI_PYTHON_INSTALL') +ENVAR_O3DE_PYTHON_INSTALL = str('O3DE_PYTHON_INSTALL') ENVAR_WINGHOME = str('WINGHOME') ENVAR_DCCSI_WING_VERSION_MAJOR = str('DCCSI_WING_VERSION_MAJOR') @@ -169,7 +181,7 @@ ENVAR_DCCSI_PY_DEFAULT = str('DCCSI_PY_DEFAULT') ENVAR_DCCSI_MAYA_VERSION = str('DCCSI_MAYA_VERSION') ENVAR_MAYA_LOCATION = str('MAYA_LOCATION') -ENVAR_DCCSI_SDK_MAYA_PATH = str('DCCSI_SDK_MAYA_PATH') +ENVAR_DCCSI_TOOLS_MAYA_PATH = str('DCCSI_TOOLS_MAYA_PATH') ENVAR_MAYA_MODULE_PATH = str('MAYA_MODULE_PATH') ENVAR_MAYA_BIN_PATH = str('MAYA_BIN_PATH') @@ -188,33 +200,33 @@ ENVAR_MAYA_SCRIPT_PATH = str('MAYA_SCRIPT_PATH') ENVAR_DCCSI_MAYA_SET_CALLBACKS = str('DCCSI_MAYA_SET_CALLBACKS') -TAG_LY_DCC_MAYA_MEL = 'dccsi_setup.mel' +TAG_O3DE_DCC_MAYA_MEL = 'dccsi_setup.mel' TAG_MAYA_WORKSPACE = 'workspace.mel' # dcc scripting interface common and default paths -PATH_LY_DEV = str(return_stub_dir(STUB_LY_DEV)) -PATH_DCCSIG_PATH = str(return_stub_dir(STUB_LY_ROOT_DCCSI)) -PATH_DCCSI_AZPY_PATH = str(return_stub_dir(STUB_LY_DCCSI_AZPY)) -PATH_DCCSI_SDK_PATH = str('{0}\\{1}'.format(PATH_DCCSIG_PATH, TAG_DIR_DCCSI_SDK)) +PATH_O3DE_DEV = str(return_stub_dir(STUB_O3DE_DEV)) +PATH_DCCSIG_PATH = str(return_stub_dir(STUB_O3DE_ROOT_DCCSI)) +PATH_DCCSI_AZPY_PATH = str(return_stub_dir(STUB_O3DE_DCCSI_AZPY)) +PATH_DCCSI_TOOLS_PATH = str('{0}\\{1}'.format(PATH_DCCSIG_PATH, TAG_DIR_DCCSI_TOOLS)) # logging into the cache -PATH_DCCSI_LOG_PATH = str('{LY_DEV}\\Cache\\{LY_PROJECT}\\pc\\user\\log\\logs') +PATH_DCCSI_LOG_PATH = str('{O3DE_PROJECT_PATH}\\user\\log\{TAG_DCCSI_NICKNAME}') # dev \ \ -STR_CONSTRUCT_LY_BUILD_PATH = str('{0}\\{1}') -PATH_LY_BUILD_PATH = str(STR_CONSTRUCT_LY_BUILD_PATH.format(PATH_LY_DEV, - TAG_DIR_LY_BUILD)) +STR_CONSTRUCT_O3DE_BUILD_PATH = str('{0}\\{1}') +PATH_O3DE_BUILD_PATH = str(STR_CONSTRUCT_O3DE_BUILD_PATH.format(PATH_O3DE_DEV, + TAG_DIR_O3DE_BUILD_FOLDER)) # ENVAR_QT_PLUGIN_PATH = TAG_QT_PLUGIN_PATH STR_QTPLUGIN_DIR = str('{0}\\bin\\profile\\EditorPlugins') STR_QTFORPYTHON_PATH = str('{0}\\Gems\\QtForPython\\3rdParty\\pyside2\\windows\\release') -STR_LY_BIN_PATH = str('{0}\\bin\\profile') +STR_O3DE_BIN_PATH = str('{0}\\bin\\profile') -PATH_LY_BUILD_PATH = str('{0}\\{1}'.format(PATH_LY_DEV, TAG_DIR_LY_BUILD)) -PATH_QTFORPYTHON_PATH = str(STR_QTFORPYTHON_PATH.format(PATH_LY_DEV)) -PATH_QT_PLUGIN_PATH = str(STR_QTPLUGIN_DIR).format(PATH_LY_BUILD_PATH) -PATH_LY_BIN_PATH = str(STR_LY_BIN_PATH).format(PATH_LY_BUILD_PATH) +PATH_O3DE_BUILD_PATH = str('{0}\\{1}'.format(PATH_O3DE_DEV, TAG_DIR_O3DE_BUILD_FOLDER)) +PATH_QTFORPYTHON_PATH = str(STR_QTFORPYTHON_PATH.format(PATH_O3DE_DEV)) +PATH_QT_PLUGIN_PATH = str(STR_QTPLUGIN_DIR).format(PATH_O3DE_BUILD_PATH) +PATH_O3DE_BIN_PATH = str(STR_O3DE_BIN_PATH).format(PATH_O3DE_BUILD_PATH) # py path string, parts, etc. TAG_DEFAULT_PY = str('Launch_pyBASE.bat') @@ -233,12 +245,19 @@ parts = os.path.split(PATH_USER_HOME) if str(parts[1].lower()) == 'documents': PATH_USER_HOME = parts[0] _LOGGER.debug('user home CORRECTED: {}'.format(PATH_USER_HOME)) + +STR_USER_O3DE_PATH = str('{home}\\{o3de}') -PATH_USER_O3DE = str('{home}\\{o3de}').format(home=PATH_USER_HOME, +PATH_USER_O3DE = str(STR_USER_O3DE_PATH).format(home=PATH_USER_HOME, o3de=TAG_O3DE_FOLDER) -PATH_USER_O3DE_REGISTRY = str('{0}\\Registry').format(PATH_USER_O3DE) -PATH_USER_O3DE_BOOTSTRAP = str('{reg}\\{file}').format(reg=PATH_USER_O3DE_REGISTRY, - file=TAG_O3DE_BOOTSTRAP) + +TAG_DIR_REGISTRY = str('Registry') +STR_USER_O3DE_REGISTRY_PATH = str('{0}\\{1}') +PATH_USER_O3DE_REGISTRY = str(STR_USER_O3DE_REGISTRY_PATH).format(PATH_USER_O3DE, TAG_DIR_REGISTRY) + +STR_USER_O3DE_BOOTSTRAP_PATH = str('{reg}\\{file}') +PATH_USER_O3DE_BOOTSTRAP = str(STR_USER_O3DE_BOOTSTRAP_PATH).format(reg=PATH_USER_O3DE_REGISTRY, + file=TAG_O3DE_BOOTSTRAP) #python and site-dir TAG_DCCSI_PY_VERSION_MAJOR = str(3) @@ -247,8 +266,8 @@ TAG_DCCSI_PY_VERSION_RELEASE = str(10) TAG_PYTHON_EXE = str('python.exe') TAG_TOOLS_DIR = str('Tools\\Python') TAG_PLATFORM = str('windows') -STR_CONSTRUCT_DCCSI_PYTHON_INSTALL = str('{0}\\{1}\\{2}.{3}.{4}\\{5}') -PATH_DCCSI_PYTHON_PATH = str(STR_CONSTRUCT_DCCSI_PYTHON_INSTALL.format(PATH_LY_DEV, +STR_CONSTRUCT_O3DE_PYTHON_INSTALL = str('{0}\\{1}\\{2}.{3}.{4}\\{5}') +PATH_DCCSI_PYTHON_PATH = str(STR_CONSTRUCT_O3DE_PYTHON_INSTALL.format(PATH_O3DE_DEV, TAG_TOOLS_DIR, TAG_DCCSI_PY_VERSION_MAJOR, TAG_DCCSI_PY_VERSION_MINOR, @@ -290,19 +309,12 @@ PATH_SAT_INSTALL_PATH = str('{0}\\{1}\\{2}\\{3}\\{4}' # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- if __name__ == '__main__': - # there are not really tests to run here due to this being a list of - # constants for shared use. - _G_DEBUG = True - _DCCSI_DEV_MODE = True - _LOGGER.setLevel(_logging.DEBUG) # force debugging - - # this is a top level module and to reduce cyclical azpy imports - # it only has a basic logger configured, add log to console - _handler = _logging.StreamHandler(sys.stdout) - _handler.setLevel(_logging.DEBUG) - _formatter = _logging.Formatter(FRMT_LOG_LONG) - _handler.setFormatter(_formatter) - _LOGGER.addHandler(_handler) + """Run this file as a standalone script""" + + # overide logger for standalone to be more verbose and lof to file + _LOGGER = azpy.initialize_logger(_MODULENAME, + log_to_file=_DCCSI_GDEBUG, + default_log_level=_DCCSI_LOGLEVEL) # happy print _LOGGER.info(STR_CROSSBAR) @@ -321,15 +333,15 @@ if __name__ == '__main__': from pathlib import Path _stash_dict = {} - _stash_dict['LY_DEV'] = Path(PATH_LY_DEV) + _stash_dict['O3DE_DEV'] = Path(PATH_O3DE_DEV) _stash_dict['DCCSIG_PATH'] = Path(PATH_DCCSIG_PATH) _stash_dict['DCCSI_AZPY_PATH'] = Path(PATH_DCCSI_AZPY_PATH) - _stash_dict['DCCSI_SDK_PATH'] = Path(PATH_DCCSI_SDK_PATH) + _stash_dict['DCCSI_TOOLS_PATH'] = Path(PATH_DCCSI_TOOLS_PATH) _stash_dict['DCCSI_PYTHON_PATH'] = Path(PATH_DCCSI_PYTHON_PATH) _stash_dict['DCCSI_PY_BASE'] = Path(PATH_DCCSI_PY_BASE) _stash_dict['DCCSI_PYTHON_LIB_PATH'] = Path(PATH_DCCSI_PYTHON_LIB_PATH) - _stash_dict['LY_BUILD_PATH'] = Path(PATH_LY_BUILD_PATH) - _stash_dict['LY_BIN_PATH'] = Path(PATH_LY_BIN_PATH) + _stash_dict['O3DE_BUILD_PATH'] = Path(PATH_O3DE_BUILD_PATH) + _stash_dict['O3DE_BIN_PATH'] = Path(PATH_O3DE_BIN_PATH) _stash_dict['QTFORPYTHON_PATH'] = Path(PATH_QTFORPYTHON_PATH) _stash_dict['QT_PLUGIN_PATH'] = Path(PATH_QT_PLUGIN_PATH) _stash_dict['SAT_INSTALL_PATH'] = Path(PATH_SAT_INSTALL_PATH) @@ -339,7 +351,7 @@ if __name__ == '__main__': # py 2 and 3 compatible iter def get_items(dict_object): for key in dict_object: - yield key, dict_object[key] + yield key, dict_object[key] for key, value in get_items(_stash_dict): # check if path exists diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/__init__.py new file mode 100644 index 0000000000..9269739f96 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/__init__.py @@ -0,0 +1,42 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +__copyright__ = "Copyright 2021, Amazon" +# ------------------------------------------------------------------------- +import sys +import logging as _logging +# ------------------------------------------------------------------------- + +#pulling from azpy.constants causes cyclical imports :( +FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)" +_DCCSI_GDEBUG = False +_DCCSI_LOGLEVEL = int(20) +if _DCCSI_GDEBUG: + _DCCSI_LOGLEVEL = int(10) + +_PACKAGENAME = 'azpy.core' +_logging.basicConfig(format=FRMT_LOG_LONG, level=_DCCSI_LOGLEVEL) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + +__all__ = [] + +if sys.version_info >= (3, 6): + from azpy.core.py3.utils import get_datadir +elif sys.version_info >= (2, 6) and sys.version_info < (3, 6): + _LOGGER.warning('Python vesion < 3 will be deprecated in the future') + from azpy.core.py2.utils import get_datadir +else: + _LOGGER.warning('Python vesion < 2.6 not recommended') + from azpy.core.py2.utils import get_datadir + +__all__ = ['get_datadir'] # you should hope it works + +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/__init__.py new file mode 100644 index 0000000000..35727ddda6 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/__init__.py @@ -0,0 +1,20 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +__copyright__ = "Copyright 2021, Amazon" +# ------------------------------------------------------------------------- +import logging as _logging +# ------------------------------------------------------------------------- +_PACKAGENAME = 'azpy.core.py2' +_LOGGER = _logging.getLogger(_PACKAGENAME) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + +__all__ = [] +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/utils.py new file mode 100644 index 0000000000..3a79b1bccf --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py2/utils.py @@ -0,0 +1,59 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +"""DCCsi.azpy.core.py2.utils +This module contain versions of utils speciufic to py3 snyntax""" +import sys +import logging as _logging + +try: + import pathlib +except: + import pathlib2 as pathlib + +__all__ = ['get_datadir'] + +_MODULENAME = 'azpy.core.py2.utils' +_LOGGER = _logging.getLogger(_MODULENAME) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) + +# ------------------------------------------------------------------------- +def get_datadir(): + """ + persistent application data. + # linux: ~/.local/share + # macOS: ~/Library/Application Support + # windows: C:/Users//AppData/Roaming + """ + + home = pathlib.Path.home() + + if sys.platform.startswith('win'): + datadir = pathlib.Path(home, "AppData/Roaming") + return datadir + elif sys.platform == "linux": + datadir = pathlib.Path(home, ".local/share") + return datadir + elif sys.platform == "darwin": + datadir = pathlib.Path(home, "Library/Application Support") + return datadir + else: # unknown + return None +# ------------------------------------------------------------------------- + + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """module testing""" + + user_data_dir = get_datadir() + _LOGGER.info(user_data_dir.resolve()) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/__init__.py new file mode 100644 index 0000000000..f5d26c9ebe --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/__init__.py @@ -0,0 +1,20 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +__copyright__ = "Copyright 2021, Amazon" +# ------------------------------------------------------------------------- +import logging as _logging +# ------------------------------------------------------------------------- +_PACKAGENAME = 'azpy.core.py3' +_LOGGER = _logging.getLogger(_PACKAGENAME) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + +__all__ = [] +# ------------------------------------------------------------------------- \ No newline at end of file diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/utils.py new file mode 100644 index 0000000000..cca07eb7dc --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/core/py3/utils.py @@ -0,0 +1,52 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- +"""DCCsi.azpy.core.py3.utils +This module contain versions of utils speciufic to py3 snyntax""" +import sys +import pathlib +import logging as _logging + +__all__ = ['get_datadir'] + +_MODULENAME = 'azpy.core.py3.utils' +_LOGGER = _logging.getLogger(_MODULENAME) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) + +# ------------------------------------------------------------------------- +def get_datadir() -> pathlib.Path: + """ + persistent application data. + # linux: ~/.local/share + # macOS: ~/Library/Application Support + # windows: C:/Users//AppData/Roaming + """ + + home = pathlib.Path.home() + + if sys.platform.startswith('win'): + return home / "AppData/Roaming" + elif sys.platform == "linux": + return home / ".local/share" + elif sys.platform == "darwin": + return home / "Library/Application Support" + else: # unknown + return None +# ------------------------------------------------------------------------- + + +########################################################################### +# Main Code Block, runs this script as main (testing) +# ------------------------------------------------------------------------- +if __name__ == '__main__': + """module testing""" + + user_data_dir = get_datadir() + _LOGGER.info(user_data_dir.resolve()) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/3dsmax/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/3dsmax/__init__.py old mode 100755 new mode 100644 similarity index 71% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/3dsmax/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/3dsmax/__init__.py index 017730d63c..a98de89ad7 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/3dsmax/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/3dsmax/__init__.py @@ -12,29 +12,53 @@ # importing all of the modules """azpy.3dsmax.__init__""" -import os -from azpy.env_bool import env_bool +import logging as _logging + +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.3dsmax' + _PACKAGENAME = 'azpy.dcc.3dsmax' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] +# To Do: procedurally discover dcc access and extend __all__ +# ------------------------------------------------------------------------- + # ------------------------------------------------------------------------- +def init(): + """If the 3dsmax api is required for a package/module to import, + then it should be initialized and added here so general imports + don't fail""" + + # Make sure we can import the native apis + import pymxs + import MaxPlus + + # extend all with submodules + #__all__.append('foo', 'bar') + + # Importing local packages/modules + pass +# ------------------------------------------------------------------------- + # ------------------------------------------------------------------------- if _DCCSI_DEV_MODE: @@ -46,21 +70,4 @@ if _DCCSI_DEV_MODE: _logger=_LOGGER) # ------------------------------------------------------------------------- - -# ------------------------------------------------------------------------- -def init(): - """If the 3dsmax api is required for a package/module to import, - then it should be initialized and added here so general imports - don't fail""" - - # __all__.append() - - # Make sure we can import the native apis - import pymxs - import MaxPlus - - # Importing local packages/modules - pass -# ------------------------------------------------------------------------- - del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/__init__.py new file mode 100644 index 0000000000..7f2b402c95 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/__init__.py @@ -0,0 +1,53 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -- This line is 75 characters ------------------------------------------- + +"""azpy.shared.__init__""" + +import logging as _logging + +import azpy.env_bool as env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG + +# global space +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) + +_PACKAGENAME = __name__ +if _PACKAGENAME is '__main__': + _PACKAGENAME = 'azpy.dcc' + +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + +# ------------------------------------------------------------------------- +# These are explicit imports for now +__all__ = [] +# To Do: procedurally discover dcc access and extend __all__ +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +if _DCCSI_DEV_MODE: + # If in dev mode this will test imports of __all__ + from azpy import test_imports + _LOGGER.debug('Testing Imports from {0}'.format(_PACKAGENAME)) + test_imports(__all__, + _pkg=_PACKAGENAME, + _logger=_LOGGER) +# ------------------------------------------------------------------------- + +del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/blender/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/blender/__init__.py old mode 100755 new mode 100644 similarity index 71% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/blender/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/blender/__init__.py index 903b76e3c4..b46c7146c2 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/blender/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/blender/__init__.py @@ -12,28 +12,49 @@ # importing all of the modules """azpy.blender.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.blender' - -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -#_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) + _PACKAGENAME = 'azpy.dcc.blender' + +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] +# To Do: procedurally discover dcc access and extend __all__ +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +def init(): + """If the blender bpy api is required for a package/module to import, + then it should be initialized and added here so general imports + don't fail""" + + # Make sure we can import the native apis + import bpy + + # extend all with submodules + #__all__.append('foo', 'bar') + + # Importing local packages/modules + pass # ------------------------------------------------------------------------- @@ -47,20 +68,4 @@ if _DCCSI_DEV_MODE: _logger=_LOGGER) # ------------------------------------------------------------------------- - -# ------------------------------------------------------------------------- -def init(): - """If the blender bpy api is required for a package/module to import, - then it should be initialized and added here so general imports - don't fail""" - - # __all__.append() - - # Make sure we can import the native apis - import bpy - - # Importing local packages/modules - pass -# ------------------------------------------------------------------------- - del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/houdini/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/houdini/__init__.py old mode 100755 new mode 100644 similarity index 70% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/houdini/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/houdini/__init__.py index c0b1ced437..0c16872073 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/houdini/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/houdini/__init__.py @@ -12,28 +12,32 @@ # importing all of the modules """azpy.houdini.__init__""" -import os +import logging as _logging -from azpy import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.houdini' + _PACKAGENAME = 'azpy.dcc.houdini' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -#_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] - +# To Do: procedurally discover dcc access and extend __all__ # ------------------------------------------------------------------------- @@ -53,12 +57,13 @@ def init(): """If the houdini api is required for a package/module to import, then it should be initialized and added here so general imports don't fail""" - - # __all__.append() # Make sure we can import the native apis import hou - + + # extend all with submodules + #__all__.append('foo', 'bar') + # Importing local packages/modules pass # ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/marmoset/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/marmoset/__init__.py old mode 100755 new mode 100644 similarity index 70% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/marmoset/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/marmoset/__init__.py index 35a081dcbf..7e95b5f19e --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/marmoset/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/marmoset/__init__.py @@ -12,28 +12,32 @@ # importing all of the modules """azpy.houdini.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.marmoset' + _PACKAGENAME = 'azpy.dcc.marmoset' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] - +# To Do: procedurally discover dcc access and extend __all__ # ------------------------------------------------------------------------- @@ -53,12 +57,13 @@ def init(): """If the marmoset api is required for a package/module to import, then it should be initialized and added here so general imports don't fail""" - - # __all__.append() # Make sure we can import the native apis import mset - + + # extend all with submodules + #__all__.append('foo', 'bar') + # Importing local packages/modules pass # ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/__init__.py old mode 100755 new mode 100644 similarity index 64% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/__init__.py index e66ee63739..870ace6166 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/__init__.py @@ -10,40 +10,32 @@ # -- This line is 75 characters ------------------------------------------- # The __init__.py files help guide import statements without automatically # importing all of the modules -"""azpy.maya.__init__""" +"""azpy.dcc.maya.__init__""" -from azpy.env_bool import env_bool +import logging as _logging + +import azpy.env_bool as env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya' + _PACKAGENAME = 'azpy.dcc.maya' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) - -# ------------------------------------------------------------------------- +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) __all__ = [] -# ------------------------------------------------------------------------- - - -# ------------------------------------------------------------------------- -if _DCCSI_DEV_MODE: - # If in dev mode this will test imports of __all__ - from azpy import test_imports - _LOGGER.debug('Testing Imports from {0}'.format(_PACKAGENAME)) - test_imports(__all__, - _pkg=_PACKAGENAME, - _logger=_LOGGER) -# ------------------------------------------------------------------------- - - # ------------------------------------------------------------------------- def init(): """If the maya api is required for a package/module to import, diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/__init__.py old mode 100755 new mode 100644 similarity index 64% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/__init__.py index 3f36bb0fba..7de93fe2bb --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/__init__.py @@ -12,23 +12,26 @@ # importing all of the modules """azpy.maya.callbacks.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.maya.callbacks' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) __all__ = ['event_callback_handler', 'node_message_callback_handler', diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/event_callback_handler.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/event_callback_handler.py old mode 100755 new mode 100644 similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/event_callback_handler.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/event_callback_handler.py index 7603e969e3..d2ca596d3c --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/event_callback_handler.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/event_callback_handler.py @@ -73,12 +73,12 @@ import maya.api.OpenMaya as openmaya #-------------------------------------------------------------------------- # -- Misc Global Space Definitions -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.callbacks.event_callback_handler' + _PACKAGENAME = 'azpy.dcc.maya.callbacks.event_callback_handler' _LOGGER = azpy.initialize_logger(_PACKAGENAME, default_log_level=int(20)) _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/node_message_callback_handler.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/node_message_callback_handler.py old mode 100755 new mode 100644 similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/node_message_callback_handler.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/node_message_callback_handler.py index cfb883be18..e41c9dbbc7 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/node_message_callback_handler.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/node_message_callback_handler.py @@ -80,12 +80,12 @@ import maya.cmds as mc # ------------------------------------------------------------------------- # -- Misc Global Space Definitions -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.callbacks.event_callback_handler' + _PACKAGENAME = 'azpy.dcc.maya.callbacks.event_callback_handler' _LOGGER = azpy.initialize_logger(_PACKAGENAME, default_log_level=int(20)) _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/on_shader_rename.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/on_shader_rename.py old mode 100755 new mode 100644 similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/on_shader_rename.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/on_shader_rename.py index 84e2426c2b..834505f076 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/callbacks/on_shader_rename.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/callbacks/on_shader_rename.py @@ -85,12 +85,12 @@ import maya.cmds as mc # -------------------------------------------------------------------------- # -- Misc Global Space Definitions -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.callbacks.on_shader_rename' + _PACKAGENAME = 'azpy.dcc.maya.callbacks.on_shader_rename' _LOGGER = azpy.initialize_logger(_PACKAGENAME, default_log_level=int(20)) _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/__init__.py old mode 100755 new mode 100644 similarity index 55% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/__init__.py index 7dab357d22..ecb164c1f7 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/__init__.py @@ -10,25 +10,28 @@ # -- This line is 75 characters ------------------------------------------- # The __init__.py files help guide import statements without automatically # importing all of the modules -"""azpy.maya.helpers.__init__""" +"""azpy.dcc.maya.helpers.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.callbacks' + _PACKAGENAME = 'azpy.dcc.maya.callbacks' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) __all__ = ['undo_context', 'utils'] diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/undo_context.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/undo_context.py old mode 100755 new mode 100644 similarity index 97% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/undo_context.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/undo_context.py index 90c12af2c9..7a6de2224b --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/undo_context.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/undo_context.py @@ -42,12 +42,12 @@ import maya.cmds as mc # ------------------------------------------------------------------------- # -- Misc Global Space Definitions -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.helpers.undo_context' + _PACKAGENAME = 'azpy.dcc.maya.helpers.undo_context' _LOGGER = initialize_logger(_PACKAGENAME, default_log_level=int(20)) _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/utils.py old mode 100755 new mode 100644 similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/utils.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/utils.py index 5fe6cd5876..5f1522e6e1 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/helpers/utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/helpers/utils.py @@ -10,7 +10,7 @@ # -- This line is 75 characters ------------------------------------------- """ -azpy.maya utility module +azpy.dcc.maya utility module """ # ------------------------------------------------------------------------- # built in's @@ -32,12 +32,12 @@ import maya.cmds as cmds # ------------------------------------------------------------------------- # -- Misc Global Space Definitions -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.helpers.undo_context' + _PACKAGENAME = 'azpy.dcc.maya.helpers.undo_context' _LOGGER = initialize_logger(_PACKAGENAME, default_log_level=int(20)) _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) @@ -46,7 +46,7 @@ _LOGGER.debug('Invoking:: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- # Initiate the Wing IDE debug connection. -if _G_DEBUG: +if _DCCSI_GDEBUG: #import azpy.dev.connectDebugger as lyDevConnnect # lyDevConnnect() pass diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/__init__.py new file mode 100644 index 0000000000..41f177bcf0 --- /dev/null +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/__init__.py @@ -0,0 +1,38 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# -------------------------------------------------------------------------- +"""azpy.dcc.maya.toolbits.__init__""" + +import logging as _logging + +import azpy.env_bool as env_bool +from azpy.constants import ENVAR_DCCSI_GDEBUG +from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG + +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) + +_PACKAGENAME = __name__ +if _PACKAGENAME is '__main__': + _PACKAGENAME = 'azpy.dcc.maya.toolbits' + +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + +__all__ = ['detach'] + +del _LOGGER +#-------------------------------------------------------------------------- + diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/detach.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/detach.py old mode 100755 new mode 100644 similarity index 94% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/detach.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/detach.py index 1afcbfa9f6..51d3bb9b66 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/detach.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/toolbits/detach.py @@ -44,12 +44,12 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.toolbits.detatch' + _PACKAGENAME = 'azpy.dcc.maya.toolbits.detatch' import azpy _LOGGER = azpy.initialize_logger(_PACKAGENAME) @@ -65,13 +65,13 @@ def clean_detach(detachType=0, args=None, name=None, or duplicating those faces without harming the orignal ''' - sel = azpy.maya.helpers.utils.Selection() + sel = azpy.dcc.maya.helpers.utils.Selection() for obj in sel.selection.keys(): print("~ cleanDetach:: Working on: {0}".format(obj)) # set up / open the maya undo context - with azpy.maya.helpers.UndoContext(): + with azpy.dcc.maya.helpers.UndoContext(): if deletHistoyIn: mc.delete( obj, constructionHistory = True) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/__init__.py similarity index 100% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/__init__.py diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/execute_wing_code.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/execute_wing_code.py similarity index 95% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/execute_wing_code.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/execute_wing_code.py index 34aaa2cbb7..3c8e7a75a3 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/execute_wing_code.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/execute_wing_code.py @@ -43,13 +43,13 @@ def get_stub_check_path(in_path=__file__, check_stub='engineroot.txt'): # ------------------------------------------------------------------------- # -- Global Definitions -- -_MODULENAME = 'azpy.maya.utils.execute_wing_code' +_MODULENAME = 'azpy.dcc.maya.utils.execute_wing_code' _LOGGER = _logging.getLogger(_MODULENAME) -_LY_DEV = get_stub_check_path() -_LOGGER.info('_LY_DEV: {}'.format(_LY_DEV)) +_O3DE_DEV = get_stub_check_path() +_LOGGER.info('_O3DE_DEV: {}'.format(_O3DE_DEV)) -_PROJ_CACHE = os.path.join(_LY_DEV, 'cache', 'DCCsi', 'wing') +_PROJ_CACHE = os.path.join(_O3DE_DEV, 'cache', 'DCCsi', 'wing') _LOGGER.info('_PROJ_CACHE: {}'.format(_PROJ_CACHE)) _LOCAL_HOST = socket.gethostbyname(socket.gethostname()) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/simple_command_port.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/simple_command_port.py similarity index 98% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/simple_command_port.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/simple_command_port.py index 659cae985d..3a130cd85f 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/simple_command_port.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/simple_command_port.py @@ -24,7 +24,7 @@ import logging as _logging # -------------------------------------------------------------------------- # -- Global Definitions -- -_MODULENAME = 'azpy.maya.utils.simple_command_port' +_MODULENAME = 'azpy.dcc.maya.utils.simple_command_port' _LOGGER = _logging.getLogger(_MODULENAME) _LOCAL_HOST = socket.gethostbyname(socket.gethostname()) @@ -205,7 +205,7 @@ class SimpleCommandPort: # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- if __name__ == '__main__': - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True _LOGGER.setLevel(_logging.DEBUG) # force debugging diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/wing_to_maya.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/wing_to_maya.py similarity index 99% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/wing_to_maya.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/wing_to_maya.py index f170090823..0c06b912d9 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/utils/wing_to_maya.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/maya/utils/wing_to_maya.py @@ -24,7 +24,7 @@ from simple_command_port import SimpleCommandPort # -------------------------------------------------------------------------- # -- Global Definitions -- -_MODULENAME = 'azpy.maya.utils.wing_to_maya' +_MODULENAME = 'azpy.dcc.maya.utils.wing_to_maya' _LOGGER = _logging.getLogger(_MODULENAME) _LOCAL_HOST = socket.gethostbyname(socket.gethostname()) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/lumberyard/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/__init__.py old mode 100755 new mode 100644 similarity index 68% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/lumberyard/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/__init__.py index 9da4e8975a..95aa781557 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/lumberyard/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/__init__.py @@ -11,30 +11,50 @@ # The __init__.py files help guide import statements without automatically # importing all of the modules """azpy.lumberyard.__init__ -All Lumberyard render related packages/modules should live here.""" +All O3DE related extension packages/modules should live here.""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.lumberyard' + _PACKAGENAME = 'azpy.dcc.o3de' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] +# To Do: procedurally discover dcc access and extend __all__ +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +def init(): + """If the lumberyard azlmbr api is required for a package/module to + import, then it should be initialized and added here so general imports + don't fail""" + + import azlmbr + + # extend all with submodules + __all__.append('atom') + + # Importing local packages/modules + pass # ------------------------------------------------------------------------- @@ -49,19 +69,4 @@ if _DCCSI_DEV_MODE: # ------------------------------------------------------------------------- -# ------------------------------------------------------------------------- -def init(): - """If the lumberyard azlmbr api is required for a package/module to - import, then it should be initialized and added here so general imports - don't fail""" - - # __all__.append() - - # Make sure we can import the native apis - #import - - # Importing local packages/modules - pass -# ------------------------------------------------------------------------- - del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/render/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/atom/__init__.py old mode 100755 new mode 100644 similarity index 72% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/render/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/atom/__init__.py index a6eb9ea373..ecd1db3ef0 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/render/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/o3de/atom/__init__.py @@ -14,28 +14,33 @@ This package generically uses 'render' to refer to Atom (which is a code name.) All Atom render related packages/modules should live here.""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) + _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.render' + _PACKAGENAME = 'azpy.dcc.o3de.atom' - -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] - +# To Do: procedurally discover dcc access and extend __all__ # ------------------------------------------------------------------------- @@ -55,12 +60,13 @@ def init(): """If the atom render api is required for a package/module to import, then it should be initialized and added here so general imports don't fail""" - - # __all__.append() # Make sure we can import the native apis # import - + + # extend all with submodules + #__all__.append('foo', 'bar') + # Importing local packages/modules pass # ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/substance/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/substance/__init__.py old mode 100755 new mode 100644 similarity index 71% rename from Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/substance/__init__.py rename to Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/substance/__init__.py index 8f9d542906..1e5ee058cc --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/substance/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dcc/substance/__init__.py @@ -12,28 +12,49 @@ # importing all of the modules """azpy.substance.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.substance' + _PACKAGENAME = 'azpy.dcc.substance' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- - +# These are explicit imports for now __all__ = [] +# To Do: procedurally discover dcc access and extend __all__ +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +def init(): + """If the substance api is required for a package/module to import, + then it should be initialized and added here so general imports + don't fail""" + + # Make sure we can import the native apis + # import + + # extend all with submodules + #__all__.append('foo', 'bar') + + # Importing local packages/modules + pass # ------------------------------------------------------------------------- @@ -47,21 +68,4 @@ if _DCCSI_DEV_MODE: _logger=_LOGGER) # ------------------------------------------------------------------------- - -# ------------------------------------------------------------------------- -def init(): - """If the substance api is required for a package/module to import, - then it should be initialized and added here so general imports - don't fail""" - - # __all__.append() - - # Make sure we can import the native apis - # import - - # Importing local packages/modules - pass - -# ------------------------------------------------------------------------- - del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/__init__.py index cbd58ba5e8..0db99ff838 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/__init__.py @@ -15,7 +15,7 @@ from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = 'azpy.dev.ide' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/hot_keys.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/hot_keys.py index 2c91398b70..1e179003fe 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/hot_keys.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/hot_keys.py @@ -119,10 +119,10 @@ def get_stub_check_path(in_path=__file__, check_stub='engineroot.txt'): # ------------------------------------------------------------------------- # globals -_LY_DEV = get_stub_check_path() -_LOGGER.info('_LY_DEV: {}'.format(_LY_DEV)) +_O3DE_DEV = get_stub_check_path() +_LOGGER.info('_O3DE_DEV: {}'.format(_O3DE_DEV)) -_PROJ_CACHE = os.path.join(_LY_DEV, 'cache', 'DCCsi', 'wing') +_PROJ_CACHE = os.path.join(_O3DE_DEV, 'cache', 'DCCsi', 'wing') _LOGGER.info('_PROJ_CACHE: {}'.format(_PROJ_CACHE)) if not os.path.exists(_PROJ_CACHE): @@ -339,7 +339,7 @@ mel_selection_to_maya.contexts = [ if __name__ == '__main__': # there are not really tests to run here due to this being a list of # constants for shared use. - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True _LOGGER.setLevel(_logging.DEBUG) # force debugging diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/test.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/test.py index dbf5025f88..838e890184 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/test.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/ide/wing/test.py @@ -73,7 +73,7 @@ dccsi_test_script.contexts = [ if __name__ == '__main__': # there are not really tests to run here due to this being a list of # constants for shared use. - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True _LOGGER.setLevel(_logging.DEBUG) # force debugging diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/maya_app.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/maya_app.py index 76dc3d9b2a..3e4344aecc 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/maya_app.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/maya_app.py @@ -25,10 +25,10 @@ from azpy.constants import ENVAR_DCCSI_DEV_MODE # -------------------------------------------------------------------------- # -- Global Definitions -- -_DCCSI_DCC_APP = None +_DCCSI_G_DCC_APP = None # set up global space, logging etc. -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULENAME = 'azpy.dev.utils.check.maya_app' @@ -43,14 +43,14 @@ _LOGGER = _logging.getLogger(_MODULENAME) def set_dcc_app(dcc_app='maya'): """ azpy.dev.utils.check.maya.set_dcc_app() - this will set global _DCCSI_DCC_APP = 'maya' - and os.environ["DCCSI_DCC_APP"] = 'maya' + this will set global _DCCSI_G_DCC_APP = 'maya' + and os.environ["DCCSI_G_DCC_APP"] = 'maya' """ - _DCCSI_DCC_APP = dcc_app + _DCCSI_G_DCC_APP = dcc_app - _LOGGER.info('Setting DCCSI_DCC_APP to: {0}'.format(dcc_app)) + _LOGGER.info('Setting DCCSI_G_DCC_APP to: {0}'.format(dcc_app)) - return _DCCSI_DCC_APP + return _DCCSI_G_DCC_APP # ------------------------------------------------------------------------- @@ -58,44 +58,44 @@ def set_dcc_app(dcc_app='maya'): def clear_dcc_app(dcc_app=False): """ azpy.dev.utils.check.maya.set_dcc_app() - this will set global _DCCSI_DCC_APP = False - and os.environ["DCCSI_DCC_APP"] = False + this will set global _DCCSI_G_DCC_APP = False + and os.environ["DCCSI_G_DCC_APP"] = False """ - _DCCSI_DCC_APP = dcc_app + _DCCSI_G_DCC_APP = dcc_app - _LOGGER.info('Setting DCCSI_DCC_APP to: {0}'.format(dcc_app)) + _LOGGER.info('Setting DCCSI_G_DCC_APP to: {0}'.format(dcc_app)) - return _DCCSI_DCC_APP + return _DCCSI_G_DCC_APP # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -def validate_state(DCCSI_DCC_APP=_DCCSI_DCC_APP): +def validate_state(DCCSI_G_DCC_APP=_DCCSI_G_DCC_APP): ''' This will detect if we are running in Maya or not, then will call either, set_dcc_app('maya') or clear_dcc_app(dcc_app=False) ''' - if _G_DEBUG: + if _DCCSI_GDEBUG: _LOGGER.debug(autolog()) try: import maya.cmds as cmds - DCCSI_DCC_APP = set_dcc_app('maya') + DCCSI_G_DCC_APP = set_dcc_app('maya') except ImportError as e: _LOGGER.warning('Can not perform: import maya.cmds as cmds') - DCCSI_DCC_APP = clear_dcc_app() + DCCSI_G_DCC_APP = clear_dcc_app() else: try: if cmds.about(batch=True): - DCCSI_DCC_APP = set_dcc_app('maya') + DCCSI_G_DCC_APP = set_dcc_app('maya') except AttributeError as e: _LOGGER.warning("maya.cmds module isn't fully loaded/populated, " "(cmds populates only in batch, maya.standalone, or maya GUI)") # NO Maya - DCCSI_DCC_APP=clear_dcc_app() + DCCSI_G_DCC_APP=clear_dcc_app() - return DCCSI_DCC_APP + return DCCSI_G_DCC_APP # ------------------------------------------------------------------------- @@ -120,7 +120,7 @@ def autolog(): # ------------------------------------------------------------------------- # run the check on import -_DCCSI_DCC_APP = validate_state() +_DCCSI_G_DCC_APP = validate_state() # ------------------------------------------------------------------------- @@ -130,7 +130,7 @@ _DCCSI_DCC_APP = validate_state() if __name__ == '__main__': # there are not really tests to run here due to this being a list of # constants for shared use. - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True _LOGGER.setLevel(_logging.DEBUG) # force debugging @@ -150,5 +150,5 @@ if __name__ == '__main__': _LOGGER.info('{} ... Running script as __main__'.format(_MODULENAME)) _LOGGER.info(STR_CROSSBAR) - _DCCSI_DCC_APP = validate_state() - _LOGGER.info('Is Maya Running? _DCCSI_DCC_APP = {}'.format(_DCCSI_DCC_APP)) + _DCCSI_G_DCC_APP = validate_state() + _LOGGER.info('Is Maya Running? _DCCSI_G_DCC_APP = {}'.format(_DCCSI_G_DCC_APP)) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/running_state.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/running_state.py index 301d49a0ea..e5304cf34c 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/running_state.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/dev/utils/check/running_state.py @@ -21,10 +21,11 @@ import logging as _logging # -------------------------------------------------------------------------- # -- Global Definitions -- -_DCCSI_DCC_APP = None +_DCCSI_G_DCC_APP = None _MODULENAME = 'azpy.dev.utils.check.running_state' _LOGGER = _logging.getLogger(_MODULENAME) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- @@ -36,7 +37,7 @@ class CheckRunningState(object): """ # Class Variables - DCCSI_DCC_APP = None + DCCSI_G_DCC_APP = None def __init__(self, *args, **kwargs): ''' @@ -82,32 +83,35 @@ class CheckRunningState(object): def check_known(self): # -- init -- # first let's check if any of these DCC apps are running + + # To Do?: Add O3DE checks, treat as a DCC app? + # 0 - maya first - CheckRunningState.DCCSI_DCC_APP = self.maya_running() + CheckRunningState.DCCSI_G_DCC_APP = self.maya_running() # 1 - then max - if not CheckRunningState.DCCSI_DCC_APP: - CheckRunningState.DCCSI_DCC_APP = self.max_running() + if not CheckRunningState.DCCSI_G_DCC_APP: + CheckRunningState.DCCSI_G_DCC_APP = self.max_running() else: - _LOGGER.warning('DCCSI_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_DCC_APP)) + _LOGGER.warning('DCCSI_G_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_G_DCC_APP)) # 2 - then blender - if not CheckRunningState.DCCSI_DCC_APP: - CheckRunningState.DCCSI_DCC_APP = self.blender_running() + if not CheckRunningState.DCCSI_G_DCC_APP: + CheckRunningState.DCCSI_G_DCC_APP = self.blender_running() else: - _LOGGER.warning('DCCSI_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_DCC_APP)) + _LOGGER.warning('DCCSI_G_DCC_APP is already set: {}'.format(CheckRunningState.DCCSI_G_DCC_APP)) # store checks for DCC info - if CheckRunningState.DCCSI_DCC_APP: + if CheckRunningState.DCCSI_G_DCC_APP: self.dcc_py = True # store check for is maya running headless - if CheckRunningState.DCCSI_DCC_APP == 'maya': + if CheckRunningState.DCCSI_G_DCC_APP == 'maya': self.maya_headless = self.is_maya_headless() # set a envar other modules can easily check - if CheckRunningState.DCCSI_DCC_APP: - os.environ['DCCSI_DCC_APP'] = CheckRunningState.DCCSI_DCC_APP + if CheckRunningState.DCCSI_G_DCC_APP: + os.environ['DCCSI_G_DCC_APP'] = CheckRunningState.DCCSI_G_DCC_APP # --------------------------------------------------------------------- @@ -164,13 +168,13 @@ class CheckRunningState(object): """< To Do: Need to document >""" try: import azpy.dev.utils.check.maya_app as check_dcc - DCCSI_DCC_APP = check_dcc.validate_state() + DCCSI_G_DCC_APP = check_dcc.validate_state() except ImportError as e: _LOGGER.info('Not Implemented: azpy.dev.utils.check.maya_app') - if DCCSI_DCC_APP: - CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state() - os.environ["DCCSI_DCC_APP"] = str(DCCSI_DCC_APP) - return CheckRunningState.DCCSI_DCC_APP + if DCCSI_G_DCC_APP: + CheckRunningState.DCCSI_G_DCC_APP = check_dcc.validate_state() + os.environ["DCCSI_G_DCC_APP"] = str(DCCSI_G_DCC_APP) + return CheckRunningState.DCCSI_G_DCC_APP #---------------------------------------------------------------------- # --method------------------------------------------------------------- @@ -200,13 +204,13 @@ class CheckRunningState(object): """ try: import azpy.dev.utils.check.max_app as check_dcc - CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state() + CheckRunningState.DCCSI_G_DCC_APP = check_dcc.validate_state() except ImportError as e: _LOGGER.info('Not Implemented: azpy.dev.utils.check.max') - if CheckRunningState.DCCSI_DCC_APP: - CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state() - os.environ["DCCSI_DCC_APP"] = str(CheckRunningState.DCCSI_DCC_APP) - return CheckRunningState.DCCSI_DCC_APP + if CheckRunningState.DCCSI_G_DCC_APP: + CheckRunningState.DCCSI_G_DCC_APP = check_dcc.validate_state() + os.environ["DCCSI_G_DCC_APP"] = str(CheckRunningState.DCCSI_G_DCC_APP) + return CheckRunningState.DCCSI_G_DCC_APP #---------------------------------------------------------------------- @@ -217,13 +221,13 @@ class CheckRunningState(object): """ try: import azpy.dev.utils.check.blender_app as check_dcc - CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state() + CheckRunningState.DCCSI_G_DCC_APP = check_dcc.validate_state() except ImportError as e: _LOGGER.info('Not Implemented: azpy.dev.utils.check.blender') - if CheckRunningState.DCCSI_DCC_APP: - CheckRunningState.DCCSI_DCC_APP = check_dcc.validate_state() - os.environ["DCCSI_DCC_APP"] = str(CheckRunningState.DCCSI_DCC_APP) - return CheckRunningState.DCCSI_DCC_APP + if CheckRunningState.DCCSI_G_DCC_APP: + CheckRunningState.DCCSI_G_DCC_APP = check_dcc.validate_state() + os.environ["DCCSI_G_DCC_APP"] = str(CheckRunningState.DCCSI_G_DCC_APP) + return CheckRunningState.DCCSI_G_DCC_APP #---------------------------------------------------------------------- @@ -231,7 +235,7 @@ class CheckRunningState(object): # Class Test #========================================================================== if __name__ == '__main__': - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True _LOGGER.setLevel(_logging.DEBUG) # force debugging @@ -251,4 +255,4 @@ if __name__ == '__main__': _LOGGER.info(STR_CROSSBAR) foo = CheckRunningState() - _LOGGER.info('DCCSI_DCC_APP: {}'.format(foo.DCCSI_DCC_APP)) + _LOGGER.info('DCCSI_G_DCC_APP: {}'.format(foo.DCCSI_G_DCC_APP)) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/env_base.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/env_base.py index dd28c98927..cdcf99d762 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/env_base.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/env_base.py @@ -10,7 +10,7 @@ # -- This line is 75 characters ------------------------------------------- from __future__ import unicode_literals ''' -Module: \azpy\shared\common\base_env.py +Module: \\azpy\\shared\\common\\base_env.py This module packs the most basic set of environment variables. @@ -57,7 +57,7 @@ _LOGGER = _logging.getLogger(_PACKAGENAME) _LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) # set up base totally non-functional defauls (denoted with $) @@ -65,15 +65,15 @@ _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _BASE_ENVVAR_DICT = OrderedDict() # project tag -_BASE_ENVVAR_DICT[ENVAR_LY_PROJECT] = '${0}'.format(ENVAR_LY_PROJECT) +_BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT] = '${0}'.format(ENVAR_O3DE_PROJECT) # paths -_BASE_ENVVAR_DICT[ENVAR_LY_DEV] = Path('${0}'.format(ENVAR_LY_DEV)) -_BASE_ENVVAR_DICT[ENVAR_LY_PROJECT_PATH] = Path('${0}'.format(ENVAR_LY_PROJECT_PATH)) +_BASE_ENVVAR_DICT[ENVAR_O3DE_DEV] = Path('${0}'.format(ENVAR_O3DE_DEV)) +_BASE_ENVVAR_DICT[ENVAR_O3DE_PROJECT_PATH] = Path('${0}'.format(ENVAR_O3DE_PROJECT_PATH)) _BASE_ENVVAR_DICT[ENVAR_DCCSIG_PATH] = Path('${0}'.format(ENVAR_DCCSIG_PATH)) _BASE_ENVVAR_DICT[ENVAR_DCCSI_LOG_PATH] = Path('${0}'.format(ENVAR_DCCSI_LOG_PATH)) _BASE_ENVVAR_DICT[ENVAR_DCCSI_AZPY_PATH] = Path('${0}'.format(ENVAR_DCCSI_AZPY_PATH)) -_BASE_ENVVAR_DICT[ENVAR_DCCSI_SDK_PATH] = Path('${0}'.format(ENVAR_DCCSI_SDK_PATH)) +_BASE_ENVVAR_DICT[ENVAR_DCCSI_TOOLS_PATH] = Path('${0}'.format(ENVAR_DCCSI_TOOLS_PATH)) # dev env flags _BASE_ENVVAR_DICT[ENVAR_DCCSI_GDEBUG] = '${0}'.format(ENVAR_DCCSI_GDEBUG) @@ -110,16 +110,16 @@ if __name__ == '__main__': # print(setEnvarDefaults(), '\r') #<-- not necissary, already called # print(BASE_ENVVAR_VALUES, '\r') _LOGGER.info('Pretty print: _BASE_ENVVAR_DICT') - print(json.dumps(_BASE_ENVVAR_DICT, - indent=4, sort_keys=False, - ensure_ascii=False), '\r') + _LOGGER.debug(json.dumps(_BASE_ENVVAR_DICT, + indent=4, sort_keys=False, + ensure_ascii=False), '\r') # retreive a Path type key from the Box - foo = _BASE_ENVVAR_DICT[ENVAR_LY_DEV] + foo = _BASE_ENVVAR_DICT[ENVAR_O3DE_DEV] _LOGGER.info('~ foo is: {0}'.format(type(foo), foo)) # simple tests - _ENV_TAG = 'LY_DEV' + _ENV_TAG = 'O3DE_DEV' foo = get_envar_default(_ENV_TAG) _LOGGER.info("~ Results of getVar on tag, '{0}':'{1}'\r".format(_ENV_TAG, foo)) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/__init__.py deleted file mode 100755 index be62dedcbe..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/maya/toolbits/__init__.py +++ /dev/null @@ -1,37 +0,0 @@ -# coding:utf-8 -#!/usr/bin/python -# -# 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 -# -# -# -- This line is 75 characters ------------------------------------------- -# The __init__.py files help guide import statements without automatically -# importing all of the modules -"""azpy.maya.toolbits.__init__""" - -mport os - -from azpy.env_bool import env_bool -from azpy.constants import ENVAR_DCCSI_GDEBUG -from azpy.constants import ENVAR_DCCSI_DEV_MODE - -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) - -_PACKAGENAME = __name__ -if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.maya.toolbits' - -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) - -__all__ = ['detach'] - -del _LOGGER -#-------------------------------------------------------------------------- - diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/return_stub.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/return_stub.py index a5a4d40aad..478d3c355e 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/return_stub.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/return_stub.py @@ -19,7 +19,7 @@ import logging as _logging # ------------------------------------------------------------------------- # global space debug flag, no fancy stuff here we use in bootstrap -_G_DEBUG = False # manually enable to debug this file +_DCCSI_GDEBUG = False # manually enable to debug this file _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': @@ -44,7 +44,7 @@ def return_stub(stub): break if (len(tail) == 0): path = "" - if _G_DEBUG: + if _DCCSI_GDEBUG: _LOGGER.debug('~ Debug Message: I was not able to find the ' 'path to that file (stub) in a walk-up ' 'from currnet path') diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/__init__.py index 7c109f42cb..d6b6bc0d1d 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/__init__.py @@ -11,23 +11,27 @@ """azpy.shared.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.shared' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/boxDumpTest.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/boxDumpTest.json deleted file mode 100644 index 52c556733c..0000000000 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/boxDumpTest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "ordered_box": true, - "COMPANY": "Amazon.Lumberyard", - "LY_PROJECT": "DccScriptingInterface", - "LY_DEV": "G:\\depot\\JG_PC1_spectrAtom\\dev", - "LY_BUILD_DIR_NAME": "windows_vs2019", - "LY_BUILD_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\windows_vs2019", - "QT_PLUGIN_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\bin\\profile\\EditorPlugins", - "LY_PROJECT_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface", - "DCCSIG_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface", - "DCCSI_AZPY_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface\\azpy", - "DCCSI_SDK_PATH": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface\\SDK", - "DCCSI_WING_VERSION_MAJOR": "7", - "DCCSI_WING_VERSION_MINOR": "1", - "WINGHOME": "C:\\Program Files (x86)\\Wing Pro 7.1", - "DCCSI_PY_DEFAULT": "G:\\depot\\JG_PC1_spectrAtom\\dev\\Tools\\Python\\3.7.5\\windows\\python.exe" -} diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/__init__.py index ab425ef427..1d552e7ea7 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/__init__.py @@ -12,23 +12,27 @@ # importing all of the modules """azpy.shared.common.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG # global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.shared.common' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- # diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/core_utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/core_utils.py index 4040620002..629f52bbd7 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/core_utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/core_utils.py @@ -48,34 +48,34 @@ import os import sys import site import fnmatch +import logging as _logging # 3rd Party from pathlib import Path # from progress.spinner import Spinner # deprecate use (or refactor) - -# Lumberyard extensions -from azpy.constants import * -from azpy import initialize_logger # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -# global space debug flag -from azpy.env_bool import env_bool +# global space +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) -_PACKAGENAME = __name__ -if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.shared.common.core_utils' +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'azpy.shared.common.core_utils' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_MODULENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- @@ -421,7 +421,7 @@ def return_stub(stub): if __name__ == "__main__": '''To Do: Document''' # constants for shared use. - _G_DEBUG = True + _DCCSI_GDEBUG = True # happy _LOGGER.info _LOGGER.info("# {0} #".format('-' * 72)) @@ -435,7 +435,7 @@ if __name__ == "__main__": _KNOWN_SITEDIR_PATHS = site._init_pathinfo() # this is just a debug developer convenience _LOGGER.info (for testing acess) - if _G_DEBUG: + if _DCCSI_GDEBUG: import pkgutil _LOGGER.info('Current working dir: {0}'.format(cwd)) search_path = ['.'] # set to None to see all modules importable from sys.path diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/envar_utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/envar_utils.py index fc99df0f30..fc2db6be0c 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/envar_utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/common/envar_utils.py @@ -12,7 +12,7 @@ from __future__ import unicode_literals # ------------------------------------------------------------------------- ''' -Module: \azpy\shared\common\config_utils.py +Module: \\azpy\\shared\\common\\config_utils.py A set of utility functions @@ -33,28 +33,31 @@ import logging as _logging # 3rd Party from box import Box from unipath import Path - -# Lumberyard extensions -from azpy.constants import * # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -from azpy.env_bool import env_bool +# global space +import azpy.env_bool as env_bool +from azpy.constants import ENVAR_O3DE_DEV +from azpy.constants import ENVAR_O3DE_PROJECT from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) -_PACKAGENAME = __name__ -if _PACKAGENAME is '__main__': - _PACKAGENAME = 'azpy.shared.common.envar_utils' +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'azpy.shared.common.envar_utils' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_MODULENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- @@ -65,7 +68,7 @@ def get_envar_default(envar, envar_default=None, envar_set=Box(ordered_box=True) Get from the system environment, or the module dictionary (a Box): like the test one in __main__ below, TEST_ENV_VALUES = Box(ordered_box=True) - TEST_ENV_VALUES[ENVAR_LY_PROJECT] = '${0}'.format(ENVAR_LY_PROJECT) + TEST_ENV_VALUES[ENVAR_O3DE_PROJECT] = '${0}'.format(ENVAR_O3DE_PROJECT) This dictionary provides a simple way to pack a default set into a structure and decouple the getter implementation. @@ -88,7 +91,7 @@ def get_envar_default(envar, envar_default=None, envar_set=Box(ordered_box=True) # -- envar util ---------------------------------------------------------- -def set_envar_defaults(envar_set, env_root=get_envar_default(ENVAR_LY_DEV)): +def set_envar_defaults(envar_set, env_root=get_envar_default(ENVAR_O3DE_DEV)): """ Set each environment variable if not alreay set with value. Must be safe, will not over-write existing. @@ -98,8 +101,8 @@ def set_envar_defaults(envar_set, env_root=get_envar_default(ENVAR_LY_DEV)): env_root = Path(env_root) if env_root.exists(): - os.environ[ENVAR_LY_DEV] = env_root - envar_set[ENVAR_LY_DEV] = env_root + os.environ[ENVAR_O3DE_DEV] = env_root + envar_set[ENVAR_O3DE_DEV] = env_root else: raise ValueError("EnvVar Root is not valid: {0}".format(env_root)) @@ -107,7 +110,7 @@ def set_envar_defaults(envar_set, env_root=get_envar_default(ENVAR_LY_DEV)): envar = str(envar) value = os.getenv(envar) - if _G_DEBUG: + if _DCCSI_GDEBUG: if not value: _LOGGER.debug('~ EnVar value NOT found: {0}\r'.format(envar)) @@ -191,8 +194,8 @@ if __name__ == '__main__': # it should be benign but leaving this comment here in case of funk # tes envars - TEST_ENV_VALUES[ENVAR_LY_PROJECT] = '${0}'.format(ENVAR_LY_PROJECT) - TEST_ENV_VALUES[ENVAR_LY_DEV] = Path('${0}'.format(ENVAR_LY_DEV)) + TEST_ENV_VALUES[ENVAR_O3DE_PROJECT] = '${0}'.format(ENVAR_O3DE_PROJECT) + TEST_ENV_VALUES[ENVAR_O3DE_DEV] = Path('${0}'.format(ENVAR_O3DE_DEV)) # try to fetch and set the base values from the environment # this makes sure all envars set, are resolved on import @@ -204,7 +207,7 @@ if __name__ == '__main__': ensure_ascii=False), '\r') # simple tests - _ENV_TAG = 'LY_DEV' + _ENV_TAG = 'O3DE_DEV' foo = get_envar_default(_ENV_TAG) _LOGGER.info("~ Results of getVar on tag, '{0}':'{1}'\r".format(_ENV_TAG, foo)) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/__init__.py index c18cf9d81a..36a354a965 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/__init__.py @@ -13,31 +13,35 @@ SPDX-License-Identifier: Apache-2.0 OR MIT import os from pathlib import Path import logging as _logging +# ------------------------------------------------------------------------- -from azpy.env_bool import env_bool +# ------------------------------------------------------------------------- +# global space +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.shared.noodely' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) -# ------------------------------------------------------------------------- -# +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) + __all__ = ['find_arg', 'helpers', 'node', 'synth', 'synth_arg_kwarg', 'test_foo'] -# # ------------------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/find_arg.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/find_arg.py index 681c88b33c..dd939f3973 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/find_arg.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/find_arg.py @@ -74,7 +74,7 @@ if __name__ == "__main__": print ('~ find_arg.py ... Running script as __main__') print ("# ----------------------------------------------------------------------- #\r") - _G_DEBUG = True + _DCCSI_GDEBUG = True from test_foo import Foo @@ -102,7 +102,7 @@ if __name__ == "__main__": self._kwargsDict[key] = value # synthesize(self, '{0}'.format(key), value) <-- I have a method, # which synthesizes properties... with gettr, settr, etc. - if _G_DEBUG: + if _DCCSI_GDEBUG: print("{0}:{1}".format(key, value)) # representation diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/node.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/node.py index ca44b9a6c8..59e25d45e1 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/node.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/node.py @@ -47,13 +47,13 @@ from azpy.constants import ENVAR_DCCSI_DEV_MODE # global space # To Do: update to dynaconf dynamic env and settings? -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULENAME = 'azpy.shared.noodely.node' _log_level = int(20) -if _G_DEBUG: +if _DCCSI_GDEBUG: _log_level = int(10) _LOGGER = azpy.initialize_logger(_MODULENAME, log_to_file=False, @@ -66,7 +66,7 @@ _LOGGER.debug('Starting:: {}.'.format({_MODULENAME})) # quick test code (remove later) from hashids import Hashids hashids = Hashids(min_length=16, salt='DCCsi') -if _G_DEBUG: +if _DCCSI_GDEBUG: print (hashids.encrypt(193487)) # test hash # ------------------------------------------------------------------------- @@ -107,7 +107,7 @@ class Node(object): """Class constructor: makes a node.""" # share the debug state - _DEBUG = _G_DEBUG + _DEBUG = _DCCSI_GDEBUG # logger _LOGGER = _G_LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/pathnode.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/pathnode.py index 5c5402cb7d..401e524060 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/pathnode.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/noodely/pathnode.py @@ -47,13 +47,13 @@ from azpy.constants import ENVAR_DCCSI_DEV_MODE # ------------------------------------------------------------------------- # global space # To Do: update to dynaconf dynamic env and settings? -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULENAME = 'azpy.shared.noodely.pathnode' _log_level = int(20) -if _G_DEBUG: +if _DCCSI_GDEBUG: _log_level = int(10) _LOGGER = azpy.initialize_logger(_MODULENAME, log_to_file=False, @@ -78,7 +78,7 @@ class PathNode(Node): """doc string""" # share the debug state - _DEBUG = _G_DEBUG + _DEBUG = _DCCSI_GDEBUG # class header _message_header = 'noodly, PathNode(): Message' diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/__init__.py index a73ee00d7c..745ec9a7be 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/__init__.py @@ -11,24 +11,26 @@ """azpy.shared.ui.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.shared.ui' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) - +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) # ------------------------------------------------------------------------- # __all__ = [] diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/base_widget.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/base_widget.py index 311ed484ed..493130eb64 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/base_widget.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/base_widget.py @@ -34,7 +34,7 @@ from shiboken2 import wrapInstance # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = settings.DCCSI_GDEBUG +_DCCSI_GDEBUG = settings.DCCSI_GDEBUG # global space debug flag _DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/custom_treemodel.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/custom_treemodel.py index d7b096468d..871b050c7a 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/custom_treemodel.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/custom_treemodel.py @@ -30,7 +30,7 @@ import PySide2.QtGui as QtGui # ------------------------------------------------------------------------- # global space # To Do: update to dynaconf dynamic env and settings? -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) _MODULE_PATH = Path(__file__) @@ -38,7 +38,7 @@ _MODULE_PATH = Path(__file__) _MODULENAME = 'azpy.shared.ui.custom_treemodel' _log_level = int(20) -if _G_DEBUG: +if _DCCSI_GDEBUG: _log_level = int(10) _LOGGER = azpy.initialize_logger(_MODULENAME, log_to_file=False, diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/help_menu.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/help_menu.py index f4b53f6b79..7fdeadc4c2 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/help_menu.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/help_menu.py @@ -27,7 +27,7 @@ import PySide2.QtWidgets as QtWidgets # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = os.getenv('DCCSI_GDEBUG', False) +_DCCSI_GDEBUG = os.getenv('DCCSI_GDEBUG', False) # global space developer mode flag _DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_qtextedit_stdout.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_qtextedit_stdout.py index 6fd4f359bc..d14d5bcbd6 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_qtextedit_stdout.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_qtextedit_stdout.py @@ -32,7 +32,7 @@ from PySide2.QtCore import QTimer # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = os.getenv('DCCSI_GDEBUG', False) +_DCCSI_GDEBUG = os.getenv('DCCSI_GDEBUG', False) # global space debug flag _DCCSI_DEV_MODE = os.getenv('DCCSI_DEV_MODE', False) @@ -48,7 +48,7 @@ _MODULENAME = __name__ if _MODULENAME is '__main__': _MODULENAME = _TOOL_TAG -if _G_DEBUG: +if _DCCSI_GDEBUG: _LOGGER = initialize_logger(_MODULENAME, log_to_file=True) _LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME})) else: @@ -130,7 +130,7 @@ if __name__ == '__main__': _TOOL_TAG = 'azpy.shared.ui.pyside2_qtextedit_stdout' _TYPE_TAG = 'test' - if _G_DEBUG: + if _DCCSI_GDEBUG: _LOGGER = initialize_logger('{0}-TEST'.format(_TOOL_TAG), log_to_file=True) _LOGGER.debug('Something invoked :: {0}.'.format({_MODULENAME})) @@ -155,7 +155,7 @@ if __name__ == '__main__': _READER.start('python', ['-u', _TEST_PY_FILE]) # start the process # after that starts, this will show the console - # LY_QSS = Path(_MODULE_PATH.parent, 'resources', 'stylesheets', 'LYstyle.qss') + # O3DE_QSS = Path(_MODULE_PATH.parent, 'resources', 'stylesheets', 'LYstyle.qss') _DARK_STYLE = Path(_MODULE_PATH.parent, 'resources', 'qdarkstyle', 'style.qss') _CONSOLE.qapp.setStyleSheet(_DARK_STYLE.read_file()) _CONSOLE.show() # make the console visible diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_ui_utils.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_ui_utils.py index b996d9baf6..0f229e45b6 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_ui_utils.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/pyside2_ui_utils.py @@ -51,7 +51,7 @@ import azpy.shared.ui.help_menu as help_menu # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = settings.DCCSI_GDEBUG +_DCCSI_GDEBUG = settings.DCCSI_GDEBUG # global space debug flag _DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE @@ -112,7 +112,7 @@ def from_ui_generate_form_and_base_class(filename, return_output=False): ui_file.exists() except FileNotFoundError as error: output += 'File does not exist: {0}/r'.format(error) - if _G_DEBUG: + if _DCCSI_GDEBUG: print(error) if return_output: return False, output diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/qt_settings.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/qt_settings.py index de2bc1aaf4..28f6e23ac2 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/qt_settings.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/qt_settings.py @@ -24,7 +24,7 @@ import azpy.config_utils _config = azpy.config_utils.get_dccsi_config() # ^ this is effectively an import and retreive of \config.py # init lumberyard Qy/PySide2 access -_config.init_ly_pyside(settings.LY_DEV) +_config.init_o3de_pyside(settings.O3DE_DEV) # now we can import lumberyards PySide2 import PySide2.QtCore as QtCore @@ -32,7 +32,7 @@ import PySide2.QtWidgets as QtWidgets # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = settings.DCCSI_GDEBUG +_DCCSI_GDEBUG = settings.DCCSI_GDEBUG # global space debug flag _DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/templates.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/templates.py index a3700e8cf4..ec1d00b6d0 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/templates.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/shared/ui/templates.py @@ -47,7 +47,7 @@ import PySide2.QtUiTools as QtUiTools # ------------------------------------------------------------------------- # global space debug flag -_G_DEBUG = settings.DCCSI_GDEBUG +_DCCSI_GDEBUG = settings.DCCSI_GDEBUG # global space debug flag _DCCSI_DEV_MODE = settings.DCCSI_DEV_MODE diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/synthetic_env.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/synthetic_env.py index 71f2682242..33ac6c6814 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/synthetic_env.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/synthetic_env.py @@ -40,12 +40,11 @@ Configures several useful environment config settings and paths, [key] : [value] # this is the required base environment - LY_PROJECT : name of project (project directory) - LY_DEV : path to Lumberyard \dev root - LY_PROJECT_PATH : path to project dir + O3DE_PROJECT : name of project (project directory) + O3DE_DEV : path to Lumberyard \dev root + O3DE_PROJECT_PATH : path to project dir DCCSIG_PATH : path to the DCCsi Gem root - DCCSI_AZPY_PATH * : path to azpy Python API (code) - DCCSI_SDK_PATH : path to associated (non-api code) DCC SDK + DCCSI_TOOLS_PATH : path to associated (non-api code) DCC SDK # nice to haves in base env to define core support DCCSI_GDEBUG : sets global debug prints @@ -59,7 +58,7 @@ Configures several useful environment config settings and paths, :: Default version py37 has a launcher (activates the env, starts py interpreter) - set DCCSI_PY_BASE=%DCCSI_PYTHON_INSTALL%\python.exe + set DCCSI_PY_BASE=%O3DE_PYTHON_INSTALL%\python.exe :: shared location for 64bit python 3.7 BASE location set DCCSI_PY_DCCSI=%DCCSI_LAUNCHERS_PATH%\Launch_pyBASE.bat @@ -74,7 +73,7 @@ Configures several useful environment config settings and paths, :: shared location for 64bit DCCSI_PY_MAYA python 2.7 DEV location set DCCSI_PY_MAYA=%MAYA_LOCATION%\bin\mayapy.exe :: wingIDE can use more then one defined/managed interpreters - :: allowing you to _G_DEBUG code in multiple runtimes in one session + :: allowing you to _DCCSI_GDEBUG code in multiple runtimes in one session ${DCCSI_PY_MAYA} # related to the WING as the default DCCSI_GDEBUGGER @@ -92,8 +91,9 @@ import os import sys import site import re -#import inspect +import inspect import json +import importlib.util import logging as _logging from collections import OrderedDict @@ -110,35 +110,33 @@ _MODULE_PATH = os.path.realpath(__file__) # To Do: what if frozen? _DCCSIG_PATH = os.path.normpath(os.path.join(_MODULE_PATH, '../..')) _DCCSIG_PATH = os.getenv('DCCSIG_PATH', _DCCSIG_PATH) site.addsitedir(_DCCSIG_PATH) -print(_DCCSIG_PATH) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -# Lumberyard extensions +# O3DE extensions from pathlib import Path # set up global space, logging etc. -import azpy -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) -_PACKAGENAME = 'DCCsi.azpy.sunthetic_env' +_PACKAGENAME = 'DCCsi.azpy.synthetic_env' -_log_level = int(20) -if _G_DEBUG: - _log_level = int(10) -_LOGGER = azpy.initialize_logger(_PACKAGENAME, - log_to_file=True, - default_log_level=_log_level) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) -_LOGGER.debug('Starting up: {0}.'.format({_PACKAGENAME})) _LOGGER.debug('_DCCSIG_PATH: {}'.format(_DCCSIG_PATH)) -_LOGGER.debug('_G_DEBUG: {}'.format(_G_DEBUG)) +_LOGGER.debug('_DCCSI_GDEBUG: {}'.format(_DCCSI_GDEBUG)) _LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE)) if _DCCSI_DEV_MODE: @@ -170,7 +168,7 @@ if os.path.exists(_DCCSI_PYTHON_LIB_PATH): # ------------------------------------------------------------------------- # post-bootstrap global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) # ------------------------------------------------------------------------- @@ -225,7 +223,7 @@ def return_stub(stub='dccsi_stub'): break if (len(tail) == 0): path = "" - if _G_DEBUG: + if _DCCSI_GDEBUG: _LOGGER.debug('~Not able to find the path to that file ' '(stub) in a walk-up from currnet path.') break @@ -262,7 +260,7 @@ def get_stub_check_path(in_path, check_stub='engineroot.txt'): # ------------------------------------------------------------------------- # TO DO: Move to a util package or module -def resolve_envar_path(envar='LY_DEV', +def resolve_envar_path(envar='O3DE_DEV', start_path=__file__, check_stub='engineroot.txt', dir_name='dev', @@ -276,7 +274,7 @@ def resolve_envar_path(envar='LY_DEV', That is a pretty safe indicator that we found the right '\dev' - Second it checks if the env var 'LY_DEV' is set, use that instead! + Second it checks if the env var 'O3DE_DEV' is set, use that instead! """ @@ -360,14 +358,14 @@ def stash_env(_SYNTH_ENV_DICT = OrderedDict()): # \dev Lumberyard ROOT PATH # someone decided to use this as a root stub (for similar reasons in C++?) - # STUB_LY_DEV = str('engineroot.txt') + # STUB_O3DE_DEV = str('engineroot.txt') # I don't own \dev so I didn't want to check in anything new there - _LY_DEV = resolve_envar_path(ENVAR_LY_DEV, # envar + _O3DE_DEV = resolve_envar_path(ENVAR_O3DE_DEV, # envar _THIS_MODULE_PATH, # search path - STUB_LY_DEV, # stub - TAG_DIR_LY_DEV) # dir + STUB_O3DE_DEV, # stub + TAG_DIR_O3DE_DEV) # dir - _SYNTH_ENV_DICT[ENVAR_LY_DEV] = _LY_DEV.as_posix() + _SYNTH_ENV_DICT[ENVAR_O3DE_DEV] = _O3DE_DEV.as_posix() # project name is a string, it should be project dir name # for siloed testing and a purely synthetc env (nothing previously set) @@ -376,17 +374,17 @@ def stash_env(_SYNTH_ENV_DICT = OrderedDict()): # for testing overrides of the default synthetic env # we can do two things here, - # first we can try to fetch from the env os.getenv('LY_PROJECT') + # first we can try to fetch from the env os.getenv('O3DE_PROJECT') # If comes back None, allows you to specify a default fallback # changed to just make the fallback what is set in boostrap # so now it's less of a fallnack and more correct if not # explicitly set - _LY_PROJECT = os.getenv(ENVAR_LY_PROJECT) - _SYNTH_ENV_DICT[ENVAR_LY_PROJECT] = _LY_PROJECT + _O3DE_PROJECT = os.getenv(ENVAR_O3DE_PROJECT) + _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT] = _O3DE_PROJECT - _LY_BUILD_DIR_NAME = os.getenv(ENVAR_LY_BUILD_DIR_NAME, - TAG_DIR_LY_BUILD) - _SYNTH_ENV_DICT[ENVAR_LY_BUILD_DIR_NAME] = _LY_BUILD_DIR_NAME + _O3DE_BUILD_DIR_NAME = os.getenv(ENVAR_O3DE_BUILD_DIR_NAME, + TAG_DIR_O3DE_BUILD_FOLDER) + _SYNTH_ENV_DICT[ENVAR_O3DE_BUILD_DIR_NAME] = _O3DE_BUILD_DIR_NAME # pattern for the above is (and will be repeated) # _SOME_ENVAR = resolve_envar_path('ENVAR', @@ -406,31 +404,31 @@ def stash_env(_SYNTH_ENV_DICT = OrderedDict()): # so we guess based on how I set up the original dev environment # -- envar -- - _LY_BUILD_PATH = Path(os.getenv(ENVAR_LY_BUILD_PATH, - PATH_LY_BUILD_PATH)) - _SYNTH_ENV_DICT[ENVAR_LY_BUILD_PATH] = _LY_BUILD_PATH.as_posix() + _O3DE_BUILD_PATH = Path(os.getenv(ENVAR_O3DE_BUILD_PATH, + PATH_O3DE_BUILD_PATH)) + _SYNTH_ENV_DICT[ENVAR_O3DE_BUILD_PATH] = _O3DE_BUILD_PATH.as_posix() # -- envar -- - _LY_BIN_PATH = Path(os.getenv(ENVAR_LY_BIN_PATH, - PATH_LY_BIN_PATH)) + _O3DE_BIN_PATH = Path(os.getenv(ENVAR_O3DE_BIN_PATH, + PATH_O3DE_BIN_PATH)) # some of these need hard checks - if not _LY_BIN_PATH.exists(): - raise Exception('LY_BIN_PATH does NOT exist: {0}'.format(_LY_BIN_PATH)) + if not _O3DE_BIN_PATH.exists(): + raise Exception('O3DE_BIN_PATH does NOT exist: {0}'.format(_O3DE_BIN_PATH)) else: - _SYNTH_ENV_DICT[ENVAR_LY_BIN_PATH] = _LY_BIN_PATH.as_posix() + _SYNTH_ENV_DICT[ENVAR_O3DE_BIN_PATH] = _O3DE_BIN_PATH.as_posix() # adding to sys.path apparently doesn't work for .dll locations like Qt - os.environ['PATH'] = _LY_BIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] + os.environ['PATH'] = _O3DE_BIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] # -- envar -- # if that stub marker doesn't exist assume DCCsi path (fallback 1) - _LY_PROJECT_PATH = Path(os.getenv(ENVAR_LY_PROJECT_PATH, - Path(_LY_DEV, _LY_PROJECT))) - _SYNTH_ENV_DICT[ENVAR_LY_PROJECT_PATH] = _LY_PROJECT_PATH.as_posix() + _O3DE_PROJECT_PATH = Path(os.getenv(ENVAR_O3DE_PROJECT_PATH, + Path(_O3DE_DEV, _O3DE_PROJECT))) + _SYNTH_ENV_DICT[ENVAR_O3DE_PROJECT_PATH] = _O3DE_PROJECT_PATH.as_posix() # -- envar -- _DCCSIG_PATH = resolve_envar_path(ENVAR_DCCSIG_PATH, # envar _THIS_MODULE_PATH, # search path - STUB_LY_ROOT_DCCSI, # stub name + STUB_O3DE_ROOT_DCCSI, # stub name TAG_DEFAULT_PROJECT) # dir _SYNTH_ENV_DICT[ENVAR_DCCSIG_PATH] = _DCCSIG_PATH.as_posix() @@ -440,9 +438,9 @@ def stash_env(_SYNTH_ENV_DICT = OrderedDict()): _SYNTH_ENV_DICT[ENVAR_DCCSI_AZPY_PATH] = _AZPY_PATH.as_posix() # -- envar -- - _DCCSI_SDK_PATH = Path(os.getenv(ENVAR_DCCSI_SDK_PATH, - Path(_DCCSIG_PATH, TAG_DIR_DCCSI_SDK))) - _SYNTH_ENV_DICT[ENVAR_DCCSI_SDK_PATH] = _DCCSI_SDK_PATH.as_posix() + _DCCSI_TOOLS_PATH = Path(os.getenv(ENVAR_DCCSI_TOOLS_PATH, + Path(_DCCSIG_PATH, TAG_DIR_DCCSI_TOOLS))) + _SYNTH_ENV_DICT[ENVAR_DCCSI_TOOLS_PATH] = _DCCSI_TOOLS_PATH.as_posix() # -- envar -- # external dccsi site-packages @@ -497,7 +495,7 @@ def init_ly_pyside(env_dict=_SYNTH_ENV_DICT): - QTFORPYTHON_PATH = Path.joinpath(LY_DEV, + QTFORPYTHON_PATH = Path.joinpath(O3DE_DEV, 'Gems', 'QtForPython', '3rdParty', @@ -509,16 +507,16 @@ def init_ly_pyside(env_dict=_SYNTH_ENV_DICT): sys.path.insert(1, str(QTFORPYTHON_PATH)) site.addsitedir(str(QTFORPYTHON_PATH)) - LY_BIN_PATH = Path.joinpath(LY_DEV, + O3DE_BIN_PATH = Path.joinpath(O3DE_DEV, 'windows_vs2019', 'bin', 'profile').resolve() - os.environ["DYNACONF_LY_BIN_PATH"] = str(LY_BIN_PATH) - os.environ["LY_BIN_PATH"] = str(LY_BIN_PATH) - site.addsitedir(str(LY_BIN_PATH)) - sys.path.insert(1, str(LY_BIN_PATH)) + os.environ["DYNACONF_O3DE_BIN_PATH"] = str(O3DE_BIN_PATH) + os.environ["O3DE_BIN_PATH"] = str(O3DE_BIN_PATH) + site.addsitedir(str(O3DE_BIN_PATH)) + sys.path.insert(1, str(O3DE_BIN_PATH)) - QT_PLUGIN_PATH = Path.joinpath(LY_BIN_PATH, + QT_PLUGIN_PATH = Path.joinpath(O3DE_BIN_PATH, 'EditorPlugins').resolve() os.environ["DYNACONF_QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH) os.environ["QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH) @@ -536,7 +534,7 @@ def init_ly_pyside(env_dict=_SYNTH_ENV_DICT): if sys.platform.startswith('win'): path = os.environ['PATH'] newPath = '' - newPath += str(LY_BIN_PATH) + os.pathsep + newPath += str(O3DE_BIN_PATH) + os.pathsep newPath += str(Path.joinpath(QTFORPYTHON_PATH, 'shiboken2').resolve()) + os.pathsep newPath += str(Path.joinpath(QTFORPYTHON_PATH, @@ -591,12 +589,12 @@ def set_env(dict_object): def test_Qt(): try: import PySide2 - print('PySide2: {0}'.format(Path(PySide2.__file__).as_posix())) + _LOGGER.info('PySide2: {0}'.format(Path(PySide2.__file__).as_posix())) # builtins.ImportError: DLL load failed: The specified procedure could not be found. from PySide2 import QtCore from PySide2 import QtWidgets except IOError as e: - print('ERROR: {0}'.format(e)) + _LOGGER.error('ERROR: {0}'.format(e)) raise e try: @@ -610,7 +608,7 @@ def test_Qt(): qapp.instance().quit qapp.exit() except Exception as e: - print('ERROR: {0}'.format(e)) + _LOGGER.error('ERROR: {0}'.format(e)) raise e # ------------------------------------------------------------------------- @@ -620,14 +618,14 @@ def main(argv, env_dict_object, debug=False, devmode=False): import getopt try: opts, args = getopt.getopt(argv, "hvt:", ["verbose=", "test="]) - except getopt.GetoptError: + except getopt.GetoptError as e: # not logging, print to cmd line console - print('synthetic_env.py -v -t ') + _LOGGER.error('synthetic_env.py -v -t ') sys.exit(2) for opt, arg in opts: if opt == '-h': - print('synthetic_env.py -v -t ') + _LOGGER.info('synthetic_env.py -v -t ') sys.exit() elif opt in ("-t", "--test"): @@ -639,14 +637,14 @@ def main(argv, env_dict_object, debug=False, devmode=False): try: from box import Box except ImportError as e: - print('ERROR: {0}'.format(e)) + _LOGGER.error('ERROR: {0}'.format(e)) raise e try: env_dict_object = Box(env_dict_object) - print(str(env_dict_object.to_json(sort_keys=False, + _LOGGER.info(str(env_dict_object.to_json(sort_keys=False, indent=4))) except Exception as e: - print('ERROR: {0}'.format(e)) + _LOGGER.error('ERROR: {0}'.format(e)) raise e # ------------------------------------------------------------------------- @@ -656,16 +654,16 @@ def main(argv, env_dict_object, debug=False, devmode=False): # ------------------------------------------------------------------------- if __name__ == '__main__': # run simple tests? - _G_DEBUG = True + _DCCSI_GDEBUG = True _DCCSI_DEV_MODE = True if _DCCSI_DEV_MODE: try: import azpy.test.entry_test - print('SUCCESS: import azpy.test.entry_test') + _LOGGER.info('SUCCESS: import azpy.test.entry_test') azpy.test.entry_test.main(verbose=True, connect_debugger=True) except ImportError as e: - print('ERROR: {0}'.format(e)) + _LOGGER.error('ERROR: {0}'.format(e)) raise e # init, stash and then activate @@ -673,9 +671,9 @@ if __name__ == '__main__': _SYNTH_ENV_DICT = stash_env(_SYNTH_ENV_DICT) _SYNTH_ENV_DICT = set_env(_SYNTH_ENV_DICT) - main(sys.argv[1:], _SYNTH_ENV_DICT, _G_DEBUG, _DCCSI_DEV_MODE) + main(sys.argv[1:], _SYNTH_ENV_DICT, _DCCSI_GDEBUG, _DCCSI_DEV_MODE) - if _G_DEBUG: + if _DCCSI_GDEBUG: tempBoxJsonFilePath = Path(_SYNTH_ENV_DICT['DCCSIG_PATH'], '.temp') tempBoxJsonFilePath = Path(tempBoxJsonFilePath, 'boxDumpTest.json') diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/__init__.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/__init__.py index 20df0e5813..554d320654 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/__init__.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/__init__.py @@ -12,28 +12,44 @@ # importing all of the modules """azpy.test.__init__""" -import os +import logging as _logging -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) _PACKAGENAME = __name__ if _PACKAGENAME is '__main__': _PACKAGENAME = 'azpy.test' -import azpy -_LOGGER = azpy.initialize_logger(_PACKAGENAME) -_LOGGER.debug('Invoking __init__.py for {0}.'.format({_PACKAGENAME})) - -# ------------------------------------------------------------------------- +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_PACKAGENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_PACKAGENAME})) __all__ = ['entry_test'] +# ------------------------------------------------------------------------- + +# ------------------------------------------------------------------------- +def init(): + """If the substance api is required for a package/module to import, + then it should be initialized and added here so general imports + don't fail""" + + # Make sure we can import the native apis + # import + + # __all__.append() + + # Importing local packages/modules + pass # ------------------------------------------------------------------------- @@ -48,20 +64,4 @@ if _DCCSI_DEV_MODE: # ------------------------------------------------------------------------- -# ------------------------------------------------------------------------- -def init(): - """If the substance api is required for a package/module to import, - then it should be initialized and added here so general imports - don't fail""" - - # __all__.append() - - # Make sure we can import the native apis - # import - - # Importing local packages/modules - pass - -# ------------------------------------------------------------------------- - del _LOGGER diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/entry_test.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/entry_test.py index d9d136f96e..aad440d11f 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/entry_test.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/azpy/test/entry_test.py @@ -11,9 +11,9 @@ from __future__ import unicode_literals # ------------------------------------------------------------------------- -import sys import os import site +import logging as _logging # note: some modules not available in py2.7 unless we boostrap with config.py # See example: @@ -23,34 +23,34 @@ from pathlib import Path # ------------------------------------------------------------------------- _BOOT_CHECK = False # set true to test breakpoint in this module directly -import azpy -from azpy.env_bool import env_bool +import azpy.env_bool as env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import FRMT_LOG_LONG -# global space -# To Do: update to dynaconf dynamic env and settings? -_G_DEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) -_DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) +_DCCSI_GDEBUG = env_bool.env_bool(ENVAR_DCCSI_GDEBUG, False) +_DCCSI_DEV_MODE = env_bool.env_bool(ENVAR_DCCSI_DEV_MODE, False) -_MODULENAME = 'azpy.test.entry_test' +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'azpy.test.entry_test' -_log_level = int(20) -if _G_DEBUG: - _log_level = int(10) -_LOGGER = azpy.initialize_logger(_MODULENAME, - log_to_file=False, - default_log_level=_log_level) -_LOGGER.debug('Starting:: {}.'.format({_MODULENAME})) +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) +_LOGGER = _logging.getLogger(_MODULENAME) +_logging.basicConfig(format=FRMT_LOG_LONG) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -def main(verbose=_G_DEBUG, connect_debugger=True): - _LOGGER.info('{}'.format('-' * 74)) - _LOGGER.info('entry_test.main()') - _LOGGER.info('Root test import successful:') - _LOGGER.info('~ {}'.format(__file__)) +def main(verbose=_DCCSI_GDEBUG, connect_debugger=True): + if verbose: + _LOGGER.info('{}'.format('-' * 74)) + _LOGGER.info('entry_test.main()') + _LOGGER.info('Root test import successful:') + _LOGGER.info('~ {}'.format(__file__)) if connect_debugger: status = connect_wing() @@ -67,7 +67,7 @@ def connect_wing(): _WINGHOME = os.environ['WINGHOME'] # test _LOGGER.info('~ WINGHOME: {0}'.format(_WINGHOME)) except Exception as e: - _LOGGER.warning(e) + _LOGGER.info(e) from azpy.constants import PATH_DEFAULT_WINGHOME _WINGHOME = PATH_DEFAULT_WINGHOME os.environ['WINGHOME'] = PATH_DEFAULT_WINGHOME @@ -134,5 +134,5 @@ def connect_wing(): # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- if __name__ == '__main__': - _G_DEBUG = True - main(verbose=_G_DEBUG, connect_debugger=_G_DEBUG) + _DCCSI_GDEBUG = True + main(verbose=_DCCSI_GDEBUG, connect_debugger=_DCCSI_GDEBUG) diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/config.py b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/config.py index 0c25d16133..5578bcd577 100755 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/config.py +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/config.py @@ -6,8 +6,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ # ------------------------------------------------------------------------- -"""Extend the .env using dynaconf (dynamic configuration and settings) -This config.py module assumes a minimal enviornment is defined in the .env +""".config +Generate dynamic and synethetic environment contest and settings +using dynaconf (dynamic configuration and settings) +This config.py synthetic env can be overriden or extended with a local .env +See: example.env.tmp (copy and rename to .env) To do: ensure that we can stack/layer the dynamic env to work with O3DE projects """ @@ -16,60 +19,97 @@ import os import sys import site import re +import logging as _logging # 3rdParty (possibly) py3 ships with pathlib, 2.7 does not # import pathlib # our framework for dcc tools need to run in apps like Maya that may still be # on py27 so we need to import and use after some boostrapping +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +_O3DE_RUNNING=None +try: + import azlmbr + _O3DE_RUNNING=True +except: + _O3DE_RUNNING=False +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def attach_debugger(): + _DCCSI_GDEBUG = True + os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG) + + _DCCSI_DEV_MODE = True + os.environ["DYNACONF_DCCSI_DEV_MODE"] = str(_DCCSI_DEV_MODE) + + from azpy.test.entry_test import connect_wing + _debugger = connect_wing() + + return _debugger +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +# global scope +_MODULENAME = __name__ +if _MODULENAME is '__main__': + _MODULENAME = 'DCCsi.config' -# -------------------------------------------------------------------+------ #os.environ['PYTHONINSPECT'] = 'True' _MODULE_PATH = os.path.abspath(__file__) # we don't have access yet to the DCCsi Lib\site-packages # (1) this will give us import access to azpy (always?) -_DCCSIG_PATH = os.getenv('DCCSIG_PATH', +_DCCSI_PATH = os.getenv('DCCSI_PATH', os.path.abspath(os.path.dirname(_MODULE_PATH))) # ^ we assume this config is in the root of the DCCsi -# if it's not, be sure to set envar 'DCCSIG_PATH' to ensure it -site.addsitedir(_DCCSIG_PATH) # PYTHONPATH +# if it's not, be sure to set envar 'DCCSI_PATH' to ensure it +site.addsitedir(_DCCSI_PATH) # must be done for azpy # now we have azpy api access import azpy from azpy.env_bool import env_bool from azpy.constants import ENVAR_DCCSI_GDEBUG from azpy.constants import ENVAR_DCCSI_DEV_MODE +from azpy.constants import ENVAR_DCCSI_LOGLEVEL # set up global space, logging etc. # set these true if you want them set globally for debugging _DCCSI_GDEBUG = env_bool(ENVAR_DCCSI_GDEBUG, False) _DCCSI_DEV_MODE = env_bool(ENVAR_DCCSI_DEV_MODE, False) - -_PACKAGENAME = 'DCCsi.config' - -_LOG_LEVEL = int(20) +_DCCSI_LOGLEVEL = int(env_bool(ENVAR_DCCSI_LOGLEVEL, int(20))) if _DCCSI_GDEBUG: - _LOG_LEVEL = int(10) -_LOGGER = azpy.initialize_logger(_PACKAGENAME, - log_to_file=False, - default_log_level=_LOG_LEVEL) -_LOGGER.info('Starting up: {}.'.format({_PACKAGENAME})) -_LOGGER.info('site.addsitedir({})'.format(_DCCSIG_PATH)) -_LOGGER.debug('_DCCSI_GDEBUG: {}'.format(_DCCSI_GDEBUG)) -_LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE)) + _DCCSI_LOGLEVEL = int(10) # early attach WingIDE debugger (can refactor to include other IDEs later) +# requires externally enabling via ENVAR if _DCCSI_DEV_MODE: - from azpy.test.entry_test import connect_wing - foo = connect_wing() + _debugger = attach_debugger() # to do: ^ this should be replaced with full featured azpy.dev.util # that supports additional debuggers (pycharm, vscode, etc.) + +# set up module logging +for handler in _logging.root.handlers[:]: + _logging.root.removeHandler(handler) + +_LOGGER = azpy.initialize_logger(_MODULENAME, + log_to_file=_DCCSI_GDEBUG, + default_log_level=_DCCSI_LOGLEVEL) +_LOGGER.debug('Initializing: {0}.'.format({_MODULENAME})) +_LOGGER.info('site.addsitedir({})'.format(_DCCSI_PATH)) +_LOGGER.debug('_DCCSI_GDEBUG: {}'.format(_DCCSI_GDEBUG)) +_LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE)) +_LOGGER.debug('_DCCSI_LOGLEVEL: {}'.format(_DCCSI_LOGLEVEL)) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -# (2) this will give us import access to modules we provide -_DCCSI_PYTHON_LIB_PATH = azpy.config_utils.bootstrap_dccsi_py_libs(_DCCSIG_PATH) +# this will give us import access to additional modules we provide with DCCsi +_DCCSI_PYTHON_LIB_PATH = azpy.config_utils.bootstrap_dccsi_py_libs(_DCCSI_PATH) # Now we should be able to just carry on with pth lib and dynaconf from dynaconf import Dynaconf @@ -79,82 +119,103 @@ except: import pathlib2 as pathlib from pathlib import Path -_DCCSIG_PATH = Path(_DCCSIG_PATH).resolve() -_DCCSI_PYTHON_LIB_PATH = Path(_DCCSI_PYTHON_LIB_PATH).resolve() +_DCCSI_PATH = Path(_DCCSI_PATH) # pathify +_DCCSI_PYTHON_PATH = Path(_DCCSI_PATH,'3rdParty','Python') +_DCCSI_PYTHON_LIB_PATH = Path(_DCCSI_PYTHON_LIB_PATH) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -def init_ly_pyside(LY_DEV=None): - """sets access to lumberyards Qt dlls and PySide""" +# start locally prepping known default values for dyanmic environment settings +_O3DE_DCCSI_PATH = os.environ['PATH'] +os.environ["DYNACONF_PATH"] = _O3DE_DCCSI_PATH - LY_DEV = Path(LY_DEV).resolve() - if not LY_DEV.exists(): - raise Exception('LY_DEV does NOT exist: {0}'.format(LY_DEV)) +# this will retreive the O3DE engine root +_O3DE_DEV = azpy.config_utils.get_o3de_engine_root() +# set up dynamic config envars +os.environ["DYNACONF_O3DE_DEV"] = str(_O3DE_DEV.resolve()) + +from azpy.constants import TAG_DIR_O3DE_BUILD_FOLDER +_O3DE_BUILD_FOLDER = TAG_DIR_O3DE_BUILD_FOLDER +os.environ["DYNACONF_O3DE_BUILD_FOLDER"] = str(_O3DE_BUILD_FOLDER) +_O3DE_BUILD_PATH = Path(_O3DE_DEV, TAG_DIR_O3DE_BUILD_FOLDER) +os.environ["DYNACONF_O3DE_BUILD_PATH"] = str(_O3DE_BUILD_PATH.resolve()) + +from azpy.constants import STR_O3DE_BIN_PATH +_O3DE_BIN_PATH = Path(STR_O3DE_BIN_PATH.format(_O3DE_BUILD_PATH)) +os.environ["DYNACONF_O3DE_BIN_PATH"] = str(_O3DE_BIN_PATH.resolve()) + +# this in most cases will return the project folder +# if it returns a matching engine folder then we don't know the project folder +_O3DE_PROJECT_PATH = azpy.config_utils.get_o3de_project_path() +os.environ["DYNACONF_O3DE_PROJECT_PATH"] = str(_O3DE_PROJECT_PATH.resolve()) + +# special, a home for stashing PYTHONPATHs into managed settings +_O3DE_PYTHONPATH = list() +_O3DE_PYTHONPATH.append(_DCCSI_PATH) +# ------------------------------------------------------------------------- + + +# ------------------------------------------------------------------------- +def init_o3de_pyside2(dccsi_path=_DCCSI_PATH, + engine_bin=_O3DE_BIN_PATH): + """Initialize the DCCsi Qt/PySide dynamic env and settings + sets access to lumberyards Qt dlls and PySide""" + + _DCCSI_PATH = Path(dccsi_path) + _O3DE_BIN_PATH = Path(engine_bin) + + if not _O3DE_BIN_PATH.exists(): + raise Exception('_O3DE_BIN_PATH does NOT exist: {0}'.format(_O3DE_BIN_PATH)) else: - # to do: 'windows_vs2019' might change or be different locally - # 'windows_vs2019' is defined as a str tag in constants - # we may not yet have access to azpy.constants :( - from azpy.constants import TAG_DIR_LY_BUILD - from azpy.constants import PATH_LY_BUILD_PATH - from azpy.constants import PATH_LY_BIN_PATH - # to do: pull some of these str and tags from constants - LY_BUILD_PATH = Path.joinpath(LY_DEV, - TAG_DIR_LY_BUILD).resolve() - LY_BIN_PATH = Path.joinpath(LY_BUILD_PATH, - 'bin', - 'profile').resolve() + pass + + # python config + _DCCSI_PYTHON_PATH = Path(_DCCSI_PATH,'3rdParty','Python') + os.environ["DYNACONF_DCCSI_PYTHON_PATH"] = str(_DCCSI_PYTHON_PATH.resolve()) # # allows to retreive from settings.QTFORPYTHON_PATH # from azpy.constants import STR_QTFORPYTHON_PATH # a path string constructor - # QTFORPYTHON_PATH = Path(STR_QTFORPYTHON_PATH.format(LY_DEV)).resolve() + # QTFORPYTHON_PATH = Path(STR_QTFORPYTHON_PATH.format(O3DE_DEV)).resolve() # os.environ["DYNACONF_QTFORPYTHON_PATH"] = str(QTFORPYTHON_PATH) # site.addsitedir(str(QTFORPYTHON_PATH)) # PYTHONPATH - QT_PLUGIN_PATH = Path.joinpath(LY_BIN_PATH, - 'EditorPlugins').resolve() - os.environ["DYNACONF_QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH) + QT_PLUGIN_PATH = Path.joinpath(_O3DE_BIN_PATH,'EditorPlugins') + os.environ["DYNACONF_QT_PLUGIN_PATH"] = str(QT_PLUGIN_PATH.resolve()) os.environ['PATH'] = QT_PLUGIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] - QT_QPA_PLATFORM_PLUGIN_PATH = Path.joinpath(QT_PLUGIN_PATH, - 'platforms').resolve() - os.environ["DYNACONF_QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH) + QT_QPA_PLATFORM_PLUGIN_PATH = Path.joinpath(QT_PLUGIN_PATH, 'platforms') + os.environ["DYNACONF_QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH.resolve()) # if the line below is removed external standalone apps can't load PySide2 - os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH) + os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(QT_QPA_PLATFORM_PLUGIN_PATH.resolve()) # ^^ bypass trying to set only with DYNACONF environment os.environ['PATH'] = QT_QPA_PLATFORM_PLUGIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] # ^^ this particular env only works correctly if put on the PATH in this manner # add Qt binaries to the Windows path to handle findings DLL file dependencies if sys.platform.startswith('win'): - # path = os.environ['PATH'] - # newPath = '' - # newPath += str(LY_BIN_PATH) + os.pathsep - # newPath += str(Path.joinpath(QTFORPYTHON_PATH, - # 'shiboken2').resolve()) + os.pathsep - # newPath += str(Path.joinpath(QTFORPYTHON_PATH, - # 'PySide2').resolve()) + os.pathsep - # newPath += path - # os.environ['PATH']=newPath - _LOGGER.debug('PySide2 bootstrapped PATH for Windows.') + _LOGGER.info('~ Qt/PySide2 bootstrapped PATH for Windows.') + else: + _LOGGER.warning('~ Not tested on Non-Windows platforms.') + # To Do: figure out how to test and/or modify to work try: import PySide2 - _LOGGER.debug('DCCsi, config.py: SUCCESS: import PySide2') + _LOGGER.info('~ SUCCESS: import PySide2') _LOGGER.debug(PySide2) status = True except ImportError as e: - _LOGGER.debug('DCCsi, config.py: FAILURE: import PySide2') + _LOGGER.error('~ FAILURE: import PySide2') status = False raise(e) try: import shiboken2 - _LOGGER.debug('DCCsi, config.py: SUCCESS: import shiboken2') + _LOGGER.info('~ SUCCESS: import shiboken2') _LOGGER.debug(shiboken2) status = True except ImportError as e: - _LOGGER.debug('DCCsi, config.py: FAILURE: import shiboken2') + _LOGGER.error('~ FAILURE: import shiboken2') status = False raise(e) @@ -162,19 +223,44 @@ def init_ly_pyside(LY_DEV=None): # to do: move path construction string to constants and build off of SDK # have not done that yet as I really want to get legal approval and # add this to the QtForPython Gem - # please pass this on the current code review - DCCSI_PYSIDE2_TOOLS = Path.joinpath(LY_DEV, - 'Gems', - 'AtomLyIntegration', - 'TechnicalArt', - 'DccScriptingInterface', - '.dev', - 'QtForPython', - 'pyside2-tools-dev') - os.environ["DYNACONF_DCCSI_PYSIDE2_TOOLS"] = str(DCCSI_PYSIDE2_TOOLS.resolve()) - os.environ['PATH'] = DCCSI_PYSIDE2_TOOLS.as_posix() + os.pathsep + os.environ['PATH'] + # please pass this in current code reviews + _DCCSI_PYSIDE2_TOOLS = Path(_DCCSI_PYTHON_PATH,'pyside2-tools') + if _DCCSI_PYSIDE2_TOOLS.exists(): + os.environ["DYNACONF_DCCSI_PYSIDE2_TOOLS"] = str(_DCCSI_PYSIDE2_TOOLS.resolve()) + os.environ['PATH'] = _DCCSI_PYSIDE2_TOOLS.as_posix() + os.pathsep + os.environ['PATH'] + + site.addsitedir(_DCCSI_PYSIDE2_TOOLS) + _O3DE_PYTHONPATH.append(_DCCSI_PYSIDE2_TOOLS.resolve()) + _LOGGER.info('~ PySide2-Tools bootstrapped PATH for Windows.') + try: + import pyside2uic + _LOGGER.info('~ SUCCESS: import pyside2uic') + _LOGGER.debug(shiboken2) + status = True + except ImportError as e: + _LOGGER.error('~ FAILURE: import pyside2uic') + status = False + raise(e) + else: + _LOGGER.warning('~ No PySide2 Tools: {}'.format(_DCCSI_PYSIDE2_TOOLS.resolve)) + + _O3DE_DCCSI_PATH = os.environ['PATH'] + os.environ["DYNACONF_PATH"] = _O3DE_DCCSI_PATH + + try: + _DCCSI_PYTHONPATH = os.environ['PYTHONPATH'] + os.environ["DYNACONF_PYTHONPATH"] = _DCCSI_PYTHONPATH + except: + pass - return status + from dynaconf import settings + + _LOGGER.info('~ config.init_o3de_pyside() ... DONE') + + if status: + return settings + else: + return None # ------------------------------------------------------------------------- @@ -182,90 +268,219 @@ def init_ly_pyside(LY_DEV=None): def test_pyside2(): """Convenience method to test Qt / PySide2 access""" # now test - _LOGGER.info('Testing Qt / PySide2') + _LOGGER.info('~ Testing Qt / PySide2') try: from PySide2.QtWidgets import QApplication, QPushButton app = QApplication(sys.argv) - hello = QPushButton("Hello world!") + hello = QPushButton("~ O3DE DCCsi PySide2 Test!") hello.resize(200, 60) hello.show() except Exception as e: - _LOGGER.error('FAILURE: Qt / PySide2') + _LOGGER.error('~ FAILURE: Qt / PySide2') status = False raise(e) - _LOGGER.info('SUCCESS: .test_pyside2()') + _LOGGER.info('~ SUCCESS: .test_pyside2()') sys.exit(app.exec_()) # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- -# `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. -# `settings_files` = Load this files in the order. -# here we are modifying or adding to the dynamic config settings on import -settings = Dynaconf( - envvar_prefix="DYNACONF", - settings_files=['settings.json', '.secrets.json'], -) +def init_o3de_core(engine_path=_O3DE_DEV, + build_folder=_O3DE_BUILD_FOLDER, + project_name=None, + project_path=_O3DE_PROJECT_PATH): + """Initialize the DCCsi Core dynamic env and settings""" + # `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`. + # `settings_files` = Load this files in the order. + # here we are modifying or adding to the dynamic config settings on import + settings = Dynaconf(envvar_prefix='DYNACONF', + settings_files=['settings.json', + 'dev.settings.json', + 'user.settings.json', + '.secrets.json']) + + # global settings + os.environ["DYNACONF_DCCSI_OS_FOLDER"] = azpy.config_utils.get_os() + os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG) + os.environ["DYNACONF_DCCSI_DEV_MODE"] = str(_DCCSI_DEV_MODE) + os.environ['DYNACONF_DCCSI_LOGLEVEL'] = str(_DCCSI_LOGLEVEL) -from azpy.constants import PATH_LY_BUILD_PATH -from azpy.constants import PATH_LY_BIN_PATH + os.environ["DYNACONF_DCCSI_PATH"] = str(_DCCSI_PATH.resolve()) + os.environ['PATH'] = _DCCSI_PATH.as_posix() + os.pathsep + os.environ['PATH'] -# global settings -os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG) -os.environ["DYNACONF_DCCSI_DEV_MODE"] = str(_DCCSI_DEV_MODE) + # we already defaulted to discovering these two early because of importance + #os.environ["DYNACONF_O3DE_DEV"] = str(_O3DE_DEV.resolve()) + #os.environ["DYNACONF_O3DE_PROJECT_PATH"] = str(_O3DE_PROJECT_PATH) + # we also already added them to DYNACONF_ + # this in an explicit pass in + if project_path: + _project_path = Path(project_path) + try: + _project_path.exists() + _O3DE_PROJECT_PATH = _project_path + os.environ["DYNACONF_O3DE_PROJECT_PATH"] = str(_O3DE_PROJECT_PATH.resolve()) + except FileExistsError as e: + _LOGGER.error('~ The project path specified does not appear to exist!') + _LOGGER.warning('~ project_path: {}'.format(project_path)) + _LOGGER.warning('~ fallback to engine root: {}'.format()) + project_path = _O3DE_DEV + os.environ["DYNACONF_O3DE_PROJECT_PATH"] = str(_O3DE_DEV.resolve()) -# search up to get \dev -_LY_DEV = azpy.config_utils.get_stub_check_path(in_path=_DCCSIG_PATH, - check_stub='engine.json') -os.environ["DYNACONF_LY_DEV"] = str(_LY_DEV.resolve()) -_LY_PROJECT = azpy.config_utils.get_current_project() -os.environ["DYNACONF_LY_PROJECT"] = str(_LY_PROJECT.resolve()) -_LY_PROJECT_PATH = Path(_LY_DEV, _LY_PROJECT) -os.environ["DYNACONF_LY_PROJECT_PATH"] = str(_LY_PROJECT_PATH) -os.environ["DYNACONF_DCCSIG_PATH"] = str(_DCCSIG_PATH) -_DCCSI_CONFIG_PATH = Path(_MODULE_PATH).resolve() -os.environ["DYNACONF_DCCSI_CONFIG_PATH"] = str(_DCCSI_CONFIG_PATH) -_DCCSIG_SDK_PATH = Path.joinpath(_DCCSIG_PATH, 'SDK').resolve() -os.environ["DYNACONF_DCCSIG_SDK_PATH"] = str(_DCCSIG_SDK_PATH) -os.environ["DYNACONF_DCCSI_PYTHON_LIB_PATH"] = str(_DCCSI_PYTHON_LIB_PATH) -os.environ["DYNACONF_OS_FOLDER"] = azpy.config_utils.get_os() + # we can pull the O3DE_PROJECT (name) from the project path + if not project_name: + project_name = Path(_O3DE_PROJECT_PATH).name + os.environ["DYNACONF_O3DE_PROJECT"] = str(project_name) + # To Do: there might be a project namespace in the project.json? -# we need to set up the Ly dev build \bin\path (for Qt dll access) -_LY_BUILD_PATH = Path(PATH_LY_BUILD_PATH).resolve() -os.environ["DYNACONF_LY_BUILD_PATH"] = str(_LY_BUILD_PATH) -_LY_BIN_PATH = Path(PATH_LY_BIN_PATH).resolve() -os.environ["DYNACONF_LY_BIN_PATH"] = str(_LY_BIN_PATH) + # -- O3DE build -- set up \bin\path (for Qt dll access) + os.environ["DYNACONF_O3DE_BUILD_FOLDER"] = str(build_folder) + _O3DE_BUILD_PATH = Path(_O3DE_DEV, build_folder) + + os.environ["DYNACONF_O3DE_BUILD_PATH"] = str(_O3DE_BUILD_PATH.resolve()) + + _O3DE_BIN_PATH = Path(STR_O3DE_BIN_PATH.format(_O3DE_BUILD_PATH)) + os.environ["DYNACONF_O3DE_BIN_PATH"] = str(_O3DE_BIN_PATH.resolve()) + + # hard check + if not _O3DE_BIN_PATH.exists(): + raise Exception('O3DE_BIN_PATH does NOT exist: {0}'.format(_O3DE_BIN_PATH)) + else: + # adding to sys.path apparently doesn't work for .dll locations like Qt + os.environ['PATH'] = _O3DE_BIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] + # -- + + from azpy.constants import TAG_DIR_DCCSI_TOOLS + _DCCSI_TOOLS_PATH = Path(_DCCSI_PATH, TAG_DIR_DCCSI_TOOLS) + os.environ["DYNACONF_DCCSI_TOOLS_PATH"] = str(_DCCSI_TOOLS_PATH.resolve()) + + from azpy.constants import TAG_DCCSI_NICKNAME + from azpy.constants import PATH_DCCSI_LOG_PATH + _DCCSI_LOG_PATH = Path(PATH_DCCSI_LOG_PATH.format(O3DE_PROJECT_PATH=project_path, + TAG_DCCSI_NICKNAME=TAG_DCCSI_NICKNAME)) + os.environ["DYNACONF_DCCSI_LOG_PATH"] = str(_DCCSI_LOG_PATH) + + from azpy.constants import TAG_DIR_REGISTRY, TAG_DCCSI_CONFIG + _DCCSI_CONFIG_PATH = Path(project_path, TAG_DIR_REGISTRY, TAG_DCCSI_CONFIG) + os.environ["DYNACONF_DCCSI_CONFIG_PATH"] = str(_DCCSI_CONFIG_PATH.resolve()) + + from azpy.constants import TAG_DIR_DCCSI_TOOLS + _DCCSIG_TOOLS_PATH = Path.joinpath(_DCCSI_PATH, TAG_DIR_DCCSI_TOOLS) + os.environ["DYNACONF_DCCSIG_TOOLS_PATH"] = str(_DCCSIG_TOOLS_PATH.resolve()) + + _O3DE_DCCSI_PATH = os.environ['PATH'] + os.environ["DYNACONF_PATH"] = _O3DE_DCCSI_PATH + + from dynaconf import settings + + _LOGGER.info('~ config.init_o3de_core() ... DONE') + + return settings +# ------------------------------------------------------------------------- -# project cache log dir path -from azpy.constants import ENVAR_DCCSI_LOG_PATH -from azpy.constants import PATH_DCCSI_LOG_PATH -_DCCSI_LOG_PATH = Path(os.getenv(ENVAR_DCCSI_LOG_PATH, - Path(PATH_DCCSI_LOG_PATH.format(LY_DEV=_LY_DEV, - LY_PROJECT=_LY_PROJECT)))) -os.environ["DYNACONF_DCCSI_LOG_PATH"] = str(_DCCSI_LOG_PATH) -# hard checks -if not _LY_BIN_PATH.exists(): - raise Exception('LY_BIN_PATH does NOT exist: {0}'.format(_LY_BIN_PATH)) -else: - # adding to sys.path apparently doesn't work for .dll locations like Qt - os.environ['PATH'] = _LY_BIN_PATH.as_posix() + os.pathsep + os.environ['PATH'] +# ------------------------------------------------------------------------- +def init_o3de_python(engine_path=_O3DE_DEV, + engine_bin=_O3DE_BIN_PATH, + dccsi_path=_DCCSI_PATH): + + # pathify + _O3DE_DEV = Path(engine_path) + _O3DE_BIN_PATH = Path(engine_bin) + _DCCSI_PATH = Path(dccsi_path) + + # python config + _DCCSI_PYTHON_PATH = Path(_DCCSI_PATH,'3rdParty','Python') + os.environ["DYNACONF_DCCSI_PYTHON_PATH"] = str(_DCCSI_PYTHON_PATH.resolve()) + + _DCCSI_PYTHON_LIB_PATH = azpy.config_utils.bootstrap_dccsi_py_libs(_DCCSI_PATH) + os.environ["DYNACONF_DCCSI_PYTHON_LIB_PATH"] = str(_DCCSI_PYTHON_LIB_PATH.resolve()) + os.environ['PATH'] = _DCCSI_PYTHON_LIB_PATH.as_posix() + os.pathsep + os.environ['PATH'] + site.addsitedir(_DCCSI_PYTHON_LIB_PATH) + _O3DE_PYTHONPATH.append(_DCCSI_PYTHON_LIB_PATH.resolve()) + + site.addsitedir(_O3DE_BIN_PATH) + _O3DE_PYTHONPATH.append(_O3DE_BIN_PATH.resolve()) + + _O3DE_PY_EXE = Path(sys.executable) + _DCCSI_PY_IDE = Path(_O3DE_PY_EXE) + os.environ["DYNACONF_DCCSI_PY_IDE"] = str(_DCCSI_PY_IDE.resolve()) + + _O3DE_PYTHONHOME = Path(_O3DE_PY_EXE.parents[0]) + os.environ["DYNACONF_O3DE_PYTHONHOME"] = str(_O3DE_PYTHONHOME.resolve()) + os.environ['PATH'] = _O3DE_PYTHONHOME.as_posix() + os.pathsep + os.environ['PATH'] + _LOGGER.info('~ O3DE_PYTHONHOME - is now the folder containing O3DE python executable') + + _O3DE_PYTHON_INSTALL = Path(_O3DE_DEV, 'python') + os.environ["DYNACONF_O3DE_PYTHON_INSTALL"] = str(_O3DE_PYTHON_INSTALL.resolve()) + os.environ['PATH'] = _O3DE_PYTHON_INSTALL.as_posix() + os.pathsep + os.environ['PATH'] -_LOGGER.info('Dynaconf config.py ... DONE') + if sys.platform.startswith('win'): + _DCCSI_PY_BASE = Path(_O3DE_PYTHON_INSTALL, 'python.cmd') + elif sys.platform == "linux": + _DCCSI_PY_BASE = Path(_O3DE_PYTHON_INSTALL, 'python.sh') + elif sys.platform == "darwin": + _DCCSI_PY_BASE = Path(_O3DE_PYTHON_INSTALL, 'python.sh') + else: + _DCCSI_PY_BASE = None + + if _DCCSI_PY_BASE: + os.environ["DYNACONF_DCCSI_PY_BASE"] = str(_DCCSI_PY_BASE.resolve()) + + _O3DE_DCCSI_PATH = os.environ['PATH'] + os.environ["DYNACONF_PATH"] = _O3DE_DCCSI_PATH + + try: + _DCCSI_PYTHONPATH = os.environ['PYTHONPATH'] + os.environ["DYNACONF_PYTHONPATH"] = _DCCSI_PYTHONPATH + except: + pass + + from dynaconf import settings + + _LOGGER.info('~ config.init_o3de_python() ... DONE') + + return settings # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # settings.setenv() # doing this will add the additional DYNACONF_ envars -def get_config_settings(setup_ly_pyside=False): - """Convenience method to set and retreive settings directly from module.""" +def get_config_settings(engine_path=_O3DE_DEV, + build_folder=_O3DE_BUILD_FOLDER, + project_name=None, + project_path=_O3DE_PROJECT_PATH, + enable_o3de_python=None, + enable_o3de_pyside2=None, + set_env=True): + """Convenience method to initialize and retreive settings directly from module.""" + + settings = init_o3de_core(engine_path, + build_folder, + project_name, + project_path) + + if enable_o3de_python: + settings = init_o3de_python(settings.O3DE_DEV, + settings.O3DE_BIN_PATH, + settings.DCCSI_PATH) + + # These should ONLY be set for O3DE and non-DCC environments + # They will most likely cause other Qt/PySide DCC apps to fail + # or hopefully they can be overridden for DCC envionments + # that provide their own Qt dlls and Pyside2 + # _LOGGER.info('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH)) + # _LOGGER.info('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH)) + # assume our standalone python tools wants this access? + # it's safe to do this for dev and from ide + if enable_o3de_pyside2: + settings = init_o3de_pyside2(settings.DCCSI_PATH, + settings.O3DE_BIN_PATH) + + # now standalone we can validate the config. env, settings. from dynaconf import settings - - if setup_ly_pyside: - init_ly_pyside(settings.LY_DEV) - - settings.setenv() + if set_env: + settings.setenv() return settings # --- END ----------------------------------------------------------------- @@ -274,54 +489,216 @@ def get_config_settings(setup_ly_pyside=False): # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- if __name__ == '__main__': - """Run this file as main""" + """Run this file as a standalone cli script""" + + _MODULENAME = __name__ + if _MODULENAME is '__main__': + _MODULENAME = 'DCCsi.config' + + from azpy.constants import STR_CROSSBAR + + while 0: # temp internal debug flag + _DCCSI_GDEBUG = True + break + + # overide logger for standalone to be more verbose and log to file + _LOGGER = azpy.initialize_logger(_MODULENAME, + log_to_file=_DCCSI_GDEBUG, + default_log_level=_DCCSI_LOGLEVEL) - _LOG_LEVEL = int(10) # same as _logging.DEBUG + # happy print + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('~ constants.py ... Running script as __main__') + _LOGGER.info(STR_CROSSBAR) - _LOGGER = azpy.initialize_logger(_PACKAGENAME, - log_to_file=True, - default_log_level=_LOG_LEVEL) + # go ahead and run the rest of the configuration + # parse the command line args + import argparse + parser = argparse.ArgumentParser( + description='O3DE DCCsi Dynamic Config (dynaconf)', + epilog="Attempts to determine O3DE project if -pp not set") + parser.add_argument('-gd', '--global-debug', + type=bool, + required=False, + help='Enables global debug flag.') + parser.add_argument('-dm', '--developer-mode', + type=bool, + required=False, + help='Enables dev mode for early auto attaching debugger.') + parser.add_argument('-ep', '--engine-path', + type=pathlib.Path, + required=False, + help='The path to the o3de engine.') + parser.add_argument('-bf', '--build-folder', + type=str, + required=False, + help='The name (tag) of the o3de build folder, example build or windows_vs2019.') + parser.add_argument('-pp', '--project-path', + type=pathlib.Path, + required=False, + help='The path to the project.') + parser.add_argument('-pn', '--project-name', + type=str, + required=False, + help='The name of the project.') + parser.add_argument('-py', '--enable-python', + type=bool, + required=False, + help='Enables O3DE python access.') + parser.add_argument('-qt', '--enable-qt', + type=bool, + required=False, + help='Enables O3DE Qt\PySide2 access.') + parser.add_argument('-sd', '--set-debugger', + type=str, + required=False, + help='Default debugger: WING, others: PYCHARM, VSCODE (not yet implemented).') + parser.add_argument('-pc', '--project-config', + type=bool, + required=False, + help='Enables reading the projects registry\dccsiconfiguration.setreg.') + parser.add_argument('-es', '--export-settings', + type=pathlib.Path, + required=False, + help='Writes managed settings to specified path.') + parser.add_argument('-ec', '--export-configuration', + type=bool, + required=False, + help='writes settings as a O3DE registry\dccsiconfiguration.setreg.') + parser.add_argument('-tp', '--test-pyside2', + type=bool, + required=False, + help='Runs Qt/PySide2 tests and reports.') + args = parser.parse_args() - from dynaconf import settings + # easy overrides + if args.global_debug: + _DCCSI_GDEBUG = True + os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG) + if args.developer_mode: + attach_debugger() # attempts to start debugger + if args.set_debugger: + _LOGGER.info('Setting and switching debugger type from WingIDE not implemented.') + # To Do: implement debugger plugin pattern + + # need to do a little plumbing + if not args.engine_path: + args.engine_path=_O3DE_DEV + if not args.build_folder: + from azpy.constants import TAG_DIR_O3DE_BUILD_FOLDER + args.build_folder = TAG_DIR_O3DE_BUILD_FOLDER + if not args.project_path: + args.project_path=_O3DE_PROJECT_PATH + + if _DCCSI_GDEBUG: + args.enable_python = True + args.enable_qt = True + + # now standalone we can validate the config. env, settings. + settings = get_config_settings(engine_path=args.engine_path, + build_folder=args.build_folder, + project_name=args.project_name, + project_path=args.project_path, + enable_o3de_python=args.enable_python, + enable_o3de_pyside2=args.enable_qt) + + ## CORE + _LOGGER.info(STR_CROSSBAR) # not using fstrings in this module because it might run in py2.7 (maya) _LOGGER.info('DCCSI_GDEBUG: {}'.format(settings.DCCSI_GDEBUG)) _LOGGER.info('DCCSI_DEV_MODE: {}'.format(settings.DCCSI_DEV_MODE)) _LOGGER.info('DCCSI_LOGLEVEL: {}'.format(settings.DCCSI_LOGLEVEL)) - - _LOGGER.info('OS_FOLDER: {}'.format(settings.OS_FOLDER)) - _LOGGER.info('LY_PROJECT: {}'.format(settings.LY_PROJECT)) - _LOGGER.info('LY_PROJECT_PATH: {}'.format(settings.LY_PROJECT_PATH)) - _LOGGER.info('LY_DEV: {}'.format(settings.LY_DEV)) - _LOGGER.info('LY_BUILD_PATH: {}'.format(settings.LY_BUILD_PATH)) - _LOGGER.info('LY_BIN_PATH: {}'.format(settings.LY_BIN_PATH)) - + _LOGGER.info('DCCSI_OS_FOLDER: {}'.format(settings.DCCSI_OS_FOLDER)) + + _LOGGER.info('O3DE_DEV: {}'.format(settings.O3DE_DEV)) + _LOGGER.info('O3DE_O3DE_BUILD_FOLDER: {}'.format(settings.O3DE_BUILD_PATH)) + _LOGGER.info('O3DE_BUILD_PATH: {}'.format(settings.O3DE_BUILD_PATH)) + _LOGGER.info('O3DE_BIN_PATH: {}'.format(settings.O3DE_BIN_PATH)) + + _LOGGER.info('O3DE_PROJECT: {}'.format(settings.O3DE_PROJECT)) + _LOGGER.info('O3DE_PROJECT_PATH: {}'.format(settings.O3DE_PROJECT_PATH)) + + _LOGGER.info('DCCSI_PATH: {}'.format(settings.DCCSI_PATH)) _LOGGER.info('DCCSI_LOG_PATH: {}'.format(settings.DCCSI_LOG_PATH)) - _LOGGER.info('DCCSI_CONFIG_PATH: {}'.format(settings.DCCSI_CONFIG_PATH)) - _LOGGER.info('DCCSIG_PATH: {}'.format(settings.DCCSIG_PATH)) - _LOGGER.info('DCCSI_PYTHON_LIB_PATH: {}'.format(settings.DCCSI_PYTHON_LIB_PATH)) - _LOGGER.info('DDCCSI_PY_BASE: {}'.format(settings.DDCCSI_PY_BASE)) - - # To Do: These should ONLY be set for Lumberyard and non-DCC environments - # They will most likely cause Qt/PySide DCC apps to fail - # or hopefully they can be overridden for DCC envionments - # that provide their own Qt dlls and Pyside2 - #_LOGGER.info('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH)) - #_LOGGER.info('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH)) - - init_ly_pyside(settings.LY_DEV) # init lumberyard Qt/PySide2 - # from dynaconf import settings # <-- no need to reimport + + if settings.O3DE_DCCSI_ENV_TEST: + _LOGGER.info('O3DE_DCCSI_ENV_TEST: {}'.format(settings.O3DE_DCCSI_ENV_TEST)) + + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('') + + if args.enable_python: + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('DCCSI_PYTHON_PATH'.format(settings.DCCSI_PYTHON_PATH)) + _LOGGER.info('DCCSI_PYTHON_LIB_PATH: {}'.format(settings.DCCSI_PYTHON_LIB_PATH)) + _LOGGER.info('DCCSI_PY_IDE'.format(settings.DCCSI_PY_IDE)) + _LOGGER.info('O3DE_PYTHONHOME'.format(settings.O3DE_PYTHONHOME)) + _LOGGER.info('O3DE_PYTHON_INSTALL'.format(settings.O3DE_PYTHON_INSTALL)) + _LOGGER.info('DCCSI_PY_BASE: {}'.format(settings.DCCSI_PY_BASE)) + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('') + else: + _LOGGER.info('Tip: add arg --enable-python to extend the environment with O3DE python access') + + if args.enable_qt: + _LOGGER.info(STR_CROSSBAR) + # _LOGGER.info('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH)) + _LOGGER.info('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH)) + _LOGGER.info('QT_QPA_PLATFORM_PLUGIN_PATH: {}'.format(settings.QT_QPA_PLATFORM_PLUGIN_PATH)) + _LOGGER.info('DCCSI_PYSIDE2_TOOLS: {}'.format(settings.DCCSI_PYSIDE2_TOOLS)) + _LOGGER.info(STR_CROSSBAR) + _LOGGER.info('') + else: + _LOGGER.info('Tip: add arg --enable-qt to extend the environment with O3DE Qt/PySide2 support') + settings.setenv() # doing this will add/set the additional DYNACONF_ envars + + if _DCCSI_GDEBUG or args.export_settings: + # to do: need to add malformed json validation to \settings.json + # this can cause a bad error that is deep and hard to debug + + # writting settings + from dynaconf import loaders + from dynaconf.utils.boxing import DynaBox + + _settings_dict = settings.as_dict() + + # default temp filename + _settings_file = Path('settings_export.json.tmp') + + # writes to a file, the format is inferred by extension + # can be .yaml, .toml, .ini, .json, .py + #loaders.write(_settings_file, DynaBox(data).to_dict(), merge=False, env='development') + #loaders.write(_settings_file, DynaBox(data).to_dict()) + + # we want to possibly modify or stash our settings into a o3de .setreg + from box import Box + _settings_box = Box(_settings_dict) + + _LOGGER.info('Pretty print, _settings_box: {}'.format(_settings_file)) + _LOGGER.info(str(_settings_box.to_json(sort_keys=True, + indent=4))) + + # writes settings box + _settings_box.to_json(filename=_settings_file.as_posix(), + sort_keys=True, + indent=4) + + if _DCCSI_GDEBUG or args.test_pyside2: + test_pyside2() # test PySide2 access with a pop-up button + try: + import pyside2uic + except ImportError as e: + _LOGGER.warning("Could not import 'pyside2uic'") + _LOGGER.warning("Refer to: '< local DCCsi >\3rdParty\Python\README.txt'") + _LOGGER.error(e) - #_LOGGER.info('QTFORPYTHON_PATH: {}'.format(settings.QTFORPYTHON_PATH)) - _LOGGER.info('LY_BIN_PATH: {}'.format(settings.LY_BIN_PATH)) - _LOGGER.info('QT_PLUGIN_PATH: {}'.format(settings.QT_PLUGIN_PATH)) - _LOGGER.info('QT_QPA_PLATFORM_PLUGIN_PATH: {}'.format(settings.QT_QPA_PLATFORM_PLUGIN_PATH)) - _LOGGER.info('DCCSI_PYSIDE2_TOOLS: {}'.format(settings.DCCSI_PYSIDE2_TOOLS)) - - test_pyside2() # test PySide2 access with a pop-up button + # return + sys.exit() +# --- END ----------------------------------------------------------------- diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/settings.json b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/settings.json index 0967ef424b..718c6110c3 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/settings.json +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/settings.json @@ -1 +1,13 @@ -{} +{ + "LOAD_DOTENV": true, + "COMPANY": "Amazon", + "DCCSI_GDEBUG": "False", + "DCCSI_DEV_MODE": "False", + "DCCSI_GDEBUGGER": "WING", + "DCCSI_LOGLEVEL": 20, + "DEFAULT_SETTINGS_PATHS": [ + "settings.py", + "settings.json", + ".secrets.json" + ] +} \ No newline at end of file diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass index 84bb85c3e1..9bfa746bf1 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_MainPipeline.pass @@ -212,7 +212,11 @@ // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated // .azsl file will need to be updated. "Name": "HairParentPass", - "TemplateName": "HairParentPassTemplate", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut and accordingly change the flag + // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' +// "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", "Enabled": true, "Connections": [ // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. diff --git a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset index a287664286..1580a4e1a9 100644 --- a/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset +++ b/Gems/AtomTressFX/Assets/Passes/AtomTressFX_PassTemplates.azasset @@ -8,6 +8,11 @@ "Name": "HairParentPassTemplate", "Path": "Passes/HairParentPass.pass" }, + { + "Name": "HairParentShortCutPassTemplate", + "Path": "Passes/HairParentShortCutPass.pass" + }, + { "Name": "HairGlobalShapeConstraintsComputePassTemplate", "Path": "Passes/HairGlobalShapeConstraintsCompute.pass" @@ -32,6 +37,7 @@ "Name": "HairUpdateFollowHairComputePassTemplate", "Path": "Passes/HairUpdateFollowHairCompute.pass" }, + { "Name": "HairPPLLRasterPassTemplate", "Path": "Passes/HairFillPPLL.pass" @@ -39,6 +45,23 @@ { "Name": "HairPPLLResolvePassTemplate", "Path": "Passes/HairResolvePPLL.pass" + }, + + { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "Path": "Passes/HairShortCutGeometryDepthAlpha.pass" + }, + { + "Name": "HairShortCutResolveDepthPassTemplate", + "Path": "Passes/HairShortCutResolveDepth.pass" + }, + { + "Name": "HairShortCutGeometryShadingPassTemplate", + "Path": "Passes/HairShortCutGeometryShading.pass" + }, + { + "Name": "HairShortCutResolveColorPassTemplate", + "Path": "Passes/HairShortCutResolveColor.pass" } ] } diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass index 6ae8b9526e..c8c90e24f7 100644 --- a/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass +++ b/Gems/AtomTressFX/Assets/Passes/HairParentPass.pass @@ -71,6 +71,12 @@ } } ], + "FallbackConnections": [ + { + "Input": "DepthLinearInput", + "Output": "DepthLinear" + } + ], "PassRequests": [ { "Name": "HairGlobalShapeConstraintsComputePass", diff --git a/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass new file mode 100644 index 0000000000..83f9f0d432 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairParentShortCutPass.pass @@ -0,0 +1,400 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairParentShortCutPassTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget" + }, + { + // used for copy from MSAA to regular RT + "Name": "RenderTargetInputOnly", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + // This is the depth stencil buffer that is to be used by the fill pass + // to early reject pixels by depth and in the resolve pass to write the + // the hair depth + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil" + }, + // Keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not availbale - connect to another viewport (non MSAA) image. + { + "Name": "DepthLinearInput", + "SlotType": "Input" + }, + { + "Name": "DepthLinear", + "SlotType": "Output" + }, + + // Lights & Shadows resources + { + "Name": "DirectionalShadowmap", + "SlotType": "Input" + }, + { + "Name": "DirectionalESM", + "SlotType": "Input" + }, + { + "Name": "ProjectedShadowmap", + "SlotType": "Input" + }, + { + "Name": "ProjectedESM", + "SlotType": "Input" + }, + { + "Name": "TileLightData", + "SlotType": "Input" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input" + } + ], + "Connections": [ + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "DepthToDepthLinearPass", + "Attachment": "Output" + } + } + ], + "FallbackConnections": [ + { + "Input": "DepthLinearInput", + "Output": "DepthLinear" + } + ], + "PassRequests": [ + { + "Name": "HairGlobalShapeConstraintsComputePass", + "TemplateName": "HairGlobalShapeConstraintsComputePassTemplate", + "Enabled": true + }, + { + "Name": "HairCalculateStrandLevelDataComputePass", + "TemplateName": "HairCalculateStrandLevelDataComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairGlobalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairVelocityShockPropagationComputePass", + "TemplateName": "HairVelocityShockPropagationComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairCalculateStrandLevelDataComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLocalShapeConstraintsComputePass", + "TemplateName": "HairLocalShapeConstraintsComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairVelocityShockPropagationComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairLengthConstraintsWindAndCollisionComputePass", + "TemplateName": "HairLengthConstraintsWindAndCollisionComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLocalShapeConstraintsComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + { + "Name": "HairUpdateFollowHairComputePass", + "TemplateName": "HairUpdateFollowHairComputePassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairLengthConstraintsWindAndCollisionComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + } + ] + }, + + // Render Target Copy from MS to Regular + { + "Name": "RenderTargetCopyPass", + "TemplateName": "FullscreenCopyTemplate", + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOnly" + } + }, + { + "LocalSlot": "Output", + "AttachmentRef": { + "Pass": "This", + "Attachment": "Output" + } + } + ], + "ImageAttachments": [ + { + "Name": "Output", + "SizeSource": { + "Source": { + "Pass": "This", + "Attachment": "Input" + } + }, + "FormatSource": { + "Pass": "This", + "Attachment": "Input" + }, + "GenerateFullMipChain": false + } + ] + }, + + // Rendering Passes + { + "Name": "HairShortCutGeometryDepthAlphaPass", + "TemplateName": "HairShortCutGeometryDepthAlphaPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "InverseAlphaRTOutput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutResolveDepthPass", + "TemplateName": "HairShortCutResolveDepthPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "HairDepthsTextureArray", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "HairDepthsTextureArray" + } + } + ] + }, + + { + "Name": "HairShortCutGeometryShadingPass", + "TemplateName": "HairShortCutGeometryShadingPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "HairColorRenderTarget", + "AttachmentRef": { + "Pass": "This", + "Attachment": "HairColorRenderTarget" + } + }, + { + // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "DepthLinear", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthLinearInput" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "SkinnedHairSharedBuffer", + "AttachmentRef": { + "Pass": "HairUpdateFollowHairComputePass", + "Attachment": "SkinnedHairSharedBuffer" + } + }, + + // Shadows resources + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ProjectedESM" + } + }, + + // Lights Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "LightListRemapped" + } + } + ] + }, + + { + "Name": "HairShortCutResolveColorPass", + "TemplateName": "HairShortCutResolveColorPassTemplate", + "Enabled": true, + "Connections": [ + { + // The final render target - this is MSAA mode RT - would it be cheaper to + // use non-MSAA and then copy? + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + { + "LocalSlot": "AccumulatedInverseAlpha", + "AttachmentRef": { + "Pass": "HairShortCutGeometryDepthAlphaPass", + "Attachment": "InverseAlphaRTOutput" + } + }, + { + "LocalSlot": "HairColorTexture", + "AttachmentRef": { + "Pass": "HairShortCutGeometryShadingPass", + "Attachment": "HairColorRenderTarget" + } + } + ] + }, + + { + // This pass copies the updated depth buffer (now contains hair depth) to linear depth texture + // for downstream passes to use. This can be optimized even further by writing into the stencil + // buffer pixels that were touched by HairPPLLResolvePass hence preventing depth update unless + // it is hair. + "Name": "DepthToDepthLinearPass", + "TemplateName": "DepthToLinearTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "Input", + "AttachmentRef": { + "Pass": "HairShortCutResolveDepthPass", + "Attachment": "Depth" + } + } + ] + } + + ] + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass new file mode 100644 index 0000000000..559224faa6 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryDepthAlpha.pass @@ -0,0 +1,101 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryDepthAlphaPassTemplate", + "PassClass": "HairShortCutGeometryDepthAlphaPass", + "Slots": [ + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // DepthStencil for early disqualifying the pixel based on depth. No write. + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + // the regular render target is blended using inverse alpha to reduce the + // incoming color contribution based on the hair thickness and alpha. + "Name": "InverseAlphaRTOutput", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { + "Value": [ 1.0, 1.0, 1.0, 1.0 ] + }, + "StoreAction": "Store" + } + }, + { + "Name": "HairDepthsTextureArray", + "SlotType": "Output", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_RWFragmentDepthsTexture", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + } + ], + "ImageAttachments": [ + { + // This buffer is used as the render target and should be at non-MSAA screen resolution + // to make sure no overwork is done. + "Name": "InverseAlphaRTOutput", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "HairDepthsTextureArray", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "DepthLinear" + } + }, + "ImageDescriptor": { + "Format": "R32_UINT", + "ArraySize": "3", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "ShaderReadWrite", + "ShaderWrite", + "ShaderRead" + ] + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryDepthAlphaDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryDepthAlpha.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass new file mode 100644 index 0000000000..5940f8c549 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutGeometryShading.pass @@ -0,0 +1,155 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutGeometryShadingPassTemplate", + "PassClass": "HairShortCutGeometryShadingPass", + "Slots": [ + + { // Temporary color buffer to store the gathered shaded hair color - MSAA + "Name": "HairColorRenderTarget", + "SlotType": "Output", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Clear", + "ClearValue": { // reverse depth order: closer --> 1.0 + "Value": [ 0.0, 0.0, 0.0, 0.0 ] + }, + "StoreAction": "Store" + } + }, + + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // Used to get the transform from screen space to world space. + "Name": "DepthLinear", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { // For comparing the depth to early disqualify but not to write + "Name": "Depth", + "SlotType": "Input", + "ScopeAttachmentUsage": "DepthStencil" + }, + { + "Name": "SkinnedHairSharedBuffer", + "ShaderInputName": "m_skinnedHairSharedBuffer", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + + //------------- Shadowing Resources ------------- + { + "Name": "DirectionalShadowmap", + "ShaderInputName": "m_directionalLightShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "DirectionalESM", + "ShaderInputName": "m_directionalLightExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedShadowmap", + "ShaderInputName": "m_projectedShadowmaps", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + { + "Name": "ProjectedESM", + "ShaderInputName": "m_projectedExponentialShadowmap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ImageViewDesc": { + "IsArray": 1 + } + }, + + //------------- Lighting Resources ------------- + { + "Name": "BRDFTextureInput", + "ShaderInputName": "m_brdfMap", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "TileLightData", + "SlotType": "Input", + "ShaderInputName": "m_tileLightData", + "ScopeAttachmentUsage": "Shader" + }, + { + "Name": "LightListRemapped", + "SlotType": "Input", + "ShaderInputName": "m_lightListRemapped", + "ScopeAttachmentUsage": "Shader" + } + + ], + "ImageAttachments": [ + { + // The shader hair color render target - important to have at a non-MSAA mode + // so that no overwork is done on sampling. + "Name": "HairColorRenderTarget", + "SizeSource": { + "Source": { + "Pass": "Parent", + "Attachment": "RenderTargetInputOutput" + } + }, + "ImageDescriptor": { + "Format": "R16G16B16A16_FLOAT", + "SharedQueueMask": "Graphics", + "BindFlags": [ + "Color", + "ShaderRead" + ] + } + }, + { + "Name": "BRDFTexture", + "Lifetime": "Imported", + "AssetRef": { + "FilePath": "Textures/BRDFTexture.attimage" + } + } + ], + "Connections": [ + { + "LocalSlot": "BRDFTextureInput", + "AttachmentRef": { + "Pass": "This", + "Attachment": "BRDFTexture" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "HairGeometryShadingDrawList", + "PipelineViewTag": "MainCamera", + "PassSrgShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutGeometryShading.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass new file mode 100644 index 0000000000..efbf993307 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveColor.pass @@ -0,0 +1,45 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveColorPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + { + // This RT is MSAA - is it cheaper to avoid doing this work and only do a copy at a separate pass? + "Name": "RenderTargetInputOutput", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "RenderTarget", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { + "Name": "HairColorTexture", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_hairColorTexture" + }, + { + "Name": "AccumulatedInverseAlpha", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_accumInvAlpha" + } + ], + "Connections": [ + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveColor.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass new file mode 100644 index 0000000000..021d711f1f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Passes/HairShortCutResolveDepth.pass @@ -0,0 +1,37 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "HairShortCutResolveDepthPassTemplate", + "PassClass": "FullScreenTriangle", + "Slots": [ + //------ General Input/Output resources and Render Target ------ + { + "Name": "Depth", + "SlotType": "InputOutput", + "ScopeAttachmentUsage": "DepthStencil", + "LoadStoreAction": { + "LoadAction": "Load", + "StoreAction": "Store" + } + }, + { // This holds the K nearset depths. The furthest depth will be taken to be written in the depth buffer. + "Name": "HairDepthsTextureArray", + "SlotType": "Input", + "ScopeAttachmentUsage": "Shader", + "ShaderInputName": "m_fragmentDepthsTexture" + } + ], + "PassData": { + "$type": "FullscreenTrianglePassData", + "ShaderAsset": { + // Looking for it in the Shaders directory relative to the Assets directory + "FilePath": "Shaders/HairShortCutResolveDepth.shader" + } + } + } + } +} + diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli index cb0bdc2c6c..a4568588d5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairComputeSrgs.azsli @@ -29,7 +29,7 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ diff --git a/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli new file mode 100644 index 0000000000..7e57e8102c --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairFullScreenUtils.azsli @@ -0,0 +1,60 @@ +/* +* Modifications 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) AND MIT +* +*/ + +#include +#include +#include + +//============================================================================== +// Generate a fullscreen triangle from pipeline provided vertex id +VSOutput FullScreenVS(VSInput input) +{ + VSOutput OUT; + + float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); + + OUT.m_texCoord = float2(posTex.z, posTex.w); // [To Do] - test sign of Y based on original code + OUT.m_position = float4(posTex.xy, 0.0, 1.0); + + return OUT; +} + +//============================================================================== +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the NDC. +// screenCoords - from 0.. dimension of the screen of the current pixel +// screenTexture - screen buffer texture representing the same resolution we work in +// sDepth - the depth buffer depth at the fragment location +// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) +float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) +{ + uint2 dimensions; + screenTexture.GetDimensions(dimensions.x, dimensions.y); + float2 UV = saturate(screenCoords / dimensions.xy); + + float x = UV.x * 2.0f - 1.0f; + float y = (1.0f - UV.y) * 2.0f - 1.0f; + float3 NDC = float3(x, y, depth); + + return NDC; +} + +// Given the depth buffer depth of the current pixel and the fragment XY position, +// reconstruct the world space position +float3 ScreenPosToWorldPos( + Texture2D screenTexture, float2 screenCoords, float depth, + inout float3 screenPosNDC ) +{ + screenPosNDC = ScreenPosToNDC(screenTexture, screenCoords, depth); + float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] + float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); + positionVS /= positionVS.w; // notice the normalization factor - crucial! + float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); + + return positionWS.xyz; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli index dcdcf43243..7beba248f7 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLighting.azsli @@ -230,7 +230,7 @@ float3 CalculateLighting( return lightingData.diffuseLighting + lightingData.specularLighting; } -float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, float3 baseColor, float thickness, int shaderParamIndex) +float3 TressFXShading(float2 pixelCoord, float depth, float3 tangent, float3 baseColor, float thickness, int shaderParamIndex) { float3 vNDC; // normalized device / screen coordinates: [-1..1, -1..1, 0..1] float3 vPositionWS = ScreenPosToWorldPos(PassSrg::m_linearDepth, pixelCoord, depth, vNDC); @@ -241,9 +241,6 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f float3 vViewDirWS = g_vEye - vPositionWS; - // Need to expand the tangent that was compressed to store in the buffer - float3 vTangent = normalize(vTangentCoverage.xyz * 2.f - 1.f); - //---- TressFX original lighting params setting ---- HairShadeParams params; params.m_color = baseColor; @@ -266,11 +263,19 @@ float3 TressFXShading(float2 pixelCoord, float depth, float3 vTangentCoverage, f if (o_hairLightingModel == HairLightingModel::Kajiya) { // This option should be removed and the Kajiya-Kay model should be operated from within // the Atom lighting loop. - accumulatedLight = SimplifiedHairLighting(vTangent, vPositionWS, vViewDirWS, params, vNDC); + accumulatedLight = SimplifiedHairLighting(tangent, vPositionWS, vViewDirWS, params, vNDC); } else { - accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, vTangent, thickness, params); + accumulatedLight = CalculateLighting(screenCoords, vPositionWS, vViewDirWS, tangent, thickness, params); } return accumulatedLight; } + +float3 TressFXShadingFullScreen(float2 pixelCoord, float depth, float3 compressedTangent, float3 baseColor, float thickness, int shaderParamIndex) +{ + // The tangent that was compressed to store in the PPLL structure + float3 tangent = normalize(compressedTangent.xyz * 2.f - 1.f); + + return TressFXShading(pixelCoord, depth, tangent, baseColor, thickness, shaderParamIndex); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli index 83ebc04521..4e4245f765 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairLightingEquations.azsli @@ -66,7 +66,7 @@ option bool o_enableAzimuthCoeff = true; float M_R(Surface surface, float Lh, float sinLiPlusSinLr) { float a = 1.0f * surface.cuticleTilt; // Tilt is translate as the mean offset - float b = 0.5 * surface.roughnessA2; // Roughness is used as the standard deviation + float b = 0.5f * surface.roughnessA2; // Roughness is used as the standard deviation // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -74,8 +74,8 @@ float M_R(Surface surface, float Lh, float sinLiPlusSinLr) float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.0 * surface.cuticleTilt; - float b = 0.5 * surface.roughnessA2; + float a = 1.0f * surface.cuticleTilt; + float b = 0.5f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); @@ -83,8 +83,8 @@ float M_TT(Surface surface, float Lh, float sinLiPlusSinLr) float M_TRT(Surface surface, float Lh, float sinLiPlusSinLr) { - float a = 1.5 * surface.cuticleTilt; - float b = 1.0 * surface.roughnessA2; + float a = 1.5f * surface.cuticleTilt; + float b = 1.0f * surface.roughnessA2; // return GaussianNormalized(sinLiPlusSinLr, a, b); // reference return GaussianNormalized(Lh, a, b); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl index 82c677faad..02777435f5 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingFillPPLL.azsl @@ -40,7 +40,8 @@ //! that can change between passes due to the application of skinning, simulation //! and physics affect and is then read by the rendering shaders. ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback -{ //! This shared buffer needs to match the SharedBuffer structure +{ + //! This shared buffer needs to match the SharedBuffer structure //! shared between all draw calls / dispatches for the hair skinning StructuredBuffer m_skinnedHairSharedBuffer; @@ -101,39 +102,8 @@ ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance #define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents //============================================================================== -#include +#include // VS resides here //============================================================================== -//! Hair input structure to Pixel shaders -struct PS_INPUT_HAIR -{ - float4 Position : SV_POSITION; - float4 Tangent : Tangent; - float4 p0p1 : TEXCOORD0; - float4 StrandColor : TEXCOORD1; -}; - -//! Hair Render VS -PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) -{ -// uint2 scrSize; -// PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); -// TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); - - // [To Do] Hair: the above code should replace the existing but requires modifications to - // the function GetExpandedTressFXVert. - // Note that in Atom g_vViewport is aspect ratio and NOT size. - TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); - - - PS_INPUT_HAIR Output; - - Output.Position = tressfxVert.Position; - Output.Tangent = tressfxVert.Tangent; - Output.p0p1 = tressfxVert.p0p1; - Output.StrandColor = tressfxVert.StrandColor; - - return Output; -} // Allocate a new fragment location in fragment color, depth, and link buffers int AllocateFragment(int2 vScreenAddress) @@ -202,9 +172,9 @@ void PPLLFillPS(PS_INPUT_HAIR input) ////////////////////////////////////////////////////////////////////// // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now - float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); - uint2 dimensions; - PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); // float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); float coverage = 1.0; ///////////////////////////////////////////////////////////////////// diff --git a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl index 82c75280f6..3d72f1e21e 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairRenderingResolvePPLL.azsl @@ -58,7 +58,7 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback // in the OIT process. // It can also be used to avoid the HW blend done at the end of the pixel // shader stage but HW blend might be cheaper than additional PS blend. - Texture2D m_frameBuffer; // The merged MSAA output + Texture2D m_frameBuffer; // The merged non-MSAA input // Linear depth is used for getting the screen to world transform Texture2D m_linearDepth; @@ -93,26 +93,11 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback #define HairParams PassSrg::m_hairParams //============================================================================== +#include // provides the Vertex Shader #include -#include -#include - -// Generates a fullscreen triangle from pipeline provided vertex id -VSOutput FullScreenVS(VSInput input) -{ - VSOutput OUT; - - float4 posTex = GetVertexPositionAndTexCoords(input.m_vertexID); - - OUT.m_texCoord = float2(posTex.z, posTex.w); - OUT.m_position = float4(posTex.x, posTex.y, 0.0, 1.0); - - return OUT; -} ////////////////////////////////////////////////////////////// // Bind data for PPLLResolvePS - #define NODE_DATA(x) LinkedListNodes[x].data #define NODE_NEXT(x) LinkedListNodes[x].uNext #define NODE_DEPTH(x) LinkedListNodes[x].depth @@ -298,7 +283,7 @@ float4 GatherLinkedList(float2 vfScreenAddress, float2 screenUV, inout float out uint shadeParamIndex; // So we know what settings to shade with float3 vColor = UnpackUintIntoFloat3Byte(color, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, fDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + fragmentColor * alpha; @@ -355,7 +340,7 @@ float4 GetClosestFragment(float2 vfScreenAddress, float2 screenUV, inout float c float alpha = 1.0; uint shadeParamIndex; // the material index float3 vColor = UnpackUintIntoFloat3Byte(curColor, shadeParamIndex); - float3 fragmentColor = TressFXShading(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); + float3 fragmentColor = TressFXShadingFullScreen(vfScreenAddress, curDepth, vTangent, vColor, fcolor.w, shadeParamIndex); // Blend in the fragment color fcolor.xyz = fcolor.xyz * (1.f - alpha) + (fragmentColor * alpha); diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl new file mode 100644 index 0000000000..23c926406f --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl @@ -0,0 +1,131 @@ +/* +* Modifications 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) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Based on [[vk::binding(0, 3)]] RWTexture2DArray RWFragmentDepthsTexture : register(u0, space3); + RWTexture2DArray m_RWFragmentDepthsTexture; +} +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here + +//!============================================================================= +//! Geometry Depth Alpha - First Pass of ShortCut Render +//! It is a Geometry pass that stores the K=3 front fragment depths, and accumulates +//! product of 1-alpha multiplications (fade out) of the input render target. +//! +//! Short explanation: in the original AMD implementation 1-alpha is multiplied +//! repeatedly with the incoming render target (back buffer) hence blending out +//! the existing back buffer color based on the density and transparency of the hair. +//! This implies that later on the hair color should be added based on the inverse +//! of this operation. +//!============================================================================= +[earlydepthstencil] +float HairShortCutDepthsAlphaPS(PS_INPUT_HAIR input) : SV_Target +{ + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage * MatBaseColor.a; + + if (alpha < SHORTCUT_MIN_ALPHA) + return 1.0; + + int2 vScreenAddress = int2(input.Position.xy); + uint uDepth = asuint(input.Position.z); + uint uDepth0Prev, uDepth1Prev, uDepth2Prev; + + // Min of depth 0 and input depth - in Atom the Z order is reverse + // Original value is uDepth0Prev + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 0)], uDepth, uDepth0Prev); + + // Min of depth 1 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth0Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 1)], uDepth, uDepth1Prev); + + // Min of depth 2 and greater of the last compare - in Atom the Z order is reverse + // If fragment opaque, always use input depth (don't need greater depths) + uDepth = (alpha > 0.98) ? uDepth : max(uDepth, uDepth1Prev); + + InterlockedMax(PassSrg::m_RWFragmentDepthsTexture[uint3(vScreenAddress, 2)], uDepth, uDepth2Prev); + + // Accumulate the alpha multiplication from all hair components by multiplying the inverse and + // therefore going down towards 0. At the end product, the inverse will be taken as the hair + // alpha and the remainder will be used to blend the back buffer. + return 1.0 - alpha; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader new file mode 100644 index 0000000000..7cd1f44510 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryDepthAlpha.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryDepthAlpha.azsl", + "DrawList" : "HairGeometryDepthAlphaDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "Zero", + "BlendDest" : "ColorSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "AlphaSource", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutDepthsAlphaPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl new file mode 100644 index 0000000000..c2a2958dfe --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.azsl @@ -0,0 +1,176 @@ +/* +* Modifications 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) AND MIT +* +*/ + +//------------------------------------------------------------------------------ +// Shader code related to lighting and shadowing for TressFX +//------------------------------------------------------------------------------ +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include +#include + +#define AMD_TRESSFX_MAX_HAIR_GROUP_RENDER 16 + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + //! This shared buffer needs to match the SharedBuffer structure + //! shared between all draw calls / dispatches for the hair skinning + StructuredBuffer m_skinnedHairSharedBuffer; + + //! Per hair object material array used by the PPLL resolve pass + //! Originally in TressFXRendering.hlsl this is space 0 + HairObjectShadeParams m_hairParams[AMD_TRESSFX_MAX_HAIR_GROUP_RENDER]; + + // Linear depth is used for getting the screen to world transform + Texture2D m_linearDepth; + + //------------------------------ + // Lighting Data + //------------------------------ + Sampler LinearSampler + { // Required by LightingData.azsli + MinFilter = Linear; + MagFilter = Linear; + MipFilter = Linear; + AddressU = Clamp; + AddressV = Clamp; + AddressW = Clamp; + }; + + Texture2DArray m_directionalLightShadowmap; + Texture2DArray m_directionalLightExponentialShadowmap; + Texture2DArray m_projectedShadowmaps; + Texture2DArray m_projectedExponentialShadowmap; + Texture2D m_brdfMap; + Texture2D m_tileLightData; + StructuredBuffer m_lightListRemapped; +} + +//------------------------------------------------------------------------------ +//! The hair objects' material array buffer used by the rendering resolve pass +#define HairParams PassSrg::m_hairParams +//============================================================================== + +//!------------------------------ SRG Structure -------------------------------- +//! Per instance/draw SRG representing dynamic read-write set of buffers +//! that are unique per instance and are shared and changed between passes due +//! to the application of skinning, simulation and physics affect. +//! It is then also read by the rendering shaders. +//! This Srg is NOT shared by the passes since it requires having barriers between +//! both passes and draw calls, instead, all buffers are allocated from a single +//! shared buffer (through BufferViews) and that buffer is then shared between +//! the passes via the PerPass Srg frequency. +ShaderResourceGroup HairDynamicDataSrg : SRG_PerObject // space 1 - per instance / object +{ + Buffer m_hairVertexPositions; + Buffer m_hairVertexTangents; + + //! Per hair object offset to the start location of each buffer within + //! 'm_skinnedHairSharedBuffer'. The offset is in bytes! + uint m_positionBufferOffset; + uint m_tangentBufferOffset; +}; +//------------------------------------------------------------------------------ +// Allow for the code to run with minimal changes - skinning / simulation compute passes +// Usage of per-instance buffer +#define g_GuideHairVertexPositions HairDynamicDataSrg::m_hairVertexPositions +#define g_GuideHairVertexTangents HairDynamicDataSrg::m_hairVertexTangents +//------------------------------------------------------------------------------ + +#include // VS resides here +#include // Required for world coordinates calculation +#include + +//!============================================================================= +//! Geometry Shading - Third Pass of ShortCut Render +//! Geometry pass that shades pixels that passes the early depth test. Due to this, it +//! is limited to the stored K near fragments due to previous depth write pass that +//! wrote the furthest depth of the K stored depths. +//! Colors are accumulated in the render target for a weighted average in final pass. +//! [To Do] - in the original short cut, the alpha is taken from the depth alpha pass +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutGeometryColorPS(PS_INPUT_HAIR input) : SV_Target +{ + // Strand Color read in is either the BaseMatColor, or BaseMatColor modulated with a color read from texture + // on vertex shader for base color along with modulation by the tip color + float4 strandColor = float4(input.StrandColor.rgb, MatBaseColor.a); + + // If we are supporting strand UV texturing, further blend in the texture color/alpha + // Do this while computing NDC and coverage to hide latency from texture lookup + if (EnableStrandUV) + { + // Grab the uv in case we need it + float2 uv = float2(input.Tangent.w, input.StrandColor.w); + + // Apply StrandUVTiling + float2 strandUV = float2(uv.x, (uv.y * StrandUVTilingFactor) - floor(uv.y * StrandUVTilingFactor)); + + strandColor.rgb *= StrandAlbedoTexture.Sample(LinearWrapSampler, strandUV).rgb; + } + + ////////////////////////////////////////////////////////////////////// + // [To Do] Hair: anti aliasing via coverage requires work and is disabled for now +// float3 vNDC = ScreenPosToNDC(PassSrg::m_linearDepth, input.Position.xy, input.Position.z); +// uint2 dimensions; +// PassSrg::m_linearDepth.GetDimensions(dimensions.x, dimensions.y); +// float2 screenCoords = saturate(pixelCoord / dimensions.xy); +// float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, float2(dimensions.x, dimensions.y)); +// original: float coverage = ComputeCoverage(input.p0p1.xy, input.p0p1.zw, vNDC.xy, g_vViewport.zw - g_vViewport.xy); + float coverage = 1.0; + ///////////////////////////////////////////////////////////////////// + + float alpha = coverage; + + // Update the alpha to have proper value (accounting for coverage, base alpha, and strand alpha) + alpha *= strandColor.w; + + // Early out + if (alpha < SHORTCUT_MIN_ALPHA) + { + return float4(0, 0, 0, 0); + } + + float2 pixelCoord = input.Position.xy; + float depth = input.Position.z; + // [To Do] - the thickness will need to be corrected somehow since this technique doesn't + // keeps track of the accumulated alpha / thickness + float thickness = alpha; + float3 shadedFragment = TressFXShading(pixelCoord, depth, input.Tangent.xyz, strandColor.rgb, thickness, RenderParamsIndex); + + // Color channel: Pre-multiply with alpha to create non-normalized weighted sum. + // Alpha Channel: Sum up all the hair alphas - this will be used to normalize the color + // per fragment at the next pass. + return float4(shadedFragment * alpha, alpha); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader new file mode 100644 index 0000000000..40e56006cd --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutGeometryShading.shader @@ -0,0 +1,45 @@ +{ + "Source" : "HairShortCutGeometryShading.azsl", + "DrawList" : "HairGeometryShadingDrawList", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, + "WriteMask" : "Zero", // Avoid writing the depth + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "One", + "BlendOp" : "Add", + "BlendAlphaSource" : "One", + "BlendAlphaDest" : "One", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "RenderHairVS", + "type": "Vertex" + }, + { + "name": "HairShortCutGeometryColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl new file mode 100644 index 0000000000..f25c9dd711 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.azsl @@ -0,0 +1,63 @@ +/* +* Modifications 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) AND MIT +* +*/ + +#include +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // oiriginally: [[vk::binding(0, 0)]] Texture2D HaiColorTexture : register(t0, space0); + // oiriginally: [[vk::binding(1, 0)]] Texture2D AccumInvAlpha : register(t1, space0); + Texture2D m_hairColorTexture; + Texture2D m_accumInvAlpha; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! HairColorPS - Fourth Pass of ShortCut Render +//! Full-screen pass that finalizes the weighted average, and blends using the +//! accumulated 1-alpha product. +//!============================================================================= +[earlydepthstencil] +float4 HairShortCutResolveColorPS(VSOutput input) : SV_Target +{ + int2 vScreenAddress = int2(input.m_position.xy); + + float fInvAlpha = PassSrg::m_accumInvAlpha[vScreenAddress]; + float fAlpha = 1.0 - fInvAlpha; + + if (fAlpha < SHORTCUT_MIN_ALPHA) + { + // next we discard of non-hair pixels to avoid manipulating them depending + // on the alpha blend state - this is the safer and faster approach as there + // is no hair in these pixels + discard; + } + + float4 finalColor; + float weightSum = PassSrg::m_hairColorTexture[vScreenAddress].w; + + // Normalize the sum of the shaded fragment from the previous pass and + // then multiply it by the alpha blend of the hairs done in the depth-alpha pass. + finalColor.xyz = PassSrg::m_hairColorTexture[vScreenAddress] * fAlpha / weightSum; + + // The alpha is set to the inverse alpha of the hair so that the original + // background will be blended using this factor emulating single step alpha blend + // over the sum of all hair fragment blends. + finalColor.w = fInvAlpha; + + return finalColor; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader new file mode 100644 index 0000000000..6703c63f18 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveColor.shader @@ -0,0 +1,41 @@ +{ + "Source" : "HairShortCutResolveColor.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : false // Avoid comparing depth + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : true, + "BlendSource" : "One", + "BlendDest" : "AlphaSource", + "BlendOp" : "Add", + "BlendAlphaSource" : "Zero", + "BlendAlphaDest" : "Zero", + "BlendAlphaOp" : "Add" + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveColorPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl new file mode 100644 index 0000000000..862b7c9015 --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.azsl @@ -0,0 +1,65 @@ +/* +* Modifications 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) AND MIT +* +*/ + +// +// Copyright (c) 2019 Advanced Micro Devices, Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#include + +//!------------------------------ SRG Structure -------------------------------- +//! Per pass SRG that holds the dynamic shared read-write buffer shared +//! across all dispatches and draw calls. It is used for all the dynamic buffers +//! that can change between passes due to the application of skinning, simulation +//! and physics affect. +//! Once the compute pases are done, it is read by the rendering shaders. +ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback +{ + // Originally: [[vk::binding(0, 0)]] Texture2DArray FragmentDepthsTexture : register(t0, space0); + Texture2DArray m_fragmentDepthsTexture; +} +//------------------------------------------------------------------------------ + +#include // provides the Vertex Shader + +//!============================================================================= +//! Resolve Depth - Second Pass of ShortCut +//! Full-screen pass that writes the farthest of the stored K near depths so it +//! could be used for depth culling during the following geometry shading pass. +//!============================================================================= +float HairShortCutResolveDepthPS(VSOutput input) : SV_Depth +{ + // Blend the layers of fragments from back to front + int2 vScreenAddress = int2(input.m_position.xy); + + // Write farthest depth value for culling in the next pass. + // It may be the initial value of 1.0 if there were not enough fragments to write all depths, but then culling not important. + const int farthestDepthIndex = 2; + uint uDepth = PassSrg::m_fragmentDepthsTexture[uint3(vScreenAddress, farthestDepthIndex)]; + + // The following line is writing the depth into the actual depth buffer + return asfloat(uDepth); +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader new file mode 100644 index 0000000000..5b518c3bfc --- /dev/null +++ b/Gems/AtomTressFX/Assets/Shaders/HairShortCutResolveDepth.shader @@ -0,0 +1,37 @@ +{ + "Source" : "HairShortCutResolveDepth.azsl", + + "DepthStencilState" : + { + "Depth" : + { + "Enable" : true, // test the written depth and accept/discard based on the depth buffer + "CompareFunc" : "GreaterEqual" + // Originally in TressFX this is LessEqual - Atom is using reverse sort + }, + "Stencil" : + { + "Enable" : false + } + }, + + "BlendState" : + { + "Enable" : false + }, + + "ProgramSettings": + { + "EntryPoints": + [ + { + "name": "FullScreenVS", + "type": "Vertex" + }, + { + "name": "HairShortCutResolveDepthPS", + "type": "Fragment" + } + ] + } +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl index 496aa5c7da..ed6ec5de27 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationCompute.azsl @@ -31,7 +31,7 @@ // THE SOFTWARE. // //-------------------------------------------------------------------------------------- -#include +#include #include //-------------------------------------------------------------------------------------- diff --git a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli similarity index 99% rename from Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli rename to Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli index f95dac92cf..3ded3ab2af 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairSimulationSrgs.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairSimulationComputeSrgs.azsli @@ -29,13 +29,13 @@ // THE SOFTWARE. // //------------------------------------------------------------------------------ -// File: HairSRGs.azsli +// File: HairSimulationComputeSrgs.azsli // // Declarations of SRGs used by the hair shaders. //------------------------------------------------------------------------------ #pragma once -#include +#include //!----------------------------------------------------------------------------- //! diff --git a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli index b8c4ea9eb4..abbbc8c0de 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairStrands.azsli @@ -66,12 +66,22 @@ float3 GetSharedTangent(int tangentIndex) ); } +//! Hair vertex geometry output - input structure for the Pixel shaders struct TressFXVertex { float4 Position; - float4 Tangent; + float4 Tangent; // xyz = Tangent, w = Strand U float4 p0p1; - float4 StrandColor; + float4 StrandColor; // xyz = Strand Color, w = Strand V +}; + +//! Matching structure to carry out as VS output / PS input +struct PS_INPUT_HAIR +{ + float4 Position : SV_POSITION; + float4 Tangent : Tangent; + float4 p0p1 : TEXCOORD0; + float4 StrandColor : TEXCOORD1; }; float3 GetStrandColor(int index, float fractionOfStrand) @@ -178,5 +188,26 @@ TressFXVertex GetExpandedTressFXShadowVert(uint vertexId, float3 eye, float2 win return Output; } -// EndHLSL +//!============================================================================= +//! Hair Render VS - Used by all geometry hair shaders +//!============================================================================= +PS_INPUT_HAIR RenderHairVS(uint vertexId : SV_VertexID) +{ + PS_INPUT_HAIR vsOutput; + // uint2 scrSize; + // PassSrg::m_linearDepth.GetDimensions(scrSize.x, scrSize.y); + // TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, float2(scrSize), g_mVP); + + // [To Do] Hair: the above code should replace the existing but requires modifications to + // the function GetExpandedTressFXVert. + // Note that in Atom g_vViewport is aspect ratio and NOT size. + TressFXVertex tressfxVert = GetExpandedTressFXVert(vertexId, g_vEye.xyz, g_vViewport.zw, g_mVP); + + vsOutput.Position = tressfxVert.Position; + vsOutput.Tangent = tressfxVert.Tangent; + vsOutput.p0p1 = tressfxVert.p0p1; + vsOutput.StrandColor = tressfxVert.StrandColor; + + return vsOutput; +} diff --git a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli index 1d50d3c040..f91ba1ff3b 100644 --- a/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli +++ b/Gems/AtomTressFX/Assets/Shaders/HairUtilities.azsli @@ -34,9 +34,6 @@ #pragma once -#include - - #define SHORTCUT_MIN_ALPHA 0.02 #define TRESSFX_FLOAT_EPSILON 1e-7 @@ -52,40 +49,6 @@ float4 MatrixMult(float4x4 m, float4 v) return mul(m, v); } -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the NDC. -// screenCoords - from 0.. dimension of the screen of the current pixel -// screenTexture - screen buffer texture representing the same resolution we work in -// sDepth - the depth buffer depth at the fragment location -// NDC - Normalized Device Coordinates = warped screen space ( -1.1, -1..1, 0..1 ) -float3 ScreenPosToNDC( Texture2D screenTexture, float2 screenCoords, float depth ) -{ - uint2 dimensions; - screenTexture.GetDimensions(dimensions.x, dimensions.y); - float2 UV = saturate(screenCoords / dimensions.xy); - - float x = UV.x * 2.0f - 1.0f; - float y = (1.0f - UV.y) * 2.0f - 1.0f; - float3 NDC = float3(x, y, depth); - - return NDC; -} - -// Given the depth buffer depth of the current pixel and the fragment XY position, -// reconstruct the world space position -float3 ScreenPosToWorldPos( - Texture2D screenTexture, float2 screenCoords, float depth, - inout float3 screenPosNDC ) -{ - screenPosNDC = ScreenPosToNDC(PassSrg::m_linearDepth, screenCoords, depth); - float4 projectedPos = float4(screenPosNDC, 1.0f); // warped projected space [0..1] - float4 positionVS = mul(ViewSrg::m_projectionMatrixInverse, projectedPos); - positionVS /= positionVS.w; // notice the normalization factor - crucial! - float4 positionWS = mul(ViewSrg::m_viewMatrixInverse, positionVS); - - return positionWS.xyz; -} - // Pack a float4 into an uint uint PackFloat4IntoUint(float4 vValue) { diff --git a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp index 66ac3d2e09..2ae8b9d97d 100644 --- a/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp +++ b/Gems/AtomTressFX/Code/Components/HairSystemComponent.cpp @@ -80,8 +80,14 @@ namespace AZ // Load the AtomTressFX pass classes passSystem->AddPassCreator(Name("HairSkinningComputePass"), &HairSkinningComputePass::Create); + + // Load the PPLL render method passes passSystem->AddPassCreator(Name("HairPPLLRasterPass"), &HairPPLLRasterPass::Create); passSystem->AddPassCreator(Name("HairPPLLResolvePass"), &HairPPLLResolvePass::Create); + + // Load the ShortCut render method passes + passSystem->AddPassCreator(Name("HairShortCutGeometryDepthAlphaPass"), &HairShortCutGeometryDepthAlphaPass::Create); + passSystem->AddPassCreator(Name("HairShortCutGeometryShadingPass"), &HairShortCutGeometryShadingPass::Create); } void HairSystemComponent::Deactivate() diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp index 7af5903cf3..b9c0d2e379 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.cpp @@ -165,6 +165,15 @@ namespace AZ return true; } + Data::Instance HairGeometryRasterPass::GetShader() + { + if (!m_initialized || !m_shader) + { + AZ_Error("Hair Gem", LoadShaderAndPipelineState(), "HairGeometryRasterPass could not initialize pipeline or shader"); + } + return m_shader; + } + void HairGeometryRasterPass::SchedulePacketBuild(HairRenderObject* hairObject) { m_newRenderObjects.insert(hairObject); @@ -188,7 +197,7 @@ namespace AZ // The PerPass is gathered through the RasterPass::m_shaderResourceGroup AZStd::lock_guard lock(m_mutex); - return hairObject->BuildPPLLDrawPacket(drawRequest); + return hairObject->BuildDrawPacket(m_shader.get(), drawRequest); } bool HairGeometryRasterPass::AddDrawPackets(AZStd::list>& hairRenderObjects) @@ -205,7 +214,7 @@ namespace AZ for (auto& renderObject : hairRenderObjects) { - const RHI::DrawPacket* drawPacket = renderObject->GetFillDrawPacket(); + const RHI::DrawPacket* drawPacket = renderObject->GetGeometrylDrawPacket(m_shader.get()); if (!drawPacket) { // might not be an error - the object might have just been added and the DrawPacket is // scheduled to be built when the render frame begins diff --git a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h index a226d2c294..d3ba22039d 100644 --- a/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h +++ b/Gems/AtomTressFX/Code/Passes/HairGeometryRasterPass.h @@ -51,7 +51,7 @@ namespace AZ //! The following will be called when an object was added or shader has been compiled void SchedulePacketBuild(HairRenderObject* hairObject); - Data::Instance GetShader() { return m_shader; } + Data::Instance GetShader(); void SetFeatureProcessor(HairFeatureProcessor* featureProcessor) { @@ -76,7 +76,6 @@ namespace AZ // Pass behavior overrides void InitializeInternal() override; -// void BuildInternal() override; void FrameBeginInternal(FramePrepareParams params) override; // Scope producer functions... diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp index fa643a5488..de66d91e0b 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.cpp @@ -30,6 +30,17 @@ namespace AZ HairPPLLResolvePass::HairPPLLResolvePass(const RPI::PassDescriptor& descriptor) : RPI::FullscreenTrianglePass(descriptor) { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); } void HairPPLLResolvePass::UpdateGlobalShaderOptions() @@ -38,17 +49,17 @@ namespace AZ m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); - shaderOption.SetValue(AZ::Name("o_enableShadows"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); - shaderOption.SetValue(AZ::Name("o_enableDirectionalLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); - shaderOption.SetValue(AZ::Name("o_enablePunctualLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); - shaderOption.SetValue(AZ::Name("o_enableAreaLights"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); - shaderOption.SetValue(AZ::Name("o_enableIBL"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); - shaderOption.SetValue(AZ::Name("o_hairLightingModel"), AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_R"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TRT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); - shaderOption.SetValue(AZ::Name("o_enableMarschner_TT"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); - shaderOption.SetValue(AZ::Name("o_enableLongtitudeCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); - shaderOption.SetValue(AZ::Name("o_enableAzimuthCoeff"), AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); } diff --git a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h index bf1a3f768e..036659b9a5 100644 --- a/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h +++ b/Gems/AtomTressFX/Code/Passes/HairPPLLResolvePass.h @@ -58,9 +58,20 @@ namespace AZ void CompileResources(const RHI::FrameGraphCompileContext& context) override; private: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + HairPPLLResolvePass(const RPI::PassDescriptor& descriptor); - private: void UpdateGlobalShaderOptions(); HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp index 24cd6465dd..1b0b034dba 100644 --- a/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp +++ b/Gems/AtomTressFX/Code/Passes/HairParentPass.cpp @@ -9,7 +9,6 @@ #include #include #include -#include namespace AZ { diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp new file mode 100644 index 0000000000..5e402e33f6 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp @@ -0,0 +1,50 @@ +/* + * 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 + * + */ + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryDepthAlphaPass::HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + SetShaderPath("Shaders/hairshortcutgeometrydepthalpha.azshader"); + } + + RPI::Ptr HairShortCutGeometryDepthAlphaPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryDepthAlphaPass(descriptor); + return pass; + } + + void HairShortCutGeometryDepthAlphaPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h new file mode 100644 index 0000000000..da6e28fe31 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryDepthAlphaPass.h @@ -0,0 +1,49 @@ +/* + * 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 + * + */ +#pragma once + +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryDepthAlphaPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryDepthAlphaPass); + + public: + AZ_RTTI(HairShortCutGeometryDepthAlphaPass, "{F09A0411-B1FF-4085-98E7-6B8B0E1B2C3D}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryDepthAlphaPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + protected: + explicit HairShortCutGeometryDepthAlphaPass(const RPI::PassDescriptor& descriptor); + + // Pass behavior overrides + void BuildInternal() override; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp new file mode 100644 index 0000000000..eda7f9f83a --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.cpp @@ -0,0 +1,111 @@ +/* + * 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 + * + */ + +#include +#include +#include + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + namespace Hair + { + + HairShortCutGeometryShadingPass::HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor) + : HairGeometryRasterPass(descriptor) + { + o_enableShadows = AZ::Name("o_enableShadows"); + o_enableDirectionalLights = AZ::Name("o_enableDirectionalLights"); + o_enablePunctualLights = AZ::Name("o_enablePunctualLights"); + o_enableAreaLights = AZ::Name("o_enableAreaLights"); + o_enableIBL = AZ::Name("o_enableIBL"); + o_hairLightingModel = AZ::Name("o_hairLightingModel"); + o_enableMarschner_R = AZ::Name("o_enableMarschner_R"); + o_enableMarschner_TRT = AZ::Name("o_enableMarschner_TRT"); + o_enableMarschner_TT = AZ::Name("o_enableMarschner_TT"); + o_enableLongtitudeCoeff = AZ::Name("o_enableLongtitudeCoeff"); + o_enableAzimuthCoeff = AZ::Name("o_enableAzimuthCoeff"); + + SetShaderPath("Shaders/hairshortcutgeometryshading.azshader"); + } + + RPI::Ptr HairShortCutGeometryShadingPass::Create(const RPI::PassDescriptor& descriptor) + { + RPI::Ptr pass = aznew HairShortCutGeometryShadingPass(descriptor); + return pass; + } + + void HairShortCutGeometryShadingPass::UpdateGlobalShaderOptions() + { + RPI::ShaderOptionGroup shaderOption = m_shader->CreateShaderOptionGroup(); + + m_featureProcessor->GetHairGlobalSettings(m_hairGlobalSettings); + + shaderOption.SetValue(o_enableShadows, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableShadows }); + shaderOption.SetValue(o_enableDirectionalLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableDirectionalLights }); + shaderOption.SetValue(o_enablePunctualLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enablePunctualLights }); + shaderOption.SetValue(o_enableAreaLights, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAreaLights }); + shaderOption.SetValue(o_enableIBL, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableIBL }); + shaderOption.SetValue(o_hairLightingModel, AZ::Name{ "HairLightingModel::" + AZStd::string(HairLightingModelNamespace::ToString(m_hairGlobalSettings.m_hairLightingModel)) }); + shaderOption.SetValue(o_enableMarschner_R, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_R }); + shaderOption.SetValue(o_enableMarschner_TRT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TRT }); + shaderOption.SetValue(o_enableMarschner_TT, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableMarschner_TT }); + shaderOption.SetValue(o_enableLongtitudeCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableLongtitudeCoeff }); + shaderOption.SetValue(o_enableAzimuthCoeff, AZ::RPI::ShaderOptionValue{ m_hairGlobalSettings.m_enableAzimuthCoeff }); + + m_shaderOptions = shaderOption.GetShaderVariantKeyFallbackValue(); + } + + void HairShortCutGeometryShadingPass::CompileResources(const RHI::FrameGraphCompileContext& context) + { + if (!m_shaderResourceGroup || !AcquireFeatureProcessor()) + { + AZ_Error("Hair Gem", m_shaderResourceGroup, "HairShortCutGeometryShadingPass: missing Srg or no feature processor yet"); + return; // no error message due to FP - initialization not complete yet, wait for the next frame + } + + UpdateGlobalShaderOptions(); + + if (m_shaderResourceGroup->HasShaderVariantKeyFallbackEntry()) + { + m_shaderResourceGroup->SetShaderVariantKeyFallbackValue(m_shaderOptions); + } + + // Update the material array constant buffer within the per pass srg + SrgBufferDescriptor descriptor = SrgBufferDescriptor( + RPI::CommonBufferPoolType::Constant, RHI::Format::Unknown, + sizeof(AMD::TressFXShadeParams), 1, + Name{ "HairMaterialsArray" }, Name{ "m_hairParams" }, 0, 0 + ); + + m_featureProcessor->GetMaterialsArray().UpdateGPUData(m_shaderResourceGroup, descriptor); + + // Compilation of remaining srgs will be done by the parent class + RPI::RasterPass::CompileResources(context); + } + + void HairShortCutGeometryShadingPass::BuildInternal() + { + RasterPass::BuildInternal(); // change this to call parent if the method exists + + if (!AcquireFeatureProcessor()) + { + return; + } + + LoadShaderAndPipelineState(); + } + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h new file mode 100644 index 0000000000..d728803473 --- /dev/null +++ b/Gems/AtomTressFX/Code/Passes/HairShortCutGeometryShadingPass.h @@ -0,0 +1,69 @@ +/* + * 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 + * + */ +#pragma once + +#include +#include + +namespace AZ +{ + namespace RHI + { + struct DrawItem; + } + + namespace Render + { + namespace Hair + { + //! This geometry pass uses the following Srgs: + //! - PerPassSrg shared by all hair passes for the shared dynamic buffer + //! - PerMaterialSrg - used solely by this pass to alter the vertices and apply the visual + //! hair properties to each fragment. + //! - HairDynamicDataSrg (PerObjectSrg) - shared buffers views for this hair object only. + //! - PerViewSrg and PerSceneSrg - as per the data from Atom. + class HairShortCutGeometryShadingPass + : public HairGeometryRasterPass + { + AZ_RPI_PASS(HairShortCutGeometryShadingPass); + + public: + AZ_RTTI(HairShortCutGeometryShadingPass, "{11BA673D-0788-4B25-978D-9737BF4E48FE}", HairGeometryRasterPass); + AZ_CLASS_ALLOCATOR(HairShortCutGeometryShadingPass, SystemAllocator, 0); + + static RPI::Ptr Create(const RPI::PassDescriptor& descriptor); + + void CompileResources(const RHI::FrameGraphCompileContext& context) override; + + protected: + AZ::Name o_enableShadows; + AZ::Name o_enableDirectionalLights; + AZ::Name o_enablePunctualLights; + AZ::Name o_enableAreaLights; + AZ::Name o_enableIBL; + AZ::Name o_hairLightingModel; + AZ::Name o_enableMarschner_R; + AZ::Name o_enableMarschner_TRT; + AZ::Name o_enableMarschner_TT; + AZ::Name o_enableLongtitudeCoeff; + AZ::Name o_enableAzimuthCoeff; + + explicit HairShortCutGeometryShadingPass(const RPI::PassDescriptor& descriptor); + + void UpdateGlobalShaderOptions(); + + // Pass behavior overrides + void BuildInternal() override; + + HairGlobalSettings m_hairGlobalSettings; + AZ::RPI::ShaderVariantKey m_shaderOptions; + }; + + } // namespace Hair + } // namespace Render +} // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp index 7a317dc09c..a0be18f0e2 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.cpp @@ -47,11 +47,11 @@ namespace AZ HairFeatureProcessor::HairFeatureProcessor() { + m_usePPLLRenderTechnique = false; // Use the ShortCut rendering technique + HairParentPassName = Name{ "HairParentPass" }; - HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; - HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; - + // Hair Skinning and Simulation Compute passes GlobalShapeConstraintsPassName = Name{ "HairGlobalShapeConstraintsComputePass" }; CalculateStrandDataPassName = Name{ "HairCalculateStrandLevelDataComputePass" }; VelocityShockPropagationPassName = Name{ "HairVelocityShockPropagationComputePass" }; @@ -59,12 +59,21 @@ namespace AZ LengthConstriantsWindAndCollisionPassName = Name{ "HairLengthConstraintsWindAndCollisionComputePass" }; UpdateFollowHairPassName = Name{ "HairUpdateFollowHairComputePass" }; + // PPLL render technique pases + HairPPLLRasterPassName = Name{ "HairPPLLRasterPass" }; + HairPPLLResolvePassName = Name{ "HairPPLLResolvePass" }; + + // ShortCut render technique pases + HairShortCutGeometryDepthAlphaPassName = Name{ "HairShortCutGeometryDepthAlphaPass" }; + HairShortCutResolveDepthPassName = Name{ "HairShortCutResolveDepthPass" }; + HairShortCutGeometryShadingPassName = Name{ "HairShortCutGeometryShadingPass" }; + HairShortCutResolveColorPassName = Name{ "HairShortCutResolveColorPass" }; + ++s_instanceCount; if (!CreatePerPassResources()) { // this might not be an error - if the pass system is still empty / minimal - // and these passes are not part of the minimal pipeline, they will not - // be created. + // and these passes are not part of the minimal pipeline, they will not be created. AZ_Error("Hair Gem", false, "Failed to create the hair shared buffer resource"); } } @@ -127,25 +136,19 @@ namespace AZ m_hairRenderObjects.push_back(renderObject); + // Adding the object will schedule Srgs binding and the DrawItem build for the geometry passes. BuildDispatchAndDrawItems(renderObject); EnablePasses(true); } - void HairFeatureProcessor::EnablePasses(bool enable) + void HairFeatureProcessor::EnablePasses([[maybe_unused]] bool enable) { - if (!m_initialized) + RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairParentPassName); + if (desiredPass) { - return; + desiredPass->SetEnabled(enable); } - - for (auto& [passName, pass] : m_computePasses) - { - pass->SetEnabled(enable); - } - - m_hairPPLLRasterPass->SetEnabled(enable); - m_hairPPLLResolvePass->SetEnabled(enable); } bool HairFeatureProcessor::RemoveHairRenderObject(Data::Instance renderObject) @@ -167,15 +170,13 @@ namespace AZ void HairFeatureProcessor::UpdateHairSkinning() { - // Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..) - - for (auto objIter = m_hairRenderObjects.begin(); objIter != m_hairRenderObjects.end(); ++objIter) + // Copying CPU side m_SimCB content to the GPU buffer (matrices, wind parameters..) + for (auto& hairRenderObject : m_hairRenderObjects) { - if (!objIter->get()->IsEnabled()) + if (hairRenderObject->IsEnabled()) { - return; + hairRenderObject->Update(); } - objIter->get()->Update(); } } @@ -214,7 +215,8 @@ namespace AZ } if (m_forceRebuildRenderData) - { + { // In the case of a force build, schedule Srgs binding and the DrawItem build for + // the geometry passes of all existing hair objects. for (auto& hairRenderObject : m_hairRenderObjects) { BuildDispatchAndDrawItems(hairRenderObject); @@ -276,17 +278,32 @@ namespace AZ pass->AddDispatchItems(m_hairRenderObjects); } - // Add all hair objects to the Render / Raster Pass - m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + if (m_usePPLLRenderTechnique) + { + // Add all hair objects to the Render / Raster Pass + m_hairPPLLRasterPass->AddDrawPackets(m_hairRenderObjects); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->AddDrawPackets(m_hairRenderObjects); + m_hairShortCutGeometryShadingPass->AddDrawPackets(m_hairRenderObjects); + } } void HairFeatureProcessor::ClearPasses() { m_initialized = false; // Avoid simulation or render m_computePasses.clear(); + + // PPLL geometry and resolve full screen passes m_hairPPLLRasterPass = nullptr; m_hairPPLLResolvePass = nullptr; + // ShortCut passes - Special handling of geometry passes only, and using the regular + // full screen pass for the resolve + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + // Mark for all passes to evacuate their render data and recreate it. m_forceRebuildRenderData = true; m_forceClearRenderData = true; @@ -338,6 +355,12 @@ namespace AZ ClearPasses(); + if (!m_renderPipeline) + { + AZ_Error("Hair Gem", false, "HairFeatureProcessor does NOT have render pipeline set yet"); + return false; + } + // Compute Passes - populate the passes map bool resultSuccess = InitComputePass(GlobalShapeConstraintsPassName); resultSuccess &= InitComputePass(CalculateStrandDataPassName); @@ -347,8 +370,15 @@ namespace AZ resultSuccess &= InitComputePass(UpdateFollowHairPassName); // Rendering Passes - resultSuccess &= InitPPLLFillPass(); - resultSuccess &= InitPPLLResolvePass(); + if (m_usePPLLRenderTechnique) + { + resultSuccess &= InitPPLLFillPass(); + resultSuccess &= InitPPLLResolvePass(); + } + else + { + resultSuccess &= InitShortCutRenderPasses(); + } m_initialized = resultSuccess; @@ -388,7 +418,8 @@ namespace AZ } } - // PPLL nodes buffer + // PPLL nodes buffer - created only if the PPLL technique is used + if (m_usePPLLRenderTechnique) { descriptor = SrgBufferDescriptor( RPI::CommonBufferPoolType::ReadWrite, RHI::Format::Unknown, @@ -425,11 +456,6 @@ namespace AZ bool HairFeatureProcessor::InitComputePass(const Name& passName, bool allowIterations) { m_computePasses[passName] = nullptr; - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "%s does NOT have render pipeline set yet", passName.GetCStr()); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(passName); if (desiredPass) @@ -452,11 +478,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLFillPass() { m_hairPPLLRasterPass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLRasterPassName); if (desiredPass) @@ -466,7 +487,7 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLRasterPass does not have any valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLRasterPass cannot be found. Check your game project's .pass assets."); return false; } return true; @@ -475,11 +496,6 @@ namespace AZ bool HairFeatureProcessor::InitPPLLResolvePass() { m_hairPPLLResolvePass = nullptr; // reset it to null, just in case it fails to load the assets properly - if (!m_renderPipeline) - { - AZ_Error("Hair Gem", false, "Hair Fill Pass does NOT have render pipeline set yet"); - return false; - } RPI::Ptr desiredPass = m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairPPLLResolvePassName); if (desiredPass) @@ -489,12 +505,46 @@ namespace AZ } else { - AZ_Error("Hair Gem", false, "HairPPLLResolvePassTemplate does not have valid passes. Check your game project's .pass assets."); + AZ_Error("Hair Gem", false, "HairPPLLResolvePass cannot be found. Check your game project's .pass assets."); return false; } return true; } + //! Set the two short cut geometry pases and assign them the FP. The other two full screen passes + //! are generic full screen passes and don't need any interaction with the FP. + bool HairFeatureProcessor::InitShortCutRenderPasses() + { + m_hairShortCutGeometryDepthAlphaPass = nullptr; + m_hairShortCutGeometryShadingPass = nullptr; + + m_hairShortCutGeometryDepthAlphaPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryDepthAlphaPassName).get()); + if (m_hairShortCutGeometryDepthAlphaPass) + { + m_hairShortCutGeometryDepthAlphaPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutResolveDepthPass cannot be found. Check your game project's .pass assets."); + return false; + } + + m_hairShortCutGeometryShadingPass = static_cast( + m_renderPipeline->GetRootPass()->FindPassByNameRecursive(HairShortCutGeometryShadingPassName).get()); + if (m_hairShortCutGeometryShadingPass) + { + m_hairShortCutGeometryShadingPass->SetFeatureProcessor(this); + } + else + { + AZ_Error("Hair Gem", false, "HairShortCutGeometryShadingPass cannot be found. Check your game project's .pass assets."); + return false; + } + + return true; + } + void HairFeatureProcessor::BuildDispatchAndDrawItems(Data::Instance renderObject) { HairRenderObject* renderObjectPtr = renderObject.get(); @@ -513,9 +563,18 @@ namespace AZ m_computePasses[UpdateFollowHairPassName]->BuildDispatchItem( renderObjectPtr, DispatchLevel::DISPATCHLEVEL_VERTEX); - // Render / Raster pass - adding the object will schedule Srgs binding - // and DrawItem build. - m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + // Schedule Srgs binding and the DrawItem build. + // Since this does not bind the PerPass srg but prepare the rest of the Srgs + // such as the dynamic srg, it should only be done once per object per frame. + if (m_usePPLLRenderTechnique) + { + m_hairPPLLRasterPass->SchedulePacketBuild(renderObjectPtr); + } + else + { + m_hairShortCutGeometryDepthAlphaPass->SchedulePacketBuild(renderObjectPtr); + m_hairShortCutGeometryShadingPass->SchedulePacketBuild(renderObjectPtr); + } } Data::Instance HairFeatureProcessor::GetHairSkinningComputegPass() @@ -527,14 +586,28 @@ namespace AZ return m_computePasses[GlobalShapeConstraintsPassName]; } - Data::Instance HairFeatureProcessor::GetHairPPLLRasterPass() + Data::Instance HairFeatureProcessor::GetGeometryRasterShader() { - if (!m_hairPPLLRasterPass) + if (m_usePPLLRenderTechnique) { - Init(m_renderPipeline); + if (!m_hairPPLLRasterPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairPPLLRasterPass was not created"); + return nullptr; + } + return m_hairPPLLRasterPass->GetShader(); } - return m_hairPPLLRasterPass; + + if (!m_hairShortCutGeometryDepthAlphaPass && !Init(m_renderPipeline)) + { + AZ_Error("Hair Gem", false, + "GetGeometryRasterShader - m_hairShortCutGeometryDepthAlphaPass was not created"); + return nullptr; + } + return m_hairShortCutGeometryDepthAlphaPass->GetShader(); } + } // namespace Hair } // namespace Render } // namespace AZ diff --git a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h index c56dc5c921..46660a6623 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h +++ b/Gems/AtomTressFX/Code/Rendering/HairFeatureProcessor.h @@ -17,14 +17,19 @@ #include #include +#include // Hair specific #include #include + #include #include +#include +#include + #include #include #include @@ -73,9 +78,7 @@ namespace AZ { Name HairParentPassName; - Name HairPPLLRasterPassName; - Name HairPPLLResolvePassName; - + // Compute passes Name GlobalShapeConstraintsPassName; Name CalculateStrandDataPassName; Name VelocityShockPropagationPassName; @@ -83,6 +86,16 @@ namespace AZ Name LengthConstriantsWindAndCollisionPassName; Name UpdateFollowHairPassName; + // PPLL render passes + Name HairPPLLRasterPassName; + Name HairPPLLResolvePassName; + + // ShortCut render passes + Name HairShortCutGeometryDepthAlphaPassName; + Name HairShortCutResolveDepthPassName; + Name HairShortCutGeometryShadingPassName; + Name HairShortCutResolveColorPassName; + public: AZ_RTTI(AZ::Render::Hair::HairFeatureProcessor, "{5F9DDA81-B43F-4E30-9E56-C7C3DC517A4C}", RPI::FeatureProcessor); @@ -117,6 +130,7 @@ namespace AZ Data::Instance GetHairSkinningComputegPass(); Data::Instance GetHairPPLLRasterPass(); + Data::Instance GetGeometryRasterShader(); //! Update the hair objects materials array. void FillHairMaterialsArray(std::vector& renderSettings); @@ -144,6 +158,7 @@ namespace AZ bool InitPPLLFillPass(); bool InitPPLLResolvePass(); + bool InitShortCutRenderPasses(); bool InitComputePass(const Name& passName, bool allowIterations = false); void BuildDispatchAndDrawItems(Data::Instance renderObject); @@ -168,10 +183,14 @@ namespace AZ //! Simulation Compute Passes AZStd::unordered_map > m_computePasses; - // Render Passes + // PPLL Render Passes Data::Instance m_hairPPLLRasterPass = nullptr; Data::Instance m_hairPPLLResolvePass = nullptr; + // ShortCut Render Passes - special case for the geometry render passes + Data::Instance m_hairShortCutGeometryDepthAlphaPass = nullptr; + Data::Instance m_hairShortCutGeometryShadingPass = nullptr; + //-------------------------------------------------------------- // Per Pass Resources //-------------------------------------------------------------- @@ -196,6 +215,7 @@ namespace AZ bool m_forceClearRenderData = false; bool m_initialized = false; bool m_isEnabled = true; + bool m_usePPLLRenderTechnique = true; static uint32_t s_instanceCount; HairGlobalSettings m_hairGlobalSettings; diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp index baf986daf6..4d615e31ae 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.cpp @@ -993,7 +993,7 @@ namespace AZ //------------------------------------- // Dynamic buffers, data and Srg creation - shared between passes and changed on the GPU if (!m_dynamicHairData.CreateDynamicGPUResources( - m_skinningShader, m_PPLLFillShader, + m_skinningShader, m_geometryRasterShader, m_NumTotalVertices, m_NumTotalStrands)) { AZ_Error("Hair Gem", false, "Hair - Error creating dynamic resources [%s]", assetName ); @@ -1028,7 +1028,7 @@ namespace AZ // Rendering setup bool renderResourcesSuccess; - renderResourcesSuccess = CreateRenderingGPUResources(m_PPLLFillShader, *asset, assetName); + renderResourcesSuccess = CreateRenderingGPUResources(m_geometryRasterShader, *asset, assetName); renderResourcesSuccess &= PopulateDrawStrandsBindSet(renderSettings); renderResourcesSuccess &= UploadRenderingGPUResources(*asset); @@ -1057,17 +1057,10 @@ namespace AZ } { - Data::Instance rasterPass = m_featureProcessor->GetHairPPLLRasterPass(); - if (!rasterPass.get()) + m_geometryRasterShader = m_featureProcessor->GetGeometryRasterShader(); + if (!m_geometryRasterShader) { - AZ_Error("Hair Gem", false, "Failed to get PPLL raster fill Pass."); - return false; - } - - m_PPLLFillShader = rasterPass->GetShader(); - if (!m_PPLLFillShader) - { - AZ_Error("Hair Gem", false, "Failed to get hair raster fill shader from raster pass"); + AZ_Error("Hair Gem", false, "Failed to get hair geometry raster shader"); return false; } } @@ -1116,7 +1109,7 @@ namespace AZ return updatedCB; } - bool HairRenderObject::BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest) + bool HairRenderObject::BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest) { RHI::DrawPacketBuilder drawPacketBuilder; RHI::DrawIndexed drawIndexed; @@ -1159,21 +1152,38 @@ namespace AZ drawPacketBuilder.AddShaderResourceGroup(simSrg->GetRHIShaderResourceGroup()); drawPacketBuilder.AddDrawItem(drawRequest); - if (m_fillDrawPacket) - { - delete m_fillDrawPacket; - } - m_fillDrawPacket = drawPacketBuilder.End(); - - if (!m_fillDrawPacket) + const RHI::DrawPacket* drawPacket = drawPacketBuilder.End(); + if (!drawPacket) { AZ_Error("Hair Gem", false, "Failed to build the hair DrawPacket."); return false; } + // Insert the newly created draw packet to the map based on its shader + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter != m_geometryDrawPackets.end()) + { + delete iter->second; + iter->second = drawPacket; + } + else + { + m_geometryDrawPackets[geometryShader] = drawPacket; + } + return true; } + const RHI::DrawPacket* HairRenderObject::GetGeometrylDrawPacket(RPI::Shader* geometryShader) + { + auto iter = m_geometryDrawPackets.find(geometryShader); + if (iter == m_geometryDrawPackets.end()) + { + return nullptr; + } + return iter->second; + } + const RHI::DispatchItem* HairRenderObject::GetDispatchItem(RPI::Shader* computeShader) { auto dispatchIter = m_dispatchItems.find(computeShader); @@ -1210,4 +1220,3 @@ namespace AZ } // namespace Hair } // namespace Render } // namespace AZ - diff --git a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h index fa4095eed0..817ebd89fa 100644 --- a/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h +++ b/Gems/AtomTressFX/Code/Rendering/HairRenderObject.h @@ -179,14 +179,9 @@ namespace AZ AMD::TressFXSimulationSettings* simSettings, AMD::TressFXRenderingSettings* renderSettings ); - //! Creates and fill the draw item associated with the PPLL render of the - //! current hair object - const RHI::DrawPacket* GetFillDrawPacket() - { - return m_fillDrawPacket; - } + bool BuildDrawPacket(RPI::Shader* geometryShader, RHI::DrawPacketBuilder::DrawRequest& drawRequest); - bool BuildPPLLDrawPacket(RHI::DrawPacketBuilder::DrawRequest& drawRequest); + const RHI::DrawPacket* GetGeometrylDrawPacket(RPI::Shader* geometryShader); //! Creates and fill the dispatch item associated with the compute shader bool BuildDispatchItem(RPI::Shader* computeShader, DispatchLevel dispatchLevel); @@ -302,17 +297,20 @@ namespace AZ //! responsible for the various stages and passes' updates HairFeatureProcessor* m_featureProcessor = nullptr; - //! The dispatch item used for the skinning - HairDispatchItem m_skinningDispatchItem; - - //! Compute dispatch items map per the existing passes - AZStd::map> m_dispatchItems; - //! Skinning compute shader used for creation of the compute Srgs and dispatch item Data::Instance m_skinningShader = nullptr; - //! PPLL fill shader used for creation of the raster Srgs and draw item - Data::Instance m_PPLLFillShader = nullptr; + //! Compute dispatch items map per the existing passes + AZStd::unordered_map> m_dispatchItems; + + //! Geometry raster shader used for creation of the raster Srgs. + //! Since the Srgs for geometry raster are the same across the shaders we keep + //! only a single shader - if this to change in the future, several shaders and sets + //! of dynamic Srgs should be created. + Data::Instance m_geometryRasterShader = nullptr; + + //! DrawPacket for the multi object geometry raster pass. + AZStd::unordered_map m_geometryDrawPackets; float m_frameDeltaTime = 0.02; @@ -378,9 +376,6 @@ namespace AZ //! Index buffer for the render pass via draw calls - naming was kept Data::Instance m_indexBuffer; RHI::IndexBufferView m_indexBufferView; - - //! DrawPacket for the multi object raster fill pass. - const RHI::DrawPacket* m_fillDrawPacket = nullptr; //------------------------------------------------------------------- AZStd::mutex m_mutex; diff --git a/Gems/AtomTressFX/Hair_files.cmake b/Gems/AtomTressFX/Hair_files.cmake index 000abeb559..180685d499 100644 --- a/Gems/AtomTressFX/Hair_files.cmake +++ b/Gems/AtomTressFX/Hair_files.cmake @@ -67,6 +67,13 @@ set(FILES # Base class of all geometry raster passes Code/Passes/HairGeometryRasterPass.h Code/Passes/HairGeometryRasterPass.cpp + + # ShortCut rendering technique - pass classes + Code/Passes/HairShortCutGeometryDepthAlphaPass.h + Code/Passes/HairShortCutGeometryDepthAlphaPass.cpp + Code/Passes/HairShortCutGeometryShadingPass.h + Code/Passes/HairShortCutGeometryShadingPass.cpp + # PPLL rendering technique - geometry raster pass Code/Passes/HairPPLLRasterPass.h Code/Passes/HairPPLLRasterPass.cpp @@ -84,30 +91,37 @@ set(FILES Code/Assets/HairAsset.cpp #) #set(shaders_sources - # Srgs and Utility files - Assets/Shaders/HairSrgs.azsli - Assets/Shaders/HairSimulationSrgs.azsli + # Geometry and Full Screen azsl utility files Assets/Shaders/HairRenderingSrgs.azsli - Assets/Shaders/HairSimulationCommon.azsli Assets/Shaders/HairStrands.azsli Assets/Shaders/HairUtilities.azsli + Assets/Shaders/HairFullScreenUtils.azsli Assets/Shaders/HairLighting.azsli Assets/Shaders/HairLightingEquations.azsli Assets/Shaders/HairLightTypes.azsli Assets/Shaders/HairSurface.azsli - # Simulation Compute shaders - Assets/Shaders/HairSimulationCompute.azsl - - # Collision shaders - to be included soon -# Assets/Shaders/HairCollisionPrepareSDF.azsl -# Assets/Shaders/HairCollisionWithSDF.azsl + # ShortCut technique shaders (using multiple RTs instead of PPLL for GPU memory reduction) + Assets/Shaders/HairShortCutGeometryDepthAlpha.azsl + Assets/Shaders/HairShortCutResolveDepth.azsl + Assets/Shaders/HairShortCutGeometryShading.azsl + Assets/Shaders/HairShortCutResolveColor.azsl - # Rendering shaders + # Rendering azsl files Assets/Shaders/HairRenderingFillPPLL.azsl Assets/Shaders/HairRenderingResolvePPLL.azsl - # Simulation .shader files + # Simulation Compute azsl files + Assets/Shaders/HairComputeSrgs.azsli + Assets/Shaders/HairSimulationComputeSrgs.azsli + Assets/Shaders/HairSimulationCommon.azsli + Assets/Shaders/HairSimulationCompute.azsl + + # Collision azsl files - to be included soon +# Assets/Shaders/HairCollisionPrepareSDF.azsl +# Assets/Shaders/HairCollisionWithSDF.azsl + + # Simulation Compute .shader files Assets/Shaders/HairGlobalShapeConstraintsCompute.shader Assets/Shaders/HairCalculateStrandLevelDataCompute.shader Assets/Shaders/HairVelocityShockPropagationCompute.shader @@ -115,9 +129,15 @@ set(FILES Assets/Shaders/HairLengthConstraintsWindAndCollisionCompute.shader Assets/Shaders/HairUpdateFollowHairCompute.shader - # Rendering .shader file + # PPLL Render .shader file Assets/Shaders/HairRenderingFillPPLL.shader Assets/Shaders/HairRenderingResolvePPLL.shader + + # ShortCut Render .shader file + Assets/Shaders/HairShortCutGeometryDepthAlpha.shader + Assets/Shaders/HairShortCutResolveDepth.shader + Assets/Shaders/HairShortCutGeometryShading.shader + Assets/Shaders/HairShortCutResolveColor.shader # Colisions .shader files - to be included soon # Assets/Shaders/HairCollisionInitializeSDF.shader @@ -127,15 +147,25 @@ set(FILES #) # #set(atom_hair_passes + # Compute simulation and skinning passes Assets/Passes/HairParentPass.pass + Assets/Passes/HairParentShortCutPass.pass Assets/Passes/HairGlobalShapeConstraintsCompute.pass Assets/Passes/HairCalculateStrandLevelDataCompute.pass Assets/Passes/HairVelocityShockPropagationCompute.pass Assets/Passes/HairLocalShapeConstraintsCompute.pass Assets/Passes/HairLengthConstraintsWindAndCollisionCompute.pass Assets/Passes/HairUpdateFollowHairCompute.pass + + # PPLL render passes Assets/Passes/HairFillPPLL.pass Assets/Passes/HairResolvePPLL.pass + + # Shortcut render passes + Assets/Passes/HairShortCutGeometryDepthAlpha.pass + Assets/Passes/HairShortCutResolveDepth.pass + Assets/Passes/HairShortCutGeometryShading.pass + Assets/Passes/HairShortCutResolveColor.pass ) set(SKIP_UNITY_BUILD_INCLUSION_FILES diff --git a/Gems/AudioEngineWwise/Code/CMakeLists.txt b/Gems/AudioEngineWwise/Code/CMakeLists.txt index 98a31fb91a..33c80bc31c 100644 --- a/Gems/AudioEngineWwise/Code/CMakeLists.txt +++ b/Gems/AudioEngineWwise/Code/CMakeLists.txt @@ -18,31 +18,18 @@ set(AUDIOENGINEWWISE_COMPILEDEFINITIONS find_package(Wwise MODULE) ################################################################################ -# Server / Unsupported +# Servers +# (and situations where Wwise SDK is not found or otherwise unavailable) ################################################################################ -if (PAL_TRAIT_BUILD_SERVER_SUPPORTED OR PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB OR NOT Wwise_FOUND) - # Stub gem for server and unsupported platforms. Audio Engine Wwise is client only - ly_add_target( - NAME AudioEngineWwise.Stub ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} - NAMESPACE Gem - FILES_CMAKE - audioenginewwise_stub_files.cmake - BUILD_DEPENDENCIES - PRIVATE - AZ::AzCore - ) -endif() - -if (PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB OR NOT Wwise_FOUND) - # setup aliases so stubs will be used if something references AudioEngineWwise(.Editor) - add_library(Gem::AudioEngineWwise ALIAS AudioEngineWwise.Stub) - add_library(Gem::AudioEngineWwise.Editor ALIAS AudioEngineWwise.Stub) +if(NOT PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED OR NOT Wwise_FOUND) + # Don't create any Gem targets and aliases. Nothing should depend on this + # Gem directly, because if it doesn't define targets it will cause an error. return() endif() ################################################################################ -# Runtime / Game +# Clients ################################################################################ ly_add_target( NAME AudioEngineWwise.Static STATIC @@ -181,7 +168,7 @@ if (PAL_TRAIT_BUILD_TESTS_SUPPORTED) endif() ################################################################################ -# Tools / Editor +# Tools / Builders ################################################################################ if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( diff --git a/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake b/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Android/PAL_android.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake b/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Linux/PAL_linux.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake b/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Mac/PAL_mac.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake b/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/Windows/PAL_windows.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake b/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake index e3729baea7..c706c464d3 100644 --- a/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake +++ b/Gems/AudioEngineWwise/Code/Platform/iOS/PAL_ios.cmake @@ -6,4 +6,4 @@ # # -set(PAL_TRAIT_AUDIO_ENGINE_WWISE_USE_STUB FALSE) +set(PAL_TRAIT_AUDIO_ENGINE_WWISE_SUPPORTED TRUE) diff --git a/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp b/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp deleted file mode 100644 index 4344eb6072..0000000000 --- a/Gems/AudioEngineWwise/Code/Source/AudioEngineWwiseModule_Stub.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 - * - */ - -#include - -AZ_DECLARE_MODULE_CLASS(Gem_AudioEngineWwise, AZ::Module) diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp index c5e50ed9dd..a0a811eff0 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphMotionNode.cpp @@ -597,7 +597,7 @@ namespace EMotionFX // reset several settings to rewind the motion instance motionInstance->ResetTimes(); motionInstance->SetIsFrozen(false); - SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32); + SetSyncIndex(animGraphInstance, InvalidIndex); uniqueData->SetCurrentPlayTime(motionInstance->GetCurrentTime()); uniqueData->SetDuration(motionInstance->GetDuration()); uniqueData->SetPreSyncTime(uniqueData->GetCurrentPlayTime()); diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNode.h b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNode.h index b515621c5b..95d9c0d77d 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNode.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNode.h @@ -213,12 +213,12 @@ namespace EMotionFX */ virtual void SkipOutput([[maybe_unused]] AnimGraphInstance* animGraphInstance) {} - MCORE_INLINE float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); } + float GetDuration(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetDuration(); } virtual void SetCurrentPlayTime(AnimGraphInstance* animGraphInstance, float timeInSeconds) { FindOrCreateUniqueNodeData(animGraphInstance)->SetCurrentPlayTime(timeInSeconds); } virtual float GetCurrentPlayTime(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetCurrentPlayTime(); } - MCORE_INLINE size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); } - MCORE_INLINE void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); } + size_t GetSyncIndex(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetSyncIndex(); } + void SetSyncIndex(AnimGraphInstance* animGraphInstance, size_t syncIndex) { FindOrCreateUniqueNodeData(animGraphInstance)->SetSyncIndex(syncIndex); } virtual void SetPlaySpeed(AnimGraphInstance* animGraphInstance, float speedFactor) { FindOrCreateUniqueNodeData(animGraphInstance)->SetPlaySpeed(speedFactor); } virtual float GetPlaySpeed(AnimGraphInstance* animGraphInstance) const { return FindOrCreateUniqueNodeData(animGraphInstance)->GetPlaySpeed(); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNodeData.h b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNodeData.h index 1ad82b1d55..5c2f0b281b 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNodeData.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphNodeData.h @@ -51,52 +51,52 @@ namespace EMotionFX void Init(AnimGraphInstance* animGraphInstance, AnimGraphNode* node); void Init(AnimGraphNodeData* nodeData); - MCORE_INLINE AnimGraphNode* GetNode() const { return reinterpret_cast(m_object); } - MCORE_INLINE void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast(node); } + AnimGraphNode* GetNode() const { return reinterpret_cast(m_object); } + void SetNode(AnimGraphNode* node) { m_object = reinterpret_cast(node); } - MCORE_INLINE void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; } - MCORE_INLINE size_t GetSyncIndex() const { return m_syncIndex; } + void SetSyncIndex(size_t syncIndex) { m_syncIndex = syncIndex; } + size_t GetSyncIndex() const { return m_syncIndex; } - MCORE_INLINE void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; } - MCORE_INLINE float GetCurrentPlayTime() const { return m_currentTime; } + void SetCurrentPlayTime(float absoluteTime) { m_currentTime = absoluteTime; } + float GetCurrentPlayTime() const { return m_currentTime; } - MCORE_INLINE void SetPlaySpeed(float speed) { m_playSpeed = speed; } - MCORE_INLINE float GetPlaySpeed() const { return m_playSpeed; } + void SetPlaySpeed(float speed) { m_playSpeed = speed; } + float GetPlaySpeed() const { return m_playSpeed; } - MCORE_INLINE void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; } - MCORE_INLINE float GetDuration() const { return m_duration; } + void SetDuration(float durationInSeconds) { m_duration = durationInSeconds; } + float GetDuration() const { return m_duration; } - MCORE_INLINE void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; } - MCORE_INLINE float GetPreSyncTime() const { return m_preSyncTime; } + void SetPreSyncTime(float timeInSeconds) { m_preSyncTime = timeInSeconds; } + float GetPreSyncTime() const { return m_preSyncTime; } - MCORE_INLINE void SetGlobalWeight(float weight) { m_globalWeight = weight; } - MCORE_INLINE float GetGlobalWeight() const { return m_globalWeight; } + void SetGlobalWeight(float weight) { m_globalWeight = weight; } + float GetGlobalWeight() const { return m_globalWeight; } - MCORE_INLINE void SetLocalWeight(float weight) { m_localWeight = weight; } - MCORE_INLINE float GetLocalWeight() const { return m_localWeight; } + void SetLocalWeight(float weight) { m_localWeight = weight; } + float GetLocalWeight() const { return m_localWeight; } - MCORE_INLINE uint8 GetInheritFlags() const { return m_inheritFlags; } + uint8 GetInheritFlags() const { return m_inheritFlags; } - MCORE_INLINE bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; } - MCORE_INLINE void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; } - MCORE_INLINE void ClearInheritFlags() { m_inheritFlags = 0; } + bool GetIsBackwardPlaying() const { return (m_inheritFlags & INHERITFLAGS_BACKWARD) != 0; } + void SetBackwardFlag() { m_inheritFlags |= INHERITFLAGS_BACKWARD; } + void ClearInheritFlags() { m_inheritFlags = 0; } - MCORE_INLINE uint8 GetPoseRefCount() const { return m_poseRefCount; } - MCORE_INLINE void IncreasePoseRefCount() { m_poseRefCount++; } - MCORE_INLINE void DecreasePoseRefCount() { m_poseRefCount--; } - MCORE_INLINE void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; } + uint8 GetPoseRefCount() const { return m_poseRefCount; } + void IncreasePoseRefCount() { m_poseRefCount++; } + void DecreasePoseRefCount() { m_poseRefCount--; } + void SetPoseRefCount(uint8 refCount) { m_poseRefCount = refCount; } - MCORE_INLINE uint8 GetRefDataRefCount() const { return m_refDataRefCount; } - MCORE_INLINE void IncreaseRefDataRefCount() { m_refDataRefCount++; } - MCORE_INLINE void DecreaseRefDataRefCount() { m_refDataRefCount--; } - MCORE_INLINE void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; } + uint8 GetRefDataRefCount() const { return m_refDataRefCount; } + void IncreaseRefDataRefCount() { m_refDataRefCount++; } + void DecreaseRefDataRefCount() { m_refDataRefCount--; } + void SetRefDataRefCount(uint8 refCount) { m_refDataRefCount = refCount; } - MCORE_INLINE void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; } - MCORE_INLINE AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; } + void SetRefCountedData(AnimGraphRefCountedData* data) { m_refCountedData = data; } + AnimGraphRefCountedData* GetRefCountedData() const { return m_refCountedData; } - MCORE_INLINE const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; } - MCORE_INLINE AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; } - MCORE_INLINE void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; } + const AnimGraphSyncTrack* GetSyncTrack() const { return m_syncTrack; } + AnimGraphSyncTrack* GetSyncTrack() { return m_syncTrack; } + void SetSyncTrack(AnimGraphSyncTrack* syncTrack) { m_syncTrack = syncTrack; } bool GetIsMirrorMotion() const { return m_isMirrorMotion; } void SetIsMirrorMotion(bool newValue) { m_isMirrorMotion = newValue; } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateTransition.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateTransition.cpp index e90491b696..c6d9b4eeec 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateTransition.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/AnimGraphStateTransition.cpp @@ -326,7 +326,7 @@ namespace EMotionFX uniqueData->m_totalSeconds = 0.0f; uniqueData->m_blendProgress = 0.0f; - m_targetNode->SetSyncIndex(animGraphInstance, MCORE_INVALIDINDEX32); + m_targetNode->SetSyncIndex(animGraphInstance, InvalidIndex); // Trigger action for (AnimGraphTriggerAction* action : m_actionSetup.GetActions()) diff --git a/Gems/ImGui/Code/Include/LYImGuiUtils/HistogramContainer.h b/Gems/ImGui/Code/Include/LYImGuiUtils/HistogramContainer.h index 6129c3ec08..e31d5743c3 100644 --- a/Gems/ImGui/Code/Include/LYImGuiUtils/HistogramContainer.h +++ b/Gems/ImGui/Code/Include/LYImGuiUtils/HistogramContainer.h @@ -85,6 +85,10 @@ namespace ImGui //! Calculate the min and maximum values for the present samples. void CalcMinMaxValues(float& outMin, float& outMax); + //! Set/get color used by either the lines in case ViewType is Lines or bars in case or Histogram. + void SetBarLineColor(const ImColor& color) { m_barLineColor = color; } + ImColor GetBarLineColor() const { return m_barLineColor; } + private: // Set the Max Size and clear the container void SetMaxSize(int size); @@ -99,6 +103,7 @@ namespace ImGui bool m_dispalyOverlays; ScaleMode m_scaleMode; //! Determines if the vertical range of the histogram will be manually specified, auto-expanded or automatically scaled based on the samples. float m_autoScaleSpeed = 0.05f; //! Indicates how fast the min max values and the visible vertical range are adapting to new samples. + ImColor m_barLineColor = ImColor(66, 166, 178); //! Color used by either the lines in case ViewType is Lines or bars in case or Histogram. bool m_collapsed; bool m_drawMostRecentValueText; }; diff --git a/Gems/ImGui/Code/Source/LYImGuiUtils/HistogramContainer.cpp b/Gems/ImGui/Code/Source/LYImGuiUtils/HistogramContainer.cpp index 1915cc3721..51bcc29b99 100644 --- a/Gems/ImGui/Code/Source/LYImGuiUtils/HistogramContainer.cpp +++ b/Gems/ImGui/Code/Source/LYImGuiUtils/HistogramContainer.cpp @@ -147,6 +147,8 @@ namespace ImGui float imGuiHistoWidgetHeight = m_collapsed ? histogramHeight : (histogramHeight - 15); if (GetSize() > 0) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, m_barLineColor.Value); + switch (m_viewType) { default: @@ -160,6 +162,8 @@ namespace ImGui ImGui::PlotLines(AZStd::string::format("##%s_lines", m_histogramName.c_str()).c_str(), ImGui::LYImGuiUtils::s_histogramContainerGetter, this, GetSize(), 0, m_histogramName.c_str(), m_minScale, m_maxScale, ImVec2(histogramWidth - 10, imGuiHistoWidgetHeight)); break; } + + ImGui::PopStyleColor(); } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h index 25fe473135..678ec2d6fd 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/LocalPredictionPlayerInputComponent.h @@ -57,6 +57,14 @@ namespace Multiplayer const AzNetworking::PacketEncodingBuffer& correction ) override; + //! Forcibly enables ProcessInput to execute on the entity. + //! Note that this function is quite dangerous and should normally never be used + void ForceEnableAutonomousUpdate(); + + //! Forcibly disables ProcessInput from executing on the entity. + //! Note that this function is quite dangerous and should normally never be used + void ForceDisableAutonomousUpdate(); + //! Return true if we're currently migrating from one host to another. //! @return boolean true if we're currently migrating from one host to another bool IsMigrating() const; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 265137a078..577e29e34f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -32,7 +32,7 @@ namespace Multiplayer using EntityStopEvent = AZ::Event; using EntityDirtiedEvent = AZ::Event<>; using EntitySyncRewindEvent = AZ::Event<>; - using EntityServerMigrationEvent = AZ::Event; + using EntityServerMigrationEvent = AZ::Event; using EntityPreRenderEvent = AZ::Event; using EntityCorrectionEvent = AZ::Event<>; @@ -113,7 +113,7 @@ namespace Multiplayer void MarkDirty(); void NotifyLocalChanges(); void NotifySyncRewindState(); - void NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId); + void NotifyServerMigration(const HostId& remoteHostId); void NotifyPreRender(float deltaTime); void NotifyCorrection(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h index cffcd3e97d..16b33167f0 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkHierarchyRootComponent.h @@ -30,7 +30,6 @@ namespace Multiplayer { friend class NetworkHierarchyChildComponent; friend class NetworkHierarchyRootComponentController; - friend class ServerToClientReplicationWindow; public: AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h index 19289e1e42..7245dbde9b 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayer.h @@ -45,7 +45,7 @@ namespace Multiplayer using ClientMigrationStartEvent = AZ::Event; using ClientMigrationEndEvent = AZ::Event<>; using ClientDisconnectedEvent = AZ::Event<>; - using NotifyClientMigrationEvent = AZ::Event; + using NotifyClientMigrationEvent = AZ::Event; using NotifyEntityMigrationEvent = AZ::Event; using ConnectionAcquiredEvent = AZ::Event; using ServerAcceptanceReceivedEvent = AZ::Event<>; @@ -136,10 +136,12 @@ namespace Multiplayer virtual void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) = 0; //! Signals a NotifyClientMigrationEvent with the provided parameters. - //! @param hostId the host id of the host the client is migrating to - //! @param userIdentifier the user identifier the client will provide the new host to validate identity - //! @param lastClientInputId the last processed clientInputId by the current host - virtual void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) = 0; + //! @param connectionId the connection id of the client that is migrating + //! @param hostId the host id of the host the client is migrating to + //! @param userIdentifier the user identifier the client will provide the new host to validate identity + //! @param lastClientInputId the last processed clientInputId by the current host + //! @param controlledEntityId the entityId of the clients autonomous entity + virtual void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) = 0; //! Signals a NotifyEntityMigrationEvent with the provided parameters. //! @param entityHandle the network entity handle of the entity being migrated @@ -181,6 +183,18 @@ namespace Multiplayer //! @return pointer to the filtered entity manager, or nullptr if not set virtual IFilterEntityManager* GetFilterEntityManager() = 0; + //! Registers a temp userId to allow a host to look up a players controlled entity in the event of a rejoin or migration event. + //! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts + //! @param controlledEntityId the controlled entityId of the players autonomous entity + virtual void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) = 0; + + //! Completes a client migration event by informing the appropriate client to migrate between hosts. + //! @param temporaryUserIdentifier the temporary user identifier used to identify a player across hosts + //! @param connectionId the connection id of the player being migrated + //! @param publicHostId the public address of the new host the client should connect to + //! @param migratedClientInputId the last clientInputId processed prior to migration + virtual void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) = 0; + //! Enables or disables automatic instantiation of netbound entities. //! This setting is controlled by the networking layer and should not be touched //! If enabled, netbound entities will instantiate as spawnables are loaded into the game world, generally true for the server diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h index 96035083d8..58a0ae63fa 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerTypes.h @@ -29,7 +29,7 @@ namespace Multiplayer using HostId = AzNetworking::IpAddress; static const HostId InvalidHostId = HostId(); - AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint32_t); + AZ_TYPE_SAFE_INTEGRAL(NetEntityId, uint64_t); static constexpr NetEntityId InvalidNetEntityId = static_cast(-1); AZ_TYPE_SAFE_INTEGRAL(NetComponentId, uint16_t); @@ -68,6 +68,7 @@ namespace Multiplayer Server, // A simulated proxy on a server Authority // An authoritative proxy on a server (full authority) }; + const char* GetEnumString(NetEntityRole value); enum class ComponentSerializationType : uint8_t { @@ -113,6 +114,24 @@ namespace Multiplayer bool Serialize(AzNetworking::ISerializer& serializer); }; + inline const char* GetEnumString(NetEntityRole value) + { + switch (value) + { + case NetEntityRole::InvalidRole: + return "InvalidRole"; + case NetEntityRole::Client: + return "Client"; + case NetEntityRole::Autonomous: + return "Autonomous"; + case NetEntityRole::Server: + return "Server"; + case NetEntityRole::Authority: + return "Authority"; + } + return "Unknown"; + } + inline PrefabEntityId::PrefabEntityId(AZ::Name name, uint32_t entityOffset) : m_prefabName(name) , m_entityOffset(entityOffset) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h index 2e1f83ae38..10346ad777 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/EntityReplicationManager.h @@ -57,7 +57,6 @@ namespace Multiplayer EntityReplicationManager(AzNetworking::IConnection& connection, AzNetworking::IConnectionListener& connectionListener, Mode mode); ~EntityReplicationManager() = default; - void SetRemoteHostId(const HostId& hostId); const HostId& GetRemoteHostId() const; void ActivatePendingEntities(); diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h index 90f622a8ae..139db2a949 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h @@ -43,8 +43,7 @@ namespace Multiplayer //! Constructor for an entity delete message. //! @param entityId the networkId of the entity being deleted //! @param isMigrated whether or not the entity is being migrated or deleted - //! @param takeOwnership true if the remote replicator should take ownership of the entity - explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated, bool takeOwnership); + explicit NetworkEntityUpdateMessage(NetEntityId entityId, bool isMigrated); NetworkEntityUpdateMessage& operator =(NetworkEntityUpdateMessage&& rhs); NetworkEntityUpdateMessage& operator =(const NetworkEntityUpdateMessage& rhs); @@ -71,10 +70,6 @@ namespace Multiplayer //! @return whether or not the entity was migrated bool GetWasMigrated() const; - //! Gets the current value of TakeOwnership. - //! @return the current value of TakeOwnership - bool GetTakeOwnership() const; - //! Gets the current value of HasValidPrefabId. //! @return the current value of HasValidPrefabId bool GetHasValidPrefabId() const; @@ -110,7 +105,6 @@ namespace Multiplayer NetEntityId m_entityId = InvalidNetEntityId; bool m_isDelete = false; bool m_wasMigrated = false; - bool m_takeOwnership = false; bool m_hasValidPrefabId = false; PrefabEntityId m_prefabEntityId; diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputArray.h similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputArray.h diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputChild.h similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputChild.h diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputHistory.h similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputHistory.h diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputMigrationVector.h similarity index 100% rename from Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.h rename to Gems/Multiplayer/Code/Include/Multiplayer/NetworkInput/NetworkInputMigrationVector.h diff --git a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml index 194c05577d..31751469de 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/LocalPredictionPlayerInputComponent.AutoComponent.xml @@ -13,9 +13,9 @@ - - - + + + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml index 091043f034..a5bf169fb4 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/Multiplayer.AutoPackets.xml @@ -9,6 +9,7 @@ + diff --git a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml index fbe6ff2135..b789506d0f 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml +++ b/Gems/Multiplayer/Code/Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.xml @@ -8,7 +8,7 @@ OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - + diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 4d0756d9a5..96d6a5e31e 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -354,6 +354,16 @@ namespace Multiplayer } } + void LocalPredictionPlayerInputComponentController::ForceEnableAutonomousUpdate() + { + m_autonomousUpdateEvent.Enqueue(AZ::TimeMs{ 1 }, true); + } + + void LocalPredictionPlayerInputComponentController::ForceDisableAutonomousUpdate() + { + m_autonomousUpdateEvent.RemoveFromQueue(); + } + bool LocalPredictionPlayerInputComponentController::IsMigrating() const { return m_lastMigratedInputId != ClientInputId{ 0 }; diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index fbf83b6e6c..cc71000d33 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -394,9 +394,9 @@ namespace Multiplayer m_syncRewindEvent.Signal(); } - void NetBindComponent::NotifyServerMigration(const HostId& hostId, AzNetworking::ConnectionId connectionId) + void NetBindComponent::NotifyServerMigration(const HostId& remoteHostId) { - m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); + m_entityServerMigrationEvent.Signal(m_netEntityHandle, remoteHostId); } void NetBindComponent::NotifyPreRender(float deltaTime) diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp index a9b8e03126..56eed58af7 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.cpp @@ -23,22 +23,16 @@ namespace Multiplayer ServerToClientConnectionData::ServerToClientConnectionData ( AzNetworking::IConnection* connection, - AzNetworking::IConnectionListener& connectionListener, - NetworkEntityHandle controlledEntity + AzNetworking::IConnectionListener& connectionListener ) : m_connection(connection) , m_controlledEntityRemovedHandler([this](const ConstNetworkEntityHandle&) { OnControlledEntityRemove(); }) - , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId) { OnControlledEntityMigration(entityHandle, remoteHostId, connectionId); }) - , m_controlledEntity(controlledEntity) + , m_controlledEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) + { + OnControlledEntityMigration(entityHandle, remoteHostId); + }) , m_entityReplicationManager(*connection, connectionListener, EntityReplicationManager::Mode::LocalServerToRemoteClient) { - NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); - if (netBindComponent != nullptr) - { - netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler); - netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler); - } - m_entityReplicationManager.SetMaxRemoteEntitiesPendingCreationCount(sv_ClientMaxRemoteEntitiesPendingCreationCount); m_entityReplicationManager.SetEntityPendingRemovalMs(sv_ClientEntityReplicatorPendingRemovalTimeMs); } @@ -54,6 +48,20 @@ namespace Multiplayer m_controlledEntityRemovedHandler.Disconnect(); } + void ServerToClientConnectionData::SetControlledEntity(NetworkEntityHandle primaryPlayerEntity) + { + m_controlledEntityRemovedHandler.Disconnect(); + m_controlledEntityMigrationHandler.Disconnect(); + + m_controlledEntity = primaryPlayerEntity; + NetBindComponent* netBindComponent = m_controlledEntity.GetNetBindComponent(); + if (netBindComponent != nullptr) + { + netBindComponent->AddEntityStopEventHandler(m_controlledEntityRemovedHandler); + netBindComponent->AddEntityServerMigrationEventHandler(m_controlledEntityMigrationHandler); + } + } + ConnectionDataType ServerToClientConnectionData::GetConnectionDataType() const { return ConnectionDataType::ServerToClient; @@ -94,8 +102,7 @@ namespace Multiplayer void ServerToClientConnectionData::OnControlledEntityMigration ( [[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, - [[maybe_unused]] const HostId& remoteHostId, - [[maybe_unused]] AzNetworking::ConnectionId connectionId + const HostId& remoteHostId ) { ClientInputId migratedClientInputId = ClientInputId{ 0 }; @@ -109,14 +116,12 @@ namespace Multiplayer } // Generate crypto-rand user identifier, send to both server and client so they can negotiate the autonomous entity to assume predictive control over after migration - const uint64_t randomUserIdentifier = AzNetworking::CryptoRand64(); + const uint64_t temporaryUserIdentifier = AzNetworking::CryptoRand64(); // Tell the new host that a client is about to (re)join - GetMultiplayer()->SendNotifyClientMigrationEvent(remoteHostId, randomUserIdentifier, migratedClientInputId); - - // Tell the client who to join - MultiplayerPackets::ClientMigration clientMigration(remoteHostId, randomUserIdentifier, migratedClientInputId); - GetConnection()->SendReliablePacket(clientMigration); + GetMultiplayer()->SendNotifyClientMigrationEvent(GetConnection()->GetConnectionId(), remoteHostId, temporaryUserIdentifier, migratedClientInputId, m_controlledEntity.GetNetEntityId()); + // We need to send a MultiplayerPackets::ClientMigration packet to complete this process + // This happens inside MultiplayerSystemComponent, once we're certain the remote host has appropriately prepared m_controlledEntity = NetworkEntityHandle(); m_canSendUpdates = false; diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h index 1b4ee2cc04..4bd30a56a9 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.h @@ -20,11 +20,12 @@ namespace Multiplayer ServerToClientConnectionData ( AzNetworking::IConnection* connection, - AzNetworking::IConnectionListener& connectionListener, - NetworkEntityHandle controlledEntity + AzNetworking::IConnectionListener& connectionListener ); ~ServerToClientConnectionData() override; + void SetControlledEntity(NetworkEntityHandle primaryPlayerEntity); + //! IConnectionData interface //! @{ ConnectionDataType GetConnectionDataType() const override; @@ -44,7 +45,7 @@ namespace Multiplayer private: void OnControlledEntityRemove(); - void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId, AzNetworking::ConnectionId connectionId); + void OnControlledEntityMigration(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId); void OnGameplayStarted(); EntityReplicationManager m_entityReplicationManager; diff --git a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.inl b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.inl index e4348fe539..c166a2a961 100644 --- a/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.inl +++ b/Gems/Multiplayer/Code/Source/ConnectionData/ServerToClientConnectionData.inl @@ -18,7 +18,6 @@ namespace Multiplayer m_canSendUpdates = canSendUpdates; } - inline NetworkEntityHandle ServerToClientConnectionData::GetPrimaryPlayerEntity() { return m_controlledEntity; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp index 7c38a340eb..ad28307204 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugHierarchyReporter.cpp @@ -74,7 +74,7 @@ namespace Multiplayer { ImGui::Text("%s", entity->GetId().ToString().c_str()); ImGui::NextColumn(); - ImGui::Text("%u", GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId())); + ImGui::Text("%llu", static_cast(GetMultiplayer()->GetNetworkEntityManager()->GetNetEntityIdById(entity->GetId()))); ImGui::NextColumn(); ImGui::Text("%s", entity->GetName().c_str()); ImGui::NextColumn(); diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index b23bc677f0..4ff78d3815 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -28,24 +28,29 @@ namespace Multiplayer ->Version(1); } } + void MultiplayerDebugSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); } + void MultiplayerDebugSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) { ; } + void MultiplayerDebugSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile) { incompatbile.push_back(AZ_CRC_CE("MultiplayerDebugSystemComponent")); } + void MultiplayerDebugSystemComponent::Activate() { #ifdef IMGUI_ENABLED ImGui::ImGuiUpdateListenerBus::Handler::BusConnect(); #endif } + void MultiplayerDebugSystemComponent::Deactivate() { #ifdef IMGUI_ENABLED @@ -75,6 +80,7 @@ namespace Multiplayer ImGui::EndMenu(); } } + void AccumulatePerSecondValues(const MultiplayerStats& stats, const MultiplayerStats::Metric& metric, float& outCallsPerSecond, float& outBytesPerSecond) { uint64_t summedCalls = 0; @@ -107,6 +113,7 @@ namespace Multiplayer ImGui::Text("%11.2f", bytesPerSecond); return open; } + bool DrawSummaryRow(const char* name, const MultiplayerStats& stats) { const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateTotalPropertyUpdateSentMetrics(); @@ -123,6 +130,7 @@ namespace Multiplayer AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); } + bool DrawComponentRow(const char* name, const MultiplayerStats& stats, NetComponentId netComponentId) { const MultiplayerStats::Metric propertyUpdatesSent = stats.CalculateComponentPropertyUpdateSentMetrics(netComponentId); @@ -139,6 +147,7 @@ namespace Multiplayer AccumulatePerSecondValues(stats, rpcsRecv, callsPerSecond, bytesPerSecond); return DrawMetricsRow(name, true, totalCalls, totalBytes, callsPerSecond, bytesPerSecond); } + void DrawComponentDetails(const MultiplayerStats& stats, NetComponentId netComponentId) { MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry(); @@ -503,4 +512,3 @@ void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth) AZ::Interface::Get()->HideEntityBandwidthDebugOverlay(); } } - diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 42a7fe43e5..fdd7602f71 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -76,6 +77,7 @@ namespace Multiplayer "The address of the remote server or host to connect to"); AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic"); AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic"); + AZ_CVAR(uint16_t, sv_portRange, 999, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The range of ports the host will incrementally attempt to bind to when initializing"); AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load"); AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); @@ -168,6 +170,7 @@ namespace Multiplayer AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom ) { OnConsoleCommandInvoked(command, args, flags, invokedFrom); }) + , m_autonomousEntityReplicatorCreatedHandler([this]([[maybe_unused]] NetEntityId netEntityId) { OnAutonomousEntityReplicatorCreated(); }) { AZ::Interface::Register(this); } @@ -205,8 +208,23 @@ namespace Multiplayer bool MultiplayerSystemComponent::StartHosting(uint16_t port, bool isDedicated) { - InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer); - return m_networkInterface->Listen(port); + if (port != sv_port) + { + sv_port = port; + } + + const uint16_t maxPort = sv_port + sv_portRange; + while (sv_port <= maxPort) + { + if (m_networkInterface->Listen(sv_port)) + { + InitializeMultiplayer(isDedicated ? MultiplayerAgentType::DedicatedServer : MultiplayerAgentType::ClientServer); + return true; + } + AZLOG_WARN("Failed to start listening on port %u, port is in use?", static_cast(sv_port)); + sv_port = sv_port + 1; + } + return false; } bool MultiplayerSystemComponent::Connect(const AZStd::string& remoteAddress, uint16_t port) @@ -328,6 +346,11 @@ namespace Multiplayer void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { + if (bg_multiplayerDebugDraw) + { + m_networkEntityManager.DebugDraw(); + } + const AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); const AZ::TimeMs serverRateMs = static_cast(sv_serverSendRateMs); const float serverRateSeconds = static_cast(serverRateMs) / 1000.0f; @@ -412,11 +435,6 @@ namespace Multiplayer { m_networkInterface->GetConnectionSet().VisitConnections(visitor); } - - if (bg_multiplayerDebugDraw) - { - m_networkEntityManager.DebugDraw(); - } } int MultiplayerSystemComponent::GetTickOrder() @@ -487,17 +505,39 @@ namespace Multiplayer auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::TerminatedByUser, TerminationEndpoint::Local); }; m_networkInterface->GetConnectionSet().VisitConnections(visitor); return true; - } + } } reinterpret_cast(connection->GetUserData())->SetProviderTicket(packet.GetTicket().c_str()); + // Hosts will spawn a new default player prefab for the user that just connected + if (GetAgentType() == MultiplayerAgentType::ClientServer + || GetAgentType() == MultiplayerAgentType::DedicatedServer) + { + // We use a temporary userId over the clients address so we can maintain client lookups even in the event of wifi handoff + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(packet.GetTemporaryUserId()); + EnableAutonomousControl(controlledEntity, connection->GetConnectionId()); + + ServerToClientConnectionData* connectionData = reinterpret_cast(connection->GetUserData()); + AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); + connectionData->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); + connectionData->SetControlledEntity(controlledEntity); + + // If this is a migrate or rejoin, immediately ready the connection for updates + if (packet.GetTemporaryUserId() != 0) + { + connectionData->SetCanSendUpdates(true); + } + } + if (connection->SendReliablePacket(MultiplayerPackets::Accept(sv_map))) { reinterpret_cast(connection->GetUserData())->SetDidHandshake(true); - - // Sync our console - ConsoleReplicator consoleReplicator(connection); - AZ::Interface::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); }); + if (packet.GetTemporaryUserId() == 0) + { + // Sync our console + ConsoleReplicator consoleReplicator(connection); + AZ::Interface::Get()->VisitRegisteredFunctors([&consoleReplicator](AZ::ConsoleFunctorBase* functor) { consoleReplicator.Visit(functor); }); + } return true; } return false; @@ -511,10 +551,26 @@ namespace Multiplayer ) { reinterpret_cast(connection->GetUserData())->SetDidHandshake(true); - AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(commandString.c_str()); - AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); - AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + if (m_temporaryUserIdentifier == 0) + { + AZ::CVarFixedString commandString = "sv_map " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(commandString.c_str()); + AZ::CVarFixedString loadLevelString = "LoadLevel " + packet.GetMap(); + AZ::Interface::Get()->PerformCommand(loadLevelString.c_str()); + } + else + { + // Bypass map loading and immediately ready the connection for updates + IConnectionData* connectionData = reinterpret_cast(connection->GetUserData()); + if (connectionData) + { + connectionData->SetCanSendUpdates(true); + + // @nt: TODO - delete once dropped RPC problem fixed + // Connection has migrated, we are now waiting for the autonomous entity replicator to be created + connectionData->GetReplicationManager().AddAutonomousEntityReplicatorCreatedHandler(m_autonomousEntityReplicatorCreatedHandler); + } + } m_serverAcceptanceReceivedEvent.Signal(); return true; @@ -637,13 +693,17 @@ namespace Multiplayer // Store the temporary user identifier so we can transmit it with our next Connect packet // The new server will use this to re-attach our set of autonomous entities + m_temporaryUserIdentifier = packet.GetTemporaryUserIdentifier(); // Disconnect our existing server connection auto visitor = [](IConnection& connection) { connection.Disconnect(DisconnectReason::ClientMigrated, TerminationEndpoint::Local); }; m_networkInterface->GetConnectionSet().VisitConnections(visitor); AZLOG_INFO("Migrating to new server shard"); m_clientMigrationStartEvent.Signal(packet.GetLastClientInputId()); - m_networkInterface->Connect(packet.GetRemoteServerAddress()); + if (m_networkInterface->Connect(packet.GetRemoteServerAddress()) == AzNetworking::InvalidConnectionId) + { + AZLOG_ERROR("Failed to connect to new host during client migration event"); + } return true; } @@ -673,7 +733,7 @@ namespace Multiplayer providerTicket = m_pendingConnectionTickets.front(); m_pendingConnectionTickets.pop(); } - connection->SendReliablePacket(MultiplayerPackets::Connect(0, providerTicket.c_str())); + connection->SendReliablePacket(MultiplayerPackets::Connect(0, m_temporaryUserIdentifier, providerTicket.c_str())); } else { @@ -681,29 +741,10 @@ namespace Multiplayer m_connectionAcquiredEvent.Signal(datum); } - // Hosts will spawn a new default player prefab for the user that just connected if (GetAgentType() == MultiplayerAgentType::ClientServer || GetAgentType() == MultiplayerAgentType::DedicatedServer) { - INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab(); - for (auto& netEntity : entityList) - { - if (netEntity.Exists()) - { - netEntity.GetNetBindComponent()->SetOwningConnectionId(connection->GetConnectionId()); - } - netEntity.Activate(); - } - - NetworkEntityHandle controlledEntity; - if (entityList.size() > 0) - { - controlledEntity = entityList[0]; - } - - connection->SetUserData(new ServerToClientConnectionData(connection, *this, controlledEntity)); - AZStd::unique_ptr window = AZStd::make_unique(controlledEntity, connection); - reinterpret_cast(connection->GetUserData())->GetReplicationManager().SetReplicationWindow(AZStd::move(window)); + connection->SetUserData(new ServerToClientConnectionData(connection, *this)); } else { @@ -725,9 +766,9 @@ namespace Multiplayer void MultiplayerSystemComponent::OnDisconnect(AzNetworking::IConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint) { - const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remote host disconnected"; + const char* endpointString = (endpoint == TerminationEndpoint::Local) ? "Disconnecting" : "Remotely disconnected"; AZStd::string reasonString = ToString(reason); - AZLOG_INFO("%s due to %s from remote address: %s", endpointString, reasonString.c_str(), connection->GetRemoteAddress().GetString().c_str()); + AZLOG_INFO("%s from remote address %s due to %s", endpointString, connection->GetRemoteAddress().GetString().c_str(), reasonString.c_str()); // The client is disconnecting if (GetAgentType() == MultiplayerAgentType::Client) @@ -809,16 +850,8 @@ namespace Multiplayer // Spawn the default player for this host since the host is also a player (not a dedicated server) if (m_agentType == MultiplayerAgentType::ClientServer) { - INetworkEntityManager::EntityList entityList = SpawnDefaultPlayerPrefab(); - - for (NetworkEntityHandle controlledEntity : entityList) - { - if (NetBindComponent* controlledEntityNetBindComponent = controlledEntity.GetNetBindComponent()) - { - controlledEntityNetBindComponent->SetAllowAutonomy(true); - } - controlledEntity.Activate(); - } + NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(0); + EnableAutonomousControl(controlledEntity, AzNetworking::InvalidConnectionId); } AZLOG_INFO("Multiplayer operating in %s mode", GetEnumString(m_agentType)); @@ -869,9 +902,9 @@ namespace Multiplayer handler.Connect(m_shutdownEvent); } - void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) + void MultiplayerSystemComponent::SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) { - m_notifyClientMigrationEvent.Signal(hostId, userIdentifier, lastClientInputId); + m_notifyClientMigrationEvent.Signal(connectionId, hostId, userIdentifier, lastClientInputId, controlledEntityId); } void MultiplayerSystemComponent::SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) @@ -925,6 +958,22 @@ namespace Multiplayer return m_filterEntityManager; } + void MultiplayerSystemComponent::RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) + { + m_playerRejoinData[temporaryUserIdentifier] = controlledEntityId; + } + + void MultiplayerSystemComponent::CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) + { + IConnection* connection = m_networkInterface->GetConnectionSet().GetConnection(connectionId); + if (connection != nullptr) // Make sure the player has not disconnected since the start of migration + { + // Tell the client who to join + MultiplayerPackets::ClientMigration clientMigration(publicHostId, temporaryUserIdentifier, migratedClientInputId); + connection->SendReliablePacket(clientMigration); + } + } + void MultiplayerSystemComponent::SetShouldSpawnNetworkEntities(bool value) { m_spawnNetboundEntities = value; @@ -1055,6 +1104,13 @@ namespace Multiplayer m_cvarCommands.PushBackItem(AZStd::move(replicateString)); } + void MultiplayerSystemComponent::OnAutonomousEntityReplicatorCreated() + { + m_autonomousEntityReplicatorCreatedHandler.Disconnect(); + //m_networkEntityManager.GetNetworkEntityAuthorityTracker()->ResetTimeoutTime(AZ::TimeMs{ 2000 }); + m_clientMigrationEndEvent.Signal(); + } + void MultiplayerSystemComponent::ExecuteConsoleCommandList(IConnection* connection, const AZStd::fixed_vector& commands) { AZ::IConsole* console = AZ::Interface::Get(); @@ -1066,19 +1122,69 @@ namespace Multiplayer } } - INetworkEntityManager::EntityList MultiplayerSystemComponent::SpawnDefaultPlayerPrefab() + NetworkEntityHandle MultiplayerSystemComponent::SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier) { + const auto node = m_playerRejoinData.find(temporaryUserIdentifier); + if (node != m_playerRejoinData.end()) + { + return m_networkEntityManager.GetNetworkEntityTracker()->Get(node->second); + } + PrefabEntityId playerPrefabEntityId(AZ::Name(static_cast(sv_defaultPlayerSpawnAsset).c_str())); INetworkEntityManager::EntityList entityList = m_networkEntityManager.CreateEntitiesImmediate(playerPrefabEntityId, NetEntityRole::Authority, AZ::Transform::CreateIdentity(), Multiplayer::AutoActivate::DoNotActivate); - return entityList; + for (NetworkEntityHandle subEntity : entityList) + { + subEntity.Activate(); + } + + NetworkEntityHandle controlledEntity; + if (!entityList.empty()) + { + controlledEntity = entityList[0]; + } + return controlledEntity; + } + + void MultiplayerSystemComponent::EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId) + { + if (!entityHandle.Exists()) + { + AZLOG_WARN("Attempting to enable autonomous control for an invalid entity"); + return; + } + + entityHandle.GetNetBindComponent()->SetOwningConnectionId(connectionId); + if (connectionId == InvalidConnectionId) + { + entityHandle.GetNetBindComponent()->SetAllowAutonomy(true); + } + + auto* hierarchyComponent = entityHandle.FindComponent(); + if (hierarchyComponent != nullptr) + { + for (AZ::Entity* subEntity : hierarchyComponent->GetHierarchicalEntities()) + { + NetworkEntityHandle subEntityHandle = NetworkEntityHandle(subEntity); + NetBindComponent* subEntityNetBindComponent = subEntityHandle.GetNetBindComponent(); + + if (subEntityNetBindComponent != nullptr) + { + subEntityNetBindComponent->SetOwningConnectionId(connectionId); + if (connectionId == InvalidConnectionId) + { + subEntityNetBindComponent->SetAllowAutonomy(true); + } + } + } + } } void host([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { if (!AZ::Interface::Get()->StartHosting(sv_port, sv_isDedicated)) { - AZLOG_ERROR("Failed to start listening on port %u, port is in use?", static_cast(sv_port)); + AZLOG_ERROR("Failed to start listening on any allocated port"); } } AZ_CONSOLEFREEFUNC(host, AZ::ConsoleFunctorFlags::DontReplicate, "Opens a multiplayer connection as a host for other clients to connect to"); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index 9ab78e0f0b..87d084d5bc 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -123,7 +123,7 @@ namespace Multiplayer void AddSessionInitHandler(SessionInitEvent::Handler& handler) override; void AddSessionShutdownHandler(SessionShutdownEvent::Handler& handler) override; void AddServerAcceptanceReceivedHandler(ServerAcceptanceReceivedEvent::Handler& handler) override; - void SendNotifyClientMigrationEvent(const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId) override; + void SendNotifyClientMigrationEvent(AzNetworking::ConnectionId connectionId, const HostId& hostId, uint64_t userIdentifier, ClientInputId lastClientInputId, NetEntityId controlledEntityId) override; void SendNotifyEntityMigrationEvent(const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) override; void SendReadyForEntityUpdates(bool readyForEntityUpdates) override; AZ::TimeMs GetCurrentHostTimeMs() const override; @@ -132,6 +132,8 @@ namespace Multiplayer INetworkEntityManager* GetNetworkEntityManager() override; void SetFilterEntityManager(IFilterEntityManager* entityFilter) override; IFilterEntityManager* GetFilterEntityManager() override; + void RegisterPlayerIdentifierForRejoin(uint64_t temporaryUserIdentifier, NetEntityId controlledEntityId) override; + void CompleteClientMigration(uint64_t temporaryUserIdentifier, AzNetworking::ConnectionId connectionId, const HostId& publicHostId, ClientInputId migratedClientInputId) override; void SetShouldSpawnNetworkEntities(bool value) override; bool GetShouldSpawnNetworkEntities() const override; //! @} @@ -145,9 +147,11 @@ namespace Multiplayer void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); + void OnAutonomousEntityReplicatorCreated(); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); - INetworkEntityManager::EntityList SpawnDefaultPlayerPrefab(); - + NetworkEntityHandle SpawnDefaultPlayerPrefab(uint64_t temporaryUserIdentifier); + void EnableAutonomousControl(NetworkEntityHandle entityHandle, AzNetworking::ConnectionId connectionId); + AZ_CONSOLEFUNC(MultiplayerSystemComponent, DumpStats, AZ::ConsoleFunctorFlags::Null, "Dumps stats for the current multiplayer session"); AzNetworking::INetworkInterface* m_networkInterface = nullptr; @@ -170,12 +174,16 @@ namespace Multiplayer ClientMigrationEndEvent m_clientMigrationEndEvent; NotifyClientMigrationEvent m_notifyClientMigrationEvent; NotifyEntityMigrationEvent m_notifyEntityMigrationEvent; + AZ::Event::Handler m_autonomousEntityReplicatorCreatedHandler; AZStd::queue m_pendingConnectionTickets; + AZStd::unordered_map m_playerRejoinData; AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0); + uint64_t m_temporaryUserIdentifier = 0; // Used in the event of a migration or rejoin + double m_serverSendAccumulator = 0.0; float m_renderBlendFactor = 0.0f; float m_tickFactor = 0.0f; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index 6f65d01e50..fa099842c5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -47,6 +47,9 @@ namespace Multiplayer , m_entityExitDomainEventHandler([this](const ConstNetworkEntityHandle& entityHandle) { OnEntityExitDomain(entityHandle); }) , m_notifyEntityMigrationHandler([this](const ConstNetworkEntityHandle& entityHandle, const HostId& remoteHostId) { OnPostEntityMigration(entityHandle, remoteHostId); }) { + // Set up our remote host identifier, by default we use the IP address of the remote host + m_remoteHostId = connection.GetRemoteAddress(); + // Our max payload size is whatever is passed in, minus room for a udp packetheader m_maxPayloadSize = connection.GetConnectionMtu() - UdpPacketHeaderSerializeSize - ReplicationManagerPacketOverhead; @@ -62,12 +65,10 @@ namespace Multiplayer networkEntityManager->AddEntityExitDomainHandler(m_entityExitDomainEventHandler); } - GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); - } - - void EntityReplicationManager::SetRemoteHostId(const HostId& hostId) - { - m_remoteHostId = hostId; + if (m_updateMode == Mode::LocalServerToRemoteServer) + { + GetMultiplayer()->AddNotifyEntityMigrationEventHandler(m_notifyEntityMigrationHandler); + } } const HostId& EntityReplicationManager::GetRemoteHostId() const @@ -258,8 +259,8 @@ namespace Multiplayer { AZLOG_WARN ( - "Serializing extremely large entity (%u) - MaxPayload: %d NeededSize %d", - aznumeric_cast(replicator->GetEntityHandle().GetNetEntityId()), + "Serializing extremely large entity (%llu) - MaxPayload: %d NeededSize %d", + aznumeric_cast(replicator->GetEntityHandle().GetNetEntityId()), m_maxPayloadSize, nextMessageSize ); @@ -364,15 +365,29 @@ namespace Multiplayer const bool changedRemoteRole = (remoteNetworkRole != entityReplicator->GetRemoteNetworkRole()); // Check if we've changed our bound local role - this can occur when we gain Autonomous or lose Autonomous on a client bool changedLocalRole(false); - if (AZ::Entity* localEnt = entityReplicator->GetEntityHandle().GetEntity()) + NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent(); + if (netBindComponent != nullptr) { - NetBindComponent* netBindComponent = entityReplicator->GetEntityHandle().GetNetBindComponent(); - AZ_Assert(netBindComponent != nullptr, "No NetBindComponent"); changedLocalRole = (netBindComponent->GetNetEntityRole() != entityReplicator->GetBoundLocalNetworkRole()); } if (changedRemoteRole || changedLocalRole) { + const AZ::u64 intEntityId = static_cast(netBindComponent->GetNetEntityId()); + const char* entityName = entityReplicator->GetEntityHandle().GetEntity()->GetName().c_str(); + if (changedLocalRole) + { + const char* oldRoleString = GetEnumString(entityReplicator->GetRemoteNetworkRole()); + const char* newRoleString = GetEnumString(remoteNetworkRole); + AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed local role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString); + } + if (changedRemoteRole) + { + const char* oldRoleString = GetEnumString(entityReplicator->GetBoundLocalNetworkRole()); + const char* newRoleString = GetEnumString(netBindComponent->GetNetEntityRole()); + AZLOG(NET_ReplicatorRoles, "Replicator %s(%llu) changed remote role, old role = %s, new role = %s", entityName, intEntityId, oldRoleString, newRoleString); + } + // If we changed roles, we need to reset everything if (!entityReplicator->IsMarkedForRemoval()) { @@ -387,8 +402,8 @@ namespace Multiplayer AZLOG ( NET_RepDeletes, - "Reinited replicator for %u from remote host %s role %d", - entityHandle.GetNetEntityId(), + "Reinited replicator for netEntityId %llu from remote host %s role %d", + static_cast(entityHandle.GetNetEntityId()), GetRemoteHostId().GetString().c_str(), aznumeric_cast(remoteNetworkRole) ); @@ -404,8 +419,8 @@ namespace Multiplayer AZLOG ( NET_RepDeletes, - "Added replicator for %u from remote host %s role %d", - entityHandle.GetNetEntityId(), + "Added replicator for netEntityId %llu from remote host %s role %d", + static_cast(entityHandle.GetNetEntityId()), GetRemoteHostId().GetString().c_str(), aznumeric_cast(remoteNetworkRole) ); @@ -413,7 +428,7 @@ namespace Multiplayer } else { - AZLOG_ERROR("Failed to add entity replicator, entity does not exist, entity id %u", entityHandle.GetNetEntityId()); + AZLOG_ERROR("Failed to add entity replicator, entity does not exist, netEntityId %llu", static_cast(entityHandle.GetNetEntityId())); AZ_Assert(false, "Failed to add entity replicator, entity does not exist"); } return entityReplicator; @@ -502,24 +517,20 @@ namespace Multiplayer { if (entityReplicator->IsMarkedForRemoval()) { - AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Got a replicator delete message that is a duplicate id %llu remote host %s", static_cast(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str()); } else if (entityReplicator->OwnsReplicatorLifetime()) { // This can occur if we migrate entities quickly - if this is a replicator from C to A, A migrates to B, B then migrates to C, and A's delete replicator has not arrived at C - AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Got a replicator delete message for a replicator we own id %llu remote host %s", static_cast(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str()); } else { shouldDeleteEntity = true; entityReplicator->MarkForRemoval(); - AZLOG(NET_RepDeletes, "Deleting replicater for entity id %u remote host %s", updateMessage.GetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Deleting replicater for entity id %llu remote host %s", static_cast(updateMessage.GetEntityId()), GetRemoteHostId().GetString().c_str()); } } - else - { - shouldDeleteEntity = updateMessage.GetTakeOwnership(); - } // Handle entity cleanup if (shouldDeleteEntity) @@ -529,17 +540,17 @@ namespace Multiplayer { if (updateMessage.GetWasMigrated()) { - AZLOG(NET_RepDeletes, "Leaving id %u using timeout remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Leaving id %llu using timeout remote host %s", static_cast(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str()); } else { - AZLOG(NET_RepDeletes, "Deleting entity id %u remote host %s", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Deleting entity id %llu remote host %s", static_cast(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str()); GetNetworkEntityManager()->MarkForRemoval(entity); } } else { - AZLOG(NET_RepDeletes, "Trying to delete entity id %u remote host %s, but it has been removed", entity.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Trying to delete entity id %llu remote host %s, but it has been removed", static_cast(entity.GetNetEntityId()), GetRemoteHostId().GetString().c_str()); } } @@ -583,9 +594,9 @@ namespace Multiplayer NetBindComponent* netBindComponent = replicatorEntity.GetNetBindComponent(); AZ_Assert(netBindComponent != nullptr, "No NetBindComponent"); - if (createEntity) + if (netBindComponent->GetOwningConnectionId() != invokingConnection->GetConnectionId()) { - // Always set our invoking connectionId for any newly created entities, since this connection now 'owns' them from a rewind perspective + // Always ensure our owning connectionId is correct for correct rewind behaviour netBindComponent->SetOwningConnectionId(invokingConnection->GetConnectionId()); } @@ -595,10 +606,11 @@ namespace Multiplayer AZ_Assert(localNetworkRole != NetEntityRole::Authority, "UpdateMessage trying to set local role to Authority, this should only happen via migration"); AZLOG_INFO ( - "EntityReplicationManager: Changing network role on entity %u, old role %u new role %u", - aznumeric_cast(netEntityId), - aznumeric_cast(netBindComponent->GetNetEntityRole()), - aznumeric_cast(localNetworkRole) + "EntityReplicationManager: Changing network role on entity %s(%llu), old role %s new role %s", + replicatorEntity.GetEntity()->GetName().c_str(), + aznumeric_cast(netEntityId), + GetEnumString(netBindComponent->GetNetEntityRole()), + GetEnumString(localNetworkRole) ); if (NetworkRoleHasController(localNetworkRole)) @@ -708,9 +720,9 @@ namespace Multiplayer AZLOG_WARN ( "Dropping Packet and LocalServerToRemoteClient connection, unexpected packet " - "LocalShard=%s EntityId=%u RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s", + "LocalShard=%s EntityId=%llu RemoteNetworkRole=%u BoundLocalNetworkRole=%u ActualNetworkRole=%u IsMarkedForRemoval=%s", GetNetworkEntityManager()->GetHostId().GetString().c_str(), - aznumeric_cast(entityReplicator->GetEntityHandle().GetNetEntityId()), + aznumeric_cast(entityReplicator->GetEntityHandle().GetNetEntityId()), aznumeric_cast(entityReplicator->GetRemoteNetworkRole()), aznumeric_cast(entityReplicator->GetBoundLocalNetworkRole()), aznumeric_cast(entityReplicator->GetNetBindComponent()->GetNetEntityRole()), @@ -760,13 +772,13 @@ namespace Multiplayer result = UpdateValidationResult::DropMessage; if (updateMessage.GetIsDelete()) { - AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %u, sequence %d latest sequence %d from remote host %s", - updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "EntityReplicationManager: Received old DeleteProxy message for entity id %llu, sequence %d latest sequence %d from remote host %s", + (AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); } else { - AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %u, sequence %d latest sequence %d from remote host %s", - updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepUpdate, "EntityReplicationManager: Received old PropertyChangeMessage message for entity id %llu, sequence %d latest sequence %d from remote host %s", + (AZ::u64)updateMessage.GetEntityId(), (uint32_t)packetId, (uint32_t)propSubscriber->GetLastReceivedPacketId(), GetRemoteHostId().GetString().c_str()); } } } @@ -853,10 +865,10 @@ namespace Multiplayer { AZLOG_INFO ( - "EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %u has already been deleted", + "EntityReplicationManager: Dropping remote RPC message for component %s of rpc index %s, entityId %llu has already been deleted", GetMultiplayerComponentRegistry()->GetComponentName(message.GetComponentId()), GetMultiplayerComponentRegistry()->GetComponentRpcName(message.GetComponentId(), message.GetRpcIndex()), - message.GetEntityId() + static_cast(message.GetEntityId()) ); return false; } @@ -1113,7 +1125,7 @@ namespace Multiplayer if (m_updateMode == EntityReplicationManager::Mode::LocalServerToRemoteServer) { - netBindComponent->NotifyServerMigration(GetRemoteHostId(), GetConnection().GetConnectionId()); + netBindComponent->NotifyServerMigration(GetRemoteHostId()); } bool didSucceed = true; @@ -1145,7 +1157,7 @@ namespace Multiplayer AZ_Assert(didSucceed, "Failed to migrate entity from server"); m_sendMigrateEntityEvent.Signal(m_connection, message); - AZLOG(NET_RepDeletes, "Migration packet sent %u to remote host %s", netEntityId, GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Migration packet sent %llu to remote host %s", static_cast(netEntityId), GetRemoteHostId().GetString().c_str()); // Notify all other EntityReplicationManagers that this entity has migrated so they can adjust their own replicators given our new proxy status GetMultiplayer()->SendNotifyEntityMigrationEvent(entityHandle, GetRemoteHostId()); @@ -1201,7 +1213,7 @@ namespace Multiplayer // Change the role on the replicator AddEntityReplicator(entityHandle, NetEntityRole::Server); - AZLOG(NET_RepDeletes, "Handle Migration %u new authority from remote host %s", entityHandle.GetNetEntityId(), GetRemoteHostId().GetString().c_str()); + AZLOG(NET_RepDeletes, "Handle Migration %llu new authority from remote host %s", static_cast(entityHandle.GetNetEntityId()), GetRemoteHostId().GetString().c_str()); return true; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 05001b5960..67527bf962 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -103,8 +103,8 @@ namespace Multiplayer AZ_Assert ( m_boundLocalNetworkRole != m_remoteNetworkRole, - "Invalid configuration detected, bound local role must differ from remote network role Role: %d", - aznumeric_cast(m_boundLocalNetworkRole) + "Invalid configuration detected, bound local role must differ from remote network role: %s", + GetEnumString(m_boundLocalNetworkRole) ); if (RemoteManagerOwnsEntityLifetime()) @@ -176,7 +176,6 @@ namespace Multiplayer switch (GetBoundLocalNetworkRole()) { case NetEntityRole::Authority: - { if (GetRemoteNetworkRole() == NetEntityRole::Client || GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); @@ -189,10 +188,8 @@ namespace Multiplayer { m_onForwardRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); } - } - break; + break; case NetEntityRole::Server: - { if (GetRemoteNetworkRole() == NetEntityRole::Authority) { m_onSendRpcHandler.Connect(netBindComponent->GetSendServerToAuthorityRpcEvent()); @@ -204,23 +201,21 @@ namespace Multiplayer // Listen for these to forward the rpc along to the other Client replicators m_onSendRpcHandler.Connect(netBindComponent->GetSendAuthorityToClientRpcEvent()); } - // NOTE: e_Autonomous is not connected to e_ServerProxy, it is always connected to an e_Authority - AZ_Assert(GetRemoteNetworkRole() != NetEntityRole::Autonomous, "Unexpected autonomous remote role") - } - break; + else if (GetRemoteNetworkRole() == NetEntityRole::Autonomous) + { + // NOTE: Autonomous is not connected to ServerProxy, it is always connected to an Authority + AZ_Assert(false, "Unexpected autonomous remote role") + } + break; case NetEntityRole::Client: - { // Nothing allowed, no Client to Server communication - } - break; + break; case NetEntityRole::Autonomous: - { if (GetRemoteNetworkRole() == NetEntityRole::Authority) { m_onSendRpcHandler.Connect(netBindComponent->GetSendAutonomousToAuthorityRpcEvent()); } - } - break; + break; default: AZ_Assert(false, "Unexpected network role"); } @@ -252,22 +247,9 @@ namespace Multiplayer if (entity->GetState() != AZ::Entity::State::Init) { - AZLOG_WARN("Trying to activate an entity that is not in the Init state (%u)", GetEntityHandle().GetNetEntityId()); + AZLOG_WARN("Trying to activate an entity that is not in the Init state (%llu)", static_cast(GetEntityHandle().GetNetEntityId())); } - // First we need to make sure the transform component has been updated with the correct value prior to activation - // This is because vanilla az components may only depend on the transform component, not the multiplayer transform component - //if (auto* locationComponent = FindCommonComponent(GetEntityHandle())) - //{ - // AZ::Transform newTransform = locationComponent->GetTransform(); - // auto* transformComponent = entity->FindComponent(); - // if (transformComponent) - // { - // // We can't use EBus here since the TransFormBus does not get connected until the activate call below - // transformComponent->SetWorldTM(newTransform); - // } - //} - // Ugly, but this is the only time we need to call a non-const function on this entity entity->Activate(); m_replicationManager.m_orphanedEntityRpcs.DispatchOrphanedRpcs(*this); @@ -281,8 +263,7 @@ namespace Multiplayer NetBindComponent* netBindComponent = m_netBindComponent; AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent"); - bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); + bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole()); bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client; bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous; if (isAuthority || isClient || isAutonomous) @@ -296,10 +277,10 @@ namespace Multiplayer bool EntityReplicator::OwnsReplicatorLifetime() const { bool ret(false); - if (GetBoundLocalNetworkRole() == NetEntityRole::Authority - || (GetBoundLocalNetworkRole() == NetEntityRole::Server + if (GetBoundLocalNetworkRole() == NetEntityRole::Authority // Authority always owns lifetime + || (GetBoundLocalNetworkRole() == NetEntityRole::Server // Server also owns lifetime if the remote endpoint is a client of some form && (GetRemoteNetworkRole() == NetEntityRole::Client - || GetRemoteNetworkRole() == NetEntityRole::Autonomous))) + || GetRemoteNetworkRole() == NetEntityRole::Autonomous))) { ret = true; } @@ -309,10 +290,9 @@ namespace Multiplayer bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const { bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority); + && (GetRemoteNetworkRole() == NetEntityRole::Authority); bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client) - || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); - + || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous); return isServer || isClient; } @@ -429,10 +409,8 @@ namespace Multiplayer if (const NetworkTransformComponent* networkTransform = entity->FindComponent()) { const NetEntityId parentId = networkTransform->GetParentEntityId(); - /* - * For root entities attached to a level, a network parent won't be set. - * In this case, this entity is the root entity of the hierarchy and it will be activated first. - */ + // For root entities attached to a level, a network parent won't be set. + // In this case, this entity is the root entity of the hierarchy and it will be activated first. if (parentId != InvalidNetEntityId) { ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId); @@ -452,9 +430,9 @@ namespace Multiplayer AZLOG ( NET_HierarchyActivationInfo, - "Hierchical entity %s asking for activation - waiting on the parent %u", + "Hierchical entity %s asking for activation - waiting on the parent %llu", entity->GetName().c_str(), - aznumeric_cast(parentId) + aznumeric_cast(parentId) ); return false; } @@ -472,19 +450,19 @@ namespace Multiplayer AZLOG ( NET_RepDeletes, - "Sending delete replicator id %u migrated %d to remote host %s", - aznumeric_cast(GetEntityHandle().GetNetEntityId()), + "Sending delete replicator id %llu migrated %d to remote host %s", + aznumeric_cast(GetEntityHandle().GetNetEntityId()), WasMigrated() ? 1 : 0, m_replicationManager.GetRemoteHostId().GetString().c_str() ); - return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated(), m_propertyPublisher->IsRemoteReplicatorEstablished()); + return NetworkEntityUpdateMessage(GetEntityHandle().GetNetEntityId(), WasMigrated()); } NetBindComponent* netBindComponent = GetNetBindComponent(); - //const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); + const bool sendSliceName = !m_propertyPublisher->IsRemoteReplicatorEstablished(); NetworkEntityUpdateMessage updateMessage(GetRemoteNetworkRole(), GetEntityHandle().GetNetEntityId()); - //if (sendSliceName) + if (sendSliceName) { updateMessage.SetPrefabEntityId(netBindComponent->GetPrefabEntityId()); } @@ -553,42 +531,33 @@ namespace Multiplayer switch (entityRpcMessage.GetRpcDeliveryType()) { case RpcDeliveryType::AuthorityToClient: - { if (((GetBoundLocalNetworkRole() == NetEntityRole::Client) || (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous)) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are a local client, and we are connected to server, aka AuthorityToClient result = RpcValidationResult::HandleRpc; } - if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) - && (GetRemoteNetworkRole() == NetEntityRole::Authority)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Server) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are on a server, and we received this message from another server, therefore we should forward this to any connected clients result = RpcValidationResult::ForwardToClient; } - } - break; + break; case RpcDeliveryType::AuthorityToAutonomous: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) - && (GetRemoteNetworkRole() == NetEntityRole::Authority)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Autonomous) && (GetRemoteNetworkRole() == NetEntityRole::Authority)) { // We are an autonomous client, and we are connected to server, aka AuthorityToAutonomous result = RpcValidationResult::HandleRpc; } - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Server)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server)) { // We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player // This can occur if we've recently migrated result = RpcValidationResult::ForwardToAutonomous; } - } - break; + break; case RpcDeliveryType::AutonomousToAuthority: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Autonomous)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Autonomous)) { if (IsMarkedForRemoval()) { @@ -610,12 +579,9 @@ namespace Multiplayer result = RpcValidationResult::HandleRpc; } } - } - break; + break; case RpcDeliveryType::ServerToAuthority: - { - if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) - && (GetRemoteNetworkRole() == NetEntityRole::Server)) + if ((GetBoundLocalNetworkRole() == NetEntityRole::Authority) && (GetRemoteNetworkRole() == NetEntityRole::Server)) { // if we're marked for removal, then we should forward to whomever now owns this entity if (IsMarkedForRemoval()) @@ -638,9 +604,9 @@ namespace Multiplayer result = RpcValidationResult::HandleRpc; } } + break; } - break; - } + if (result == RpcValidationResult::DropRpcAndDisconnect) { bool isLocalServer = (GetBoundLocalNetworkRole() == NetEntityRole::Authority) || (GetBoundLocalNetworkRole() == NetEntityRole::Server); @@ -654,30 +620,29 @@ namespace Multiplayer { AZLOG_ERROR ( - "Dropping RPC and Connection EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", - aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + "Dropping RPC and Connection EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", + aznumeric_cast(m_entityHandle.GetNetEntityId()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); } } + if (result == RpcValidationResult::DropRpc) { AZLOG ( NET_Rpc, - "Dropping RPC EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", - aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + "Dropping RPC EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", + aznumeric_cast(m_entityHandle.GetNetEntityId()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); @@ -696,13 +661,12 @@ namespace Multiplayer { AZLOG_WARN ( - "Dropping RPC since entity deleted EntityId=%u LocalRole=%u RemoteRole=%u RpcDeliveryType=%u ComponentId=%u RpcType=%u IsReliable=%s IsMarkedForRemoval=%s", - aznumeric_cast(m_entityHandle.GetNetEntityId()), - aznumeric_cast(GetBoundLocalNetworkRole()), - aznumeric_cast(GetRemoteNetworkRole()), + "Dropping RPC since entity deleted EntityId=%llu LocalRole=%s RemoteRole=%s RpcDeliveryType=%u RpcName=%s IsReliable=%s IsMarkedForRemoval=%s", + aznumeric_cast(m_entityHandle.GetNetEntityId()), + GetEnumString(GetBoundLocalNetworkRole()), + GetEnumString(GetRemoteNetworkRole()), aznumeric_cast(entityRpcMessage.GetRpcDeliveryType()), - aznumeric_cast(entityRpcMessage.GetComponentId()), - aznumeric_cast(entityRpcMessage.GetRpcIndex()), + GetMultiplayerComponentRegistry()->GetComponentRpcName(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex()), entityRpcMessage.GetReliability() == ReliabilityType::Reliable ? "true" : "false", IsMarkedForRemoval() ? "true" : "false" ); @@ -740,23 +704,23 @@ namespace Multiplayer case RpcValidationResult::DropRpcAndDisconnect: return false; case RpcValidationResult::ForwardToClient: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendAuthorityToClientRpcEvent().Signal(entityRpcMessage); + } return true; - } case RpcValidationResult::ForwardToAutonomous: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendAuthorityToAutonomousRpcEvent().Signal(entityRpcMessage); + } return true; - } case RpcValidationResult::ForwardToAuthority: - { - ScopedForwardingMessage forwarding(*this); - m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage); + { + ScopedForwardingMessage forwarding(*this); + m_netBindComponent->GetSendServerToAuthorityRpcEvent().Signal(entityRpcMessage); + } return true; - } default: break; } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp index 0a99f4b0d4..b3f87ea9ab 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityAuthorityTracker.cpp @@ -33,8 +33,8 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Removing timeout for networkEntityId %u from %s, new owner is %s", - aznumeric_cast(entityHandle.GetNetEntityId()), + "AuthTracker: Removing timeout for networkEntityId %llu from %s, new owner is %s", + aznumeric_cast(entityHandle.GetNetEntityId()), timeoutData->second.m_previousOwner.GetString().c_str(), newOwner.GetString().c_str() ); @@ -48,8 +48,8 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %u from %s to %s", - aznumeric_cast(entityHandle.GetNetEntityId()), + "AuthTracker: Assigning networkEntityId %llu from %s to %s", + aznumeric_cast(entityHandle.GetNetEntityId()), iter->second.back().GetString().c_str(), newOwner.GetString().c_str() ); @@ -59,8 +59,8 @@ namespace Multiplayer AZLOG ( NET_AuthTracker, - "AuthTracker: Assigning networkEntityId %u to %s", - aznumeric_cast(entityHandle.GetNetEntityId()), + "AuthTracker: Assigning networkEntityId %llu to %s", + aznumeric_cast(entityHandle.GetNetEntityId()), newOwner.GetString().c_str() ); } @@ -87,7 +87,7 @@ namespace Multiplayer } } - AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %u from %s", aznumeric_cast(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str()); + AZLOG(NET_AuthTracker, "AuthTracker: Removing networkEntityId %llu from %s", aznumeric_cast(entityHandle.GetNetEntityId()), previousOwner.GetString().c_str()); if (auto localEnt = entityHandle.GetEntity()) { if (authorityStack.empty()) @@ -114,14 +114,14 @@ namespace Multiplayer } else { - AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %u", aznumeric_cast(entityHandle.GetNetEntityId())); + AZLOG(NET_AuthTracker, "AuthTracker: Skipping timeout for Autonomous networkEntityId %llu", aznumeric_cast(entityHandle.GetNetEntityId())); } } } } else { - AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %u", aznumeric_cast(entityHandle.GetNetEntityId())); + AZLOG(NET_AuthTracker, "AuthTracker: Remove authority called on networkEntityId that was never added %llu", aznumeric_cast(entityHandle.GetNetEntityId())); AZ_Assert(false, "AuthTracker: Remove authority called on entity that was never added"); } } @@ -205,8 +205,8 @@ namespace Multiplayer { AZLOG_ERROR ( - "Timed out entity id %u during migration previous owner %s, removing it", - aznumeric_cast(entityHandle.GetNetEntityId()), + "Timed out entity id %llu during migration previous owner %s, removing it", + aznumeric_cast(entityHandle.GetNetEntityId()), timeoutData->second.m_previousOwner.GetString().c_str() ); m_networkEntityManager.MarkForRemoval(entityHandle); diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp index 7794d9026b..457395a61e 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityHandle.cpp @@ -18,21 +18,13 @@ namespace Multiplayer { ConstNetworkEntityHandle::ConstNetworkEntityHandle(AZ::Entity* entity, const NetworkEntityTracker* networkEntityTracker) : m_entity(entity) - , m_networkEntityTracker(networkEntityTracker) + , m_networkEntityTracker((networkEntityTracker != nullptr) ? networkEntityTracker : GetNetworkEntityTracker()) { - if (m_networkEntityTracker == nullptr) - { - m_networkEntityTracker = GetNetworkEntityTracker(); - } - - if (m_networkEntityTracker) - { - m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); - } + AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid"); + m_changeDirty = m_networkEntityTracker->GetChangeDirty(m_entity); if (entity) { - AZ_Assert(m_networkEntityTracker, "NetworkEntityTracker is not valid"); m_netBindComponent = m_networkEntityTracker->GetNetBindComponent(entity); if (m_netBindComponent != nullptr) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp index b72ea21719..c7582af83f 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityManager.cpp @@ -48,6 +48,20 @@ namespace Multiplayer void NetworkEntityManager::Initialize(const HostId& hostId, AZStd::unique_ptr entityDomain) { m_hostId = hostId; + + // Configure our vended NetEntityIds so that no two hosts generate the same NetEntityId + { + // Needs more thought + const uint64_t addrPortion = hostId.GetAddress(AzNetworking::ByteOrder::Host); + const uint64_t portPortion = hostId.GetPort(AzNetworking::ByteOrder::Host); + const uint64_t hostIdentifier = (portPortion << 32) | addrPortion; + const AZ::HashValue32 hostHash = AZ::TypeHash32(hostIdentifier); + + NetEntityId hostEntityIdOffset = static_cast(hostHash) << 32; + m_nextEntityId &= NetEntityId{ 0x0000000000000000FFFFFFFFFFFFFFFF }; + m_nextEntityId |= hostEntityIdOffset; + } + m_entityDomain = AZStd::move(entityDomain); m_updateEntityDomainEvent.Enqueue(net_EntityDomainUpdateMs, true); m_entityDomain->ActivateTracking(m_ownedEntities); @@ -227,11 +241,19 @@ namespace Multiplayer { AZ::Entity* entity = it->second; NetBindComponent* netBindComponent = m_networkEntityTracker.GetNetBindComponent(entity); + AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); + entityBounds.Expand(AZ::Vector3(0.01f)); if (netBindComponent->GetNetEntityRole() == NetEntityRole::Authority) { - const AZ::Aabb entityBounds = AZ::Interface::Get()->GetEntityWorldBoundsUnion(entity->GetId()); - debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); + debugDisplay->SetColor(AZ::Colors::Black); + debugDisplay->SetAlpha(0.5f); } + else + { + debugDisplay->SetColor(AZ::Colors::DeepSkyBlue); + debugDisplay->SetAlpha(0.25f); + } + debugDisplay->DrawWireBox(entityBounds.GetMin(), entityBounds.GetMax()); } if (m_entityDomain != nullptr) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp index 4a2f12ce17..d635dbaf80 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/NetworkEntityUpdateMessage.cpp @@ -18,7 +18,6 @@ namespace Multiplayer , m_entityId(rhs.m_entityId) , m_isDelete(rhs.m_isDelete) , m_wasMigrated(rhs.m_wasMigrated) - , m_takeOwnership(rhs.m_takeOwnership) , m_hasValidPrefabId(rhs.m_hasValidPrefabId) , m_prefabEntityId(rhs.m_prefabEntityId) , m_data(AZStd::move(rhs.m_data)) @@ -31,7 +30,6 @@ namespace Multiplayer , m_entityId(rhs.m_entityId) , m_isDelete(rhs.m_isDelete) , m_wasMigrated(rhs.m_wasMigrated) - , m_takeOwnership(rhs.m_takeOwnership) , m_hasValidPrefabId(rhs.m_hasValidPrefabId) , m_prefabEntityId(rhs.m_prefabEntityId) { @@ -58,11 +56,10 @@ namespace Multiplayer ; } - NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated, bool takeOwnership) + NetworkEntityUpdateMessage::NetworkEntityUpdateMessage(NetEntityId entityId, bool wasMigrated) : m_entityId(entityId) , m_isDelete(true) , m_wasMigrated(wasMigrated) - , m_takeOwnership(takeOwnership) { // this is a delete entity message c-tor } @@ -73,7 +70,6 @@ namespace Multiplayer m_entityId = rhs.m_entityId; m_isDelete = rhs.m_isDelete; m_wasMigrated = rhs.m_wasMigrated; - m_takeOwnership = rhs.m_takeOwnership; m_hasValidPrefabId = rhs.m_hasValidPrefabId; m_prefabEntityId = rhs.m_prefabEntityId; m_data = AZStd::move(rhs.m_data); @@ -86,7 +82,6 @@ namespace Multiplayer m_entityId = rhs.m_entityId; m_isDelete = rhs.m_isDelete; m_wasMigrated = rhs.m_wasMigrated; - m_takeOwnership = rhs.m_takeOwnership; m_hasValidPrefabId = rhs.m_hasValidPrefabId; m_prefabEntityId = rhs.m_prefabEntityId; if (rhs.m_data != nullptr) @@ -104,7 +99,6 @@ namespace Multiplayer && (m_entityId == rhs.m_entityId) && (m_isDelete == rhs.m_isDelete) && (m_wasMigrated == rhs.m_wasMigrated) - && (m_takeOwnership == rhs.m_takeOwnership) && (m_hasValidPrefabId == rhs.m_hasValidPrefabId) && (m_prefabEntityId == rhs.m_prefabEntityId)); } @@ -160,11 +154,6 @@ namespace Multiplayer return m_wasMigrated; } - bool NetworkEntityUpdateMessage::GetTakeOwnership() const - { - return m_takeOwnership; - } - bool NetworkEntityUpdateMessage::GetHasValidPrefabId() const { return m_hasValidPrefabId; @@ -210,17 +199,15 @@ namespace Multiplayer serializer.Serialize(m_entityId, "EntityId"); // Use the upper 4 bits for boolean flags, and the lower 4 bits for the network role - uint8_t networkTypeAndFlags = (m_isDelete ? 0x80 : 0x00) - | (m_wasMigrated ? 0x40 : 0x00) - | (m_takeOwnership ? 0x20 : 0x00) + uint8_t networkTypeAndFlags = (m_isDelete ? 0x40 : 0x00) + | (m_wasMigrated ? 0x20 : 0x00) | (m_hasValidPrefabId ? 0x10 : 0x00) | static_cast(m_networkRole); if (serializer.Serialize(networkTypeAndFlags, "TypeAndFlags")) { - m_isDelete = (networkTypeAndFlags & 0x80) == 0x80; - m_wasMigrated = (networkTypeAndFlags & 0x40) == 0x40; - m_takeOwnership = (networkTypeAndFlags & 0x20) == 0x20; + m_isDelete = (networkTypeAndFlags & 0x40) == 0x40; + m_wasMigrated = (networkTypeAndFlags & 0x20) == 0x20; m_hasValidPrefabId = (networkTypeAndFlags & 0x10) == 0x10; m_networkRole = static_cast(networkTypeAndFlags & 0x0F); } diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp index 638dc9a900..9d566537c5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputArray.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp index bea110f298..59fb62e1b5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputChild.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.cpp index 54813327ab..00fd9f13b8 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputHistory.cpp @@ -6,7 +6,7 @@ * */ -#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.cpp b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.cpp index d95c46261f..ec57891725 100644 --- a/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkInput/NetworkInputMigrationVector.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp index 856fd59f9d..deff1f640f 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.cpp @@ -92,17 +92,10 @@ namespace Multiplayer return m_isPoorConnection ? sv_MinEntitiesToReplicate : sv_MaxEntitiesToReplicate; } - bool ServerToClientReplicationWindow::IsInWindow(const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const + bool ServerToClientReplicationWindow::IsInWindow([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, NetEntityRole& outNetworkRole) const { - // TODO: Clean up this interface, this function is used for server->server migrations, and probably shouldn't be exposed in it's current setup AZ_Assert(false, "IsInWindow should not be called on the ServerToClientReplicationWindow"); outNetworkRole = NetEntityRole::InvalidRole; - auto iter = m_replicationSet.find(entityHandle); - if (iter != m_replicationSet.end()) - { - outNetworkRole = iter->second.m_netEntityRole; - return true; - } return false; } @@ -146,7 +139,7 @@ namespace Multiplayer NetworkEntityTracker* networkEntityTracker = GetNetworkEntityTracker(); IFilterEntityManager* filterEntityManager = GetMultiplayer()->GetFilterEntityManager(); - // Add all the neighbors + // Add all the neighbours for (AzFramework::VisibilityEntry* visEntry : gatheredEntries) { AZ::Entity* entity = static_cast(visEntry->m_userData); @@ -301,7 +294,6 @@ namespace Multiplayer void ServerToClientReplicationWindow::AddEntityToReplicationSet(ConstNetworkEntityHandle& entityHandle, float priority, [[maybe_unused]] float distanceSquared) { // Assumption: the entity has been checked for filtering prior to this call. - if (!sv_ReplicateServerProxies) { NetBindComponent* netBindComponent = entityHandle.GetNetBindComponent(); @@ -312,11 +304,11 @@ namespace Multiplayer } } - const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set + const bool isQueueFull = (m_candidateQueue.size() >= sv_MaxEntitiesToTrackReplication); // See if have the maximum number of entities in our set const bool isInReplicationSet = m_replicationSet.find(entityHandle) != m_replicationSet.end(); if (!isInReplicationSet) { - if (isQueueFull) // if our set is full, then we need to remove the worst priority in our set + if (isQueueFull) // If our set is full, then we need to remove the worst priority in our set { ConstNetworkEntityHandle removeEnt = m_candidateQueue.top().m_entityHandle; m_candidateQueue.pop(); @@ -332,7 +324,7 @@ namespace Multiplayer INetworkEntityManager* networkEntityManager = AZ::Interface::Get(); AZ_Assert(networkEntityManager, "NetworkEntityManager must be created."); - for (const AZ::Entity* controlledEntity : hierarchyComponent.m_hierarchicalEntities) + for (const AZ::Entity* controlledEntity : hierarchyComponent.GetHierarchicalEntities()) { NetEntityId controlledNetEntitydId = networkEntityManager->GetNetEntityIdById(controlledEntity->GetId()); AZ_Assert(controlledNetEntitydId != InvalidNetEntityId, "Unable to find the hierarchy entity in Network Entity Manager"); diff --git a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h index 8816209d7b..3b4fddfe99 100644 --- a/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h +++ b/Gems/Multiplayer/Code/Source/ReplicationWindows/ServerToClientReplicationWindow.h @@ -76,8 +76,6 @@ namespace Multiplayer AZ::EntityActivatedEvent::Handler m_entityActivatedEventHandler; AZ::EntityDeactivatedEvent::Handler m_entityDeactivatedEventHandler; - //NetBindComponent* m_controlledNetBindComponent = nullptr; - AzNetworking::IConnection* m_connection = nullptr; // Cached values to detect a poor network connection diff --git a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp index badee73e04..12a49a9ffc 100644 --- a/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp +++ b/Gems/Multiplayer/Code/Tests/ClientHierarchyTests.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace Multiplayer @@ -213,9 +213,8 @@ namespace Multiplayer constexpr uint32_t bufferSize = 100; AZStd::array buffer = {}; NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); - inSerializer.Serialize(reinterpret_cast(value), - "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ - AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + ISerializer& serializer = inSerializer; + serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); diff --git a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h index 5a528ed497..3c3d77e011 100644 --- a/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonBenchmarkSetup.h @@ -342,8 +342,11 @@ namespace Multiplayer void AddClientMigrationEndEventHandler([[maybe_unused]] ClientMigrationEndEvent::Handler& handler) override {} void AddNotifyClientMigrationHandler([[maybe_unused]] NotifyClientMigrationEvent::Handler& handler) override {} void AddNotifyEntityMigrationEventHandler([[maybe_unused]] NotifyEntityMigrationEvent::Handler& handler) override {} - void SendNotifyClientMigrationEvent([[maybe_unused]] const HostId& hostId, [[maybe_unused]] uint64_t userIdentifier, [[maybe_unused]] ClientInputId lastClientInputId) override {} + void SendNotifyClientMigrationEvent([[maybe_unused]] AzNetworking::ConnectionId connectionId, [[maybe_unused]] const HostId& hostId, + [[maybe_unused]] uint64_t userIdentifier, [[maybe_unused]] ClientInputId lastClientInputId, [[maybe_unused]] NetEntityId netEntityId) override {} void SendNotifyEntityMigrationEvent([[maybe_unused]] const ConstNetworkEntityHandle& entityHandle, [[maybe_unused]] const HostId& remoteHostId) override {} + void RegisterPlayerIdentifierForRejoin(uint64_t, NetEntityId) override {} + void CompleteClientMigration(uint64_t, AzNetworking::ConnectionId, const HostId&, ClientInputId) override {} void SetShouldSpawnNetworkEntities([[maybe_unused]] bool value) override {} bool GetShouldSpawnNetworkEntities() const override { return true; } @@ -535,9 +538,8 @@ namespace Multiplayer constexpr uint32_t bufferSize = 100; AZStd::array buffer = {}; NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); - inSerializer.Serialize(reinterpret_cast(netParentId), - "parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */ - AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + ISerializer& serializer = inSerializer; + serializer.Serialize(netParentId, "parentEntityId"); // Derived from NetworkTransformComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); @@ -560,9 +562,8 @@ namespace Multiplayer constexpr uint32_t bufferSize = 100; AZStd::array buffer = {}; NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); - inSerializer.Serialize(reinterpret_cast(value), - "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ - AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + ISerializer& serializer = inSerializer; + serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); diff --git a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h index 38027c11a9..249837b484 100644 --- a/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h +++ b/Gems/Multiplayer/Code/Tests/CommonHierarchySetup.h @@ -317,9 +317,8 @@ namespace Multiplayer constexpr uint32_t bufferSize = 100; AZStd::array buffer = {}; NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); - inSerializer.Serialize(reinterpret_cast(netParentId), - "parentEntityId", /* Derived from NetworkTransformComponent.AutoComponent.xml */ - AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + ISerializer& serializer = inSerializer; + serializer.Serialize(netParentId, "parentEntityId"); // Derived from NetworkTransformComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); @@ -365,9 +364,8 @@ namespace Multiplayer constexpr uint32_t bufferSize = 100; AZStd::array buffer = {}; NetworkInputSerializer inSerializer(buffer.begin(), bufferSize); - inSerializer.Serialize(reinterpret_cast(value), - "hierarchyRoot", /* Derived from NetworkHierarchyChildComponent.AutoComponent.xml */ - AZStd::numeric_limits::min(), AZStd::numeric_limits::max()); + ISerializer& serializer = inSerializer; + serializer.Serialize(value, "hierarchyRoot"); // Derived from NetworkHierarchyChildComponent.AutoComponent.xml NetworkOutputSerializer outSerializer(buffer.begin(), bufferSize); diff --git a/Gems/Multiplayer/Code/Tests/MockInterfaces.h b/Gems/Multiplayer/Code/Tests/MockInterfaces.h index 527aeb51bc..8cebf280b9 100644 --- a/Gems/Multiplayer/Code/Tests/MockInterfaces.h +++ b/Gems/Multiplayer/Code/Tests/MockInterfaces.h @@ -33,7 +33,7 @@ namespace UnitTest MOCK_METHOD1(AddServerAcceptanceReceivedHandler, void(Multiplayer::ServerAcceptanceReceivedEvent::Handler&)); MOCK_METHOD1(AddSessionInitHandler, void(Multiplayer::SessionInitEvent::Handler&)); MOCK_METHOD1(AddSessionShutdownHandler, void(Multiplayer::SessionShutdownEvent::Handler&)); - MOCK_METHOD3(SendNotifyClientMigrationEvent, void(const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId)); + MOCK_METHOD5(SendNotifyClientMigrationEvent, void(AzNetworking::ConnectionId, const Multiplayer::HostId&, uint64_t, Multiplayer::ClientInputId, Multiplayer::NetEntityId)); MOCK_METHOD2(SendNotifyEntityMigrationEvent, void(const Multiplayer::ConstNetworkEntityHandle&, const Multiplayer::HostId&)); MOCK_METHOD1(SendReadyForEntityUpdates, void(bool)); MOCK_CONST_METHOD0(GetCurrentHostTimeMs, AZ::TimeMs()); @@ -42,6 +42,8 @@ namespace UnitTest MOCK_METHOD0(GetNetworkEntityManager, Multiplayer::INetworkEntityManager* ()); MOCK_METHOD1(SetFilterEntityManager, void(Multiplayer::IFilterEntityManager*)); MOCK_METHOD0(GetFilterEntityManager, Multiplayer::IFilterEntityManager* ()); + MOCK_METHOD2(RegisterPlayerIdentifierForRejoin, void(uint64_t, Multiplayer::NetEntityId)); + MOCK_METHOD4(CompleteClientMigration, void(uint64_t, AzNetworking::ConnectionId, const Multiplayer::HostId&, Multiplayer::ClientInputId)); MOCK_METHOD1(SetShouldSpawnNetworkEntities, void(bool)); MOCK_CONST_METHOD0(GetShouldSpawnNetworkEntities, bool()); }; diff --git a/Gems/Multiplayer/Code/Tests/NetworkInputTests.cpp b/Gems/Multiplayer/Code/Tests/NetworkInputTests.cpp index 64381bf195..0df0fce610 100644 --- a/Gems/Multiplayer/Code/Tests/NetworkInputTests.cpp +++ b/Gems/Multiplayer/Code/Tests/NetworkInputTests.cpp @@ -17,9 +17,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace Multiplayer { diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index f16483e663..a799278203 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -45,6 +45,10 @@ set(FILES Include/Multiplayer/NetworkEntity/NetworkEntityUpdateMessage.h Include/Multiplayer/NetworkInput/IMultiplayerComponentInput.h Include/Multiplayer/NetworkInput/NetworkInput.h + Include/Multiplayer/NetworkInput/NetworkInputArray.h + Include/Multiplayer/NetworkInput/NetworkInputChild.h + Include/Multiplayer/NetworkInput/NetworkInputHistory.h + Include/Multiplayer/NetworkInput/NetworkInputMigrationVector.h Include/Multiplayer/NetworkTime/INetworkTime.h Include/Multiplayer/NetworkTime/RewindableArray.h Include/Multiplayer/NetworkTime/RewindableArray.inl @@ -114,13 +118,9 @@ set(FILES Source/NetworkEntity/NetworkSpawnableLibrary.h Source/NetworkInput/NetworkInput.cpp Source/NetworkInput/NetworkInputArray.cpp - Source/NetworkInput/NetworkInputArray.h Source/NetworkInput/NetworkInputChild.cpp - Source/NetworkInput/NetworkInputChild.h Source/NetworkInput/NetworkInputHistory.cpp - Source/NetworkInput/NetworkInputHistory.h Source/NetworkInput/NetworkInputMigrationVector.cpp - Source/NetworkInput/NetworkInputMigrationVector.h Source/NetworkTime/NetworkTime.cpp Source/NetworkTime/NetworkTime.h Source/Pipeline/NetworkSpawnableHolderComponent.cpp diff --git a/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp b/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp index 38cfee2c26..f7a3d4d69d 100644 --- a/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp +++ b/Gems/Profiler/Code/Source/ProfilerImGuiModule.cpp @@ -46,4 +46,4 @@ namespace Profiler }; }// namespace Profiler -AZ_DECLARE_MODULE_CLASS(Gem_Profiler, Profiler::ProfilerImGuiModule) +AZ_DECLARE_MODULE_CLASS(Gem_ProfilerImGui, Profiler::ProfilerImGuiModule) diff --git a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py index 6b9fbc8f08..758cc539d7 100755 --- a/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py +++ b/Gems/QtForPython/Editor/Scripts/az_qt_helpers.py @@ -18,6 +18,14 @@ from shiboken2 import wrapInstance, getCppPointer view_pane_handlers = {} registration_handlers = {} +# Helper method for retrieving the Editor QMainWindow instance +def get_editor_main_window(): + params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters") + editor_id = QtWidgets.QWidget.find(params.mainWindowId) + editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) + + return editor_main_window + # Helper method for registering a Python widget as a tool/view pane with the Editor def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): global view_pane_handlers @@ -29,9 +37,7 @@ def register_view_pane(name, widget_type, options=editor.ViewPaneOptions()): # This method will be invoked by the ViewPaneCallbackBus::CreateViewPaneWidget # when our view pane needs to be created def on_create_view_pane_widget(parameters): - params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters") - editor_id = QtWidgets.QWidget.find(params.mainWindowId) - editor_main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) + editor_main_window = get_editor_main_window() dock_main_window = editor_main_window.findChild(QtWidgets.QMainWindow) # Create the view pane widget parented to the Editor QMainWindow, so it can be found diff --git a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype index ec04412fe6..01b862c4f8 100644 --- a/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype +++ b/Gems/Terrain/Assets/Materials/Terrain/PbrTerrain.materialtype @@ -1,7 +1,7 @@ { "description": "A material for rendering terrain with a physically-based rendering (PBR) material shading model.", + "version": 1, "propertyLayout": { - "version": 1, "groups": [ { "id": "settings", diff --git a/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype b/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype index 3cdab8da10..17769ffb92 100644 --- a/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype +++ b/Gems/Terrain/Assets/Materials/Terrain/TerrainMacroMaterial.materialtype @@ -1,7 +1,7 @@ { "description": "A material for providing terrain with low-fidelity color and normals. This material will get blended with surface detail materials.", + "version": 1, "propertyLayout": { - "version": 1, "groups": [ { "name": "baseColor", diff --git a/Tools/LyTestTools/setup.py b/Tools/LyTestTools/setup.py index e23f914dab..a523cbc2dd 100755 --- a/Tools/LyTestTools/setup.py +++ b/Tools/LyTestTools/setup.py @@ -48,7 +48,8 @@ if __name__ == '__main__': 'ly_test_tools=ly_test_tools._internal.pytest_plugin.test_tools_fixtures', 'testrail_filter=ly_test_tools._internal.pytest_plugin.case_id', 'terminal_report=ly_test_tools._internal.pytest_plugin.terminal_report', - 'editor_test=ly_test_tools._internal.pytest_plugin.editor_test' + 'editor_test=ly_test_tools._internal.pytest_plugin.editor_test', + 'pytester=_pytest.pytester' ], }, ) diff --git a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake index d6fdc5cd9b..6210b9cc18 100644 --- a/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake +++ b/cmake/3rdParty/Platform/Linux/BuiltInPackages_linux.cmake @@ -39,7 +39,7 @@ ly_associate_package(PACKAGE_NAME libsamplerate-0.2.1-rev2-linux ly_associate_package(PACKAGE_NAME OpenSSL-1.1.1b-rev2-linux TARGETS OpenSSL PACKAGE_HASH b779426d1e9c5ddf71160d5ae2e639c3b956e0fb5e9fcaf9ce97c4526024e3bc) ly_associate_package(PACKAGE_NAME DirectXShaderCompilerDxc-1.6.2104-o3de-rev3-linux TARGETS DirectXShaderCompilerDxc PACKAGE_HASH 88c4a359325d749bc34090b9ac466424847f3b71ba0de15045cf355c17c07099) ly_associate_package(PACKAGE_NAME SPIRVCross-2021.04.29-rev1-linux TARGETS SPIRVCross PACKAGE_HASH 7889ee5460a688e9b910c0168b31445c0079d363affa07b25d4c8aeb608a0b80) -ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev2-linux TARGETS azslc PACKAGE_HASH 1ba84d8321a566d35a1e9aa7400211ba8e6d1c11c08e4be3c93e6e74b8f7aef1) +ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-linux TARGETS azslc PACKAGE_HASH 6d7dc671936c34ff70d2632196107ca1b8b2b41acdd021bfbc69a9fd56215c22) ly_associate_package(PACKAGE_NAME zlib-1.2.11-rev5-linux TARGETS ZLIB PACKAGE_HASH 9be5ea85722fc27a8645a9c8a812669d107c68e6baa2ca0740872eaeb6a8b0fc) ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-linux TARGETS squish-ccr PACKAGE_HASH 85fecafbddc6a41a27c5f59ed4a5dfb123a94cb4666782cf26e63c0a4724c530) ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-linux TARGETS astc-encoder PACKAGE_HASH 2ba97a06474d609945f0ab4419af1f6bbffdd294ca6b869f5fcebec75c573c0f) diff --git a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake index 41df718b71..d054ba22e1 100644 --- a/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake +++ b/cmake/3rdParty/Platform/Mac/BuiltInPackages_mac.cmake @@ -17,7 +17,6 @@ ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) -ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev1-multiplatform TARGETS azslc PACKAGE_HASH 664439954bad54cc43731c684adbc1249d971ad7379fcd83ca8bba5e1cc4a2d0) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) @@ -43,4 +42,4 @@ ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-mac ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-mac TARGETS astc-encoder PACKAGE_HASH 96f6ea8c3e45ec7fe525230c7c53ca665c8300d8e28456cc19bb3159ce6f8dcc) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b) - +ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-mac TARGETS azslc PACKAGE_HASH a9d81946b42ffa55c0d14d6a9249b3340e59a8fb8835e7a96c31df80f14723bc) diff --git a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake index cccd2591e8..fce2229771 100644 --- a/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake +++ b/cmake/3rdParty/Platform/Windows/BuiltInPackages_windows.cmake @@ -17,7 +17,6 @@ ly_associate_package(PACKAGE_NAME cityhash-1.1-multiplatform ly_associate_package(PACKAGE_NAME expat-2.1.0-multiplatform TARGETS expat PACKAGE_HASH 452256acd1fd699cef24162575b3524fccfb712f5321c83f1df1ce878de5b418) ly_associate_package(PACKAGE_NAME zstd-1.35-multiplatform TARGETS zstd PACKAGE_HASH 45d466c435f1095898578eedde85acf1fd27190e7ea99aeaa9acfd2f09e12665) ly_associate_package(PACKAGE_NAME SQLite-3.32.2-rev3-multiplatform TARGETS SQLite PACKAGE_HASH dd4d3de6cbb4ce3d15fc504ba0ae0587e515dc89a25228037035fc0aef4831f4) -ly_associate_package(PACKAGE_NAME azslc-1.7.23-rev1-multiplatform TARGETS azslc PACKAGE_HASH 664439954bad54cc43731c684adbc1249d971ad7379fcd83ca8bba5e1cc4a2d0) ly_associate_package(PACKAGE_NAME glad-2.0.0-beta-rev2-multiplatform TARGETS glad PACKAGE_HASH ff97ee9664e97d0854b52a3734c2289329d9f2b4cd69478df6d0ca1f1c9392ee) ly_associate_package(PACKAGE_NAME lux_core-2.2-rev5-multiplatform TARGETS lux_core PACKAGE_HASH c8c13cf7bc351643e1abd294d0841b24dee60e51647dff13db7aec396ad1e0b5) ly_associate_package(PACKAGE_NAME xxhash-0.7.4-rev1-multiplatform TARGETS xxhash PACKAGE_HASH e81f3e6c4065975833996dd1fcffe46c3cf0f9e3a4207ec5f4a1b564ba75861e) @@ -50,3 +49,4 @@ ly_associate_package(PACKAGE_NAME squish-ccr-deb557d-rev1-windows ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev1-windows TARGETS astc-encoder PACKAGE_HASH 3addc6fc1a7eb0d6b7f3d530e962af967e6d92b3825ef485da243346357cf78e) ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-windows TARGETS ISPCTexComp PACKAGE_HASH b6fa6ea28a2808a9a5524c72c37789c525925e435770f2d94eb2d387360fa2d0) ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-windows TARGETS lz4 PACKAGE_HASH 4ea457b833cd8cfaf8e8e06ed6df601d3e6783b606bdbc44a677f77e19e0db16) +ly_associate_package(PACKAGE_NAME azslc-1.7.34-rev1-windows TARGETS azslc PACKAGE_HASH 44eb2e0fc4b0f1c75d0fb6f24c93a5753655b84dbc3e6ad45389ed3b9cf7a4b0) diff --git a/cmake/LYTestWrappers.cmake b/cmake/LYTestWrappers.cmake index 87649c5be6..9b139645fb 100644 --- a/cmake/LYTestWrappers.cmake +++ b/cmake/LYTestWrappers.cmake @@ -17,7 +17,7 @@ set(LY_GOOGLETEST_EXTRA_PARAMS CACHE STRING "Allows injection of additional opti find_package(Python REQUIRED MODULE) -ly_set(LY_PYTEST_EXECUTABLE ${LY_PYTHON_CMD} -B -m pytest -v --tb=short --show-capture=log -c ${LY_ROOT_FOLDER}/ctest_pytest.ini --build-directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$") +ly_set(LY_PYTEST_EXECUTABLE ${LY_PYTHON_CMD} -B -m pytest -v --tb=short --show-capture=log -c ${LY_ROOT_FOLDER}/pytest.ini --build-directory "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$") ly_set(LY_TEST_GLOBAL_KNOWN_SUITE_NAMES "smoke" "main" "periodic" "benchmark" "sandbox" "awsi") ly_set(LY_TEST_GLOBAL_KNOWN_REQUIREMENTS "gpu") diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index c453fadd2e..44cdeb1994 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -364,7 +364,7 @@ function(ly_setup_o3de_install) # Misc install(FILES - ${LY_ROOT_FOLDER}/ctest_pytest.ini + ${LY_ROOT_FOLDER}/pytest.ini ${LY_ROOT_FOLDER}/LICENSE.txt ${LY_ROOT_FOLDER}/README.md DESTINATION . diff --git a/ctest_pytest.ini b/pytest.ini similarity index 91% rename from ctest_pytest.ini rename to pytest.ini index 93310a522d..65c93e0eb2 100644 --- a/ctest_pytest.ini +++ b/pytest.ini @@ -7,10 +7,11 @@ # [pytest] -python_files = 'test_*.py' , '*_test.py' , '*_tests.py' +python_files = 'test_*.py' , '*_test.py' , '*_tests.py', 'TestSuite_*.py' norecursedirs = Python/2.7.* Python/3.7.5 BinTemp Cache SDKs JenkinsScripts cmake junit_family=legacy log_format=%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s) +addopts='--tb=short' '--show-capture=log' # primary suite markers which should appear on every filterable test and be mutually exclusive: markers = SUITE_smoke: Tiny, quick tests of fundamental operation (tests with no suite marker will also execute here in CI) diff --git a/python/get_python_path.bat b/python/get_python_path.bat new file mode 100644 index 0000000000..66942efd70 --- /dev/null +++ b/python/get_python_path.bat @@ -0,0 +1,33 @@ +@echo off +REM +REM Copyright (c) Contributors to the Open 3D Engine Project. +REM For complete copyright and license terms please see the LICENSE at the root of this distribution. +REM +REM SPDX-License-Identifier: Apache-2.0 OR MIT +REM +REM + +:: Retreives the path to the O3DE python executable(s) + +:: Skip initialization if already completed +IF "%O3DE_PYTHONHOME_INIT%"=="1" GOTO :END_OF_FILE + +SET CMD_DIR=%~dp0 + +:: Note, many DCC tools (like Maya) include thier own versioned python interpretter. +:: Some apps may not operate correctly if PYTHONHOME is set/propogated. +:: This is definitely the case with Maya, doing so causes Maya to not boot. +FOR /F "tokens=* USEBACKQ" %%F IN (`%CMD_DIR%\python.cmd %CMD_DIR%\get_python_path.py`) DO (SET O3DE_PYTHONHOME=%%F) +echo O3DE_PYTHONHOME - is now the folder containing O3DE python executable +echo O3DE_PYTHONHOME = %O3DE_PYTHONHOME% + +SET PYTHON=%O3DE_PYTHONHOME%\python.exe + +:: Set flag so we don't initialize dccsi environment twice +SET O3DE_PYTHONHOME_INIT=1 +GOTO END_OF_FILE + +:: Return to starting directory +POPD + +:END_OF_FILE diff --git a/python/get_python_path.py b/python/get_python_path.py new file mode 100644 index 0000000000..fc503ae4ab --- /dev/null +++ b/python/get_python_path.py @@ -0,0 +1,16 @@ +# coding:utf-8 +#!/usr/bin/python +# +# 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 +# +# +# ------------------------------------------------------------------------- +"""retreive the O3DE python path""" +import sys +from pathlib import Path +py_exe = Path(sys.executable) +py_dir = py_exe.parents[0] +print(py_dir.resolve()) \ No newline at end of file diff --git a/scripts/o3de/o3de/engine_template.py b/scripts/o3de/o3de/engine_template.py index cba7539aaa..e93f972301 100755 --- a/scripts/o3de/o3de/engine_template.py +++ b/scripts/o3de/o3de/engine_template.py @@ -407,15 +407,12 @@ def create_template(source_path: pathlib.Path, source_name = os.path.basename(source_path) sanitized_source_name = utils.sanitize_identifier_for_cpp(source_name) - # if no template path, error + # if no template path, use default_templates_folder path if not template_path: - logger.info(f'Template path empty. Using source name {source_name}') - template_path = pathlib.Path(source_name) - if not template_path.is_absolute(): default_templates_folder = manifest.get_registered(default_folder='templates') - template_path = default_templates_folder / template_path - logger.info(f'Template path not a full path. Using default templates folder {template_path}') - if not force and template_path.is_dir(): + template_path = default_templates_folder / source_name + logger.info(f'Template path empty. Using default templates folder {template_path}') + if not force and template_path.is_dir() and len(list(template_path.iterdir())): logger.error(f'Template path {template_path} already exists.') return 1 @@ -1105,7 +1102,7 @@ def create_from_template(destination_path: pathlib.Path, logger.error(f'Could not find the template {template_name}=>{template_path}') return 1 - # the template.json should be in the template_path, make sure it's there a nd valid + # the template.json should be in the template_path, make sure it is valid template_json = template_path / 'template.json' if not validation.valid_o3de_template_json(template_json): logger.error(f'Template json {template_path} is invalid.') @@ -1254,7 +1251,7 @@ def create_from_template(destination_path: pathlib.Path, # destination restricted path elif destination_restricted_path: if os.path.isabs(destination_restricted_path): - restricted_default_path = manifest.get_registered(default='restricted') + restricted_default_path = manifest.get_registered(default_folder='restricted') new_destination_restricted_path = restricted_default_path / destination_restricted_path logger.info(f'{destination_restricted_path} is not a full path, making it relative' f' to default restricted path = {new_destination_restricted_path}') @@ -1346,7 +1343,7 @@ def create_project(project_path: pathlib.Path, Template instantiation specialization that makes all default assumptions for a Project template instantiation, reducing the effort needed in instancing a project :param project_path: the project path, can be absolute or relative to default projects path - :param project_name: the project name, defaults to project_path basename if not provided + :param project_name: the project name, defaults to project_path basename if not provided :param template_path: the path to the template you want to instance, can be absolute or relative to default templates path :param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path :param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects' @@ -1523,13 +1520,9 @@ def create_project(project_path: pathlib.Path, if not project_path: logger.error('Project path cannot be empty.') return 1 - if not os.path.isabs(project_path): - default_projects_folder = manifest.get_registered(default_folder='projects') - new_project_path = default_projects_folder / project_path - logger.info(f'Project Path {project_path} is not a full path, we must assume its relative' - f' to default projects path = {new_project_path}') - project_path = new_project_path - if not force and os.path.isdir(project_path) and len(os.listdir(project_path)) > 0: + + project_path = project_path.resolve() + if not force and project_path.is_dir() and len(list(project_path.iterdir())): logger.error(f'Project path {project_path} already exists and is not empty.') return 1 elif not os.path.isdir(project_path): @@ -1904,14 +1897,10 @@ def create_gem(gem_path: pathlib.Path, if not gem_path: logger.error('Gem path cannot be empty.') return 1 - if not os.path.isabs(gem_path): - default_gems_folder = manifest.get_registered(default_folder='gems') - new_gem_path = default_gems_folder / gem_path - logger.info(f'Gem Path {gem_path} is not a full path, we must assume its relative' - f' to default gems path = {new_gem_path}') - gem_path = new_gem_path - if not force and os.path.isdir(gem_path): - logger.error(f'Gem path {gem_path} already exists.') + + gem_path = gem_path.resolve() + if not force and gem_path.is_dir() and len(list(gem_path.iterdir())): + logger.error(f'Gem path {gem_path} already exists and is not empty.') return 1 else: os.makedirs(gem_path, exist_ok=force) @@ -1936,16 +1925,18 @@ def create_gem(gem_path: pathlib.Path, # gem restricted path elif gem_restricted_path: if not os.path.isabs(gem_restricted_path): - default_gems_restricted_folder = manifest.get_registered(restricted_name='gems') - new_gem_restricted_path = default_gems_restricted_folder /gem_restricted_path - logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' - f' relative to default gems restricted path = {new_gem_restricted_path}') - gem_restricted_path = new_gem_restricted_path - elif template_restricted_path: + gem_restricted_default_path = manifest.get_registered(restricted_name='gems') + if gem_restricted_default_path: + new_gem_restricted_path = gem_restricted_default_path / gem_restricted_path + logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' + f' relative to default gems restricted path = {new_gem_restricted_path}') + gem_restricted_path = new_gem_restricted_path + else: gem_restricted_default_path = manifest.get_registered(restricted_name='gems') - logger.info(f'--gem-restricted-path is not specified, using default gem restricted path / gem name' - f' = {gem_restricted_default_path}') - gem_restricted_path = gem_restricted_default_path + if gem_restricted_default_path: + logger.info(f'--gem-restricted-path is not specified, using default / ' + f' = {gem_restricted_default_path}') + gem_restricted_path = gem_restricted_default_path / gem_name # gem restricted relative if not gem_restricted_platform_relative_path: @@ -1964,7 +1955,7 @@ def create_gem(gem_path: pathlib.Path, replacements.append(("${NameUpper}", gem_name.upper())) replacements.append(("${NameLower}", gem_name.lower())) replacements.append(("${SanitizedCppName}", sanitized_cpp_name)) - + # module id is a uuid with { and - if module_id: @@ -2244,14 +2235,14 @@ def add_args(subparsers) -> None: create_from_template_subparser = subparsers.add_parser('create-from-template') create_from_template_subparser.add_argument('-dp', '--destination-path', type=pathlib.Path, required=True, help='The path to where you want the template instantiated,' - ' can be absolute or dev root relative.' + ' can be absolute or relative to the current working directory.' 'Ex. C:/o3de/Test' 'Test = ') group = create_from_template_subparser.add_mutually_exclusive_group(required=True) group.add_argument('-tp', '--template-path', type=pathlib.Path, required=False, help='The path to the template you want to instantiate, can be absolute' - ' or dev root/Templates relative.' + ' or relative to the current working directory.' 'Ex. C:/o3de/Template/TestTemplate' 'TestTemplate = ') group.add_argument('-tn', '--template-name', type=str, required=False, @@ -2327,7 +2318,7 @@ def add_args(subparsers) -> None: create_project_subparser = subparsers.add_parser('create-project') create_project_subparser.add_argument('-pp', '--project-path', type=pathlib.Path, required=True, help='The location of the project you wish to create from the template,' - ' can be an absolute path or dev root relative.' + ' can be an absolute path or relative to the current working directory.' ' Ex. C:/o3de/TestProject' ' TestProject = if --project-name not provided') create_project_subparser.add_argument('-pn', '--project-name', type=str, required=False, @@ -2349,8 +2340,8 @@ def add_args(subparsers) -> None: group = create_project_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-prp', '--project-restricted-path', type=pathlib.Path, required=False, default=None, - help='path to the projects restricted folder, can be absolute or relative' - ' to the restricted="projects"') + help='path to the projects restricted folder, can be absolute or relative to' + ' the default restricted projects directory') group.add_argument('-prn', '--project-restricted-name', type=str, required=False, default=None, help='The name of the registered projects restricted path. If supplied this will resolve' @@ -2360,7 +2351,7 @@ def add_args(subparsers) -> None: group.add_argument('-trp', '--template-restricted-path', type=pathlib.Path, required=False, default=None, help='The templates restricted path can be absolute or relative to' - ' restricted="templates"') + 'the default restricted templates directory') group.add_argument('-trn', '--template-restricted-name', type=str, required=False, default=None, help='The name of the registered templates restricted path. If supplied this will resolve' @@ -2423,7 +2414,7 @@ def add_args(subparsers) -> None: # creation of a gem from a template (like create from template but makes gem assumptions) create_gem_subparser = subparsers.add_parser('create-gem') create_gem_subparser.add_argument('-gp', '--gem-path', type=pathlib.Path, required=True, - help='The gem path, can be absolute or relative to default gems path') + help='The gem path, can be absolute or relative to the current working directory') create_gem_subparser.add_argument('-gn', '--gem-name', type=str, help='The name to use when substituting the ${Name} placeholder for the gem,' ' must be alphanumeric, ' @@ -2444,19 +2435,18 @@ def add_args(subparsers) -> None: group = create_gem_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-grp', '--gem-restricted-path', type=pathlib.Path, required=False, default=None, - help='The path to the gem restricted to write to folder if any, can be' - 'absolute or dev root relative, default is dev root/restricted.') + help='The gem restricted path, can be absolute or relative to' + ' the default restricted gems directory') group.add_argument('-grn', '--gem-restricted-name', type=str, required=False, default=None, - help='The path to the gem restricted to write to folder if any, can be' - 'absolute or dev root relative, default is dev root/restricted. If supplied' - ' this will resolve the --gem-restricted-path.') + help='The name of the gem to look up the gem restricted path if any.' + 'If supplied this will resolve the --gem-restricted-path.') group = create_gem_subparser.add_mutually_exclusive_group(required=False) group.add_argument('-trp', '--template-restricted-path', type=pathlib.Path, required=False, default=None, help='The templates restricted path, can be absolute or relative to' - ' the restricted="templates"') + ' the default restricted templates directory') group.add_argument('-trn', '--template-restricted-name', type=str, required=False, default=None, help='The name of the registered templates restricted path. If supplied' diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index c3d201f23c..0de161177c 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -467,7 +467,7 @@ def register_restricted_path(json_data: dict, def register_repo(json_data: dict, - repo_uri: str or pathlib.Path, + repo_uri: str, remove: bool = False) -> int: if not repo_uri: logger.error(f'Repo URI cannot be empty.') @@ -480,9 +480,9 @@ def register_repo(json_data: dict, while repo_uri in json_data['repos']: json_data['repos'].remove(repo_uri) else: - repo_uri = pathlib.Path(repo_uri).resolve() - while repo_uri.as_posix() in json_data['repos']: - json_data['repos'].remove(repo_uri.as_posix()) + repo_uri = pathlib.Path(repo_uri).resolve().as_posix() + while repo_uri in json_data['repos']: + json_data['repos'].remove(repo_uri) if remove: logger.warn(f'Removing repo uri {repo_uri}.') @@ -566,7 +566,7 @@ def register(engine_path: pathlib.Path = None, external_subdir_path: pathlib.Path = None, template_path: pathlib.Path = None, restricted_path: pathlib.Path = None, - repo_uri: str or pathlib.Path = None, + repo_uri: str = None, default_engines_folder: pathlib.Path = None, default_projects_folder: pathlib.Path = None, default_gems_folder: pathlib.Path = None, @@ -641,7 +641,7 @@ def register(engine_path: pathlib.Path = None, return 1 result = result or register_restricted_path(json_data, restricted_path, remove, project_path, engine_path) - if isinstance(repo_uri, str) or isinstance(repo_uri, pathlib.PurePath): + if isinstance(repo_uri, str): if not repo_uri: logger.error(f'Repo URI cannot be empty.') return 1