Merge branch 'stabilization/2110' into Prism/FixGemCart

This commit is contained in:
nggieber
2021-11-15 10:20:36 -08:00
26 changed files with 494 additions and 429 deletions
@@ -158,7 +158,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
else:
cmd.append(f"--{key}")
if append_defaults:
cmd.append(f"--project-path={workspace.project}")
cmd.append(f"--project-path={workspace.paths.project()}")
return cmd
# ******
@@ -88,214 +88,6 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
bundler_batch_helper.call_bundles(help="")
bundler_batch_helper.call_bundleSeed(help="")
@pytest.mark.BAT
@pytest.mark.assetpipeline
@pytest.mark.test_case_id("C16877175")
@pytest.mark.skip("'animations/animationeditorfiles/sample1.animgraph' missing, needs investigation")
def test_WindowsAndMac_CreateAssetList_DependenciesCorrect(self, workspace, bundler_batch_helper):
r"""
Tests that an asset list created maps dependencies correctly.
testdependencieslevel\level.pak and lists of known dependencies are used for validation
Test Steps:
1. Create an asset list from the level.pak
2. Create Lists of expected assets in the level.pak
3. Add lists of expected assets to a single list
4. Compare list of expected assets to actual assets
"""
helper = bundler_batch_helper
# Create the asset list file
helper.call_assetLists(
addSeed=r"levels\testdependencieslevel\level.pak",
assetListFile=helper['asset_info_file_request']
)
assert os.path.isfile(helper["asset_info_file_result"])
# Lists of known relative locations of assets
default_level_assets = [
"engineassets/texturemsg/defaultnouvs.dds",
"engineassets/texturemsg/defaultnouvs.dds.1",
"engineassets/texturemsg/defaultnouvs.dds.2",
"engineassets/texturemsg/defaultnouvs.dds.3",
"engineassets/texturemsg/defaultnouvs.dds.4",
"engineassets/texturemsg/defaultnouvs.dds.5",
"engineassets/texturemsg/defaultnouvs.dds.6",
"engineassets/texturemsg/defaultnouvs.dds.7",
"engineassets/texturemsg/defaultnouvs_ddn.dds",
"engineassets/texturemsg/defaultnouvs_ddn.dds.1",
"engineassets/texturemsg/defaultnouvs_ddn.dds.2",
"engineassets/texturemsg/defaultnouvs_ddn.dds.3",
"engineassets/texturemsg/defaultnouvs_ddn.dds.4",
"engineassets/texturemsg/defaultnouvs_ddn.dds.5",
"engineassets/texturemsg/defaultnouvs_spec.dds",
"engineassets/texturemsg/defaultnouvs_spec.dds.1",
"engineassets/texturemsg/defaultnouvs_spec.dds.2",
"engineassets/texturemsg/defaultnouvs_spec.dds.3",
"engineassets/texturemsg/defaultnouvs_spec.dds.4",
"engineassets/texturemsg/defaultnouvs_spec.dds.5",
"engineassets/textures/defaults/16_grey.dds",
"engineassets/textures/cubemap/default_level_cubemap.dds",
"engineassets/textures/cubemap/default_level_cubemap.dds.1",
"engineassets/textures/cubemap/default_level_cubemap.dds.2",
"engineassets/textures/cubemap/default_level_cubemap.dds.3",
"engineassets/textures/cubemap/default_level_cubemap.dds.4",
"engineassets/textures/cubemap/default_level_cubemap_diff.dds",
"engineassets/materials/water/ocean_default.mtl",
"engineassets/textures/defaults/spot_default.dds",
"engineassets/textures/defaults/spot_default.dds.1",
"engineassets/textures/defaults/spot_default.dds.2",
"engineassets/textures/defaults/spot_default.dds.3",
"engineassets/textures/defaults/spot_default.dds.4",
"engineassets/textures/defaults/spot_default.dds.5",
"materials/material_terrain_default.mtl",
"textures/skys/night/half_moon.dds",
"textures/skys/night/half_moon.dds.1",
"textures/skys/night/half_moon.dds.2",
"textures/skys/night/half_moon.dds.3",
"textures/skys/night/half_moon.dds.4",
"textures/skys/night/half_moon.dds.5",
"textures/skys/night/half_moon.dds.6",
"engineassets/materials/sky/sky.mtl",
"levels/testdependencieslevel/level.pak",
"levels/testdependencieslevel/terrain/cover.ctc",
"levels/testdependencieslevel/terraintexture.pak",
]
sequence_material_cube_assets = [
"textures/test_texture_sequence/test_texture_sequence000.dds",
"textures/test_texture_sequence/test_texture_sequence001.dds",
"textures/test_texture_sequence/test_texture_sequence002.dds",
"textures/test_texture_sequence/test_texture_sequence003.dds",
"textures/test_texture_sequence/test_texture_sequence004.dds",
"textures/test_texture_sequence/test_texture_sequence005.dds",
"objects/_primitives/_box_1x1.cgf",
"materials/test_texture_sequence.mtl",
"objects/_primitives/_box_1x1.mtl",
"textures/_primitives/middle_gray_checker.dds",
"textures/_primitives/middle_gray_checker.dds.1",
"textures/_primitives/middle_gray_checker.dds.2",
"textures/_primitives/middle_gray_checker.dds.3",
"textures/_primitives/middle_gray_checker.dds.4",
"textures/_primitives/middle_gray_checker.dds.5",
"textures/_primitives/middle_gray_checker_ddn.dds",
"textures/_primitives/middle_gray_checker_ddn.dds.1",
"textures/_primitives/middle_gray_checker_ddn.dds.2",
"textures/_primitives/middle_gray_checker_ddn.dds.3",
"textures/_primitives/middle_gray_checker_ddn.dds.4",
"textures/_primitives/middle_gray_checker_ddn.dds.5",
"textures/_primitives/middle_gray_checker_spec.dds",
"textures/_primitives/middle_gray_checker_spec.dds.1",
"textures/_primitives/middle_gray_checker_spec.dds.2",
"textures/_primitives/middle_gray_checker_spec.dds.3",
"textures/_primitives/middle_gray_checker_spec.dds.4",
"textures/_primitives/middle_gray_checker_spec.dds.5",
]
character_with_simplified_material_assets = [
"objects/characters/jack/jack.actor",
"objects/characters/jack/jack.mtl",
"objects/characters/jack/textures/jack_diff.dds",
"objects/characters/jack/textures/jack_diff.dds.1",
"objects/characters/jack/textures/jack_diff.dds.2",
"objects/characters/jack/textures/jack_diff.dds.3",
"objects/characters/jack/textures/jack_diff.dds.4",
"objects/characters/jack/textures/jack_diff.dds.5",
"objects/characters/jack/textures/jack_diff.dds.6",
"objects/characters/jack/textures/jack_diff.dds.7",
"objects/characters/jack/textures/jack_spec.dds",
"objects/characters/jack/textures/jack_spec.dds.1",
"objects/characters/jack/textures/jack_spec.dds.2",
"objects/characters/jack/textures/jack_spec.dds.3",
"objects/characters/jack/textures/jack_spec.dds.4",
"objects/characters/jack/textures/jack_spec.dds.5",
"objects/characters/jack/textures/jack_spec.dds.6",
"objects/characters/jack/textures/jack_spec.dds.7",
"objects/default/editorprimitive.mtl",
"engineassets/textures/grey.dds",
"animations/animationeditorfiles/sample0.animgraph",
"animations/motions/jack_death_fall_back_zup.motion",
"animations/animationeditorfiles/sample1.animgraph",
"animations/animationeditorfiles/sample0.motionset",
"animations/motions/rin_jump.motion",
"animations/animationeditorfiles/sample1.motionset",
"animations/motions/rin_idle.motion",
"animations/motions/jack_idle_aim_zup.motion",
]
spawner_assets = [
"slices/sphere.dynamicslice",
"objects/default/primitive_sphere.cgf",
"test1.luac",
"test2.luac",
]
ui_canvas_assets = [
"fonts/vera.ttf",
"fonts/vera.font",
"scriptcanvas/mainmenu.scriptcanvas_compiled",
"fonts/vera.fontfamily",
"ui/canvas/start.uicanvas",
"fonts/vera-italic.font",
"ui/textureatlas/sample.texatlasidx",
"fonts/vera-bold-italic.ttf",
"fonts/vera-bold.font",
"ui/textures/prefab/button_normal.dds",
"ui/textures/prefab/button_normal.sprite",
"fonts/vera-italic.ttf",
"ui/textureatlas/sample.dds",
"fonts/vera-bold-italic.font",
"fonts/vera-bold.ttf",
"ui/textures/prefab/button_disabled.dds",
"ui/textures/prefab/button_disabled.sprite",
]
wwise_and_atl_assets = [
"libs/gameaudio/wwise/levels/testdependencieslevel/test_dependencies_level.xml",
"sounds/wwise/test_bank3.bnk",
"sounds/wwise/test_bank4.bnk",
"sounds/wwise/test_bank5.bnk",
"sounds/wwise/test_bank1.bnk",
"sounds/wwise/init.bnk",
"sounds/wwise/499820003.wem",
"sounds/wwise/196049145.wem",
]
particle_library_assets = [
"libs/particles/milestone2particles.xml",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.1",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.2",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.3",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.4",
"textures/milestone2/particles/fx_launchermuzzlering_01.dds.5",
"textures/milestone2/particles/fx_sparkstreak_01.dds",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.1",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.2",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.3",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.4",
"textures/milestone2/particles/fx_launchermuzzlefront_01.dds.5",
]
lens_flares_library_assets = ["libs/flares/flares.xml", "textures/lights/flare01.dds"]
expected_assets_list = default_level_assets
expected_assets_list.extend(sequence_material_cube_assets)
expected_assets_list.extend(character_with_simplified_material_assets)
expected_assets_list.extend(spawner_assets)
expected_assets_list.extend(ui_canvas_assets)
expected_assets_list.extend(wwise_and_atl_assets)
expected_assets_list.extend(particle_library_assets)
expected_assets_list.extend(lens_flares_library_assets) # All expected assets
# Get actual calculated dependencies from the asset list created
actual_assets_list = []
for rel_path in helper.get_asset_relative_paths(helper["asset_info_file_result"]):
actual_assets_list.append(rel_path)
assert sorted(actual_assets_list) == sorted(expected_assets_list)
@pytest.mark.BAT
@pytest.mark.assetpipeline
@@ -310,9 +102,9 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
3. Read and store contents of asset list into memory
4. Attempt to create a new asset list in without using --allowOverwrites
5. Verify that Asset Bundler returns false
6. Verify that file contents of the orignally created asset list did not change from what was stored in memory
6. Verify that file contents of the originally created asset list did not change from what was stored in memory
7. Attempt to create a new asset list without debug while allowing overwrites
8. Verify that file contents of the orignally created asset list changed from what was stored in memory
8. Verify that file contents of the originally created asset list changed from what was stored in memory
"""
helper = bundler_batch_helper
seed_list = os.path.join(workspace.paths.engine_root(), "Assets", "Engine", "SeedAssetList.seed") # Engine seed list
@@ -919,7 +711,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Extra arguments for pattern comparison
cmd.extend([f"--filePatternType={pattern_type}", f"--filePattern={pattern}"])
if workspace.project:
cmd.append(f'--project-path={project_name}')
cmd.append(f'--project-path={workspace.paths.project()}')
return cmd
# End generate_compare_command()
@@ -960,7 +752,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
output_mac_asset_list = helper.platform_file_name(last_output_arg, platform)
# Build execution command
cmd = generate_compare_command(platform_arg, workspace.project)
cmd = generate_compare_command(platform_arg, workspace.paths.project())
# Execute command
subprocess.check_call(cmd)
@@ -995,7 +787,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
f"--comparisonRulesFile={rule_file}",
f"--comparisonType={args[1]}",
r"--addComparison",
f"--project-path={workspace.project}",
f"--project-path={workspace.paths.project()}",
]
if args[1] == "4":
# If pattern comparison, append a few extra arguments
@@ -1117,7 +909,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
"--addDefaultSeedListFiles",
"--platform=pc",
"--print",
f"--project-path={workspace.project}"
f"--project-path={workspace.paths.project()}"
],
universal_newlines=True,
)
@@ -1189,7 +981,7 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
# Make sure file gets deleted on teardown
request.addfinalizer(lambda: fs.delete([bundle_result_path], True, False))
bundles_folder = os.path.join(workspace.paths.engine_root(), workspace.project, "Bundles")
bundles_folder = os.path.join(workspace.paths.project(), "Bundles")
level_pak = r"levels\testdependencieslevel\level.pak"
bundle_request_path = os.path.join(bundles_folder, "bundle.pak")
bundle_result_path = os.path.join(bundles_folder,
@@ -1243,23 +1035,64 @@ class TestsAssetBundlerBatch_WindowsAndMac(object):
2. Verify file was created
3. Verify that only the expected assets are present in the created asset list
"""
expected_assets = [
expected_assets = sorted([
"ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas",
"ui/textures/prefab/button_normal.sprite"
]
"ui/textures/prefab/button_disabled.tif.streamingimage",
"ui/textures/prefab/tooltip_sliced.tif.streamingimage",
"ui/textures/prefab/button_normal.tif.streamingimage"
])
# Printing these lists out can save a step in debugging if this test fails on Jenkins.
logger.info(f"expected_assets: {expected_assets}")
skip_assets = sorted([
"ui/scripts/lyshineexamples/animation/multiplesequences.luac",
"ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac",
"fonts/vera.fontfamily",
"fonts/vera-italic.font",
"fonts/vera.font",
"fonts/vera-bold.font",
"fonts/vera-bold-italic.font",
"fonts/vera-italic.ttf",
"fonts/vera.ttf",
"fonts/vera-bold.ttf",
"fonts/vera-bold-italic.ttf"
])
logger.info(f"skip_assets: {skip_assets}")
expected_and_skip_assets = sorted(expected_assets + skip_assets)
# Printing both together to make it quick to compare the results in the logs for a test failure on Jenkins
logger.info(f"expected_and_skip_assets: {expected_and_skip_assets}")
# First, generate an asset info file without skipping, to get a list that can be used as a baseline to verify
# the files were actually skipped, and not just missing.
bundler_batch_helper.call_assetLists(
assetListFile=bundler_batch_helper['asset_info_file_request'],
addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas"
)
assert os.path.isfile(bundler_batch_helper["asset_info_file_result"])
assets_in_no_skip_list = []
for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]):
assets_in_no_skip_list.append(rel_path)
assets_in_no_skip_list = sorted(assets_in_no_skip_list)
logger.info(f"assets_in_no_skip_list: {assets_in_no_skip_list}")
assert assets_in_no_skip_list == expected_and_skip_assets
# Now generate an asset info file using the skip command, and verify the skip files are not in the list.
bundler_batch_helper.call_assetLists(
assetListFile=bundler_batch_helper['asset_info_file_request'],
addSeed="ui/canvases/lyshineexamples/animation/multiplesequences.uicanvas",
skip="ui/textures/prefab/button_disabled.sprite,ui/scripts/lyshineexamples/animation/multiplesequences.luac,"
"ui/textures/prefab/tooltip_sliced.sprite,ui/scripts/lyshineexamples/unloadthiscanvasbutton.luac,fonts/vera.fontfamily,fonts/vera-italic.font,"
"fonts/vera.font,fonts/vera-bold.font,fonts/vera-bold-italic.font,fonts/vera-italic.ttf,fonts/vera.ttf,fonts/vera-bold.ttf,fonts/vera-bold-italic.ttf"
allowOverwrites="",
skip=','.join(skip_assets)
)
assert os.path.isfile(bundler_batch_helper["asset_info_file_result"])
assets_in_list = []
for rel_path in bundler_batch_helper.get_asset_relative_paths(bundler_batch_helper["asset_info_file_result"]):
assets_in_list.append(rel_path)
assets_in_list = sorted(assets_in_list)
logger.info(f"assets_in_list: {assets_in_list}")
assert assets_in_list == expected_assets
assert sorted(assets_in_list) == sorted(expected_assets)
@pytest.mark.BAT
@pytest.mark.assetpipeline
@@ -64,6 +64,9 @@ namespace AzToolsFramework::Prefab
);
m_backButton->setToolTip("Up one level (-)");
// Currently hide this button until we can correctly disable/enable it based on context.
m_backButton->hide();
}
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
@@ -140,7 +140,8 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
m_gemModel->GetSelectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
});
}
@@ -223,7 +224,7 @@ namespace O3DE::ProjectManager
const bool gemFound = gemInfoHash.contains(gemName);
if (!gemFound && !m_gemModel->IsAdded(index) && !m_gemModel->IsAddedDependency(index))
{
m_gemModel->removeRow(i);
m_gemModel->RemoveGem(index);
}
else
{
@@ -253,7 +254,7 @@ namespace O3DE::ProjectManager
m_filterWidget->ResetAllFilters();
// Reselect the same selection to proc UI updates
m_proxyModel->GetSelectionModel()->select(m_proxyModel->GetSelectionModel()->selection(), QItemSelectionModel::Select);
m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select);
}
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
@@ -282,7 +283,7 @@ namespace O3DE::ProjectManager
if (added && GemModel::GetDownloadStatus(modelIndex) == GemInfo::DownloadStatus::NotDownloaded)
{
m_downloadController->AddGemDownload(GemModel::GetName(modelIndex));
GemModel::SetDownloadStatus(*m_proxyModel, m_proxyModel->mapFromSource(modelIndex), GemInfo::DownloadStatus::Downloading);
GemModel::SetDownloadStatus(*m_gemModel, modelIndex, GemInfo::DownloadStatus::Downloading);
}
}
@@ -314,7 +315,7 @@ namespace O3DE::ProjectManager
}
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_gemListView->scrollTo(proxyIndex);
m_rightPanelStack->setCurrentIndex(RightPanelWidgetOrder::Inspector);
@@ -378,6 +379,9 @@ namespace O3DE::ProjectManager
{
const QString selectedGemPath = m_gemModel->GetPath(modelIndex);
// Remove gem from gems to be added
GemModel::SetIsAdded(*m_gemModel, modelIndex, false);
// Unregister the gem
auto unregisterResult = PythonBindingsInterface::Get()->UnregisterGem(selectedGemPath);
if (!unregisterResult)
@@ -386,8 +390,10 @@ namespace O3DE::ProjectManager
}
else
{
const QString selectedGemName = m_gemModel->GetName(modelIndex);
// Remove gem from model
m_gemModel->removeRow(modelIndex.row());
m_gemModel->RemoveGem(modelIndex);
// Delete uninstalled gem directory
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
@@ -398,6 +404,11 @@ namespace O3DE::ProjectManager
// Show undownloaded remote gem again
Refresh();
// Select remote gem
QModelIndex remoteGemIndex = m_gemModel->FindIndexByNameString(selectedGemName);
QModelIndex proxyIndex = m_proxyModel->mapFromSource(remoteGemIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
}
@@ -592,7 +603,8 @@ namespace O3DE::ProjectManager
if (succeeded)
{
// refresh the information for downloaded gems
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
const AZ::Outcome<QVector<GemInfo>, AZStd::string>& allGemInfosResult =
PythonBindingsInterface::Get()->GetAllGemInfos(m_projectPath);
if (allGemInfosResult.IsSuccess())
{
// we should find the gem name now in all gem infos
@@ -600,15 +612,33 @@ namespace O3DE::ProjectManager
{
if (gemInfo.m_name == gemName)
{
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
QModelIndex oldIndex = m_gemModel->FindIndexByNameString(gemName);
if (oldIndex.isValid())
{
m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadSuccessful, GemModel::RoleDownloadStatus);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink);
// Check if old gem is selected
bool oldGemSelected = false;
if (m_gemModel->GetSelectionModel()->currentIndex() == oldIndex)
{
oldGemSelected = true;
}
// Remove old remote gem
m_gemModel->RemoveGem(oldIndex);
// Add new downloaded version of gem
QModelIndex newIndex = m_gemModel->AddGem(gemInfo);
GemModel::SetDownloadStatus(*m_gemModel, newIndex, GemInfo::DownloadSuccessful);
GemModel::SetIsAdded(*m_gemModel, newIndex, true);
// Select new version of gem if it was previously selected
if (oldGemSelected)
{
QModelIndex proxyIndex = m_proxyModel->mapFromSource(newIndex);
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
}
return;
break;
}
}
}
@@ -618,7 +648,7 @@ namespace O3DE::ProjectManager
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
{
m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadFailed, GemModel::RoleDownloadStatus);
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
}
}
}
@@ -26,14 +26,14 @@ namespace O3DE::ProjectManager
return m_selectionModel;
}
void GemModel::AddGem(const GemInfo& gemInfo)
QModelIndex GemModel::AddGem(const GemInfo& gemInfo)
{
if (FindIndexByNameString(gemInfo.m_name).isValid())
{
// do not add gems with duplicate names
// this can happen by mistake or when a gem repo has a gem with the same name as a local gem
AZ_TracePrintf("GemModel", "Ignoring duplicate gem: %s", gemInfo.m_name.toUtf8().constData());
return;
return QModelIndex();
}
QStandardItem* item = new QStandardItem();
@@ -67,6 +67,22 @@ namespace O3DE::ProjectManager
const QModelIndex modelIndex = index(rowCount()-1, 0);
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
return modelIndex;
}
void GemModel::RemoveGem(const QModelIndex& modelIndex)
{
removeRow(modelIndex.row());
}
void GemModel::RemoveGem(const QString& gemName)
{
auto nameFind = m_nameToIndexMap.find(gemName);
if (nameFind != m_nameToIndexMap.end())
{
removeRow(nameFind->row());
}
}
void GemModel::Clear()
@@ -391,11 +407,11 @@ namespace O3DE::ProjectManager
// Select a valid row if currently selected row was removed
if (selectedRowRemoved)
{
for (const QModelIndex& index : m_nameToIndexMap)
for (const QModelIndex& index : m_nameToIndexMap)
{
if (index.isValid())
{
GetSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
break;
}
}
@@ -55,7 +55,9 @@ namespace O3DE::ProjectManager
RoleRepoUri
};
void AddGem(const GemInfo& gemInfo);
QModelIndex AddGem(const GemInfo& gemInfo);
void RemoveGem(const QModelIndex& modelIndex);
void RemoveGem(const QString& gemName);
void Clear();
void UpdateGemDependencies();
@@ -75,7 +75,7 @@ namespace O3DE::ProjectManager
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
m_gemRepoListView->selectionModel()->setCurrentIndex(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
+1
View File
@@ -160,6 +160,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::Atom_RPI.Public
Gem::Atom_RHI.Public
Gem::Atom_RPI.Edit
Gem::Atom_Utils.TestUtils.Static
)
ly_add_googletest(
NAME Gem::Atom_RPI.Tests
@@ -209,10 +209,6 @@ namespace AZ
//! Traversal will stop once all properties have been enumerated or the callback function returns false
void EnumeratePropertiesInDisplayOrder(const EnumeratePropertiesCallback& callback) const;
//! Convert the property value into the format that will be stored in the source data
//! This is primarily needed to support conversions of special types like enums and images
bool ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const;
Outcome<Data::Asset<MaterialTypeAsset>> CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath = "", bool elevateWarnings = true) const;
//! Possibly renames @propertyId based on the material version update steps.
@@ -300,48 +300,6 @@ namespace AZ
}
}
bool MaterialTypeSourceData::ConvertPropertyValueToSourceDataFormat(const PropertyDefinition& propertyDefinition, MaterialPropertyValue& propertyValue) const
{
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
{
const uint32_t index = propertyValue.GetValue<uint32_t>();
if (index >= propertyDefinition.m_enumValues.size())
{
AZ_Error("Material source data", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
propertyValue = propertyDefinition.m_enumValues[index];
return true;
}
// Image asset references must be converted from asset IDs to a relative source file path
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image && propertyValue.Is<Data::Asset<ImageAsset>>())
{
const Data::Asset<ImageAsset>& imageAsset = propertyValue.GetValue<Data::Asset<ImageAsset>>();
Data::AssetInfo imageAssetInfo;
if (imageAsset.GetId().IsValid())
{
bool result = false;
AZStd::string rootFilePath;
const AZStd::string platformName = ""; // Empty for default
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetAssetInfoById,
imageAsset.GetId(), imageAsset.GetType(), platformName, imageAssetInfo, rootFilePath);
if (!result)
{
AZ_Error("Material source data", false, "Image asset could not be found for property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
}
propertyValue = imageAssetInfo.m_relativePath;
return true;
}
return true;
}
Outcome<Data::Asset<MaterialTypeAsset>> MaterialTypeSourceData::CreateMaterialTypeAsset(Data::AssetId assetId, AZStd::string_view materialTypeSourceFilePath, bool elevateWarnings) const
{
MaterialTypeAssetCreator materialTypeAssetCreator;
@@ -21,7 +21,7 @@
#include <Common/AssetManagerTestFixture.h>
#include <Common/RHI/Stubs.h>
#include <Common/RHI/Factory.h>
#include <Common/AssetSystemStub.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
namespace UnitTest
{
@@ -10,8 +10,6 @@ set(FILES
Tests/Buffer/BufferTests.cpp
Tests/Common/AssetManagerTestFixture.cpp
Tests/Common/AssetManagerTestFixture.h
Tests/Common/AssetSystemStub.cpp
Tests/Common/AssetSystemStub.h
Tests/Common/ErrorMessageFinder.cpp
Tests/Common/ErrorMessageFinder.h
Tests/Common/ErrorMessageFinderTests.cpp
@@ -61,3 +61,31 @@ ly_add_target(
PRIVATE
Gem::AtomToolsFramework.Static
)
################################################################################
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AtomToolsFramework.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
atomtoolsframework_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
Tests
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzTestShared
Gem::AtomToolsFramework.Static
Gem::Atom_Utils.TestUtils.Static
)
ly_add_googletest(
NAME Gem::AtomToolsFramework.Tests
)
endif()
@@ -7,11 +7,12 @@
*/
#pragma once
#include <AzCore/std/any.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyDescriptor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertyValue.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/any.h>
namespace AzToolsFramework
{
@@ -35,12 +36,31 @@ namespace AtomToolsFramework
//! Convert and assign material property meta data fields to editor dynamic property configuration
void ConvertToPropertyConfig(AtomToolsFramework::DynamicPropertyConfig& propertyConfig, const AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData);
//! Convert and assign editor dynamic property configuration fields to material property meta data
//! Convert and assign editor dynamic property configuration fields to material property meta data
void ConvertToPropertyMetaData(AZ::RPI::MaterialPropertyDynamicMetadata& propertyMetaData, const AtomToolsFramework::DynamicPropertyConfig& propertyConfig);
//! Compare equality of data types and values of editor property stored in AZStd::any
bool ArePropertyValuesEqual(const AZStd::any& valueA, const AZStd::any& valueB);
//! Convert the property value into the format that will be stored in the source data
//! This is primarily needed to support conversions of special types like enums and images
//! @param exportPath absolute path of the file being saved
//! @param propertyDefinition describes type information and other details about propertyValue
//! @param propertyValue the value being converted before saving
bool ConvertToExportFormat(
const AZStd::string& exportPath,
const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition,
AZ::RPI::MaterialPropertyValue& propertyValue);
//! Generate a file path from the exported file to the external reference.
//! This function returns a relative path from the export file to the reference file.
//! If the relative path is too different or distant from the export path then we return the asset folder relative path.
//! @param exportPath absolute path of the file being saved
//! @param referencePath absolute path of a file that will be treated as an external reference
//! @param maxPathDepth the maximum relative depth or number of parent or child folders between the export path and the reference path
AZStd::string GetExteralReferencePath(
const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth = 2);
//! Traverse up the instance data node hierarchy to find the containing dynamic property object
const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode);
} // namespace AtomToolsFramework
@@ -6,9 +6,10 @@
*
*/
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AtomToolsFramework/DynamicProperty/DynamicProperty.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Reflect/Image/ImageAsset.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
@@ -18,6 +19,7 @@
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Vector4.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
namespace AtomToolsFramework
@@ -163,6 +165,88 @@ namespace AtomToolsFramework
return false;
}
bool ConvertToExportFormat(
const AZStd::string& exportPath,
const AZ::RPI::MaterialTypeSourceData::PropertyDefinition& propertyDefinition,
AZ::RPI::MaterialPropertyValue& propertyValue)
{
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Enum && propertyValue.Is<uint32_t>())
{
const uint32_t index = propertyValue.GetValue<uint32_t>();
if (index >= propertyDefinition.m_enumValues.size())
{
AZ_Error("AtomToolsFramework", false, "Invalid value for material enum property: '%s'.", propertyDefinition.m_name.c_str());
return false;
}
propertyValue = propertyDefinition.m_enumValues[index];
return true;
}
// Image asset references must be converted from asset IDs to a relative source file path
if (propertyDefinition.m_dataType == AZ::RPI::MaterialPropertyDataType::Image)
{
if (propertyValue.Is<AZ::Data::Asset<AZ::RPI::ImageAsset>>())
{
const auto& imageAsset = propertyValue.GetValue<AZ::Data::Asset<AZ::RPI::ImageAsset>>();
const auto& imagePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(imageAsset.GetId());
propertyValue = GetExteralReferencePath(exportPath, imagePath);
return true;
}
if (propertyValue.Is<AZ::Data::Instance<AZ::RPI::Image>>())
{
const auto& image = propertyValue.GetValue<AZ::Data::Instance<AZ::RPI::Image>>();
const auto& imagePath = image ? AZ::RPI::AssetUtils::GetSourcePathByAssetId(image->GetAssetId()) : "";
propertyValue = GetExteralReferencePath(exportPath, imagePath);
return true;
}
}
return true;
}
AZStd::string GetExteralReferencePath(const AZStd::string& exportPath, const AZStd::string& referencePath, const uint32_t maxPathDepth)
{
if (referencePath.empty())
{
return {};
}
AZ::IO::BasicPath<AZStd::string> exportFolder(exportPath);
exportFolder.RemoveFilename();
const AZStd::string relativePath = AZ::IO::PathView(referencePath).LexicallyRelative(exportFolder).StringAsPosix();
// Count the difference in depth between the export file path and the referenced file path.
uint32_t parentFolderCount = 0;
AZStd::string::size_type pos = 0;
const AZStd::string parentFolderToken = "..";
while ((pos = relativePath.find(parentFolderToken, pos)) != AZStd::string::npos)
{
parentFolderCount++;
pos += parentFolderToken.length();
}
// If the difference in depth is too great then revert to using the asset folder relative path.
// We could change this to only use relative paths for references in subfolders.
if (parentFolderCount > maxPathDepth)
{
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
bool sourceInfoFound = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, referencePath.c_str(),
assetInfo, watchFolder);
if (sourceInfoFound)
{
return assetInfo.m_relativePath;
}
}
return relativePath;
}
const AtomToolsFramework::DynamicProperty* FindDynamicPropertyForInstanceDataNode(const AzToolsFramework::InstanceDataNode* pNode)
{
// Traverse up the hierarchy from the input node to search for an instance corresponding to material inspector property
@@ -172,7 +256,8 @@ namespace AtomToolsFramework
const AZ::SerializeContext::ClassData* classData = currentNode->GetClassMetadata();
if (context && classData)
{
if (context->CanDowncast(classData->m_typeId, azrtti_typeid<AtomToolsFramework::DynamicProperty>(), classData->m_azRtti, nullptr))
if (context->CanDowncast(
classData->m_typeId, azrtti_typeid<AtomToolsFramework::DynamicProperty>(), classData->m_azRtti, nullptr))
{
return static_cast<const AtomToolsFramework::DynamicProperty*>(currentNode->FirstInstance());
}
@@ -7,25 +7,71 @@
*/
#include <AzTest/AzTest.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
class AtomToolsFrameworkTest
: public ::testing::Test
namespace UnitTest
{
protected:
void SetUp() override
class AtomToolsFrameworkTest : public ::testing::Test
{
protected:
void SetUp() override
{
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create(AZ::SystemAllocator::Descriptor());
}
m_assetSystemStub.Activate();
RegisterSourceAsset("objects/upgrades/materials/supercondor.material");
RegisterSourceAsset("materials/condor.material");
RegisterSourceAsset("materials/talisman.material");
RegisterSourceAsset("materials/city.material");
RegisterSourceAsset("materials/totem.material");
RegisterSourceAsset("textures/orange.png");
RegisterSourceAsset("textures/red.png");
RegisterSourceAsset("textures/gold.png");
RegisterSourceAsset("textures/fuzz.png");
}
void TearDown() override
{
m_assetSystemStub.Deactivate();
if (AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
}
void RegisterSourceAsset(const AZStd::string& path)
{
const AZ::IO::BasicPath assetRootPath = AZ::IO::PathView(m_assetRoot).LexicallyNormal();
const AZ::IO::BasicPath normalizedPath = AZ::IO::BasicPath(assetRootPath).Append(path).LexicallyNormal();
AZ::Data::AssetInfo assetInfo = {};
assetInfo.m_assetId = AZ::Uuid::CreateRandom();
assetInfo.m_relativePath = normalizedPath.LexicallyRelative(assetRootPath).StringAsPosix();
m_assetSystemStub.RegisterSourceInfo(normalizedPath.StringAsPosix().c_str(), assetInfo, assetRootPath.StringAsPosix().c_str());
}
static constexpr const char* m_assetRoot = "d:/project/assets/";
AssetSystemStub m_assetSystemStub;
};
TEST_F(AtomToolsFrameworkTest, GetExteralReferencePath_Succeeds)
{
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/condor.material", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "", 2), "");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 2), "../textures/gold.png");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/materials/talisman.material", "d:/project/assets/textures/gold.png", 0), "textures/gold.png");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 3), "../../../materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 2), "materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 1), "materials/condor.material");
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 0), "materials/condor.material");
}
void TearDown() override
{
}
};
TEST_F(AtomToolsFrameworkTest, SanityTest)
{
ASSERT_TRUE(true);
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
} // namespace UnitTest
@@ -0,0 +1,11 @@
#
# 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
#
#
set(FILES
Tests/AtomToolsFrameworkTest.cpp
)
@@ -230,18 +230,13 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial);
// populate sourceData with modified or overwritten properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(m_absolutePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue);
});
@@ -304,18 +299,13 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
sourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial);
// populate sourceData with modified or overwritten properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue);
});
@@ -377,23 +367,18 @@ namespace MaterialEditor
// create source data from properties
MaterialSourceData sourceData;
sourceData.m_materialType = m_materialSourceData.m_materialType;
AZ_Assert(m_materialAsset && m_materialAsset->GetMaterialTypeAsset(), "When IsOpen() is true, these assets should not be null.");
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType);
// Only assign a parent path if the source was a .material
if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension))
{
sourceData.m_parentMaterial = m_relativePath;
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_absolutePath);
}
// Force save data to store forward slashes
AzFramework::StringFunc::Replace(sourceData.m_materialType, "\\", "/");
AzFramework::StringFunc::Replace(sourceData.m_parentMaterial, "\\", "/");
// populate sourceData with modified properties
const bool savedProperties = SavePropertiesToSourceData(sourceData, [](const AtomToolsFramework::DynamicProperty& property) {
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue);
});
@@ -590,7 +575,8 @@ namespace MaterialEditor
}
}
bool MaterialDocument::SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const
bool MaterialDocument::SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const
{
using namespace AZ;
using namespace RPI;
@@ -598,7 +584,7 @@ namespace MaterialEditor
bool result = true;
// populate sourceData with properties that meet the filter
m_materialTypeSourceData.EnumerateProperties([this, &sourceData, &propertyFilter, &result](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
const MaterialPropertyId propertyId(groupName, propertyName);
@@ -608,7 +594,7 @@ namespace MaterialEditor
MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue());
if (propertyValue.IsValid())
{
if (!m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyDefinition, propertyValue))
{
AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetFullName().GetCStr(), m_absolutePath.c_str());
result = false;
@@ -662,8 +648,6 @@ namespace MaterialEditor
return false;
}
AZStd::string materialTypeSourceFilePath;
// The material document and inspector are constructed from source data
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialSourceData::Extension))
{
@@ -674,13 +658,24 @@ namespace MaterialEditor
return false;
}
// We must also always load the material type data for a complete, ordered set of the
// groups and properties that will be needed for comparison and building the inspector
materialTypeSourceFilePath = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath);
// We always need the absolute path for the material type and parent material to load source data and resolving
// relative paths when saving. This will convert and store them as absolute paths for use within the document.
if (!m_materialSourceData.m_parentMaterial.empty())
{
m_materialSourceData.m_parentMaterial =
AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
}
if (!m_materialSourceData.m_materialType.empty())
{
m_materialSourceData.m_materialType = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
}
// Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", materialTypeSourceFilePath.c_str());
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return false;
}
m_materialTypeSourceData = materialTypeOutcome.GetValue();
@@ -693,10 +688,10 @@ namespace MaterialEditor
}
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension))
{
materialTypeSourceFilePath = m_absolutePath;
// Load the material type source data, which will be used for enumerating properties and building material source data
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(materialTypeSourceFilePath);
// A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the
// material type path.
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess())
{
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
@@ -704,9 +699,8 @@ namespace MaterialEditor
}
m_materialTypeSourceData = materialTypeOutcome.GetValue();
// The document represents a material, not a material type.
// If the input data is a material type file we have to generate the material source data by referencing it.
m_materialSourceData.m_materialType = m_relativePath;
// We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times.
m_materialSourceData.m_materialType = m_absolutePath;
m_materialSourceData.m_parentMaterial.clear();
}
else
@@ -748,25 +742,24 @@ namespace MaterialEditor
if (!m_materialSourceData.m_parentMaterial.empty())
{
AZ::RPI::MaterialSourceData parentMaterialSourceData;
const auto parentMaterialFilePath = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(parentMaterialFilePath, parentMaterialSourceData))
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_materialSourceData.m_parentMaterial, parentMaterialSourceData))
{
AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", parentMaterialFilePath.c_str());
AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false;
}
const auto parentMaterialAssetIdResult = AssetUtils::MakeAssetId(parentMaterialFilePath, 0);
const auto parentMaterialAssetIdResult = AssetUtils::MakeAssetId(m_materialSourceData.m_parentMaterial, 0);
if (!parentMaterialAssetIdResult)
{
AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", parentMaterialFilePath.c_str());
AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false;
}
auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData(
parentMaterialAssetIdResult.GetValue(), parentMaterialFilePath, true, true);
parentMaterialAssetIdResult.GetValue(), m_materialSourceData.m_parentMaterial, true, true);
if (!parentMaterialAssetResult)
{
AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", parentMaterialFilePath.c_str());
AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false;
}
@@ -877,7 +870,8 @@ namespace MaterialEditor
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
}
const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext(materialTypeSourceFilePath, m_materialAsset->GetMaterialPropertiesLayout());
const MaterialFunctorSourceData::EditorContext editorContext =
MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (Ptr<MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
{
MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext);
@@ -104,7 +104,8 @@ namespace MaterialEditor
void SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceUUID) override;
//////////////////////////////////////////////////////////////////////////
bool SavePropertiesToSourceData(AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const;
bool SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const;
bool OpenInternal(AZStd::string_view loadPath);
+21
View File
@@ -27,6 +27,27 @@ ly_add_target(
3rdParty::libpng
)
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME Atom_Utils.TestUtils.Static STATIC
NAMESPACE Gem
FILES_CMAKE
atom_utils_editor_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Source
PUBLIC
Include
BUILD_DEPENDENCIES
PRIVATE
AZ::AtomCore
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework
)
endif()
################################################################################
# Tests
################################################################################
@@ -6,7 +6,7 @@
*
*/
#include <Common/AssetSystemStub.h>
#include <Atom/Utils/TestUtils/AssetSystemStub.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace UnitTest
@@ -0,0 +1,12 @@
#
# 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
#
#
set(FILES
Include/Atom/Utils/TestUtils/AssetSystemStub.h
Source/TestUtils/AssetSystemStub.cpp
)
@@ -17,6 +17,7 @@
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <Atom/RPI.Reflect/Material/MaterialTypeAsset.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -97,51 +98,28 @@ namespace AZ
bool SaveSourceMaterialFromEditData(const AZStd::string& path, const MaterialEditData& editData)
{
// Construct the material source data object that will be exported
AZ::RPI::MaterialSourceData exportData;
// Converting absolute material paths to relative paths
bool result = false;
AZ::Data::AssetInfo info;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
editData.m_materialTypeSourcePath.c_str(), info, watchFolder);
if (!result)
if (path.empty() || !editData.m_materialAsset.IsReady() || !editData.m_materialTypeAsset.IsReady() ||
editData.m_materialTypeSourcePath.empty())
{
AZ_Error(
"AZ::Render::EditorMaterialComponentUtil", false,
"Failed to get material type source file info while attempting to export: %s", path.c_str());
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Can not export: %s", path.c_str());
return false;
}
exportData.m_materialType = info.m_relativePath;
if (!editData.m_materialParentSourcePath.empty())
{
result = false;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
editData.m_materialParentSourcePath.c_str(), info, watchFolder);
if (!result)
{
AZ_Error(
"AZ::Render::EditorMaterialComponentUtil", false,
"Failed to get parent material source file info while attempting to export: %s", path.c_str());
return false;
}
exportData.m_parentMaterial = info.m_relativePath;
}
// Construct the material source data object that will be exported
AZ::RPI::MaterialSourceData exportData;
exportData.m_materialTypeVersion = editData.m_materialTypeAsset->GetVersion();
exportData.m_materialType = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialTypeSourcePath);
exportData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(path, editData.m_materialParentSourcePath);
// Copy all of the properties from the material asset to the source data that will be exported
result = true;
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition) {
bool result = true;
editData.m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& groupName, const AZStd::string& propertyName, const auto& propertyDefinition){
const AZ::RPI::MaterialPropertyId propertyId(groupName, propertyName);
const AZ::RPI::MaterialPropertyIndex propertyIndex =
editData.m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyId.GetFullName());
AZ::RPI::MaterialPropertyValue propertyValue = editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
AZ::RPI::MaterialPropertyValue propertyValue =
editData.m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()];
AZ::RPI::MaterialPropertyValue propertyValueDefault = propertyDefinition.m_value;
if (editData.m_materialParentAsset.IsReady())
@@ -151,12 +129,12 @@ namespace AZ
// Check for and apply any property overrides before saving property values
auto propertyOverrideItr = editData.m_materialPropertyOverrideMap.find(propertyId.GetFullName());
if(propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
if (propertyOverrideItr != editData.m_materialPropertyOverrideMap.end())
{
propertyValue = AZ::RPI::MaterialPropertyValue::FromAny(propertyOverrideItr->second);
}
if (!editData.m_materialTypeSourceData.ConvertPropertyValueToSourceDataFormat(propertyDefinition, propertyValue))
if (!AtomToolsFramework::ConvertToExportFormat(path, propertyDefinition, propertyValue))
{
AZ_Error("AZ::Render::EditorMaterialComponentUtil", false, "Failed to export: %s", path.c_str());
result = false;
@@ -9,8 +9,8 @@
#include "SlideAlongAxisBasedOnAngle.h"
#include "StartingPointCamera/StartingPointCameraUtilities.h"
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Serialization/EditContext.h>
namespace Camera
{
@@ -32,31 +32,43 @@ namespace Camera
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<SlideAlongAxisBasedOnAngle>("SlideAlongAxisBasedOnAngle", "Slide 0..SlideDistance along Axis based on Angle Type. Maps from 90..-90 degrees")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_axisToSlideAlong, "Axis to slide along", "The Axis to slide along")
->EnumAttribute(RelativeAxisType::ForwardBackward, "Forwards and Backwards")
->EnumAttribute(RelativeAxisType::LeftRight, "Right and Left")
->EnumAttribute(RelativeAxisType::UpDown, "Up and Down")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_angleTypeToChangeFor, "Angle Type", "The angle type to base the slide off of")
->EnumAttribute(EulerAngleType::Pitch, "Pitch")
->EnumAttribute(EulerAngleType::Roll, "Roll")
->EnumAttribute(EulerAngleType::Yaw, "Yaw")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumPositiveSlideDistance, "Max Positive Slide Distance", "The maximum distance to slide in the positive")
->Attribute(AZ::Edit::Attributes::Suffix, "m")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumNegativeSlideDistance, "Max Negative Slide Distance", "The maximum distance to slide in the negative")
->Attribute(AZ::Edit::Attributes::Suffix, "m")
->ClassElement(AZ::Edit::ClassElements::Group, "Vector Components To Ignore")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreX, "X", "When active, the X Component will be ignored.")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreY, "Y", "When active, the Y Component will be ignored.")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreZ, "Z", "When active, the Z Component will be ignored.")
editContext->Class<SlideAlongAxisBasedOnAngle>("SlideAlongAxisBasedOnAngle",
"Slide 0..SlideDistance along Axis based on Angle Type. Maps from 90..-90 degrees")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_axisToSlideAlong, "Axis to slide along",
"The Axis to slide along")
->EnumAttribute(RelativeAxisType::ForwardBackward, "Forwards and Backwards")
->EnumAttribute(RelativeAxisType::LeftRight, "Right and Left")
->EnumAttribute(RelativeAxisType::UpDown, "Up and Down")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &SlideAlongAxisBasedOnAngle::m_angleTypeToChangeFor, "Angle Type",
"The angle type to base the slide off of")
->EnumAttribute(EulerAngleType::Pitch, "Pitch")
->EnumAttribute(EulerAngleType::Roll, "Roll")
->EnumAttribute(EulerAngleType::Yaw, "Yaw")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumPositiveSlideDistance, "Max Positive Slide Distance",
"The maximum distance to slide in the positive")
->Attribute(AZ::Edit::Attributes::Suffix, "m")
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_maximumNegativeSlideDistance, "Max Negative Slide Distance",
"The maximum distance to slide in the negative")
->Attribute(AZ::Edit::Attributes::Suffix, "m")
->ClassElement(AZ::Edit::ClassElements::Group, "Vector Components To Ignore")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreX, "X", "When active, the X Component will be ignored.")
->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::YAndZIgnored)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreY, "Y", "When active, the Y Component will be ignored.")
->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::XAndZIgnored)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(0, &SlideAlongAxisBasedOnAngle::m_ignoreZ, "Z", "When active, the Z Component will be ignored.")
->Attribute(AZ::Edit::Attributes::ReadOnly, &SlideAlongAxisBasedOnAngle::XAndYIgnored)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
;
}
}
}
void SlideAlongAxisBasedOnAngle::AdjustLookAtTarget([[maybe_unused]] float deltaTime, [[maybe_unused]] const AZ::Transform& targetTransform, AZ::Transform& outLookAtTargetTransform)
void SlideAlongAxisBasedOnAngle::AdjustLookAtTarget(
[[maybe_unused]] float deltaTime, [[maybe_unused]] const AZ::Transform& targetTransform, AZ::Transform& outLookAtTargetTransform)
{
float angle = GetEulerAngleFromTransform(outLookAtTargetTransform, m_angleTypeToChangeFor);
float currentPositionOnRange = -angle / AZ::Constants::HalfPi;
@@ -67,4 +79,20 @@ namespace Camera
outLookAtTargetTransform.SetTranslation(outLookAtTargetTransform.GetTranslation() + basis * currentPositionOnRange * slideScale);
}
}
bool SlideAlongAxisBasedOnAngle::XAndYIgnored() const
{
return m_ignoreX && m_ignoreY;
}
bool SlideAlongAxisBasedOnAngle::XAndZIgnored() const
{
return m_ignoreX && m_ignoreZ;
}
bool SlideAlongAxisBasedOnAngle::YAndZIgnored() const
{
return m_ignoreY && m_ignoreZ;
}
} // namespace Camera
@@ -6,11 +6,11 @@
*
*/
#pragma once
#include <CameraFramework/ICameraLookAtBehavior.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/RTTI/ReflectContext.h>
#include "StartingPointCamera/StartingPointCameraConstants.h"
#include <AzCore/Math/Transform.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <CameraFramework/ICameraLookAtBehavior.h>
namespace Camera
{
@@ -38,6 +38,10 @@ namespace Camera
void Activate(AZ::EntityId) override {}
void Deactivate() override {}
bool XAndYIgnored() const;
bool XAndZIgnored() const;
bool YAndZIgnored() const;
private:
//////////////////////////////////////////////////////////////////////////
// Reflected data