Merge branch 'development' of https://github.com/aws-lumberyard-dev/o3de into scripting/rte_issues
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import os, traceback, binascii, sys, json, pathlib
|
||||
import azlmbr.math
|
||||
import azlmbr.bus
|
||||
|
||||
#
|
||||
# SceneAPI Processor
|
||||
#
|
||||
|
||||
|
||||
def log_exception_traceback():
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
data = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
print(str(data))
|
||||
|
||||
def get_mesh_node_names(sceneGraph):
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
from scene_api import scene_data as sceneData
|
||||
|
||||
meshDataList = []
|
||||
node = sceneGraph.get_root()
|
||||
children = []
|
||||
paths = []
|
||||
|
||||
while node.IsValid():
|
||||
# store children to process after siblings
|
||||
if sceneGraph.has_node_child(node):
|
||||
children.append(sceneGraph.get_node_child(node))
|
||||
|
||||
nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
|
||||
paths.append(nodeName.get_path())
|
||||
|
||||
# store any node that has mesh data content
|
||||
nodeContent = sceneGraph.get_node_content(node)
|
||||
if nodeContent.CastWithTypeName('MeshData'):
|
||||
if sceneGraph.is_node_end_point(node) is False:
|
||||
if (len(nodeName.get_path())):
|
||||
meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
|
||||
|
||||
# advance to next node
|
||||
if sceneGraph.has_node_sibling(node):
|
||||
node = sceneGraph.get_node_sibling(node)
|
||||
elif children:
|
||||
node = children.pop()
|
||||
else:
|
||||
node = azlmbr.scene.graph.NodeIndex()
|
||||
|
||||
return meshDataList, paths
|
||||
|
||||
def update_manifest(scene):
|
||||
import json
|
||||
import uuid, os
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
from scene_api import scene_data as sceneData
|
||||
|
||||
graph = sceneData.SceneGraph(scene.graph)
|
||||
# Get a list of all the mesh nodes, as well as all the nodes
|
||||
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
|
||||
scene_manifest = sceneData.SceneManifest()
|
||||
|
||||
clean_filename = scene.sourceFilename.replace('.', '_')
|
||||
|
||||
# Compute the filename of the scene file
|
||||
source_basepath = scene.watchFolder
|
||||
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
|
||||
source_filename_only = os.path.basename(clean_filename)
|
||||
|
||||
created_entities = []
|
||||
|
||||
# Loop every mesh node in the scene
|
||||
for activeMeshIndex in range(len(mesh_name_list)):
|
||||
mesh_name = mesh_name_list[activeMeshIndex]
|
||||
mesh_path = mesh_name.get_path()
|
||||
# Create a unique mesh group name using the filename + node name
|
||||
mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name())
|
||||
# Remove forbidden filename characters from the name since this will become a file on disk later
|
||||
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
|
||||
# Add the MeshGroup to the manifest and give it a unique ID
|
||||
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
|
||||
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
|
||||
# Set our current node as the only node that is included in this MeshGroup
|
||||
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
|
||||
|
||||
# Explicitly remove all other nodes to prevent implicit inclusions
|
||||
for node in all_node_paths:
|
||||
if node != mesh_path:
|
||||
scene_manifest.mesh_group_unselect_node(mesh_group, node)
|
||||
|
||||
# Create an editor entity
|
||||
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
|
||||
# Add an EditorMeshComponent to the entity
|
||||
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
|
||||
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
|
||||
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
|
||||
# The assetHint will be converted to an AssetId later during prefab loading
|
||||
json_update = json.dumps({
|
||||
"Controller": { "Configuration": { "ModelAsset": {
|
||||
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
|
||||
});
|
||||
# Apply the JSON above to the component we created
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed")
|
||||
|
||||
# Keep track of the entity we set up, we'll add them all to the prefab we're creating later
|
||||
created_entities.append(entity_id)
|
||||
|
||||
# Create a prefab with all our entities
|
||||
prefab_filename = source_filename_only + ".prefab"
|
||||
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename)
|
||||
|
||||
if created_template_id == azlmbr.prefab.InvalidTemplateId:
|
||||
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
|
||||
|
||||
# Convert the prefab to a JSON string
|
||||
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
|
||||
|
||||
if output.IsSuccess():
|
||||
jsonString = output.GetValue()
|
||||
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
|
||||
jsonResult = json.loads(jsonString)
|
||||
# Add a PrefabGroup to the manifest and store the JSON on it
|
||||
scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult)
|
||||
else:
|
||||
raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
new_manifest = scene_manifest.export()
|
||||
|
||||
return new_manifest
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
log_exception_traceback()
|
||||
|
||||
global sceneJobHandler
|
||||
sceneJobHandler = None
|
||||
|
||||
# try to create SceneAPI handler for processing
|
||||
try:
|
||||
import azlmbr.scene as sceneApi
|
||||
if (sceneJobHandler == None):
|
||||
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
|
||||
sceneJobHandler.connect()
|
||||
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
|
||||
except:
|
||||
sceneJobHandler = None
|
||||
@@ -21,7 +21,6 @@ set(ENABLED_GEMS
|
||||
QtForPython
|
||||
PythonAssetBuilder
|
||||
Metastream
|
||||
|
||||
Camera
|
||||
EMotionFX
|
||||
AtomTressFX
|
||||
@@ -53,6 +52,6 @@ set(ENABLED_GEMS
|
||||
AWSCore
|
||||
AWSClientAuth
|
||||
AWSMetrics
|
||||
|
||||
PrefabBuilder
|
||||
AudioSystem
|
||||
)
|
||||
|
||||
@@ -65,4 +65,18 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedT
|
||||
COMPONENT
|
||||
Atom
|
||||
)
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::Atom_TestSuite_Main_GPU_Optimized
|
||||
TEST_SUITE main
|
||||
TEST_REQUIRES gpu
|
||||
TEST_SERIAL
|
||||
TIMEOUT 1200
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_GPU_Optimized.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
Editor
|
||||
COMPONENT
|
||||
Atom
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
|
||||
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
|
||||
from .atom_utils.atom_component_helper import create_screenshots_archive, golden_images_directory
|
||||
|
||||
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
|
||||
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
|
||||
|
||||
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
|
||||
use_null_renderer = False # Default is True
|
||||
screenshot_name = "AtomBasicLevelSetup.ppm"
|
||||
test_screenshots = [] # Gets set by setup()
|
||||
screenshot_directory = "" # Gets set by setup()
|
||||
|
||||
# Clear existing test screenshots before starting test.
|
||||
def setup(self, workspace):
|
||||
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
|
||||
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
|
||||
file_system.delete(test_screenshots, True, True)
|
||||
|
||||
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
|
||||
|
||||
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
|
||||
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
|
||||
compare_screenshots(test_screenshot, golden_screenshot)
|
||||
create_screenshots_archive(screenshot_directory)
|
||||
@@ -3,13 +3,54 @@ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
File to assist with common hydra component functions used across various Atom tests.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
from editor_python_test_tools.editor_test_helper import EditorTestHelper
|
||||
|
||||
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
|
||||
def create_screenshots_archive(screenshot_path):
|
||||
"""
|
||||
Creates a new zip file archive at archive_path containing all files listed within archive_path.
|
||||
:param screenshot_path: location containing the files to archive, the zip archive file will also be saved here.
|
||||
:return: None, but creates a new zip file archive inside path containing all of the files inside archive_path.
|
||||
"""
|
||||
files_to_archive = []
|
||||
|
||||
# Search for .png and .ppm files to add to the zip archive file.
|
||||
for (folder_name, sub_folders, file_names) in os.walk(screenshot_path):
|
||||
for file_name in file_names:
|
||||
if file_name.endswith(".png") or file_name.endswith(".ppm"):
|
||||
file_path = os.path.join(folder_name, file_name)
|
||||
files_to_archive.append(file_path)
|
||||
|
||||
# Setup variables for naming the zip archive file.
|
||||
timestamp = datetime.datetime.now().timestamp()
|
||||
formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S")
|
||||
screenshots_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip')
|
||||
|
||||
# Write all of the valid .png and .ppm files to the archive file.
|
||||
with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive:
|
||||
for file_path in files_to_archive:
|
||||
file_name = os.path.basename(file_path)
|
||||
zip_archive.write(file_path, file_name)
|
||||
|
||||
|
||||
def golden_images_directory():
|
||||
"""
|
||||
Uses this file location to return the valid location for golden image files.
|
||||
:return: The path to the golden_images directory, but raises an IOError if the golden_images directory is missing.
|
||||
"""
|
||||
current_file_directory = os.path.join(os.path.dirname(__file__))
|
||||
golden_images_dir = os.path.join(current_file_directory, '..', 'golden_images')
|
||||
|
||||
if not os.path.exists(golden_images_dir):
|
||||
raise IOError(
|
||||
f'golden_images" directory was not found at path "{golden_images_dir}"'
|
||||
f'Please add a "golden_images" directory inside: "{current_file_directory}"'
|
||||
)
|
||||
|
||||
return golden_images_dir
|
||||
|
||||
|
||||
def create_basic_atom_level(level_name):
|
||||
@@ -31,6 +72,9 @@ def create_basic_atom_level(level_name):
|
||||
import azlmbr.object
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.editor_test_helper import EditorTestHelper
|
||||
|
||||
helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper")
|
||||
|
||||
# Create a new level.
|
||||
new_level_name = level_name
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
# fmt: off
|
||||
class Tests :
|
||||
camera_component_added = ("Camera component was added", "Camera component wasn't added")
|
||||
camera_fov_set = ("Camera component FOV property set", "Camera component FOV property wasn't set")
|
||||
directional_light_component_added = ("Directional Light component added", "Directional Light component wasn't added")
|
||||
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
|
||||
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
|
||||
global_skylight_component_added = ("Global Skylight (IBL) component added", "Global Skylight (IBL) component wasn't added")
|
||||
global_skylight_diffuse_image_set = ("Global Skylight Diffuse Image property set", "Global Skylight Diffuse Image property wasn't set")
|
||||
global_skylight_specular_image_set = ("Global Skylight Specular Image property set", "Global Skylight Specular Image property wasn't set")
|
||||
ground_plane_material_asset_set = ("Ground Plane Material Asset was set", "Ground Plane Material Asset wasn't set")
|
||||
ground_plane_material_component_added = ("Ground Plane Material component added", "Ground Plane Material component wasn't added")
|
||||
ground_plane_mesh_asset_set = ("Ground Plane Mesh Asset property was set", "Ground Plane Mesh Asset property wasn't set")
|
||||
hdri_skybox_component_added = ("HDRi Skybox component added", "HDRi Skybox component wasn't added")
|
||||
hdri_skybox_cubemap_texture_set = ("HDRi Skybox Cubemap Texture property set", "HDRi Skybox Cubemap Texture property wasn't set")
|
||||
mesh_component_added = ("Mesh component added", "Mesh component wasn't added")
|
||||
no_assert_occurred = ("No asserts detected", "Asserts were detected")
|
||||
no_error_occurred = ("No errors detected", "Errors were detected")
|
||||
secondary_grid_spacing = ("Secondary Grid Spacing set", "Secondary Grid Spacing not set")
|
||||
sphere_material_component_added = ("Sphere Material component added", "Sphere Material component wasn't added")
|
||||
sphere_material_set = ("Sphere Material Asset was set", "Sphere Material Asset wasn't set")
|
||||
sphere_mesh_asset_set = ("Sphere Mesh Asset was set", "Sphere Mesh Asset wasn't set")
|
||||
viewport_set = ("Viewport set to correct size", "Viewport not set to correct size")
|
||||
# fmt: on
|
||||
|
||||
|
||||
def AtomGPU_BasicLevelSetup_SetsUpLevel():
|
||||
"""
|
||||
Summary:
|
||||
Sets up a level to match the AtomBasicLevelSetup.ppm golden image then takes a screenshot to verify the setup.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The scene can be setup for a basic level.
|
||||
The test screenshot matches the appearance of the AtomBasicLevelSetup.ppm golden image.
|
||||
|
||||
Test Steps:
|
||||
1. Close error windows and display helpers then update the viewport size.
|
||||
2. Create Default Level Entity.
|
||||
3. Create Grid Entity as a child entity of the Default Level Entity.
|
||||
4. Add Grid component to Grid Entity and set Secondary Grid Spacing.
|
||||
5. Create Global Skylight (IBL) Entity as a child entity of the Default Level Entity.
|
||||
6. Add HDRi Skybox component to the Global Skylight (IBL) Entity.
|
||||
7. Add Global Skylight (IBL) component to the Global Skylight (IBL) Entity.
|
||||
8. Set the Cubemap Texture property of the HDRi Skybox component.
|
||||
9. Set the Diffuse Image property of the Global Skylight (IBL) component.
|
||||
10. Set the Specular Image property of the Global Skylight (IBL) component.
|
||||
11. Create a Ground Plane Entity with a Material component that is a child entity of the Default Level Entity.
|
||||
12. Set the Material Asset property of the Material component for the Ground Plane Entity.
|
||||
13. Add the Mesh component to the Ground Plane Entity and set the Mesh component Mesh Asset property.
|
||||
14. Create a Directional Light Entity as a child entity of the Default Level Entity.
|
||||
15. Add Directional Light component to Directional Light Entity and set entity rotation.
|
||||
16. Create a Sphere Entity as a child entity of the Default Level Entity then add a Material component.
|
||||
17. Set the Material Asset property of the Material component for the Sphere Entity.
|
||||
18. Add Mesh component to Sphere Entity and set the Mesh Asset property for the Mesh component.
|
||||
19. Create a Camera Entity as a child entity of the Default Level Entity then add a Camera component.
|
||||
20. Set the Camera Entity rotation value and set the Camera component Field of View value.
|
||||
21. Enter game mode.
|
||||
22. Take screenshot.
|
||||
23. Exit game mode.
|
||||
24. Look for errors.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import os
|
||||
from math import isclose
|
||||
|
||||
import azlmbr.asset as asset
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.math as math
|
||||
import azlmbr.paths
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper
|
||||
|
||||
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
|
||||
|
||||
MATERIAL_COMPONENT_NAME = "Material"
|
||||
MESH_COMPONENT_NAME = "Mesh"
|
||||
SCREENSHOT_NAME = "AtomBasicLevelSetup"
|
||||
SCREEN_WIDTH = 1280
|
||||
SCREEN_HEIGHT = 720
|
||||
DEGREE_RADIAN_FACTOR = 0.0174533
|
||||
|
||||
def initial_viewport_setup(screen_width, screen_height):
|
||||
general.set_viewport_size(screen_width, screen_height)
|
||||
general.update_viewport()
|
||||
result = isclose(
|
||||
a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and isclose(
|
||||
a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1)
|
||||
|
||||
return result
|
||||
|
||||
with Tracer() as error_tracer:
|
||||
# Test setup begins.
|
||||
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
|
||||
helper.init_idle()
|
||||
helper.open_level("", "Base")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Close error windows and display helpers then update the viewport size.
|
||||
helper.close_error_windows()
|
||||
helper.close_display_helpers()
|
||||
general.update_viewport()
|
||||
Report.critical_result(Tests.viewport_set, initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT))
|
||||
|
||||
# 2. Create Default Level Entity.
|
||||
default_level_entity_name = "Default Level"
|
||||
default_level_entity = EditorEntity.create_editor_entity_at(
|
||||
math.Vector3(0.0, 0.0, 0.0), default_level_entity_name)
|
||||
|
||||
# 3. Create Grid Entity as a child entity of the Default Level Entity.
|
||||
grid_name = "Grid"
|
||||
grid_entity = EditorEntity.create_editor_entity(grid_name, default_level_entity.id)
|
||||
|
||||
# 4. Add Grid component to Grid Entity and set Secondary Grid Spacing.
|
||||
grid_component = grid_entity.add_component(grid_name)
|
||||
secondary_grid_spacing_property = "Controller|Configuration|Secondary Grid Spacing"
|
||||
secondary_grid_spacing_value = 1.0
|
||||
grid_component.set_component_property_value(secondary_grid_spacing_property, secondary_grid_spacing_value)
|
||||
secondary_grid_spacing_set = grid_component.get_component_property_value(
|
||||
secondary_grid_spacing_property) == secondary_grid_spacing_value
|
||||
Report.result(Tests.secondary_grid_spacing, secondary_grid_spacing_set)
|
||||
|
||||
# 5. Create Global Skylight (IBL) Entity as a child entity of the Default Level Entity.
|
||||
global_skylight_name = "Global Skylight (IBL)"
|
||||
global_skylight_entity = EditorEntity.create_editor_entity(global_skylight_name, default_level_entity.id)
|
||||
|
||||
# 6. Add HDRi Skybox component to the Global Skylight (IBL) Entity.
|
||||
hdri_skybox_name = "HDRi Skybox"
|
||||
hdri_skybox_component = global_skylight_entity.add_component(hdri_skybox_name)
|
||||
Report.result(Tests.hdri_skybox_component_added, global_skylight_entity.has_component(hdri_skybox_name))
|
||||
|
||||
# 7. Add Global Skylight (IBL) component to the Global Skylight (IBL) Entity.
|
||||
global_skylight_component = global_skylight_entity.add_component(global_skylight_name)
|
||||
Report.result(Tests.global_skylight_component_added, global_skylight_entity.has_component(global_skylight_name))
|
||||
|
||||
# 8. Set the Cubemap Texture property of the HDRi Skybox component.
|
||||
global_skylight_image_asset_path = os.path.join(
|
||||
"LightingPresets", "greenwich_park_02_4k_iblskyboxcm_iblspecular.exr.streamingimage")
|
||||
global_skylight_image_asset = asset.AssetCatalogRequestBus(
|
||||
bus.Broadcast, "GetAssetIdByPath", global_skylight_image_asset_path, math.Uuid(), False)
|
||||
hdri_skybox_cubemap_texture_property = "Controller|Configuration|Cubemap Texture"
|
||||
hdri_skybox_component.set_component_property_value(
|
||||
hdri_skybox_cubemap_texture_property, global_skylight_image_asset)
|
||||
Report.result(
|
||||
Tests.hdri_skybox_cubemap_texture_set,
|
||||
hdri_skybox_component.get_component_property_value(
|
||||
hdri_skybox_cubemap_texture_property) == global_skylight_image_asset)
|
||||
|
||||
# 9. Set the Diffuse Image property of the Global Skylight (IBL) component.
|
||||
# Re-use the same image that was used in the previous test step.
|
||||
global_skylight_diffuse_image_property = "Controller|Configuration|Diffuse Image"
|
||||
global_skylight_component.set_component_property_value(
|
||||
global_skylight_diffuse_image_property, global_skylight_image_asset)
|
||||
Report.result(
|
||||
Tests.global_skylight_diffuse_image_set,
|
||||
global_skylight_component.get_component_property_value(
|
||||
global_skylight_diffuse_image_property) == global_skylight_image_asset)
|
||||
|
||||
# 10. Set the Specular Image property of the Global Skylight (IBL) component.
|
||||
# Re-use the same image that was used in the previous test step.
|
||||
global_skylight_specular_image_property = "Controller|Configuration|Specular Image"
|
||||
global_skylight_component.set_component_property_value(
|
||||
global_skylight_specular_image_property, global_skylight_image_asset)
|
||||
global_skylight_specular_image_set = global_skylight_component.get_component_property_value(
|
||||
global_skylight_specular_image_property)
|
||||
Report.result(
|
||||
Tests.global_skylight_specular_image_set, global_skylight_specular_image_set == global_skylight_image_asset)
|
||||
|
||||
# 11. Create a Ground Plane Entity with a Material component that is a child entity of the Default Level Entity.
|
||||
ground_plane_name = "Ground Plane"
|
||||
ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id)
|
||||
ground_plane_material_component = ground_plane_entity.add_component(MATERIAL_COMPONENT_NAME)
|
||||
Report.result(
|
||||
Tests.ground_plane_material_component_added, ground_plane_entity.has_component(MATERIAL_COMPONENT_NAME))
|
||||
|
||||
# 12. Set the Material Asset property of the Material component for the Ground Plane Entity.
|
||||
ground_plane_entity.set_local_uniform_scale(32.0)
|
||||
ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial")
|
||||
ground_plane_material_asset = asset.AssetCatalogRequestBus(
|
||||
bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False)
|
||||
ground_plane_material_asset_property = "Default Material|Material Asset"
|
||||
ground_plane_material_component.set_component_property_value(
|
||||
ground_plane_material_asset_property, ground_plane_material_asset)
|
||||
Report.result(
|
||||
Tests.ground_plane_material_asset_set,
|
||||
ground_plane_material_component.get_component_property_value(
|
||||
ground_plane_material_asset_property) == ground_plane_material_asset)
|
||||
|
||||
# 13. Add the Mesh component to the Ground Plane Entity and set the Mesh component Mesh Asset property.
|
||||
ground_plane_mesh_component = ground_plane_entity.add_component(MESH_COMPONENT_NAME)
|
||||
Report.result(Tests.mesh_component_added, ground_plane_entity.has_component(MESH_COMPONENT_NAME))
|
||||
ground_plane_mesh_asset_path = os.path.join("Objects", "plane.azmodel")
|
||||
ground_plane_mesh_asset = asset.AssetCatalogRequestBus(
|
||||
bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False)
|
||||
ground_plane_mesh_asset_property = "Controller|Configuration|Mesh Asset"
|
||||
ground_plane_mesh_component.set_component_property_value(
|
||||
ground_plane_mesh_asset_property, ground_plane_mesh_asset)
|
||||
Report.result(
|
||||
Tests.ground_plane_mesh_asset_set,
|
||||
ground_plane_mesh_component.get_component_property_value(
|
||||
ground_plane_mesh_asset_property) == ground_plane_mesh_asset)
|
||||
|
||||
# 14. Create a Directional Light Entity as a child entity of the Default Level Entity.
|
||||
directional_light_name = "Directional Light"
|
||||
directional_light_entity = EditorEntity.create_editor_entity_at(
|
||||
math.Vector3(0.0, 0.0, 10.0), directional_light_name, default_level_entity.id)
|
||||
|
||||
# 15. Add Directional Light component to Directional Light Entity and set entity rotation.
|
||||
directional_light_entity.add_component(directional_light_name)
|
||||
directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0)
|
||||
directional_light_entity.set_local_rotation(directional_light_entity_rotation)
|
||||
Report.result(
|
||||
Tests.directional_light_component_added, directional_light_entity.has_component(directional_light_name))
|
||||
|
||||
# 16. Create a Sphere Entity as a child entity of the Default Level Entity then add a Material component.
|
||||
sphere_entity = EditorEntity.create_editor_entity_at(
|
||||
math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id)
|
||||
sphere_material_component = sphere_entity.add_component(MATERIAL_COMPONENT_NAME)
|
||||
Report.result(Tests.sphere_material_component_added, sphere_entity.has_component(MATERIAL_COMPONENT_NAME))
|
||||
|
||||
# 17. Set the Material Asset property of the Material component for the Sphere Entity.
|
||||
sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial")
|
||||
sphere_material_asset = asset.AssetCatalogRequestBus(
|
||||
bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False)
|
||||
sphere_material_asset_property = "Default Material|Material Asset"
|
||||
sphere_material_component.set_component_property_value(sphere_material_asset_property, sphere_material_asset)
|
||||
Report.result(Tests.sphere_material_set, sphere_material_component.get_component_property_value(
|
||||
sphere_material_asset_property) == sphere_material_asset)
|
||||
|
||||
# 18. Add Mesh component to Sphere Entity and set the Mesh Asset property for the Mesh component.
|
||||
sphere_mesh_component = sphere_entity.add_component(MESH_COMPONENT_NAME)
|
||||
sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel")
|
||||
sphere_mesh_asset = asset.AssetCatalogRequestBus(
|
||||
bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False)
|
||||
sphere_mesh_asset_property = "Controller|Configuration|Mesh Asset"
|
||||
sphere_mesh_component.set_component_property_value(sphere_mesh_asset_property, sphere_mesh_asset)
|
||||
Report.result(Tests.sphere_mesh_asset_set, sphere_mesh_component.get_component_property_value(
|
||||
sphere_mesh_asset_property) == sphere_mesh_asset)
|
||||
|
||||
# 19. Create a Camera Entity as a child entity of the Default Level Entity then add a Camera component.
|
||||
camera_name = "Camera"
|
||||
camera_entity = EditorEntity.create_editor_entity_at(
|
||||
math.Vector3(5.5, -12.0, 9.0), camera_name, default_level_entity.id)
|
||||
camera_component = camera_entity.add_component(camera_name)
|
||||
Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name))
|
||||
|
||||
# 20. Set the Camera Entity rotation value and set the Camera component Field of View value.
|
||||
camera_entity_rotation = math.Vector3(
|
||||
DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0)
|
||||
camera_entity.set_local_rotation(camera_entity_rotation)
|
||||
camera_fov_property = "Controller|Configuration|Field of view"
|
||||
camera_fov_value = 60.0
|
||||
camera_component.set_component_property_value(camera_fov_property, camera_fov_value)
|
||||
azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id)
|
||||
Report.result(Tests.camera_fov_set, camera_component.get_component_property_value(
|
||||
camera_fov_property) == camera_fov_value)
|
||||
|
||||
# 21. Enter game mode.
|
||||
helper.enter_game_mode(Tests.enter_game_mode)
|
||||
helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=4.0)
|
||||
|
||||
# 22. Take screenshot.
|
||||
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{SCREENSHOT_NAME}.ppm")
|
||||
|
||||
# 23. Exit game mode.
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=4.0)
|
||||
|
||||
# 24. Look for errors.
|
||||
helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
Report.result(Tests.no_assert_occurred, not error_tracer.has_asserts)
|
||||
Report.result(Tests.no_error_occurred, not error_tracer.has_errors)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(AtomGPU_BasicLevelSetup_SetsUpLevel)
|
||||
+16
@@ -361,3 +361,19 @@ class EditorEntity:
|
||||
:return: True if "isVisible" is enabled, False otherwise.
|
||||
"""
|
||||
return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id)
|
||||
|
||||
def set_local_uniform_scale(self, scale_float) -> None:
|
||||
"""
|
||||
Sets the "SetLocalUniformScale" value on the entity.
|
||||
:param scale_float: value for "SetLocalUniformScale" to set to.
|
||||
:return: None
|
||||
"""
|
||||
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", self.id, scale_float)
|
||||
|
||||
def set_local_rotation(self, vector3_rotation) -> None:
|
||||
"""
|
||||
Sets the "SetLocalRotation" value on the entity.
|
||||
:param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians).
|
||||
:return: None
|
||||
"""
|
||||
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, vector3_rotation)
|
||||
|
||||
+41
-14
@@ -4,18 +4,18 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
import math
|
||||
import traceback
|
||||
from typing import Callable, Tuple
|
||||
|
||||
import azlmbr
|
||||
import azlmbr.legacy.general as general
|
||||
import azlmbr.debug
|
||||
import json
|
||||
|
||||
import traceback
|
||||
|
||||
from typing import Callable, Tuple
|
||||
|
||||
class FailFast(Exception):
|
||||
"""
|
||||
@@ -127,6 +127,31 @@ class TestHelper:
|
||||
if ret:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def close_error_windows():
|
||||
"""
|
||||
Closes Error Report and Error Log windows that block focus if they are visible.
|
||||
:return: None
|
||||
"""
|
||||
if general.is_pane_visible("Error Report"):
|
||||
general.close_pane("Error Report")
|
||||
if general.is_pane_visible("Error Log"):
|
||||
general.close_pane("Error Log")
|
||||
|
||||
@staticmethod
|
||||
def close_display_helpers():
|
||||
"""
|
||||
Closes helper gizmos, anti-aliasing, and FPS meters.
|
||||
:return: None
|
||||
"""
|
||||
if general.is_helpers_shown():
|
||||
general.toggle_helpers()
|
||||
general.idle_wait(1.0)
|
||||
general.idle_wait(1.0)
|
||||
general.run_console("r_displayInfo=0")
|
||||
general.run_console("r_antialiasingmode=0")
|
||||
general.idle_wait(1.0)
|
||||
|
||||
|
||||
class Timeout:
|
||||
# type: (float) -> None
|
||||
@@ -149,6 +174,7 @@ class Timeout:
|
||||
def timed_out(self):
|
||||
return time.time() > self.die_after
|
||||
|
||||
|
||||
class Report:
|
||||
_results = []
|
||||
_exception = None
|
||||
@@ -290,8 +316,8 @@ class Report:
|
||||
Report.info(" x: {:.2f}, y: {:.2f}, z: {:.2f}".format(vector3.x, vector3.y, vector3.z))
|
||||
if magnitude is not None:
|
||||
Report.info(" magnitude: {:.2f}".format(magnitude))
|
||||
|
||||
|
||||
|
||||
|
||||
'''
|
||||
Utility for scope tracing errors and warnings.
|
||||
Usage:
|
||||
@@ -303,7 +329,7 @@ Usage:
|
||||
|
||||
Report.result(Tests.warnings_not_found_in_section, not section_tracer.has_warnings)
|
||||
|
||||
'''
|
||||
'''
|
||||
class Tracer:
|
||||
def __init__(self):
|
||||
self.warnings = []
|
||||
@@ -349,10 +375,10 @@ class Tracer:
|
||||
self.line = args[1]
|
||||
self.function = args[2]
|
||||
self.message = args[3]
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"Assert: [{self.filename}:{self.function}:{self.line}]: {self.message}"
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"[Assert: {self.message}]"
|
||||
|
||||
@@ -360,21 +386,21 @@ class Tracer:
|
||||
def __init__(self, args):
|
||||
self.window = args[0]
|
||||
self.message = args[1]
|
||||
|
||||
|
||||
def _on_warning(self, args):
|
||||
warningInfo = Tracer.WarningInfo(args)
|
||||
self.warnings.append(warningInfo)
|
||||
Report.info("Tracer caught Warning: %s" % warningInfo.message)
|
||||
self.has_warnings = True
|
||||
return False
|
||||
|
||||
|
||||
def _on_error(self, args):
|
||||
errorInfo = Tracer.ErrorInfo(args)
|
||||
self.errors.append(errorInfo)
|
||||
Report.info("Tracer caught Error: %s" % errorInfo.message)
|
||||
self.has_errors = True
|
||||
return False
|
||||
|
||||
|
||||
def _on_assert(self, args):
|
||||
assertInfo = Tracer.AssertInfo(args)
|
||||
self.asserts.append(assertInfo)
|
||||
@@ -436,6 +462,7 @@ class AngleHelper:
|
||||
|
||||
def vector3_str(vector3):
|
||||
return "(x: {:.2f}, y: {:.2f}, z: {:.2f})".format(vector3.x, vector3.y, vector3.z)
|
||||
|
||||
|
||||
|
||||
def aabb_str(aabb):
|
||||
return "[Min: %s, Max: %s]" % (vector3_str(aabb.min), vector3_str(aabb.max))
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
+1
-1
@@ -121,7 +121,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
},
|
||||
"MaterialLibrary": {
|
||||
"assetId": {
|
||||
"guid": "{62446378-67F8-5E49-AC31-761DD5942695}"
|
||||
"guid": "{7CDF49C3-91A2-5C4E-B642-6D1AEC80E70E}"
|
||||
},
|
||||
"loadBehavior": "QueueLoad",
|
||||
"assetHint": "assets/physics/surfacetypemateriallibrary.physmaterial"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:827a63985273229050bf4f63030bcc666f045091fe81cf8157a9ca23b40074b6
|
||||
size 3214
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d8d24963e6e8765205bc79cbe2304fc39f1245ee75249e2834a71c96c3cab824
|
||||
size 22700
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "ScriptProcessorRule",
|
||||
"scriptFilename": "Editor/Scripts/scene_mesh_to_prefab.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -95,6 +95,7 @@ namespace SandboxEditor
|
||||
cameras.AddCamera(m_firstPersonPanCamera);
|
||||
cameras.AddCamera(m_firstPersonTranslateCamera);
|
||||
cameras.AddCamera(m_firstPersonScrollCamera);
|
||||
cameras.AddCamera(m_firstPersonFocusCamera);
|
||||
cameras.AddCamera(m_pivotCamera);
|
||||
});
|
||||
|
||||
@@ -111,6 +112,7 @@ namespace SandboxEditor
|
||||
viewportId, &AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Events::BeginCursorCapture);
|
||||
}
|
||||
};
|
||||
|
||||
const auto showCursor = [viewportId = m_viewportId]
|
||||
{
|
||||
if (SandboxEditor::CameraCaptureCursorForLook())
|
||||
@@ -172,19 +174,34 @@ namespace SandboxEditor
|
||||
return SandboxEditor::CameraScrollSpeed();
|
||||
};
|
||||
|
||||
const auto pivotFn = []
|
||||
{
|
||||
// use the manipulator transform as the pivot point
|
||||
AZStd::optional<AZ::Transform> entityPivot;
|
||||
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
|
||||
entityPivot, AzToolsFramework::GetEntityContextId(),
|
||||
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
|
||||
|
||||
if (entityPivot.has_value())
|
||||
{
|
||||
return entityPivot->GetTranslation();
|
||||
}
|
||||
|
||||
// otherwise just use the identity
|
||||
return AZ::Vector3::CreateZero();
|
||||
};
|
||||
|
||||
m_firstPersonFocusCamera =
|
||||
AZStd::make_shared<AzFramework::FocusCameraInput>(SandboxEditor::CameraFocusChannelId(), AzFramework::FocusLook);
|
||||
|
||||
m_firstPersonFocusCamera->SetPivotFn(pivotFn);
|
||||
|
||||
m_pivotCamera = AZStd::make_shared<AzFramework::PivotCameraInput>(SandboxEditor::CameraPivotChannelId());
|
||||
|
||||
m_pivotCamera->SetPivotFn(
|
||||
[]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
[pivotFn]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
{
|
||||
// use the manipulator transform as the pivot point
|
||||
AZStd::optional<AZ::Transform> entityPivot;
|
||||
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
|
||||
entityPivot, AzToolsFramework::GetEntityContextId(),
|
||||
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
|
||||
|
||||
// otherwise just use the identity
|
||||
return entityPivot.value_or(AZ::Transform::CreateIdentity()).GetTranslation();
|
||||
return pivotFn();
|
||||
});
|
||||
|
||||
m_pivotRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraPivotLookChannelId());
|
||||
@@ -244,11 +261,17 @@ namespace SandboxEditor
|
||||
return SandboxEditor::CameraPanInvertedY();
|
||||
};
|
||||
|
||||
m_pivotFocusCamera =
|
||||
AZStd::make_shared<AzFramework::FocusCameraInput>(SandboxEditor::CameraFocusChannelId(), AzFramework::FocusPivot);
|
||||
|
||||
m_pivotFocusCamera->SetPivotFn(pivotFn);
|
||||
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotRotateCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotTranslateCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyScrollCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyMoveCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotPanCamera);
|
||||
m_pivotCamera->m_pivotCameras.AddCamera(m_pivotFocusCamera);
|
||||
}
|
||||
|
||||
void EditorModularViewportCameraComposer::OnEditorModularViewportCameraComposerSettingsChanged()
|
||||
@@ -257,12 +280,14 @@ namespace SandboxEditor
|
||||
m_firstPersonTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds);
|
||||
m_firstPersonPanCamera->SetPanInputChannelId(SandboxEditor::CameraFreePanChannelId());
|
||||
m_firstPersonRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraFreeLookChannelId());
|
||||
m_firstPersonFocusCamera->SetFocusInputChannelId(SandboxEditor::CameraFocusChannelId());
|
||||
|
||||
m_pivotCamera->SetPivotInputChannelId(SandboxEditor::CameraPivotChannelId());
|
||||
m_pivotTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds);
|
||||
m_pivotPanCamera->SetPanInputChannelId(SandboxEditor::CameraPivotPanChannelId());
|
||||
m_pivotRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraPivotLookChannelId());
|
||||
m_pivotDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraPivotDollyChannelId());
|
||||
m_pivotFocusCamera->SetFocusInputChannelId(SandboxEditor::CameraFocusChannelId());
|
||||
}
|
||||
|
||||
void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId)
|
||||
|
||||
@@ -42,12 +42,14 @@ namespace SandboxEditor
|
||||
AZStd::shared_ptr<AzFramework::PanCameraInput> m_firstPersonPanCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
|
||||
AZStd::shared_ptr<AzFramework::ScrollTranslationCameraInput> m_firstPersonScrollCamera;
|
||||
AZStd::shared_ptr<AzFramework::FocusCameraInput> m_firstPersonFocusCamera;
|
||||
AZStd::shared_ptr<AzFramework::PivotCameraInput> m_pivotCamera;
|
||||
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_pivotRotateCamera;
|
||||
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_pivotTranslateCamera;
|
||||
AZStd::shared_ptr<AzFramework::PivotDollyScrollCameraInput> m_pivotDollyScrollCamera;
|
||||
AZStd::shared_ptr<AzFramework::PivotDollyMotionCameraInput> m_pivotDollyMoveCamera;
|
||||
AZStd::shared_ptr<AzFramework::PanCameraInput> m_pivotPanCamera;
|
||||
AZStd::shared_ptr<AzFramework::FocusCameraInput> m_pivotFocusCamera;
|
||||
|
||||
AzFramework::ViewportId m_viewportId;
|
||||
};
|
||||
|
||||
@@ -91,7 +91,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
->Field("FreePan", &CameraInputSettings::m_freePanChannelId)
|
||||
->Field("PivotLook", &CameraInputSettings::m_pivotLookChannelId)
|
||||
->Field("PivotDolly", &CameraInputSettings::m_pivotDollyChannelId)
|
||||
->Field("PivotPan", &CameraInputSettings::m_pivotPanChannelId);
|
||||
->Field("PivotPan", &CameraInputSettings::m_pivotPanChannelId)
|
||||
->Field("Focus", &CameraInputSettings::m_focusChannelId);
|
||||
|
||||
serialize.Class<CEditorPreferencesPage_ViewportCamera>()
|
||||
->Version(1)
|
||||
@@ -206,14 +207,17 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotPanChannelId, "Pivot Pan",
|
||||
"Key/button to begin camera pivot pan")
|
||||
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames)
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_focusChannelId, "Focus", "Key/button to focus camera pivot")
|
||||
->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames);
|
||||
|
||||
editContext->Class<CEditorPreferencesPage_ViewportCamera>("Viewport Preferences", "Viewport Preferences")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20))
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraMovementSettings,
|
||||
"Camera Movement Settings", "Camera Movement Settings")
|
||||
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraMovementSettings, "Camera Movement Settings",
|
||||
"Camera Movement Settings")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_ViewportCamera::m_cameraInputSettings, "Camera Input Settings",
|
||||
"Camera Input Settings");
|
||||
@@ -281,6 +285,7 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
|
||||
SandboxEditor::SetCameraPivotLookChannelId(m_cameraInputSettings.m_pivotLookChannelId);
|
||||
SandboxEditor::SetCameraPivotDollyChannelId(m_cameraInputSettings.m_pivotDollyChannelId);
|
||||
SandboxEditor::SetCameraPivotPanChannelId(m_cameraInputSettings.m_pivotPanChannelId);
|
||||
SandboxEditor::SetCameraFocusChannelId(m_cameraInputSettings.m_focusChannelId);
|
||||
|
||||
SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Broadcast(
|
||||
&SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Events::OnEditorModularViewportCameraComposerSettingsChanged);
|
||||
@@ -316,4 +321,5 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
|
||||
m_cameraInputSettings.m_pivotLookChannelId = SandboxEditor::CameraPivotLookChannelId().GetName();
|
||||
m_cameraInputSettings.m_pivotDollyChannelId = SandboxEditor::CameraPivotDollyChannelId().GetName();
|
||||
m_cameraInputSettings.m_pivotPanChannelId = SandboxEditor::CameraPivotPanChannelId().GetName();
|
||||
m_cameraInputSettings.m_focusChannelId = SandboxEditor::CameraFocusChannelId().GetName();
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ private:
|
||||
AZStd::string m_pivotLookChannelId;
|
||||
AZStd::string m_pivotDollyChannelId;
|
||||
AZStd::string m_pivotPanChannelId;
|
||||
AZStd::string m_focusChannelId;
|
||||
};
|
||||
|
||||
CameraMovementSettings m_cameraMovementSettings;
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace SandboxEditor
|
||||
constexpr AZStd::string_view CameraPivotLookIdSetting = "/Amazon/Preferences/Editor/Camera/PivotLookId";
|
||||
constexpr AZStd::string_view CameraPivotDollyIdSetting = "/Amazon/Preferences/Editor/Camera/PivotDollyId";
|
||||
constexpr AZStd::string_view CameraPivotPanIdSetting = "/Amazon/Preferences/Editor/Camera/PivotPanId";
|
||||
constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId";
|
||||
|
||||
template<typename T>
|
||||
void SetRegistry(const AZStd::string_view setting, T&& value)
|
||||
@@ -462,4 +463,14 @@ namespace SandboxEditor
|
||||
{
|
||||
SetRegistry(CameraPivotPanIdSetting, cameraPivotPanId);
|
||||
}
|
||||
|
||||
AzFramework::InputChannelId CameraFocusChannelId()
|
||||
{
|
||||
return AzFramework::InputChannelId(GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str());
|
||||
}
|
||||
|
||||
void SetCameraFocusChannelId(AZStd::string_view cameraFocusId)
|
||||
{
|
||||
SetRegistry(CameraFocusIdSetting, cameraFocusId);
|
||||
}
|
||||
} // namespace SandboxEditor
|
||||
|
||||
@@ -136,4 +136,7 @@ namespace SandboxEditor
|
||||
|
||||
SANDBOX_API AzFramework::InputChannelId CameraPivotPanChannelId();
|
||||
SANDBOX_API void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId);
|
||||
|
||||
SANDBOX_API AzFramework::InputChannelId CameraFocusChannelId();
|
||||
SANDBOX_API void SetCameraFocusChannelId(AZStd::string_view cameraFocusId);
|
||||
} // namespace SandboxEditor
|
||||
|
||||
@@ -135,18 +135,12 @@ namespace AZ
|
||||
{
|
||||
stringLength = strlen(uuidString);
|
||||
}
|
||||
if (stringLength > MaxPermissiveStringSize)
|
||||
{
|
||||
if (!skipWarnings)
|
||||
{
|
||||
AZ_Warning("Math", false, "Can't create UUID from string length %zu over maximum %zu", stringLength, MaxPermissiveStringSize);
|
||||
}
|
||||
return Uuid::CreateNull();
|
||||
}
|
||||
|
||||
size_t newLength{ 0 };
|
||||
char createString[MaxPermissiveStringSize];
|
||||
|
||||
for (size_t curPos = 0; curPos < stringLength; ++curPos)
|
||||
// Loop until we get to the end of the string OR stop once we've accumulated a full UUID string worth of data
|
||||
for (size_t curPos = 0; curPos < stringLength && newLength < ValidUuidStringLength; ++curPos)
|
||||
{
|
||||
char curChar = uuidString[curPos];
|
||||
switch (curChar)
|
||||
|
||||
@@ -42,8 +42,9 @@ namespace AZ
|
||||
//VER_AZ_RANDOM_CRC32 = 6, // 0 1 1 0
|
||||
};
|
||||
|
||||
static constexpr int ValidUuidStringLength = 32; /// Number of characters (data only, no extra formatting) in a valid UUID string
|
||||
static const size_t MaxStringBuffer = 39; /// 32 Uuid + 4 dashes + 2 brackets + 1 terminate
|
||||
|
||||
|
||||
Uuid() {}
|
||||
Uuid(const char* string, size_t stringLength = 0) { *this = CreateString(string, stringLength); }
|
||||
|
||||
|
||||
@@ -247,4 +247,27 @@ namespace UnitTest
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr1);
|
||||
EXPECT_EQ(left, right);
|
||||
}
|
||||
|
||||
TEST_F(UuidTests, CreateStringPermissive_StringWithExtraData_Succeeds)
|
||||
{
|
||||
const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}";
|
||||
Uuid left = Uuid::CreateString(uuidStr);
|
||||
|
||||
const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World";
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr);
|
||||
EXPECT_EQ(left, right);
|
||||
|
||||
}
|
||||
|
||||
TEST_F(UuidTests, CreateStringPermissive_StringWithLotsOfExtraData_Succeeds)
|
||||
{
|
||||
const char uuidStr[] = "{34D44249-E599-4B30-811F-4215C2DEA269}";
|
||||
Uuid left = Uuid::CreateString(uuidStr);
|
||||
|
||||
const char permissiveStr[] = "0x34D44249-0xE5994B30-0x811F4215-0xC2DEA269 Hello World this is a really long string "
|
||||
"with lots of extra data to make sure we can parse a long string without failing as long as the uuid is in "
|
||||
"the beginning of the string then we should succeed";
|
||||
Uuid right = Uuid::CreateStringPermissive(permissiveStr);
|
||||
EXPECT_EQ(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,8 @@ namespace AzFramework
|
||||
behaviorContext->Class<BehaviorEntity>("Entity")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::BehaviorEntityScriptConstructor)
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Constructor()
|
||||
->Constructor<AZ::EntityId>()
|
||||
->Constructor<AZ::Entity*>()
|
||||
|
||||
@@ -623,15 +623,24 @@ namespace AzFramework
|
||||
{
|
||||
Camera nextCamera = targetCamera;
|
||||
|
||||
const auto pivotDirection = targetCamera.m_offset.GetNormalized();
|
||||
nextCamera.m_offset -= pivotDirection * delta;
|
||||
const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset);
|
||||
const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot);
|
||||
|
||||
const auto minDistance = 0.01f;
|
||||
if (distance < minDistance || pivotDot < 0.0f)
|
||||
// handle case where pivot and offset may be the same to begin with
|
||||
// choose negative y-axis for offset to default to moving the camera backwards from the pivot (standard centered pivot behavior)
|
||||
const auto pivotDirection = [&targetCamera]
|
||||
{
|
||||
nextCamera.m_offset = pivotDirection * minDistance;
|
||||
if (const auto offsetLength = targetCamera.m_offset.GetLength(); AZ::IsCloseMag(offsetLength, 0.0f))
|
||||
{
|
||||
return -AZ::Vector3::CreateAxisY();
|
||||
}
|
||||
else
|
||||
{
|
||||
return targetCamera.m_offset / offsetLength;
|
||||
}
|
||||
}();
|
||||
|
||||
nextCamera.m_offset -= pivotDirection * delta;
|
||||
if (pivotDirection.Dot(nextCamera.m_offset) < 0.0f)
|
||||
{
|
||||
nextCamera.m_offset = pivotDirection * 0.001f;
|
||||
}
|
||||
|
||||
return nextCamera;
|
||||
@@ -771,6 +780,73 @@ namespace AzFramework
|
||||
return camera;
|
||||
}
|
||||
|
||||
FocusCameraInput::FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn)
|
||||
: m_focusChannelId(focusChannelId)
|
||||
, m_offsetFn(offsetFn)
|
||||
{
|
||||
}
|
||||
|
||||
bool FocusCameraInput::HandleEvents(
|
||||
const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
|
||||
{
|
||||
if (const auto* input = AZStd::get_if<DiscreteInputEvent>(&event))
|
||||
{
|
||||
if (input->m_channelId == m_focusChannelId && input->m_state == InputChannel::State::Began)
|
||||
{
|
||||
BeginActivation();
|
||||
}
|
||||
}
|
||||
|
||||
return !Idle();
|
||||
}
|
||||
|
||||
Camera FocusCameraInput::StepCamera(
|
||||
const Camera& targetCamera,
|
||||
[[maybe_unused]] const ScreenVector& cursorDelta,
|
||||
[[maybe_unused]] float scrollDelta,
|
||||
[[maybe_unused]] float deltaTime)
|
||||
{
|
||||
if (Beginning())
|
||||
{
|
||||
// as the camera starts, record the camera we would like to end up as
|
||||
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
|
||||
const auto angles =
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
|
||||
m_nextCamera.m_pitch = angles.GetX();
|
||||
m_nextCamera.m_yaw = angles.GetZ();
|
||||
m_nextCamera.m_pivot = targetCamera.m_pivot;
|
||||
}
|
||||
|
||||
// end the behavior when the camera is in alignment
|
||||
if (AZ::IsCloseMag(targetCamera.m_pitch, m_nextCamera.m_pitch) && AZ::IsCloseMag(targetCamera.m_yaw, m_nextCamera.m_yaw))
|
||||
{
|
||||
EndActivation();
|
||||
}
|
||||
|
||||
return m_nextCamera;
|
||||
}
|
||||
|
||||
void FocusCameraInput::SetPivotFn(PivotFn pivotFn)
|
||||
{
|
||||
m_pivotFn = AZStd::move(pivotFn);
|
||||
}
|
||||
|
||||
void FocusCameraInput::SetFocusInputChannelId(const InputChannelId& focusChannelId)
|
||||
{
|
||||
m_focusChannelId = focusChannelId;
|
||||
}
|
||||
|
||||
bool CustomCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta)
|
||||
{
|
||||
return m_handleEventsFn(*this, event, cursorDelta, scrollDelta);
|
||||
}
|
||||
|
||||
Camera CustomCameraInput::StepCamera(
|
||||
const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
|
||||
{
|
||||
return m_stepCameraFn(*this, targetCamera, cursorDelta, scrollDelta, deltaTime);
|
||||
}
|
||||
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize)
|
||||
{
|
||||
const auto& inputChannelId = inputChannel.GetInputChannelId();
|
||||
|
||||
@@ -617,6 +617,60 @@ namespace AzFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Callback to use for FocusCameraInput when a free look camera is being used.
|
||||
//! @note This is when offset is zero.
|
||||
inline AZ::Vector3 FocusLook(float)
|
||||
{
|
||||
return AZ::Vector3::CreateZero();
|
||||
}
|
||||
|
||||
//! Callback to use for FocusCameraInput when a pivot camera is being used.
|
||||
//! @note This is when offset is non zero.
|
||||
inline AZ::Vector3 FocusPivot(const float length)
|
||||
{
|
||||
return AZ::Vector3::CreateAxisY(-length);
|
||||
}
|
||||
|
||||
using FocusOffsetFn = AZStd::function<AZ::Vector3(float)>;
|
||||
|
||||
//! A focus behavior to align the camera view to the position returned by the pivot function.
|
||||
//! @note This only alters the camera orientation, the translation is unaffected.
|
||||
class FocusCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
using PivotFn = AZStd::function<AZ::Vector3()>;
|
||||
|
||||
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
|
||||
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
|
||||
|
||||
//! Override the default behavior for how a pivot point is calculated.
|
||||
void SetPivotFn(PivotFn pivotFn);
|
||||
|
||||
void SetFocusInputChannelId(const InputChannelId& focusChannelId);
|
||||
|
||||
private:
|
||||
InputChannelId m_focusChannelId; //!< Input channel to begin the focus camera input.
|
||||
Camera m_nextCamera;
|
||||
PivotFn m_pivotFn;
|
||||
FocusOffsetFn m_offsetFn;
|
||||
};
|
||||
|
||||
//! Provides a CameraInput type that can be implemented without needing to create a new type deriving from CameraInput.
|
||||
//! This can be very useful for specific use cases that are less generally applicable.
|
||||
class CustomCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
// CameraInput overrides ...
|
||||
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;
|
||||
Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override;
|
||||
|
||||
AZStd::function<bool(CameraInput&, const InputEvent&, const ScreenVector&, float)> m_handleEventsFn;
|
||||
AZStd::function<Camera(CameraInput&, const Camera&, const ScreenVector&, float, float)> m_stepCameraFn;
|
||||
};
|
||||
|
||||
//! Map from a generic InputChannel event to a camera specific InputEvent.
|
||||
InputEvent BuildInputEvent(const InputChannel& inputChannel, const WindowSize& windowSize);
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiSystemComponent.h>
|
||||
#include <AzToolsFramework/Undo/UndoCacheInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <QtWidgets/QMessageBox>
|
||||
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
|
||||
@@ -271,7 +272,8 @@ namespace AzToolsFramework
|
||||
azrtti_typeid<AzToolsFramework::EditorInteractionSystemComponent>(),
|
||||
azrtti_typeid<Components::EditorEntitySearchComponent>(),
|
||||
azrtti_typeid<Components::EditorIntersectorComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::SliceRequestComponent>()
|
||||
azrtti_typeid<AzToolsFramework::SliceRequestComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::EntityUtilityComponent>()
|
||||
});
|
||||
|
||||
return components;
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
|
||||
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
|
||||
|
||||
AZ_DEFINE_BUDGET(AzToolsFramework);
|
||||
|
||||
@@ -71,6 +72,7 @@ namespace AzToolsFramework
|
||||
Components::EditorSelectionAccentSystemComponent::CreateDescriptor(),
|
||||
EditorEntityContextComponent::CreateDescriptor(),
|
||||
EditorEntityFixupComponent::CreateDescriptor(),
|
||||
EntityUtilityComponent::CreateDescriptor(),
|
||||
ContainerEntitySystemComponent::CreateDescriptor(),
|
||||
FocusModeSystemComponent::CreateDescriptor(),
|
||||
SliceMetadataEntityContextComponent::CreateDescriptor(),
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* 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 <sstream>
|
||||
#include <AzCore/JSON/rapidjson.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializationSettings.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzFramework/Entity/EntityContext.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
|
||||
#include <Entity/EditorEntityContextBus.h>
|
||||
#include <rapidjson/document.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
void ComponentDetails::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<ComponentDetails>()
|
||||
->Field("TypeInfo", &ComponentDetails::m_typeInfo)
|
||||
->Field("BaseClasses", &ComponentDetails::m_baseClasses);
|
||||
|
||||
serializeContext->RegisterGenericType<AZStd::vector<ComponentDetails>>();
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<ComponentDetails>()
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Property("TypeInfo", BehaviorValueProperty(&ComponentDetails::m_typeInfo))
|
||||
->Property("BaseClasses", BehaviorValueProperty(&ComponentDetails::m_baseClasses))
|
||||
->Method("__repr__", [](const ComponentDetails& obj)
|
||||
{
|
||||
std::ostringstream result;
|
||||
bool first = true;
|
||||
|
||||
for (const auto& baseClass : obj.m_baseClasses)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
result << ", ";
|
||||
}
|
||||
|
||||
first = false;
|
||||
result << baseClass.c_str();
|
||||
}
|
||||
|
||||
return AZStd::string::format("%s, Base Classes: <%s>", obj.m_typeInfo.c_str(), result.str().c_str());
|
||||
})
|
||||
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString);
|
||||
}
|
||||
}
|
||||
|
||||
AZ::EntityId EntityUtilityComponent::CreateEditorReadyEntity(const AZStd::string& entityName)
|
||||
{
|
||||
auto* newEntity = m_entityContext->CreateEntity(entityName.c_str());
|
||||
|
||||
if (!newEntity)
|
||||
{
|
||||
AZ_Error("EditorEntityUtility", false, "Failed to create new entity %s", entityName.c_str());
|
||||
return AZ::EntityId();
|
||||
}
|
||||
|
||||
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
|
||||
&AzToolsFramework::EditorEntityContextRequestBus::Events::AddRequiredComponents, *newEntity);
|
||||
|
||||
newEntity->Init();
|
||||
auto newEntityId = newEntity->GetId();
|
||||
|
||||
m_createdEntities.emplace_back(newEntityId);
|
||||
|
||||
return newEntityId;
|
||||
}
|
||||
|
||||
AZ::TypeId GetComponentTypeIdFromName(const AZStd::string& typeName)
|
||||
{
|
||||
// Try to create a TypeId first. We won't show any warnings if this fails as the input might be a class name instead
|
||||
AZ::TypeId typeId = AZ::TypeId::CreateStringPermissive(typeName.data());
|
||||
|
||||
// If the typeId is null, try a lookup by class name
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
auto typeNameCrc = AZ::Crc32(typeName.data());
|
||||
auto typeUuidList = serializeContext->FindClassId(typeNameCrc);
|
||||
|
||||
// TypeId is invalid or class name is invalid
|
||||
if (typeUuidList.empty())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Provided type %s is either an invalid TypeId or does not match any class names", typeName.c_str());
|
||||
return AZ::TypeId::CreateNull();
|
||||
}
|
||||
|
||||
typeId = typeUuidList[0];
|
||||
}
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
AZ::Component* FindComponentHelper(AZ::EntityId entityId, const AZ::TypeId& typeId, AZ::ComponentId componentId, bool createComponent = false)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
|
||||
|
||||
if (!entity)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Invalid entityId %s", entityId.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AZ::Component* component = nullptr;
|
||||
if (componentId != AZ::InvalidComponentId)
|
||||
{
|
||||
component = entity->FindComponent(componentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
component = entity->FindComponent(typeId);
|
||||
}
|
||||
|
||||
if (!component && createComponent)
|
||||
{
|
||||
component = entity->CreateComponent(typeId);
|
||||
}
|
||||
|
||||
if (!component)
|
||||
{
|
||||
AZ_Error(
|
||||
"EntityUtilityComponent", false, "Failed to find component (%s) on entity %s (%s)",
|
||||
componentId != AZ::InvalidComponentId ? AZStd::to_string(componentId).c_str()
|
||||
: typeId.ToString<AZStd::string>().c_str(),
|
||||
entityId.ToString().c_str(),
|
||||
entity->GetName().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
AzFramework::BehaviorComponentId EntityUtilityComponent::GetOrAddComponentByTypeName(AZ::EntityId entityId, const AZStd::string& typeName)
|
||||
{
|
||||
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
|
||||
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
return AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
|
||||
}
|
||||
|
||||
AZ::Component* component = FindComponentHelper(entityId, typeId, AZ::InvalidComponentId, true);
|
||||
|
||||
return component ? AzFramework::BehaviorComponentId(component->GetId()) :
|
||||
AzFramework::BehaviorComponentId(AZ::InvalidComponentId);
|
||||
}
|
||||
|
||||
bool EntityUtilityComponent::UpdateComponentForEntity(AZ::EntityId entityId, AzFramework::BehaviorComponentId componentId, const AZStd::string& json)
|
||||
{
|
||||
if (!componentId.IsValid())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Invalid componentId passed to UpdateComponentForEntity");
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::Component* component = FindComponentHelper(entityId, AZ::TypeId::CreateNull(), componentId);
|
||||
|
||||
if (!component)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using namespace AZ::JsonSerializationResult;
|
||||
|
||||
AZ::JsonDeserializerSettings settings = AZ::JsonDeserializerSettings{};
|
||||
settings.m_reporting = []([[maybe_unused]] AZStd::string_view message, ResultCode result, AZStd::string_view) -> auto
|
||||
{
|
||||
if (result.GetProcessing() == Processing::Halted)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "JSON %s\n", message.data());
|
||||
}
|
||||
else if (result.GetOutcome() > Outcomes::PartialDefaults)
|
||||
{
|
||||
AZ_Warning("EntityUtilityComponent", false, "JSON %s\n", message.data());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
rapidjson::Document doc;
|
||||
doc.Parse<rapidjson::kParseCommentsFlag>(json.data(), json.size());
|
||||
ResultCode resultCode = AZ::JsonSerialization::Load(*component, doc, settings);
|
||||
|
||||
return resultCode.GetProcessing() != Processing::Halted;
|
||||
}
|
||||
|
||||
AZStd::string EntityUtilityComponent::GetComponentDefaultJson(const AZStd::string& typeName)
|
||||
{
|
||||
AZ::TypeId typeId = GetComponentTypeIdFromName(typeName);
|
||||
|
||||
if (typeId.IsNull())
|
||||
{
|
||||
// GetComponentTypeIdFromName already does error handling
|
||||
return "";
|
||||
}
|
||||
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(typeId);
|
||||
|
||||
if (!classData)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to find ClassData for typeId %s (%s)", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
void* component = classData->m_factory->Create("Component");
|
||||
rapidjson::Document document;
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_keepDefaults = true;
|
||||
|
||||
auto resultCode = AZ::JsonSerialization::Store(document, document.GetAllocator(), component, nullptr, typeId, settings);
|
||||
|
||||
// Clean up the allocated component ASAP, we don't need it anymore
|
||||
classData->m_factory->Destroy(component);
|
||||
|
||||
if (resultCode.GetProcessing() == AZ::JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to serialize component to json (%s): %s",
|
||||
typeName.c_str(), resultCode.ToString(typeName).c_str())
|
||||
return "";
|
||||
}
|
||||
|
||||
AZStd::string jsonString;
|
||||
AZ::Outcome<void, AZStd::string> outcome = AZ::JsonSerializationUtils::WriteJsonString(document, jsonString);
|
||||
|
||||
if (!outcome.IsSuccess())
|
||||
{
|
||||
AZ_Error("EntityUtilityComponent", false, "Failed to write component json to string: %s", outcome.GetError().c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
return jsonString;
|
||||
}
|
||||
|
||||
AZStd::vector<ComponentDetails> EntityUtilityComponent::FindMatchingComponents(const AZStd::string& searchTerm)
|
||||
{
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
|
||||
if (m_typeInfo.empty())
|
||||
{
|
||||
serializeContext->EnumerateDerived<AZ::Component>(
|
||||
[this, serializeContext](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*typeId*/)
|
||||
{
|
||||
auto& typeInfo = m_typeInfo.emplace_back(classData->m_typeId, classData->m_name, AZStd::vector<AZStd::string>{});
|
||||
|
||||
serializeContext->EnumerateBase(
|
||||
[&typeInfo](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&)
|
||||
{
|
||||
if (classData)
|
||||
{
|
||||
AZStd::get<2>(typeInfo).emplace_back(classData->m_name);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
classData->m_typeId);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
AZStd::vector<ComponentDetails> matches;
|
||||
|
||||
for (const auto& [typeId, typeName, baseClasses] : m_typeInfo)
|
||||
{
|
||||
if (AZStd::wildcard_match(searchTerm, typeName))
|
||||
{
|
||||
ComponentDetails details;
|
||||
details.m_typeInfo = AZStd::string::format("%s %s", typeId.ToString<AZStd::string>().c_str(), typeName.c_str());
|
||||
details.m_baseClasses = baseClasses;
|
||||
|
||||
matches.emplace_back(AZStd::move(details));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::ResetEntityContext()
|
||||
{
|
||||
for (AZ::EntityId entityId : m_createdEntities)
|
||||
{
|
||||
m_entityContext->DestroyEntityById(entityId);
|
||||
}
|
||||
|
||||
m_createdEntities.clear();
|
||||
m_entityContext->ResetContext();
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
ComponentDetails::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<EntityUtilityComponent, AZ::Component>();
|
||||
}
|
||||
|
||||
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty("InvalidComponentId", BehaviorConstant(AZ::InvalidComponentId))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Entity")
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity");
|
||||
|
||||
behaviorContext->EBus<EntityUtilityBus>("EntityUtilityBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Entity")
|
||||
->Attribute(AZ::Script::Attributes::Module, "entity")
|
||||
->Event("CreateEditorReadyEntity", &EntityUtilityBus::Events::CreateEditorReadyEntity)
|
||||
->Event("GetOrAddComponentByTypeName", &EntityUtilityBus::Events::GetOrAddComponentByTypeName)
|
||||
->Event("UpdateComponentForEntity", &EntityUtilityBus::Events::UpdateComponentForEntity)
|
||||
->Event("FindMatchingComponents", &EntityUtilityBus::Events::FindMatchingComponents)
|
||||
->Event("GetComponentDefaultJson", &EntityUtilityBus::Events::GetComponentDefaultJson)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Activate()
|
||||
{
|
||||
m_entityContext = AZStd::make_unique<AzFramework::EntityContext>(UtilityEntityContextId);
|
||||
m_entityContext->InitContext();
|
||||
EntityUtilityBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void EntityUtilityComponent::Deactivate()
|
||||
{
|
||||
EntityUtilityBus::Handler::BusDisconnect();
|
||||
m_entityContext = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 <AzCore/Component/Component.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorDisabledCompositionBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorPendingCompositionBus.h>
|
||||
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzFramework/Entity/BehaviorEntity.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
struct ComponentDetails
|
||||
{
|
||||
AZ_TYPE_INFO(AzToolsFramework::ComponentDetails, "{107D8379-4AD4-4547-BEE1-184B120F23E9}");
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
AZStd::string m_typeInfo;
|
||||
AZStd::vector<AZStd::string> m_baseClasses;
|
||||
};
|
||||
|
||||
// This ebus is intended to provide behavior-context friendly APIs to create and manage entities
|
||||
struct EntityUtilityTraits : AZ::EBusTraits
|
||||
{
|
||||
AZ_RTTI(AzToolsFramework::EntityUtilityTraits, "{A6305CAE-C825-43F9-A44D-E503910912AF}");
|
||||
|
||||
virtual ~EntityUtilityTraits() = default;
|
||||
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
|
||||
// Creates an entity with the default editor components attached and initializes the entity
|
||||
virtual AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) = 0;
|
||||
|
||||
virtual AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) = 0;
|
||||
|
||||
virtual bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) = 0;
|
||||
|
||||
// Gets a JSON string containing describing the default serialization state of the specified component
|
||||
virtual AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) = 0;
|
||||
|
||||
// Returns a list of matching component type names. Supports wildcard search terms
|
||||
virtual AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) = 0;
|
||||
|
||||
virtual void ResetEntityContext() = 0;
|
||||
};
|
||||
|
||||
using EntityUtilityBus = AZ::EBus<EntityUtilityTraits>;
|
||||
|
||||
struct EntityUtilityComponent : AZ::Component
|
||||
, EntityUtilityBus::Handler
|
||||
{
|
||||
inline const static AZ::Uuid UtilityEntityContextId = AZ::Uuid("{9C277B88-E79E-4F8A-BAFF-A4C175BD565F}");
|
||||
|
||||
AZ_COMPONENT(EntityUtilityComponent, "{47205907-A0EA-4FFF-A620-04D20C04A379}");
|
||||
|
||||
AZ::EntityId CreateEditorReadyEntity(const AZStd::string& entityName) override;
|
||||
AzFramework::BehaviorComponentId GetOrAddComponentByTypeName(AZ::EntityId entity, const AZStd::string& typeName) override;
|
||||
bool UpdateComponentForEntity(AZ::EntityId entity, AzFramework::BehaviorComponentId component, const AZStd::string& json) override;
|
||||
AZStd::string GetComponentDefaultJson(const AZStd::string& typeName) override;
|
||||
AZStd::vector<ComponentDetails> FindMatchingComponents(const AZStd::string& searchTerm) override;
|
||||
void ResetEntityContext() override;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
protected:
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
// Our own entity context. This API is intended mostly for use in Asset Builders where there is no editor context
|
||||
// Additionally, an entity context is needed when using the Behavior Entity class
|
||||
AZStd::unique_ptr<AzFramework::EntityContext> m_entityContext;
|
||||
|
||||
// TypeId, TypeName, Vector<BaseClassName>
|
||||
AZStd::vector<AZStd::tuple<AZ::TypeId, AZStd::string, AZStd::vector<AZStd::string>>> m_typeInfo;
|
||||
|
||||
// Keep track of the entities we create so they can be reset
|
||||
AZStd::vector<AZ::EntityId> m_createdEntities;
|
||||
};
|
||||
}; // namespace AzToolsFramework
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
|
||||
@@ -50,17 +50,19 @@ namespace AzToolsFramework
|
||||
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ_Assert(settingsRegistry, "Settings registry is not set");
|
||||
|
||||
|
||||
[[maybe_unused]] bool result =
|
||||
settingsRegistry->Get(m_projectPathWithOsSeparator.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
|
||||
AZ_Warning("Prefab", result, "Couldn't retrieve project root path");
|
||||
m_projectPathWithSlashSeparator = AZ::IO::Path(m_projectPathWithOsSeparator.Native(), '/').MakePreferred();
|
||||
|
||||
AZ::Interface<PrefabLoaderInterface>::Register(this);
|
||||
m_scriptingPrefabLoader.Connect(this);
|
||||
}
|
||||
|
||||
void PrefabLoader::UnregisterPrefabLoaderInterface()
|
||||
{
|
||||
m_scriptingPrefabLoader.Disconnect();
|
||||
AZ::Interface<PrefabLoaderInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
@@ -568,7 +570,7 @@ namespace AzToolsFramework
|
||||
(pathStr.find_first_of(AZ_FILESYSTEM_INVALID_CHARACTERS) == AZStd::string::npos) &&
|
||||
(pathStr.back() != '\\' && pathStr.back() != '/');
|
||||
}
|
||||
|
||||
|
||||
AZ::IO::Path PrefabLoader::GetFullPath(AZ::IO::PathView path)
|
||||
{
|
||||
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred();
|
||||
@@ -596,26 +598,38 @@ namespace AzToolsFramework
|
||||
{
|
||||
// The asset system provided us with a valid root folder and relative path, so return it.
|
||||
fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath;
|
||||
return fullPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
|
||||
|
||||
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
|
||||
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
|
||||
// a unit test, so just execute the fallback logic without an error.
|
||||
[[maybe_unused]] bool assetProcessorReady = false;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
|
||||
AZ_STRING_ARG(path.Native()));
|
||||
|
||||
// If a relative path was passed in, make it relative to the project root.
|
||||
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
|
||||
// attempt to find the absolute from the Cache folder
|
||||
AZStd::string assetRootFolder;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(assetRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
|
||||
}
|
||||
fullPath = AZ::IO::Path(assetRootFolder) / path;
|
||||
if (fullPath.IsAbsolute() && AZ::IO::SystemFile::Exists(fullPath.c_str()))
|
||||
{
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
|
||||
|
||||
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
|
||||
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
|
||||
// a unit test, so just execute the fallback logic without an error.
|
||||
[[maybe_unused]] bool assetProcessorReady = false;
|
||||
AzFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
|
||||
AZ_STRING_ARG(path.Native()));
|
||||
|
||||
// If a relative path was passed in, make it relative to the project root.
|
||||
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <Prefab/ScriptingPrefabLoader.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -114,6 +115,7 @@ namespace AzToolsFramework
|
||||
void SetSaveAllPrefabsPreference(SaveAllPrefabsPreference saveAllPrefabsPreference) override;
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Copies the template dom provided and manipulates it into the proper format to be saved to disk.
|
||||
* @param templateRef The template whose dom we want to transform into the proper format to be saved to disk.
|
||||
@@ -177,6 +179,7 @@ namespace AzToolsFramework
|
||||
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> StoreTemplateIntoFileFormat(TemplateId templateId);
|
||||
|
||||
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
|
||||
ScriptingPrefabLoader m_scriptingPrefabLoader;
|
||||
AZ::IO::Path m_projectPathWithOsSeparator;
|
||||
AZ::IO::Path m_projectPathWithSlashSeparator;
|
||||
};
|
||||
|
||||
@@ -99,7 +99,6 @@ namespace AzToolsFramework
|
||||
// Generates a new path
|
||||
static AZ::IO::Path GeneratePath();
|
||||
};
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
// Ebus for script-friendly APIs for the prefab loader
|
||||
struct PrefabLoaderScriptingTraits : AZ::EBusTraits
|
||||
{
|
||||
AZ_TYPE_INFO(PrefabLoaderScriptingTraits, "{C344B7D8-8299-48C9-8450-26E1332EA011}");
|
||||
|
||||
virtual ~PrefabLoaderScriptingTraits() = default;
|
||||
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
|
||||
/**
|
||||
* Saves a Prefab Template into the provided output string.
|
||||
* Converts Prefab Template form into .prefab form by collapsing nested Template info
|
||||
* into a source path and patches.
|
||||
* @param templateId Id of the template to be saved
|
||||
* @return Will contain the serialized template json on success
|
||||
*/
|
||||
virtual AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) = 0;
|
||||
};
|
||||
|
||||
using PrefabLoaderScriptingBus = AZ::EBus<PrefabLoaderScriptingTraits>;
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
|
||||
@@ -36,12 +37,14 @@ namespace AzToolsFramework
|
||||
m_instanceToTemplatePropagator.RegisterInstanceToTemplateInterface();
|
||||
m_prefabPublicHandler.RegisterPrefabPublicHandlerInterface();
|
||||
m_prefabPublicRequestHandler.Connect();
|
||||
m_prefabSystemScriptingHandler.Connect(this);
|
||||
AZ::SystemTickBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::Deactivate()
|
||||
{
|
||||
AZ::SystemTickBus::Handler::BusDisconnect();
|
||||
m_prefabSystemScriptingHandler.Disconnect();
|
||||
m_prefabPublicRequestHandler.Disconnect();
|
||||
m_prefabPublicHandler.UnregisterPrefabPublicHandlerInterface();
|
||||
m_instanceToTemplatePropagator.UnregisterInstanceToTemplateInterface();
|
||||
@@ -58,13 +61,24 @@ namespace AzToolsFramework
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
|
||||
PrefabPublicRequestHandler::Reflect(context);
|
||||
PrefabLoader::Reflect(context);
|
||||
PrefabSystemScriptingHandler::Reflect(context);
|
||||
|
||||
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serialize)
|
||||
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serialize->Class<PrefabSystemComponent, AZ::Component>()->Version(1);
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
|
||||
behaviorContext->EBus<PrefabLoaderScriptingBus>("PrefabLoaderScriptingBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab")
|
||||
->Event("SaveTemplateToString", &PrefabLoaderScriptingBus::Events::SaveTemplateToString);
|
||||
;
|
||||
}
|
||||
|
||||
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
|
||||
if (jsonRegistration)
|
||||
{
|
||||
@@ -145,7 +159,7 @@ namespace AzToolsFramework
|
||||
newInstance->SetTemplateId(newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
#include <Prefab/PrefabSystemScriptingHandler.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -219,7 +220,7 @@ namespace AzToolsFramework
|
||||
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
|
||||
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr,
|
||||
InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override;
|
||||
|
||||
|
||||
PrefabDom& FindTemplateDom(TemplateId templateId) override;
|
||||
|
||||
/**
|
||||
@@ -244,7 +245,7 @@ namespace AzToolsFramework
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
|
||||
|
||||
|
||||
/**
|
||||
* Builds a new Prefab Template out of entities and instances and returns the first instance comprised of
|
||||
* these entities and instances.
|
||||
@@ -412,6 +413,8 @@ namespace AzToolsFramework
|
||||
|
||||
// Handler of the public Prefab requests.
|
||||
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
|
||||
|
||||
PrefabSystemScriptingHandler m_prefabSystemScriptingHandler;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+1
-2
@@ -78,8 +78,7 @@ namespace AzToolsFramework
|
||||
AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr, InstanceOptionalReference parent = AZStd::nullopt,
|
||||
bool shouldCreateLinks = true) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 <AzCore/Interface/Interface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/Link/Link.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
// Bus that exposes a script-friendly interface to the PrefabSystemComponent
|
||||
struct PrefabSystemScriptingEbusTraits : AZ::EBusTraits
|
||||
{
|
||||
using MutexType = AZ::NullMutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
|
||||
virtual TemplateId CreatePrefabTemplate(
|
||||
const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) = 0;
|
||||
};
|
||||
|
||||
using PrefabSystemScriptingBus = AZ::EBus<PrefabSystemScriptingEbusTraits>;
|
||||
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/PrefabSystemScriptingHandler.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
{
|
||||
void PrefabSystemScriptingHandler::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty("InvalidTemplateId", BehaviorConstant(InvalidTemplateId))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab");
|
||||
|
||||
behaviorContext->EBus<PrefabSystemScriptingBus>("PrefabSystemScriptingBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab")
|
||||
->Event("CreatePrefab", &PrefabSystemScriptingBus::Events::CreatePrefabTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemScriptingHandler::Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface)
|
||||
{
|
||||
AZ_Assert(prefabSystemComponentInterface != nullptr, "prefabSystemComponentInterface must not be null");
|
||||
m_prefabSystemComponentInterface = prefabSystemComponentInterface;
|
||||
PrefabSystemScriptingBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void PrefabSystemScriptingHandler::Disconnect()
|
||||
{
|
||||
PrefabSystemScriptingBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
TemplateId PrefabSystemScriptingHandler::CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
|
||||
for (const auto& entityId : entityIds)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationBus::Events::FindEntity, entityId);
|
||||
|
||||
AZ_Warning(
|
||||
"PrefabSystemComponent", entity, "EntityId %s was not found and will not be added to the prefab",
|
||||
entityId.ToString().c_str());
|
||||
|
||||
if (entity)
|
||||
{
|
||||
entities.push_back(entity);
|
||||
}
|
||||
}
|
||||
|
||||
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)));
|
||||
|
||||
if (!prefab)
|
||||
{
|
||||
AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str());
|
||||
return InvalidTemplateId;
|
||||
}
|
||||
|
||||
return prefab->GetTemplateId();
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 <Prefab/PrefabSystemScriptingBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
class PrefabSystemScriptingHandler
|
||||
: PrefabSystemScriptingBus::Handler
|
||||
{
|
||||
public:
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
PrefabSystemScriptingHandler() = default;
|
||||
|
||||
void Connect(PrefabSystemComponentInterface* prefabSystemComponentInterface);
|
||||
void Disconnect();
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY(PrefabSystemScriptingHandler);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// PrefabSystemScriptingBus implementation
|
||||
TemplateId CreatePrefabTemplate(const AZStd::vector<AZ::EntityId>& entityIds, const AZStd::string& filePath) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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 <Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
static constexpr const char s_useProceduralPrefabsKey[] = "/O3DE/Preferences/Prefabs/UseProceduralPrefabs";
|
||||
|
||||
// ProceduralPrefabAsset
|
||||
|
||||
ProceduralPrefabAsset::ProceduralPrefabAsset(const AZ::Data::AssetId& assetId)
|
||||
: AZ::Data::AssetData(assetId)
|
||||
, m_templateId(AzToolsFramework::Prefab::InvalidTemplateId)
|
||||
{
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
PrefabDomData::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<ProceduralPrefabAsset, AZ::Data::AssetData>()
|
||||
->Version(1)
|
||||
->Field("Template Name", &ProceduralPrefabAsset::m_templateName)
|
||||
->Field("Template ID", &ProceduralPrefabAsset::m_templateId);
|
||||
}
|
||||
}
|
||||
|
||||
const AZStd::string& ProceduralPrefabAsset::GetTemplateName() const
|
||||
{
|
||||
return m_templateName;
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::SetTemplateName(AZStd::string templateName)
|
||||
{
|
||||
m_templateName = AZStd::move(templateName);
|
||||
}
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId ProceduralPrefabAsset::GetTemplateId() const
|
||||
{
|
||||
return m_templateId;
|
||||
}
|
||||
|
||||
void ProceduralPrefabAsset::SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId)
|
||||
{
|
||||
m_templateId = templateId;
|
||||
}
|
||||
|
||||
bool ProceduralPrefabAsset::UseProceduralPrefabs()
|
||||
{
|
||||
bool useProceduralPrefabs = false;
|
||||
bool result = AZ::SettingsRegistry::Get()->GetObject(useProceduralPrefabs, s_useProceduralPrefabsKey);
|
||||
return result && useProceduralPrefabs;
|
||||
}
|
||||
|
||||
// PrefabDomData
|
||||
|
||||
void PrefabDomData::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* jsonContext = azrtti_cast<AZ::JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<PrefabDomDataJsonSerializer>()->HandlesType<PrefabDomData>();
|
||||
}
|
||||
|
||||
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<PrefabDomData>()
|
||||
->Version(1);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabDomData::CopyValue(const rapidjson::Value& inputValue)
|
||||
{
|
||||
m_prefabDom.CopyFrom(inputValue, m_prefabDom.GetAllocator());
|
||||
}
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& PrefabDomData::GetValue() const
|
||||
{
|
||||
return m_prefabDom;
|
||||
}
|
||||
|
||||
// PrefabDomDataJsonSerializer
|
||||
|
||||
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Load(
|
||||
void* outputValue,
|
||||
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
AZ::JsonDeserializerContext& context)
|
||||
{
|
||||
AZ_Assert(outputValueTypeId == azrtti_typeid<PrefabDomData>(),
|
||||
"PrefabDomDataJsonSerializer Load against output typeID that was not PrefabDomData");
|
||||
AZ_Assert(outputValue, "PrefabDomDataJsonSerializer Load against null output");
|
||||
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
|
||||
if (inputValue.IsObject() == false)
|
||||
{
|
||||
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing object"));
|
||||
return context.Report(result, "Prefab should be an object.");
|
||||
}
|
||||
|
||||
if (inputValue.MemberCount() < 1)
|
||||
{
|
||||
result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, "Missing members"));
|
||||
return context.Report(result, "Prefab should have multiple members.");
|
||||
}
|
||||
|
||||
auto* outputVariable = reinterpret_cast<PrefabDomData*>(outputValue);
|
||||
outputVariable->CopyValue(inputValue);
|
||||
return context.Report(result, "Loaded procedural prefab");
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::Result PrefabDomDataJsonSerializer::Store(
|
||||
rapidjson::Value& outputValue,
|
||||
const void* inputValue,
|
||||
[[maybe_unused]] const void* defaultValue,
|
||||
[[maybe_unused]] const AZ::Uuid& valueTypeId,
|
||||
AZ::JsonSerializerContext& context)
|
||||
{
|
||||
AZ_Assert(inputValue, "Input value for PrefabDomDataJsonSerializer can't be null.");
|
||||
AZ_Assert(azrtti_typeid<PrefabDomData>() == valueTypeId,
|
||||
"Unable to Serialize because the provided type is not PrefabGroup::PrefabDomData.");
|
||||
|
||||
const PrefabDomData* prefabDomData = reinterpret_cast<const PrefabDomData*>(inputValue);
|
||||
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
JSR::ResultCode result(JSR::Tasks::WriteValue);
|
||||
outputValue.SetObject();
|
||||
outputValue.CopyFrom(prefabDomData->GetValue(), context.GetJsonAllocator());
|
||||
return context.Report(result, "Stored procedural prefab");
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
//! A wrapper around the JSON DOM type so that the assets can read in and write out
|
||||
//! JSON directly since Prefabs are JSON serialized entity-component data
|
||||
class PrefabDomData final
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabDomData, "{C73A3360-D772-4D41-9118-A039BF9340C1}");
|
||||
AZ_CLASS_ALLOCATOR(PrefabDomData, AZ::SystemAllocator, 0);
|
||||
|
||||
PrefabDomData() = default;
|
||||
~PrefabDomData() = default;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
void CopyValue(const rapidjson::Value& inputValue);
|
||||
const AzToolsFramework::Prefab::PrefabDom& GetValue() const;
|
||||
|
||||
private:
|
||||
AzToolsFramework::Prefab::PrefabDom m_prefabDom;
|
||||
};
|
||||
|
||||
//! Registered to help read/write JSON for the PrefabDomData::m_prefabDom
|
||||
class PrefabDomDataJsonSerializer final
|
||||
: public AZ::BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabDomDataJsonSerializer, "{9FC48652-A00B-4EFA-8FD9-345A8E625439}", BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR(PrefabDomDataJsonSerializer, AZ::SystemAllocator, 0);
|
||||
|
||||
~PrefabDomDataJsonSerializer() override = default;
|
||||
|
||||
AZ::JsonSerializationResult::Result Load(
|
||||
void* outputValue,
|
||||
const AZ::Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
AZ::JsonDeserializerContext& context) override;
|
||||
|
||||
AZ::JsonSerializationResult::Result Store(
|
||||
rapidjson::Value& outputValue,
|
||||
const void* inputValue,
|
||||
const void* defaultValue,
|
||||
const AZ::Uuid& valueTypeId,
|
||||
AZ::JsonSerializerContext& context) override;
|
||||
};
|
||||
|
||||
//! An asset type to register templates into the Prefab system so that they
|
||||
//! can instantiate like Authored Prefabs
|
||||
class ProceduralPrefabAsset
|
||||
: public AZ::Data::AssetData
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ProceduralPrefabAsset, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(ProceduralPrefabAsset, "{9B7C8459-471E-4EAD-A363-7990CC4065A9}", AZ::Data::AssetData);
|
||||
|
||||
static bool UseProceduralPrefabs();
|
||||
|
||||
ProceduralPrefabAsset(const AZ::Data::AssetId& assetId = AZ::Data::AssetId());
|
||||
~ProceduralPrefabAsset() override = default;
|
||||
ProceduralPrefabAsset(const ProceduralPrefabAsset& rhs) = delete;
|
||||
ProceduralPrefabAsset& operator=(const ProceduralPrefabAsset& rhs) = delete;
|
||||
|
||||
const AZStd::string& GetTemplateName() const;
|
||||
void SetTemplateName(AZStd::string templateName);
|
||||
|
||||
AzToolsFramework::Prefab::TemplateId GetTemplateId() const;
|
||||
void SetTemplateId(AzToolsFramework::Prefab::TemplateId templateId);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
private:
|
||||
AZStd::string m_templateName;
|
||||
AzToolsFramework::Prefab::TemplateId m_templateId;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 <Prefab/ScriptingPrefabLoader.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
{
|
||||
void ScriptingPrefabLoader::Connect(PrefabLoaderInterface* prefabLoaderInterface)
|
||||
{
|
||||
AZ_Assert(prefabLoaderInterface, "prefabLoaderInterface must not be null");
|
||||
|
||||
m_prefabLoaderInterface = prefabLoaderInterface;
|
||||
PrefabLoaderScriptingBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void ScriptingPrefabLoader::Disconnect()
|
||||
{
|
||||
PrefabLoaderScriptingBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
AZ::Outcome<AZStd::string, void> ScriptingPrefabLoader::SaveTemplateToString(TemplateId templateId)
|
||||
{
|
||||
AZStd::string json;
|
||||
|
||||
if (m_prefabLoaderInterface->SaveTemplateToString(templateId, json))
|
||||
{
|
||||
return AZ::Success(json);
|
||||
}
|
||||
|
||||
return AZ::Failure();
|
||||
}
|
||||
} // namespace AzToolsFramework::Prefab
|
||||
@@ -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
|
||||
|
||||
#include <Prefab/PrefabLoaderInterface.h>
|
||||
#include <Prefab/PrefabLoaderScriptingBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
/**
|
||||
* The Scripting Prefab Loader handles scripting-friendly API requests for the prefab loader
|
||||
*/
|
||||
class ScriptingPrefabLoader
|
||||
: private PrefabLoaderScriptingBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ScriptingPrefabLoader, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(ScriptingPrefabLoader, "{ABC3C989-4D4F-41E7-B25B-B0FEF97177E6}");
|
||||
|
||||
void Connect(PrefabLoaderInterface* prefabLoaderInterface);
|
||||
void Disconnect();
|
||||
|
||||
private:
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// PrefabLoaderRequestBus implementation
|
||||
AZ::Outcome<AZStd::string, void> SaveTemplateToString(TemplateId templateId) override;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
|
||||
};
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+73
@@ -13,6 +13,7 @@
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
|
||||
#include <AzFramework/API/ApplicationAPI.h>
|
||||
#include <AzFramework/Asset/AssetSystemBus.h>
|
||||
@@ -25,6 +26,9 @@
|
||||
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
|
||||
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
|
||||
@@ -218,6 +222,16 @@ namespace AzToolsFramework
|
||||
instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); });
|
||||
}
|
||||
|
||||
// Instantiate Procedural Prefab
|
||||
if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs())
|
||||
{
|
||||
QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
|
||||
action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
|
||||
|
||||
QObject::connect(
|
||||
action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
|
||||
}
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
bool itemWasShown = false;
|
||||
@@ -435,6 +449,38 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab()
|
||||
{
|
||||
AZStd::string prefabAssetPath;
|
||||
bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath);
|
||||
|
||||
if (hasUserForProceduralPrefabAsset)
|
||||
{
|
||||
AZ::EntityId parentId;
|
||||
AZ::Vector3 position = AZ::Vector3::CreateZero();
|
||||
|
||||
EntityIdList selectedEntities;
|
||||
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
||||
if (selectedEntities.size() == 1)
|
||||
{
|
||||
parentId = selectedEntities.front();
|
||||
AZ::TransformBus::EventResult(position, parentId, &AZ::TransformInterface::GetWorldTranslation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise return since it needs to be inside an authored prefab
|
||||
return;
|
||||
}
|
||||
|
||||
// Instantiating from context menu always puts the instance at the root level
|
||||
auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position);
|
||||
if (!createPrefabOutcome.IsSuccess())
|
||||
{
|
||||
WarnUserOfError("Prefab Instantiation Error", createPrefabOutcome.GetError());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
|
||||
{
|
||||
s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
|
||||
@@ -690,6 +736,33 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath)
|
||||
{
|
||||
using namespace AzToolsFramework;
|
||||
auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>());
|
||||
EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection);
|
||||
|
||||
if (!selection.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
|
||||
if (product == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outPrefabAssetPath = product->GetRelativePath();
|
||||
|
||||
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
|
||||
product->GetAssetId(),
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
|
||||
AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
|
||||
{
|
||||
QWidget* activeWindow = QApplication::activeWindow();
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace AzToolsFramework
|
||||
// Context menu item handlers
|
||||
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
|
||||
static void ContextMenu_InstantiatePrefab();
|
||||
static void ContextMenu_InstantiateProceduralPrefab();
|
||||
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_DeleteSelected();
|
||||
@@ -101,6 +102,7 @@ namespace AzToolsFramework
|
||||
const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow,
|
||||
AZStd::string& outPrefabName, AZStd::string& outPrefabFilePath);
|
||||
static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath);
|
||||
static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath);
|
||||
static void WarnUserOfError(AZStd::string_view title, AZStd::string_view message);
|
||||
|
||||
// Path and filename generation
|
||||
|
||||
@@ -152,6 +152,8 @@ set(FILES
|
||||
Entity/SliceEditorEntityOwnershipService.h
|
||||
Entity/SliceEditorEntityOwnershipService.cpp
|
||||
Entity/SliceEditorEntityOwnershipServiceBus.h
|
||||
Entity/EntityUtilityComponent.h
|
||||
Entity/EntityUtilityComponent.cpp
|
||||
Fingerprinting/TypeFingerprinter.h
|
||||
Fingerprinting/TypeFingerprinter.cpp
|
||||
FocusMode/FocusModeInterface.h
|
||||
@@ -646,9 +648,15 @@ set(FILES
|
||||
Prefab/PrefabLoader.h
|
||||
Prefab/PrefabLoader.cpp
|
||||
Prefab/PrefabLoaderInterface.h
|
||||
Prefab/PrefabLoaderScriptingBus.h
|
||||
Prefab/ScriptingPrefabLoader.h
|
||||
Prefab/ScriptingPrefabLoader.cpp
|
||||
Prefab/PrefabSystemComponent.h
|
||||
Prefab/PrefabSystemComponent.cpp
|
||||
Prefab/PrefabSystemComponentInterface.h
|
||||
Prefab/PrefabSystemScriptingBus.h
|
||||
Prefab/PrefabSystemScriptingHandler.h
|
||||
Prefab/PrefabSystemScriptingHandler.cpp
|
||||
Prefab/Instance/Instance.h
|
||||
Prefab/Instance/Instance.cpp
|
||||
Prefab/Instance/InstanceSerializer.h
|
||||
@@ -671,6 +679,8 @@ set(FILES
|
||||
Prefab/Instance/TemplateInstanceMapperInterface.h
|
||||
Prefab/Link/Link.h
|
||||
Prefab/Link/Link.cpp
|
||||
Prefab/Procedural/ProceduralPrefabAsset.h
|
||||
Prefab/Procedural/ProceduralPrefabAsset.cpp
|
||||
Prefab/PrefabPublicHandler.h
|
||||
Prefab/PrefabPublicHandler.cpp
|
||||
Prefab/PrefabPublicInterface.h
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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 <AzFramework/Entity/BehaviorEntity.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
#include <ToolsComponents/TransformComponent.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Global variables for communicating between Lua test code and C++
|
||||
AZ::EntityId g_globalEntityId = AZ::EntityId{};
|
||||
AZStd::string g_globalString = "";
|
||||
AzFramework::BehaviorComponentId g_globalComponentId = {};
|
||||
AZStd::vector<AzToolsFramework::ComponentDetails> g_globalComponentDetails = {};
|
||||
bool g_globalBool = false;
|
||||
|
||||
class EntityUtilityComponentTests
|
||||
: public ToolsApplicationFixture
|
||||
{
|
||||
void InitProperties()
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
behaviorContext->Property("g_globalEntityId", BehaviorValueProperty(&g_globalEntityId));
|
||||
behaviorContext->Property("g_globalString", BehaviorValueProperty(&g_globalString));
|
||||
behaviorContext->Property("g_globalComponentId", BehaviorValueProperty(&g_globalComponentId));
|
||||
behaviorContext->Property("g_globalBool", BehaviorValueProperty(&g_globalBool));
|
||||
behaviorContext->Property("g_globalComponentDetails", BehaviorValueProperty(&g_globalComponentDetails));
|
||||
|
||||
g_globalEntityId = AZ::EntityId{};
|
||||
g_globalString = AZStd::string{};
|
||||
g_globalComponentId = AzFramework::BehaviorComponentId{};
|
||||
g_globalBool = false;
|
||||
g_globalComponentDetails = AZStd::vector<AzToolsFramework::ComponentDetails>{};
|
||||
}
|
||||
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
InitProperties();
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
g_globalString.set_capacity(0); // Free all memory
|
||||
g_globalComponentDetails.set_capacity(0);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateEntity)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
my_entity = Entity(g_globalEntityId)
|
||||
g_globalString = my_entity:GetName()
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
|
||||
EXPECT_STREQ(g_globalString.c_str(), "test");
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_NE(entity, nullptr);
|
||||
|
||||
// Test cleaning up, make sure the entity is destroyed
|
||||
AzToolsFramework::EntityUtilityBus::Broadcast(&AzToolsFramework::EntityUtilityBus::Events::ResetEntityContext);
|
||||
|
||||
entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_EQ(entity, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateEntityEmptyName)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId{});
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
ASSERT_NE(entity, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, FindComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0 TransformComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, InvalidComponentName)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ThisIsNotAComponent-Error")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
EXPECT_FALSE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, InvalidComponentId)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "{1234-hello-world-this-is-not-an-id}")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // Should get 1 error stating the type id is not valid
|
||||
EXPECT_FALSE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, CreateComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
ent_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
g_globalComponentId = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(ent_id, "ScriptEditorComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalComponentId.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, UpdateComponent)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalEntityId = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
comp_id = EntityUtilityBus.Broadcast.GetOrAddComponentByTypeName(g_globalEntityId, "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent")
|
||||
json_update = [[
|
||||
{
|
||||
"Transform Data": { "Rotate": [0.0, 0.1, 180.0] }
|
||||
}
|
||||
]]
|
||||
g_globalBool = EntityUtilityBus.Broadcast.UpdateComponentForEntity(g_globalEntityId, comp_id, json_update);
|
||||
)LUA");
|
||||
|
||||
EXPECT_TRUE(g_globalBool);
|
||||
EXPECT_NE(g_globalEntityId, AZ::EntityId(AZ::EntityId::InvalidEntityId));
|
||||
|
||||
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(g_globalEntityId);
|
||||
|
||||
auto* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
|
||||
ASSERT_NE(transformComponent, nullptr);
|
||||
|
||||
AZ::Vector3 localRotation = transformComponent->GetLocalRotationQuaternion().GetEulerDegrees();
|
||||
|
||||
EXPECT_EQ(localRotation, AZ::Vector3(.0f, 0.1f, 180.0f));
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, GetComponentJson)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("ScriptEditorComponent")
|
||||
)LUA");
|
||||
|
||||
EXPECT_STRNE(g_globalString.c_str(), "");
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, GetComponentJsonDoesNotExist)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
g_globalString = EntityUtilityBus.Broadcast.GetComponentDefaultJson("404")
|
||||
)LUA");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 error: Failed to find component id for type name 404
|
||||
|
||||
EXPECT_STREQ(g_globalString.c_str(), "");
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, SearchComponents)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("Transform*")
|
||||
)LUA");
|
||||
|
||||
// There should be 2 transform components
|
||||
EXPECT_EQ(g_globalComponentDetails.size(), 2);
|
||||
}
|
||||
|
||||
TEST_F(EntityUtilityComponentTests, SearchComponentsNotFound)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
g_globalComponentDetails = EntityUtilityBus.Broadcast.FindMatchingComponents("404")
|
||||
)LUA");
|
||||
|
||||
EXPECT_EQ(g_globalComponentDetails.size(), 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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 <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <Prefab/PrefabTestComponent.h>
|
||||
#include <Prefab/PrefabTestDomUtils.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
TemplateId g_globalTemplateId = {};
|
||||
AZStd::string g_globalPrefabString = "";
|
||||
|
||||
class PrefabScriptingTest : public PrefabTestFixture
|
||||
{
|
||||
void InitProperties() const
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
behaviorContext->Property("g_globalTemplateId", BehaviorValueProperty(&g_globalTemplateId));
|
||||
behaviorContext->Property("g_globalPrefabString", BehaviorValueProperty(&g_globalPrefabString));
|
||||
|
||||
g_globalTemplateId = TemplateId{};
|
||||
g_globalPrefabString = AZStd::string{};
|
||||
}
|
||||
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
InitProperties();
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
g_globalPrefabString.set_capacity(0); // Free all memory
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
entities:push_back(my_id)
|
||||
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalTemplateId, TemplateId{});
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
|
||||
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
|
||||
|
||||
EXPECT_TRUE(templateRef);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoEntities)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
g_globalTemplateId = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
)LUA");
|
||||
|
||||
EXPECT_NE(g_globalTemplateId, TemplateId{});
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
|
||||
ASSERT_NE(prefabSystemComponentInterface, nullptr);
|
||||
|
||||
TemplateReference templateRef = prefabSystemComponentInterface->FindTemplate(g_globalTemplateId);
|
||||
|
||||
EXPECT_TRUE(templateRef);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_CreatePrefab_NoPath)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "")
|
||||
)LUA");
|
||||
/*
|
||||
error: PrefabSystemComponent::CreateTemplateFromInstance - Attempted to create a prefab template from an instance without a source file path. Unable to proceed.
|
||||
error: Failed to create a Template associated with file path during CreatePrefab.
|
||||
error: Failed to create prefab
|
||||
*/
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(3);
|
||||
}
|
||||
|
||||
TEST_F(PrefabScriptingTest, PrefabScripting_SaveToString)
|
||||
{
|
||||
AZ::ScriptContext sc;
|
||||
auto behaviorContext = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->GetBehaviorContext();
|
||||
|
||||
sc.BindTo(behaviorContext);
|
||||
sc.Execute(R"LUA(
|
||||
my_id = EntityUtilityBus.Broadcast.CreateEditorReadyEntity("test")
|
||||
entities = vector_EntityId()
|
||||
entities:push_back(my_id)
|
||||
template_id = PrefabSystemScriptingBus.Broadcast.CreatePrefab(entities, "test.prefab")
|
||||
my_result = PrefabLoaderScriptingBus.Broadcast.SaveTemplateToString(template_id)
|
||||
|
||||
if my_result:IsSuccess() then
|
||||
g_globalPrefabString = my_result:GetValue()
|
||||
end
|
||||
)LUA");
|
||||
|
||||
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
prefabSystemComponentInterface->RemoveAllTemplates();
|
||||
|
||||
EXPECT_STRNE(g_globalPrefabString.c_str(), "");
|
||||
TemplateId templateFromString = AZ::Interface<PrefabLoaderInterface>::Get()->LoadTemplateFromString(g_globalPrefabString);
|
||||
|
||||
EXPECT_NE(templateFromString, InvalidTemplateId);
|
||||
|
||||
// Create another entity for comparison purposes
|
||||
AZ::EntityId entityId;
|
||||
AzToolsFramework::EntityUtilityBus::BroadcastResult(
|
||||
entityId, &AzToolsFramework::EntityUtilityBus::Events::CreateEditorReadyEntity, "test");
|
||||
|
||||
AZ::Entity* testEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
|
||||
|
||||
// Instantiate the prefab we saved
|
||||
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->InstantiatePrefab(templateFromString);
|
||||
|
||||
EXPECT_NE(instance, nullptr);
|
||||
|
||||
AZStd::vector<const AZ::Entity*> loadedEntities;
|
||||
|
||||
// Get the entities from the instance
|
||||
instance->GetConstEntities(
|
||||
[&loadedEntities](const AZ::Entity& entity)
|
||||
{
|
||||
loadedEntities.push_back(&entity);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Make sure the instance has an entity with the same number of components as our test entity
|
||||
EXPECT_EQ(loadedEntities.size(), 1);
|
||||
EXPECT_EQ(loadedEntities[0]->GetComponents().size(), testEntity->GetComponents().size());
|
||||
|
||||
g_globalPrefabString.set_capacity(0); // Free all memory
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
#include <Prefab/PrefabTestComponent.h>
|
||||
#include <Prefab/PrefabTestDomUtils.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class ProceduralPrefabAssetTest
|
||||
: public PrefabTestFixture
|
||||
{
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
ASSERT_NE(componentApplicationRequests, nullptr);
|
||||
|
||||
auto* behaviorContext = componentApplicationRequests->GetBehaviorContext();
|
||||
ASSERT_NE(behaviorContext, nullptr);
|
||||
|
||||
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
|
||||
ASSERT_NE(jsonRegistrationContext, nullptr);
|
||||
|
||||
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
|
||||
ASSERT_NE(serializeContext, nullptr);
|
||||
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext);
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(behaviorContext);
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(jsonRegistrationContext);
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
componentApplicationRequests->GetJsonRegistrationContext()->EnableRemoveReflection();
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(componentApplicationRequests->GetJsonRegistrationContext());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, ReflectContext_AccessMethods_Works)
|
||||
{
|
||||
AZ::ComponentApplicationRequests* componentApplicationRequests = AZ::Interface<AZ::ComponentApplicationRequests>::Get();
|
||||
|
||||
auto* serializeContext = componentApplicationRequests->GetSerializeContext();
|
||||
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>()).empty());
|
||||
EXPECT_TRUE(!serializeContext->CreateAny(azrtti_typeid<AZ::Prefab::PrefabDomData>()).empty());
|
||||
|
||||
auto* jsonRegistrationContext = componentApplicationRequests->GetJsonRegistrationContext();
|
||||
EXPECT_TRUE(jsonRegistrationContext->GetSerializerForSerializerType(azrtti_typeid<AZ::Prefab::PrefabDomDataJsonSerializer>()));
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, ProceduralPrefabAsset_AccessMethods_Works)
|
||||
{
|
||||
const auto templateId = TemplateId(1);
|
||||
const auto prefabString = "fake.prefab";
|
||||
|
||||
AZ::Prefab::ProceduralPrefabAsset asset{};
|
||||
asset.SetTemplateId(templateId);
|
||||
EXPECT_EQ(asset.GetTemplateId(), templateId);
|
||||
|
||||
asset.SetTemplateName(prefabString);
|
||||
EXPECT_EQ(asset.GetTemplateName(), prefabString);
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomData_AccessMethods_Works)
|
||||
{
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("boolValue", true, dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
prefabDomData.CopyValue(dom);
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& result = prefabDomData.GetValue();
|
||||
EXPECT_TRUE(result.HasMember("boolValue"));
|
||||
EXPECT_TRUE(result.FindMember("boolValue")->value.GetBool());
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Load_Works)
|
||||
{
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("member", "value", dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
|
||||
AZ::JsonDeserializerSettings settings;
|
||||
settings.m_reporting = [](auto, auto, auto)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::ReadField);
|
||||
return result;
|
||||
};
|
||||
AZ::JsonDeserializerContext context{ settings };
|
||||
|
||||
auto result = prefabDomDataJsonSerializer.Load(&prefabDomData, azrtti_typeid(prefabDomData), dom, context);
|
||||
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
|
||||
EXPECT_TRUE(prefabDomData.GetValue().HasMember("member"));
|
||||
EXPECT_STREQ(prefabDomData.GetValue().FindMember("member")->value.GetString(), "value");
|
||||
}
|
||||
|
||||
TEST_F(ProceduralPrefabAssetTest, PrefabDomDataJsonSerializer_Store_Works)
|
||||
{
|
||||
AzToolsFramework::Prefab::PrefabDom dom;
|
||||
dom.SetObject();
|
||||
dom.AddMember("member", "value", dom.GetAllocator());
|
||||
|
||||
AZ::Prefab::PrefabDomData prefabDomData;
|
||||
prefabDomData.CopyValue(dom);
|
||||
|
||||
AZ::Prefab::PrefabDomDataJsonSerializer prefabDomDataJsonSerializer;
|
||||
AzToolsFramework::Prefab::PrefabDom outputValue;
|
||||
AZ::JsonSerializerSettings settings;
|
||||
settings.m_reporting = [](auto, auto, auto)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode result(AZ::JsonSerializationResult::Tasks::WriteValue);
|
||||
return result;
|
||||
};
|
||||
AZ::JsonSerializerContext context{ settings, outputValue.GetAllocator() };
|
||||
auto result = prefabDomDataJsonSerializer.Store(outputValue, &prefabDomData, nullptr, azrtti_typeid(prefabDomData), context);
|
||||
EXPECT_EQ(result.GetResultCode().GetOutcome(), AZ::JsonSerializationResult::Outcomes::DefaultsUsed);
|
||||
EXPECT_TRUE(outputValue.HasMember("member"));
|
||||
EXPECT_STREQ(outputValue.FindMember("member")->value.GetString(), "value");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ set(FILES
|
||||
Entity/EditorEntityHelpersTests.cpp
|
||||
Entity/EditorEntitySearchComponentTests.cpp
|
||||
Entity/EditorEntitySelectionTests.cpp
|
||||
Entity/EntityUtilityComponentTests.cpp
|
||||
EntityIdQLabelTests.cpp
|
||||
EntityInspectorTests.cpp
|
||||
EntityOwnershipService/EntityOwnershipServiceTestFixture.cpp
|
||||
@@ -91,6 +92,8 @@ set(FILES
|
||||
Prefab/SpawnableSortEntitiesTestFixture.cpp
|
||||
Prefab/SpawnableSortEntitiesTestFixture.h
|
||||
Prefab/SpawnableSortEntitiesTests.cpp
|
||||
Prefab/PrefabScriptingTests.cpp
|
||||
Prefab/ProceduralPrefabAssetTests.cpp
|
||||
PropertyIntCtrlCommonTests.h
|
||||
PropertyIntSliderCtrlTests.cpp
|
||||
PropertyIntSpinCtrlTests.cpp
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <AssetBuilderComponent.h>
|
||||
#include <AssetBuilderInfo.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
|
||||
namespace AssetBuilder
|
||||
{
|
||||
@@ -80,6 +81,7 @@ AZ::ComponentTypeList AssetBuilderApplication::GetRequiredSystemComponents() con
|
||||
azrtti_typeid<AzToolsFramework::Components::EditorEntityModelComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::EditorEntityContextComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::Prefab::PrefabSystemComponent>(),
|
||||
azrtti_typeid<AzToolsFramework::EntityUtilityComponent>(),
|
||||
});
|
||||
|
||||
return components;
|
||||
|
||||
@@ -62,7 +62,10 @@ namespace AssetProcessor
|
||||
{
|
||||
searchStr = searchStr.mid(0, subidPos);
|
||||
}
|
||||
AZ::Uuid filterAsUuid = AZ::Uuid::CreateStringPermissive(searchStr.toUtf8(), 0);
|
||||
|
||||
// Cap the string to some reasonable length, we don't want to try parsing an entire book
|
||||
size_t cappedStringLength = searchStr.length() > 60 ? 60 : searchStr.length();
|
||||
AZ::Uuid filterAsUuid = AZ::Uuid::CreateStringPermissive(searchStr.toUtf8(), cappedStringLength);
|
||||
|
||||
return DescendantMatchesFilter(*assetTreeItem, filter, filterAsUuid);
|
||||
}
|
||||
|
||||
@@ -194,8 +194,8 @@ namespace AZ
|
||||
if (baseClass)
|
||||
{
|
||||
m_behaviorClass = behaviorClass;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,16 @@ namespace AZ
|
||||
return m_sourceGuid;
|
||||
}
|
||||
|
||||
void Scene::SetWatchFolder(const AZStd::string& watchFolder)
|
||||
{
|
||||
m_watchFolder = watchFolder;
|
||||
}
|
||||
|
||||
const AZStd::string& Scene::GetWatchFolder() const
|
||||
{
|
||||
return m_watchFolder;
|
||||
}
|
||||
|
||||
void Scene::SetManifestFilename(const AZStd::string& name)
|
||||
{
|
||||
m_manifestFilename = name;
|
||||
@@ -111,6 +121,7 @@ namespace AZ
|
||||
->Property("sourceGuid", BehaviorValueGetter(&Scene::m_sourceGuid), nullptr)
|
||||
->Property("graph", BehaviorValueGetter(&Scene::m_graph), nullptr)
|
||||
->Property("manifest", BehaviorValueGetter(&Scene::m_manifest), nullptr)
|
||||
->Property("watchFolder", BehaviorValueGetter(&Scene::m_watchFolder), nullptr)
|
||||
->Constant("SceneOrientation_YUp", BehaviorConstant(SceneOrientation::YUp))
|
||||
->Constant("SceneOrientation_ZUp", BehaviorConstant(SceneOrientation::ZUp))
|
||||
->Constant("SceneOrientation_XUp", BehaviorConstant(SceneOrientation::XUp))
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace AZ
|
||||
const AZStd::string& GetSourceFilename() const;
|
||||
const Uuid& GetSourceGuid() const;
|
||||
|
||||
void SetWatchFolder(const AZStd::string& watchFolder);
|
||||
const AZStd::string& GetWatchFolder() const;
|
||||
|
||||
void SetManifestFilename(const AZStd::string& name);
|
||||
void SetManifestFilename(AZStd::string&& name);
|
||||
const AZStd::string& GetManifestFilename() const;
|
||||
@@ -59,6 +62,7 @@ namespace AZ
|
||||
AZStd::string m_name;
|
||||
AZStd::string m_manifestFilename;
|
||||
AZStd::string m_sourceFilename;
|
||||
AZStd::string m_watchFolder;
|
||||
Uuid m_sourceGuid;
|
||||
SceneGraph m_graph;
|
||||
SceneManifest m_manifest;
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace AZ
|
||||
GraphObjectProxy* proxy = aznew GraphObjectProxy(graphObject);
|
||||
return proxy;
|
||||
}
|
||||
return nullptr;
|
||||
return aznew GraphObjectProxy(nullptr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,7 @@ namespace AZ
|
||||
{
|
||||
namespace Containers
|
||||
{
|
||||
//! Protects from allocating too much memory. The choice of a 5MB threshold is arbitrary.
|
||||
const size_t MaxSceneManifestFileSizeInBytes = 5 * 1024 * 1024;
|
||||
|
||||
|
||||
const char ErrorWindowName[] = "SceneManifest";
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ namespace AZ
|
||||
|
||||
AZ_RTTI(SceneManifest, "{9274AD17-3212-4651-9F3B-7DCCB080E467}");
|
||||
|
||||
static constexpr size_t MaxSceneManifestFileSizeInBytes = AZStd::numeric_limits<size_t>::max();
|
||||
|
||||
virtual ~SceneManifest();
|
||||
|
||||
static AZStd::shared_ptr<const DataTypes::IManifestObject> SceneManifestConstDataConverter(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <API/EditorAssetSystemAPI.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
@@ -73,6 +74,10 @@ namespace AZ
|
||||
{
|
||||
}
|
||||
|
||||
void AssetImportRequest::GetGeneratedManifestExtension(AZStd::string& /*result*/)
|
||||
{
|
||||
}
|
||||
|
||||
void AssetImportRequest::GetSupportedFileExtensions(AZStd::unordered_set<AZStd::string>& /*extensions*/)
|
||||
{
|
||||
}
|
||||
@@ -103,14 +108,34 @@ namespace AZ
|
||||
AZ_UNUSED(value);
|
||||
}
|
||||
|
||||
void AssetImportRequest::GetManifestDependencyPaths(AZStd::vector<AZStd::string>&)
|
||||
{
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<Containers::Scene> AssetImportRequest::LoadSceneFromVerifiedPath(const AZStd::string& assetFilePath, const Uuid& sourceGuid,
|
||||
RequestingApplication requester, const Uuid& loadingComponentUuid)
|
||||
RequestingApplication requester, const Uuid& loadingComponentUuid)
|
||||
{
|
||||
AZStd::string sceneName;
|
||||
AzFramework::StringFunc::Path::GetFileName(assetFilePath.c_str(), sceneName);
|
||||
AZStd::shared_ptr<Containers::Scene> scene = AZStd::make_shared<Containers::Scene>(AZStd::move(sceneName));
|
||||
AZ_Assert(scene, "Unable to create new scene for asset importing.");
|
||||
|
||||
Data::AssetInfo assetInfo;
|
||||
AZStd::string watchFolder;
|
||||
bool result = false;
|
||||
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, sourceGuid, assetInfo, watchFolder);
|
||||
|
||||
if (result)
|
||||
{
|
||||
scene->SetWatchFolder(watchFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(
|
||||
"AssetImportRequest", false, "Failed to get watch folder for source %s",
|
||||
sourceGuid.ToString<AZStd::string>().c_str());
|
||||
}
|
||||
|
||||
// Unique pointer, will deactivate and clean up once going out of scope.
|
||||
SceneCore::EntityConstructor::EntityPointer loaders =
|
||||
SceneCore::EntityConstructor::BuildEntity("Scene Loading", loadingComponentUuid);
|
||||
|
||||
@@ -78,6 +78,8 @@ namespace AZ
|
||||
virtual void GetSupportedFileExtensions(AZStd::unordered_set<AZStd::string>& extensions);
|
||||
//! Gets the file extension for the manifest.
|
||||
virtual void GetManifestExtension(AZStd::string& result);
|
||||
//! Gets the file extension for the generated manifest.
|
||||
virtual void GetGeneratedManifestExtension(AZStd::string& result);
|
||||
|
||||
//! Before asset loading starts this is called to allow for any required initialization.
|
||||
virtual ProcessingResult PrepareForAssetLoading(Containers::Scene& scene, RequestingApplication requester);
|
||||
@@ -97,6 +99,23 @@ namespace AZ
|
||||
// Get scene processing project setting: UseCustomNormal
|
||||
virtual void AreCustomNormalsUsed(bool & value);
|
||||
|
||||
/*!
|
||||
Optional method for reporting source file dependencies that may exist in the scene manifest
|
||||
Paths is a vector of JSON Path strings, relative to the IRule object
|
||||
For example, the following path: /scriptFilename
|
||||
Would match with this manifest:
|
||||
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "Test",
|
||||
"scriptFilename": "file.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
virtual void GetManifestDependencyPaths(AZStd::vector<AZStd::string>& paths);
|
||||
|
||||
//! Utility function to load an asset and manifest from file by using the EBus functions above.
|
||||
//! @param assetFilePath The absolute path to the source file (not the manifest).
|
||||
//! @param sourceGuid The guid assigned to the source file (not the manifest).
|
||||
|
||||
@@ -56,8 +56,14 @@ namespace AZ
|
||||
result = s_extension;
|
||||
}
|
||||
|
||||
void ManifestImportRequestHandler::GetGeneratedManifestExtension(AZStd::string& result)
|
||||
{
|
||||
result = s_extension;
|
||||
result.append(s_generated);
|
||||
}
|
||||
|
||||
Events::LoadingResult ManifestImportRequestHandler::LoadAsset(Containers::Scene& scene, const AZStd::string& path,
|
||||
const Uuid& /*guid*/, RequestingApplication /*requester*/)
|
||||
const Uuid& /*guid*/, RequestingApplication /*requester*/)
|
||||
{
|
||||
AZStd::string manifestPath = path + s_extension;
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace AZ
|
||||
static void Reflect(ReflectContext* context);
|
||||
|
||||
void GetManifestExtension(AZStd::string& result) override;
|
||||
void GetGeneratedManifestExtension(AZStd::string& result) override;
|
||||
Events::LoadingResult LoadAsset(Containers::Scene& scene, const AZStd::string& path, const Uuid& guid,
|
||||
RequestingApplication requester) override;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
|
||||
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
|
||||
#include <AzToolsFramework/Debug/TraceContext.h>
|
||||
#include <Entity/EntityUtilityComponent.h>
|
||||
#include <Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <Prefab/PrefabSystemScriptingBus.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
|
||||
@@ -99,6 +102,7 @@ namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
AZStd::string result;
|
||||
CallResult(result, FN_OnUpdateManifest, scene);
|
||||
ScriptBuildingNotificationBusHandler::BusDisconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -110,6 +114,7 @@ namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
ExportProductList result;
|
||||
CallResult(result, FN_OnPrepareForExport, scene, outputDirectory, platformIdentifier, productList);
|
||||
ScriptBuildingNotificationBusHandler::BusDisconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -168,21 +173,9 @@ namespace AZ::SceneAPI::Behaviors
|
||||
UnloadPython();
|
||||
}
|
||||
|
||||
bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene)
|
||||
bool ScriptProcessorRuleBehavior::LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath)
|
||||
{
|
||||
if (m_editorPythonEventsInterface && !m_scriptFilename.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// get project folder
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ::IO::FixedMaxPath projectPath;
|
||||
if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int scriptDiscoveryAttempts = 0;
|
||||
const AZ::SceneAPI::Containers::SceneManifest& manifest = scene.GetManifest();
|
||||
auto view = Containers::MakeDerivedFilterView<DataTypes::IScriptProcessorRule>(manifest.GetValueStorage());
|
||||
for (const auto& scriptItem : view)
|
||||
@@ -194,9 +187,21 @@ namespace AZ::SceneAPI::Behaviors
|
||||
continue;
|
||||
}
|
||||
|
||||
++scriptDiscoveryAttempts;
|
||||
|
||||
// check for file exist via absolute path
|
||||
if (!IO::FileIOBase::GetInstance()->Exists(scriptFilename.c_str()))
|
||||
{
|
||||
// get project folder
|
||||
auto settingsRegistry = AZ::SettingsRegistry::Get();
|
||||
AZ::IO::FixedMaxPath projectPath;
|
||||
if (!settingsRegistry->Get(projectPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath))
|
||||
{
|
||||
AZ_Error("scene", false, "With (%s) could not find Project Path during script discovery.",
|
||||
scene.GetManifestFilename().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for script in the project folder
|
||||
AZ::IO::FixedMaxPath projectScriptPath = projectPath / scriptFilename;
|
||||
if (!IO::FileIOBase::GetInstance()->Exists(projectScriptPath.c_str()))
|
||||
@@ -209,32 +214,47 @@ namespace AZ::SceneAPI::Behaviors
|
||||
scriptFilename = AZStd::move(projectScriptPath);
|
||||
}
|
||||
|
||||
// lazy load the Python interface
|
||||
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
|
||||
if (editorPythonEventsInterface->IsPythonActive() == false)
|
||||
{
|
||||
const bool silenceWarnings = false;
|
||||
if (editorPythonEventsInterface->StartPython(silenceWarnings) == false)
|
||||
{
|
||||
editorPythonEventsInterface = nullptr;
|
||||
}
|
||||
}
|
||||
scriptPath = scriptFilename.c_str();
|
||||
break;
|
||||
}
|
||||
|
||||
// both Python and the script need to be ready
|
||||
if (editorPythonEventsInterface == nullptr || scriptFilename.empty())
|
||||
{
|
||||
AZ_Warning("scene", false,"The scene manifest (%s) attempted to use script(%s) but Python is not enabled;"
|
||||
"please add the EditorPythonBinding gem & PythonAssetBuilder gem to your project.",
|
||||
scene.GetManifestFilename().c_str(), scriptFilename.c_str());
|
||||
if (scriptPath.empty())
|
||||
{
|
||||
AZ_Warning("scene", scriptDiscoveryAttempts == 0,
|
||||
"The scene manifest (%s) attempted to use script rule, but no script file path could be found.",
|
||||
scene.GetManifestFilename().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_editorPythonEventsInterface = editorPythonEventsInterface;
|
||||
m_scriptFilename = scriptFilename.c_str();
|
||||
// already prepared the Python VM?
|
||||
if (m_editorPythonEventsInterface)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
// lazy load the Python interface
|
||||
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
|
||||
if (editorPythonEventsInterface->IsPythonActive() == false)
|
||||
{
|
||||
const bool silenceWarnings = false;
|
||||
if (editorPythonEventsInterface->StartPython(silenceWarnings) == false)
|
||||
{
|
||||
editorPythonEventsInterface = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// both Python and the script need to be ready
|
||||
if (editorPythonEventsInterface == nullptr)
|
||||
{
|
||||
AZ_Warning("scene", false,
|
||||
"The scene manifest (%s) attempted to prepare Python but Python can not start",
|
||||
scene.GetManifestFilename().c_str());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_editorPythonEventsInterface = editorPythonEventsInterface;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptProcessorRuleBehavior::UnloadPython()
|
||||
@@ -251,11 +271,13 @@ namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
using namespace AzToolsFramework;
|
||||
|
||||
auto executeCallback = [this, &context]()
|
||||
AZStd::string scriptPath;
|
||||
|
||||
auto executeCallback = [&context, &scriptPath]()
|
||||
{
|
||||
// set up script's hook callback
|
||||
EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename,
|
||||
m_scriptFilename.c_str());
|
||||
scriptPath.c_str());
|
||||
|
||||
// call script's callback to allow extra products
|
||||
ExportProductList extraProducts;
|
||||
@@ -279,7 +301,7 @@ namespace AZ::SceneAPI::Behaviors
|
||||
}
|
||||
};
|
||||
|
||||
if (LoadPython(context.GetScene()))
|
||||
if (LoadPython(context.GetScene(), scriptPath))
|
||||
{
|
||||
EditorPythonConsoleNotificationHandler logger;
|
||||
m_editorPythonEventsInterface->ExecuteWithLock(executeCallback);
|
||||
@@ -306,23 +328,19 @@ namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
using namespace AzToolsFramework;
|
||||
|
||||
// This behavior persists on the same AssetBuilder. Clear the script file name so that if
|
||||
// this builder processes a scene file with a script file name, and then later processes
|
||||
// a scene without a script file name, it won't run the old script on the new scene.
|
||||
m_scriptFilename.clear();
|
||||
|
||||
if (action != ManifestAction::Update)
|
||||
{
|
||||
return Events::ProcessingResult::Ignored;
|
||||
}
|
||||
|
||||
if (LoadPython(scene))
|
||||
AZStd::string scriptPath;
|
||||
if (LoadPython(scene, scriptPath))
|
||||
{
|
||||
AZStd::string manifestUpdate;
|
||||
auto executeCallback = [this, &scene, &manifestUpdate]()
|
||||
auto executeCallback = [&scene, &manifestUpdate, &scriptPath]()
|
||||
{
|
||||
EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename,
|
||||
m_scriptFilename.c_str());
|
||||
scriptPath.c_str());
|
||||
|
||||
ScriptBuildingNotificationBus::BroadcastResult(manifestUpdate, &ScriptBuildingNotificationBus::Events::OnUpdateManifest,
|
||||
scene);
|
||||
@@ -331,6 +349,9 @@ namespace AZ::SceneAPI::Behaviors
|
||||
EditorPythonConsoleNotificationHandler logger;
|
||||
m_editorPythonEventsInterface->ExecuteWithLock(executeCallback);
|
||||
|
||||
EntityUtilityBus::Broadcast(&EntityUtilityBus::Events::ResetEntityContext);
|
||||
AZ::Interface<Prefab::PrefabSystemComponentInterface>::Get()->RemoveAllTemplates();
|
||||
|
||||
// attempt to load the manifest string back to a JSON-scene-manifest
|
||||
auto sceneManifestLoader = AZStd::make_unique<AZ::SceneAPI::Containers::SceneManifest>();
|
||||
auto loadOutcome = sceneManifestLoader->LoadFromString(manifestUpdate);
|
||||
@@ -347,4 +368,8 @@ namespace AZ::SceneAPI::Behaviors
|
||||
return Events::ProcessingResult::Ignored;
|
||||
}
|
||||
|
||||
void ScriptProcessorRuleBehavior::GetManifestDependencyPaths(AZStd::vector<AZStd::string>& paths)
|
||||
{
|
||||
paths.emplace_back("/scriptFilename");
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace AZ::SceneAPI::Behaviors
|
||||
, public Events::AssetImportRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
|
||||
AZ_COMPONENT(ScriptProcessorRuleBehavior, "{24054E73-1B92-43B0-AC13-174B2F0E3F66}", SceneCore::BehaviorComponent);
|
||||
|
||||
~ScriptProcessorRuleBehavior() override = default;
|
||||
@@ -44,22 +45,21 @@ namespace AZ::SceneAPI::Behaviors
|
||||
SCENE_DATA_API void Activate() override;
|
||||
SCENE_DATA_API void Deactivate() override;
|
||||
static void Reflect(ReflectContext* context);
|
||||
|
||||
|
||||
// AssetImportRequestBus::Handler
|
||||
SCENE_DATA_API Events::ProcessingResult UpdateManifest(
|
||||
Containers::Scene& scene,
|
||||
ManifestAction action,
|
||||
RequestingApplication requester) override;
|
||||
|
||||
|
||||
SCENE_DATA_API void GetManifestDependencyPaths(AZStd::vector<AZStd::string>& paths) override;
|
||||
protected:
|
||||
bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene);
|
||||
bool LoadPython(const AZ::SceneAPI::Containers::Scene& scene, AZStd::string& scriptPath);
|
||||
void UnloadPython();
|
||||
bool DoPrepareForExport(Events::PreExportEventContext& context);
|
||||
|
||||
private:
|
||||
AzToolsFramework::EditorPythonEventsInterface* m_editorPythonEventsInterface = nullptr;
|
||||
AZStd::string m_scriptFilename;
|
||||
|
||||
struct ExportEventHandler;
|
||||
AZStd::shared_ptr<ExportEventHandler> m_exportEventHandler;
|
||||
|
||||
@@ -147,22 +147,6 @@
|
||||
},
|
||||
"LoadAction": "Clear"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "ScatterDistanceOutput",
|
||||
"SlotType": "Output",
|
||||
"ScopeAttachmentUsage": "RenderTarget",
|
||||
"LoadStoreAction": {
|
||||
"ClearValue": {
|
||||
"Value": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"LoadAction": "Clear"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ImageAttachments": [
|
||||
@@ -257,23 +241,6 @@
|
||||
"AssetRef": {
|
||||
"FilePath": "Textures/BRDFTexture.attimage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "ScatterDistanceImage",
|
||||
"SizeSource": {
|
||||
"Source": {
|
||||
"Pass": "Parent",
|
||||
"Attachment": "Output"
|
||||
}
|
||||
},
|
||||
"MultisampleSource": {
|
||||
"Pass": "This",
|
||||
"Attachment": "DepthStencilInputOutput"
|
||||
},
|
||||
"ImageDescriptor": {
|
||||
"Format": "R11G11B10_FLOAT",
|
||||
"SharedQueueMask": "Graphics"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Connections": [
|
||||
@@ -318,13 +285,6 @@
|
||||
"Pass": "This",
|
||||
"Attachment": "BRDFTexture"
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalSlot": "ScatterDistanceOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "This",
|
||||
"Attachment": "ScatterDistanceImage"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// box-filtered from the MSAA sub-pixels of the reflection texture.
|
||||
|
||||
#include <scenesrg.srgi>
|
||||
#include <viewsrg.srgi>
|
||||
|
||||
#include <Atom/Features/PostProcessing/FullscreenVertexUtil.azsli>
|
||||
#include <Atom/Features/PostProcessing/FullscreenVertexInfo.azsli>
|
||||
@@ -26,8 +25,6 @@ ShaderResourceGroup PassSrg : SRG_PerPass
|
||||
Texture2DMS<float4> m_reflection;
|
||||
}
|
||||
|
||||
#include <Atom/RPI/ShaderResourceGroups/DefaultDrawSrg.azsli>
|
||||
|
||||
// Vertex Shader
|
||||
VSOutput MainVS(VSInput input)
|
||||
{
|
||||
|
||||
@@ -286,8 +286,11 @@ namespace Blast
|
||||
DefaultConfigurationPath, globalConfiguration);
|
||||
AZ_Warning("Blast", loaded, "Failed to load Blast configuration, initializing with default configs.");
|
||||
|
||||
SetGlobalConfiguration(globalConfiguration);
|
||||
SaveConfiguration();
|
||||
ApplyGlobalConfiguration(globalConfiguration);
|
||||
if (!loaded)
|
||||
{
|
||||
SaveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
void BlastSystemComponent::SaveConfiguration()
|
||||
@@ -394,8 +397,13 @@ namespace Blast
|
||||
|
||||
void BlastSystemComponent::SetGlobalConfiguration(const BlastGlobalConfiguration& globalConfiguration)
|
||||
{
|
||||
m_configuration = globalConfiguration;
|
||||
ApplyGlobalConfiguration(globalConfiguration);
|
||||
SaveConfiguration();
|
||||
}
|
||||
|
||||
void BlastSystemComponent::ApplyGlobalConfiguration(const BlastGlobalConfiguration& globalConfiguration)
|
||||
{
|
||||
m_configuration = globalConfiguration;
|
||||
|
||||
{
|
||||
AZ::Data::Asset<Blast::BlastMaterialLibraryAsset>& materialLibrary = m_configuration.m_materialLibrary;
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace Blast
|
||||
void InitPhysics();
|
||||
void DeactivatePhysics();
|
||||
|
||||
void ApplyGlobalConfiguration(const BlastGlobalConfiguration& materialLibrary);
|
||||
void RegisterCommands();
|
||||
|
||||
// Internal helper functions & classes
|
||||
|
||||
@@ -36,6 +36,7 @@ ly_add_target(
|
||||
PUBLIC
|
||||
AZ::AtomCore
|
||||
Gem::Atom_RPI.Public
|
||||
Gem::AtomLyIntegration_CommonFeatures.Static
|
||||
Gem::LmbrCentral
|
||||
COMPILE_DEFINITIONS
|
||||
PUBLIC
|
||||
|
||||
@@ -142,12 +142,31 @@ namespace EMotionFX
|
||||
{
|
||||
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
|
||||
AZ::TickBus::Handler::BusConnect();
|
||||
|
||||
// Remember the lod type and level so that we can set it back to the previous one on deactivation of the component.
|
||||
AZ::Render::MeshComponentRequestBus::EventResult(m_previousLodType,
|
||||
GetEntityId(),
|
||||
&AZ::Render::MeshComponentRequestBus::Events::GetLodType);
|
||||
|
||||
if (m_actorInstance)
|
||||
{
|
||||
m_previousLodLevel = m_actorInstance->GetLODLevel();
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleLODComponent::Deactivate()
|
||||
{
|
||||
AZ::TickBus::Handler::BusDisconnect();
|
||||
ActorComponentNotificationBus::Handler::BusDisconnect();
|
||||
|
||||
AZ::Render::MeshComponentRequestBus::Event(GetEntityId(),
|
||||
&AZ::Render::MeshComponentRequestBus::Events::SetLodType,
|
||||
m_previousLodType);
|
||||
|
||||
if (m_actorInstance)
|
||||
{
|
||||
m_actorInstance->SetLODLevel(m_previousLodLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void SimpleLODComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
|
||||
@@ -183,7 +202,7 @@ namespace EMotionFX
|
||||
return max - 1;
|
||||
}
|
||||
|
||||
void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance * actorInstance, const Configuration& configuration, AZ::EntityId entityId)
|
||||
void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance* actorInstance, const Configuration& configuration, AZ::EntityId entityId)
|
||||
{
|
||||
if (actorInstance)
|
||||
{
|
||||
@@ -201,15 +220,30 @@ namespace EMotionFX
|
||||
AZ::RPI::ViewportContextPtr defaultViewportContext =
|
||||
viewportContextManager->GetViewportContextByName(viewportContextManager->GetDefaultViewportContextName());
|
||||
const float distance = worldPos.GetDistance(defaultViewportContext->GetCameraTransform().GetTranslation());
|
||||
const size_t lodByDistance = GetLodByDistance(configuration.m_lodDistances, distance);
|
||||
actorInstance->SetLODLevel(lodByDistance);
|
||||
const size_t requestedLod = GetLodByDistance(configuration.m_lodDistances, distance);
|
||||
actorInstance->SetLODLevel(requestedLod);
|
||||
|
||||
if (configuration.m_enableLodSampling)
|
||||
{
|
||||
const float animGraphSampleRate = configuration.m_lodSampleRates[lodByDistance];
|
||||
const float animGraphSampleRate = configuration.m_lodSampleRates[requestedLod];
|
||||
const float updateRateInSeconds = animGraphSampleRate > 0.0f ? 1.0f / animGraphSampleRate : 0.0f;
|
||||
actorInstance->SetMotionSamplingRate(updateRateInSeconds);
|
||||
}
|
||||
|
||||
// Disable the automatic mesh LOD level adjustment based on screen space in case a simple LOD component is present.
|
||||
// The simple LOD component overrides the mesh LOD level and syncs the skeleton with the mesh LOD level.
|
||||
AZ::Render::MeshComponentRequestBus::Event(entityId,
|
||||
&AZ::Render::MeshComponentRequestBus::Events::SetLodType,
|
||||
AZ::RPI::Cullable::LodType::SpecificLod);
|
||||
|
||||
// When setting the actor instance LOD level, a change is just requested and with the next update it will get applied.
|
||||
// This means that the current LOD level might differ from the requested one. We need to sync the Atom LOD level with the
|
||||
// current LOD level of the actor instance to avoid skinning artifacts. The requested LOD level will be present and applied
|
||||
// the following frame.
|
||||
const size_t currentLod = actorInstance->GetLODLevel();
|
||||
AZ::Render::MeshComponentRequestBus::Event(entityId,
|
||||
&AZ::Render::MeshComponentRequestBus::Events::SetLodOverride,
|
||||
static_cast<AZ::RPI::Cullable::LodOverride>(currentLod));
|
||||
}
|
||||
}
|
||||
} // namespace integration
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
#include <Integration/Assets/MotionAsset.h>
|
||||
#include <Integration/ActorComponentBus.h>
|
||||
|
||||
#include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
|
||||
|
||||
namespace EMotionFX
|
||||
{
|
||||
@@ -93,6 +93,9 @@ namespace EMotionFX
|
||||
|
||||
Configuration m_configuration; // Component configuration.
|
||||
EMotionFX::ActorInstance* m_actorInstance; // Associated actor instance (retrieved from Actor Component).
|
||||
|
||||
AZ::RPI::Cullable::LodType m_previousLodType = AZ::RPI::Cullable::LodType::Default;
|
||||
size_t m_previousLodLevel = 0;
|
||||
};
|
||||
|
||||
} // namespace Integration
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
#include "EditorSystemComponent.h"
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzFramework/Physics/SystemBus.h>
|
||||
#include <AzFramework/Physics/Collision/CollisionEvents.h>
|
||||
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
|
||||
@@ -23,7 +25,7 @@
|
||||
|
||||
namespace PhysX
|
||||
{
|
||||
constexpr const char* DefaultAssetFilePath = "Physics/SurfaceTypeMaterialLibrary";
|
||||
constexpr const char* DefaultAssetFilePath = "Assets/Physics/SurfaceTypeMaterialLibrary";
|
||||
constexpr const char* TemplateAssetFilename = "PhysX/TemplateMaterialLibrary";
|
||||
|
||||
static AZStd::optional<AZ::Data::Asset<AZ::Data::AssetData>> GetMaterialLibraryTemplate()
|
||||
@@ -67,7 +69,7 @@ namespace PhysX
|
||||
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, relativePath.c_str(), assetType, true /*autoRegisterIfNotFound*/);
|
||||
|
||||
AZ::Data::Asset<AZ::Data::AssetData> newAsset =
|
||||
AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
|
||||
AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
if (auto* newMaterialLibraryData = azrtti_cast<Physics::MaterialLibraryAsset*>(newAsset.GetData()))
|
||||
{
|
||||
@@ -138,6 +140,14 @@ namespace PhysX
|
||||
if (auto retrievedMaterialLibrary = RetrieveDefaultMaterialLibrary())
|
||||
{
|
||||
physxSystem->UpdateMaterialLibrary(retrievedMaterialLibrary.value());
|
||||
|
||||
// After setting the default material library, save the physx configuration.
|
||||
auto saveCallback = []([[maybe_unused]] const PhysXSystemConfiguration& config, [[maybe_unused]] PhysXSettingsRegistryManager::Result result)
|
||||
{
|
||||
AZ_Warning("PhysX", result == PhysXSettingsRegistryManager::Result::Success,
|
||||
"Unable to save the PhysX configuration after setting default material library.");
|
||||
};
|
||||
physxSystem->GetSettingsRegistryManager().SaveSystemConfiguration(physxSystem->GetPhysXConfiguration(), saveCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,11 +246,15 @@ namespace PhysX
|
||||
if (!resultAssetId.IsValid())
|
||||
{
|
||||
// No file for the default material library, create it
|
||||
const char* assetRoot = AZ::IO::FileIOBase::GetInstance()->GetAlias("@projectsourceassets@");
|
||||
AZStd::string fullPath;
|
||||
AzFramework::StringFunc::Path::ConstructFull(assetRoot, DefaultAssetFilePath, assetExtension.c_str(), fullPath);
|
||||
AZ::IO::Path fullPath;
|
||||
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Get(fullPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
|
||||
}
|
||||
fullPath /= DefaultAssetFilePath;
|
||||
fullPath.ReplaceExtension(AZ::IO::PathView(assetExtension));
|
||||
|
||||
if (auto materialLibraryOpt = CreateMaterialLibrary(fullPath, relativePath))
|
||||
if (auto materialLibraryOpt = CreateMaterialLibrary(fullPath.Native(), relativePath))
|
||||
{
|
||||
return materialLibraryOpt;
|
||||
}
|
||||
|
||||
@@ -511,10 +511,12 @@ namespace PhysX
|
||||
|
||||
materialLibrary.BlockUntilLoadComplete();
|
||||
|
||||
AZ_Warning("PhysX", (materialLibrary.GetData() != nullptr),
|
||||
const bool loadedSuccessfully = materialLibrary.GetData() != nullptr && !materialLibrary.IsError();
|
||||
|
||||
AZ_Warning("PhysX", loadedSuccessfully,
|
||||
"LoadDefaultMaterialLibrary: Default Material Library asset data is invalid.");
|
||||
|
||||
return materialLibrary.GetData() != nullptr && !materialLibrary.IsError();
|
||||
return loadedSuccessfully;
|
||||
}
|
||||
|
||||
//TEMP -- until these are fully moved over here
|
||||
|
||||
@@ -458,7 +458,13 @@ namespace PhysX
|
||||
{
|
||||
const PhysXSystemConfiguration defaultConfig = PhysXSystemConfiguration::CreateDefault();
|
||||
m_physXSystem->Initialize(&defaultConfig);
|
||||
registryManager.SaveSystemConfiguration(defaultConfig, {});
|
||||
|
||||
auto saveCallback = []([[maybe_unused]] const PhysXSystemConfiguration& config, [[maybe_unused]] PhysXSettingsRegistryManager::Result result)
|
||||
{
|
||||
AZ_Warning("PhysX", result == PhysXSettingsRegistryManager::Result::Success,
|
||||
"Unable to save the default PhysX configuration.");
|
||||
};
|
||||
registryManager.SaveSystemConfiguration(defaultConfig, saveCallback);
|
||||
}
|
||||
|
||||
//Load the DefaultSceneConfig
|
||||
@@ -471,7 +477,13 @@ namespace PhysX
|
||||
{
|
||||
const AzPhysics::SceneConfiguration defaultConfig = AzPhysics::SceneConfiguration::CreateDefault();
|
||||
m_physXSystem->UpdateDefaultSceneConfiguration(defaultConfig);
|
||||
registryManager.SaveDefaultSceneConfiguration(defaultConfig, {});
|
||||
|
||||
auto saveCallback = []([[maybe_unused]] const AzPhysics::SceneConfiguration& config, [[maybe_unused]] PhysXSettingsRegistryManager::Result result)
|
||||
{
|
||||
AZ_Warning("PhysX", result == PhysXSettingsRegistryManager::Result::Success,
|
||||
"Unable to save the default Scene configuration.");
|
||||
};
|
||||
registryManager.SaveDefaultSceneConfiguration(defaultConfig, saveCallback);
|
||||
}
|
||||
|
||||
//load the debug configuration and initialize the PhysX debug interface
|
||||
@@ -486,7 +498,13 @@ namespace PhysX
|
||||
{
|
||||
const Debug::DebugConfiguration defaultConfig = Debug::DebugConfiguration::CreateDefault();
|
||||
debug->Initialize(defaultConfig);
|
||||
registryManager.SaveDebugConfiguration(defaultConfig, {});
|
||||
|
||||
auto saveCallback = []([[maybe_unused]] const Debug::DebugConfiguration& config, [[maybe_unused]] PhysXSettingsRegistryManager::Result result)
|
||||
{
|
||||
AZ_Warning("PhysX", result == PhysXSettingsRegistryManager::Result::Success,
|
||||
"Unable to save the default PhysX Debug configuration.");
|
||||
};
|
||||
registryManager.SaveDebugConfiguration(defaultConfig, saveCallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ endif()
|
||||
ly_add_target(
|
||||
NAME PrefabBuilder.Static STATIC
|
||||
NAMESPACE Gem
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
FILES_CMAKE
|
||||
prefabbuilder_files.cmake
|
||||
BUILD_DEPENDENCIES
|
||||
@@ -20,6 +23,9 @@ ly_add_target(
|
||||
AZ::AzCore
|
||||
AZ::AzToolsFramework
|
||||
AZ::AssetBuilderSDK
|
||||
AZ::SceneCore
|
||||
AZ::SceneData
|
||||
3rdParty::RapidJSON
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
@@ -35,7 +41,8 @@ ly_add_target(
|
||||
Gem::PrefabBuilder.Static
|
||||
)
|
||||
|
||||
# the prefab builder only needs to be active in builders
|
||||
# create an alias for the tool version
|
||||
ly_create_alias(NAME PrefabBuilder.Tools NAMESPACE Gem TARGETS Gem::PrefabBuilder.Builders)
|
||||
|
||||
# we automatically add this gem, if it is present, to all our known set of builder applications:
|
||||
ly_enable_gems(GEMS PrefabBuilder)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <AzCore/Module/Module.h>
|
||||
#include <PrefabBuilderComponent.h>
|
||||
#include <PrefabGroup/PrefabGroupBehavior.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
@@ -22,7 +23,8 @@ namespace AZ::Prefab
|
||||
: Module()
|
||||
{
|
||||
m_descriptors.insert(m_descriptors.end(), {
|
||||
PrefabBuilderComponent::CreateDescriptor()
|
||||
PrefabBuilderComponent::CreateDescriptor(),
|
||||
AZ::SceneAPI::Behaviors::PrefabGroupBehavior::CreateDescriptor()
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <Application/ToolsApplication.h>
|
||||
#include <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Component/ComponentApplication.h>
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 <AzCore/RTTI/RTTI.h>
|
||||
#include <SceneAPI/SceneCore/DataTypes/Groups/ISceneNodeGroup.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
|
||||
namespace AZ::SceneAPI::DataTypes
|
||||
{
|
||||
class IPrefabGroup
|
||||
: public ISceneNodeGroup
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IPrefabGroup, "{7E50FAEF-3379-4521-99C5-B428FDEE3B7B}", ISceneNodeGroup);
|
||||
|
||||
~IPrefabGroup() override = default;
|
||||
virtual AzToolsFramework::Prefab::PrefabDomConstReference GetPrefabDomRef() const = 0;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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 <PrefabBuilderTests.h>
|
||||
#include <PrefabGroup/IPrefabGroup.h>
|
||||
#include <PrefabGroup/PrefabGroup.h>
|
||||
#include <PrefabGroup/PrefabGroupBehavior.h>
|
||||
#include <AzTest/Utils.h>
|
||||
#include <Tests/AssetSystemMocks.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
||||
#include <SceneAPI/SceneCore/Events/CallProcessorBus.h>
|
||||
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
|
||||
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
|
||||
#include <SceneAPI/SceneCore/SceneCoreStandaloneAllocator.h>
|
||||
|
||||
#include <PrefabGroup/PrefabBehaviorTests.inl>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class PrefabBehaviorTests
|
||||
: public PrefabBuilderTests
|
||||
{
|
||||
public:
|
||||
static void SetUpTestCase()
|
||||
{
|
||||
// Allocator needed by SceneCore
|
||||
if (!AZ::AllocatorInstance<AZ::SystemAllocator>().IsReady())
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>().Create();
|
||||
}
|
||||
AZ::SceneAPI::SceneCoreStandaloneAllocator::Initialize(AZ::Environment::GetInstance());
|
||||
}
|
||||
|
||||
static void TearDownTestCase()
|
||||
{
|
||||
AZ::SceneAPI::SceneCoreStandaloneAllocator::TearDown();
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>().Destroy();
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
PrefabBuilderTests::SetUp();
|
||||
m_prefabGroupBehavior = AZStd::make_unique<AZ::SceneAPI::Behaviors::PrefabGroupBehavior>();
|
||||
m_prefabGroupBehavior->Activate();
|
||||
|
||||
// Mocking the asset system replacing the AssetSystem::AssetSystemComponent
|
||||
AZ::Entity* systemEntity = m_app.FindEntity(AZ::SystemEntityId);
|
||||
systemEntity->FindComponent<AzToolsFramework::AssetSystem::AssetSystemComponent>()->Deactivate();
|
||||
using namespace testing;
|
||||
ON_CALL(m_assetSystemRequestMock, GetSourceInfoBySourcePath(_, _, _)).WillByDefault([](auto* path, auto& info, auto&)
|
||||
{
|
||||
return PrefabBehaviorTests::OnGetSourceInfoBySourcePath(path, info);
|
||||
});
|
||||
m_assetSystemRequestMock.BusConnect();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_assetSystemRequestMock.BusDisconnect();
|
||||
|
||||
m_prefabGroupBehavior->Deactivate();
|
||||
m_prefabGroupBehavior.reset();
|
||||
|
||||
PrefabBuilderTests::TearDown();
|
||||
}
|
||||
|
||||
static bool OnGetSourceInfoBySourcePath(AZStd::string_view sourcePath, AZ::Data::AssetInfo& assetInfo)
|
||||
{
|
||||
if (sourcePath == AZStd::string_view("mock"))
|
||||
{
|
||||
assetInfo.m_assetId = AZ::Uuid::CreateRandom();
|
||||
assetInfo.m_assetType = azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>();
|
||||
assetInfo.m_relativePath = "mock/path";
|
||||
assetInfo.m_sizeBytes = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct TestPreExportEventContext
|
||||
{
|
||||
TestPreExportEventContext()
|
||||
: m_scene("test_context")
|
||||
{
|
||||
using namespace AZ::SceneAPI::Events;
|
||||
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
|
||||
}
|
||||
|
||||
void SetOutputDirectory(AZStd::string outputDirectory)
|
||||
{
|
||||
using namespace AZ::SceneAPI::Events;
|
||||
m_outputDirectory = AZStd::move(outputDirectory);
|
||||
m_preExportEventContext = AZStd::make_unique<PreExportEventContext>(m_productList, m_outputDirectory, m_scene, "mock");
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::SceneAPI::Events::PreExportEventContext> m_preExportEventContext;
|
||||
AZ::SceneAPI::Events::ExportProductList m_productList;
|
||||
AZStd::string m_outputDirectory;
|
||||
AZ::SceneAPI::Containers::Scene m_scene;
|
||||
};
|
||||
|
||||
AZStd::unique_ptr<AZ::SceneAPI::Behaviors::PrefabGroupBehavior> m_prefabGroupBehavior;
|
||||
testing::NiceMock<UnitTests::MockAssetSystemRequest> m_assetSystemRequestMock;
|
||||
};
|
||||
|
||||
TEST_F(PrefabBehaviorTests, PrefabBehavior_EmptyContextIgnored_Works)
|
||||
{
|
||||
auto context = TestPreExportEventContext{};
|
||||
|
||||
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
|
||||
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
|
||||
result,
|
||||
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
|
||||
context.m_preExportEventContext.get());
|
||||
|
||||
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Ignored);
|
||||
}
|
||||
|
||||
TEST_F(PrefabBehaviorTests, PrefabBehavior_SimplePrefab_Works)
|
||||
{
|
||||
auto context = TestPreExportEventContext{};
|
||||
|
||||
// check for the file at <temp_directory>/mock/fake_prefab.procprefab
|
||||
AZ::Test::ScopedAutoTempDirectory tempDir;
|
||||
context.SetOutputDirectory(tempDir.GetDirectory());
|
||||
|
||||
auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(Data::jsonPrefab);
|
||||
ASSERT_TRUE(jsonOutcome);
|
||||
|
||||
auto prefabGroup = AZStd::make_shared<AZ::SceneAPI::SceneData::PrefabGroup>();
|
||||
prefabGroup.get()->SetId(AZ::Uuid::CreateRandom());
|
||||
prefabGroup.get()->SetName("fake_prefab");
|
||||
prefabGroup.get()->SetPrefabDom(AZStd::move(jsonOutcome.GetValue()));
|
||||
context.m_scene.GetManifest().AddEntry(prefabGroup);
|
||||
context.m_scene.SetSource("mock", AZ::Uuid::CreateRandom());
|
||||
|
||||
auto result = AZ::SceneAPI::Events::ProcessingResult::Failure;
|
||||
AZ::SceneAPI::Events::CallProcessorBus::BroadcastResult(
|
||||
result,
|
||||
&AZ::SceneAPI::Events::CallProcessorBus::Events::Process,
|
||||
context.m_preExportEventContext.get());
|
||||
|
||||
EXPECT_EQ(result, AZ::SceneAPI::Events::ProcessingResult::Success);
|
||||
|
||||
AZStd::string pathStr;
|
||||
AzFramework::StringFunc::Path::ConstructFull(tempDir.GetDirectory(), "mock/fake_prefab.procprefab", pathStr, true);
|
||||
if (!AZ::IO::SystemFile::Exists(pathStr.c_str()))
|
||||
{
|
||||
AZ_Warning("testing", false, "The product asset (%s) is missing", pathStr.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
namespace Data
|
||||
{
|
||||
const char* jsonPrefab = R"JSON(
|
||||
{
|
||||
"ContainerEntity": {
|
||||
"Id": "ContainerEntity",
|
||||
"Name": "test_template_1",
|
||||
"Components": {
|
||||
"Component_[12122553907433030840]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 12122553907433030840
|
||||
},
|
||||
"Component_[5666150279650800686]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 5666150279650800686,
|
||||
"Parent Entity": ""
|
||||
},
|
||||
"Component_[8790726658974076423]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 8790726658974076423
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entities": {
|
||||
"Entity_[1588652751483]": {
|
||||
"Id": "Entity_[1588652751483]",
|
||||
"Name": "root",
|
||||
"Components": {
|
||||
"Component_[11872748096995986607]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 11872748096995986607,
|
||||
"Parent Entity": "ContainerEntity",
|
||||
"Transform Data": {
|
||||
"Rotate": [
|
||||
0.0,
|
||||
0.10000000149011612,
|
||||
180.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"Component_[12138841758570858610]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 12138841758570858610
|
||||
},
|
||||
"Component_[15735658354806796004]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 15735658354806796004
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[1592947718779]": {
|
||||
"Id": "Entity_[1592947718779]",
|
||||
"Name": "cube",
|
||||
"Components": {
|
||||
"Component_[2505301170249328189]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 2505301170249328189
|
||||
},
|
||||
"Component_[3716170894544198343]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 3716170894544198343,
|
||||
"Parent Entity": "Entity_[1588652751483]"
|
||||
},
|
||||
"Component_[5862175558847453681]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 5862175558847453681
|
||||
}
|
||||
}
|
||||
},
|
||||
"Entity_[1597242686075]": {
|
||||
"Id": "Entity_[1597242686075]",
|
||||
"Name": "cubeKid",
|
||||
"Components": {
|
||||
"Component_[10128771992421174485]": {
|
||||
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
|
||||
"Id": 10128771992421174485,
|
||||
"Parent Entity": "Entity_[1592947718779]"
|
||||
},
|
||||
"Component_[14936165953779771344]": {
|
||||
"$type": "EditorVisibilityComponent",
|
||||
"Id": 14936165953779771344
|
||||
},
|
||||
"Component_[403416213715997356]": {
|
||||
"$type": "EditorOnlyEntityComponent",
|
||||
"Id": 403416213715997356
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)JSON";
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/PrefabGroup.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/JSON/error/error.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
|
||||
namespace AZ::SceneAPI::SceneData
|
||||
{
|
||||
// PrefabGroup
|
||||
|
||||
PrefabGroup::PrefabGroup()
|
||||
: m_id(Uuid::CreateNull())
|
||||
, m_name()
|
||||
{
|
||||
}
|
||||
|
||||
const AZStd::string& PrefabGroup::GetName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void PrefabGroup::SetName(AZStd::string name)
|
||||
{
|
||||
m_name = AZStd::move(name);
|
||||
}
|
||||
|
||||
const Uuid& PrefabGroup::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void PrefabGroup::SetId(Uuid id)
|
||||
{
|
||||
m_id = AZStd::move(id);
|
||||
}
|
||||
|
||||
Containers::RuleContainer& PrefabGroup::GetRuleContainer()
|
||||
{
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
const Containers::RuleContainer& PrefabGroup::GetRuleContainerConst() const
|
||||
{
|
||||
return m_rules;
|
||||
}
|
||||
|
||||
DataTypes::ISceneNodeSelectionList& PrefabGroup::GetSceneNodeSelectionList()
|
||||
{
|
||||
return m_nodeSelectionList;
|
||||
}
|
||||
|
||||
const DataTypes::ISceneNodeSelectionList& PrefabGroup::GetSceneNodeSelectionList() const
|
||||
{
|
||||
return m_nodeSelectionList;
|
||||
}
|
||||
|
||||
void PrefabGroup::SetPrefabDom(AzToolsFramework::Prefab::PrefabDom prefabDom)
|
||||
{
|
||||
m_prefabDomData = AZStd::make_shared<Prefab::PrefabDomData>();
|
||||
m_prefabDomData->CopyValue(prefabDom);
|
||||
}
|
||||
|
||||
AzToolsFramework::Prefab::PrefabDomConstReference PrefabGroup::GetPrefabDomRef() const
|
||||
{
|
||||
if (m_prefabDomData)
|
||||
{
|
||||
return m_prefabDomData->GetValue();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void PrefabGroup::Reflect(ReflectContext* context)
|
||||
{
|
||||
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<DataTypes::IPrefabGroup, DataTypes::ISceneNodeGroup>()
|
||||
->Version(1);
|
||||
|
||||
serializeContext->Class<PrefabGroup, DataTypes::IPrefabGroup>()
|
||||
->Version(1)
|
||||
->Field("name", &PrefabGroup::m_name)
|
||||
->Field("nodeSelectionList", &PrefabGroup::m_nodeSelectionList)
|
||||
->Field("rules", &PrefabGroup::m_rules)
|
||||
->Field("id", &PrefabGroup::m_id)
|
||||
->Field("prefabDomData", &PrefabGroup::m_prefabDomData);
|
||||
}
|
||||
|
||||
BehaviorContext* behaviorContext = azrtti_cast<BehaviorContext*>(context);
|
||||
if (behaviorContext)
|
||||
{
|
||||
auto setPrefabDomData = [](PrefabGroup& self, const AZStd::string& json)
|
||||
{
|
||||
auto jsonOutcome = JsonSerializationUtils::ReadJsonString(json);
|
||||
if (jsonOutcome.IsSuccess())
|
||||
{
|
||||
self.SetPrefabDom(AZStd::move(jsonOutcome.GetValue()));
|
||||
return true;
|
||||
}
|
||||
AZ_Error("prefab", false, "Set PrefabDom failed (%s)", jsonOutcome.GetError().c_str());
|
||||
return false;
|
||||
};
|
||||
|
||||
auto getPrefabDomData = [](const PrefabGroup& self) -> AZStd::string
|
||||
{
|
||||
if (self.GetPrefabDomRef().has_value() == false)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
AZStd::string buffer;
|
||||
JsonSerializationUtils::WriteJsonString(self.GetPrefabDomRef().value(), buffer);
|
||||
return buffer;
|
||||
};
|
||||
|
||||
behaviorContext->Class<PrefabGroup>()
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
|
||||
->Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)
|
||||
->Attribute(Script::Attributes::Module, "prefab")
|
||||
->Property("name", BehaviorValueProperty(&PrefabGroup::m_name))
|
||||
->Property("id", BehaviorValueProperty(&PrefabGroup::m_id))
|
||||
->Property("prefabDomData", getPrefabDomData, setPrefabDomData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/IPrefabGroup.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <SceneAPI/SceneCore/Containers/RuleContainer.h>
|
||||
#include <SceneAPI/SceneData/ManifestBase/SceneNodeSelectionList.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace AZ::SceneAPI::Containers
|
||||
{
|
||||
class Scene;
|
||||
}
|
||||
|
||||
namespace AZ::SceneAPI::SceneData
|
||||
{
|
||||
class PrefabGroup final
|
||||
: public DataTypes::IPrefabGroup
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(PrefabGroup, "{99FE3C6F-5B55-4D8B-8013-2708010EC715}", DataTypes::IPrefabGroup);
|
||||
AZ_CLASS_ALLOCATOR(PrefabGroup, SystemAllocator, 0);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
PrefabGroup();
|
||||
~PrefabGroup() override = default;
|
||||
|
||||
// DataTypes::IPrefabGroup
|
||||
AzToolsFramework::Prefab::PrefabDomConstReference GetPrefabDomRef() const override;
|
||||
const AZStd::string& GetName() const override;
|
||||
const Uuid& GetId() const override;
|
||||
Containers::RuleContainer& GetRuleContainer() override;
|
||||
const Containers::RuleContainer& GetRuleContainerConst() const override;
|
||||
DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() override;
|
||||
const DataTypes::ISceneNodeSelectionList& GetSceneNodeSelectionList() const override;
|
||||
|
||||
// Concrete API
|
||||
void SetId(Uuid id);
|
||||
void SetName(AZStd::string name);
|
||||
void SetPrefabDom(AzToolsFramework::Prefab::PrefabDom prefabDom);
|
||||
|
||||
private:
|
||||
SceneNodeSelectionList m_nodeSelectionList;
|
||||
Containers::RuleContainer m_rules;
|
||||
AZStd::string m_name;
|
||||
Uuid m_id;
|
||||
AZStd::shared_ptr<Prefab::PrefabDomData> m_prefabDomData;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/PrefabGroupBehavior.h>
|
||||
#include <PrefabGroup/PrefabGroup.h>
|
||||
#include <PrefabGroup/ProceduralAssetHandler.h>
|
||||
#include <AzCore/Asset/AssetManagerBus.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
#include <AzCore/JSON/error/error.h>
|
||||
#include <AzCore/JSON/prettywriter.h>
|
||||
#include <AzCore/JSON/stringbuffer.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <SceneAPI/SceneCore/Components/ExportingComponent.h>
|
||||
#include <SceneAPI/SceneCore/Containers/Scene.h>
|
||||
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
|
||||
#include <SceneAPI/SceneCore/Events/ExportEventContext.h>
|
||||
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
|
||||
#include <SceneAPI/SceneCore/Utilities/FileUtilities.h>
|
||||
|
||||
namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
//
|
||||
// ExportEventHandler
|
||||
//
|
||||
|
||||
struct PrefabGroupBehavior::ExportEventHandler final
|
||||
: public AZ::SceneAPI::SceneCore::ExportingComponent
|
||||
{
|
||||
using PreExportEventContextFunction = AZStd::function<Events::ProcessingResult(Events::PreExportEventContext&)>;
|
||||
PreExportEventContextFunction m_preExportEventContextFunction;
|
||||
AZ::Prefab::PrefabGroupAssetHandler m_prefabGroupAssetHandler;
|
||||
|
||||
ExportEventHandler() = delete;
|
||||
|
||||
ExportEventHandler(PreExportEventContextFunction function)
|
||||
: m_preExportEventContextFunction(AZStd::move(function))
|
||||
{
|
||||
BindToCall(&ExportEventHandler::PrepareForExport);
|
||||
AZ::SceneAPI::SceneCore::ExportingComponent::Activate();
|
||||
}
|
||||
|
||||
~ExportEventHandler()
|
||||
{
|
||||
AZ::SceneAPI::SceneCore::ExportingComponent::Deactivate();
|
||||
}
|
||||
|
||||
Events::ProcessingResult PrepareForExport(Events::PreExportEventContext& context)
|
||||
{
|
||||
return m_preExportEventContextFunction(context);
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// PrefabGroupBehavior
|
||||
//
|
||||
|
||||
void PrefabGroupBehavior::Activate()
|
||||
{
|
||||
m_exportEventHandler = AZStd::make_shared<ExportEventHandler>([this](auto& context)
|
||||
{
|
||||
return this->OnPrepareForExport(context);
|
||||
});
|
||||
}
|
||||
|
||||
void PrefabGroupBehavior::Deactivate()
|
||||
{
|
||||
m_exportEventHandler.reset();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<rapidjson::Document> PrefabGroupBehavior::CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab;
|
||||
|
||||
auto* prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
|
||||
if (!prefabLoaderInterface)
|
||||
{
|
||||
AZ_Error("prefab", false, "Could not get PrefabLoaderInterface");
|
||||
return {};
|
||||
}
|
||||
|
||||
// write to a UTF-8 string buffer
|
||||
auto prefabDomRef = prefabGroup->GetPrefabDomRef();
|
||||
if (!prefabDomRef)
|
||||
{
|
||||
AZ_Error("prefab", false, "PrefabGroup(%s) missing PrefabDom", prefabGroup->GetName().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& prefabDom = prefabDomRef.value();
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<>> writer(sb);
|
||||
if (prefabDom.Accept(writer) == false)
|
||||
{
|
||||
AZ_Error("prefab", false, "Could not write PrefabGroup(%s) to JSON", prefabGroup->GetName().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
// validate the PrefabDom will make a valid Prefab template instance
|
||||
auto templateId = prefabLoaderInterface->LoadTemplateFromString(sb.GetString(), prefabGroup->GetName().c_str());
|
||||
if (templateId == InvalidTemplateId)
|
||||
{
|
||||
AZ_Error("prefab", false, "PrefabGroup(%s) Could not write load template", prefabGroup->GetName().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
|
||||
if (!prefabSystemComponentInterface)
|
||||
{
|
||||
AZ_Error("prefab", false, "Could not get PrefabSystemComponentInterface");
|
||||
return {};
|
||||
}
|
||||
|
||||
// create instance to update the asset hints
|
||||
auto instance = prefabSystemComponentInterface->InstantiatePrefab(templateId);
|
||||
if (!instance)
|
||||
{
|
||||
AZ_Error("prefab", false, "PrefabGroup(%s) Could not instantiate prefab", prefabGroup->GetName().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
|
||||
if (!instanceToTemplateInterface)
|
||||
{
|
||||
AZ_Error("prefab", false, "Could not get InstanceToTemplateInterface");
|
||||
return {};
|
||||
}
|
||||
|
||||
// fill out a JSON DOM
|
||||
auto proceduralPrefab = AZStd::make_unique<rapidjson::Document>(rapidjson::kObjectType);
|
||||
instanceToTemplateInterface->GenerateDomForInstance(*proceduralPrefab.get(), *instance.get());
|
||||
return proceduralPrefab;
|
||||
}
|
||||
|
||||
bool PrefabGroupBehavior::WriteOutProductAsset(
|
||||
Events::PreExportEventContext& context,
|
||||
const SceneData::PrefabGroup* prefabGroup,
|
||||
const rapidjson::Document& doc) const
|
||||
{
|
||||
// Retrieve source asset info so we can get a string with the relative path to the asset
|
||||
bool assetInfoResult;
|
||||
Data::AssetInfo info;
|
||||
AZStd::string watchFolder;
|
||||
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
|
||||
assetInfoResult,
|
||||
&AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
|
||||
context.GetScene().GetSourceFilename().c_str(),
|
||||
info,
|
||||
watchFolder);
|
||||
|
||||
AZ::IO::FixedMaxPath assetPath(info.m_relativePath);
|
||||
assetPath.ReplaceFilename(prefabGroup->GetName().c_str());
|
||||
|
||||
AZStd::string filePath = AZ::SceneAPI::Utilities::FileUtilities::CreateOutputFileName(
|
||||
assetPath.c_str(),
|
||||
context.GetOutputDirectory(),
|
||||
AZ::Prefab::PrefabGroupAssetHandler::s_Extension);
|
||||
|
||||
AZ::IO::FileIOStream fileStream(filePath.c_str(), AZ::IO::OpenMode::ModeWrite);
|
||||
if (fileStream.IsOpen() == false)
|
||||
{
|
||||
AZ_Error("prefab", false, "File path(%s) could not open for write", filePath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// write to a UTF-8 string buffer
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<>> writer(sb);
|
||||
if (doc.Accept(writer) == false)
|
||||
{
|
||||
AZ_Error("prefab", false, "PrefabGroup(%s) Could not buffer JSON", prefabGroup->GetName().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto bytesWritten = fileStream.Write(sb.GetSize(), sb.GetString());
|
||||
if (bytesWritten > 1)
|
||||
{
|
||||
AZ::u32 subId = AZ::Crc32(filePath.c_str());
|
||||
context.GetProductList().AddProduct(
|
||||
filePath,
|
||||
context.GetScene().GetSourceGuid(),
|
||||
azrtti_typeid<Prefab::ProceduralPrefabAsset>(),
|
||||
{},
|
||||
AZStd::make_optional(subId));
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Events::ProcessingResult PrefabGroupBehavior::OnPrepareForExport(Events::PreExportEventContext& context) const
|
||||
{
|
||||
AZStd::vector<const SceneData::PrefabGroup*> prefabGroupCollection;
|
||||
const Containers::SceneManifest& manifest = context.GetScene().GetManifest();
|
||||
|
||||
for (size_t i = 0; i < manifest.GetEntryCount(); ++i)
|
||||
{
|
||||
const auto* group = azrtti_cast<const SceneData::PrefabGroup*>(manifest.GetValue(i).get());
|
||||
if (group)
|
||||
{
|
||||
prefabGroupCollection.push_back(group);
|
||||
}
|
||||
}
|
||||
|
||||
if (prefabGroupCollection.empty())
|
||||
{
|
||||
return AZ::SceneAPI::Events::ProcessingResult::Ignored;
|
||||
}
|
||||
|
||||
for (const auto* prefabGroup : prefabGroupCollection)
|
||||
{
|
||||
auto result = CreateProductAssetData(prefabGroup);
|
||||
if (!result)
|
||||
{
|
||||
return Events::ProcessingResult::Failure;
|
||||
}
|
||||
|
||||
if (WriteOutProductAsset(context, prefabGroup, *result.get()) == false)
|
||||
{
|
||||
return Events::ProcessingResult::Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return Events::ProcessingResult::Success;
|
||||
}
|
||||
|
||||
void PrefabGroupBehavior::Reflect(ReflectContext* context)
|
||||
{
|
||||
AZ::SceneAPI::SceneData::PrefabGroup::Reflect(context);
|
||||
Prefab::ProceduralPrefabAsset::Reflect(context);
|
||||
|
||||
SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context);
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->Class<PrefabGroupBehavior, BehaviorComponent>()->Version(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/PrefabGroup.h>
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <SceneAPI/SceneCore/Events/ProcessingResult.h>
|
||||
#include <SceneAPI/SceneCore/Components/BehaviorComponent.h>
|
||||
#include <AzCore/JSON/rapidjson.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace AZ::SceneAPI::Events
|
||||
{
|
||||
class PreExportEventContext;
|
||||
}
|
||||
|
||||
namespace AZ::SceneAPI::Behaviors
|
||||
{
|
||||
class PrefabGroupBehavior
|
||||
: public SceneCore::BehaviorComponent
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(PrefabGroupBehavior, "{13DC2819-CAC2-4977-91D7-C870087072AB}", SceneCore::BehaviorComponent);
|
||||
|
||||
~PrefabGroupBehavior() override = default;
|
||||
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
static void Reflect(ReflectContext* context);
|
||||
|
||||
private:
|
||||
Events::ProcessingResult OnPrepareForExport(Events::PreExportEventContext& context) const;
|
||||
AZStd::unique_ptr<rapidjson::Document> CreateProductAssetData(const SceneData::PrefabGroup* prefabGroup) const;
|
||||
|
||||
bool WriteOutProductAsset(
|
||||
Events::PreExportEventContext& context,
|
||||
const SceneData::PrefabGroup* prefabGroup,
|
||||
const rapidjson::Document& doc) const;
|
||||
|
||||
struct ExportEventHandler;
|
||||
AZStd::shared_ptr<ExportEventHandler> m_exportEventHandler;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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 <PrefabBuilderTests.h>
|
||||
#include <PrefabGroup/IPrefabGroup.h>
|
||||
#include <PrefabGroup/PrefabGroup.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_FindsRequiredReflection_True)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
auto* serializeContext = m_app.GetSerializeContext();
|
||||
ASSERT_NE(nullptr, serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid<DataTypes::IPrefabGroup>()));
|
||||
ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid<SceneData::PrefabGroup>()));
|
||||
|
||||
auto findElementWithName = [](const AZ::SerializeContext::ClassData* classData, const char* name)
|
||||
{
|
||||
auto it = AZStd::find_if(classData->m_elements.begin(), classData->m_elements.end(), [name](const auto& element)
|
||||
{
|
||||
return strcmp(element.m_name, name) == 0;
|
||||
});
|
||||
return it != classData->m_elements.end();
|
||||
};
|
||||
|
||||
auto* prefabGroupClassData = serializeContext->FindClassData(azrtti_typeid<SceneData::PrefabGroup>());
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "name"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "nodeSelectionList"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "rules"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "id"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "prefabDomData"));
|
||||
|
||||
m_app.GetJsonRegistrationContext()->EnableRemoveReflection();
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_JsonWithPrefabArbitraryPrefab_Works)
|
||||
{
|
||||
namespace JSR = AZ::JsonSerializationResult;
|
||||
using namespace AZ::SceneAPI;
|
||||
auto* serializeContext = m_app.GetSerializeContext();
|
||||
ASSERT_NE(nullptr, serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(serializeContext);
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(m_app.GetJsonRegistrationContext());
|
||||
|
||||
// fill out a PrefabGroup using JSON
|
||||
AZStd::string_view input = R"JSON(
|
||||
{
|
||||
"name" : "tester",
|
||||
"id" : "{49698DBC-B447-49EF-9B56-25BB29342AFB}",
|
||||
"prefabDomData" : {"foo": "bar"}
|
||||
})JSON";
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse<rapidjson::kParseCommentsFlag>(input.data(), input.size());
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
|
||||
SceneData::PrefabGroup instancePrefabGroup;
|
||||
EXPECT_EQ(AZ::JsonSerialization::Load(instancePrefabGroup, document).GetOutcome(), JSR::Outcomes::PartialDefaults);
|
||||
|
||||
ASSERT_TRUE(instancePrefabGroup.GetPrefabDomRef().has_value());
|
||||
const AzToolsFramework::Prefab::PrefabDom& dom = instancePrefabGroup.GetPrefabDomRef().value();
|
||||
EXPECT_TRUE(dom.IsObject());
|
||||
EXPECT_TRUE(dom.GetObject().HasMember("foo"));
|
||||
EXPECT_STREQ(dom.GetObject().FindMember("foo")->value.GetString(), "bar");
|
||||
EXPECT_STREQ(instancePrefabGroup.GetName().c_str(), "tester");
|
||||
EXPECT_STREQ(instancePrefabGroup.GetId().ToString<AZStd::string>().c_str(), "{49698DBC-B447-49EF-9B56-25BB29342AFB}");
|
||||
|
||||
m_app.GetJsonRegistrationContext()->EnableRemoveReflection();
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
AZ::Prefab::ProceduralPrefabAsset::Reflect(m_app.GetJsonRegistrationContext());
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJson_Detected)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
|
||||
AZStd::string_view input = R"JSON(
|
||||
{
|
||||
bad json that will not parse
|
||||
})JSON";
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse<rapidjson::kParseCommentsFlag>(input.data(), input.size());
|
||||
|
||||
SceneData::PrefabGroup prefabGroup;
|
||||
prefabGroup.SetId(AZ::Uuid::CreateRandom());
|
||||
prefabGroup.SetName("tester");
|
||||
prefabGroup.SetPrefabDom(AZStd::move(document));
|
||||
|
||||
const AzToolsFramework::Prefab::PrefabDom& dom = prefabGroup.GetPrefabDomRef().value();
|
||||
EXPECT_TRUE(dom.IsNull());
|
||||
EXPECT_STREQ("tester", prefabGroup.GetName().c_str());
|
||||
}
|
||||
|
||||
struct PrefabBuilderBehaviorTests
|
||||
: public PrefabBuilderTests
|
||||
{
|
||||
void SetUp() override
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
|
||||
PrefabBuilderTests::SetUp();
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetSerializeContext());
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetBehaviorContext());
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
m_scriptContext = AZStd::make_unique<AZ::ScriptContext>();
|
||||
m_scriptContext->BindTo(m_app.GetBehaviorContext());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
m_app.GetJsonRegistrationContext()->EnableRemoveReflection();
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetJsonRegistrationContext());
|
||||
|
||||
m_scriptContext.reset();
|
||||
PrefabBuilderTests::TearDown();
|
||||
}
|
||||
|
||||
void ExpectExecute(AZStd::string_view script)
|
||||
{
|
||||
EXPECT_TRUE(m_scriptContext->Execute(script.data()));
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::ScriptContext> m_scriptContext;
|
||||
};
|
||||
|
||||
TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupClass_Exists)
|
||||
{
|
||||
ExpectExecute("group = PrefabGroup()");
|
||||
ExpectExecute("assert(group)");
|
||||
ExpectExecute("assert(group.name)");
|
||||
ExpectExecute("assert(group.id)");
|
||||
ExpectExecute("assert(group.prefabDomData)");
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupAssignment_Works)
|
||||
{
|
||||
ExpectExecute("group = PrefabGroup()");
|
||||
ExpectExecute("group.name = 'tester'");
|
||||
ExpectExecute("group.id = Uuid.CreateString('{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}', 0)");
|
||||
ExpectExecute("group.prefabDomData = '{\"foo\": \"bar\"}'");
|
||||
ExpectExecute("assert(group.name == 'tester')");
|
||||
ExpectExecute("assert(tostring(group.id) == '{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}')");
|
||||
ExpectExecute("assert(group.prefabDomData == '{\\n \"foo\": \"bar\"\\n}')");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/ProceduralAssetHandler.h>
|
||||
#include <AzCore/Asset/AssetTypeInfoBus.h>
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/FileFunc/FileFunc.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
// AssetTypeInfoHandler
|
||||
|
||||
class PrefabGroupAssetHandler::AssetTypeInfoHandler final
|
||||
: public AZ::AssetTypeInfoBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(AssetTypeInfoHandler, AZ::SystemAllocator, 0);
|
||||
AssetTypeInfoHandler();
|
||||
~AssetTypeInfoHandler() override;
|
||||
AZ::Data::AssetType GetAssetType() const override;
|
||||
const char* GetAssetTypeDisplayName() const override;
|
||||
const char* GetGroup() const override;
|
||||
const char* GetBrowserIcon() const override;
|
||||
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
|
||||
};
|
||||
|
||||
PrefabGroupAssetHandler::AssetTypeInfoHandler::AssetTypeInfoHandler()
|
||||
{
|
||||
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<ProceduralPrefabAsset>());
|
||||
}
|
||||
|
||||
PrefabGroupAssetHandler::AssetTypeInfoHandler::~AssetTypeInfoHandler()
|
||||
{
|
||||
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<ProceduralPrefabAsset>());
|
||||
}
|
||||
|
||||
AZ::Data::AssetType PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetType() const
|
||||
{
|
||||
return azrtti_typeid<ProceduralPrefabAsset>();
|
||||
}
|
||||
|
||||
const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetTypeDisplayName() const
|
||||
{
|
||||
return "Procedural Prefab";
|
||||
}
|
||||
|
||||
const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetGroup() const
|
||||
{
|
||||
return "Prefab";
|
||||
}
|
||||
|
||||
const char* PrefabGroupAssetHandler::AssetTypeInfoHandler::GetBrowserIcon() const
|
||||
{
|
||||
return "Icons/Components/Box.png";
|
||||
}
|
||||
|
||||
void PrefabGroupAssetHandler::AssetTypeInfoHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
|
||||
{
|
||||
extensions.push_back(PrefabGroupAssetHandler::s_Extension);
|
||||
}
|
||||
|
||||
// PrefabGroupAssetHandler
|
||||
|
||||
AZStd::string_view PrefabGroupAssetHandler::s_Extension{ "procprefab" };
|
||||
|
||||
PrefabGroupAssetHandler::PrefabGroupAssetHandler()
|
||||
{
|
||||
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
|
||||
if (assetCatalog)
|
||||
{
|
||||
assetCatalog->EnableCatalogForAsset(azrtti_typeid<ProceduralPrefabAsset>());
|
||||
assetCatalog->AddExtension(s_Extension.data());
|
||||
}
|
||||
if (AZ::Data::AssetManager::IsReady())
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<ProceduralPrefabAsset>());
|
||||
}
|
||||
m_assetTypeInfoHandler = AZStd::make_shared<AssetTypeInfoHandler>();
|
||||
}
|
||||
|
||||
PrefabGroupAssetHandler::~PrefabGroupAssetHandler()
|
||||
{
|
||||
m_assetTypeInfoHandler.reset();
|
||||
if (AZ::Data::AssetManager::IsReady())
|
||||
{
|
||||
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Data::AssetData* PrefabGroupAssetHandler::CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id, const AZ::Data::AssetType& type)
|
||||
{
|
||||
if (type != azrtti_typeid<ProceduralPrefabAsset>())
|
||||
{
|
||||
AZ_Error("prefab", false, "Invalid asset type! Only handle 'ProceduralPrefabAsset'");
|
||||
return nullptr;
|
||||
}
|
||||
return aznew ProceduralPrefabAsset{};
|
||||
}
|
||||
|
||||
void PrefabGroupAssetHandler::DestroyAsset(AZ::Data::AssetData* ptr)
|
||||
{
|
||||
// Note: the PrefabLoaderInterface will handle the lifetime of the Prefab Template
|
||||
delete ptr;
|
||||
}
|
||||
|
||||
void PrefabGroupAssetHandler::GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes)
|
||||
{
|
||||
assetTypes.push_back(azrtti_typeid<ProceduralPrefabAsset>());
|
||||
}
|
||||
|
||||
AZ::Data::AssetHandler::LoadResult PrefabGroupAssetHandler::LoadAssetData(
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab;
|
||||
|
||||
auto* proceduralPrefabAsset = asset.GetAs<ProceduralPrefabAsset>();
|
||||
if (!proceduralPrefabAsset)
|
||||
{
|
||||
AZ_Error("prefab", false, "This should be a ProceduralPrefabAsset type, as this is the only type we process!");
|
||||
return LoadResult::Error;
|
||||
}
|
||||
|
||||
AZStd::string buffer;
|
||||
buffer.resize(stream->GetLoadedSize());
|
||||
stream->Read(stream->GetLoadedSize(), buffer.data());
|
||||
|
||||
auto jsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(buffer);
|
||||
if (jsonOutcome.IsSuccess() == false)
|
||||
{
|
||||
AZ_Error("prefab", false, "Asset JSON failed to compile %s", jsonOutcome.GetError().c_str());
|
||||
return LoadResult::Error;
|
||||
}
|
||||
const auto& jsonDoc = jsonOutcome.GetValue();
|
||||
|
||||
if (jsonDoc.IsObject() == false)
|
||||
{
|
||||
return LoadResult::Error;
|
||||
}
|
||||
|
||||
if (jsonDoc.FindMember("Source") == jsonDoc.MemberEnd())
|
||||
{
|
||||
return LoadResult::Error;
|
||||
}
|
||||
const auto& templateName = jsonDoc["Source"];
|
||||
|
||||
AZStd::string stringJson;
|
||||
auto stringOutcome = AZ::JsonSerializationUtils::WriteJsonString(jsonDoc, stringJson);
|
||||
if (stringOutcome.IsSuccess() == false)
|
||||
{
|
||||
AZ_Error("prefab", false, "Could not write to JSON string %s", stringOutcome.GetError().c_str());
|
||||
return LoadResult::Error;
|
||||
}
|
||||
|
||||
// prepare the template
|
||||
auto* prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
|
||||
if (!prefabLoaderInterface)
|
||||
{
|
||||
return LoadResult::Error;
|
||||
}
|
||||
|
||||
auto templateId = prefabLoaderInterface->LoadTemplateFromString(stringJson.data(), templateName.GetString());
|
||||
if (templateId == InvalidTemplateId)
|
||||
{
|
||||
return LoadResult::Error;
|
||||
}
|
||||
|
||||
proceduralPrefabAsset->SetTemplateId(templateId);
|
||||
proceduralPrefabAsset->SetTemplateName(templateName.GetString());
|
||||
return LoadResult::LoadComplete;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<PrefabGroupAssetHandler> s_PrefabGroupAssetHandler;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 <AzCore/Asset/AssetManager.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
|
||||
namespace AZ::Prefab
|
||||
{
|
||||
class PrefabGroupAssetHandler final
|
||||
: public AZ::Data::AssetHandler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(PrefabGroupAssetHandler, AZ::SystemAllocator, 0);
|
||||
PrefabGroupAssetHandler();
|
||||
~PrefabGroupAssetHandler() override;
|
||||
|
||||
static AZStd::string_view s_Extension;
|
||||
|
||||
protected:
|
||||
AZ::Data::AssetData* CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override;
|
||||
void DestroyAsset(AZ::Data::AssetData* ptr) override;
|
||||
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override;
|
||||
AZ::Data::AssetHandler::LoadResult LoadAssetData(
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
|
||||
|
||||
class AssetTypeInfoHandler;
|
||||
AZStd::shared_ptr<AssetTypeInfoHandler> m_assetTypeInfoHandler;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 <PrefabGroup/PrefabBuilderTests.h>
|
||||
#include <PrefabGroup/IPrefabGroup.h>
|
||||
#include <PrefabGroup/PrefabGroup.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_FindsRequiredReflection_True)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
auto* serializeContext = m_app.GetSerializeContext();
|
||||
ASSERT_NE(nullptr, serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(serializeContext);
|
||||
ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid<DataTypes::IPrefabGroup>()));
|
||||
ASSERT_NE(nullptr, serializeContext->FindClassData(azrtti_typeid<SceneData::PrefabGroup>()));
|
||||
|
||||
auto findElementWithName = [](const AZ::SerializeContext::ClassData* classData, const char* name)
|
||||
{
|
||||
auto it = AZStd::find_if(classData->m_elements.begin(), classData->m_elements.end(), [name](const auto& element)
|
||||
{
|
||||
return strcmp(element.m_name, name) == 0;
|
||||
});
|
||||
return it != classData->m_elements.end();
|
||||
};
|
||||
|
||||
auto* prefabGroupClassData = serializeContext->FindClassData(azrtti_typeid<SceneData::PrefabGroup>());
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "name"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "nodeSelectionList"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "rules"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "id"));
|
||||
EXPECT_TRUE(findElementWithName(prefabGroupClassData, "prefabDomBuffer"));
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_JsonWithPrefabArbitraryPrefab_Works)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
auto* serializeContext = m_app.GetSerializeContext();
|
||||
ASSERT_NE(nullptr, serializeContext);
|
||||
SceneData::PrefabGroup::Reflect(serializeContext);
|
||||
|
||||
// fill out a PrefabGroup using JSON
|
||||
AZStd::string_view input = R"JSON(
|
||||
{
|
||||
"name" : "tester",
|
||||
"id" : "{49698DBC-B447-49EF-9B56-25BB29342AFB}",
|
||||
"prefabDomBuffer" : "{\"foo\":\"bar\"}"
|
||||
})JSON";
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse<rapidjson::kParseCommentsFlag>(input.data(), input.size());
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
|
||||
SceneData::PrefabGroup instancePrefabGroup;
|
||||
AZ::JsonSerialization::Load(instancePrefabGroup, document);
|
||||
|
||||
const auto& dom = instancePrefabGroup.GetPrefabDom();
|
||||
EXPECT_TRUE(dom.GetObject().HasMember("foo"));
|
||||
EXPECT_STREQ(dom.GetObject().FindMember("foo")->value.GetString(), "bar");
|
||||
EXPECT_STREQ(instancePrefabGroup.GetName().c_str(), "tester");
|
||||
EXPECT_STREQ(
|
||||
instancePrefabGroup.GetId().ToString<AZStd::string>().c_str(),
|
||||
"{49698DBC-B447-49EF-9B56-25BB29342AFB}");
|
||||
EXPECT_TRUE(instancePrefabGroup.GetPrefabDom().IsObject());
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJson_Detected)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
|
||||
AZStd::string_view input = R"JSON(
|
||||
{
|
||||
bad json that will not parse
|
||||
})JSON";
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse<rapidjson::kParseCommentsFlag>(input.data(), input.size());
|
||||
|
||||
SceneData::PrefabGroup prefabGroup;
|
||||
prefabGroup.SetId(AZStd::move(AZ::Uuid::CreateRandom()));
|
||||
prefabGroup.SetName(AZStd::move("tester"));
|
||||
prefabGroup.SetPrefabDom(AZStd::move(document));
|
||||
|
||||
const auto& dom = prefabGroup.GetPrefabDom();
|
||||
EXPECT_TRUE(dom.IsNull());
|
||||
EXPECT_STREQ("tester", prefabGroup.GetName().c_str());
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderTests, PrefabGroup_InvalidPrefabJsonBuffer_Detected)
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
|
||||
AZStd::string_view inputJson = R"JSON(
|
||||
{
|
||||
bad json that will not parse
|
||||
})JSON";
|
||||
|
||||
SceneData::PrefabGroup prefabGroup;
|
||||
prefabGroup.SetId(AZStd::move(AZ::Uuid::CreateRandom()));
|
||||
prefabGroup.SetName(AZStd::move("tester"));
|
||||
prefabGroup.SetPrefabDomBuffer(std::move(inputJson));
|
||||
|
||||
const auto& dom = prefabGroup.GetPrefabDom();
|
||||
EXPECT_TRUE(dom.IsNull());
|
||||
EXPECT_STREQ("tester", prefabGroup.GetName().c_str());
|
||||
}
|
||||
|
||||
struct PrefabBuilderBehaviorTests
|
||||
: public PrefabBuilderTests
|
||||
{
|
||||
void SetUp() override
|
||||
{
|
||||
using namespace AZ::SceneAPI;
|
||||
|
||||
PrefabBuilderTests::SetUp();
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetSerializeContext());
|
||||
SceneData::PrefabGroup::Reflect(m_app.GetBehaviorContext());
|
||||
m_scriptContext = AZStd::make_unique<AZ::ScriptContext>();
|
||||
m_scriptContext->BindTo(m_app.GetBehaviorContext());
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_scriptContext.reset();
|
||||
PrefabBuilderTests::TearDown();
|
||||
}
|
||||
|
||||
void ExpectExecute(AZStd::string_view script)
|
||||
{
|
||||
EXPECT_TRUE(m_scriptContext->Execute(script.data()));
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::ScriptContext> m_scriptContext;
|
||||
};
|
||||
|
||||
TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupClass_Exists)
|
||||
{
|
||||
ExpectExecute("group = PrefabGroup()");
|
||||
ExpectExecute("assert(group)");
|
||||
ExpectExecute("assert(group.name)");
|
||||
ExpectExecute("assert(group.id)");
|
||||
ExpectExecute("assert(group.prefabDomBuffer)");
|
||||
}
|
||||
|
||||
TEST_F(PrefabBuilderBehaviorTests, PrefabGroup_PrefabGroupAssignment_Works)
|
||||
{
|
||||
ExpectExecute("group = PrefabGroup()");
|
||||
ExpectExecute("group.name = 'tester'");
|
||||
ExpectExecute("group.id = Uuid.CreateString('{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}', 0)");
|
||||
ExpectExecute("group.prefabDomBuffer = '{}'");
|
||||
ExpectExecute("assert(group.name == 'tester')");
|
||||
ExpectExecute("assert(tostring(group.id) == '{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}')");
|
||||
ExpectExecute("assert(group.prefabDomBuffer == '{}')");
|
||||
}
|
||||
}
|
||||
@@ -9,4 +9,11 @@
|
||||
set(FILES
|
||||
PrefabBuilderComponent.h
|
||||
PrefabBuilderComponent.cpp
|
||||
PrefabGroup/IPrefabGroup.h
|
||||
PrefabGroup/PrefabGroup.cpp
|
||||
PrefabGroup/PrefabGroup.h
|
||||
PrefabGroup/PrefabGroupBehavior.cpp
|
||||
PrefabGroup/PrefabGroupBehavior.h
|
||||
PrefabGroup/ProceduralAssetHandler.cpp
|
||||
PrefabGroup/ProceduralAssetHandler.h
|
||||
)
|
||||
|
||||
@@ -9,4 +9,7 @@
|
||||
set(FILES
|
||||
PrefabBuilderTests.h
|
||||
PrefabBuilderTests.cpp
|
||||
PrefabGroup/PrefabGroupTests.cpp
|
||||
PrefabGroup/PrefabBehaviorTests.cpp
|
||||
PrefabGroup/PrefabBehaviorTests.inl
|
||||
)
|
||||
|
||||
@@ -104,6 +104,15 @@ class SceneManifest():
|
||||
self.manifest['values'].append(meshGroup)
|
||||
return meshGroup
|
||||
|
||||
def add_prefab_group(self, name, id, json) -> dict:
|
||||
prefabGroup = {}
|
||||
prefabGroup['$type'] = '{99FE3C6F-5B55-4D8B-8013-2708010EC715} PrefabGroup'
|
||||
prefabGroup['name'] = name
|
||||
prefabGroup['id'] = id
|
||||
prefabGroup['prefabDomData'] = json
|
||||
self.manifest['values'].append(prefabGroup)
|
||||
return prefabGroup
|
||||
|
||||
def mesh_group_select_node(self, meshGroup, nodeName):
|
||||
meshGroup['nodeSelectionList']['selectedNodes'].append(nodeName)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/containers/set.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzToolsFramework/Debug/TraceContext.h>
|
||||
@@ -34,6 +35,9 @@
|
||||
#include <SceneAPI/SceneCore/DataTypes/GraphData/IAnimationData.h>
|
||||
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <rapidjson/pointer.h>
|
||||
#include <SceneBuilder/SceneBuilderWorker.h>
|
||||
#include <SceneBuilder/TraceMessageHook.h>
|
||||
|
||||
@@ -81,6 +85,93 @@ namespace SceneBuilder
|
||||
return m_cachedFingerprint.c_str();
|
||||
}
|
||||
|
||||
void SceneBuilderWorker::PopulateSourceDependencies(const AZStd::string& manifestJson, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies)
|
||||
{
|
||||
auto readJsonOutcome = AZ::JsonSerializationUtils::ReadJsonString(manifestJson);
|
||||
AZStd::string errorMsg;
|
||||
if (!readJsonOutcome.IsSuccess())
|
||||
{
|
||||
// This may be an old format xml file. We don't have any dependencies in the old format so there's no point trying to parse an xml
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document document = readJsonOutcome.TakeValue();
|
||||
|
||||
auto manifestObject = document.GetObject();
|
||||
auto valuesIterator = manifestObject.FindMember("values");
|
||||
auto valuesArray = valuesIterator->value.GetArray();
|
||||
|
||||
AZStd::vector<AZStd::string> paths;
|
||||
AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
|
||||
&AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestDependencyPaths, paths);
|
||||
|
||||
for (const auto& value : valuesArray)
|
||||
{
|
||||
auto object = value.GetObject();
|
||||
|
||||
for (const auto& path : paths)
|
||||
{
|
||||
rapidjson::Pointer pointer(path.c_str());
|
||||
|
||||
auto dependencyValue = pointer.Get(object);
|
||||
|
||||
if (dependencyValue && dependencyValue->IsString())
|
||||
{
|
||||
AZStd::string dependency = dependencyValue->GetString();
|
||||
|
||||
sourceFileDependencies.emplace_back(AssetBuilderSDK::SourceFileDependency(
|
||||
dependency, AZ::Uuid::CreateNull(), AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Absolute));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SceneBuilderWorker::ManifestDependencyCheck(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
|
||||
{
|
||||
AZStd::string manifestExtension;
|
||||
AZStd::string generatedManifestExtension;
|
||||
|
||||
AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
|
||||
&AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetManifestExtension, manifestExtension);
|
||||
AZ::SceneAPI::Events::AssetImportRequestBus::Broadcast(
|
||||
&AZ::SceneAPI::Events::AssetImportRequestBus::Events::GetGeneratedManifestExtension, generatedManifestExtension);
|
||||
|
||||
if (manifestExtension.empty() || generatedManifestExtension.empty())
|
||||
{
|
||||
AZ_Error("SceneBuilderWorker", false, "Failed to get scene manifest extension");
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ::SettingsRegistryInterface::FixedValueString assetCacheRoot;
|
||||
AZ::SettingsRegistry::Get()->Get(assetCacheRoot, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
|
||||
|
||||
auto manifestPath = (AZ::IO::Path(request.m_watchFolder) / (request.m_sourceFile + manifestExtension));
|
||||
auto generatedManifestPath = (AZ::IO::Path(assetCacheRoot) / (request.m_sourceFile + generatedManifestExtension));
|
||||
|
||||
auto populateDependenciesFunc = [&response](const AZStd::string& path)
|
||||
{
|
||||
auto readFileOutcome = AZ::Utils::ReadFile(path, AZ::SceneAPI::Containers::SceneManifest::MaxSceneManifestFileSizeInBytes);
|
||||
if (!readFileOutcome.IsSuccess())
|
||||
{
|
||||
AZ_Error("SceneBuilderWorker", false, "%s", readFileOutcome.GetError().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
PopulateSourceDependencies(readFileOutcome.TakeValue(), response.m_sourceFileDependencyList);
|
||||
};
|
||||
|
||||
if (AZ::IO::FileIOBase::GetInstance()->Exists(manifestPath.Native().c_str()))
|
||||
{
|
||||
populateDependenciesFunc(manifestPath.Native());
|
||||
}
|
||||
else if (AZ::IO::FileIOBase::GetInstance()->Exists(generatedManifestPath.Native().c_str()))
|
||||
{
|
||||
populateDependenciesFunc(generatedManifestPath.Native());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SceneBuilderWorker::CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response)
|
||||
{
|
||||
// Check for shutdown
|
||||
@@ -118,6 +209,11 @@ namespace SceneBuilder
|
||||
sourceFileDependencyInfo.m_sourceDependencyType = AssetBuilderSDK::SourceFileDependency::SourceFileDependencyType::Wildcards;
|
||||
response.m_sourceFileDependencyList.push_back(sourceFileDependencyInfo);
|
||||
|
||||
if (!ManifestDependencyCheck(request, response))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderBusses.h>
|
||||
#include <AssetBuilderSDK/AssetBuilderSDK.h>
|
||||
|
||||
namespace AssetBuilderSDK
|
||||
{
|
||||
@@ -45,11 +46,15 @@ namespace SceneBuilder
|
||||
public:
|
||||
~SceneBuilderWorker() override = default;
|
||||
|
||||
|
||||
void CreateJobs(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response);
|
||||
void ProcessJob(const AssetBuilderSDK::ProcessJobRequest& request, AssetBuilderSDK::ProcessJobResponse& response);
|
||||
|
||||
void ShutDown() override;
|
||||
const char* GetFingerprint() const;
|
||||
static void PopulateSourceDependencies(
|
||||
const AZStd::string& manifestJson, AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies);
|
||||
static bool ManifestDependencyCheck(const AssetBuilderSDK::CreateJobsRequest& request, AssetBuilderSDK::CreateJobsResponse& response);
|
||||
static AZ::Uuid GetUUID();
|
||||
|
||||
void PopulateProductDependencies(const AZ::SceneAPI::Events::ExportProduct& exportProduct, const char* watchFolder, AssetBuilderSDK::JobProduct& jobProduct) const;
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
|
||||
#include <AzCore/UserSettings/UserSettingsComponent.h>
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
#include <SceneAPI/SceneCore/Events/AssetImportRequest.h>
|
||||
#include <SceneBuilder/SceneBuilderWorker.h>
|
||||
#include <SceneAPI/SceneCore/Events/ExportProductList.h>
|
||||
#include <Tests/FileIOBaseTestTypes.h>
|
||||
|
||||
using namespace AZ;
|
||||
using namespace SceneBuilder;
|
||||
@@ -193,3 +196,200 @@ TEST_F(SceneBuilderTests, SceneBuilderWorker_ExportProductDependencies_ProductAn
|
||||
|
||||
TestSuccessCase(exportProduct, expectedPathDependencies, { dependencyId });
|
||||
}
|
||||
|
||||
struct ImportHandler
|
||||
: SceneAPI::Events::AssetImportRequestBus::Handler
|
||||
{
|
||||
ImportHandler()
|
||||
{
|
||||
BusConnect();
|
||||
}
|
||||
|
||||
~ImportHandler() override
|
||||
{
|
||||
BusDisconnect();
|
||||
}
|
||||
|
||||
void GetManifestDependencyPaths(AZStd::vector<AZStd::string>& paths) override
|
||||
{
|
||||
paths.emplace_back("/scriptFilename");
|
||||
paths.emplace_back("/layer1/layer2/0/target");
|
||||
}
|
||||
|
||||
void GetManifestExtension(AZStd::string& result) override
|
||||
{
|
||||
result = ".test";
|
||||
}
|
||||
|
||||
void GetGeneratedManifestExtension(AZStd::string& result) override
|
||||
{
|
||||
result = ".test.gen";
|
||||
}
|
||||
};
|
||||
|
||||
using SourceDependencyTests = UnitTest::ScopedAllocatorSetupFixture;
|
||||
|
||||
namespace SourceDependencyJson
|
||||
{
|
||||
constexpr const char* TestJson = R"JSON(
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "Test1",
|
||||
"scriptFilename": "a/test/path.png"
|
||||
},
|
||||
{
|
||||
"$type": "Test2",
|
||||
"layer1" : {
|
||||
"layer2" : [
|
||||
{
|
||||
"target": "value.png",
|
||||
"otherData": "value2.png"
|
||||
},
|
||||
{
|
||||
"target" : "wrong.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)JSON";
|
||||
}
|
||||
|
||||
TEST_F(SourceDependencyTests, SourceDependencyTest)
|
||||
{
|
||||
ImportHandler handler;
|
||||
AZStd::vector<AssetBuilderSDK::SourceFileDependency> dependencies;
|
||||
|
||||
SceneBuilderWorker::PopulateSourceDependencies(SourceDependencyJson::TestJson, dependencies);
|
||||
|
||||
ASSERT_EQ(dependencies.size(), 2);
|
||||
ASSERT_STREQ(dependencies[0].m_sourceFileDependencyPath.c_str(), "a/test/path.png");
|
||||
ASSERT_STREQ(dependencies[1].m_sourceFileDependencyPath.c_str(), "value.png");
|
||||
}
|
||||
|
||||
struct SettingsRegistryMock : AZ::Interface<SettingsRegistryInterface>::Registrar
|
||||
{
|
||||
bool Get(FixedValueString& result, AZStd::string_view) const override
|
||||
{
|
||||
result = "cache";
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& /*applyPatchSettings*/) override{}
|
||||
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& /*applyPatchSettings*/) override{}
|
||||
|
||||
MOCK_CONST_METHOD1(GetType, Type (AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool (Visitor&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool (const VisitorCallback&, AZStd::string_view));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler (const NotifyCallback&));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler (NotifyCallback&&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler (const PreMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler (PreMergeEventCallback&&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler (const PostMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler (PostMergeEventCallback&&));
|
||||
MOCK_CONST_METHOD2(Get, bool (bool&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool (s64&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool (u64&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool (double&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool (AZStd::string&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD3(GetObject, bool (void*, Uuid, AZStd::string_view));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, bool));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, s64));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, u64));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, double));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, AZStd::string_view));
|
||||
MOCK_METHOD2(Set, bool (AZStd::string_view, const char*));
|
||||
MOCK_METHOD3(SetObject, bool (AZStd::string_view, const void*, Uuid));
|
||||
MOCK_METHOD1(Remove, bool (AZStd::string_view));
|
||||
MOCK_METHOD3(MergeCommandLineArgument, bool (AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
|
||||
MOCK_METHOD2(MergeSettings, bool (AZStd::string_view, Format));
|
||||
MOCK_METHOD4(MergeSettingsFile, bool (AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
|
||||
MOCK_METHOD5(MergeSettingsFolder, bool (AZStd::string_view, const Specializations&, AZStd::string_view, AZStd::string_view, AZStd::vector<char>*));
|
||||
MOCK_METHOD1(SetUseFileIO, void (bool));
|
||||
};
|
||||
|
||||
struct SourceDependencyMockedIOTests : UnitTest::ScopedAllocatorSetupFixture
|
||||
, UnitTest::SetRestoreFileIOBaseRAII
|
||||
{
|
||||
SourceDependencyMockedIOTests()
|
||||
: UnitTest::SetRestoreFileIOBaseRAII(m_ioMock)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
using namespace ::testing;
|
||||
|
||||
ON_CALL(m_ioMock, Open(_, _, _))
|
||||
.WillByDefault(Invoke(
|
||||
[](auto, auto, IO::HandleType& handle)
|
||||
{
|
||||
handle = 1234;
|
||||
return AZ::IO::Result(AZ::IO::ResultCode::Success);
|
||||
}));
|
||||
|
||||
ON_CALL(m_ioMock, Size(An<AZ::IO::HandleType>(), _)).WillByDefault(Invoke([](auto, AZ::u64& size)
|
||||
{
|
||||
size = strlen(SourceDependencyJson::TestJson);
|
||||
return AZ::IO::ResultCode::Success;
|
||||
}));
|
||||
|
||||
EXPECT_CALL(m_ioMock, Read(_, _, _, _, _))
|
||||
.WillRepeatedly(Invoke(
|
||||
[](auto, void* buffer, auto, auto, AZ::u64* bytesRead)
|
||||
{
|
||||
memcpy(buffer, SourceDependencyJson::TestJson, strlen(SourceDependencyJson::TestJson));
|
||||
*bytesRead = strlen(SourceDependencyJson::TestJson);
|
||||
return AZ::IO::ResultCode::Success;
|
||||
}));
|
||||
|
||||
EXPECT_CALL(m_ioMock, Close(_)).WillRepeatedly(Return(AZ::IO::ResultCode::Success));
|
||||
}
|
||||
|
||||
IO::NiceFileIOBaseMock m_ioMock;
|
||||
};
|
||||
|
||||
TEST_F(SourceDependencyMockedIOTests, RegularManifestHasPriority)
|
||||
{
|
||||
ImportHandler handler;
|
||||
SettingsRegistryMock settingsRegistry;
|
||||
|
||||
AssetBuilderSDK::CreateJobsRequest request;
|
||||
AssetBuilderSDK::CreateJobsResponse response;
|
||||
|
||||
request.m_sourceFile = "file.fbx";
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
AZStd::string genPath = AZStd::string("cache").append(1, AZ_TRAIT_OS_PATH_SEPARATOR).append("file.fbx.test.gen");
|
||||
|
||||
EXPECT_CALL(m_ioMock, Exists(StrEq("file.fbx.test"))).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(m_ioMock, Exists(StrEq(genPath.c_str()))).Times(Exactly(0));
|
||||
|
||||
ASSERT_TRUE(SceneBuilderWorker::ManifestDependencyCheck(request, response));
|
||||
ASSERT_EQ(response.m_sourceFileDependencyList.size(), 2);
|
||||
}
|
||||
|
||||
TEST_F(SourceDependencyMockedIOTests, GeneratedManifestTest)
|
||||
{
|
||||
ImportHandler handler;
|
||||
SettingsRegistryMock settingsRegistry;
|
||||
|
||||
AssetBuilderSDK::CreateJobsRequest request;
|
||||
AssetBuilderSDK::CreateJobsResponse response;
|
||||
|
||||
request.m_sourceFile = "file.fbx";
|
||||
|
||||
using namespace ::testing;
|
||||
|
||||
AZStd::string genPath = AZStd::string("cache").append(1, AZ_TRAIT_OS_PATH_SEPARATOR).append("file.fbx.test.gen");
|
||||
|
||||
EXPECT_CALL(m_ioMock, Exists(StrEq("file.fbx.test"))).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(m_ioMock, Exists(StrEq(genPath.c_str()))).WillRepeatedly(Return(true));
|
||||
|
||||
ASSERT_TRUE(SceneBuilderWorker::ManifestDependencyCheck(request, response));
|
||||
ASSERT_EQ(response.m_sourceFileDependencyList.size(), 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user