Merge pull request #5675 from aws-lumberyard-dev/amzn-tommy/gitflow_211116_o3de2
Merge stabilization/2110 as of c3dacb1 to development
This commit is contained in:
+54
@@ -14,6 +14,7 @@ from datetime import datetime
|
||||
import ly_test_tools.log.log_monitor
|
||||
|
||||
from AWS.common import constants
|
||||
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
|
||||
from .aws_metrics_custom_thread import AWSMetricsThread
|
||||
|
||||
# fixture imports
|
||||
@@ -200,6 +201,59 @@ class TestAWSMetricsWindows(object):
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/Metrics'])
|
||||
def test_realtime_and_batch_analytics_no_global_accountid(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_metrics_utils: pytest.fixture):
|
||||
"""
|
||||
Verify that the metrics events are sent to CloudWatch and S3 for analytics.
|
||||
"""
|
||||
# Remove top-level account ID from resource mappings
|
||||
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
|
||||
# Start Kinesis analytics application on a separate thread to avoid blocking the test.
|
||||
kinesis_analytics_application_thread = AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, True))
|
||||
kinesis_analytics_application_thread.start()
|
||||
|
||||
log_monitor = setup(launcher, asset_processor)
|
||||
|
||||
# Kinesis analytics application needs to be in the running state before we start the game launcher.
|
||||
kinesis_analytics_application_thread.join()
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
start_time = datetime.utcnow()
|
||||
with launcher.start(launch_ap=False):
|
||||
monitor_metrics_submission(log_monitor)
|
||||
|
||||
# Verify that real-time analytics metrics are delivered to CloudWatch.
|
||||
aws_metrics_utils.verify_cloud_watch_delivery(
|
||||
AWS_METRICS_FEATURE_NAME,
|
||||
'TotalLogins',
|
||||
[],
|
||||
start_time)
|
||||
logger.info('Real-time metrics are sent to CloudWatch.')
|
||||
|
||||
# Run time-consuming operations on separate threads to avoid blocking the test.
|
||||
operational_threads = list()
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=query_metrics_from_s3,
|
||||
args=(aws_metrics_utils, resource_mappings)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=verify_operational_metrics,
|
||||
args=(aws_metrics_utils, resource_mappings, start_time)))
|
||||
operational_threads.append(
|
||||
AWSMetricsThread(target=update_kinesis_analytics_application_status,
|
||||
args=(aws_metrics_utils, resource_mappings, False)))
|
||||
for thread in operational_threads:
|
||||
thread.start()
|
||||
for thread in operational_threads:
|
||||
thread.join()
|
||||
|
||||
@pytest.mark.parametrize('level', ['AWS/Metrics'])
|
||||
def test_unauthorized_user_request_rejected(self,
|
||||
level: str,
|
||||
|
||||
@@ -18,6 +18,7 @@ import ly_test_tools.environment.process_utils as process_utils
|
||||
import ly_test_tools.o3de.asset_processor_utils as asset_processor_utils
|
||||
|
||||
from AWS.common import constants
|
||||
from AWS.common.resource_mappings import AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY
|
||||
|
||||
# fixture imports
|
||||
from assetpipeline.ap_fixtures.asset_processor_fixture import asset_processor
|
||||
@@ -141,3 +142,51 @@ class TestAWSCoreAWSResourceInteraction(object):
|
||||
'The expected file wasn\'t successfully downloaded.'
|
||||
# clean up the file directories.
|
||||
shutil.rmtree(s3_download_dir)
|
||||
|
||||
@pytest.mark.parametrize('expected_lines', [
|
||||
['(Script) - [S3] Head object request is done',
|
||||
'(Script) - [S3] Head object success: Object example.txt is found.',
|
||||
'(Script) - [S3] Get object success: Object example.txt is downloaded.',
|
||||
'(Script) - [Lambda] Completed Invoke',
|
||||
'(Script) - [Lambda] Invoke success: {"statusCode": 200, "body": {}}',
|
||||
'(Script) - [DynamoDB] Results finished']])
|
||||
@pytest.mark.parametrize('unexpected_lines', [
|
||||
['(Script) - [S3] Head object error: No response body.',
|
||||
'(Script) - [S3] Get object error: Request validation failed, output file directory doesn\'t exist.',
|
||||
'(Script) - Request validation failed, output file miss full path.',
|
||||
'(Script) - ']])
|
||||
def test_scripting_behavior_no_global_accountid(self,
|
||||
level: str,
|
||||
launcher: pytest.fixture,
|
||||
workspace: pytest.fixture,
|
||||
asset_processor: pytest.fixture,
|
||||
resource_mappings: pytest.fixture,
|
||||
aws_utils: pytest.fixture,
|
||||
expected_lines: typing.List[str],
|
||||
unexpected_lines: typing.List[str]):
|
||||
"""
|
||||
Setup: Updates resource mapping file using existing CloudFormation stacks.
|
||||
Tests: Interact with AWS S3, DynamoDB and Lambda services.
|
||||
Verification: Script canvas nodes can communicate with AWS services successfully.
|
||||
"""
|
||||
|
||||
resource_mappings.clear_select_keys([AWS_RESOURCE_MAPPINGS_ACCOUNT_ID_KEY])
|
||||
log_monitor, s3_download_dir = setup(launcher, asset_processor)
|
||||
write_test_data_to_dynamodb_table(resource_mappings, aws_utils)
|
||||
|
||||
launcher.args = ['+LoadLevel', level]
|
||||
launcher.args.extend(['-rhi=null'])
|
||||
|
||||
with launcher.start(launch_ap=False):
|
||||
result = log_monitor.monitor_log_for_lines(
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True
|
||||
)
|
||||
|
||||
assert result, "Expected lines weren't found."
|
||||
|
||||
assert os.path.exists(os.path.join(s3_download_dir, 'output.txt')), \
|
||||
'The expected file wasn\'t successfully downloaded.'
|
||||
# clean up the file directories.
|
||||
shutil.rmtree(s3_download_dir)
|
||||
|
||||
@@ -102,3 +102,17 @@ class ResourceMappings:
|
||||
|
||||
def get_resource_name_id(self, resource_key: str):
|
||||
return self._resource_mappings[AWS_RESOURCE_MAPPINGS_KEY][resource_key]['Name/ID']
|
||||
|
||||
def clear_select_keys(self, resource_keys=None) -> None:
|
||||
"""
|
||||
Clears values from select resource mapping keys.
|
||||
:param resource_keys: list of keys to clear out
|
||||
"""
|
||||
with open(self._resource_mapping_file_path) as file_content:
|
||||
resource_mappings = json.load(file_content)
|
||||
|
||||
for key in resource_keys:
|
||||
resource_mappings[key] = ''
|
||||
|
||||
with open(self._resource_mapping_file_path, 'w') as file_content:
|
||||
json.dump(resource_mappings, file_content, indent=4)
|
||||
+1
-1
@@ -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={os.path.join(workspace.paths.engine_root(), workspace.project)}")
|
||||
cmd.append(f"--project-path={workspace.paths.project()}")
|
||||
return cmd
|
||||
|
||||
# ******
|
||||
|
||||
+55
-222
@@ -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
|
||||
|
||||
@@ -199,41 +199,6 @@ namespace AzAssetBrowserRequestHandlerPrivate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper utility - determines if the thing being dragged is a FBX from the scene import pipeline
|
||||
// This is important to differentiate.
|
||||
// when someone drags a MTL file directly into the viewport, even from a FBX, we want to spawn it as a decal
|
||||
// but when someone drags a FBX that contains MTL files, we want only to spawn the meshes.
|
||||
// so we have to specifically differentiate here between the mimeData type that contains the source as the root
|
||||
// (dragging the fbx file itself)
|
||||
// and one which contains the actual product at its root.
|
||||
|
||||
bool IsDragOfFBX(const QMimeData* mimeData)
|
||||
{
|
||||
AZStd::vector<AssetBrowserEntry*> entries;
|
||||
if (!AssetBrowserEntry::FromMimeData(mimeData, entries))
|
||||
{
|
||||
// if mimedata does not even contain entries, no point in proceeding.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto entry : entries)
|
||||
{
|
||||
if (entry->GetEntryType() != AssetBrowserEntry::AssetEntryType::Source)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// this is a source file. Is it the filetype we're looking for?
|
||||
if (SourceAssetBrowserEntry* source = azrtti_cast<SourceAssetBrowserEntry*>(entry))
|
||||
{
|
||||
if (AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AzAssetBrowserRequestHandler::AzAssetBrowserRequestHandler()
|
||||
|
||||
+10
-4
@@ -370,10 +370,8 @@ void CCryEditApp::RegisterActionHandlers()
|
||||
ON_COMMAND(ID_EDIT_FETCH, OnEditFetch)
|
||||
ON_COMMAND(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, OnFileExportToGameNoSurfaceTexture)
|
||||
ON_COMMAND(ID_VIEW_SWITCHTOGAME, OnViewSwitchToGame)
|
||||
MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_VIEW_SWITCHTOGAME_FULLSCREEN, [this]() {
|
||||
ed_previewGameInFullscreen_once = true;
|
||||
OnViewSwitchToGame();
|
||||
});
|
||||
ON_COMMAND(ID_VIEW_SWITCHTOGAME_VIEWPORT, OnViewSwitchToGame)
|
||||
ON_COMMAND(ID_VIEW_SWITCHTOGAME_FULLSCREEN, OnViewSwitchToGameFullScreen)
|
||||
ON_COMMAND(ID_MOVE_OBJECT, OnMoveObject)
|
||||
ON_COMMAND(ID_RENAME_OBJ, OnRenameObj)
|
||||
ON_COMMAND(ID_UNDO, OnUndo)
|
||||
@@ -2573,6 +2571,12 @@ void CCryEditApp::OnViewSwitchToGame()
|
||||
GetIEditor()->SetInGameMode(inGame);
|
||||
}
|
||||
|
||||
void CCryEditApp::OnViewSwitchToGameFullScreen()
|
||||
{
|
||||
ed_previewGameInFullscreen_once = true;
|
||||
OnViewSwitchToGame();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CCryEditApp::OnExportSelectedObjects()
|
||||
{
|
||||
@@ -4184,6 +4188,8 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
|
||||
"\nThis could be because of incorrectly configured components, or missing required gems."
|
||||
"\nSee other errors for more details.");
|
||||
|
||||
AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEvents::NotifyEditorInitialized);
|
||||
|
||||
if (didCryEditStart)
|
||||
{
|
||||
app->EnableOnIdle();
|
||||
|
||||
@@ -212,6 +212,7 @@ public:
|
||||
void OnEditFetch();
|
||||
void OnFileExportToGameNoSurfaceTexture();
|
||||
void OnViewSwitchToGame();
|
||||
void OnViewSwitchToGameFullScreen();
|
||||
void OnViewDeploy();
|
||||
void DeleteSelectedEntities(bool includeDescendants);
|
||||
void OnMoveObject();
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
#define CRYINCLUDE_EDITOR_MATERIAL_IEDITORMATERIALMANAGER_H
|
||||
#pragma once
|
||||
|
||||
#define MATERIAL_FILE_EXT ".mtl"
|
||||
#define DCC_MATERIAL_FILE_EXT ".dccmtl"
|
||||
#define MATERIALS_PATH "materials/"
|
||||
|
||||
#include <Include/IBaseLibraryManager.h>
|
||||
#include <IMaterial.h>
|
||||
|
||||
|
||||
@@ -939,27 +939,27 @@ void MainWindow::InitActions()
|
||||
.Connect(&QAction::triggered, this, &MainWindow::OnRefreshAudioSystem);
|
||||
|
||||
// Game actions
|
||||
am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play &Game"))
|
||||
am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play Game"))
|
||||
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Play.svg"))
|
||||
.SetToolTip(tr("Play Game"))
|
||||
.SetStatusTip(tr("Activate the game input mode"))
|
||||
.SetCheckable(true)
|
||||
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
|
||||
am->AddAction(ID_VIEW_SWITCHTOGAME_VIEWPORT, tr("Play Game"))
|
||||
.SetShortcut(tr("Ctrl+G"))
|
||||
.SetToolTip(tr("Play Game (Ctrl+G)"))
|
||||
.SetStatusTip(tr("Activate the game input mode"))
|
||||
.SetApplyHoverEffect()
|
||||
.SetCheckable(true)
|
||||
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
|
||||
am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play &Game (Maximized)"))
|
||||
am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play Game (Maximized)"))
|
||||
.SetShortcut(tr("Ctrl+Shift+G"))
|
||||
.SetStatusTip(tr("Activate the game input mode (maximized)"))
|
||||
.SetIcon(Style::icon("Play"))
|
||||
.SetApplyHoverEffect()
|
||||
.SetCheckable(true);
|
||||
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
|
||||
am->AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, tr("Play Controls"))
|
||||
.SetText(tr("Play Controls"));
|
||||
am->AddAction(ID_SWITCH_PHYSICS, tr("Simulate"))
|
||||
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Simulate_Physics.svg"))
|
||||
.SetShortcut(tr("Ctrl+P"))
|
||||
.SetToolTip(tr("Simulate (Ctrl+P)"))
|
||||
.SetCheckable(true)
|
||||
.SetStatusTip(tr("Enable processing of Physics and AI."))
|
||||
.SetApplyHoverEffect()
|
||||
.SetCheckable(true)
|
||||
@@ -1266,7 +1266,9 @@ void MainWindow::OnGameModeChanged(bool inGameMode)
|
||||
// block signals on the switch to game actions before setting the checked state, as
|
||||
// setting the checked state triggers the action, which will re-enter this function
|
||||
// and result in an infinite loop
|
||||
AZStd::vector<QAction*> actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME), m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN) };
|
||||
AZStd::vector<QAction*> actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT),
|
||||
m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN),
|
||||
m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME)};
|
||||
for (auto action : actions)
|
||||
{
|
||||
action->blockSignals(true);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#include <LmbrCentral/Rendering/LensFlareAsset.h>
|
||||
#include <LmbrCentral/Rendering/MeshAsset.h>
|
||||
#include <LmbrCentral/Rendering/MaterialAsset.h>
|
||||
|
||||
#include <AzCore/Memory/Memory.h>
|
||||
#include <AzCore/RTTI/TypeInfo.h>
|
||||
@@ -136,14 +135,6 @@ AssetCatalogModel::AssetCatalogModel(QObject* parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Special cases for SimpleAssets. If these get full-fledged AssetData types, these cases can be removed.
|
||||
QString textureExtensions = LmbrCentral::TextureAsset::GetFileFilter();
|
||||
m_extensionToAssetType.insert(AZStd::make_pair(textureExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::TextureAsset>::Uuid() }));
|
||||
QString materialExtensions = LmbrCentral::MaterialAsset::GetFileFilter();
|
||||
m_extensionToAssetType.insert(AZStd::make_pair(materialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::MaterialAsset>::Uuid() }));
|
||||
QString dccMaterialExtensions = LmbrCentral::DccMaterialAsset::GetFileFilter();
|
||||
m_extensionToAssetType.insert(AZStd::make_pair(dccMaterialExtensions.replace("*", "").replace(" ", "").toStdString().c_str(), AZStd::vector<AZ::Uuid> { AZ::AzTypeInfo<LmbrCentral::DccMaterialAsset>::Uuid() }));
|
||||
|
||||
AZ::SerializeContext* serializeContext = nullptr;
|
||||
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
|
||||
AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
#define ID_FILE_EXPORTTOGAMENOSURFACETEXTURE 33473
|
||||
#define ID_VIEW_SWITCHTOGAME 33477
|
||||
#define ID_VIEW_SWITCHTOGAME_FULLSCREEN 33478
|
||||
#define ID_VIEW_SWITCHTOGAME_VIEWPORT 33479
|
||||
#define ID_MOVE_OBJECT 33481
|
||||
#define ID_RENAME_OBJ 33483
|
||||
#define ID_FETCH 33496
|
||||
|
||||
@@ -590,6 +590,16 @@ AmazonToolbar ToolbarManager::GetObjectToolbar() const
|
||||
return t;
|
||||
}
|
||||
|
||||
QMenu* ToolbarManager::CreatePlayButtonMenu() const
|
||||
{
|
||||
QMenu* playButtonMenu = new QMenu("Play Game");
|
||||
|
||||
playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_VIEWPORT));
|
||||
playButtonMenu->addAction(m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN));
|
||||
|
||||
return playButtonMenu;
|
||||
}
|
||||
|
||||
AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const
|
||||
{
|
||||
AmazonToolbar t = AmazonToolbar("PlayConsole", QObject::tr("Play Controls"));
|
||||
@@ -598,8 +608,17 @@ AmazonToolbar ToolbarManager::GetPlayConsoleToolbar() const
|
||||
t.AddAction(ID_TOOLBAR_WIDGET_SPACER_RIGHT, ORIGINAL_TOOLBAR_VERSION);
|
||||
t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION);
|
||||
t.AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, ORIGINAL_TOOLBAR_VERSION);
|
||||
t.AddAction(ID_VIEW_SWITCHTOGAME, TOOLBARS_WITH_PLAY_GAME);
|
||||
t.AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, TOOLBARS_WITH_PLAY_GAME);
|
||||
|
||||
QAction* playAction = m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME);
|
||||
QToolButton* playButton = new QToolButton(t.Toolbar());
|
||||
|
||||
QMenu* menu = CreatePlayButtonMenu();
|
||||
menu->setParent(t.Toolbar());
|
||||
playAction->setMenu(menu);
|
||||
|
||||
playButton->setDefaultAction(playAction);
|
||||
t.AddWidget(playButton, ID_VIEW_SWITCHTOGAME, ORIGINAL_TOOLBAR_VERSION);
|
||||
|
||||
t.AddAction(ID_TOOLBAR_SEPARATOR, ORIGINAL_TOOLBAR_VERSION);
|
||||
t.AddAction(ID_SWITCH_PHYSICS, TOOLBARS_WITH_PLAY_GAME);
|
||||
return t;
|
||||
@@ -728,7 +747,14 @@ void AmazonToolbar::SetActionsOnInternalToolbar(ActionManager* actionManager)
|
||||
{
|
||||
if (actionManager->HasAction(actionId))
|
||||
{
|
||||
m_toolbar->addAction(actionManager->GetAction(actionId));
|
||||
if (actionData.widget != nullptr)
|
||||
{
|
||||
m_toolbar->addWidget(actionData.widget);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_toolbar->addAction(actionManager->GetAction(actionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1367,7 +1393,12 @@ void AmazonToolbar::InstantiateToolbar(QMainWindow* mainWindow, ToolbarManager*
|
||||
|
||||
void AmazonToolbar::AddAction(int actionId, int toolbarVersionAdded)
|
||||
{
|
||||
m_actions.push_back({ actionId, toolbarVersionAdded });
|
||||
AddWidget(nullptr, actionId, toolbarVersionAdded);
|
||||
}
|
||||
|
||||
void AmazonToolbar::AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded)
|
||||
{
|
||||
m_actions.push_back({ actionId, toolbarVersionAdded, widget });
|
||||
}
|
||||
|
||||
void AmazonToolbar::Clear()
|
||||
|
||||
@@ -87,6 +87,7 @@ public:
|
||||
const QString& GetTranslatedName() const { return m_translatedName; }
|
||||
|
||||
void AddAction(int actionId, int toolbarVersionAdded = 0);
|
||||
void AddWidget(QWidget* widget, int actionId, int toolbarVersionAdded = 0);
|
||||
|
||||
QToolBar* Toolbar() const { return m_toolbar; }
|
||||
|
||||
@@ -117,6 +118,7 @@ private:
|
||||
{
|
||||
int actionId;
|
||||
int toolbarVersionAdded;
|
||||
QWidget* widget;
|
||||
|
||||
bool operator ==(const AmazonToolbar::ActionData& other) const
|
||||
{
|
||||
@@ -133,7 +135,9 @@ private:
|
||||
class AmazonToolBarExpanderWatcher;
|
||||
|
||||
class ToolbarManager
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ToolbarManager(ActionManager* actionManager, MainWindow* mainWindow);
|
||||
~ToolbarManager();
|
||||
@@ -178,6 +182,8 @@ private:
|
||||
void UpdateAllowedAreas(QToolBar* toolbar);
|
||||
bool IsDirty(const AmazonToolbar& toolbar) const;
|
||||
|
||||
QMenu* CreatePlayButtonMenu() const;
|
||||
|
||||
const AmazonToolbar* FindDefaultToolbar(const QString& toolbarName) const;
|
||||
AmazonToolbar* FindToolbar(const QString& toolbarName);
|
||||
|
||||
|
||||
@@ -57,11 +57,12 @@ namespace AZ
|
||||
int numberOfWorkerThreads = m_numberOfWorkerThreads;
|
||||
if (numberOfWorkerThreads <= 0) // spawn default number of threads
|
||||
{
|
||||
#if (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
|
||||
numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS;
|
||||
#else
|
||||
uint32_t scaledHardwareThreads = Threading::CalcNumWorkerThreads(cl_jobThreadsConcurrencyRatio, cl_jobThreadsMinNumber, cl_jobThreadsNumReserved);
|
||||
numberOfWorkerThreads = AZ::GetMin(static_cast<unsigned int>(desc.m_workerThreads.capacity()), scaledHardwareThreads);
|
||||
#if (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
|
||||
numberOfWorkerThreads = AZ::GetMin(numberOfWorkerThreads, AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS);
|
||||
#endif // (AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS)
|
||||
#endif // (AZ_TRAIT_THREAD_NUM_JOB_MANAGER_WORKER_THREADS)
|
||||
}
|
||||
|
||||
threadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
|
||||
|
||||
@@ -32,7 +32,12 @@ namespace AZ
|
||||
|
||||
if (!s_instance)
|
||||
{
|
||||
s_instance = AZ::Environment::CreateVariable<NameDictionary>(NameDictionaryInstanceName);
|
||||
// Because the NameDictionary allocates memory using the AZ::Allocator and it is created
|
||||
// in the executable memory space, it's ownership cannot be transferred to other module memory spaces
|
||||
// Otherwise this could cause the the NameDictionary to be destroyed in static de-init
|
||||
// after the AZ::Allocators have been destroyed
|
||||
// Therefore we supply the isTransferOwnership value of false using CreateVariableEx
|
||||
s_instance = AZ::Environment::CreateVariableEx<NameDictionary>(NameDictionaryInstanceName, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ namespace AZ
|
||||
->HandlesType<AZStd::variant>();
|
||||
jsonContext->Serializer<JsonOptionalSerializer>()
|
||||
->HandlesType<AZStd::optional>();
|
||||
jsonContext->Serializer<JsonBitsetSerializer>()
|
||||
->HandlesType<AZStd::bitset>();
|
||||
|
||||
MathReflect(jsonContext);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace AZ
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonAnySerializer, SystemAllocator, 0);
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonVariantSerializer, SystemAllocator, 0);
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonOptionalSerializer, SystemAllocator, 0);
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonBitsetSerializer, SystemAllocator, 0);
|
||||
|
||||
JsonSerializationResult::Result JsonUnsupportedTypesSerializer::Load(void*, const Uuid&, const rapidjson::Value&,
|
||||
JsonDeserializerContext& context)
|
||||
@@ -49,4 +50,10 @@ namespace AZ
|
||||
return "The Json Serialization doesn't support AZStd::optional by design. No JSON format has yet been found that wasn't deemed too "
|
||||
"complex or overly verbose.";
|
||||
}
|
||||
|
||||
AZStd::string_view JsonBitsetSerializer::GetMessage() const
|
||||
{
|
||||
return "The Json Serialization doesn't support AZStd::bitset by design. No JSON format has yet been found that is content creator "
|
||||
"friendly i.e., easy to comprehend the intent.";
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@@ -65,4 +65,14 @@ namespace AZ
|
||||
protected:
|
||||
AZStd::string_view GetMessage() const override;
|
||||
};
|
||||
|
||||
class JsonBitsetSerializer : public JsonUnsupportedTypesSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonBitsetSerializer, "{10CE969D-D69E-4B3F-8593-069736F8F705}", JsonUnsupportedTypesSerializer);
|
||||
AZ_CLASS_ALLOCATOR_DECL;
|
||||
|
||||
protected:
|
||||
AZStd::string_view GetMessage() const override;
|
||||
};
|
||||
} // namespace AZ
|
||||
|
||||
@@ -30,8 +30,13 @@ namespace AZ
|
||||
|
||||
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
|
||||
{
|
||||
#if (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
|
||||
const uint32_t numberOfWorkerThreads = AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS;
|
||||
#else
|
||||
const uint32_t numberOfWorkerThreads = Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved);
|
||||
#endif // (AZ_TRAIT_THREAD_NUM_TASK_GRAPH_WORKER_THREADS)
|
||||
Interface<TaskGraphActiveInterface>::Register(this); // small window that another thread can try to use taskgraph between this line and the set instance.
|
||||
m_taskExecutor = aznew TaskExecutor(Threading::CalcNumWorkerThreads(cl_taskGraphThreadsConcurrencyRatio, cl_taskGraphThreadsMinNumber, cl_taskGraphThreadsNumReserved));
|
||||
m_taskExecutor = aznew TaskExecutor(numberOfWorkerThreads);
|
||||
TaskExecutor::SetInstance(m_taskExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 0
|
||||
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
|
||||
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
|
||||
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
|
||||
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
|
||||
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
|
||||
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
|
||||
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
|
||||
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
|
||||
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
|
||||
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
|
||||
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
|
||||
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
|
||||
|
||||
@@ -75,7 +75,6 @@
|
||||
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
|
||||
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
|
||||
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 1
|
||||
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
|
||||
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 1
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
|
||||
|
||||
@@ -76,7 +76,6 @@
|
||||
#define AZ_TRAIT_HEAPSCHEMA_COMPILE_MALLINFO 1
|
||||
#define AZ_TRAIT_IS_ABS_PATH_IF_COLON_FOUND_ANYWHERE 0
|
||||
#define AZ_TRAIT_JSON_CLANG_IGNORE_UNKNOWN_WARNING 0
|
||||
#define AZ_TRAIT_MAX_JOB_MANAGER_WORKER_THREADS 0
|
||||
#define AZ_TRAIT_PERF_MEMORYBENCHMARK_IS_AVAILABLE 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING 0
|
||||
#define AZ_TRAIT_PUMP_SYSTEM_EVENTS_WHILE_LOADING_INTERVAL_MS 0
|
||||
|
||||
@@ -1241,6 +1241,10 @@ namespace AZ::IO
|
||||
|
||||
m_arrZips.insert(revItZip.base(), desc);
|
||||
|
||||
// This lock is for m_arrZips.
|
||||
// Unlock it now because the modification is complete, and events responding to this signal
|
||||
// will attempt to lock the same mutex, causing the application to lock up.
|
||||
lock.unlock();
|
||||
m_levelOpenEvent.Signal(levelDirs);
|
||||
}
|
||||
|
||||
|
||||
@@ -927,6 +927,9 @@ namespace AzToolsFramework
|
||||
/// Notify that the MainWindow has been fully initialized
|
||||
virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {}
|
||||
|
||||
/// Notify that the Editor has been fully initialized
|
||||
virtual void NotifyEditorInitialized() {}
|
||||
|
||||
/// Signal that an asset should be highlighted / selected
|
||||
virtual void SelectAsset(const QString& /* assetPath */) {}
|
||||
};
|
||||
|
||||
@@ -215,12 +215,17 @@ namespace AzToolsFramework
|
||||
, public AZ::BehaviorEBusHandler
|
||||
{
|
||||
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
|
||||
NotifyRegisterViews);
|
||||
NotifyRegisterViews, NotifyEditorInitialized);
|
||||
|
||||
void NotifyRegisterViews() override
|
||||
{
|
||||
Call(FN_NotifyRegisterViews);
|
||||
}
|
||||
|
||||
void NotifyEditorInitialized() override
|
||||
{
|
||||
Call(FN_NotifyEditorInitialized);
|
||||
}
|
||||
};
|
||||
|
||||
} // Internal
|
||||
@@ -445,6 +450,7 @@ namespace AzToolsFramework
|
||||
->Attribute(AZ::Script::Attributes::Module, "editor")
|
||||
->Handler<Internal::EditorEventsBusHandler>()
|
||||
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
|
||||
->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized)
|
||||
;
|
||||
|
||||
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
|
||||
|
||||
-5
@@ -234,11 +234,6 @@ namespace AzToolsFramework
|
||||
return SourceFileDetails("Icons/AssetBrowser/Lua_16.svg");
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Equal(extension.c_str(), ".mtl"))
|
||||
{
|
||||
return SourceFileDetails("Icons/AssetBrowser/Material_16.svg");
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str()))
|
||||
{
|
||||
return SourceFileDetails("Icons/AssetBrowser/Slice_16.svg");
|
||||
|
||||
+5
-2
@@ -31,7 +31,10 @@ AZ_POP_DISABLE_WARNING
|
||||
AZ_CVAR(
|
||||
bool, ed_hideAssetPickerPathColumn, true, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"Hide AssetPicker path column for a clearer view.");
|
||||
AZ_CVAR_EXTERNED(bool, ed_useNewAssetBrowserTableView);
|
||||
|
||||
AZ_CVAR(
|
||||
bool, ed_useNewAssetPickerView, false, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"Uses the new Asset Picker View.");
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
@@ -106,7 +109,7 @@ namespace AzToolsFramework
|
||||
m_persistentState = AZ::UserSettings::CreateFind<AzToolsFramework::QWidgetSavedState>(AZ::Crc32(("AssetBrowserTreeView_Dialog_" + name).toUtf8().data()), AZ::UserSettings::CT_GLOBAL);
|
||||
|
||||
m_ui->m_assetBrowserTableViewWidget->setVisible(false);
|
||||
if (ed_useNewAssetBrowserTableView)
|
||||
if (ed_useNewAssetPickerView)
|
||||
{
|
||||
m_ui->m_assetBrowserTreeViewWidget->setVisible(false);
|
||||
m_ui->m_assetBrowserTableViewWidget->setVisible(true);
|
||||
|
||||
+2
@@ -597,11 +597,13 @@ namespace AzToolsFramework
|
||||
pte.SetVisibleEnforcement(true);
|
||||
}
|
||||
|
||||
ScopedUndoBatch undo("Modify Entity Property");
|
||||
PropertyOutcome result = pte.SetProperty(propertyPath, value);
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
PropertyEditorEntityChangeNotificationBus::Event(componentInstance.GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, componentInstance.GetComponentId());
|
||||
}
|
||||
undo.MarkEntityDirty(componentInstance.GetEntityId());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+3
@@ -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()
|
||||
|
||||
+1
-18
@@ -4705,13 +4705,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (mimeData->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType()))
|
||||
{
|
||||
// extra special case: MTLs from FBX drags are ignored. are we dragging a FBX file?
|
||||
bool isDraggingFBXFile = false;
|
||||
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::SourceAssetBrowserEntry>(mimeData, [&](const AssetBrowser::SourceAssetBrowserEntry* source)
|
||||
{
|
||||
isDraggingFBXFile = isDraggingFBXFile || AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false);
|
||||
});
|
||||
|
||||
// the usual case - we only allow asset browser drops of assets that have actually been associated with a kind of component.
|
||||
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::ProductAssetBrowserEntry>(mimeData, [&](const AssetBrowser::ProductAssetBrowserEntry* product)
|
||||
{
|
||||
@@ -4723,17 +4716,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (canCreateComponent && !componentTypeId.IsNull())
|
||||
{
|
||||
// we have a component type that handles this asset.
|
||||
// but we disallow it if its a MTL file from a FBX and the FBX itself is being dragged. Its still allowed
|
||||
// to drag the actual MTL.
|
||||
EBusFindAssetTypeByName materialAssetTypeResult("Material");
|
||||
AZ::AssetTypeInfoBus::BroadcastResult(materialAssetTypeResult, &AZ::AssetTypeInfo::GetAssetType);
|
||||
AZ::Data::AssetType materialAssetType = materialAssetTypeResult.GetAssetType();
|
||||
|
||||
if ((!isDraggingFBXFile) || (product->GetAssetType() != materialAssetType))
|
||||
{
|
||||
callbackFunction(product);
|
||||
}
|
||||
callbackFunction(product);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -690,7 +690,6 @@ namespace AssetBuilderSDK
|
||||
static const char* textureExtensions = ".dds";
|
||||
static const char* staticMeshExtensions = ".cgf";
|
||||
static const char* skinnedMeshExtensions = ".skin";
|
||||
static const char* materialExtensions = ".mtl";
|
||||
|
||||
// MIPS
|
||||
static const int c_MaxMipsCount = 11; // 11 is for 8k textures non-compressed. When not compressed it is using one file per mip.
|
||||
@@ -805,11 +804,6 @@ namespace AssetBuilderSDK
|
||||
return textureAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(materialExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return materialAssetType;
|
||||
}
|
||||
|
||||
if (AzFramework::StringFunc::Find(staticMeshExtensions, extension.c_str()) != AZStd::string::npos)
|
||||
{
|
||||
return meshAssetType;
|
||||
|
||||
@@ -23,25 +23,11 @@ namespace O3DE::ProjectManager
|
||||
QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles";
|
||||
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
|
||||
|
||||
// On Linux the default compiler is gcc. For O3DE, it is clang, so we need to specify the version of clang that is detected
|
||||
// in order to get the compiler option.
|
||||
auto compilerOptionResult = ProjectUtils::FindSupportedCompilerForPlatform();
|
||||
if (!compilerOptionResult.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(compilerOptionResult.GetError());
|
||||
}
|
||||
auto clangCompilers = compilerOptionResult.GetValue().split('|');
|
||||
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
|
||||
|
||||
QString clangCompilerOption = clangCompilers[0];
|
||||
QString clangPPCompilerOption = clangCompilers[1];
|
||||
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
|
||||
QStringList generateProjectArgs = QStringList{ProjectCMakeCommand,
|
||||
"-B", ProjectBuildPathPostfix,
|
||||
"-S", ".",
|
||||
QString("-G%1").arg(cmakeGenerator),
|
||||
QString("-DCMAKE_C_COMPILER=").append(clangCompilerOption),
|
||||
QString("-DCMAKE_CXX_COMPILER=").append(clangPPCompilerOption),
|
||||
QString("-DLY_3RDPARTY_PATH=").append(thirdPartyPath)};
|
||||
if (!compileProfileOnBuild)
|
||||
{
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
|
||||
|
||||
@@ -17,13 +17,11 @@ namespace O3DE::ProjectManager
|
||||
namespace ProjectUtils
|
||||
{
|
||||
// The list of clang C/C++ compiler command lines to validate on the host Linux system
|
||||
const QStringList SupportedClangCommands = {"clang-12|clang++-12"};
|
||||
const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"};
|
||||
|
||||
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment()
|
||||
{
|
||||
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
currentEnvironment.insert("CC", "clang-12");
|
||||
currentEnvironment.insert("CXX", "clang++-12");
|
||||
return AZ::Success(currentEnvironment);
|
||||
}
|
||||
|
||||
@@ -39,16 +37,13 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
// Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE.
|
||||
for (const QString& supportClangCommand : SupportedClangCommands)
|
||||
for (const QString& supportClangVersion : SupportedClangVersions)
|
||||
{
|
||||
auto clangCompilers = supportClangCommand.split('|');
|
||||
AZ_Assert(clangCompilers.length()==2, "Invalid clang compiler pair specification");
|
||||
|
||||
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[0]}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{clangCompilers[1]}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
|
||||
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment());
|
||||
if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess())
|
||||
{
|
||||
return AZ::Success(supportClangCommand);
|
||||
return AZ::Success(QString("clang-%1").arg(supportClangVersion));
|
||||
}
|
||||
}
|
||||
return AZ::Failure(QObject::tr("Clang not found. <br><br>"
|
||||
@@ -101,5 +96,10 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return AZ::Utils::GetExecutableDirectory();
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR false
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT false
|
||||
|
||||
@@ -137,5 +137,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return editorPath;
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut([[maybe_unused]] const QString& filename, [[maybe_unused]] const QString& targetPath, [[maybe_unused]] const QStringList& arguments)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Creating desktop shortcuts functionality not implemented for this platform yet."));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
#pragma once
|
||||
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CUSTOM_TITLEBAR true
|
||||
#define AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT true
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
@@ -146,5 +147,26 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
return AZ::Utils::GetExecutableDirectory();
|
||||
}
|
||||
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments)
|
||||
{
|
||||
const QString cmd{"powershell.exe"};
|
||||
const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename);
|
||||
const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();")
|
||||
.arg(shortcutPath)
|
||||
.arg(targetPath)
|
||||
.arg(arguments.join(' '));
|
||||
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment());
|
||||
if (!createShortcutResult.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
|
||||
"Please verify you have permission to create files at the specified location.<br><br> %2")
|
||||
.arg(shortcutPath)
|
||||
.arg(createShortcutResult.GetError()));
|
||||
}
|
||||
|
||||
return AZ::Success(QObject::tr("Desktop shortcut created at<br><a href=\"%1\">%2</a>").arg(desktopPath).arg(shortcutPath));
|
||||
}
|
||||
} // namespace ProjectUtils
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -41,5 +41,6 @@
|
||||
<file>Download.svg</file>
|
||||
<file>in_progress.gif</file>
|
||||
<file>gem.svg</file>
|
||||
<file>checkmark.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -563,6 +563,52 @@ QProgressBar::chunk {
|
||||
margin-top:5px;
|
||||
}
|
||||
|
||||
#gemCatalogUpdateGemButton,
|
||||
#gemCatalogUninstallGemButton
|
||||
{
|
||||
qproperty-flat: true;
|
||||
min-height:24px;
|
||||
max-height:24px;
|
||||
border-radius: 3px;
|
||||
text-align:center;
|
||||
font-size:12px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
#gemCatalogUpdateGemButton {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #888888, stop: 1.0 #555555);
|
||||
}
|
||||
#gemCatalogUpdateGemButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #999999, stop: 1.0 #666666);
|
||||
}
|
||||
#gemCatalogUpdateGemButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #555555, stop: 1.0 #777777);
|
||||
}
|
||||
|
||||
#footer > #gemCatalogUninstallGemButton,
|
||||
#gemCatalogUninstallGemButton {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #E32C27, stop: 1.0 #951D21);
|
||||
}
|
||||
#footer > #gemCatalogUninstallGemButton:hover,
|
||||
#gemCatalogUninstallGemButton:hover {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #FD3129, stop: 1.0 #AF2221);
|
||||
}
|
||||
#footer > #gemCatalogUninstallGemButton:pressed,
|
||||
#gemCatalogUninstallGemButton:pressed {
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
|
||||
stop: 0 #951D1F, stop: 1.0 #C92724);
|
||||
}
|
||||
|
||||
#gemCatalogDialogSubTitle {
|
||||
font-size:14px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
/************** Filter Tag widget **************/
|
||||
|
||||
#FilterTagWidgetTextLabel {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="14px" viewBox="0 0 15 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icons / Hub / Download Copy 5</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Screen-1-Copy-80" transform="translate(-425.000000, -251.000000)">
|
||||
<g id="Icons-/-Hub-/-Download-Copy-5" transform="translate(424.573941, 250.098705)">
|
||||
<rect id="Icon-Background" x="0" y="0" width="16" height="16"></rect>
|
||||
<path d="M8,1.33333333 C11.6818983,1.33333333 14.6666667,4.31810167 14.6666667,8 C14.6666667,11.6818983 11.6818983,14.6666667 8,14.6666667 C4.31810167,14.6666667 1.33333333,11.6818983 1.33333333,8 C1.33333333,4.31810167 4.31810167,1.33333333 8,1.33333333 Z M12.0947571,4 L5.96649831,10.1282588 L3.60947571,7.77123617 L2.66666667,8.71404521 L5.96649831,12.0138769 L13.0375661,4.94280904 L12.0947571,4 Z" id="Combined-Shape" fill="#58BC61"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -18,7 +18,6 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
DownloadController::DownloadController(QWidget* parent)
|
||||
: QObject()
|
||||
, m_lastProgress(0)
|
||||
, m_parent(parent)
|
||||
{
|
||||
m_worker = new DownloadWorker();
|
||||
@@ -69,10 +68,9 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadController::UpdateUIProgress(int progress)
|
||||
void DownloadController::UpdateUIProgress(int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
m_lastProgress = progress;
|
||||
emit GemDownloadProgress(m_gemNames.front(), progress);
|
||||
emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes);
|
||||
}
|
||||
|
||||
void DownloadController::HandleResults(const QString& result)
|
||||
@@ -88,6 +86,7 @@ namespace O3DE::ProjectManager
|
||||
QString gemName = m_gemNames.front();
|
||||
m_gemNames.erase(m_gemNames.begin());
|
||||
emit Done(gemName, succeeded);
|
||||
emit GemDownloadRemoved(gemName);
|
||||
|
||||
if (!m_gemNames.empty())
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
public slots:
|
||||
void UpdateUIProgress(int progress);
|
||||
void UpdateUIProgress(int bytesDownloaded, int totalBytes);
|
||||
void HandleResults(const QString& result);
|
||||
|
||||
signals:
|
||||
@@ -61,14 +61,12 @@ namespace O3DE::ProjectManager
|
||||
void Done(const QString& gemName, bool success = true);
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemDownloadProgress(const QString& gemName, int percentage);
|
||||
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
|
||||
|
||||
private:
|
||||
DownloadWorker* m_worker;
|
||||
QThread m_workerThread;
|
||||
QWidget* m_parent;
|
||||
AZStd::vector<QString> m_gemNames;
|
||||
|
||||
int m_lastProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -20,12 +20,13 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void DownloadWorker::StartDownload()
|
||||
{
|
||||
auto gemDownloadProgress = [=](int downloadProgress)
|
||||
auto gemDownloadProgress = [=](int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
m_downloadProgress = downloadProgress;
|
||||
emit UpdateProgress(downloadProgress);
|
||||
emit UpdateProgress(bytesDownloaded, totalBytes);
|
||||
};
|
||||
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
|
||||
AZ::Outcome<void, AZStd::string> gemInfoResult =
|
||||
PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, /*force*/true);
|
||||
|
||||
if (gemInfoResult.IsSuccess())
|
||||
{
|
||||
emit Done("");
|
||||
|
||||
@@ -31,12 +31,11 @@ namespace O3DE::ProjectManager
|
||||
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
|
||||
|
||||
signals:
|
||||
void UpdateProgress(int progress);
|
||||
void UpdateProgress(int bytesDownloaded, int totalBytes);
|
||||
void Done(QString result = "");
|
||||
|
||||
private:
|
||||
|
||||
QString m_gemName;
|
||||
int m_downloadProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <QProgressBar>
|
||||
#include <TagWidget.h>
|
||||
#include <QMenu>
|
||||
#include <QLocale>
|
||||
#include <QMovie>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -224,7 +226,6 @@ namespace O3DE::ProjectManager
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress);
|
||||
connect(m_downloadController, &DownloadController::Done, this, &CartOverlayWidget::GemDownloadComplete);
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadAdded(const QString& gemName)
|
||||
@@ -288,29 +289,41 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int percentage)
|
||||
void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
QWidget* gemToUpdate = m_downloadingListWidget->findChild<QWidget*>(gemName);
|
||||
if (gemToUpdate)
|
||||
{
|
||||
QLabel* progressLabel = gemToUpdate->findChild<QLabel*>("DownloadProgressLabel");
|
||||
if (progressLabel)
|
||||
{
|
||||
progressLabel->setText(QString("%1%").arg(percentage));
|
||||
}
|
||||
QProgressBar* progressBar = gemToUpdate->findChild<QProgressBar*>("DownloadProgressBar");
|
||||
if (progressBar)
|
||||
|
||||
// totalBytes can be 0 if the server does not return a content-length for the object
|
||||
if (totalBytes != 0)
|
||||
{
|
||||
progressBar->setValue(percentage);
|
||||
int downloadPercentage = static_cast<int>((bytesDownloaded / static_cast<float>(totalBytes)) * 100);
|
||||
if (progressLabel)
|
||||
{
|
||||
progressLabel->setText(QString("%1%").arg(downloadPercentage));
|
||||
}
|
||||
if (progressBar)
|
||||
{
|
||||
progressBar->setValue(downloadPercentage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (progressLabel)
|
||||
{
|
||||
progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded));
|
||||
}
|
||||
if (progressBar)
|
||||
{
|
||||
progressBar->setRange(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CartOverlayWidget::GemDownloadComplete(const QString& gemName, bool /*success*/)
|
||||
{
|
||||
GemDownloadRemoved(gemName); // update the list to remove the gem that has finished
|
||||
}
|
||||
|
||||
QVector<Tag> CartOverlayWidget::GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
{
|
||||
QVector<Tag> tags;
|
||||
@@ -389,7 +402,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
const QVector<QModelIndex> toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
|
||||
const QVector<QModelIndex> toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
|
||||
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
|
||||
if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -430,6 +443,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QFrame(parent)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setAlignment(Qt::AlignLeft);
|
||||
@@ -456,8 +470,25 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
|
||||
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
|
||||
|
||||
CartButton* cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(cartButton);
|
||||
// spinner
|
||||
m_downloadSpinnerMovie = new QMovie(":/in_progress.gif");
|
||||
m_downloadSpinner = new QLabel(this);
|
||||
m_downloadSpinner->setScaledContents(true);
|
||||
m_downloadSpinner->setMaximumSize(16, 16);
|
||||
m_downloadSpinner->setMovie(m_downloadSpinnerMovie);
|
||||
hLayout->addWidget(m_downloadSpinner);
|
||||
hLayout->addSpacing(8);
|
||||
|
||||
// downloading label
|
||||
m_downloadLabel = new QLabel(tr("Downloading"));
|
||||
hLayout->addWidget(m_downloadLabel);
|
||||
m_downloadSpinner->hide();
|
||||
m_downloadLabel->hide();
|
||||
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
m_cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(m_cartButton);
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
// Separating line
|
||||
@@ -469,6 +500,7 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
QMenu* gemMenu = new QMenu(this);
|
||||
gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); });
|
||||
gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); });
|
||||
gemMenu->addSeparator();
|
||||
gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); });
|
||||
@@ -479,6 +511,27 @@ namespace O3DE::ProjectManager
|
||||
gemMenuButton->setIcon(QIcon(":/menu.svg"));
|
||||
gemMenuButton->setIconSize(QSize(36, 24));
|
||||
hLayout->addWidget(gemMenuButton);
|
||||
|
||||
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
|
||||
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/)
|
||||
{
|
||||
m_downloadSpinner->show();
|
||||
m_downloadLabel->show();
|
||||
m_downloadSpinnerMovie->start();
|
||||
m_cartButton->ShowOverlay();
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/)
|
||||
{
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
m_downloadSpinner->hide();
|
||||
m_downloadLabel->hide();
|
||||
m_downloadSpinnerMovie->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::ReinitForProject()
|
||||
|
||||
@@ -24,6 +24,7 @@ QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QHideEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
|
||||
QT_FORWARD_DECLARE_CLASS(QMovie)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -39,8 +40,7 @@ namespace O3DE::ProjectManager
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
void GemDownloadProgress(const QString& gemName, int percentage);
|
||||
void GemDownloadComplete(const QString& gemName, bool success);
|
||||
void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
|
||||
|
||||
private:
|
||||
QVector<Tag> GetTagsFromModelIndices(const QVector<QModelIndex>& gems) const;
|
||||
@@ -96,12 +96,22 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void ReinitForProject();
|
||||
|
||||
public slots:
|
||||
void GemDownloadAdded(const QString& gemName);
|
||||
void GemDownloadRemoved(const QString& gemName);
|
||||
|
||||
signals:
|
||||
void AddGem();
|
||||
void OpenGemsRepo();
|
||||
void RefreshGems();
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
inline constexpr static int s_height = 60;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
QLabel* m_downloadSpinner = nullptr;
|
||||
QLabel* m_downloadLabel = nullptr;
|
||||
QMovie* m_downloadSpinnerMovie = nullptr;
|
||||
CartButton* m_cartButton = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <GemCatalog/GemRequirementDialog.h>
|
||||
#include <GemCatalog/GemDependenciesDialog.h>
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
#include <GemCatalog/GemUninstallDialog.h>
|
||||
#include <DownloadController.h>
|
||||
#include <ProjectUtils.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -47,6 +51,7 @@ namespace O3DE::ProjectManager
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked);
|
||||
connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult);
|
||||
@@ -60,6 +65,8 @@ namespace O3DE::ProjectManager
|
||||
m_gemInspector->setFixedWidth(240);
|
||||
|
||||
connect(m_gemInspector, &GemInspector::TagClicked, [=](const Tag& tag) { SelectGem(tag.id); });
|
||||
connect(m_gemInspector, &GemInspector::UpdateGem, this, &GemCatalogScreen::UpdateGem);
|
||||
connect(m_gemInspector, &GemInspector::UninstallGem, this, &GemCatalogScreen::UninstallGem);
|
||||
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
@@ -99,7 +106,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
FillModel(projectPath);
|
||||
|
||||
m_proxyModel->ResetFilters();
|
||||
m_proxyModel->ResetFilters(false);
|
||||
m_proxyModel->sort(/*column=*/0);
|
||||
|
||||
if (m_filterWidget)
|
||||
@@ -118,9 +125,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
QModelIndex firstModelIndex = m_gemListView->model()->index(0,0);
|
||||
m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
QModelIndex firstModelIndex = m_gemModel->index(0, 0);
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(firstModelIndex);
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnAddGemClicked()
|
||||
@@ -202,7 +210,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
|
||||
{
|
||||
@@ -228,8 +236,11 @@ namespace O3DE::ProjectManager
|
||||
m_proxyModel->sort(/*column=*/0);
|
||||
|
||||
// temporary, until we can refresh filter counts
|
||||
m_proxyModel->ResetFilters();
|
||||
m_proxyModel->ResetFilters(false);
|
||||
m_filterWidget->ResetAllFilters();
|
||||
|
||||
// Reselect the same selection to proc UI updates
|
||||
m_proxyModel->GetSelectionModel()->setCurrentIndex(m_proxyModel->GetSelectionModel()->currentIndex(), QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies)
|
||||
@@ -253,24 +264,24 @@ namespace O3DE::ProjectManager
|
||||
notification = GemModel::GetDisplayName(modelIndex);
|
||||
if (numChangedDependencies > 0)
|
||||
{
|
||||
notification += " " + tr("and") + " ";
|
||||
notification += tr(" and ");
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (numChangedDependencies == 1 )
|
||||
if (numChangedDependencies == 1)
|
||||
{
|
||||
notification += "1 Gem " + tr("dependency");
|
||||
notification += tr("1 Gem dependency");
|
||||
}
|
||||
else if (numChangedDependencies > 1)
|
||||
{
|
||||
notification += QString("%1 Gem ").arg(numChangedDependencies) + tr("dependencies");
|
||||
notification += tr("%1 Gem %2").arg(QString(numChangedDependencies), tr("dependencies"));
|
||||
}
|
||||
notification += " " + (added ? tr("activated") : tr("deactivated"));
|
||||
notification += (added ? tr(" activated") : tr(" deactivated"));
|
||||
|
||||
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
|
||||
toastConfiguration.m_customIconImage = ":/gem.svg";
|
||||
@@ -290,10 +301,102 @@ 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);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex)
|
||||
{
|
||||
const QString selectedGemName = m_gemModel->GetName(modelIndex);
|
||||
const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex);
|
||||
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
|
||||
const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex);
|
||||
|
||||
// Refresh gem repo
|
||||
if (!selectedGemRepoUri.isEmpty())
|
||||
{
|
||||
AZ::Outcome<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri);
|
||||
if (refreshResult.IsSuccess())
|
||||
{
|
||||
Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Operation failed"),
|
||||
tr("Failed to refresh gem repository %1<br>Error:<br>%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
// If repo uri isn't specified warn user that repo might not be refreshed
|
||||
else
|
||||
{
|
||||
int result = QMessageBox::warning(
|
||||
this, tr("Gem Repository Unspecified"),
|
||||
tr("The repo for %1 is unspecfied. Repository cannot be automatically refreshed. "
|
||||
"Please ensure this gem's repo is refreshed before attempting to update.")
|
||||
.arg(selectedDisplayGemName),
|
||||
QMessageBox::Cancel, QMessageBox::Ok);
|
||||
|
||||
// Allow user to cancel update to manually refresh repo
|
||||
if (result != QMessageBox::Ok)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is an update avaliable now that repo is refreshed
|
||||
bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate);
|
||||
|
||||
GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this);
|
||||
if (confirmUpdateDialog->exec() == QDialog::Accepted)
|
||||
{
|
||||
m_downloadController->AddGemDownload(selectedGemName);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex)
|
||||
{
|
||||
const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex);
|
||||
|
||||
GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this);
|
||||
if (confirmUninstallDialog->exec() == QDialog::Accepted)
|
||||
{
|
||||
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)
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString selectedGemName = m_gemModel->GetName(modelIndex);
|
||||
|
||||
// Remove gem from model
|
||||
m_gemModel->RemoveGem(modelIndex);
|
||||
|
||||
// Delete uninstalled gem directory
|
||||
if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true))
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:<br>%1").arg(selectedGemPath));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::hideEvent(QHideEvent* event)
|
||||
{
|
||||
ScreenWidget::hideEvent(event);
|
||||
@@ -472,7 +575,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
|
||||
@@ -480,19 +584,45 @@ 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_gemModel->setData(index, GemInfo::Downloaded, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (index.isValid())
|
||||
{
|
||||
GemModel::SetDownloadStatus(*m_gemModel, index, GemInfo::DownloadFailed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
|
||||
|
||||
@@ -51,6 +51,8 @@ namespace O3DE::ProjectManager
|
||||
void SelectGem(const QString& gemName);
|
||||
void OnGemDownloadResult(const QString& gemName, bool succeeded = true);
|
||||
void Refresh();
|
||||
void UpdateGem(const QModelIndex& modelIndex);
|
||||
void UninstallGem(const QModelIndex& modelIndex);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
@@ -77,6 +79,6 @@ namespace O3DE::ProjectManager
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
bool m_notificationsEnabled = true;
|
||||
QSet<QString> m_gemsToRegisterWithProject;
|
||||
QString m_projectPath = nullptr;
|
||||
QString m_projectPath;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -57,7 +57,9 @@ namespace O3DE::ProjectManager
|
||||
UnknownDownloadStatus = -1,
|
||||
NotDownloaded,
|
||||
Downloading,
|
||||
Downloaded,
|
||||
DownloadSuccessful,
|
||||
DownloadFailed,
|
||||
Downloaded
|
||||
};
|
||||
static QString GetDownloadStatusString(DownloadStatus status);
|
||||
|
||||
@@ -85,6 +87,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_licenseLink;
|
||||
QString m_directoryLink;
|
||||
QString m_documentationLink;
|
||||
QString m_repoUri;
|
||||
QString m_version = "Unknown Version";
|
||||
QString m_lastUpdatedDate = "Unknown Date";
|
||||
int m_binarySizeInKB = 0;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QSpacerItem>
|
||||
#include <QVBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -70,6 +71,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void GemInspector::Update(const QModelIndex& modelIndex)
|
||||
{
|
||||
m_curModelIndex = modelIndex;
|
||||
|
||||
if (!modelIndex.isValid())
|
||||
{
|
||||
m_mainWidget->hide();
|
||||
@@ -123,6 +126,20 @@ namespace O3DE::ProjectManager
|
||||
const int binarySize = m_model->GetBinarySizeInKB(modelIndex);
|
||||
m_binarySizeLabel->setText(tr("Binary Size: %1").arg(binarySize ? tr("%1 KB").arg(binarySize) : tr("Unknown")));
|
||||
|
||||
// Update and Uninstall buttons
|
||||
if (m_model->GetGemOrigin(modelIndex) == GemInfo::Remote &&
|
||||
(m_model->GetDownloadStatus(modelIndex) == GemInfo::Downloaded ||
|
||||
m_model->GetDownloadStatus(modelIndex) == GemInfo::DownloadSuccessful))
|
||||
{
|
||||
m_updateGemButton->show();
|
||||
m_uninstallGemButton->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_updateGemButton->hide();
|
||||
m_uninstallGemButton->hide();
|
||||
}
|
||||
|
||||
m_mainWidget->adjustSize();
|
||||
m_mainWidget->show();
|
||||
}
|
||||
@@ -223,7 +240,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Depending gems
|
||||
m_dependingGems = new GemsSubWidget();
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const Tag& tag){ emit TagClicked(tag); });
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); });
|
||||
m_mainLayout->addWidget(m_dependingGems);
|
||||
m_mainLayout->addSpacing(20);
|
||||
|
||||
@@ -234,5 +251,20 @@ namespace O3DE::ProjectManager
|
||||
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
|
||||
m_mainLayout->addSpacing(20);
|
||||
|
||||
// Update and Uninstall buttons
|
||||
m_updateGemButton = new QPushButton(tr("Update Gem"));
|
||||
m_updateGemButton->setObjectName("gemCatalogUpdateGemButton");
|
||||
m_mainLayout->addWidget(m_updateGemButton);
|
||||
connect(m_updateGemButton, &QPushButton::clicked, this , [this]{ emit UpdateGem(m_curModelIndex); });
|
||||
|
||||
m_mainLayout->addSpacing(10);
|
||||
|
||||
m_uninstallGemButton = new QPushButton(tr("Uninstall Gem"));
|
||||
m_uninstallGemButton->setObjectName("gemCatalogUninstallGemButton");
|
||||
m_mainLayout->addWidget(m_uninstallGemButton);
|
||||
connect(m_uninstallGemButton, &QPushButton::clicked, this , [this]{ emit UninstallGem(m_curModelIndex); });
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
#include <QItemSelection>
|
||||
#include <QScrollArea>
|
||||
#include <QSpacerItem>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
QT_FORWARD_DECLARE_CLASS(QLabel)
|
||||
QT_FORWARD_DECLARE_CLASS(QSpacerItem)
|
||||
QT_FORWARD_DECLARE_CLASS(QPushButton)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -45,6 +46,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void TagClicked(const Tag& tag);
|
||||
void UpdateGem(const QModelIndex& modelIndex);
|
||||
void UninstallGem(const QModelIndex& modelIndex);
|
||||
|
||||
private slots:
|
||||
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
@@ -55,6 +58,7 @@ namespace O3DE::ProjectManager
|
||||
GemModel* m_model = nullptr;
|
||||
QWidget* m_mainWidget = nullptr;
|
||||
QVBoxLayout* m_mainLayout = nullptr;
|
||||
QModelIndex m_curModelIndex;
|
||||
|
||||
// General info (top) section
|
||||
QLabel* m_nameLabel = nullptr;
|
||||
@@ -77,5 +81,8 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_versionLabel = nullptr;
|
||||
QLabel* m_lastUpdatedLabel = nullptr;
|
||||
QLabel* m_binarySizeLabel = nullptr;
|
||||
|
||||
QPushButton* m_updateGemButton = nullptr;
|
||||
QPushButton* m_uninstallGemButton = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
|
||||
SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
|
||||
SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg");
|
||||
SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg");
|
||||
|
||||
m_downloadingMovie = new QMovie(":/in_progress.gif");
|
||||
}
|
||||
@@ -480,6 +482,14 @@ namespace O3DE::ProjectManager
|
||||
currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
|
||||
statusPixmap = ¤tFrame;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful)
|
||||
{
|
||||
statusPixmap = &m_downloadSuccessfulPixmap;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed)
|
||||
{
|
||||
statusPixmap = &m_downloadFailedPixmap;
|
||||
}
|
||||
else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
|
||||
{
|
||||
statusPixmap = &m_notDownloadedPixmap;
|
||||
|
||||
@@ -97,6 +97,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QPixmap m_unknownStatusPixmap;
|
||||
QPixmap m_notDownloadedPixmap;
|
||||
QPixmap m_downloadSuccessfulPixmap;
|
||||
QPixmap m_downloadFailedPixmap;
|
||||
QMovie* m_downloadingMovie = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -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();
|
||||
@@ -61,11 +61,28 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
|
||||
item->setData(gemInfo.m_licenseText, RoleLicenseText);
|
||||
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
|
||||
item->setData(gemInfo.m_repoUri, RoleRepoUri);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
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()
|
||||
@@ -255,6 +272,11 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RoleLicenseLink).toString();
|
||||
}
|
||||
|
||||
QString GemModel::GetRepoUri(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleRepoUri).toString();
|
||||
}
|
||||
|
||||
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
|
||||
{
|
||||
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
|
||||
@@ -369,11 +391,30 @@ namespace O3DE::ProjectManager
|
||||
|
||||
void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last)
|
||||
{
|
||||
bool selectedRowRemoved = false;
|
||||
for (int i = first; i <= last; ++i)
|
||||
{
|
||||
QModelIndex modelIndex = index(i, 0, parent);
|
||||
const QString& gemName = GetName(modelIndex);
|
||||
m_nameToIndexMap.remove(gemName);
|
||||
|
||||
if (GetSelectionModel()->isRowSelected(i))
|
||||
{
|
||||
selectedRowRemoved = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Select a valid row if currently selected row was removed
|
||||
if (selectedRowRemoved)
|
||||
{
|
||||
for (const QModelIndex& index : m_nameToIndexMap)
|
||||
{
|
||||
if (index.isValid())
|
||||
{
|
||||
GetSelectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,10 +51,13 @@ namespace O3DE::ProjectManager
|
||||
RoleRequirement,
|
||||
RoleDownloadStatus,
|
||||
RoleLicenseText,
|
||||
RoleLicenseLink
|
||||
RoleLicenseLink,
|
||||
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();
|
||||
|
||||
@@ -80,6 +83,7 @@ namespace O3DE::ProjectManager
|
||||
static QString GetRequirement(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseText(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseLink(const QModelIndex& modelIndex);
|
||||
static QString GetRepoUri(const QModelIndex& modelIndex);
|
||||
static GemModel* GetSourceModel(QAbstractItemModel* model);
|
||||
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
|
||||
|
||||
|
||||
@@ -204,9 +204,12 @@ namespace O3DE::ProjectManager
|
||||
emit OnInvalidated();
|
||||
}
|
||||
|
||||
void GemSortFilterProxyModel::ResetFilters()
|
||||
void GemSortFilterProxyModel::ResetFilters(bool clearSearchString)
|
||||
{
|
||||
m_searchString.clear();
|
||||
if (clearSearchString)
|
||||
{
|
||||
m_searchString.clear();
|
||||
}
|
||||
m_gemSelectedFilter = GemSelected::NoFilter;
|
||||
m_gemActiveFilter = GemActive::NoFilter;
|
||||
m_gemOriginFilter = {};
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace O3DE::ProjectManager
|
||||
void SetFeatures(const QSet<QString>& features) { m_featureFilter = features; InvalidateFilter(); }
|
||||
|
||||
void InvalidateFilter();
|
||||
void ResetFilters();
|
||||
void ResetFilters(bool clearSearchString = true);
|
||||
|
||||
signals:
|
||||
void OnInvalidated();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 <GemCatalog/GemUninstallDialog.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QVariant>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemUninstallDialog::GemUninstallDialog(const QString& gemName, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Uninstall Remote Gem"));
|
||||
setObjectName("GemUninstallDialog");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setMargin(30);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
||||
// Body
|
||||
QLabel* subTitleLabel = new QLabel(tr("Are you sure you want to uninstall %1?").arg(gemName));
|
||||
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
|
||||
layout->addWidget(subTitleLabel);
|
||||
|
||||
layout->addSpacing(10);
|
||||
|
||||
QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem's repository. "
|
||||
"You can reinstall this Gem from the Catalog, but its contents may be subject to change."));
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setFixedSize(QSize(440, 80));
|
||||
layout->addWidget(bodyLabel);
|
||||
|
||||
layout->addSpacing(40);
|
||||
|
||||
// Buttons
|
||||
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
|
||||
dialogButtons->setObjectName("footer");
|
||||
layout->addWidget(dialogButtons);
|
||||
|
||||
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
|
||||
cancelButton->setProperty("secondary", true);
|
||||
QPushButton* uninstallButton = dialogButtons->addButton(tr("Uninstall Gem"), QDialogButtonBox::ApplyRole);
|
||||
uninstallButton->setObjectName("gemCatalogUninstallGemButton");
|
||||
|
||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(uninstallButton, &QPushButton::clicked, this, &QDialog::accept);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -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
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QDialog>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class GemUninstallDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
public:
|
||||
explicit GemUninstallDialog(const QString& gemName, QWidget *parent = nullptr);
|
||||
~GemUninstallDialog() = default;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <GemCatalog/GemUpdateDialog.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QVariant>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("Update Remote Gem"));
|
||||
setObjectName("GemUpdateDialog");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setModal(true);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setMargin(30);
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
setLayout(layout);
|
||||
|
||||
// Body
|
||||
QLabel* subTitleLabel = new QLabel(tr("%1 to the latest version of %2?").arg(
|
||||
updateAvaliable ? tr("Update") : tr("Force update"), gemName));
|
||||
subTitleLabel->setObjectName("gemCatalogDialogSubTitle");
|
||||
layout->addWidget(subTitleLabel);
|
||||
|
||||
layout->addSpacing(10);
|
||||
|
||||
QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. "
|
||||
"Updating this Gem will remove any local changes made to this Gem, "
|
||||
"and may remove old features that are in use.").arg(
|
||||
updateAvaliable ? "" : tr("No update detected for Gem. "
|
||||
"This will force a re-download of the gem. ")));
|
||||
bodyLabel->setWordWrap(true);
|
||||
bodyLabel->setFixedSize(QSize(440, 80));
|
||||
layout->addWidget(bodyLabel);
|
||||
|
||||
layout->addSpacing(40);
|
||||
|
||||
// Buttons
|
||||
QDialogButtonBox* dialogButtons = new QDialogButtonBox();
|
||||
dialogButtons->setObjectName("footer");
|
||||
layout->addWidget(dialogButtons);
|
||||
|
||||
QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole);
|
||||
cancelButton->setProperty("secondary", true);
|
||||
QPushButton* updateButton =
|
||||
dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole);
|
||||
|
||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
|
||||
connect(updateButton, &QPushButton::clicked, this, &QDialog::accept);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -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
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QDialog>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class GemUpdateDialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
public :
|
||||
explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr);
|
||||
~GemUpdateDialog() = default;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
|
||||
#include <ProjectButtonWidget.h>
|
||||
#include <ProjectManagerDefs.h>
|
||||
#include <ProjectUtils.h>
|
||||
#include <ProjectManager_Traits_Platform.h>
|
||||
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -23,6 +27,7 @@
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -205,6 +210,29 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
AzQtComponents::ShowFileOnDesktop(m_projectInfo.m_path);
|
||||
});
|
||||
|
||||
#if AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
|
||||
menu->addAction(tr("Create Editor desktop shortcut..."), this, [this]()
|
||||
{
|
||||
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
|
||||
AZStd::string executableFilename = "Editor";
|
||||
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
|
||||
|
||||
const QString shortcutName = QString("%1 Editor").arg(m_projectInfo.m_displayName);
|
||||
const QString arg = QString("--regset=\"/Amazon/AzCore/Bootstrap/project_path=%1\"").arg(m_projectInfo.m_path);
|
||||
|
||||
auto result = ProjectUtils::CreateDesktopShortcut(shortcutName, editorExecutablePath.c_str(), { arg });
|
||||
if(result.IsSuccess())
|
||||
{
|
||||
QMessageBox::information(this, tr("Desktop Shortcut Created"), result.GetValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Failed to create shortcut"), result.GetError());
|
||||
}
|
||||
});
|
||||
#endif // AZ_TRAIT_PROJECT_MANAGER_CREATE_DESKTOP_SHORTCUT
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addAction(tr("Duplicate"), this, [this]() { emit CopyProject(m_projectInfo); });
|
||||
menu->addSeparator();
|
||||
|
||||
@@ -628,11 +628,11 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' timed out at %2 seconds").arg(cmd).arg(commandTimeoutSeconds));
|
||||
}
|
||||
int resultCode = execProcess.exitCode();
|
||||
QString resultOutput = execProcess.readAllStandardOutput();
|
||||
if (resultCode != 0)
|
||||
{
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2").arg(cmd).arg(resultCode));
|
||||
return AZ::Failure(QObject::tr("Process for command '%1' failed (result code %2) %3").arg(cmd).arg(resultCode).arg(resultOutput));
|
||||
}
|
||||
QString resultOutput = execProcess.readAllStandardOutput();
|
||||
return AZ::Success(resultOutput);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,15 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
|
||||
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
|
||||
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
|
||||
|
||||
/**
|
||||
* Create a desktop shortcut.
|
||||
* @param filename the name of the desktop shorcut file
|
||||
* @param target the path to the target to run
|
||||
* @param arguments the argument list to provide to the target
|
||||
* @return AZ::Outcome with the command result on success
|
||||
*/
|
||||
AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments);
|
||||
|
||||
AZ::IO::FixedMaxPath GetEditorDirectory();
|
||||
|
||||
|
||||
@@ -515,7 +515,11 @@ namespace O3DE::ProjectManager
|
||||
auto pyProjectPath = QString_To_Py_Path(projectPath);
|
||||
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
|
||||
{
|
||||
gems.push_back(GemInfoFromPath(path, pyProjectPath));
|
||||
GemInfo gemInfo = GemInfoFromPath(path, pyProjectPath);
|
||||
// Mark as downloaded because this gem was registered with an existing directory
|
||||
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
|
||||
|
||||
gems.push_back(AZStd::move(gemInfo));
|
||||
}
|
||||
});
|
||||
if (!result.IsSuccess())
|
||||
@@ -560,7 +564,7 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemNames));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::GemRegistration(const QString& gemPath, const QString& projectPath, bool remove)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
@@ -582,7 +586,8 @@ namespace O3DE::ProjectManager
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // default_third_party_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
externalProjectPath // external_subdir_project_path
|
||||
externalProjectPath, // external_subdir_project_path
|
||||
remove // remove
|
||||
);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
@@ -595,12 +600,23 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else if (!registrationResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>(AZStd::string::format("Failed to register gem path %s", gemPath.toUtf8().constData()));
|
||||
return AZ::Failure<AZStd::string>(AZStd::string::format(
|
||||
"Failed to %s gem path %s", remove ? "unregister" : "register", gemPath.toUtf8().constData()));
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return GemRegistration(gemPath, projectPath);
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::UnregisterGem(const QString& gemPath, const QString& projectPath)
|
||||
{
|
||||
return GemRegistration(gemPath, projectPath, /*remove*/true);
|
||||
}
|
||||
|
||||
bool PythonBindings::AddProject(const QString& path)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
@@ -715,6 +731,7 @@ namespace O3DE::ProjectManager
|
||||
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
|
||||
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
|
||||
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
|
||||
gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", "");
|
||||
|
||||
if (gemInfo.m_creator.contains("Open 3D Engine"))
|
||||
{
|
||||
@@ -728,6 +745,11 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
|
||||
}
|
||||
// If no origin was provided this cannot be remote and would be specified if O3DE so it should be local
|
||||
else
|
||||
{
|
||||
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
|
||||
}
|
||||
|
||||
// As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
|
||||
if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
|
||||
@@ -1166,49 +1188,6 @@ namespace O3DE::ProjectManager
|
||||
return AZ::Success(AZStd::move(gemRepos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback)
|
||||
{
|
||||
// This process is currently limited to download a single gem at a time.
|
||||
bool downloadSucceeded = false;
|
||||
|
||||
m_requestCancelDownload = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto downloadResult = m_download.attr("download_gem")(
|
||||
QString_To_Py_String(gemName), // gem name
|
||||
pybind11::none(), // destination path
|
||||
false, // skip auto register
|
||||
false, // force
|
||||
pybind11::cpp_function(
|
||||
[this, gemProgressCallback](int progress)
|
||||
{
|
||||
gemProgressCallback(progress);
|
||||
|
||||
return m_requestCancelDownload;
|
||||
}) // Callback for download progress and cancelling
|
||||
);
|
||||
downloadSucceeded = (downloadResult.cast<int>() == 0);
|
||||
});
|
||||
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to download gem.");
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
void PythonBindings::CancelDownload()
|
||||
{
|
||||
m_requestCancelDownload = true;
|
||||
}
|
||||
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos()
|
||||
{
|
||||
QVector<GemInfo> gemInfos;
|
||||
@@ -1235,4 +1214,64 @@ namespace O3DE::ProjectManager
|
||||
|
||||
return AZ::Success(AZStd::move(gemInfos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force)
|
||||
{
|
||||
// This process is currently limited to download a single gem at a time.
|
||||
bool downloadSucceeded = false;
|
||||
|
||||
m_requestCancelDownload = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto downloadResult = m_download.attr("download_gem")(
|
||||
QString_To_Py_String(gemName), // gem name
|
||||
pybind11::none(), // destination path
|
||||
false, // skip auto register
|
||||
force, // force overwrite
|
||||
pybind11::cpp_function(
|
||||
[this, gemProgressCallback](int bytesDownloaded, int totalBytes)
|
||||
{
|
||||
gemProgressCallback(bytesDownloaded, totalBytes);
|
||||
|
||||
return m_requestCancelDownload;
|
||||
}) // Callback for download progress and cancelling
|
||||
);
|
||||
downloadSucceeded = (downloadResult.cast<int>() == 0);
|
||||
});
|
||||
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to download gem.");
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
void PythonBindings::CancelDownload()
|
||||
{
|
||||
m_requestCancelDownload = true;
|
||||
}
|
||||
|
||||
bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated)
|
||||
{
|
||||
bool updateAvaliableResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
auto pyGemName = QString_To_Py_String(gemName);
|
||||
auto pyLastUpdated = QString_To_Py_String(lastUpdated);
|
||||
auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated);
|
||||
|
||||
updateAvaliableResult = pythonUpdateAvaliableResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && updateAvaliableResult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) override;
|
||||
AZ::Outcome<QVector<AZStd::string>, AZStd::string> GetEnabledGemNames(const QString& projectPath) override;
|
||||
AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) override;
|
||||
AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) override;
|
||||
|
||||
// Project
|
||||
AZ::Outcome<ProjectInfo> CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override;
|
||||
@@ -64,9 +65,10 @@ namespace O3DE::ProjectManager
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
|
||||
void CancelDownload() override;
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() override;
|
||||
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) override;
|
||||
void CancelDownload() override;
|
||||
bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
@@ -77,6 +79,7 @@ namespace O3DE::ProjectManager
|
||||
GemRepoInfo GetGemRepoInfo(pybind11::handle repoUri);
|
||||
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
|
||||
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
|
||||
AZ::Outcome<void, AZStd::string> GemRegistration(const QString& gemPath, const QString& projectPath, bool remove = false);
|
||||
bool RegisterThisEngine();
|
||||
bool StopPython();
|
||||
|
||||
|
||||
@@ -94,11 +94,19 @@ namespace O3DE::ProjectManager
|
||||
/**
|
||||
* Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given
|
||||
* @param gemPath the path to the gem
|
||||
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
|
||||
* @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
|
||||
|
||||
/**
|
||||
* Unregisters the gem from the specified project, or from the o3de_manifest.json if no project path is given
|
||||
* @param gemPath the path to the gem
|
||||
* @param projectPath the path to the project. If empty, will unregister the external path in o3de_manifest.json
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> UnregisterGem(const QString& gemPath, const QString& projectPath = {}) = 0;
|
||||
|
||||
|
||||
// Projects
|
||||
|
||||
@@ -209,24 +217,34 @@ namespace O3DE::ProjectManager
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
|
||||
|
||||
/**
|
||||
* Downloads and registers a Gem.
|
||||
* @param gemName the name of the Gem to download
|
||||
* @param gemProgressCallback a callback function that is called with an int percentage download value
|
||||
* @return an outcome with a string error message on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) = 0;
|
||||
|
||||
/**
|
||||
* Cancels the current download.
|
||||
*/
|
||||
virtual void CancelDownload() = 0;
|
||||
|
||||
/**
|
||||
* Gathers all gem infos for all gems registered from repos.
|
||||
* @return A list of gem infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemRepoGemsInfos() = 0;
|
||||
|
||||
/**
|
||||
* Downloads and registers a Gem.
|
||||
* @param gemName the name of the Gem to download.
|
||||
* @param gemProgressCallback a callback function that is called with an int percentage download value.
|
||||
* @param force should we forcibly overwrite the old version of the gem.
|
||||
* @return an outcome with a string error message on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> DownloadGem(
|
||||
const QString& gemName, std::function<void(int, int)> gemProgressCallback, bool force = false) = 0;
|
||||
|
||||
/**
|
||||
* Cancels the current download.
|
||||
*/
|
||||
virtual void CancelDownload() = 0;
|
||||
|
||||
/**
|
||||
* Checks if there is an update avaliable for a gem on a repo.
|
||||
* @param gemName the name of the gem to check.
|
||||
* @param lastUpdated last time the gem was update.
|
||||
* @return true if update is avaliable, false if not.
|
||||
*/
|
||||
virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -96,6 +96,10 @@ set(FILES
|
||||
Source/GemCatalog/GemListHeaderWidget.cpp
|
||||
Source/GemCatalog/GemModel.h
|
||||
Source/GemCatalog/GemModel.cpp
|
||||
Source/GemCatalog/GemUninstallDialog.h
|
||||
Source/GemCatalog/GemUninstallDialog.cpp
|
||||
Source/GemCatalog/GemUpdateDialog.h
|
||||
Source/GemCatalog/GemUpdateDialog.cpp
|
||||
Source/GemCatalog/GemDependenciesDialog.h
|
||||
Source/GemCatalog/GemDependenciesDialog.cpp
|
||||
Source/GemCatalog/GemRequirementDialog.h
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace AWSCore
|
||||
},
|
||||
"AccountIdString": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]{12}$|EMPTY"
|
||||
"pattern": "^[0-9]{12}$|EMPTY|^$"
|
||||
},
|
||||
"NonEmptyString": {
|
||||
"type": "string",
|
||||
|
||||
@@ -59,6 +59,34 @@ R"({
|
||||
"Version": "1.0.0"
|
||||
})";
|
||||
|
||||
static constexpr const char TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE[] =
|
||||
R"({
|
||||
"AWSResourceMappings": {
|
||||
"TestLambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Name/ID": "MyTestLambda",
|
||||
"Region": "us-east-1",
|
||||
"AccountId": "012345678912"
|
||||
},
|
||||
"TestS3Bucket": {
|
||||
"Type": "AWS::S3::Bucket",
|
||||
"Name/ID": "MyTestS3Bucket"
|
||||
},
|
||||
"TestService.RESTApiId": {
|
||||
"Type": "AWS::ApiGateway::RestApi",
|
||||
"Name/ID": "1234567890"
|
||||
},
|
||||
"TestService.RESTApiStage": {
|
||||
"Type": "AWS::ApiGateway::Stage",
|
||||
"Name/ID": "prod",
|
||||
"Region": "us-east-1"
|
||||
}
|
||||
},
|
||||
"AccountId": "",
|
||||
"Region": "us-west-2",
|
||||
"Version": "1.0.0"
|
||||
})";
|
||||
|
||||
static constexpr const char TEST_INVALID_RESOURCE_MAPPING_CONFIG_FILE[] =
|
||||
R"({
|
||||
"AWSResourceMappings": {},
|
||||
@@ -237,6 +265,21 @@ TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_Confi
|
||||
EXPECT_TRUE(actualEbusCalls == testThreadNumber);
|
||||
}
|
||||
|
||||
TEST_F(AWSResourceMappingManagerTest, ActivateManager_ParseValidConfigFile_GlobalAccountIdEmpty)
|
||||
{
|
||||
CreateTestConfigFile(TEST_VALID_EMPTY_ACCOUNTID_RESOURCE_MAPPING_CONFIG_FILE);
|
||||
m_resourceMappingManager->ActivateManager();
|
||||
|
||||
AZStd::string actualAccountId;
|
||||
AZStd::string actualRegion;
|
||||
AWSResourceMappingRequestBus::BroadcastResult(actualAccountId, &AWSResourceMappingRequests::GetDefaultAccountId);
|
||||
AWSResourceMappingRequestBus::BroadcastResult(actualRegion, &AWSResourceMappingRequests::GetDefaultRegion);
|
||||
EXPECT_EQ(m_reloadConfigurationCounter, 0);
|
||||
EXPECT_TRUE(actualAccountId.empty());
|
||||
EXPECT_FALSE(actualRegion.empty());
|
||||
EXPECT_TRUE(m_resourceMappingManager->GetStatus() == AWSResourceMappingManager::Status::Ready);
|
||||
}
|
||||
|
||||
TEST_F(AWSResourceMappingManagerTest, DeactivateManager_AfterActivatingWithValidConfigFile_ConfigDataGetCleanedUp)
|
||||
{
|
||||
CreateTestConfigFile(TEST_VALID_RESOURCE_MAPPING_CONFIG_FILE);
|
||||
|
||||
@@ -69,17 +69,13 @@ class ViewEditController(QObject):
|
||||
json_dict: Dict[str, any] = \
|
||||
json_utils.convert_resources_to_json_dict(self._proxy_model.get_resources(), self._config_file_json_source)
|
||||
|
||||
configuration: Configuration = self._configuration_manager.configuration
|
||||
if json_dict.get(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME) == \
|
||||
json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE:
|
||||
json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = configuration.account_id
|
||||
|
||||
if json_dict == self._config_file_json_source:
|
||||
# skip because no difference found against existing json file
|
||||
return True
|
||||
|
||||
# try to write in memory json content into json file
|
||||
try:
|
||||
configuration: Configuration = self._configuration_manager.configuration
|
||||
config_file_full_path: str = file_utils.join_path(configuration.config_directory, config_file_name)
|
||||
json_utils.write_into_json_file(config_file_full_path, json_dict)
|
||||
self._config_file_json_source = json_dict
|
||||
|
||||
+49
-3
@@ -420,8 +420,30 @@ class TestViewEditController(TestCase):
|
||||
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
|
||||
TestViewEditController._expected_config_file_name
|
||||
expected_json_dict: Dict[str, any] = {
|
||||
"dummyKey": "dummyValue",
|
||||
self._expected_account_id_attribute_name: self._expected_account_id_template_vale}
|
||||
"dummyKey": "dummyValue"
|
||||
}
|
||||
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
|
||||
mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict
|
||||
mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path
|
||||
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
|
||||
|
||||
mocked_call_args[0]() # triggering save_changes_button connected function
|
||||
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
|
||||
mock_json_utils.write_into_json_file.assert_called_once_with(
|
||||
TestViewEditController._expected_config_file_full_path, expected_json_dict)
|
||||
self._mocked_proxy_model.override_all_resources_status.assert_called_once_with(
|
||||
ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE,
|
||||
[ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE]))
|
||||
|
||||
@patch("controller.view_edit_controller.file_utils")
|
||||
@patch("controller.view_edit_controller.json_utils")
|
||||
def test_page_save_changes_button_json_file_saved_and_template_account_id_unchanged(
|
||||
self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None:
|
||||
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
|
||||
TestViewEditController._expected_config_file_name
|
||||
expected_json_dict: Dict[str, any] = {
|
||||
self._expected_account_id_attribute_name: self._expected_account_id_template_vale
|
||||
}
|
||||
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name
|
||||
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE = self._expected_account_id_template_vale
|
||||
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
|
||||
@@ -430,7 +452,31 @@ class TestViewEditController(TestCase):
|
||||
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
|
||||
|
||||
mocked_call_args[0]() # triggering save_changes_button connected function
|
||||
assert expected_json_dict["AccountId"] == self._mocked_configuration_manager.configuration.account_id
|
||||
assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == self._expected_account_id_template_vale
|
||||
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
|
||||
mock_json_utils.write_into_json_file.assert_called_once_with(
|
||||
TestViewEditController._expected_config_file_full_path, expected_json_dict)
|
||||
self._mocked_proxy_model.override_all_resources_status.assert_called_once_with(
|
||||
ResourceMappingAttributesStatus(ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE,
|
||||
[ResourceMappingAttributesStatus.SUCCESS_STATUS_VALUE]))
|
||||
|
||||
@patch("controller.view_edit_controller.file_utils")
|
||||
@patch("controller.view_edit_controller.json_utils")
|
||||
def test_page_save_changes_button_json_file_saved_and_empty_account_id_unchanged(
|
||||
self, mock_json_utils: MagicMock, mock_file_utils: MagicMock) -> None:
|
||||
self._mocked_view_edit_page.config_file_combobox.currentText.return_value = \
|
||||
TestViewEditController._expected_config_file_name
|
||||
expected_json_dict: Dict[str, any] = {
|
||||
self._expected_account_id_attribute_name: ''
|
||||
}
|
||||
mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME = self._expected_account_id_attribute_name
|
||||
mock_json_utils.validate_resources_according_to_json_schema.return_value = []
|
||||
mock_json_utils.convert_resources_to_json_dict.return_value = expected_json_dict
|
||||
mock_file_utils.join_path.return_value = TestViewEditController._expected_config_file_full_path
|
||||
mocked_call_args: call = self._mocked_view_edit_page.save_changes_button.clicked.connect.call_args[0]
|
||||
|
||||
mocked_call_args[0]() # triggering save_changes_button connected function
|
||||
assert expected_json_dict[mock_json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] == ''
|
||||
mock_json_utils.convert_resources_to_json_dict.assert_called_once()
|
||||
mock_json_utils.write_into_json_file.assert_called_once_with(
|
||||
TestViewEditController._expected_config_file_full_path, expected_json_dict)
|
||||
|
||||
@@ -103,6 +103,11 @@ class TestJsonUtils(TestCase):
|
||||
invalid_json_dict.pop(json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME)
|
||||
self.assertRaises(KeyError, json_utils.validate_json_dict_according_to_json_schema, invalid_json_dict)
|
||||
|
||||
def test_validate_json_dict_according_to_json_schema_raise_error_when_json_dict_has_empty_accountid(self) -> None:
|
||||
valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict)
|
||||
valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = ''
|
||||
json_utils.validate_json_dict_according_to_json_schema(valid_json_dict)
|
||||
|
||||
def test_validate_json_dict_according_to_json_schema_pass_when_json_dict_has_template_accountid(self) -> None:
|
||||
valid_json_dict: Dict[str, any] = copy.deepcopy(TestJsonUtils._expected_json_dict)
|
||||
valid_json_dict[json_utils.RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME] = \
|
||||
|
||||
@@ -28,7 +28,7 @@ _RESOURCE_MAPPING_JSON_FORMAT_VERSION: str = "1.0.0"
|
||||
|
||||
RESOURCE_MAPPING_ACCOUNTID_JSON_KEY_NAME: str = "AccountId"
|
||||
RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE: str = "EMPTY"
|
||||
_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}$"
|
||||
_RESOURCE_MAPPING_ACCOUNTID_PATTERN: str = f"^[0-9]{{12}}$|{RESOURCE_MAPPING_ACCOUNTID_TEMPLATE_VALUE}|^$"
|
||||
_RESOURCE_MAPPING_REGION_PATTERN: str = "^[a-z]{2}-[a-z]{4,9}-[0-9]{1}$"
|
||||
_RESOURCE_MAPPING_VERSION_PATTERN: str = "^[0-9]{1}.[0-9]{1}.[0-9]{1}$"
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace AZ
|
||||
// Register Shader Asset Builder
|
||||
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
|
||||
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
|
||||
shaderAssetBuilderDescriptor.m_version = 107; // Required .azsl extension in .shader file references
|
||||
shaderAssetBuilderDescriptor.m_version = 108; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond()
|
||||
// .shader file changes trigger rebuilds
|
||||
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
||||
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
|
||||
@@ -108,7 +108,7 @@ namespace AZ
|
||||
shaderVariantAssetBuilderDescriptor.m_name = "Shader Variant Asset Builder";
|
||||
// Both "Shader Variant Asset Builder" and "Shader Asset Builder" produce ShaderVariantAsset products. If you update
|
||||
// ShaderVariantAsset you will need to update BOTH version numbers, not just "Shader Variant Asset Builder".
|
||||
shaderVariantAssetBuilderDescriptor.m_version = 26; // [AZSL] Changing inlineConstant to rootConstant keyword work.
|
||||
shaderVariantAssetBuilderDescriptor.m_version = 27; // The Build Time Stamp of ShaderAsset And ShaderVariantAsset Should Be Based On GetTimeUTCMilliSecond().
|
||||
shaderVariantAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern(AZStd::string::format("*.%s", RPI::ShaderVariantListSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
|
||||
shaderVariantAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderVariantAssetBuilder>();
|
||||
shaderVariantAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderVariantAssetBuilder::CreateJobs, &m_shaderVariantAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace AZ
|
||||
// has the same value, because later the ShaderVariantTreeAsset job will fetch this value from the local ShaderAsset
|
||||
// which could cross platforms (i.e. building an android ShaderVariantTreeAsset on PC would fetch the tiemstamp from
|
||||
// the PC's ShaderAsset).
|
||||
AZStd::sys_time_t shaderAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
|
||||
AZ::u64 shaderAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond();
|
||||
|
||||
// Need to get the name of the azsl file from the .shader source asset, to be able to declare a dependency to SRG Layout Job.
|
||||
// and the macro options to preprocess.
|
||||
@@ -229,8 +229,8 @@ namespace AZ
|
||||
} // for all request.m_enabledPlatforms
|
||||
|
||||
AZ_TracePrintf(
|
||||
ShaderAssetBuilderName, "CreateJobs for %s took %llu microseconds", shaderAssetSourceFileFullPath.c_str(),
|
||||
AZStd::GetTimeNowMicroSecond() - shaderAssetBuildTimestamp);
|
||||
ShaderAssetBuilderName, "CreateJobs for %s took %llu milliseconds", shaderAssetSourceFileFullPath.c_str(),
|
||||
AZStd::GetTimeUTCMilliSecond() - shaderAssetBuildTimestamp);
|
||||
|
||||
response.m_result = AssetBuilderSDK::CreateJobsResultCode::Success;
|
||||
}
|
||||
@@ -355,8 +355,8 @@ namespace AZ
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the time stamp string as sys_time_t, and also convert back to string to make sure it was converted correctly.
|
||||
AZStd::sys_time_t shaderAssetBuildTimestamp = 0;
|
||||
// Get the time stamp string as u64, and also convert back to string to make sure it was converted correctly.
|
||||
AZ::u64 shaderAssetBuildTimestamp = 0;
|
||||
auto shaderAssetBuildTimestampIterator = request.m_jobDescription.m_jobParameters.find(ShaderAssetBuildTimestampParam);
|
||||
if (shaderAssetBuildTimestampIterator != request.m_jobDescription.m_jobParameters.end())
|
||||
{
|
||||
|
||||
@@ -765,7 +765,7 @@ namespace AZ
|
||||
return;
|
||||
}
|
||||
|
||||
const AZStd::sys_time_t shaderVariantAssetBuildTimestamp = AZStd::GetTimeNowMicroSecond();
|
||||
const AZ::u64 shaderVariantAssetBuildTimestamp = AZStd::GetTimeUTCMilliSecond();
|
||||
|
||||
auto supervariantList = ShaderBuilderUtility::GetSupervariantListFromShaderSourceData(shaderSourceDescriptor);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace AZ
|
||||
const AZStd::string& m_tempDirPath;
|
||||
//! Used to synchronize versions of the ShaderAsset and ShaderVariantAsset,
|
||||
//! especially during hot-reload. A (ShaderVariantAsset.timestamp) >= (ShaderAsset.timestamp).
|
||||
const AZStd::sys_time_t m_assetBuildTimestamp;
|
||||
const AZ::u64 m_assetBuildTimestamp;
|
||||
const RPI::ShaderSourceData& m_shaderSourceDataDescriptor;
|
||||
const RPI::ShaderOptionGroupLayout& m_shaderOptionGroupLayout;
|
||||
const MapOfStringToStageType& m_shaderEntryPoints;
|
||||
|
||||
+2
-2
@@ -166,7 +166,7 @@ float DirectionalLightShadow::GetThickness(uint lightIndex, float3 shadowCoords[
|
||||
bool2 DirectionalLightShadow::IsShadowed(float3 shadowCoord, uint indexOfCascade)
|
||||
{
|
||||
static const float PixelMargin = 1.5; // avoiding artifact between cascade levels.
|
||||
static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds.
|
||||
static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds.
|
||||
|
||||
// size is the shadowap's width and height.
|
||||
const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize;
|
||||
@@ -210,8 +210,8 @@ float DirectionalLightShadow::GetVisibilityFromLightNoFilter()
|
||||
|
||||
float DirectionalLightShadow::GetVisibilityFromLightPcf()
|
||||
{
|
||||
static const float DepthMargin = 0.01; // avoiding artifact when near depth bounds.
|
||||
static const float PixelMargin = 1.5; // avoiding artifact between cascade levels.
|
||||
static const float DepthMargin = 1e-8; // avoiding artifact when near depth bounds.
|
||||
|
||||
const uint size = ViewSrg::m_directionalLightShadows[m_lightIndex].m_shadowmapSize;
|
||||
const uint cascadeCount = ViewSrg::m_directionalLightShadows[m_lightIndex].m_cascadeCount;
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user