Merge branch 'main' into Atom/dmcdiar/ATOM-15517
commit
ef05412063
File diff suppressed because it is too large
Load Diff
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Atom/Features/SrgSemantics.azsli>
|
||||
|
||||
ShaderResourceGroup PassSrg : SRG_PerPass
|
||||
{
|
||||
// [GFX TODO][ATOM-2012] adapt to multiple shadowmaps
|
||||
Texture2DArray<float> m_directionalLightShadowmap;
|
||||
Texture2DArray<float> m_directionalLightExponentialShadowmap;
|
||||
Texture2DArray<float> m_projectedShadowmaps;
|
||||
Texture2DArray<float> m_projectedExponentialShadowmap;
|
||||
Texture2D m_brdfMap;
|
||||
|
||||
Sampler LinearSampler
|
||||
{
|
||||
MinFilter = Linear;
|
||||
MagFilter = Linear;
|
||||
MipFilter = Linear;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
AddressW = Clamp;
|
||||
};
|
||||
|
||||
Texture2D<uint4> m_tileLightData;
|
||||
StructuredBuffer<uint> m_lightListRemapped;
|
||||
Texture2D<float> m_linearDepthTexture;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
* its licensors.
|
||||
*
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this
|
||||
* distribution (the "License"). All use of this software is governed by the License,
|
||||
* or, if provided, by the license below or the license accompanying this file. Do not
|
||||
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace LyIntegration
|
||||
{
|
||||
namespace Thumbnails
|
||||
{
|
||||
//! ThumbnailFeatureProcessorProviderRequests allows registering custom Feature Processors for thumbnail generation
|
||||
//! Duplicates will be ignored
|
||||
//! You can check minimal feature processors that are already registered in CommonThumbnailRenderer.cpp
|
||||
class ThumbnailFeatureProcessorProviderRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//! Get a list of custom feature processors to register with thumbnail renderer
|
||||
virtual const AZStd::vector<AZStd::string>& GetCustomFeatureProcessors() const = 0;
|
||||
};
|
||||
|
||||
using ThumbnailFeatureProcessorProviderBus = AZ::EBus<ThumbnailFeatureProcessorProviderRequests>;
|
||||
} // namespace Thumbnails
|
||||
} // namespace LyIntegration
|
||||
} // namespace AZ
|
||||
@ -0,0 +1,104 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from o3de import manifest
|
||||
|
||||
logger = logging.getLogger()
|
||||
logging.basicConfig()
|
||||
|
||||
def get_project_props(name: str = None, path: pathlib.Path = None) -> dict:
|
||||
proj_json = manifest.get_project_json_data(project_name=name, project_path=path)
|
||||
if not proj_json:
|
||||
param = name if name else path
|
||||
logger.error(f'Could not retrieve project.json file for {param}')
|
||||
return None
|
||||
return proj_json
|
||||
|
||||
def edit_project_props(proj_path, proj_name, new_origin, new_display,
|
||||
new_summary, new_icon, new_tag, remove_tag) -> int:
|
||||
proj_json = get_project_props(proj_name, proj_path)
|
||||
|
||||
if not proj_json:
|
||||
return 1
|
||||
|
||||
if new_origin:
|
||||
proj_json['origin'] = new_origin
|
||||
if new_display:
|
||||
proj_json['display_name'] = new_display
|
||||
if new_summary:
|
||||
proj_json['summary'] = new_summary
|
||||
if new_icon:
|
||||
proj_json['icon_path'] = new_icon
|
||||
if new_tag:
|
||||
proj_json.setdefault('user_tags', []).append(new_tag)
|
||||
if remove_tag:
|
||||
if 'user_tags' in proj_json:
|
||||
if remove_tag in proj_json['user_tags']:
|
||||
proj_json['user_tags'].remove(remove_tag)
|
||||
else:
|
||||
logger.warn(f'{remove_tag} not found in user_tags for removal.')
|
||||
else:
|
||||
logger.warn(f'user_tags property not found for removal of tag {remove_tag}.')
|
||||
|
||||
manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json')
|
||||
return 0
|
||||
|
||||
def _edit_project_props(args: argparse) -> int:
|
||||
return edit_project_props(args.project_path,
|
||||
args.project_name,
|
||||
args.project_origin,
|
||||
args.project_display,
|
||||
args.project_summary,
|
||||
args.project_icon,
|
||||
args.project_tag,
|
||||
args.remove_tag)
|
||||
|
||||
def add_parser_args(parser):
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False,
|
||||
help='The path to the project.')
|
||||
group.add_argument('-pn', '--project-name', type=str, required=False,
|
||||
help='The name of the project.')
|
||||
group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.')
|
||||
group.add_argument('-po', '--project-origin', type=str, required=False,
|
||||
help='Sets description or url for project origin (such as project host, repository, owner...etc).')
|
||||
group.add_argument('-pd', '--project-display', type=str, required=False,
|
||||
help='Sets the project display name.')
|
||||
group.add_argument('-ps', '--project-summary', type=str, required=False,
|
||||
help='Sets the summary description of the project.')
|
||||
group.add_argument('-pi', '--project-icon', type=str, required=False,
|
||||
help='Sets the path to the projects icon resource.')
|
||||
group.add_argument('-pt', '--project-tag', type=str, required=False,
|
||||
help='Adds a tag to user_tags property. These tags are intended for documentation and filtering.')
|
||||
group.add_argument('-rt', '--remove-tag', type=str, required=False,
|
||||
help='Removes a tag from the user_tags property.')
|
||||
parser.set_defaults(func=_edit_project_props)
|
||||
|
||||
def add_args(subparsers) -> None:
|
||||
enable_project_props_subparser = subparsers.add_parser('edit-project-properties')
|
||||
add_parser_args(enable_project_props_subparser)
|
||||
|
||||
def main():
|
||||
the_parser = argparse.ArgumentParser()
|
||||
add_parser_args(the_parser)
|
||||
the_args = the_parser.parse_args()
|
||||
ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1
|
||||
sys.exit(ret)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,22 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
ly_download_associated_package(pyside2)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME test_pyside
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME test_projects
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py
|
||||
)
|
||||
@ -1,10 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
@ -1,804 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
# PySide project and gem selector GUI
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import subprocess
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from typing import List
|
||||
from pyside import add_pyside_environment, is_pyside_ready, uninstall_env
|
||||
|
||||
engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
sys.path.append(engine_path)
|
||||
executable_path = ''
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
from o3de import disable_gem, enable_gem, cmake, engine_template, manifest, register
|
||||
|
||||
o3de_folder = manifest.get_o3de_folder()
|
||||
o3de_logs_folder = manifest.get_o3de_logs_folder()
|
||||
project_manager_log_file_path = o3de_logs_folder / "project_manager.log"
|
||||
log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1)
|
||||
formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s')
|
||||
log_file_handler.setFormatter(formatter)
|
||||
logger.addHandler(log_file_handler)
|
||||
|
||||
logger.info("Starting Project Manager")
|
||||
|
||||
|
||||
def initialize_pyside_from_parser():
|
||||
# Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up
|
||||
# PySide
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--executable-path', required=True, help='Path to Executable to launch with project')
|
||||
parser.add_argument('--binaries-path', default=None, help='Path to QT Binaries necessary for PySide. If not'
|
||||
' provided executable_path folder is assumed')
|
||||
parser.add_argument('--parent-pid', default=0, help='Process ID of launching process')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logger.info(f"parent_pid is {args.parent_pid}")
|
||||
global executable_path
|
||||
executable_path = args.executable_path
|
||||
binaries_path = args.binaries_path or os.path.dirname(executable_path)
|
||||
|
||||
# Initialize PySide before imports below. This adds both PySide python modules to the python system interpreter
|
||||
# path and adds the necessary paths to binaries for the DLLs to be found and load their dependencies
|
||||
add_pyside_environment(binaries_path)
|
||||
|
||||
|
||||
if not is_pyside_ready():
|
||||
initialize_pyside_from_parser()
|
||||
|
||||
try:
|
||||
from PySide2.QtWidgets import QApplication, QDialogButtonBox, QPushButton, QComboBox, QMessageBox, QFileDialog
|
||||
from PySide2.QtWidgets import QListView, QLabel
|
||||
from PySide2.QtUiTools import QUiLoader
|
||||
from PySide2.QtCore import QFile, QObject, Qt, Signal, Slot
|
||||
from PySide2.QtGui import QIcon, QStandardItemModel, QStandardItem
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import PySide2 with error {e}")
|
||||
exit(-1)
|
||||
|
||||
logger.error(f"PySide2 imports successful")
|
||||
|
||||
|
||||
class DialogLoggerSignaller(QObject):
|
||||
send_to_dialog = Signal(str)
|
||||
|
||||
def __init__(self, dialog_logger):
|
||||
super(DialogLoggerSignaller, self).__init__()
|
||||
|
||||
self.dialog_logger = dialog_logger
|
||||
|
||||
|
||||
# Independent class to handle log forwarding. Logger and qt signals both use emit method.
|
||||
# This class's job is to receive the logger record and then emit the formatted message through
|
||||
# DialogLoggerSignaller which is what the ProjectDialog handler listens for
|
||||
class DialogLogger(logging.Handler):
|
||||
|
||||
def __init__(self, log_dialog, log_level=logging.INFO, forward_log_level=logging.WARNING,
|
||||
message_box_log_level=logging.ERROR):
|
||||
super(DialogLogger, self).__init__()
|
||||
|
||||
self.log_dialog = log_dialog
|
||||
self.log_level = log_level
|
||||
self.forward_log_level = forward_log_level
|
||||
self.message_box_log_level = message_box_log_level
|
||||
self.log_records = []
|
||||
self.formatter = logging.Formatter('%(levelname)s : %(message)s')
|
||||
self.setFormatter(self.formatter)
|
||||
self.signaller = DialogLoggerSignaller(self)
|
||||
|
||||
def emit(self, record):
|
||||
self.log_records.append(record)
|
||||
if record.levelno >= self.message_box_log_level:
|
||||
QMessageBox.warning(None, record.levelname, record.message)
|
||||
elif record.levelno >= self.forward_log_level:
|
||||
self.signaller.send_to_dialog.emit(self.format(record))
|
||||
|
||||
|
||||
class ProjectManagerDialog(QObject):
|
||||
"""
|
||||
Main project manager dialog is responsible for displaying the project selection list and output pane
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ProjectManagerDialog, self).__init__(parent)
|
||||
|
||||
self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve()
|
||||
self.home_folder = manifest.get_home_folder()
|
||||
|
||||
self.log_display = None
|
||||
self.dialog_logger = DialogLogger(self)
|
||||
logger.addHandler(self.dialog_logger)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message)
|
||||
self.mru_file_path = o3de_folder / 'mru.json'
|
||||
|
||||
self.create_from_template_ui_file_path = self.ui_path / 'create_from_template.ui'
|
||||
self.create_gem_ui_file_path = self.ui_path / 'create_gem.ui'
|
||||
self.create_project_ui_file_path = self.ui_path / 'create_project.ui'
|
||||
self.manage_project_gem_targets_ui_file_path = self.ui_path / 'manage_gem_targets.ui'
|
||||
self.project_manager_icon_file_path = self.ui_path / 'project_manager.ico'
|
||||
self.project_manager_ui_file_path = self.ui_path / 'project_manager.ui'
|
||||
|
||||
self.project_manager_ui_file = QFile(self.project_manager_ui_file_path.as_posix())
|
||||
self.project_manager_ui_file.open(QFile.ReadOnly)
|
||||
|
||||
loader = QUiLoader()
|
||||
self.dialog = loader.load(self.project_manager_ui_file)
|
||||
self.dialog.setWindowIcon(QIcon(self.project_manager_icon_file_path.as_posix()))
|
||||
self.dialog.setFixedSize(self.dialog.size())
|
||||
|
||||
self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox')
|
||||
self.refresh_project_list()
|
||||
mru = self.get_mru_list()
|
||||
if len(mru):
|
||||
last_mru = pathlib.Path(mru[0]).resolve()
|
||||
for this_slot in range(self.project_list_box.count()):
|
||||
item_text = self.project_list_box.itemText(this_slot)
|
||||
if last_mru.as_posix() in item_text:
|
||||
self.project_list_box.setCurrentIndex(this_slot)
|
||||
break
|
||||
|
||||
self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton')
|
||||
self.create_project_button.clicked.connect(self.create_project_handler)
|
||||
self.create_gem_button = self.dialog.findChild(QPushButton, 'createGemButton')
|
||||
self.create_gem_button.clicked.connect(self.create_gem_handler)
|
||||
self.create_template_button = self.dialog.findChild(QPushButton, 'createTemplateButton')
|
||||
self.create_template_button.clicked.connect(self.create_template_handler)
|
||||
self.create_from_template_button = self.dialog.findChild(QPushButton, 'createFromTemplateButton')
|
||||
self.create_from_template_button.clicked.connect(self.create_from_template_handler)
|
||||
|
||||
self.add_project_button = self.dialog.findChild(QPushButton, 'addProjectButton')
|
||||
self.add_project_button.clicked.connect(self.add_project_handler)
|
||||
self.add_gem_button = self.dialog.findChild(QPushButton, 'addGemButton')
|
||||
self.add_gem_button.clicked.connect(self.add_gem_handler)
|
||||
self.add_template_button = self.dialog.findChild(QPushButton, 'addTemplateButton')
|
||||
self.add_template_button.clicked.connect(self.add_template_handler)
|
||||
self.add_restricted_button = self.dialog.findChild(QPushButton, 'addRestrictedButton')
|
||||
self.add_restricted_button.clicked.connect(self.add_restricted_handler)
|
||||
|
||||
self.remove_project_button = self.dialog.findChild(QPushButton, 'removeProjectButton')
|
||||
self.remove_project_button.clicked.connect(self.remove_project_handler)
|
||||
self.remove_gem_button = self.dialog.findChild(QPushButton, 'removeGemButton')
|
||||
self.remove_gem_button.clicked.connect(self.remove_gem_handler)
|
||||
self.remove_template_button = self.dialog.findChild(QPushButton, 'removeTemplateButton')
|
||||
self.remove_template_button.clicked.connect(self.remove_template_handler)
|
||||
self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton')
|
||||
self.remove_restricted_button.clicked.connect(self.remove_restricted_handler)
|
||||
|
||||
self.manage_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton')
|
||||
self.manage_project_gem_targets_button.clicked.connect(self.manage_project_gem_targets_handler)
|
||||
|
||||
self.log_display = self.dialog.findChild(QLabel, 'logDisplay')
|
||||
|
||||
self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.ok_cancel_button.accepted.connect(self.accepted_handler)
|
||||
|
||||
self.dialog.show()
|
||||
|
||||
def refresh_project_list(self) -> None:
|
||||
projects = manifest.get_all_projects()
|
||||
self.project_list_box.clear()
|
||||
for this_slot in range(len(projects)):
|
||||
display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})'
|
||||
self.project_list_box.addItem(display_name)
|
||||
self.project_list_box.setItemData(self.project_list_box.count() - 1, projects[this_slot],
|
||||
Qt.ToolTipRole)
|
||||
|
||||
def accepted_handler(self) -> None:
|
||||
"""
|
||||
Override for handling "Ok" on main project dialog to first check whether the user has selected a project and
|
||||
prompt them to if not. If a project is selected will attempt to open it.
|
||||
:return: None
|
||||
"""
|
||||
if not self.project_list_box.currentText():
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText("Please select a project")
|
||||
msg_box.exec()
|
||||
return
|
||||
self.launch_with_project_path(self.get_selected_project_path())
|
||||
|
||||
def get_launch_project(self) -> str:
|
||||
return os.path.normpath(self.get_selected_project_path())
|
||||
|
||||
def get_executable_launch_params(self) -> list:
|
||||
"""
|
||||
Retrieve the necessary launch parameters to make the subprocess launch call with - this is the path
|
||||
to the executable such as the Editor and the path to the selected project
|
||||
:return: list of params
|
||||
"""
|
||||
launch_params = [executable_path,
|
||||
f'-regset="/Amazon/AzCore/Bootstrap/project_path={self.get_launch_project()}"']
|
||||
return launch_params
|
||||
|
||||
def launch_with_project_path(self, project_path: str) -> None:
|
||||
"""
|
||||
Launch the desired application given the selected project
|
||||
:param project_path: Path to currently selected project
|
||||
:return: None
|
||||
"""
|
||||
logger.info(f'Attempting to open {project_path}')
|
||||
self.update_mru_list(project_path)
|
||||
launch_params = self.get_executable_launch_params()
|
||||
logger.info(f'Launching with params {launch_params}')
|
||||
subprocess.run(launch_params, env=uninstall_env())
|
||||
|
||||
def get_selected_project_path(self) -> str:
|
||||
if self.project_list_box.currentIndex() == -1:
|
||||
logger.warning("No project selected")
|
||||
return ""
|
||||
return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole)
|
||||
|
||||
def get_selected_project_name(self) -> str:
|
||||
project_data = manifest.get_project_json_data(project_path=self.get_selected_project_path())
|
||||
return project_data['project_name']
|
||||
|
||||
def create_project_handler(self):
|
||||
"""
|
||||
Opens the Create Project pane. Retrieves a list of available templates for display
|
||||
:return: None
|
||||
"""
|
||||
loader = QUiLoader()
|
||||
self.create_project_file = QFile(self.create_project_ui_file_path.as_posix())
|
||||
|
||||
if not self.create_project_file:
|
||||
logger.error(f'Failed to create project UI file at {self.create_project_file}')
|
||||
return
|
||||
|
||||
self.create_project_dialog = loader.load(self.create_project_file)
|
||||
|
||||
if not self.create_project_dialog:
|
||||
logger.error(f'Failed to load create project dialog file at {self.create_project_file}')
|
||||
return
|
||||
|
||||
self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler)
|
||||
|
||||
self.create_project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates')
|
||||
self.refresh_create_project_template_list()
|
||||
|
||||
self.create_project_dialog.exec()
|
||||
|
||||
def create_project_accepted_handler(self) -> None:
|
||||
"""
|
||||
Searches the available gems list for selected gems and attempts to add each one to the current project.
|
||||
Updates UI after completion.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
selected_item = self.create_project_template_list.selectionModel().currentIndex()
|
||||
project_template_path = self.create_project_template_list.model().data(selected_item)
|
||||
if not project_template_path:
|
||||
return
|
||||
|
||||
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name",
|
||||
manifest.get_o3de_projects_folder().as_posix())
|
||||
folder_dialog.setFileMode(QFileDialog.AnyFile)
|
||||
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
|
||||
project_count = 0
|
||||
project_name = "MyNewProject"
|
||||
while os.path.exists(os.path.join(engine_path, project_name)):
|
||||
project_name = f"MyNewProject{project_count}"
|
||||
project_count += 1
|
||||
folder_dialog.selectFile(project_name)
|
||||
project_folder = None
|
||||
if folder_dialog.exec():
|
||||
project_folder = folder_dialog.selectedFiles()
|
||||
if project_folder:
|
||||
if engine_template.create_project(project_path=project_folder[0],
|
||||
template_path=project_template_path) == 0:
|
||||
# Success
|
||||
register.register(project_path=project_folder[0])
|
||||
self.refresh_project_list()
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Project {project_folder[0]} created.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def create_gem_handler(self):
|
||||
"""
|
||||
Opens the Create Gem pane. Retrieves a list of available templates for display
|
||||
:return: None
|
||||
"""
|
||||
loader = QUiLoader()
|
||||
self.create_gem_file = QFile(self.create_gem_ui_file_path.as_posix())
|
||||
|
||||
if not self.create_gem_file:
|
||||
logger.error(f'Failed to create gem UI file at {self.create_gem_file}')
|
||||
return
|
||||
|
||||
self.create_gem_dialog = loader.load(self.create_gem_file)
|
||||
|
||||
if not self.create_gem_dialog:
|
||||
logger.error(f'Failed to load create gem dialog file at {self.create_gem_file}')
|
||||
return
|
||||
|
||||
self.create_gem_ok_button = self.create_gem_dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.create_gem_ok_button.accepted.connect(self.create_gem_accepted_handler)
|
||||
|
||||
self.create_gem_template_list = self.create_gem_dialog.findChild(QListView, 'gemTemplates')
|
||||
self.refresh_create_gem_template_list()
|
||||
|
||||
self.create_gem_dialog.exec()
|
||||
|
||||
def create_gem_accepted_handler(self) -> None:
|
||||
"""
|
||||
Searches the available gems list for selected gems and attempts to add each one to the current gem.
|
||||
Updates UI after completion.
|
||||
:return: None
|
||||
"""
|
||||
selected_item = self.create_gem_template_list.selectionModel().currentIndex()
|
||||
gem_template_path = self.create_gem_template_list.model().data(selected_item)
|
||||
if not gem_template_path:
|
||||
return
|
||||
|
||||
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name",
|
||||
manifest.get_o3de_gems_folder().as_posix())
|
||||
folder_dialog.setFileMode(QFileDialog.AnyFile)
|
||||
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
|
||||
gem_count = 0
|
||||
gem_name = "MyNewGem"
|
||||
while os.path.exists(os.path.join(engine_path, gem_name)):
|
||||
gem_name = f"MyNewGem{gem_count}"
|
||||
gem_count += 1
|
||||
folder_dialog.selectFile(gem_name)
|
||||
gem_folder = None
|
||||
if folder_dialog.exec():
|
||||
gem_folder = folder_dialog.selectedFiles()
|
||||
if gem_folder:
|
||||
if engine_template.create_gem(gem_path=gem_folder[0],
|
||||
template_path=gem_template_path) == 0:
|
||||
# Success
|
||||
register.register(gem_path=gem_folder[0])
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Gem {gem_folder[0]} created.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def create_template_handler(self):
|
||||
"""
|
||||
Opens a foldr select dialog and lets the user select the source folder they want to make a template
|
||||
out of, then opens a second folder select dialog to get where they want to put the template and it name
|
||||
:return: None
|
||||
"""
|
||||
|
||||
source_folder = QFileDialog.getExistingDirectory(self.dialog,
|
||||
"Select a Folder to make a template out of.",
|
||||
manifest.get_o3de_folder().as_posix())
|
||||
if not source_folder:
|
||||
return
|
||||
|
||||
destination_template_folder_dialog = QFileDialog(self.dialog,
|
||||
"Select where the template is to be created and named.",
|
||||
manifest.get_o3de_templates_folder().as_posix())
|
||||
destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile)
|
||||
destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
|
||||
destination_folder = None
|
||||
if destination_template_folder_dialog.exec():
|
||||
destination_folder = destination_template_folder_dialog.selectedFiles()
|
||||
if not destination_folder:
|
||||
return
|
||||
|
||||
if engine_template.create_template(source_path=source_folder,
|
||||
template_path=destination_folder[0]) == 0:
|
||||
# Success
|
||||
register.register(template_path=destination_folder[0])
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Template {destination_folder[0]} created.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def create_from_template_handler(self):
|
||||
"""
|
||||
Opens the Create from_template pane. Retrieves a list of available from_templates for display
|
||||
:return: None
|
||||
"""
|
||||
loader = QUiLoader()
|
||||
self.create_from_template_file = QFile(self.create_from_template_ui_file_path.as_posix())
|
||||
|
||||
if not self.create_from_template_file:
|
||||
logger.error(f'Failed to create from_template UI file at {self.create_from_template_file}')
|
||||
return
|
||||
|
||||
self.create_from_template_dialog = loader.load(self.create_from_template_file)
|
||||
|
||||
if not self.create_from_template_dialog:
|
||||
logger.error(f'Failed to load create from_template dialog file at {self.create_from_template_file}')
|
||||
return
|
||||
|
||||
self.create_from_template_ok_button = self.create_from_template_dialog.findChild(QDialogButtonBox, 'okCancel')
|
||||
self.create_from_template_ok_button.accepted.connect(self.create_from_template_accepted_handler)
|
||||
|
||||
self.create_from_template_list = self.create_from_template_dialog.findChild(QListView, 'genericTemplates')
|
||||
self.refresh_create_from_template_list()
|
||||
|
||||
self.create_from_template_dialog.exec()
|
||||
|
||||
def create_from_template_accepted_handler(self) -> None:
|
||||
"""
|
||||
Searches the available gems list for selected gems and attempts to add each one to the current gem.
|
||||
Updates UI after completion.
|
||||
:return: None
|
||||
"""
|
||||
create_gem_item = self.get_selected_gem_template()
|
||||
if not create_gem_item:
|
||||
return
|
||||
|
||||
folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name",
|
||||
manifest.get_o3de_gems_folder().as_posix())
|
||||
folder_dialog.setFileMode(QFileDialog.AnyFile)
|
||||
folder_dialog.setOptions(QFileDialog.ShowDirsOnly)
|
||||
gem_count = 0
|
||||
gem_name = "MyNewGem"
|
||||
while os.path.exists(os.path.join(engine_path, gem_name)):
|
||||
gem_name = f"MyNewGem{gem_count}"
|
||||
gem_count += 1
|
||||
folder_dialog.selectFile(gem_name)
|
||||
gem_folder = None
|
||||
if folder_dialog.exec():
|
||||
gem_folder = folder_dialog.selectedFiles()
|
||||
if gem_folder:
|
||||
if engine_template.create_gem(gem_folder[0], create_gem_item[1]) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"gem {os.path.basename(os.path.normpath(gem_folder[0]))} created."
|
||||
" Build your\nnew gem before hitting OK to launch.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def add_project_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid project. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder",
|
||||
manifest.get_o3de_projects_folder().as_posix())
|
||||
if project_folder:
|
||||
if register.register(project_path=project_folder) == 0:
|
||||
# Success
|
||||
self.refresh_project_list()
|
||||
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Added Project {project_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def add_gem_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a gem. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder",
|
||||
manifest.get_o3de_gems_folder().as_posix())
|
||||
if gem_folder:
|
||||
if register.register(gem_path=gem_folder) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Added Gem {gem_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def add_template_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid template. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder",
|
||||
manifest.get_o3de_templates_folder().as_posix())
|
||||
if template_folder:
|
||||
if register.register(template_path=template_folder) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Added Template {template_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def add_restricted_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid template. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder",
|
||||
manifest.get_o3de_restricted_folder().as_posix())
|
||||
if restricted_folder:
|
||||
if register.register(restricted_path=restricted_folder) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Added Restricted {restricted_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def remove_project_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid project. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder",
|
||||
manifest.get_o3de_projects_folder().as_posix())
|
||||
if project_folder:
|
||||
if register.register(project_path=project_folder, remove=True) == 0:
|
||||
# Success
|
||||
self.refresh_project_list()
|
||||
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Removed Project {project_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def remove_gem_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a gem. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder",
|
||||
manifest.get_o3de_gems_folder().as_posix())
|
||||
if gem_folder:
|
||||
if register.register(gem_path=gem_folder, remove=True) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Removed Gem {gem_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def remove_template_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid template. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder",
|
||||
manifest.get_o3de_templates_folder().as_posix())
|
||||
if template_folder:
|
||||
if register.register(template_path=template_folder, remove=True) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Removed Template {template_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def remove_restricted_handler(self):
|
||||
"""
|
||||
Open a file search dialog looking for a folder which contains a valid template. If valid
|
||||
will update the mru list with the new entry, if invalid will warn the user.
|
||||
:return: None
|
||||
"""
|
||||
restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder",
|
||||
manifest.get_o3de_restricted_folder().as_posix())
|
||||
if restricted_folder:
|
||||
if register.register(restricted_path=restricted_folder, remove=True) == 0:
|
||||
# Success
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText(f"Removed Restricted {restricted_folder}.")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
def manage_project_gem_targets_handler(self):
|
||||
"""
|
||||
Opens the Gem management pane. Waits for the load thread to complete if still running and displays all
|
||||
active gems for the current project as well as all available gems which aren't currently active.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
if not self.get_selected_project_path():
|
||||
msg_box = QMessageBox(parent=self.dialog)
|
||||
msg_box.setWindowTitle("O3DE")
|
||||
msg_box.setText("Please select a project")
|
||||
msg_box.exec()
|
||||
return
|
||||
|
||||
loader = QUiLoader()
|
||||
self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix())
|
||||
|
||||
if not self.manage_project_gem_targets_file:
|
||||
logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}')
|
||||
return
|
||||
|
||||
self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file)
|
||||
|
||||
if not self.manage_project_gem_targets_dialog:
|
||||
logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}')
|
||||
return
|
||||
|
||||
self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Gems for Project:"
|
||||
f" {self.get_selected_project_name()}")
|
||||
|
||||
self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton')
|
||||
self.add_gem_button.clicked.connect(self.add_project_gem_targets_handler)
|
||||
|
||||
self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView,
|
||||
'availableGemTargetsList')
|
||||
self.refresh_project_gem_targets_available_list()
|
||||
|
||||
self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton,
|
||||
'removeGemTargetsButton')
|
||||
self.remove_project_gem_targets_button.clicked.connect(self.remove_project_gem_targets_handler)
|
||||
|
||||
self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView,
|
||||
'enabledGemTargetsList')
|
||||
self.refresh_project_gem_targets_enabled_list()
|
||||
|
||||
self.manage_project_gem_targets_dialog.exec()
|
||||
|
||||
|
||||
def manage_project_gem_targets_get_selected_available_gems(self) -> list:
|
||||
selected_items = self.available_gem_targets_list.selectionModel().selectedRows()
|
||||
return [(self.available_gem_targets_list.model().data(item)) for item in selected_items]
|
||||
|
||||
def manage_project_gem_targets_get_selected_enabled_gems(self) -> list:
|
||||
selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows()
|
||||
return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items]
|
||||
|
||||
def add_project_gem_targets_handler(self) -> None:
|
||||
gem_paths = manifest.get_all_gems()
|
||||
for gem_target in self.manage_project_gem_targets_get_selected_available_gems():
|
||||
for gem_path in gem_paths:
|
||||
enable_gem.enable_gem_in_project(gem_path=gem_path,
|
||||
project_path=self.get_selected_project_path())
|
||||
self.refresh_project_gem_targets_available_list()
|
||||
self.refresh_project_gem_targets_enabled_list()
|
||||
return
|
||||
self.refresh_project_gem_targets_available_list()
|
||||
self.refresh_project_gem_targets_enabled_list()
|
||||
|
||||
def remove_project_gem_targets_handler(self):
|
||||
gem_paths = manifest.get_all_gems()
|
||||
for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems():
|
||||
for gem_path in gem_paths:
|
||||
disable_gem.disable_gem_in_project(gem_path=gem_path,
|
||||
project_path=self.get_selected_project_path())
|
||||
self.refresh_project_gem_targets_available_list()
|
||||
self.refresh_project_gem_targets_enabled_list()
|
||||
return
|
||||
self.refresh_project_gem_targets_available_list()
|
||||
self.refresh_project_gem_targets_enabled_list()
|
||||
|
||||
def refresh_project_gem_targets_enabled_list(self) -> None:
|
||||
enabled_project_gem_targets_model = QStandardItemModel()
|
||||
enabled_project_gems = cmake.get_project_gems(project_path=self.get_selected_project_path())
|
||||
for gem_target in sorted(enabled_project_gems):
|
||||
model_item = QStandardItem(gem_target)
|
||||
enabled_project_gem_targets_model.appendRow(model_item)
|
||||
self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model)
|
||||
|
||||
|
||||
def refresh_project_gem_targets_available_list(self) -> None:
|
||||
available_project_gem_targets_model = QStandardItemModel()
|
||||
enabled_project_gem_targets = cmake.get_project_gems(project_path=self.get_selected_project_path())
|
||||
all_gem_targets = manifest.get_all_gems()
|
||||
for gem_target in sorted(all_gem_targets):
|
||||
if gem_target not in enabled_project_gem_targets:
|
||||
model_item = QStandardItem(gem_target)
|
||||
available_project_gem_targets_model.appendRow(model_item)
|
||||
self.available_gem_targets_list.setModel(available_project_gem_targets_model)
|
||||
|
||||
|
||||
def refresh_create_project_template_list(self) -> None:
|
||||
self.create_project_template_model = QStandardItemModel()
|
||||
for project_template_path in manifest.get_project_templates():
|
||||
model_item = QStandardItem(project_template_path)
|
||||
self.create_project_template_model.appendRow(model_item)
|
||||
self.create_project_template_list.setModel(self.create_project_template_model)
|
||||
|
||||
def refresh_create_gem_template_list(self) -> None:
|
||||
self.create_gem_template_model = QStandardItemModel()
|
||||
for gem_template_path in manifest.get_gem_templates():
|
||||
model_item = QStandardItem(gem_template_path)
|
||||
self.create_gem_template_model.appendRow(model_item)
|
||||
self.create_gem_template_list.setModel(self.create_gem_template_model)
|
||||
|
||||
def refresh_create_from_template_list(self) -> None:
|
||||
self.create_from_template_model = QStandardItemModel()
|
||||
for generic_template_path in manifest.get_generic_templates():
|
||||
model_item = QStandardItem(generic_template_path)
|
||||
self.create_from_template_model.appendRow(model_item)
|
||||
self.create_from_template_list.setModel(self.create_from_template_model)
|
||||
|
||||
def update_mru_list(self, used_project: str) -> None:
|
||||
"""
|
||||
Promote a supplied project name to the "most recent" in a given MRU list.
|
||||
:param used_project: path to project to promote
|
||||
:param file_path: path to mru list file
|
||||
:return: None
|
||||
"""
|
||||
used_project = os.path.normpath(used_project)
|
||||
if not os.path.exists(os.path.dirname(self.mru_file_path)):
|
||||
os.makedirs(os.path.dirname(self.mru_file_path), exist_ok=True)
|
||||
mru_data = {}
|
||||
try:
|
||||
with open(self.mru_file_path, 'r') as mru_file:
|
||||
mru_data = json.loads(mru_file.read())
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
recent_list = mru_data.get('Projects', [])
|
||||
recent_list = [item for item in recent_list if item.get('Path') != used_project and
|
||||
self.is_project_folder(item.get('Path'))]
|
||||
|
||||
new_list = [{'Path': used_project}]
|
||||
new_list.extend(recent_list)
|
||||
|
||||
mru_data['Projects'] = new_list
|
||||
try:
|
||||
with open(self.mru_file_path, 'w') as mru_file:
|
||||
mru_file.write(json.dumps(mru_data, indent=1))
|
||||
except PermissionError as e:
|
||||
logger.warning(f"Failed to write {self.mru_file_path} with error {e}")
|
||||
|
||||
def get_mru_list(self) -> List[str]:
|
||||
"""
|
||||
Retrieve the current MRU list. Does not perform validation that the projects still appear valid
|
||||
:return: list of full path strings to project folders
|
||||
"""
|
||||
if not os.path.exists(os.path.dirname(self.mru_file_path)):
|
||||
return []
|
||||
try:
|
||||
with open(self.mru_file_path, 'r') as mru_file:
|
||||
mru_data = json.loads(mru_file.read())
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
except json.JSONDecodeError:
|
||||
logger.error(f'MRU list at {self.mru_file_path} is not valid JSON')
|
||||
return []
|
||||
|
||||
recent_list = mru_data.get('Projects', [])
|
||||
return [item.get('Path') for item in recent_list if item.get('Path') is not None]
|
||||
|
||||
@Slot(str)
|
||||
def handle_log_message(self, message: str) -> None:
|
||||
"""
|
||||
Signal handler for messages from the logger. Displays the most recent warning/error
|
||||
:param message: formatted log message from DialogLoggerSignaller
|
||||
:return:
|
||||
"""
|
||||
if not self.log_display:
|
||||
return
|
||||
self.log_display.setText(message)
|
||||
self.log_display.setToolTip(message)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dialog_app = QApplication(sys.argv)
|
||||
my_dialog = ProjectManagerDialog()
|
||||
dialog_app.exec_()
|
||||
sys.exit(0)
|
||||
@ -1,61 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
pyside_initialized = False
|
||||
old_env = os.environ.copy()
|
||||
|
||||
# Helper to extend OS PATH for pyside to locate our QT binaries based on our build folder
|
||||
def add_pyside_environment(bin_path):
|
||||
if is_pyside_ready():
|
||||
# No need to reinitialize currently
|
||||
logger.info("Pyside environment already initialized")
|
||||
return
|
||||
global old_env
|
||||
old_env = os.environ.copy()
|
||||
binaries_path = Path(os.path.normpath(bin_path))
|
||||
platforms_path = binaries_path.joinpath("platforms")
|
||||
logger.info(f'Adding binaries path {binaries_path}')
|
||||
os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(platforms_path)
|
||||
|
||||
path = os.environ['PATH']
|
||||
|
||||
new_path = os.pathsep.join([str(binaries_path), str(platforms_path), path])
|
||||
os.environ['PATH'] = new_path
|
||||
|
||||
global pyside_initialized
|
||||
pyside_initialized = True
|
||||
|
||||
|
||||
def is_pyside_ready():
|
||||
return pyside_initialized
|
||||
|
||||
|
||||
def is_configuration_valid(workspace):
|
||||
return os.path.basename(workspace.paths.build_directory()) != "debug"
|
||||
|
||||
|
||||
def uninstall_env():
|
||||
if not is_pyside_ready():
|
||||
logger.warning("Pyside not initialized")
|
||||
return os.environ
|
||||
|
||||
global old_env
|
||||
if old_env.get("QT_QPA_PLATFORM_PLUGIN_PATH"):
|
||||
old_env.pop("QT_QPA_PLATFORM_PLUGIN_PATH")
|
||||
os.environ = old_env
|
||||
return old_env
|
||||
@ -1,10 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
@ -1,255 +0,0 @@
|
||||
#
|
||||
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
||||
# its licensors.
|
||||
#
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this
|
||||
# distribution (the "License"). All use of this software is governed by the License,
|
||||
# or, if provided, by the license below or the license accompanying this file. Do not
|
||||
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
#
|
||||
|
||||
|
||||
import pytest
|
||||
'''
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import logging
|
||||
import pathlib
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
# Code lives one folder above
|
||||
project_manager_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
|
||||
sys.path.append(project_manager_path)
|
||||
|
||||
from pyside import add_pyside_environment, is_configuration_valid
|
||||
from ly_test_tools import WINDOWS
|
||||
|
||||
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
||||
executable_path = ''
|
||||
from cmake.Tools import registration
|
||||
from cmake.Tools import engine_template
|
||||
|
||||
|
||||
class ProjectHelper:
|
||||
def __init__(self):
|
||||
self._temp_directory = pathlib.Path(tempfile.TemporaryDirectory().name).resolve()
|
||||
self._temp_directory.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
self.home_path = self._temp_directory
|
||||
registration.override_home_folder = self.home_path
|
||||
self.engine_path = registration.get_this_engine_path()
|
||||
if registration.register(engine_path=self.engine_path):
|
||||
assert True, f"Failed to register the engine."
|
||||
|
||||
if registration.register_shipped_engine_o3de_objects():
|
||||
assert True, f"Failed to register shipped engine objects."
|
||||
|
||||
self.projects_folder = registration.get_o3de_projects_folder()
|
||||
if not self.projects_folder.is_dir():
|
||||
assert True
|
||||
|
||||
self.application = None
|
||||
self.dialog = None
|
||||
|
||||
def create_empty_projects(self):
|
||||
self.project_1_dir = self.projects_folder / "Project1"
|
||||
if engine_template.create_project(project_manager_path=self.project_1_dir):
|
||||
assert True, f"Failed to create Project1."
|
||||
|
||||
self.project_2_dir = self.projects_folder / "Project2"
|
||||
if engine_template.create_project(project_manager_path=self.project_2_dir):
|
||||
assert True, f"Failed to create Project2."
|
||||
|
||||
self.project_3_dir = self.projects_folder / "Project3"
|
||||
if engine_template.create_project(project_manager_path=self.project_3_dir):
|
||||
assert True, f"Failed to create Project3."
|
||||
|
||||
self.invalid_project_dir = self.projects_folder / "InvalidProject"
|
||||
self.invalid_project_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def setup_dialog_test(self, workspace):
|
||||
add_pyside_environment(workspace.paths.build_directory())
|
||||
|
||||
if not is_configuration_valid(workspace):
|
||||
# This is essentially skipif debug. Our debug tests use our profile version of python, but that means we'd
|
||||
# need to use the profile version of PySide which works with the profile QT libs which aren't in the debug
|
||||
# folder we've built.
|
||||
return None
|
||||
|
||||
from PySide2.QtWidgets import QApplication, QMessageBox
|
||||
|
||||
if QApplication.instance():
|
||||
self.application = QApplication.instance()
|
||||
else:
|
||||
self.application = QApplication(sys.argv)
|
||||
assert self.application
|
||||
|
||||
from projects import ProjectManagerDialog
|
||||
|
||||
try:
|
||||
self.dialog = ProjectManagerDialog(settings_folder=self.home_path)
|
||||
return self.dialog
|
||||
except Exception as e:
|
||||
logger.error(f'Failed to create ProjectManagerDialog with error {e}')
|
||||
return None
|
||||
|
||||
def create_project_from_template(self, project_name) -> bool:
|
||||
"""
|
||||
Uses the dialog to create a temporary project based on the first template found
|
||||
:param project_name: Name of project to create. Will be created under temp_project_root
|
||||
:return: True for Success, False for failure
|
||||
"""
|
||||
from PySide2.QtWidgets import QWidget, QFileDialog
|
||||
from projects import ProjectManagerDialog
|
||||
|
||||
QWidget.exec = MagicMock()
|
||||
self.dialog.create_project_handler()
|
||||
QWidget.exec.assert_called_once()
|
||||
|
||||
assert len(self.dialog.project_templates), 'Failed to find any project templates'
|
||||
ProjectManagerDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0])
|
||||
|
||||
QFileDialog.exec = MagicMock()
|
||||
create_project_path = self.projects_folder / project_name
|
||||
QFileDialog.selectedFiles = MagicMock(return_value=[create_project_path])
|
||||
self.dialog.create_project_accepted_handler()
|
||||
if create_project_path.is_dir():
|
||||
assert True, f"Expected project creation folder not found at {create_project_path}"
|
||||
|
||||
if QWidget.exec.call_count == 2:
|
||||
assert True, "Message box confirming project creation failed to show"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_helper():
|
||||
return ProjectHelper()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_logger_handler(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning = MagicMock()
|
||||
logger.error(f'Testing logger')
|
||||
QMessageBox.warning.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_mru_list(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
project_helper.create_empty_projects()
|
||||
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}'
|
||||
|
||||
QMessageBox.warning = MagicMock()
|
||||
my_dialog.add_project(project_helper.invalid_project_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}'
|
||||
QMessageBox.warning.assert_called_once()
|
||||
|
||||
my_dialog.add_project(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}'
|
||||
|
||||
my_dialog.add_project(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
|
||||
|
||||
my_dialog.update_mru_list(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}'
|
||||
|
||||
my_dialog.add_project(project_helper.project_2_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}'
|
||||
|
||||
assert mru_list[0] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
|
||||
|
||||
my_dialog.update_mru_list(project_helper.project_1_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list added wrong items {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
|
||||
|
||||
my_dialog.add_project(project_helper.invalid_project_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item"
|
||||
|
||||
my_dialog.add_project(project_helper.project_3_dir)
|
||||
mru_list = my_dialog.get_mru_list()
|
||||
assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}'
|
||||
assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item"
|
||||
assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item"
|
||||
assert mru_list[2] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't third item"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_create_project(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
project_helper.create_project_from_template("TestCreateProject")
|
||||
|
||||
|
||||
@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently")
|
||||
@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent
|
||||
def test_add_remove_gems(workspace, project_helper):
|
||||
my_dialog = project_helper.setup_dialog_test(workspace)
|
||||
if not my_dialog:
|
||||
return
|
||||
|
||||
my_project_name = "TestAddRemoveGems"
|
||||
|
||||
project_helper.create_project_from_template(project_manager_path=my_project_name)
|
||||
my_project_path = project_helper.projects_folder / my_project_name
|
||||
|
||||
from PySide2.QtWidgets import QWidget, QFileDialog
|
||||
from projects import ProjectManagerDialog
|
||||
|
||||
assert my_dialog.get_selected_project_path() == my_project_path, "TestAddRemoveGems project not selected"
|
||||
QWidget.exec = MagicMock()
|
||||
my_dialog.manage_gems_handler()
|
||||
assert my_dialog.manage_gem_targets_dialog, "No gem management dialog created"
|
||||
QWidget.exec.assert_called_once()
|
||||
|
||||
if not len(my_dialog.all_gems_list):
|
||||
assert True, 'Failed to find any gems'
|
||||
|
||||
my_test_gem_path = my_dialog.all_gems_list[0]
|
||||
gem_data = registration.get_gem_data(my_test_gem_path)
|
||||
my_test_gem_selection = (my_test_gem_name, my_test_gem_path)
|
||||
ProjectManagerDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection])
|
||||
|
||||
assert my_test_gem_name, "No Name set in test gem"
|
||||
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list'
|
||||
|
||||
my_dialog.add_gems_handler()
|
||||
assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list'
|
||||
|
||||
ProjectManagerDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name])
|
||||
my_dialog.remove_gems_handler()
|
||||
assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list'
|
||||
'''
|
||||
|
||||
def test_project_place_holder():
|
||||
pass
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue