From 4ea3bcc51b29be347c201fc4fef6a1d5eaeba76b Mon Sep 17 00:00:00 2001 From: Song Date: Tue, 28 Jun 2022 09:52:05 -0700 Subject: [PATCH 01/16] adding relevant files Signed-off-by: Song --- .vscode/settings.json | 4 + Editor/Scripts/bush.prefab | 129 +++ Editor/Scripts/demo_tutorial.py | 1 + Editor/Scripts/editor_tutorial.py | 130 +++ Editor/Scripts/interactivetutorials_dialog.py | 1 - Editor/Scripts/pyside_utils.py | 903 ++++++++++++++++++ Editor/Scripts/tutorial.py | 8 +- Editor/Scripts/utils.py | 565 +++++++++++ 8 files changed, 1739 insertions(+), 2 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 Editor/Scripts/bush.prefab create mode 100644 Editor/Scripts/editor_tutorial.py create mode 100644 Editor/Scripts/pyside_utils.py create mode 100644 Editor/Scripts/utils.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cc67606 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true +} \ No newline at end of file diff --git a/Editor/Scripts/bush.prefab b/Editor/Scripts/bush.prefab new file mode 100644 index 0000000..ef497bd --- /dev/null +++ b/Editor/Scripts/bush.prefab @@ -0,0 +1,129 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "Bush", + "Components": { + "Component_[1140272189295067758]": { + "$type": "EditorInspectorComponent", + "Id": 1140272189295067758 + }, + "Component_[13437832196484687256]": { + "$type": "EditorOnlyEntityComponent", + "Id": 13437832196484687256 + }, + "Component_[1553903646452669645]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1553903646452669645 + }, + "Component_[15914009348632444632]": { + "$type": "EditorEntitySortComponent", + "Id": 15914009348632444632, + "Child Entity Order": [ + "Entity_[7511491868318]" + ] + }, + "Component_[18046340308818780248]": { + "$type": "EditorPrefabComponent", + "Id": 18046340308818780248 + }, + "Component_[1948833233489872938]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 1948833233489872938, + "Parent Entity": "" + }, + "Component_[2903632350157981339]": { + "$type": "SelectionComponent", + "Id": 2903632350157981339 + }, + "Component_[48827510535192710]": { + "$type": "EditorPendingCompositionComponent", + "Id": 48827510535192710 + }, + "Component_[5609536793322429681]": { + "$type": "EditorLockComponent", + "Id": 5609536793322429681 + }, + "Component_[5859168386298620990]": { + "$type": "EditorEntityIconComponent", + "Id": 5859168386298620990 + }, + "Component_[6604616929271524505]": { + "$type": "EditorVisibilityComponent", + "Id": 6604616929271524505 + } + } + }, + "Entities": { + "Entity_[7511491868318]": { + "Id": "Entity_[7511491868318]", + "Name": "Bush", + "Components": { + "Component_[10227459330338484901]": { + "$type": "EditorInspectorComponent", + "Id": 10227459330338484901, + "ComponentOrderEntryArray": [ + { + "ComponentId": 4998941225335869157 + }, + { + "ComponentId": 9922994635792843826, + "SortIndex": 1 + } + ] + }, + "Component_[10972351222359420947]": { + "$type": "EditorOnlyEntityComponent", + "Id": 10972351222359420947 + }, + "Component_[12101122374155214392]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12101122374155214392 + }, + "Component_[1535264614652988260]": { + "$type": "SelectionComponent", + "Id": 1535264614652988260 + }, + "Component_[16367811417907891218]": { + "$type": "EditorVisibilityComponent", + "Id": 16367811417907891218 + }, + "Component_[17044216787716682880]": { + "$type": "EditorEntitySortComponent", + "Id": 17044216787716682880 + }, + "Component_[2129822594969629430]": { + "$type": "EditorEntityIconComponent", + "Id": 2129822594969629430 + }, + "Component_[2838015156782745450]": { + "$type": "EditorLockComponent", + "Id": 2838015156782745450 + }, + "Component_[4998941225335869157]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 4998941225335869157, + "Parent Entity": "ContainerEntity" + }, + "Component_[8773358049076362578]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 8773358049076362578 + }, + "Component_[9922994635792843826]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 9922994635792843826, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{1201406D-FB20-5B5F-B9B5-6A6E8DE00A14}", + "subId": 276506120 + }, + "assetHint": "assets/objects/foliage/bush_privet_01.azmodel" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Editor/Scripts/demo_tutorial.py b/Editor/Scripts/demo_tutorial.py index 9726b15..0fdd519 100644 --- a/Editor/Scripts/demo_tutorial.py +++ b/Editor/Scripts/demo_tutorial.py @@ -21,6 +21,7 @@ def on_step_start(self): def on_step_end(self): print("Ended the select Entity step") + class DemoTutorial(Tutorial): def __init__(self): diff --git a/Editor/Scripts/editor_tutorial.py b/Editor/Scripts/editor_tutorial.py new file mode 100644 index 0000000..91948b8 --- /dev/null +++ b/Editor/Scripts/editor_tutorial.py @@ -0,0 +1,130 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +from PySide2.QtWidgets import QMenuBar + +from tutorial import Tutorial, TutorialStep + + +# Example of a custom step that overrides the start/end hooks +class SelectEntityStep(TutorialStep): + def __init__(self): + super(SelectEntityStep, self).__init__("Select an Entity", + "Next, select any Entity in the Entity Outliner", "EntityOutlinerWidgetUI") + + def on_step_start(self): + print("Starting X from AutomatedTesting") + print("Starting the select Entity step") + + def on_step_end(self): + print("Ended the select Entity step") + +class DemoTutorial(Tutorial): + def __init__(self): + super(DemoTutorial, self).__init__() + + self.title = "Demo Tutorial" + + self.add_step(TutorialStep("First things first", "Welcome! This first step shouldn't highlight any widget.")) + self.add_step(SelectEntityStep()) + self.add_step(TutorialStep("Add a component", "Use the Add Component button to add a component to the Entity", "m_addComponentButton")) + self.add_step(TutorialStep("Cool menu bar", "This step is just to showcase highlighting an item without a direct name but by using a type pattern instead", {"type": QMenuBar})) + + def on_tutorial_start(self): + print("Where we're going, we don't need roads") + + def on_tutorial_end(self): + print("Follow the yellow brick road") + +# Interactive tutorial version of this Editor tour: +# https://www.o3de.org/docs/welcome-guide/tours/editor-tour/ +class IntroTutorial(Tutorial): + def __init__(self): + super(IntroTutorial, self).__init__() + + self.title = "Intro to the Editor" + + self.add_step(TutorialStep("Introduction", """The default layout of O3DE Editor contains the most commonly used tools in a configuration, similar to other content creation applications. The core workflow of O3DE is to create and place entities in a level, so the default layout contains a menu bar, toolbars, panes, and tool tabs focused on entity creation and placement. +

+You can customize the layout through drag and drop, and save to a custom layout through the Layouts option in the View menu of the main menu bar. Drag the separator bars between panes to resize the panes. Drag the title bar of a pane to tear off the pane. The pane can be dropped anywhere in the layout or dropped outside of O3DE Editor as its own window. To restore the default layout, in the main menu bar choose View > Layouts > Default Layout. +""")) + + self.add_step(TutorialStep("Menu Bar", """Near the top of O3DE Editor are the Menu Bar and the Tool Bar. +

+The Menu Bar contains several familiar menus: + +""", {"type": QMenuBar})) + + self.add_step(TutorialStep("Toolbar", """ +The Tool Bar provides easy access to various editor tools and features. On the left are buttons to open various O3DE tools and editors, on the right are controls to run your project or activate simulation in editor. The Tool Bar is docked at the top of the editor by default, but you can also dock it vertically on the edges of the editor. To customize the toolbar, right-click anywhere on the toolbar and select Customize from the context menu. You can choose which toolbars to include, and add commands to the toolbar. +""", "EditMode")) + + self.add_step(TutorialStep("Entity Outliner", """ +On the left side of O3DE Editor, Entity Outliner displays a list of entities and prefabs in the current level. Right-click in Entity Outliner to open the context menu to create entities and instantiate prefabs. When an entity or prefab is selected in Entity Outliner, the context menu also has options to duplicate or delete entities, find selected entities and prefabs, organize the list, and open the properties for the selected entity or prefab. +""", "EntityOutlinerWidgetUI")) + + self.add_step(TutorialStep("Asset Browser", """ +Below Entity Outliner is Asset Browser, which you can use to browse your project’s on-disk assets. Assets such as meshes, animations, and textures are created in third-party applications. Assets such as materials, scripts, and prefabs are created in O3DE Editor, or in editor tools such as Script Canvas. The assets that you create are stored in your project directory. You can also browse default assets that are included with O3DE, as well as assets that are included with Gems that have been added to your project. +

+The left pane of the Asset Browser displays a directory structure that you can browse for available assets. When an asset is selected, the preview pane on the right displays a thumbnail preview and information about the asset, if available. +

+With an asset selected in Asset Browser, the right-click context menu has options to open Scene Settings where you can set Asset Processor options for the asset, as well as open the asset in an associated application such as a modeling program, or open the file location in the system file browser. +""", "AzAssetBrowserWindowClass")) + + self.add_step(TutorialStep("Entity Inspector", """ +On the right side of O3DE Editor, Entity Inspector displays the components of the currently selected entity. At the top of Entity Inspector is a field for the entity Name and an Add Component button. The Add Component button opens a list of available components, sorted by type, that can be added to the entity. Each component has its own set of properties that are displayed in Entity Inspector. All entities contain a transform component that sets the position, rotation, and scale of the entity in the level. +""", "InspectorMainWindow")) + + self.add_step(TutorialStep("Console", """ +At the bottom of the default O3DE Editor layout is the Editor Console, which shows command and process output from O3DE Editor and your project. When you load a level, for example, the console displays messages about assets and configuration files as they load, and might display warnings and errors if issues are encountered. +

+You can enter console commands such as setting console variables in the entry field at the bottom of the console. Choose the {x} button in the lower left of Editor Console to open the Console Variables Editor, which provides a simple interface for setting console variables. +""", "Console")) + + self.add_step(TutorialStep("Viewport", """ +In the center of the default O3DE Editor layout is Perspective. This 3D viewport is a real-time view of your level. In Perspective, you create and place entities, and view and play your project. +

+Right-click in the title bar of Perspective to open the perspective menu. From the perspective menu, you can toggle visibility for various helpers such as the construction plane, icons, bounds, and guides. You can also select an aspect ratio, view through various cameras placed in the level, create new cameras from the current view, and split Perspective into multiple views. +

+On the right side of the Perspective title bar, are several icons to select cameras, set camera movement speed, set information display, enable view icons, set aspect ratio, and set grid snapping options. +

+Right-click in the viewport of Perspective to open the context menu to create entities and prefabs. Much of the context menu functionality in Perspective is shared with the context menu functionality of Entity Outliner. +

+In the upper left and upper right corners of Perspective are icon trays for manipulating entities. On the left, you can use the icons to select a transform operation. From top to bottom the icons represent translate, rotate, and scale operations. On the right, you can use the icons to select a space for the transform operation. From top to bottom the icons represent world, parent, and local spaces. +""", "renderOverlay")) + + self.add_step(TutorialStep("Navigating the O3DE Perspective viewport", """ +O3DE has familiar viewport interaction models based on first-person PC games and popular modeling applications, with a few minor tweaks and additions. Movement is handled by keyboard input, and view is handled by pointer device input. + +The camera controls above are game-centric. If you prefer to use camera controls closer to those you would find in a DCC application such as Maya, use these hotkeys. + +""", "renderOverlay")) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index e5bcf2d..133ffd3 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -252,7 +252,6 @@ def load_next_step(self): def load_previous_step(self): if self.current_step: - self.current_step_index -= 1 prev_step = self.current_step.prev_step if prev_step: self.current_step_index -= 1 diff --git a/Editor/Scripts/pyside_utils.py b/Editor/Scripts/pyside_utils.py new file mode 100644 index 0000000..aeed99b --- /dev/null +++ b/Editor/Scripts/pyside_utils.py @@ -0,0 +1,903 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +import azlmbr.qt +import azlmbr.qt_helpers +import asyncio +import re +from shiboken2 import wrapInstance, getCppPointer +from PySide2 import QtCore, QtWidgets, QtGui, QtTest +from PySide2.QtCore import Qt +import traceback +import threading +import types + + +qApp = QtWidgets.QApplication.instance() + + +class LmbrQtEventLoop(asyncio.AbstractEventLoop): + def __init__(self): + self.running = False + self.shutdown = threading.Event() + self.blocked_events = set() + self.finished_events = set() + self.queue = [] + self._wait_future = None + self._event_loop_nesting = 0 + + def get_debug(self): + return False + + def time(self): + return azlmbr.qt_helpers.time() + + def wait_for_condition(self, condition, action, on_timeout=None, timeout=1.0): + timeout = self.time() + timeout if timeout is not None else None + def callback(time): + # Run our action and remove us from the queue if our condition is satisfied + if condition(): + action() + return True + # Give up if timeout has elapsed + if time > timeout: + if on_timeout is not None: + on_timeout() + return True + return False + self.queue.append((callback)) + + def event_loop(self): + time = self.time() + def run_event(event): + if event in self.blocked_events or event in self.finished_events: + return False + self.blocked_events.add(event) + try: + if event(time): + self.finished_events.add(event) + except Exception: + traceback.print_exc() + self.finished_events.add(event) + finally: + self.blocked_events.remove(event) + + self._event_loop_nesting += 1 + try: + for event in self.queue: + run_event(event) + finally: + self._event_loop_nesting -= 1 + + # Clear out any finished events if the queue is safe to mutate + if self._event_loop_nesting == 0: + self.queue = [event for event in self.queue if event not in self.finished_events] + self.finished_events = set() + + if not self.running or self._wait_future is not None and self._wait_future.done(): + self.close() + + def run_until_shutdown(self): + # Run our event loop callback (via azlmbr.qt_helpers) by pumping the Qt event loop + # azlmbr.qt_helpers will attempt to ensure our event loop is always run, even when a + # new event loop is started and run from the main event loop + self.running = True + self.shutdown.clear() + azlmbr.qt_helpers.set_loop_callback(self.event_loop) + while not self.shutdown.is_set(): + qApp.processEvents(QtCore.QEventLoop.AllEvents, 0) + + def run_forever(self): + self._wait_future = None + self.run_until_shutdown() + + def run_until_complete(self, future): + # Wrap coroutines into Tasks (future-like analogs) + if isinstance(future, types.CoroutineType): + future = self.create_task(future) + self._wait_future = future + self.run_until_shutdown() + + def _timer_handle_cancelled(self, handle): + pass + + def is_running(self): + return self.running + + def is_closed(self): + return not azlmbr.qt_helpers.loop_is_running() + + def stop(self): + self.running = False + + def close(self): + self.running = False + self.shutdown.set() + azlmbr.qt_helpers.clear_loop_callback() + + def shutdown_asyncgens(self): + pass + + def call_exception_handler(self, context): + try: + raise context.get('exception', None) + except: + traceback.print_exc() + + def call_soon(self, callback, *args, **kw): + h = asyncio.Handle(callback, args, self) + def callback_wrapper(time): + if not h.cancelled(): + h._run() + return True + self.queue.append(callback_wrapper) + return h + + def call_later(self, delay, callback, *args, **kw): + if delay < 0: + raise Exception("Can't schedule in the past") + return self.call_at(self.time() + delay, callback, *args) + + def call_at(self, when, callback, *args, **kw): + h = asyncio.TimerHandle(when, callback, args, self) + h._scheduled = True + def callback_wrapper(time): + if time > when: + if not h.cancelled(): + h._run() + return True + return False + self.queue.append(callback_wrapper) + return h + + def create_task(self, coro): + return asyncio.Task(coro, loop=self) + + def create_future(self): + return asyncio.Future(loop=self) + + +class EventLoopTimeoutException(Exception): + pass + + +event_loop = LmbrQtEventLoop() +def wait_for_condition(condition, timeout=1.0): + """ + Asynchronously waits for `condition` to evaluate to True. + condition: A function with the signature def condition() -> bool + This condition will be evaluated until it evaluates to True or the timeout elapses + timeout: The time in seconds to wait - if 0, this will wait forever + Throws pyside_utils.EventLoopTimeoutException on timeout. + """ + future = event_loop.create_future() + def on_complete(): + future.set_result(True) + def on_timeout(): + future.set_exception(EventLoopTimeoutException()) + event_loop.wait_for_condition(condition, on_complete, on_timeout=on_timeout, timeout=timeout) + return future + + +async def wait_for(expression, timeout=1.0): + """ + Asynchronously waits for "expression" to evaluate to a non-None value, + then returns that value. + expression: A function with the signature def expression() -> Generic[Any,None] + The result of expression will be returned as soon as it returns a non-None value. + timeout: The time in seconds to wait - if 0, this will wait forever + Throws pyside_utils.EventLoopTimeoutException on timeout. + """ + result = None + def condition(): + nonlocal result + result = expression() + return result is not None + await wait_for_condition(condition, timeout) + return result + + +def run_soon(fn): + """ + Runs a function on the event loop to enable asynchronous execution. + fn: The function to run, should be a function that takes no arguments + Returns a future that will be popualted with the result of fn or the exception it threw. + """ + future = event_loop.create_future() + def coroutine(): + try: + fn() + future.set_result(True) + except Exception as e: + future.set_exception(e) + event_loop.call_soon(coroutine) + return future + + +def run_async(awaitable): + """ + Synchronously runs a coroutine or a future on the event loop. + This can be used in lieu of "await" in non-async functions. + awaitable: The coroutine or future to await. + Returns the result of operation specified. + """ + if isinstance(awaitable, types.CoroutineType): + awaitable = event_loop.create_task(awaitable) + event_loop.run_until_complete(awaitable) + return awaitable.result() + + +def wrap_async(fn): + """ + This decorator enables an async function's execution from a synchronous one. + For example: + @pyside_utils.wrap_async + async def foo(): + result = await long_operation() + return result + def non_async_fn(): + x = foo() # this will return the correct result by executing the event loop + fn: The function to wrap + Returns the decorated function. + """ + def wrapper(*args, **kw): + result = fn(*args, **kw) + return run_async(result) + return wrapper + + +def get_editor_main_window(): + """ + Fetches the main Editor instance of QMainWindow for use with PySide tests + :return Instance of QMainWindow for the Editor + """ + params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters") + editor_id = QtWidgets.QWidget.find(params.mainWindowId) + main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) + return main_window + + +def get_action_for_menu_path(editor_window: QtWidgets.QMainWindow, main_menu_item: str, *menu_item_path: str): + """ + main_menu_item: Main menu item among the MenuBar actions. Ex: "File" + menu_item_path: Path to any nested menu item. Ex: "Viewport", "Goto Coordinates" + returns: QAction object for the corresponding path. + """ + # Check if path is valid + menu_bar = editor_window.menuBar() + menu_bar_actions = [index.iconText() for index in menu_bar.actions()] + + # Verify if the given Menu exists in the Menubar + if main_menu_item not in menu_bar_actions: + print(f"QAction not found for main menu item '{main_menu_item}'") + return None + curr_action = menu_bar.actions()[menu_bar_actions.index(main_menu_item)] + curr_menu = curr_action.menu() + for index, element in enumerate(menu_item_path): + curr_menu_actions = [index.iconText() for index in curr_menu.actions()] + if element not in curr_menu_actions: + print(f"QAction not found for menu item '{element}'") + return None + if index == len(menu_item_path) - 1: + return curr_menu.actions()[curr_menu_actions.index(element)] + curr_action = curr_menu.actions()[curr_menu_actions.index(element)] + curr_menu = curr_action.menu() + return None + + +def _pattern_to_dict(pattern, **kw): + """ + Helper function, turns a pattern match parameter into a normalized dictionary + """ + + def is_string_or_regex(x): + return isinstance(x, str) or isinstance(x, re.Pattern) + + # If it's None, just make an empty dict + if pattern is None: + pattern = {} + # If our pattern is a string or regex, turn it into a text match + elif is_string_or_regex(pattern): + pattern = dict(text=pattern) + # If our pattern is an (int, int) tuple, turn it into a row/column match + elif isinstance(pattern, tuple) and isinstance(pattern[0], int) and isinstance(pattern[1], int): + pattern = dict(row=pattern[0], column=pattern[1]) + # If our pattern is a QObject type, turn it into a type match + elif isinstance(pattern, type(QtCore.QObject)): + pattern = dict(type=pattern) + # Otherwise assume it's a dict and make a copy + else: + pattern = dict(pattern) + + # Merge with any kw arguments + for key, value in kw.items(): + pattern[key] = value + return pattern + + +def _match_pattern(obj, pattern): + """ + Helper function, determines whether obj matches the pattern specified by pattern. + It is required that pattern is normalized into a dict before calling this. + """ + + def compare(value1, value2): + # Do a regex search if it's a regex, otherwise do a normal compare + if isinstance(value2, re.Pattern): + return re.search(value2, value1) + return value1 == value2 + + item_roles = Qt.ItemDataRole.values.values() + for key, value in pattern.items(): + if key == "type": # Class type + if not isinstance(obj, value): + return False + elif key == "text": # Default 'text' path, depends on type + text_values = [] + + def get_from_attrs(*args): + for attr in args: + try: + text_values.append(getattr(obj, attr)()) + except Exception: + pass + + # Use any of the following fields for default matching, if they're defined + get_from_attrs("text", "objectName", "windowTitle") + # Additionally, use the DisplayRole for QModelIndexes + if isinstance(obj, QtCore.QModelIndex): + text_values.append(obj.data(Qt.DisplayRole)) + + if not any(compare(text, value) for text in text_values): + return False + elif key in item_roles: # QAbstractItemModel display role + if not isinstance(obj, QtCore.QModelIndex): + raise RuntimeError(f"Attempted to match data role on unsupported object {obj}") + if not compare(obj.data(key), value): + return False + elif hasattr(obj, key): + # Look up our key on the object itself + objectValue = getattr(obj, key) + # Invoke it if it's a getter + if callable(objectValue): + objectValue = objectValue() + if not compare(objectValue, value): + return False + else: + return False + + return True + + +def get_child_indexes(model, parent_index=QtCore.QModelIndex()): + indexes = [parent_index] + while len(indexes) > 0: + parent_index = indexes.pop(0) + for row in range(model.rowCount(parent_index)): + # FIXME + # PySide appears to have a bug where-in it thinks columnCount is private + # Bail gracefully for now, we can add a C++ wrapper to work around if needed + try: + column_count = model.columnCount(parent_index) + except Exception: + column_count = 1 + for col in range(column_count): + cur_index = model.index(row, col, parent_index) + yield cur_index + + +def _get_children(obj): + """ + Helper function. Get the direct descendants from a given PySide object. + This includes all: QObject children, QActions owned by the object, and QModelIndexes if applicable + """ + if isinstance(obj, QtCore.QObject): + yield from obj.children() + if isinstance(obj, QtWidgets.QWidget): + yield from obj.actions() + if isinstance(obj, (QtWidgets.QAbstractItemView, QtCore.QModelIndex)): + model = obj.model() + if model is None: + return + + # For a QAbstractItemView (e.g. QTreeView, QListView), the parent index + # will be an invalid QModelIndex(), which will use find all indexes on the root. + # For a QModelIndex, we use the actual QModelIndex as the parent_index so that + # it will find any child indexes under it + parent_index = QtCore.QModelIndex() + if isinstance(obj, QtCore.QModelIndex): + parent_index = obj + + yield from get_child_indexes(model, parent_index) + + +def _get_parents_to_search(obj_entry_or_list): + """ + Helper function, turns obj_entry_or_list into a list of parents to search + If obj_entry_or_list is None, returns all visible top level widgets + If obj_entry_or_list is iterable, return it as a list + Otherwise, return a list containing obj_entry_or_list + """ + if obj_entry_or_list is None: + return [widget for widget in QtWidgets.QApplication.topLevelWidgets() if widget.isVisible()] + try: + return list(obj_entry_or_list) + except TypeError: + return [obj_entry_or_list] + + +def find_children_by_pattern(obj=None, pattern=None, recursive=True, **kw): + """ + Finds the children of an object that match a given pattern. + See find_child_by_pattern for more information on usage. + """ + pattern = _pattern_to_dict(pattern, **kw) + parents_to_search = _get_parents_to_search(obj) + + while len(parents_to_search) > 0: + parent = parents_to_search.pop(0) + for child in _get_children(parent): + if _match_pattern(child, pattern): + yield child + if recursive: + parents_to_search.append(child) + + +def find_child_by_pattern(obj=None, pattern=None, recursive=True, **kw): + """ + Finds the child of an object that matches a given pattern. + A "child" in this context is not necessarily a QObject child. + QActions are also considered children, as are the QModelIndex children of QAbstractItemViews. + obj: The object to search - should be either a QObject or a QModelIndex, or a list of them + If None this will search all top level windows. + pattern: The pattern to match, the first child that matches all of the criteria specified will + be returned. This is a dictionary with any combination of the following: + - "text": generic text to match, will search object names for QObjects, display role text + for QModelIndexes, or action text() for QActions + - "type": a class type, e.g. QtWidgets.QMenu, a child will only match if it's of this type + - "row" / "column": integer row and column indices of a QModelIndex + - "type": type class (e.g. PySide.QtWidgets.QComboBox) that the object must inherit from + - A Qt.ItemDataRole: matches for QModelIndexes with data of a given value + - Any other fields will fall back on being looked up on the object itself by name, e.g. + {"windowTitle": "Foo"} would match a windowTitle named "Foo" + Any instances where a field is specified as text can also be specified as a regular expression: + find_child_by_pattern(obj, {text: re.compile("Foo_.*")}) would find a child with text starting + with "Foo_" + For convenience, these parameter types may also be specified as keyword arguments: + find_child_by_pattern(obj, text="foo", type=QtWidgets.QAction) + is equivalent to + find_child_by_pattern(obj, {"text": "foo", "type": QtWidgets.QAction}) + If pattern is specified as a string, it will turn into a pattern matching "text": + find_child_by_pattern(obj, "foo") + is equivalent to + find_child_by_pattern(obj, {"text": "foo"}) + If a pattern is specified as an (int, int) tuple, it will turn into a row/column match: + find_child_by_pattern(obj, (0, 2)) + is equivalent to + find_child_by_pattern(obj, {"row": 0, "column": 2}) + If a pattern is specified as a type, like PySide.QtWidgets.QLabel, it will turn into a type match: + find_child_by_pattern(obj, PySide.QtWidgets.QLabel) + is equivalent to + find_child_by_pattern(obj, {"type": PySide.QtWidgets.QLabel}) + """ + # Return the first match result, if found + for match in find_children_by_pattern(obj, pattern=pattern, recursive=recursive, **kw): + return match + return None + + +def find_child_by_hierarchy(parent, *patterns): + """ + Searches for a hierarchy of children descending from parent. + parent: The Qt object (or list of Qt obejcts) to search within + If none, this will search all top level windows. + patterns: A list of patterns to match to find a hierarchy of descendants. + These patterns will be tested in order. + For example, to look for the QComboBox in a hierarchy like the following: + QWidget (window) + -QTabWidget + -QWidget named "m_exampleTab" + -QComboBox + One might invoke: + find_child_by_hierarchy(window, QtWidgets.QTabWidget, "m_exampleTab", QtWidgets.QComboBox) + Alternatively, "..." may be specified in place of a parent, where the hierarchy will match any + ancestors along the path, so the above might be shortened to: + find_child_by_hierarchy(window, ..., "m_exampleTab", QtWidgets.QComboBox) + """ + search_recursively = False + current_objects = _get_parents_to_search(parent) + for pattern in patterns: + # If it's an ellipsis, do the next search recursively as we're looking for any number of intermediate ancestors + if pattern is ...: + search_recursively = True + continue + + candidates = [] + for parent_candidate in current_objects: + candidates += find_children_by_pattern(parent_candidate, pattern=pattern, recursive=search_recursively) + if len(candidates) == 0: + return None + current_objects = candidates + + search_recursively = False + return current_objects[0] + +async def wait_for_child_by_hierarchy(parent, *patterns, timeout=1.0): + """ + Searches for a hierarchy of children descending from parent until timeout occurs. + Returns a future that will result in either the found child or an EventLoopTimeoutException. + See find_child_by_hierarchy for usage information. + """ + match = None + def condition(): + nonlocal match + match = find_child_by_hierarchy(parent, *patterns) + return match is not None + await wait_for_condition(condition, timeout) + return match + + +async def wait_for_child_by_pattern(obj=None, pattern=None, recursive=True, timeout=1.0, **kw): + """ + Finds the child of an object that matches a given pattern. + Returns a future that will result in either the found child or an EventLoopTimeoutException. + See find_child_by_hierarchy for usage information. + """ + match = None + def condition(): + nonlocal match + match = find_child_by_pattern(obj, pattern, recursive, **kw) + return match is not None + await wait_for_condition(condition, timeout) + return match + + +def find_child_by_property(obj, obj_type, property_name, property_value, reg_exp_search=False): + """ + Finds the child of an object which has the property name matching the property value + of type obj_type + obj: The property value is searched through obj children + obj_type: Type of object to be matched + property_name: Property of the child which should be verified for the required value. + property_value: Property value that needs to be matched + reg_exp_search: If True searches for the property_value based on re search. Defaults to False. + """ + for child in obj.children(): + if reg_exp_search and re.search(property_value, getattr(child, property_name)()): + return child + if not reg_exp_search and isinstance(child, obj_type) and getattr(child, property_name)() == property_value: + return child + return None + +def get_item_view_index(item_view, row, column=0, parent=QtCore.QModelIndex()): + """ + Retrieve the index for a specified row/column, with optional parent + This is necessary when needing to reference into nested hierarchies in a QTreeView + item_view: The QAbstractItemView instance + row: The requested row index + column: The requested column index (defaults to 0 in case of single column) + parent: Parent index (defaults to invalid) + """ + item_model = item_view.model() + model_index = item_model.index(row, column, parent) + return model_index + + +def get_item_view_index_rect(item_view, index): + """ + Gets the QRect for a given index in a QAbstractItemView (e.g. QTreeView, QTableView, QListView). + This is helpful because for sending mouse events to a QAbstractItemView, you have to send them to + the viewport() widget of the QAbstractItemView. + item_view: The QAbstractItemView instance + index: A QModelIndex for the item index + """ + return item_view.visualRect(index) + + +def item_view_index_mouse_click(item_view, index, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): + """ + Helper method version of QTest.mouseClick for injecting mouse clicks on a QAbstractItemView + item_view: The QAbstractItemView instance + index: A QModelIndex for the item index to be clicked + """ + item_index_rect = get_item_view_index_rect(item_view, index) + item_index_center = item_index_rect.center() + + # For QAbstractItemView widgets, the events need to be forwarded to the actual viewport() widget + QTest.mouseClick(item_view.viewport(), button, modifier, item_index_center) + + +def item_view_mouse_click(item_view, row, column=0, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): + """ + Helper method version of 'item_view_index_mouse_click' using a row, column instead of a QModelIndex + item_view: The QAbstractItemView instance + row: The requested row index + column: The requested column index (defaults to 0 in case of single column) + """ + index = get_item_view_index(item_view, row, column) + item_view_index_mouse_click(item_view, index, button, modifier) + + +async def wait_for_action_in_menu(menu, pattern, timeout=1.0): + """ + Finds a QAction inside a menu, based on the specified pattern. + menu: The QMenu to search + pattern: The action text or pattern to match (see find_child_by_pattern) + If pattern specifies a QWidget, this will search for the associated QWidgetAction + """ + action = await wait_for_child_by_pattern(menu, pattern, timeout=timeout) + if action is None: + raise TimeoutError(f"Failed to find context menu action for {pattern}") + + # If we've found a valid QAction, we're good to go + if hasattr(action, 'trigger'): + return action + + # If pattern matches a widget and not a QAction, look for an associated QWidgetAction + widget_actions = find_children_by_pattern(menu, type=QtWidgets.QWidgetAction) + underlying_widget_action = None + for widget_action in widget_actions: + widgets_to_check = [widget_action.defaultWidget()] + widget_action.createdWidgets() + for check_widget in widgets_to_check: + if action in _get_children(check_widget): + underlying_widget_action = widget_action + break + if underlying_widget_action is not None: + action = underlying_widget_action + break + + if not hasattr(action, 'trigger'): + raise RuntimeError(f"Failed to find action associated with widget {action}") + return action + + +def queue_hide_event(widget): + """ + Explicitly post a hide event for the next frame, this can be used to ensure modal dialogs exit correctly. + widget: The widget to hide + """ + qApp.postEvent(widget, QtGui.QHideEvent()) + + +async def wait_for_destroyed(obj, timeout=1.0): + """ + Waits for a QObject (including a widget) to be fully destroyed + This can be used to wait for a modal dialog to shut down properly + obj: The object to wait on destruction + timeout: The time, in seconds to wait. 0 for an indefinite wait. + """ + was_destroyed = False + def on_destroyed(): + nonlocal was_destroyed + was_destroyed = True + obj.destroyed.connect(on_destroyed) + return await wait_for_condition(lambda: was_destroyed, timeout=timeout) + + +async def close_modal(modal_widget, timeout=1.0): + """ + Closes a modal dialog and waits for it to be cleaned up. + This attempts to ensure the modal event loop gets properly exited. + modal_widget: The widget to close + timeout: The time, in seconds, to wait. 0 for an indefinite wait. + """ + queue_hide_event(modal_widget) + return await wait_for_destroyed(modal_widget, timeout=timeout) + + +def trigger_context_menu_entry(widget, pattern, pos=None, index=None): + """ + Trigger a context menu event on a widget and activate an entry + widget: The widget to trigger the event on + pattern: The action text or pattern to match (see find_child_by_pattern) + pos: Optional, the QPoint to set as the event origin + index: Optional, the QModelIndex to click in widget + widget must be a QAbstractItemView + """ + async def async_wrapper(): + menu = await open_context_menu(widget, pos=pos, index=index) + action = await wait_for_action_in_menu(menu, pattern) + action.trigger() + queue_hide_event(menu) + + result = async_wrapper() + # If we have an event loop, go ahead and just return the coroutine + # Otherwise, do a synchronous wait + if event_loop.is_running(): + return result + else: + return run_async(result) + + +async def open_context_menu(widget, pos=None, index=None, timeout=1.0): + """ + Trigger a context menu event on a widget + widget: The widget to trigger the event on + pos: Optional, the QPoint to set as the event origin + index: Optional, the QModelIndex to click in widget + widget must be a QAbstractItemView + Returns the menu that was created. + """ + if index is not None: + if pos is not None: + raise RuntimeError("Error: 'index' and 'pos' are mutually exclusive") + pos = widget.visualRect(index).center() + parent = widget + widget = widget.viewport() + pos = widget.mapFrom(parent, pos) + if pos is None: + pos = widget.rect().center() + + # Post both a mouse event and a context menu to let the widget handle whichever is appropriate + qApp.postEvent(widget, QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, pos)) + QtTest.QTest.mouseClick(widget, Qt.RightButton, Qt.NoModifier, pos) + + menu = None + # Wait for a menu popup + def menu_has_focus(): + nonlocal menu + for fw in [QtWidgets.QApplication.activePopupWidget(), QtWidgets.QApplication.activeModalWidget(), + QtWidgets.QApplication.focusWidget(), QtWidgets.QApplication.activeWindow()]: + if fw and isinstance(fw, QtWidgets.QMenu) and fw.isVisible(): + menu = fw + return True + return False + await wait_for_condition(menu_has_focus, timeout) + return menu + + +def move_mouse(widget, position): + """ + Helper method to move the mouse to a specified position on a widget + widget: The widget to trigger the event on + position: The QPoint (local to widget) to move the mouse to + """ + # For some reason, Qt wouldn't register the mouse movement correctly unless both of these ways are invoked. + # The QTest.mouseMove seems to update the global cursor position, but doesn't always result in the MouseMove event being + # triggered, which prevents drag/drop being able to be simulated. + # Similarly, if only the MouseMove event is sent by itself to the core application, the global cursor position wasn't + # updated properly, so drag/drop logic that depends on grabbing the globalPos didn't work. + QtTest.QTest.mouseMove(widget, position) + event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, position, widget.mapToGlobal(position), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) + QtCore.QCoreApplication.sendEvent(widget, event) + + +def drag_and_drop(source, target, source_point = QtCore.QPoint(), target_point = QtCore.QPoint()): + """ + Simulate a drag/drop event from a source object to a specified target + This has special case handling if the source is a QDockWidget (for docking) vs normal drag/drop + source: The source object to initiate the drag from + This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dragging an item view item + target: The target object to drop on after dragging + This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dropping on an item view item + source_point: Optional, The QPoint to initiate the drag from. If none is specified, the center of the source will be used. + target_point: Optional, The QPoint to drop on. If none is specified, the center of the target will be used. + """ + # Flag if this drag/drop is for docking, which has some special cases + docking = False + + # If the source is a tuple of (QAbstractItemView, QModelIndex), we need to use the + # viewport() as the source, and find the location of the index + if isinstance(source, tuple) and len(source) == 2: + source_item_view = source[0] + source_widget = source_item_view.viewport() + source_model_index = source[1] + source_rect = source_item_view.visualRect(source_model_index) + else: + # There are some special case actions if we are doing this drag for docking, + # so figure this out by checking if the source is a QDockWidget + if isinstance(source, QtWidgets.QDockWidget): + docking = True + + source_widget = source + source_rect = source.rect() + + # If the target is a tuple of (QAbstractItemView, QModelIndex), we need to use the + # viewport() as the target, and find the location of the index + if isinstance(target, tuple) and len(target) == 2: + target_item_view = target[0] + target_widget = target_item_view.viewport() + target_model_index = target[1] + target_rect = target_item_view.visualRect(target_model_index) + else: + # If we are doing a drag for docking, we actually want all the mouse events + # to still be directed through the source widget + if docking: + target_widget = source_widget + else: + target_widget = target + target_rect = target.rect() + + # If no source_point is specified, we need to find the center point of + # the source widget + if source_point.isNull(): + # If we are dragging for docking, initiate the drag from the center of the + # dock widget title bar + if docking: + title_bar_widget = source.titleBarWidget() + if title_bar_widget: + source_point = title_bar_widget.geometry().center() + else: + raise RuntimeError("No titleBarWidget found for QDockWidget") + # Otherwise, can just find the center of the rect + else: + source_point = source_rect.center() + + # If no target_point was specified, we need to find the center point of the target widget + if target_point.isNull(): + target_point = target_rect.center() + + # If we are dragging for docking and we aren't dragging within the same source/target, + # the mouse movements need to be directed to the source_widget, so we need to use the + # difference in global positions of our source and target widgets to adjust the target_point + # to be relative to the source + if docking and source != target: + source_top_left = source.mapToGlobal(QtCore.QPoint(0, 0)) + target_top_left = target.mapToGlobal(QtCore.QPoint(0, 0)) + offset = target_top_left - source_top_left + target_point += offset + + # Move the mouse to the source spot where we will start the drag + move_mouse(source_widget, source_point) + + # Press the left-mouse button to begin the drag + QtTest.QTest.mousePress(source_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, source_point) + + # If we are dragging for docking, we first need to drag the mouse past the minimum distance to + # trigger the docking system properly + if docking: + drag_distance = QtWidgets.QApplication.startDragDistance() + 1 + docking_trigger_point = source_point + QtCore.QPoint(drag_distance, drag_distance) + move_mouse(source_widget, docking_trigger_point) + + # Drag the mouse to the target widget over the desired point + move_mouse(target_widget, target_point) + + # Release the left-mouse button to complete the drop. + # If we are docking, we need to delay the actual mouse button release because the docking system has + # a delay before the drop zone becomes active after it has been hovered, which can be found here: + # FancyDockingDropZoneConstants::dockingTargetDelayMS = 110 ms + # So we need to delay greater than dockingTargetDelayMS after the final mouse move + # over the intended target. + delay = -1 + if docking: + delay = 200 + QtTest.QTest.mouseRelease(target_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, target_point, delay) + + # Some drag/drop events have extra processing on the following event tick, so let those processEvents + # first before we complete the drag/drop operation + QtWidgets.QApplication.processEvents() + + +def trigger_action_async(action): + """ + Convenience function. Triggers an action asynchronously. + This can be used if calling action.trigger might block (e.g. if it opens a modal dialog) + action: The action to trigger + """ + return run_soon(lambda: action.trigger()) + + +def click_button_async(button): + """ + Convenience function. Clicks a button asynchronously. + This can be used if calling button.click might block (e.g. if it opens a modal dialog) + button: The button to click + """ + return run_soon(lambda: button.click()) + + +async def wait_for_modal_widget(timeout=1.0): + """ + Waits for an active modal widget and returns it. + """ + return await wait_for(lambda: QtWidgets.QApplication.activeModalWidget(), timeout=timeout) + +async def wait_for_popup_widget(timeout=1.0): + """ + Waits for an active popup widget and returns it. + """ + return await wait_for(lambda: QtWidgets.QApplication.activePopupWidget(), timeout=timeout) \ No newline at end of file diff --git a/Editor/Scripts/tutorial.py b/Editor/Scripts/tutorial.py index 78e9644..99238b8 100644 --- a/Editor/Scripts/tutorial.py +++ b/Editor/Scripts/tutorial.py @@ -7,7 +7,8 @@ import json import os - +from fbx import * +import pyside_utils # Class that describes a step in the tutorial class TutorialStep: @@ -28,8 +29,13 @@ def __init__(self, title, content, highlight_pattern=None): # A step class can override this method if they need # to setup any special handling/listeners def on_step_start(self): + # Automated testing scripts go here + exec(bush.prefab) pass + def service_func(): + print ("step 1") + # Method that will be called after a step has ended # A step class can override this method if they need # to perform any cleanup or other tasks diff --git a/Editor/Scripts/utils.py b/Editor/Scripts/utils.py new file mode 100644 index 0000000..3b6e4fc --- /dev/null +++ b/Editor/Scripts/utils.py @@ -0,0 +1,565 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +import json +import math +import os +import time +import traceback +from typing import Callable, Tuple + +import azlmbr +import azlmbr.legacy.general as general +import azlmbr.multiplayer as multiplayer +import azlmbr.debug +import ly_test_tools.environment.waiter as waiter +import ly_test_tools.environment.process_utils as process_utils + + +class FailFast(Exception): + """ + Raise to stop proceeding through test steps. + """ + pass + + +class TestHelper: + @staticmethod + def init_idle(): + general.idle_enable(True) + + @staticmethod + def create_level(level_name: str) -> bool: + """ + :param level_name: The name of the level to be created + :return: True if ECreateLevelResult returns 0, False otherwise with logging to report reason + """ + Report.info(f"Creating level {level_name}") + + # Use these hardcoded values to pass expected values for old terrain system until new create_level API is + # available + heightmap_resolution = 1024 + heightmap_meters_per_pixel = 1 + terrain_texture_resolution = 4096 + use_terrain = False + + result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel, + terrain_texture_resolution, use_terrain) + + # Result codes are ECreateLevelResult defined in CryEdit.h + if result == 1: + Report.info(f"{level_name} level already exists") + elif result == 2: + Report.info("Failed to create directory") + elif result == 3: + Report.info("Directory length is too long") + elif result != 0: + Report.info("Unknown error, failed to create level") + else: + Report.info(f"{level_name} level created successfully") + + return result == 0 + + @staticmethod + def open_level(directory : str, level : str, no_prompt: bool = True): + # type: (str, str) -> None + """ + :param level: the name of the level folder in AutomatedTesting\\Physics\\ + :return: None + """ + # Make sure we are not in game mode + if general.is_in_game_mode(): + general.exit_game_mode() + TestHelper.wait_for_condition(lambda : not general.is_in_game_mode(), 1.0) + assert not general.is_in_game_mode(), "Editor was in gamemode when opening the level and was unable to exit from it" + + Report.info("Open level {}/{}".format(directory, level)) + if no_prompt: + success = general.open_level_no_prompt(os.path.join(directory, level)) + else: + success = general.open_level(os.path.join(directory, level)) + + if not success: + open_level_name = general.get_current_level_name() + if open_level_name == level: + Report.info("{} was already opened".format(level)) + else: + assert False, "Failed to open level: {} does not exist or is invalid".format(level) + + # FIX-ME: Expose call for checking when has been finished loading and change this frame waiting + # Jira: LY-113761 + general.idle_wait_frames(200) + + @staticmethod + def enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None: + """ + :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. + :return: None + """ + Report.info("Entering game mode") + general.enter_game_mode() + + TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0) + Report.critical_result(msgtuple_success_fail, general.is_in_game_mode()) + + @staticmethod + def find_line(window, line, print_infos): + """ + Looks for an expected line in a list of tracer log lines + :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. + :param line: The log message to search for. + :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints + :return: True if the line is found, otherwise false. + """ + for printInfo in print_infos: + if printInfo.window == window.strip() and printInfo.message.strip() == line: + return True + return False + + @staticmethod + def succeed_if_log_line_found(window, line, print_infos, time_out): + """ + Looks for a line in a list of tracer log lines and reports success if found. + :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. + :param line: The log message we're hoping to find. + :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints + :param time_out: The total amount of time to wait before giving up looking for the expected line. + :return: No return value, but if the message is found, a successful critical result is reported; otherwise failure. + """ + TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out) + Report.critical_result(("Found expected line: " + line, "Failed to find expected line: " + line), TestHelper.find_line(window, line, print_infos)) + + @staticmethod + def fail_if_log_line_found(window, line, print_infos, time_out): + """ + Reports a failure if a log line in a list of tracer log lines is found. + :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. + :param line: The log message we're hoping to not find. + :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints + :param time_out: The total amount of time to wait before giving up looking for the unexpected line. If time runs out and we don't see the unexpected line then report a success. + :return: No return value, but if the line is found, a failed critical result is reported; otherwise success. + """ + TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out) + Report.critical_result(("Unexpected line not found: " + line, "Unexpected line found: " + line), not TestHelper.find_line(window, line, print_infos)) + + @staticmethod + def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None: + """ + :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. + :return: None + """ + Report.info("Entering game mode") + + with Tracer() as section_tracer: + # enter game-mode. + # game-mode in multiplayer will also launch ServerLauncher.exe and connect to the editor + multiplayer.PythonEditorFuncs_enter_game_mode() + + # make sure the server launcher binary exists + TestHelper.fail_if_log_line_found("MultiplayerEditor", "LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5) + + # make sure the server launcher is running + waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0) + + TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor has connected to the editor-server.", section_tracer.prints, 120.0) + + TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0) + + TestHelper.succeed_if_log_line_found("EditorServer", "System: Editor Server completed receiving the editor's level assets, responding to Editor...", section_tracer.prints, 5.0) + + TestHelper.succeed_if_log_line_found("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0) + + TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0) + Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode()) + + @staticmethod + def exit_game_mode(msgtuple_success_fail : Tuple[str, str]): + # type: (tuple) -> None + """ + :param msgtuple_success_fail: The tuple with the expected/unexpected messages for exiting game mode. + :return: None + """ + Report.info("Exiting game mode") + general.exit_game_mode() + + TestHelper.wait_for_condition(lambda : not general.is_in_game_mode(), 1.0) + Report.critical_result(msgtuple_success_fail, not general.is_in_game_mode()) + + @staticmethod + def close_editor(): + general.exit_no_prompt() + + @staticmethod + def fail_fast(message=None): + # type: (str) -> None + """ + A state has been reached where progressing in the test is not viable. + raises FailFast + :return: None + """ + if message: + Report.info("Fail fast message: {}".format(message)) + raise FailFast() + + @staticmethod + def wait_for_condition(function, timeout_in_seconds): + # type: (function, float) -> bool + """ + **** Will be replaced by a function of the same name exposed in the Engine***** + a function to run until it returns True or timeout is reached + the function can have no parameters and + waiting idle__wait_* is handled here not in the function + :param function: a function that returns a boolean indicating a desired condition is achieved + :param timeout_in_seconds: when reached, function execution is abandoned and False is returned + """ + + with Timeout(timeout_in_seconds) as t: + while True: + try: + general.idle_wait_frames(1) + except: + Report.info("WARNING: Couldn't wait for frame") + + if t.timed_out: + return False + + ret = function() + if not isinstance(ret, bool): + raise TypeError("return value for wait_for_condition function must be a bool") + if ret: + return True + + @staticmethod + def close_error_windows(): + """ + Closes Error Report and Error Log windows that block focus if they are visible. + :return: None + """ + if general.is_pane_visible("Error Report"): + general.close_pane("Error Report") + if general.is_pane_visible("Error Log"): + general.close_pane("Error Log") + + @staticmethod + def close_display_helpers(): + """ + Closes helper gizmos, anti-aliasing, and FPS meters. + :return: None + """ + if general.is_helpers_shown(): + general.toggle_helpers() + general.idle_wait(1.0) + general.idle_wait(1.0) + general.run_console("r_displayInfo=0") + general.idle_wait(1.0) + + +class Timeout: + # type: (float) -> None + """ + contextual timeout + :param seconds: float seconds to allow before timed_out is True + """ + + def __init__(self, seconds): + self.seconds = seconds + + def __enter__(self): + self.die_after = time.time() + self.seconds + return self + + def __exit__(self, type, value, traceback): + pass + + @property + def timed_out(self): + return time.time() > self.die_after + + +class Report: + _results = [] + _exception = None + + @staticmethod + def start_test(test_function : Callable): + """ + Runs the test, outputs the report and asserts in case of failure. + @param: The test function to execute + """ + Report._results = [] + Report._exception = None + general.test_output(f"Starting test {test_function.__name__}...\n") + try: + test_function() + except Exception as ex: + Report._exception = traceback.format_exc() + + success, report_str = Report.get_report(test_function) + # Print on the o3de console, for debugging purpuses + print(report_str) + # Print the report on the piped stdout of the application + general.test_output(report_str) + assert success, f"Test {test_function.__name__} failed" + + @staticmethod + def get_report(test_function : Callable) -> (bool, str): + """ + Outputs infomation on the editor console for the test + @param msg: message to output + @return: (success, report_information) tuple + """ + success = True + report = f"Test {test_function.__name__} finished.\nReport:\n" + # report_dict is a JSON that can be used to parse test run information from a external runner + # The regular report string is intended to be used for manual debugging + filename = os.path.splitext(os.path.basename(test_function.__code__.co_filename))[0] + report_dict = {'name' : filename, 'success' : True, 'exception' : None} + for result in Report._results: + passed, info = result + success = success and passed + test_result_info = "" + if passed: + test_result_info = f"[SUCCESS] {info}" + else: + test_result_info = f"[FAILED ] {info}" + report += f"{test_result_info}\n" + if Report._exception: + exception_str = Report._exception[:-1].replace("\n", "\n ") + report += "EXCEPTION raised:\n %s\n" % exception_str + report_dict['exception'] = exception_str + success = False + report += "Test result: " + ("SUCCESS" if success else "FAILURE") + report_dict['success'] = success + report_dict['output'] = report + report_json_str = json.dumps(report_dict) + # For helping parsing, the json will be always contained between JSON_START JSON_END + report += f"\nJSON_START({report_json_str})JSON_END\n" + return success, report + + @staticmethod + def info(msg : str): + """ + Outputs infomation on the editor console for the test + @param msg: message to output + """ + print("Info: {}".format(msg)) + + @staticmethod + def success(msgtuple_success_fail : Tuple[str, str]): + """ + Given a test string tuple (success_string, failure_string), registers the test result as success + @param msgtuple_success_fail: Two element tuple of success and failure strings + """ + outcome = "Success: {}".format(msgtuple_success_fail[0]) + print(outcome) + Report._results.append((True, outcome)) + + @staticmethod + def failure(msgtuple_success_fail : Tuple[str, str]): + """ + Given a test string tuple (success_string, failure_string), registers the test result as failed + @param msgtuple_success_fail: Two element tuple of success and failure strings + """ + outcome = "Failure: {}".format(msgtuple_success_fail[1]) + print(outcome) + Report._results.append((False, outcome)) + + @staticmethod + def result(msgtuple_success_fail : Tuple[str, str], outcome : bool): + """ + Given a test string tuple (success_string, failure_string), registers the test result based on the + given outcome + @param msgtuple_success_fail: Two element tuple of success and failure strings + @param outcome: True or False if the result has been a sucess or failure + """ + if not isinstance(outcome, bool): + raise TypeError("outcome argument must be a bool") + + if outcome: + Report.success(msgtuple_success_fail) + else: + Report.failure(msgtuple_success_fail) + return outcome + + @staticmethod + def critical_result(msgtuple_success_fail : Tuple[str, str], outcome : bool, fast_fail_message : str = None): + # type: (tuple, bool, str) -> None + """ + if outcome is False we will fail fast + :param msgtuple_success_fail: messages to print based on the outcome + :param outcome: success (True) or failure (False) + :param fast_fail_message: [optional] message to include on fast fail + """ + if not isinstance(outcome, bool): + raise TypeError("outcome argument must be a bool") + + if not Report.result(msgtuple_success_fail, outcome): + TestHelper.fail_fast(fast_fail_message) + + # DEPRECATED: Use vector3_str() + @staticmethod + def info_vector3(vector3 : azlmbr.math.Vector3, label : str ="", magnitude : float =None): + # type: (azlmbr.math.Vector3, str, float) -> None + """ + prints the vector to the Report.info log. If applied, label will print first, + followed by the vector's values (x, y, z,) to 2 decimal places. Lastly if the + magnitude is supplied, it will print on the third line. + :param vector3: a azlmbr.math.Vector3 object to print + prints in [x: , y: , z: ] format. + :param label: [optional] A string to print before printing the vector3's contents + :param magnitude: [optional] the vector's magnitude to print after the vector's contents + :return: None + """ + if label != "": + Report.info(label) + Report.info(" x: {:.2f}, y: {:.2f}, z: {:.2f}".format(vector3.x, vector3.y, vector3.z)) + if magnitude is not None: + Report.info(" magnitude: {:.2f}".format(magnitude)) + + +''' +Utility for scope tracing errors and warnings. +Usage: + ... + with Tracer() as section_tracer: + # section were we are interested in capturing errors/warnings/asserts + ... + Report.result(Tests.warnings_not_found_in_section, not section_tracer.has_warnings) +''' +class Tracer: + def __init__(self): + self.warnings = [] + self.errors = [] + self.asserts = [] + self.prints = [] + self.has_warnings = False + self.has_errors = False + self.has_asserts = False + self.handler = None + + class WarningInfo: + def __init__(self, args): + self.window = args[0] + self.filename = args[1] + self.line = args[2] + self.function = args[3] + self.message = args[4] + + def __str__(self): + return f"Warning: [{self.filename}:{self.function}:{self.line}]: [{self.window}] {self.message}" + + def __repr__(self): + return f"[Warning: {self.message}]" + + class ErrorInfo: + def __init__(self, args): + self.window = args[0] + self.filename = args[1] + self.line = args[2] + self.function = args[3] + self.message = args[4] + + def __str__(self): + return f"Error: [{self.filename}:{self.function}:{self.line}]: [{self.window}] {self.message}" + + def __repr__(self): + return f"[Error: {self.message}]" + + class AssertInfo: + def __init__(self, args): + self.filename = args[0] + self.line = args[1] + self.function = args[2] + self.message = args[3] + + def __str__(self): + return f"Assert: [{self.filename}:{self.function}:{self.line}]: {self.message}" + + def __repr__(self): + return f"[Assert: {self.message}]" + + class PrintInfo: + def __init__(self, args): + self.window = args[0] + self.message = args[1] + + def _on_warning(self, args): + warningInfo = Tracer.WarningInfo(args) + self.warnings.append(warningInfo) + Report.info("Tracer caught Warning: %s" % warningInfo.message) + self.has_warnings = True + return False + + def _on_error(self, args): + errorInfo = Tracer.ErrorInfo(args) + self.errors.append(errorInfo) + Report.info("Tracer caught Error: %s" % errorInfo.message) + self.has_errors = True + return False + + def _on_assert(self, args): + assertInfo = Tracer.AssertInfo(args) + self.asserts.append(assertInfo) + Report.info("Tracer caught Assert: %s:%i[%s] \"%s\"" % (assertInfo.filename, assertInfo.line, assertInfo.function, assertInfo.message)) + self.has_asserts = True + return False + + def _on_printf(self, args): + printInfo = Tracer.PrintInfo(args) + self.prints.append(printInfo) + return False + + def __enter__(self): + self.handler = azlmbr.debug.TraceMessageBusHandler() + self.handler.connect(None) + self.handler.add_callback("OnPreAssert", self._on_assert) + self.handler.add_callback("OnPreWarning", self._on_warning) + self.handler.add_callback("OnPreError", self._on_error) + self.handler.add_callback("OnPrintf", self._on_printf) + return self + + def __exit__(self, type, value, traceback): + self.handler.disconnect() + self.handler = None + return False + + +class AngleHelper: + @staticmethod + def is_angle_close(x_rad, y_rad, tolerance): + # type: (float, float , float) -> bool + """ + compare if 2 angles measured in radians are close + :param x_rad: angle in radians + :param y_rad: angle in radians + :param tolerance: the tolerance to define close + :return: bool + """ + sinx_sub_siny = math.sin(x_rad) - math.sin(y_rad) + cosx_sub_cosy = math.cos(x_rad) - math.cos(y_rad) + r = sinx_sub_siny * sinx_sub_siny + cosx_sub_cosy * cosx_sub_cosy + diff = math.acos((2.0 - r) / 2.0) + return abs(diff) <= tolerance + + @staticmethod + def is_angle_close_deg(x_deg, y_deg, tolerance): + # type: (float, float , float) -> bool + """ + compare if 2 angles measured in degrees are close + :param x_deg: angle in degrees + :param y_deg: angle in degrees + :param tolerance: the tolerance to define close + :return: bool + """ + return AngleHelper.is_angle_close(math.radians(x_deg), math.radians(y_deg), tolerance) + + +def vector3_str(vector3): + return "(x: {:.2f}, y: {:.2f}, z: {:.2f})".format(vector3.x, vector3.y, vector3.z) + + +def aabb_str(aabb): + return "[Min: %s, Max: %s]" % (vector3_str(aabb.min), vector3_str(aabb.max)) \ No newline at end of file From 3045eaea24e52c5a76ee25c9e3fa12bdc8d3dd4f Mon Sep 17 00:00:00 2001 From: Song Date: Thu, 30 Jun 2022 06:20:20 -0700 Subject: [PATCH 02/16] creating entity scripts Signed-off-by: Song --- Editor/Scripts/create_wind_forces_tutorial.py | 64 ------------------ .../customize_mesh_asset_processing.py | 28 +++----- Editor/Scripts/decompose_input_meshes.py | 42 ------------ Editor/Scripts/editor_tutorial.py | 8 ++- Editor/Scripts/interactivetutorials_dialog.py | 15 +---- .../Scripts/process_physx_collider_assets.py | 65 ------------------- Editor/Scripts/tutorial.py | 6 -- Editor/Scripts/utils.py | 5 +- 8 files changed, 22 insertions(+), 211 deletions(-) delete mode 100644 Editor/Scripts/create_wind_forces_tutorial.py delete mode 100644 Editor/Scripts/decompose_input_meshes.py delete mode 100644 Editor/Scripts/process_physx_collider_assets.py diff --git a/Editor/Scripts/create_wind_forces_tutorial.py b/Editor/Scripts/create_wind_forces_tutorial.py deleted file mode 100644 index 004d0a5..0000000 --- a/Editor/Scripts/create_wind_forces_tutorial.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -from tutorial import Tutorial, TutorialStep - -class WindForcesTutorial(Tutorial): - def __init__(self): - super(WindForcesTutorial, self).__init__() - - self.title = "Create Wind Forces" - - self.add_step(TutorialStep("Create Wind Forces", """

Greetings!

We can use a PhysX Force Region component to - create both global and local wind forces. Wind forces affect entities with components that are affected by wind, - such as cloth. Wind forces don't affect PhysX Rigid Body components. - For this tutorial, make sure that you have the Nvidia Cloth Gem enabled in your project. -

Click next to continue.

""")) - self.add_step(TutorialStep("Create a wind provider entity", """

  • First, create an entity - for the wind provider.
  • Next, add a Tag component for the entity. The Tag will specify whether the - wind is a global or a local force. Edit the tag value to specify the wind type by choosing PhysX Configuration - from the Tools menu and toggling the Wind Configuration.

    - Wind Configuration includes the Global wind tag and the Local wind tag.

    """, "EntityOutlinerWidgetUI")) - self.add_step(TutorialStep("Update the Shape", """

    Set the PhysX Collider component's Shape - property to Box.

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Scale the Entity", """

    Adjust the Box Dimensions - to your specifications. If you're creating a localized wind force, make the dimenesions large enough that they can - contain the entity that receives the wind force.

    """, "InspectorMainWindow")) - self.add_step(TutorialStep("Position the Entity", """

    Use the Move tool - to position the entity in the level. For instance, you might consider positioning the entity so that - the bottom of the box is level with the ground.

    """, "InspectorMainWindow")) - self.add_step(TutorialStep("Add a PhysX Region component to the entity", """

    - Select the Add button located next to Forces. Then, in the Direction property - of your new force, set the Y component to -1.0, and the Z component to 0.0, to create - a direction for the wind. The PhysX collider box displays cones indicating the direction of the wind force.
    - Set the Magnitude to 10.0.

    """, "InspectorMainWindow")) - self.add_step(TutorialStep("Add a Cloth Prefab", """

    We'll test the wind provider by adding - a NVIDIA Cloth Mesh. In Asset Browser, navigate to Gems\NvCloth\Assets\prefabs\Cloth, then locate - cloth_locked_edge.prefab, and drag that asset into the viewport.

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Position the Prefab", """

    Use the Move tool to place the cloth prefab. - Once the prefab's in position, you can hide the wind provider entity. In Entity Outliner, in the column to the - right of the wind provider entity, toggle the Show/Hide Entity setting.

    Note: if you're using the - Local wind tag, you must place the cloth asset inside the PhysX Collider volume of the wind provider entity. -

    """, "EntityOutlinerWidgetUI")) - self.add_step(TutorialStep("Edit the Prefab", """

    The cloth prefab has a local wind property - enabled which generates a local wind force that overrides our wind provider entity. We will deactiate the local - wind force of the prefab so that we can view the results of the wind provider we created.

    - In Entity Outliner, double-click the cloth_locked_edge prefab to edit it in Focus Mode, and select the cloth_locked_edge - entity. Then, in the Cloth component of the Entity Inspector, expand the Wind propoerty group and deselect - Enable local wind velocity.

    """, "EntityOutlinerWidgetUI")) - self.add_step(TutorialStep("Run the simulation", """

    We can now run the - simualtion and view the results. With the cloth_locked_edge prefab open for editing in Focus mode, select the - Simulate in editor option located at the top of the Cloth component. As the simulation begins, the cloth object might flip and - stretch wildly, but it will quickly settle into a breezy wind simulation. The simulation plays while in editor mode, so you can - adjust various properties within the Cloth component and view the results in real time.
    Consider adjusting the:

  • - Air drag coefficient
  • Air lift coefficient
  • Air density

    ""","InspectorMainWindow")) - def on_tutorial_start(self): - print("Starting Wind Forces tutorial.") - - def on_tutorial_end(self): - print("Wind Forces tutorial complete!") - diff --git a/Editor/Scripts/customize_mesh_asset_processing.py b/Editor/Scripts/customize_mesh_asset_processing.py index b64bcd2..67947be 100644 --- a/Editor/Scripts/customize_mesh_asset_processing.py +++ b/Editor/Scripts/customize_mesh_asset_processing.py @@ -4,8 +4,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ - -from tutorial import Tutorial, TutorialStep +import azlmbr.bus as bus # type: ignore +import azlmbr.editor as editor # type: ignore +import azlmbr.entity # type: ignore +from azlmbr.entity import EntityId # type: ignore +from tutorial import Tutorial, TutorialStep # type: ignore class CustomizeMeshAssetProcessingTutorial(Tutorial): def __init__(self): @@ -17,11 +20,6 @@ def __init__(self): """

    Greetings!

    Meshes refer to the external appearances of objects.
    In this tutorial, we'll edit the mesh of a sphere by enlarging it. We'll also introduce some other ways you can edit the mesh.

    Click next to continue.

    """)) - self.add_step(TutorialStep("Preparing the scene", """

    The Asset Processor runs - in the background automatically detecting source assets and scheduling process jobs for them. -

    In Asset Browser, search for the sphere.fbx source asset and - right click to expand it. Select 'Edit Settings'. -

    """, "AzAssetBrowserWindowClass")) self.add_step(TutorialStep("Edit the Scene Settings", """

    By default, all the meshes in a source asset are processed as a single group, each of which produces a set of product assets. Let's create additional mesh groups for our sphere source asset by choosing Add another mesh.

    @@ -30,24 +28,18 @@ def __init__(self): You can choose the file select button to select which meshes to include in the mesh group. For this tutorial, you can use the default mesh group with all meshes selected.

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Change the Coordinate System", """

    Let’s add a modifier to customize - how the asset is processed. Choose the Add Modifier button to view the mesh modifier list - and select Coordinate system change.

    - The Coordinate system change mesh modifier is used to scale or transform the asset for scenarios - where the asset might be too small, too large, or incorrectly oriented in O3DE. - By default, the modifier provides a single option to rotate the mesh 180 degrees. - Activate the Use advanced settings toggle to expose the advanced modifier settings.

    - Let’s customize the scale of the asset. Set the Scale property to 5.0 to scale the asset to five times its size. -

    """, "AzAssetBrowserWindowClass")) self.add_step(TutorialStep("View Changes", """

    To use the prefab in our level we need to create an instance, or instantiate the prefab in the level.

    Drag the 20-sided-dice.prefab into the viewport to instatiate it in the level.

    """, "AzAssetBrowserWindowClass")) self.add_step(TutorialStep("Position the prefab", """

    Choose the Update button at the bottom-right of Scene Settings. - This creates or updates the .assetinfo sidecar file and triggers Asset Processor to reprocess the asset. - Drag the .azmodel product asset from Asset Browser into the viewport. + This creates or updates the .assetinfo sidecar file and triggers Asset Processor to reprocess the asset. Drag the .azmodel product asset from Asset Browser into the viewport.

    """, "renderOverlay")) + + def on_step_start(self): + print("Starting the select Entity step") + CustomizeMeshAssetProcessingTutorial.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) def on_tutorial_start(self): print("Starting Mesh Asset Process tutorial.") diff --git a/Editor/Scripts/decompose_input_meshes.py b/Editor/Scripts/decompose_input_meshes.py deleted file mode 100644 index c9149ec..0000000 --- a/Editor/Scripts/decompose_input_meshes.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -from tutorial import Tutorial, TutorialStep - -class DecomposeInputMeshes(Tutorial): - def __init__(self): - super(DecomposeInputMeshes, self).__init__() - - self.title = "Decompose Input Meshes" - - self.add_step(TutorialStep("Decompose Input Meshes", - """

    Greetings!

    Assets composed of multiple and/or complex meshes, - such as some kinematic or dynamic entities, might require similarly complex PhysX collider assets. In these cases, - you can decompose the input mesh into convex parts. You can automatically generate primitive or convex colliders, - fit them to each part, and process them as collider .pxmesh product assets.

    - Mesh decomposition is part of the process of - generating and fitting primitive or convex collider assets, and it doesn’t alter the input mesh. - .

    Click next to continue.

    """)) - self.add_step(TutorialStep("Select the Asset", """

    Locate your source asset in the - Asset Browser. You can use your own or use one of the provided .fbx files, such as sphere.fbx. - Right click the .fbx source asset and select 'Edit Settings.' -

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Decompose Meshes", """

    In Scene Settings, underneath - PhysX Mesh Group, choose to Export As either Primitive or Convex. Then enable - Decompose Meshes.

    -

  • For primitive colliders, the input mesh is decomposed into primitive parts. The best fitting primitive shapes - are automatically selected and transformed to encompass each part and the primitives are processed as a - product asset.
  • For convex colliders, the input mesh is decomposed into convex parts. - A convex hull is generated for each part and the convex hulls are processed as a collider product asset. - In general, collider assets generated with decomposed meshes provide a more accurate representation of - the render mesh than a single primitive or convex collider can.

    """, "EntityOutlinerWidgetUI")) - - def on_tutorial_start(self): - print("Starting tutorial to decompose input meshes.") - - def on_tutorial_end(self): - print("'Decompose input meshes' tutorial complete!") diff --git a/Editor/Scripts/editor_tutorial.py b/Editor/Scripts/editor_tutorial.py index 91948b8..030f801 100644 --- a/Editor/Scripts/editor_tutorial.py +++ b/Editor/Scripts/editor_tutorial.py @@ -4,9 +4,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ - +import azlmbr.bus as bus # type: ignore +import azlmbr.editor as editor # type: ignore +import azlmbr.entity # type: ignore +from azlmbr.entity import EntityId # type: ignore from PySide2.QtWidgets import QMenuBar - from tutorial import Tutorial, TutorialStep @@ -19,6 +21,7 @@ def __init__(self): def on_step_start(self): print("Starting X from AutomatedTesting") print("Starting the select Entity step") + DemoTutorial.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) def on_step_end(self): print("Ended the select Entity step") @@ -36,6 +39,7 @@ def __init__(self): def on_tutorial_start(self): print("Where we're going, we don't need roads") + rootentity = DemoTutorial.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) def on_tutorial_end(self): print("Follow the yellow brick road") diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index 133ffd3..c037152 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -23,10 +23,7 @@ from demo_tutorial import DemoTutorial, IntroTutorial from rigid_body_tutorial import RigidBodyTutorial -from process_physx_collider_assets import ColliderAssetsTutorial -from decompose_input_meshes import DecomposeInputMeshes from customize_mesh_asset_processing import CustomizeMeshAssetProcessingTutorial - from tutorial import Tutorial class HighlightWidget(QWidget): @@ -95,15 +92,7 @@ def __init__(self, parent=None): { "name": "PhysX Rigid Bodies", "tutorial": RigidBodyTutorial - }, - { - "name": "Process PhysX Collider Assets", - "tutorial": ColliderAssetsTutorial - }, - { - "name": "Decompose Input Meshes", - "tutorial": DecomposeInputMeshes - }, + }, { "name": "Customize Mesh Asset Processing", "tutorial": CustomizeMeshAssetProcessingTutorial @@ -233,7 +222,7 @@ def load_step(self, step): self.current_step.on_step_end() self.current_step = step - self.current_step_index += 1 + self.current_step_index += 1 # Invoke the method for the beginning of this step self.current_step.on_step_start() diff --git a/Editor/Scripts/process_physx_collider_assets.py b/Editor/Scripts/process_physx_collider_assets.py deleted file mode 100644 index ecdfc6c..0000000 --- a/Editor/Scripts/process_physx_collider_assets.py +++ /dev/null @@ -1,65 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -from tutorial import Tutorial, TutorialStep - -class ColliderAssetsTutorial(Tutorial): - def __init__(self): - super(ColliderAssetsTutorial, self).__init__() - - self.title = "PhysX Collider Assets" - - self.add_step(TutorialStep("Process PhysX Collider Assets", - """

    Greetings!

    Collider assets are assets - generated based on input meshes. There are various types of colliders, such as triangle, primitive, and convex. - Colliders are categorized based on the shapes they are comprised of: triangle, sphere, - box, capsule, and/or convex.

    In this tutorial, we'll edit an - existing source asset to create a Physx collider asset.

    Click next to continue.

    """)) - self.add_step(TutorialStep("Select the Asset", """

    Locate your source asset in the - Asset Browser. You can use your own or use one of the provided .fbx files, such as sphere.fbx. - Right click the .fbx source asset and select 'Edit Settings.' -

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Add a PhysX Mesh", """

    Select the - PhysX tab and select Add another physxmesh to create a PhysX mesh group.

    - Each PhysX group produces as .pxmesh product asset. -

    """, - "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Add a PhysX Mesh", """

    To select which - meshes to include in the PhysX mesh group, use the file select button.

    If you select multiple meshes, - you might want to also enable the Merge Meshes and Weld Vertices - features to optimize the asset's appearance. -

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Customize the PhysX Mesh Collider Type", """

    Customize the - PhysX mesh collider type by setting the Export As property to the type you choose. You might prefer to - selecct the 'Convex' type so that you can create any type of entity (static, kinematic, dynamic), but - Triangle and Primitive types each also have their respective advantages and limitations.

    You can visit the - Docs online if you want to learn more.

    Then select Update (in the bottom right corner) - to update the .assetinfo file and trigger the Asset Processor.

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("View the Entity", """

    .

  • Drag the .azmodel - product asset into the viewport from the Asset Browser.

    As you do this, O3DE automatically creates - an entity with a Mesh component referencing the mesh product asset.
  • -

    """, "renderOverlay")) - self.add_step(TutorialStep("View the Entity with the Collider component", """

    Entity Inspector, select Add Component and then PhysX Collider. - The component we've just added automatically detects the .pxmesh asset.

    - The is set to PhysicsAsset and the references the .pxmesh product asset.


    - """, "InspectorMainWindow")) - self.add_step(TutorialStep("Optimize your Entity", """

    Our entity is currently static, because - we only have a PhysX Collider component. A static entity is solid (it can be collided with) but does not move - in response to collisions.

  • If you want a static entity, you can enable the Static property within the Transform - component to maximize your entity's runtime performance.
  • If you want a dynamic entity, add a PhysX Rigid Body - component. (Make sure that the Static property in the Transform component is disabled.) - Select the entity in the viewport. Then, in Entity Inspector, select Add Compoonent and then - PhysX Rigid Body to enable the effect of gravity on your entity. You can observe the changes by selecting the - simulation button.
  • If you want a kinematic entity, add a PhysX Rigid Body Component. Then, within the component, - enable the Kinematic property. (Make sure that the Static property in the Transform component is disabled.)

    """, "InspectorMainWindow")) - - def on_tutorial_start(self): - print("Starting PhysX Collider Assets tutorial.") - - def on_tutorial_end(self): - print("PhysX Collider Assets tutorial complete!") diff --git a/Editor/Scripts/tutorial.py b/Editor/Scripts/tutorial.py index 99238b8..6aa55bd 100644 --- a/Editor/Scripts/tutorial.py +++ b/Editor/Scripts/tutorial.py @@ -7,8 +7,6 @@ import json import os -from fbx import * -import pyside_utils # Class that describes a step in the tutorial class TutorialStep: @@ -30,12 +28,8 @@ def __init__(self, title, content, highlight_pattern=None): # to setup any special handling/listeners def on_step_start(self): # Automated testing scripts go here - exec(bush.prefab) pass - def service_func(): - print ("step 1") - # Method that will be called after a step has ended # A step class can override this method if they need # to perform any cleanup or other tasks diff --git a/Editor/Scripts/utils.py b/Editor/Scripts/utils.py index 3b6e4fc..a4b7f8e 100644 --- a/Editor/Scripts/utils.py +++ b/Editor/Scripts/utils.py @@ -12,12 +12,15 @@ from typing import Callable, Tuple import azlmbr +import azlmbr.bus as bus +import azlmbr.editor as editor import azlmbr.legacy.general as general import azlmbr.multiplayer as multiplayer import azlmbr.debug import ly_test_tools.environment.waiter as waiter import ly_test_tools.environment.process_utils as process_utils - +import azlmbr.entity +from azlmbr.entity import EntityId class FailFast(Exception): """ From 79ed5ada802b61e96664a27bb0c18493bff067ad Mon Sep 17 00:00:00 2001 From: Song Date: Thu, 30 Jun 2022 11:43:21 -0700 Subject: [PATCH 03/16] creating an entity Signed-off-by: Song --- .../customize_mesh_asset_processing.py | 49 ------------------- Editor/Scripts/interactivetutorials_dialog.py | 6 +-- Editor/Scripts/tutorial_for_scripts.py | 43 ++++++++++++++++ 3 files changed, 46 insertions(+), 52 deletions(-) delete mode 100644 Editor/Scripts/customize_mesh_asset_processing.py create mode 100644 Editor/Scripts/tutorial_for_scripts.py diff --git a/Editor/Scripts/customize_mesh_asset_processing.py b/Editor/Scripts/customize_mesh_asset_processing.py deleted file mode 100644 index 67947be..0000000 --- a/Editor/Scripts/customize_mesh_asset_processing.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" -import azlmbr.bus as bus # type: ignore -import azlmbr.editor as editor # type: ignore -import azlmbr.entity # type: ignore -from azlmbr.entity import EntityId # type: ignore -from tutorial import Tutorial, TutorialStep # type: ignore - -class CustomizeMeshAssetProcessingTutorial(Tutorial): - def __init__(self): - super(CustomizeMeshAssetProcessingTutorial, self).__init__() - - self.title = "Customize Mesh Asset Processing" - - self.add_step(TutorialStep("Customize Mesh Asset Processing", - """

    Greetings!

    Meshes refer to the external appearances - of objects.
    In this tutorial, we'll edit the mesh of a sphere by enlarging it. We'll also introduce - some other ways you can edit the mesh.

    Click next to continue.

    """)) - self.add_step(TutorialStep("Edit the Scene Settings", """

    By default, - all the meshes in a source asset are processed as a single group, each of which produces a set of product assets. Let's create additional mesh groups for our sphere source asset by - choosing Add another mesh.

    - The Name mesh property contains the name of the source asset. - The Select meshes property reads All meshes selected. - You can choose the file select button to select which meshes to include in the mesh group. - For this tutorial, you can use the default mesh group with all meshes selected. -

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("View Changes", """

    To use the prefab in our - level we need to create an instance, or instantiate the prefab in the level.

    Drag the - 20-sided-dice.prefab into the - viewport to instatiate it in the level.

    """, "AzAssetBrowserWindowClass")) - self.add_step(TutorialStep("Position the prefab", """

    - Choose the Update button at the bottom-right of Scene Settings. - This creates or updates the .assetinfo sidecar file and triggers Asset Processor to reprocess the asset. Drag the .azmodel product asset from Asset Browser into the viewport. -

    """, "renderOverlay")) - - def on_step_start(self): - print("Starting the select Entity step") - CustomizeMeshAssetProcessingTutorial.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - - def on_tutorial_start(self): - print("Starting Mesh Asset Process tutorial.") - - def on_tutorial_end(self): - print("Mesh Asset Process tutorial complete!") - diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index c037152..1e60673 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -23,7 +23,7 @@ from demo_tutorial import DemoTutorial, IntroTutorial from rigid_body_tutorial import RigidBodyTutorial -from customize_mesh_asset_processing import CustomizeMeshAssetProcessingTutorial +from tutorial_for_scripts import TutorialForScripts from tutorial import Tutorial class HighlightWidget(QWidget): @@ -94,8 +94,8 @@ def __init__(self, parent=None): "tutorial": RigidBodyTutorial }, { - "name": "Customize Mesh Asset Processing", - "tutorial": CustomizeMeshAssetProcessingTutorial + "name": "Scripts", + "tutorial": TutorialForScripts } ] tutorial_names = [tutorial['name'] for tutorial in self.tutorials] diff --git a/Editor/Scripts/tutorial_for_scripts.py b/Editor/Scripts/tutorial_for_scripts.py new file mode 100644 index 0000000..506e80b --- /dev/null +++ b/Editor/Scripts/tutorial_for_scripts.py @@ -0,0 +1,43 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import os +import azlmbr.bus as bus # type: ignore +import azlmbr.editor as editor # type: ignore +import azlmbr.entity # type: ignore +from azlmbr.entity import EntityId # type: ignore +import azlmbr.paths as paths +from tutorial import Tutorial, TutorialStep # type: ignore + +class TutorialForScripts(Tutorial): + FILE_PATH = os.path.join(paths.projectroot, "TestAssets", "test_file.scriptevents") + + def __init__(self): + super(TutorialForScripts, self).__init__() + + self.title = "Entity Creating and Naming" + + self.add_step(TutorialStep("Entity Creating and Naming", + """

    My goals for this tutorial are to use scripts + to create an entity, to name an entity, and to delete an entity. + Future goals: open a window (tools-> ___), + create a script to apply the 'result' of a tutorial to an entity

    """)) + + def on_step_start(self): + print("Starting the select Entity step") + newEntity = TutorialForScripts.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH) + # SCRIPTS I HAVE TRIED + # self.generate_variable_test_output(self, "create") + # get_action_for_menu_path + # _get_children --> the direct descendants from a given PySide object. + + def on_tutorial_start(self): + print("Starting Mesh Asset Process tutorial.") + + def on_tutorial_end(self): + print("Mesh Asset Process tutorial complete!") + From 967c8bb093eeeda61ada89183b0b1c3e8b290989 Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 1 Jul 2022 10:22:38 -0700 Subject: [PATCH 04/16] create and delete Signed-off-by: Song --- Editor/Scripts/pyside_utils.py | 903 ------------------------- Editor/Scripts/tutorial_for_scripts.py | 16 +- 2 files changed, 10 insertions(+), 909 deletions(-) delete mode 100644 Editor/Scripts/pyside_utils.py diff --git a/Editor/Scripts/pyside_utils.py b/Editor/Scripts/pyside_utils.py deleted file mode 100644 index aeed99b..0000000 --- a/Editor/Scripts/pyside_utils.py +++ /dev/null @@ -1,903 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -import azlmbr.qt -import azlmbr.qt_helpers -import asyncio -import re -from shiboken2 import wrapInstance, getCppPointer -from PySide2 import QtCore, QtWidgets, QtGui, QtTest -from PySide2.QtCore import Qt -import traceback -import threading -import types - - -qApp = QtWidgets.QApplication.instance() - - -class LmbrQtEventLoop(asyncio.AbstractEventLoop): - def __init__(self): - self.running = False - self.shutdown = threading.Event() - self.blocked_events = set() - self.finished_events = set() - self.queue = [] - self._wait_future = None - self._event_loop_nesting = 0 - - def get_debug(self): - return False - - def time(self): - return azlmbr.qt_helpers.time() - - def wait_for_condition(self, condition, action, on_timeout=None, timeout=1.0): - timeout = self.time() + timeout if timeout is not None else None - def callback(time): - # Run our action and remove us from the queue if our condition is satisfied - if condition(): - action() - return True - # Give up if timeout has elapsed - if time > timeout: - if on_timeout is not None: - on_timeout() - return True - return False - self.queue.append((callback)) - - def event_loop(self): - time = self.time() - def run_event(event): - if event in self.blocked_events or event in self.finished_events: - return False - self.blocked_events.add(event) - try: - if event(time): - self.finished_events.add(event) - except Exception: - traceback.print_exc() - self.finished_events.add(event) - finally: - self.blocked_events.remove(event) - - self._event_loop_nesting += 1 - try: - for event in self.queue: - run_event(event) - finally: - self._event_loop_nesting -= 1 - - # Clear out any finished events if the queue is safe to mutate - if self._event_loop_nesting == 0: - self.queue = [event for event in self.queue if event not in self.finished_events] - self.finished_events = set() - - if not self.running or self._wait_future is not None and self._wait_future.done(): - self.close() - - def run_until_shutdown(self): - # Run our event loop callback (via azlmbr.qt_helpers) by pumping the Qt event loop - # azlmbr.qt_helpers will attempt to ensure our event loop is always run, even when a - # new event loop is started and run from the main event loop - self.running = True - self.shutdown.clear() - azlmbr.qt_helpers.set_loop_callback(self.event_loop) - while not self.shutdown.is_set(): - qApp.processEvents(QtCore.QEventLoop.AllEvents, 0) - - def run_forever(self): - self._wait_future = None - self.run_until_shutdown() - - def run_until_complete(self, future): - # Wrap coroutines into Tasks (future-like analogs) - if isinstance(future, types.CoroutineType): - future = self.create_task(future) - self._wait_future = future - self.run_until_shutdown() - - def _timer_handle_cancelled(self, handle): - pass - - def is_running(self): - return self.running - - def is_closed(self): - return not azlmbr.qt_helpers.loop_is_running() - - def stop(self): - self.running = False - - def close(self): - self.running = False - self.shutdown.set() - azlmbr.qt_helpers.clear_loop_callback() - - def shutdown_asyncgens(self): - pass - - def call_exception_handler(self, context): - try: - raise context.get('exception', None) - except: - traceback.print_exc() - - def call_soon(self, callback, *args, **kw): - h = asyncio.Handle(callback, args, self) - def callback_wrapper(time): - if not h.cancelled(): - h._run() - return True - self.queue.append(callback_wrapper) - return h - - def call_later(self, delay, callback, *args, **kw): - if delay < 0: - raise Exception("Can't schedule in the past") - return self.call_at(self.time() + delay, callback, *args) - - def call_at(self, when, callback, *args, **kw): - h = asyncio.TimerHandle(when, callback, args, self) - h._scheduled = True - def callback_wrapper(time): - if time > when: - if not h.cancelled(): - h._run() - return True - return False - self.queue.append(callback_wrapper) - return h - - def create_task(self, coro): - return asyncio.Task(coro, loop=self) - - def create_future(self): - return asyncio.Future(loop=self) - - -class EventLoopTimeoutException(Exception): - pass - - -event_loop = LmbrQtEventLoop() -def wait_for_condition(condition, timeout=1.0): - """ - Asynchronously waits for `condition` to evaluate to True. - condition: A function with the signature def condition() -> bool - This condition will be evaluated until it evaluates to True or the timeout elapses - timeout: The time in seconds to wait - if 0, this will wait forever - Throws pyside_utils.EventLoopTimeoutException on timeout. - """ - future = event_loop.create_future() - def on_complete(): - future.set_result(True) - def on_timeout(): - future.set_exception(EventLoopTimeoutException()) - event_loop.wait_for_condition(condition, on_complete, on_timeout=on_timeout, timeout=timeout) - return future - - -async def wait_for(expression, timeout=1.0): - """ - Asynchronously waits for "expression" to evaluate to a non-None value, - then returns that value. - expression: A function with the signature def expression() -> Generic[Any,None] - The result of expression will be returned as soon as it returns a non-None value. - timeout: The time in seconds to wait - if 0, this will wait forever - Throws pyside_utils.EventLoopTimeoutException on timeout. - """ - result = None - def condition(): - nonlocal result - result = expression() - return result is not None - await wait_for_condition(condition, timeout) - return result - - -def run_soon(fn): - """ - Runs a function on the event loop to enable asynchronous execution. - fn: The function to run, should be a function that takes no arguments - Returns a future that will be popualted with the result of fn or the exception it threw. - """ - future = event_loop.create_future() - def coroutine(): - try: - fn() - future.set_result(True) - except Exception as e: - future.set_exception(e) - event_loop.call_soon(coroutine) - return future - - -def run_async(awaitable): - """ - Synchronously runs a coroutine or a future on the event loop. - This can be used in lieu of "await" in non-async functions. - awaitable: The coroutine or future to await. - Returns the result of operation specified. - """ - if isinstance(awaitable, types.CoroutineType): - awaitable = event_loop.create_task(awaitable) - event_loop.run_until_complete(awaitable) - return awaitable.result() - - -def wrap_async(fn): - """ - This decorator enables an async function's execution from a synchronous one. - For example: - @pyside_utils.wrap_async - async def foo(): - result = await long_operation() - return result - def non_async_fn(): - x = foo() # this will return the correct result by executing the event loop - fn: The function to wrap - Returns the decorated function. - """ - def wrapper(*args, **kw): - result = fn(*args, **kw) - return run_async(result) - return wrapper - - -def get_editor_main_window(): - """ - Fetches the main Editor instance of QMainWindow for use with PySide tests - :return Instance of QMainWindow for the Editor - """ - params = azlmbr.qt.QtForPythonRequestBus(azlmbr.bus.Broadcast, "GetQtBootstrapParameters") - editor_id = QtWidgets.QWidget.find(params.mainWindowId) - main_window = wrapInstance(int(getCppPointer(editor_id)[0]), QtWidgets.QMainWindow) - return main_window - - -def get_action_for_menu_path(editor_window: QtWidgets.QMainWindow, main_menu_item: str, *menu_item_path: str): - """ - main_menu_item: Main menu item among the MenuBar actions. Ex: "File" - menu_item_path: Path to any nested menu item. Ex: "Viewport", "Goto Coordinates" - returns: QAction object for the corresponding path. - """ - # Check if path is valid - menu_bar = editor_window.menuBar() - menu_bar_actions = [index.iconText() for index in menu_bar.actions()] - - # Verify if the given Menu exists in the Menubar - if main_menu_item not in menu_bar_actions: - print(f"QAction not found for main menu item '{main_menu_item}'") - return None - curr_action = menu_bar.actions()[menu_bar_actions.index(main_menu_item)] - curr_menu = curr_action.menu() - for index, element in enumerate(menu_item_path): - curr_menu_actions = [index.iconText() for index in curr_menu.actions()] - if element not in curr_menu_actions: - print(f"QAction not found for menu item '{element}'") - return None - if index == len(menu_item_path) - 1: - return curr_menu.actions()[curr_menu_actions.index(element)] - curr_action = curr_menu.actions()[curr_menu_actions.index(element)] - curr_menu = curr_action.menu() - return None - - -def _pattern_to_dict(pattern, **kw): - """ - Helper function, turns a pattern match parameter into a normalized dictionary - """ - - def is_string_or_regex(x): - return isinstance(x, str) or isinstance(x, re.Pattern) - - # If it's None, just make an empty dict - if pattern is None: - pattern = {} - # If our pattern is a string or regex, turn it into a text match - elif is_string_or_regex(pattern): - pattern = dict(text=pattern) - # If our pattern is an (int, int) tuple, turn it into a row/column match - elif isinstance(pattern, tuple) and isinstance(pattern[0], int) and isinstance(pattern[1], int): - pattern = dict(row=pattern[0], column=pattern[1]) - # If our pattern is a QObject type, turn it into a type match - elif isinstance(pattern, type(QtCore.QObject)): - pattern = dict(type=pattern) - # Otherwise assume it's a dict and make a copy - else: - pattern = dict(pattern) - - # Merge with any kw arguments - for key, value in kw.items(): - pattern[key] = value - return pattern - - -def _match_pattern(obj, pattern): - """ - Helper function, determines whether obj matches the pattern specified by pattern. - It is required that pattern is normalized into a dict before calling this. - """ - - def compare(value1, value2): - # Do a regex search if it's a regex, otherwise do a normal compare - if isinstance(value2, re.Pattern): - return re.search(value2, value1) - return value1 == value2 - - item_roles = Qt.ItemDataRole.values.values() - for key, value in pattern.items(): - if key == "type": # Class type - if not isinstance(obj, value): - return False - elif key == "text": # Default 'text' path, depends on type - text_values = [] - - def get_from_attrs(*args): - for attr in args: - try: - text_values.append(getattr(obj, attr)()) - except Exception: - pass - - # Use any of the following fields for default matching, if they're defined - get_from_attrs("text", "objectName", "windowTitle") - # Additionally, use the DisplayRole for QModelIndexes - if isinstance(obj, QtCore.QModelIndex): - text_values.append(obj.data(Qt.DisplayRole)) - - if not any(compare(text, value) for text in text_values): - return False - elif key in item_roles: # QAbstractItemModel display role - if not isinstance(obj, QtCore.QModelIndex): - raise RuntimeError(f"Attempted to match data role on unsupported object {obj}") - if not compare(obj.data(key), value): - return False - elif hasattr(obj, key): - # Look up our key on the object itself - objectValue = getattr(obj, key) - # Invoke it if it's a getter - if callable(objectValue): - objectValue = objectValue() - if not compare(objectValue, value): - return False - else: - return False - - return True - - -def get_child_indexes(model, parent_index=QtCore.QModelIndex()): - indexes = [parent_index] - while len(indexes) > 0: - parent_index = indexes.pop(0) - for row in range(model.rowCount(parent_index)): - # FIXME - # PySide appears to have a bug where-in it thinks columnCount is private - # Bail gracefully for now, we can add a C++ wrapper to work around if needed - try: - column_count = model.columnCount(parent_index) - except Exception: - column_count = 1 - for col in range(column_count): - cur_index = model.index(row, col, parent_index) - yield cur_index - - -def _get_children(obj): - """ - Helper function. Get the direct descendants from a given PySide object. - This includes all: QObject children, QActions owned by the object, and QModelIndexes if applicable - """ - if isinstance(obj, QtCore.QObject): - yield from obj.children() - if isinstance(obj, QtWidgets.QWidget): - yield from obj.actions() - if isinstance(obj, (QtWidgets.QAbstractItemView, QtCore.QModelIndex)): - model = obj.model() - if model is None: - return - - # For a QAbstractItemView (e.g. QTreeView, QListView), the parent index - # will be an invalid QModelIndex(), which will use find all indexes on the root. - # For a QModelIndex, we use the actual QModelIndex as the parent_index so that - # it will find any child indexes under it - parent_index = QtCore.QModelIndex() - if isinstance(obj, QtCore.QModelIndex): - parent_index = obj - - yield from get_child_indexes(model, parent_index) - - -def _get_parents_to_search(obj_entry_or_list): - """ - Helper function, turns obj_entry_or_list into a list of parents to search - If obj_entry_or_list is None, returns all visible top level widgets - If obj_entry_or_list is iterable, return it as a list - Otherwise, return a list containing obj_entry_or_list - """ - if obj_entry_or_list is None: - return [widget for widget in QtWidgets.QApplication.topLevelWidgets() if widget.isVisible()] - try: - return list(obj_entry_or_list) - except TypeError: - return [obj_entry_or_list] - - -def find_children_by_pattern(obj=None, pattern=None, recursive=True, **kw): - """ - Finds the children of an object that match a given pattern. - See find_child_by_pattern for more information on usage. - """ - pattern = _pattern_to_dict(pattern, **kw) - parents_to_search = _get_parents_to_search(obj) - - while len(parents_to_search) > 0: - parent = parents_to_search.pop(0) - for child in _get_children(parent): - if _match_pattern(child, pattern): - yield child - if recursive: - parents_to_search.append(child) - - -def find_child_by_pattern(obj=None, pattern=None, recursive=True, **kw): - """ - Finds the child of an object that matches a given pattern. - A "child" in this context is not necessarily a QObject child. - QActions are also considered children, as are the QModelIndex children of QAbstractItemViews. - obj: The object to search - should be either a QObject or a QModelIndex, or a list of them - If None this will search all top level windows. - pattern: The pattern to match, the first child that matches all of the criteria specified will - be returned. This is a dictionary with any combination of the following: - - "text": generic text to match, will search object names for QObjects, display role text - for QModelIndexes, or action text() for QActions - - "type": a class type, e.g. QtWidgets.QMenu, a child will only match if it's of this type - - "row" / "column": integer row and column indices of a QModelIndex - - "type": type class (e.g. PySide.QtWidgets.QComboBox) that the object must inherit from - - A Qt.ItemDataRole: matches for QModelIndexes with data of a given value - - Any other fields will fall back on being looked up on the object itself by name, e.g. - {"windowTitle": "Foo"} would match a windowTitle named "Foo" - Any instances where a field is specified as text can also be specified as a regular expression: - find_child_by_pattern(obj, {text: re.compile("Foo_.*")}) would find a child with text starting - with "Foo_" - For convenience, these parameter types may also be specified as keyword arguments: - find_child_by_pattern(obj, text="foo", type=QtWidgets.QAction) - is equivalent to - find_child_by_pattern(obj, {"text": "foo", "type": QtWidgets.QAction}) - If pattern is specified as a string, it will turn into a pattern matching "text": - find_child_by_pattern(obj, "foo") - is equivalent to - find_child_by_pattern(obj, {"text": "foo"}) - If a pattern is specified as an (int, int) tuple, it will turn into a row/column match: - find_child_by_pattern(obj, (0, 2)) - is equivalent to - find_child_by_pattern(obj, {"row": 0, "column": 2}) - If a pattern is specified as a type, like PySide.QtWidgets.QLabel, it will turn into a type match: - find_child_by_pattern(obj, PySide.QtWidgets.QLabel) - is equivalent to - find_child_by_pattern(obj, {"type": PySide.QtWidgets.QLabel}) - """ - # Return the first match result, if found - for match in find_children_by_pattern(obj, pattern=pattern, recursive=recursive, **kw): - return match - return None - - -def find_child_by_hierarchy(parent, *patterns): - """ - Searches for a hierarchy of children descending from parent. - parent: The Qt object (or list of Qt obejcts) to search within - If none, this will search all top level windows. - patterns: A list of patterns to match to find a hierarchy of descendants. - These patterns will be tested in order. - For example, to look for the QComboBox in a hierarchy like the following: - QWidget (window) - -QTabWidget - -QWidget named "m_exampleTab" - -QComboBox - One might invoke: - find_child_by_hierarchy(window, QtWidgets.QTabWidget, "m_exampleTab", QtWidgets.QComboBox) - Alternatively, "..." may be specified in place of a parent, where the hierarchy will match any - ancestors along the path, so the above might be shortened to: - find_child_by_hierarchy(window, ..., "m_exampleTab", QtWidgets.QComboBox) - """ - search_recursively = False - current_objects = _get_parents_to_search(parent) - for pattern in patterns: - # If it's an ellipsis, do the next search recursively as we're looking for any number of intermediate ancestors - if pattern is ...: - search_recursively = True - continue - - candidates = [] - for parent_candidate in current_objects: - candidates += find_children_by_pattern(parent_candidate, pattern=pattern, recursive=search_recursively) - if len(candidates) == 0: - return None - current_objects = candidates - - search_recursively = False - return current_objects[0] - -async def wait_for_child_by_hierarchy(parent, *patterns, timeout=1.0): - """ - Searches for a hierarchy of children descending from parent until timeout occurs. - Returns a future that will result in either the found child or an EventLoopTimeoutException. - See find_child_by_hierarchy for usage information. - """ - match = None - def condition(): - nonlocal match - match = find_child_by_hierarchy(parent, *patterns) - return match is not None - await wait_for_condition(condition, timeout) - return match - - -async def wait_for_child_by_pattern(obj=None, pattern=None, recursive=True, timeout=1.0, **kw): - """ - Finds the child of an object that matches a given pattern. - Returns a future that will result in either the found child or an EventLoopTimeoutException. - See find_child_by_hierarchy for usage information. - """ - match = None - def condition(): - nonlocal match - match = find_child_by_pattern(obj, pattern, recursive, **kw) - return match is not None - await wait_for_condition(condition, timeout) - return match - - -def find_child_by_property(obj, obj_type, property_name, property_value, reg_exp_search=False): - """ - Finds the child of an object which has the property name matching the property value - of type obj_type - obj: The property value is searched through obj children - obj_type: Type of object to be matched - property_name: Property of the child which should be verified for the required value. - property_value: Property value that needs to be matched - reg_exp_search: If True searches for the property_value based on re search. Defaults to False. - """ - for child in obj.children(): - if reg_exp_search and re.search(property_value, getattr(child, property_name)()): - return child - if not reg_exp_search and isinstance(child, obj_type) and getattr(child, property_name)() == property_value: - return child - return None - -def get_item_view_index(item_view, row, column=0, parent=QtCore.QModelIndex()): - """ - Retrieve the index for a specified row/column, with optional parent - This is necessary when needing to reference into nested hierarchies in a QTreeView - item_view: The QAbstractItemView instance - row: The requested row index - column: The requested column index (defaults to 0 in case of single column) - parent: Parent index (defaults to invalid) - """ - item_model = item_view.model() - model_index = item_model.index(row, column, parent) - return model_index - - -def get_item_view_index_rect(item_view, index): - """ - Gets the QRect for a given index in a QAbstractItemView (e.g. QTreeView, QTableView, QListView). - This is helpful because for sending mouse events to a QAbstractItemView, you have to send them to - the viewport() widget of the QAbstractItemView. - item_view: The QAbstractItemView instance - index: A QModelIndex for the item index - """ - return item_view.visualRect(index) - - -def item_view_index_mouse_click(item_view, index, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): - """ - Helper method version of QTest.mouseClick for injecting mouse clicks on a QAbstractItemView - item_view: The QAbstractItemView instance - index: A QModelIndex for the item index to be clicked - """ - item_index_rect = get_item_view_index_rect(item_view, index) - item_index_center = item_index_rect.center() - - # For QAbstractItemView widgets, the events need to be forwarded to the actual viewport() widget - QTest.mouseClick(item_view.viewport(), button, modifier, item_index_center) - - -def item_view_mouse_click(item_view, row, column=0, button=QtCore.Qt.LeftButton, modifier=QtCore.Qt.NoModifier): - """ - Helper method version of 'item_view_index_mouse_click' using a row, column instead of a QModelIndex - item_view: The QAbstractItemView instance - row: The requested row index - column: The requested column index (defaults to 0 in case of single column) - """ - index = get_item_view_index(item_view, row, column) - item_view_index_mouse_click(item_view, index, button, modifier) - - -async def wait_for_action_in_menu(menu, pattern, timeout=1.0): - """ - Finds a QAction inside a menu, based on the specified pattern. - menu: The QMenu to search - pattern: The action text or pattern to match (see find_child_by_pattern) - If pattern specifies a QWidget, this will search for the associated QWidgetAction - """ - action = await wait_for_child_by_pattern(menu, pattern, timeout=timeout) - if action is None: - raise TimeoutError(f"Failed to find context menu action for {pattern}") - - # If we've found a valid QAction, we're good to go - if hasattr(action, 'trigger'): - return action - - # If pattern matches a widget and not a QAction, look for an associated QWidgetAction - widget_actions = find_children_by_pattern(menu, type=QtWidgets.QWidgetAction) - underlying_widget_action = None - for widget_action in widget_actions: - widgets_to_check = [widget_action.defaultWidget()] + widget_action.createdWidgets() - for check_widget in widgets_to_check: - if action in _get_children(check_widget): - underlying_widget_action = widget_action - break - if underlying_widget_action is not None: - action = underlying_widget_action - break - - if not hasattr(action, 'trigger'): - raise RuntimeError(f"Failed to find action associated with widget {action}") - return action - - -def queue_hide_event(widget): - """ - Explicitly post a hide event for the next frame, this can be used to ensure modal dialogs exit correctly. - widget: The widget to hide - """ - qApp.postEvent(widget, QtGui.QHideEvent()) - - -async def wait_for_destroyed(obj, timeout=1.0): - """ - Waits for a QObject (including a widget) to be fully destroyed - This can be used to wait for a modal dialog to shut down properly - obj: The object to wait on destruction - timeout: The time, in seconds to wait. 0 for an indefinite wait. - """ - was_destroyed = False - def on_destroyed(): - nonlocal was_destroyed - was_destroyed = True - obj.destroyed.connect(on_destroyed) - return await wait_for_condition(lambda: was_destroyed, timeout=timeout) - - -async def close_modal(modal_widget, timeout=1.0): - """ - Closes a modal dialog and waits for it to be cleaned up. - This attempts to ensure the modal event loop gets properly exited. - modal_widget: The widget to close - timeout: The time, in seconds, to wait. 0 for an indefinite wait. - """ - queue_hide_event(modal_widget) - return await wait_for_destroyed(modal_widget, timeout=timeout) - - -def trigger_context_menu_entry(widget, pattern, pos=None, index=None): - """ - Trigger a context menu event on a widget and activate an entry - widget: The widget to trigger the event on - pattern: The action text or pattern to match (see find_child_by_pattern) - pos: Optional, the QPoint to set as the event origin - index: Optional, the QModelIndex to click in widget - widget must be a QAbstractItemView - """ - async def async_wrapper(): - menu = await open_context_menu(widget, pos=pos, index=index) - action = await wait_for_action_in_menu(menu, pattern) - action.trigger() - queue_hide_event(menu) - - result = async_wrapper() - # If we have an event loop, go ahead and just return the coroutine - # Otherwise, do a synchronous wait - if event_loop.is_running(): - return result - else: - return run_async(result) - - -async def open_context_menu(widget, pos=None, index=None, timeout=1.0): - """ - Trigger a context menu event on a widget - widget: The widget to trigger the event on - pos: Optional, the QPoint to set as the event origin - index: Optional, the QModelIndex to click in widget - widget must be a QAbstractItemView - Returns the menu that was created. - """ - if index is not None: - if pos is not None: - raise RuntimeError("Error: 'index' and 'pos' are mutually exclusive") - pos = widget.visualRect(index).center() - parent = widget - widget = widget.viewport() - pos = widget.mapFrom(parent, pos) - if pos is None: - pos = widget.rect().center() - - # Post both a mouse event and a context menu to let the widget handle whichever is appropriate - qApp.postEvent(widget, QtGui.QContextMenuEvent(QtGui.QContextMenuEvent.Mouse, pos)) - QtTest.QTest.mouseClick(widget, Qt.RightButton, Qt.NoModifier, pos) - - menu = None - # Wait for a menu popup - def menu_has_focus(): - nonlocal menu - for fw in [QtWidgets.QApplication.activePopupWidget(), QtWidgets.QApplication.activeModalWidget(), - QtWidgets.QApplication.focusWidget(), QtWidgets.QApplication.activeWindow()]: - if fw and isinstance(fw, QtWidgets.QMenu) and fw.isVisible(): - menu = fw - return True - return False - await wait_for_condition(menu_has_focus, timeout) - return menu - - -def move_mouse(widget, position): - """ - Helper method to move the mouse to a specified position on a widget - widget: The widget to trigger the event on - position: The QPoint (local to widget) to move the mouse to - """ - # For some reason, Qt wouldn't register the mouse movement correctly unless both of these ways are invoked. - # The QTest.mouseMove seems to update the global cursor position, but doesn't always result in the MouseMove event being - # triggered, which prevents drag/drop being able to be simulated. - # Similarly, if only the MouseMove event is sent by itself to the core application, the global cursor position wasn't - # updated properly, so drag/drop logic that depends on grabbing the globalPos didn't work. - QtTest.QTest.mouseMove(widget, position) - event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, position, widget.mapToGlobal(position), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier) - QtCore.QCoreApplication.sendEvent(widget, event) - - -def drag_and_drop(source, target, source_point = QtCore.QPoint(), target_point = QtCore.QPoint()): - """ - Simulate a drag/drop event from a source object to a specified target - This has special case handling if the source is a QDockWidget (for docking) vs normal drag/drop - source: The source object to initiate the drag from - This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dragging an item view item - target: The target object to drop on after dragging - This is either a QWidget, or a tuple of (QAbstractItemView, QModelIndex) for dropping on an item view item - source_point: Optional, The QPoint to initiate the drag from. If none is specified, the center of the source will be used. - target_point: Optional, The QPoint to drop on. If none is specified, the center of the target will be used. - """ - # Flag if this drag/drop is for docking, which has some special cases - docking = False - - # If the source is a tuple of (QAbstractItemView, QModelIndex), we need to use the - # viewport() as the source, and find the location of the index - if isinstance(source, tuple) and len(source) == 2: - source_item_view = source[0] - source_widget = source_item_view.viewport() - source_model_index = source[1] - source_rect = source_item_view.visualRect(source_model_index) - else: - # There are some special case actions if we are doing this drag for docking, - # so figure this out by checking if the source is a QDockWidget - if isinstance(source, QtWidgets.QDockWidget): - docking = True - - source_widget = source - source_rect = source.rect() - - # If the target is a tuple of (QAbstractItemView, QModelIndex), we need to use the - # viewport() as the target, and find the location of the index - if isinstance(target, tuple) and len(target) == 2: - target_item_view = target[0] - target_widget = target_item_view.viewport() - target_model_index = target[1] - target_rect = target_item_view.visualRect(target_model_index) - else: - # If we are doing a drag for docking, we actually want all the mouse events - # to still be directed through the source widget - if docking: - target_widget = source_widget - else: - target_widget = target - target_rect = target.rect() - - # If no source_point is specified, we need to find the center point of - # the source widget - if source_point.isNull(): - # If we are dragging for docking, initiate the drag from the center of the - # dock widget title bar - if docking: - title_bar_widget = source.titleBarWidget() - if title_bar_widget: - source_point = title_bar_widget.geometry().center() - else: - raise RuntimeError("No titleBarWidget found for QDockWidget") - # Otherwise, can just find the center of the rect - else: - source_point = source_rect.center() - - # If no target_point was specified, we need to find the center point of the target widget - if target_point.isNull(): - target_point = target_rect.center() - - # If we are dragging for docking and we aren't dragging within the same source/target, - # the mouse movements need to be directed to the source_widget, so we need to use the - # difference in global positions of our source and target widgets to adjust the target_point - # to be relative to the source - if docking and source != target: - source_top_left = source.mapToGlobal(QtCore.QPoint(0, 0)) - target_top_left = target.mapToGlobal(QtCore.QPoint(0, 0)) - offset = target_top_left - source_top_left - target_point += offset - - # Move the mouse to the source spot where we will start the drag - move_mouse(source_widget, source_point) - - # Press the left-mouse button to begin the drag - QtTest.QTest.mousePress(source_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, source_point) - - # If we are dragging for docking, we first need to drag the mouse past the minimum distance to - # trigger the docking system properly - if docking: - drag_distance = QtWidgets.QApplication.startDragDistance() + 1 - docking_trigger_point = source_point + QtCore.QPoint(drag_distance, drag_distance) - move_mouse(source_widget, docking_trigger_point) - - # Drag the mouse to the target widget over the desired point - move_mouse(target_widget, target_point) - - # Release the left-mouse button to complete the drop. - # If we are docking, we need to delay the actual mouse button release because the docking system has - # a delay before the drop zone becomes active after it has been hovered, which can be found here: - # FancyDockingDropZoneConstants::dockingTargetDelayMS = 110 ms - # So we need to delay greater than dockingTargetDelayMS after the final mouse move - # over the intended target. - delay = -1 - if docking: - delay = 200 - QtTest.QTest.mouseRelease(target_widget, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier, target_point, delay) - - # Some drag/drop events have extra processing on the following event tick, so let those processEvents - # first before we complete the drag/drop operation - QtWidgets.QApplication.processEvents() - - -def trigger_action_async(action): - """ - Convenience function. Triggers an action asynchronously. - This can be used if calling action.trigger might block (e.g. if it opens a modal dialog) - action: The action to trigger - """ - return run_soon(lambda: action.trigger()) - - -def click_button_async(button): - """ - Convenience function. Clicks a button asynchronously. - This can be used if calling button.click might block (e.g. if it opens a modal dialog) - button: The button to click - """ - return run_soon(lambda: button.click()) - - -async def wait_for_modal_widget(timeout=1.0): - """ - Waits for an active modal widget and returns it. - """ - return await wait_for(lambda: QtWidgets.QApplication.activeModalWidget(), timeout=timeout) - -async def wait_for_popup_widget(timeout=1.0): - """ - Waits for an active popup widget and returns it. - """ - return await wait_for(lambda: QtWidgets.QApplication.activePopupWidget(), timeout=timeout) \ No newline at end of file diff --git a/Editor/Scripts/tutorial_for_scripts.py b/Editor/Scripts/tutorial_for_scripts.py index 506e80b..49a2db3 100644 --- a/Editor/Scripts/tutorial_for_scripts.py +++ b/Editor/Scripts/tutorial_for_scripts.py @@ -4,13 +4,14 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ -import os +import os, sys import azlmbr.bus as bus # type: ignore +import azlmbr.entity as entity # type: ignore import azlmbr.editor as editor # type: ignore -import azlmbr.entity # type: ignore from azlmbr.entity import EntityId # type: ignore import azlmbr.paths as paths from tutorial import Tutorial, TutorialStep # type: ignore +import editor_python_test_tools.pyside_utils as pyside_utils class TutorialForScripts(Tutorial): FILE_PATH = os.path.join(paths.projectroot, "TestAssets", "test_file.scriptevents") @@ -28,16 +29,19 @@ def __init__(self): def on_step_start(self): print("Starting the select Entity step") - newEntity = TutorialForScripts.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH) + rootEntityId = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + #editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', rootEntityId) + + # editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', newEntityId, meshComponentTypeId) + # editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH) # SCRIPTS I HAVE TRIED # self.generate_variable_test_output(self, "create") # get_action_for_menu_path # _get_children --> the direct descendants from a given PySide object. def on_tutorial_start(self): - print("Starting Mesh Asset Process tutorial.") + print("Starting tutorial for scripts tutorial.") def on_tutorial_end(self): - print("Mesh Asset Process tutorial complete!") + print("Tutorial for scripts complete!") From e02b951adf60d84bc530b1b366cc0414e0b4a2b2 Mon Sep 17 00:00:00 2001 From: Song Date: Wed, 6 Jul 2022 19:53:28 -0700 Subject: [PATCH 05/16] delete shader ball and add prefab Signed-off-by: Song --- Editor/Scripts/interactivetutorials_dialog.py | 5 ---- Editor/Scripts/rigid_body_tutorial.py | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index 1e60673..1033fe4 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -23,7 +23,6 @@ from demo_tutorial import DemoTutorial, IntroTutorial from rigid_body_tutorial import RigidBodyTutorial -from tutorial_for_scripts import TutorialForScripts from tutorial import Tutorial class HighlightWidget(QWidget): @@ -92,10 +91,6 @@ def __init__(self, parent=None): { "name": "PhysX Rigid Bodies", "tutorial": RigidBodyTutorial - }, - { - "name": "Scripts", - "tutorial": TutorialForScripts } ] tutorial_names = [tutorial['name'] for tutorial in self.tutorials] diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index 544e45e..f4221b8 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -4,9 +4,19 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ - -import sys +import os, sys +sys.path.append(os.path.dirname(__file__)) import azlmbr +import azlmbr.bus as bus +import azlmbr.editor as editor +import azlmbr.legacy.general as general +import azlmbr.entity as entity +import azlmbr.math as math +import azlmbr.prefab as prefab + +# import editor_python_test_tools.pyside_utils as pyside_utils +# from Editor_TestClass import BaseClass + from PySide2.QtWidgets import QMenuBar @@ -141,8 +151,24 @@ def __init__(self): fly in the air an spin, then fall and collide with the ground plane. Press ESC to exit.

    """)) + + def on_tutorial_start(self): print("Starting PhysX Rigid Body tutorial.") + #Delete the Shader Ball entity + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + entityId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', entityId) + # Instantiate prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(64.0, 64.0, 32.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.join("Assets", "Prefabs", "BushFlowerBlender.prefab") + prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, + entity.EntityId(), position) def on_tutorial_end(self): print("PhysX Rigid Body tutorial complete!") \ No newline at end of file From 4439940b782fee185ae1168da7d8d81fd29ee53b Mon Sep 17 00:00:00 2001 From: Song Date: Thu, 7 Jul 2022 08:46:00 -0700 Subject: [PATCH 06/16] Positioned dice prefab Signed-off-by: Song --- Editor/Scripts/rigid_body_tutorial.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index f4221b8..fd3f4fb 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -4,8 +4,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ -import os, sys -sys.path.append(os.path.dirname(__file__)) +import os +#import sys +#sys.path.append(os.path.dirname(__file__)) import azlmbr import azlmbr.bus as bus import azlmbr.editor as editor @@ -164,11 +165,17 @@ def on_tutorial_start(self): editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', entityId) # Instantiate prefab transform = math.Transform_CreateIdentity() - position = math.Vector3(64.0, 64.0, 32.0) + position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) - test_prefab_path = os.path.join("Assets", "Prefabs", "BushFlowerBlender.prefab") - prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, + #test_prefab_path = os.path.join("Assets", "20-sided-dice.prefab") + #test_prefab_path = open(r'C:\Users\jmadeson\InteractiveTutorials\Assets\20-sided-dice\20-sided-dice.prefab') + #test_prefab_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '20-sided-dice', '20-sided-dice.prefab')) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + # Add PhysX Collider + + # Add PhysX Rigid Body def on_tutorial_end(self): print("PhysX Rigid Body tutorial complete!") \ No newline at end of file From 51d53501c2a456fb288ae0555e75012d14d16f2e Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 11 Jul 2022 09:45:51 -0700 Subject: [PATCH 07/16] simulate step button Signed-off-by: Song --- Editor/Scripts/interactivetutorials_dialog.py | 19 +++++- Editor/Scripts/rigid_body_tutorial.py | 64 ++++++++++++++----- Editor/Scripts/tutorial.py | 9 ++- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index 1033fe4..05d9635 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -136,6 +136,13 @@ def __init__(self, parent=None): self.button_box.addButton(self.back_button, QDialogButtonBox.ResetRole) self.tutorial_layout.addWidget(self.button_box) + self.button_box = QDialogButtonBox(self) + self.simulate_button = QPushButton("Simulate This Step", self) + self.simulate_button.setDefault(True) + self.simulate_button.clicked.connect(self.simulate_step) + self.button_box.addButton(self.simulate_button, QDialogButtonBox.ActionRole) + self.tutorial_layout.addWidget(self.button_box) + self.tutorial_widget.setLayout(self.tutorial_layout) self.stacked_widget.addWidget(self.tutorial_widget) @@ -162,8 +169,7 @@ def load_tutorial(self, index): self.current_step_index = 0 self.current_step = None first_step = self.current_tutorial.get_first_step() - self.load_step(first_step) - + self.load_step(first_step) def end_tutorial(self): if not self.current_step: @@ -236,12 +242,21 @@ def load_next_step(self): def load_previous_step(self): if self.current_step: + self.current_step_index -= 1 prev_step = self.current_step.prev_step if prev_step: self.current_step_index -= 1 self.load_step(prev_step) + def simulate_step(self): + print("Entered simulate step") + if self.tutorial_list.currentIndex().row() == 3: + print("Rigid Body Tutorial identified") + RigidBodyTutorial.set_step_number(self, self.current_step_index) + RigidBodyTutorial.set_simulate_on(self) + RigidBodyTutorial.simulate(self) + def on_start_button_clicked(self): tutorial_index = self.tutorial_list.currentIndex().row() diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index fd3f4fb..d698a72 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -14,13 +14,10 @@ import azlmbr.entity as entity import azlmbr.math as math import azlmbr.prefab as prefab - -# import editor_python_test_tools.pyside_utils as pyside_utils -# from Editor_TestClass import BaseClass - +from azlmbr.entity import EntityId from PySide2.QtWidgets import QMenuBar - +from PySide2.QtWidgets import QDialog from tutorial import Tutorial, TutorialStep class RigidBodyTutorial(Tutorial): @@ -28,6 +25,8 @@ def __init__(self): super(RigidBodyTutorial, self).__init__() self.title = "PhysX Rigid Bodies" + self.current_step_index = 0 + self.simulate_clicked = False self.add_step(TutorialStep("Dynamic Simulation with PhysX Rigid Bodies", """

    Greetings!

    Rigid bodies are dynamic solid objects that @@ -41,7 +40,7 @@ def __init__(self): all the entities in the level. Each entity has a collection of components that provide some functionality. The Sun entity, for example, has a Directional Light component the simulates a very bright distant light with parallel rays. It also has a Transform component that - places it in the level in realtion to it's parent, the Atom Default Environment entity. + places it in the level in relation to it's parent, the Atom Default Environment entity.

    """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Delete the Shader Ball entity", """

    Let's tidy up a bit.

    In Entity Outliner, click the Shader Ball entity to select it and press @@ -156,6 +155,9 @@ def __init__(self): def on_tutorial_start(self): print("Starting PhysX Rigid Body tutorial.") + + + def step_one(self): #Delete the Shader Ball entity editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -163,19 +165,51 @@ def on_tutorial_start(self): searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) entityId = searchResult[0] editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', entityId) - # Instantiate prefab + + def step_two(self): + # Instantiate and position the prefab transform = math.Transform_CreateIdentity() position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) - #test_prefab_path = os.path.join("Assets", "20-sided-dice.prefab") - #test_prefab_path = open(r'C:\Users\jmadeson\InteractiveTutorials\Assets\20-sided-dice\20-sided-dice.prefab') - #test_prefab_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '20-sided-dice', '20-sided-dice.prefab')) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, - entity.EntityId(), position) - # Add PhysX Collider - - # Add PhysX Rigid Body + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + def step_three(self): + # Find created dice entity + search_filter = entity.SearchFilter() + search_filter.names = ["20-sided-dice"] + diceEntity = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)[0] + + #Add PhysX Collider + #Get Component Type for PhysX Collider + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game + )[0] + #newEntityId = + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', EntityId, [colliderComponentTypeId]) + + def set_step_number(self, x): + self.current_step_index = x + + def set_simulate_on(self): + """ simulate button has been clicked """ + self.simulate_clicked = True + + def set_simulate_off(self): + self.simulate_clicked = False + + def simulate(self): + #if simulate_clicked is true + step_count = self.current_step_index + # for loop with steps + # for int i = 1 i is num tutorial steps i ++ + #list_of_steps = ['step_one(self)', 'step_two(self)', 'step_three(self)'] + list_of_steps = ['print("Hello")', 'print("Now")', 'print("Goodbye")'] + for step in list_of_steps: + eval(step) + print("Reached Eval Step") + if step == step_count: + break def on_tutorial_end(self): print("PhysX Rigid Body tutorial complete!") \ No newline at end of file diff --git a/Editor/Scripts/tutorial.py b/Editor/Scripts/tutorial.py index 6aa55bd..30d0ebf 100644 --- a/Editor/Scripts/tutorial.py +++ b/Editor/Scripts/tutorial.py @@ -22,6 +22,8 @@ def __init__(self, title, content, highlight_pattern=None): self.prev_step = None self.next_step = None + self.current_step_index = 0 + self.simulate_clicked = False # Method that will be called when the step starts # A step class can override this method if they need @@ -36,6 +38,9 @@ def on_step_start(self): def on_step_end(self): pass + def simulate(self): + pass + def get_title(self): return self.title @@ -43,13 +48,15 @@ def get_content(self): return self.content def get_highlight_pattern(self): - return self.highlight_pattern + return self.highlight_pattern # Top-level entry point for describing a tutorial which is made up of a series of steps class Tutorial: def __init__(self): self.steps = [] self.title = "" + self.current_step_index = 0 + self.simulate_clicked = False @classmethod def create_from_json_file(cls, file_path): From 329181226a2d002e6f485778f251ea265deffb93 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 11 Jul 2022 12:39:49 -0700 Subject: [PATCH 08/16] simulator with edits. no component added Signed-off-by: Song --- Editor/Scripts/interactivetutorials_dialog.py | 4 +- Editor/Scripts/rigid_body_tutorial.py | 58 +++++++++---------- Editor/Scripts/tutorial.py | 4 +- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index 05d9635..a16bbb3 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -250,9 +250,9 @@ def load_previous_step(self): def simulate_step(self): - print("Entered simulate step") + #print("Entered simulate step") if self.tutorial_list.currentIndex().row() == 3: - print("Rigid Body Tutorial identified") + #print("Rigid Body Tutorial identified") RigidBodyTutorial.set_step_number(self, self.current_step_index) RigidBodyTutorial.set_simulate_on(self) RigidBodyTutorial.simulate(self) diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index d698a72..7952c41 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -157,8 +157,34 @@ def on_tutorial_start(self): print("Starting PhysX Rigid Body tutorial.") - def step_one(self): + def set_step_number(self, x): + self.current_step_index = x + + def set_simulate_on(self): + """ simulate button has been clicked """ + self.simulate_clicked = True + + def set_simulate_off(self): + self.simulate_clicked = False + + def on_tutorial_end(self): + print("PhysX Rigid Body tutorial complete!") + + def simulate(self): + #if simulate_clicked is true + step_count = self.current_step_index + # for loop with steps + # for int i = 1 i is num tutorial steps i ++ + #list_of_steps = ['step_one(self)', 'step_two(self)', 'step_three(self)'] + #list_of_steps = ['print("Hello")', 'print("Now")', 'print("Goodbye")'] + + #for step in list_of_steps: + # eval(step) + # print("Reached Eval Step") + # if step == step_count: + # break #Delete the Shader Ball entity + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() searchFilter.names = ['Shader Ball'] @@ -166,7 +192,6 @@ def step_one(self): entityId = searchResult[0] editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', entityId) - def step_two(self): # Instantiate and position the prefab transform = math.Transform_CreateIdentity() position = math.Vector3(0.0, 0.0, 5.0) @@ -174,7 +199,6 @@ def step_two(self): test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - def step_three(self): # Find created dice entity search_filter = entity.SearchFilter() search_filter.names = ["20-sided-dice"] @@ -186,30 +210,4 @@ def step_three(self): )[0] #newEntityId = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', EntityId, [colliderComponentTypeId]) - - def set_step_number(self, x): - self.current_step_index = x - - def set_simulate_on(self): - """ simulate button has been clicked """ - self.simulate_clicked = True - - def set_simulate_off(self): - self.simulate_clicked = False - - def simulate(self): - #if simulate_clicked is true - step_count = self.current_step_index - # for loop with steps - # for int i = 1 i is num tutorial steps i ++ - #list_of_steps = ['step_one(self)', 'step_two(self)', 'step_three(self)'] - list_of_steps = ['print("Hello")', 'print("Now")', 'print("Goodbye")'] - for step in list_of_steps: - eval(step) - print("Reached Eval Step") - if step == step_count: - break - - def on_tutorial_end(self): - print("PhysX Rigid Body tutorial complete!") \ No newline at end of file + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', EntityId, [colliderComponentTypeId]) \ No newline at end of file diff --git a/Editor/Scripts/tutorial.py b/Editor/Scripts/tutorial.py index 30d0ebf..2ba7201 100644 --- a/Editor/Scripts/tutorial.py +++ b/Editor/Scripts/tutorial.py @@ -48,8 +48,10 @@ def get_content(self): return self.content def get_highlight_pattern(self): - return self.highlight_pattern + return self.highlight_pattern + + # Top-level entry point for describing a tutorial which is made up of a series of steps class Tutorial: def __init__(self): From ec8bb265afcb6955f312a49e376f9b02f1a65245 Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 15 Jul 2022 07:21:31 -0700 Subject: [PATCH 09/16] first pr Signed-off-by: Song --- Editor/Scripts/interactivetutorials_dialog.py | 1 - Editor/Scripts/rigid_body_tutorial.py | 355 ++++++++++++++++-- 2 files changed, 316 insertions(+), 40 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index a16bbb3..51e3bfb 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -259,7 +259,6 @@ def simulate_step(self): def on_start_button_clicked(self): tutorial_index = self.tutorial_list.currentIndex().row() - self.load_tutorial(tutorial_index) diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index 7952c41..335a72a 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -5,8 +5,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT """ import os -#import sys -#sys.path.append(os.path.dirname(__file__)) +import sys import azlmbr import azlmbr.bus as bus import azlmbr.editor as editor @@ -15,11 +14,20 @@ import azlmbr.math as math import azlmbr.prefab as prefab from azlmbr.entity import EntityId +import azlmbr.asset as asset +from editor_python_test_tools.utils import TestHelper as helper from PySide2.QtWidgets import QMenuBar from PySide2.QtWidgets import QDialog from tutorial import Tutorial, TutorialStep +class Tests(): + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Couldn't exit game mode") +# fmt: on + + + class RigidBodyTutorial(Tutorial): def __init__(self): super(RigidBodyTutorial, self).__init__() @@ -28,6 +36,7 @@ def __init__(self): self.current_step_index = 0 self.simulate_clicked = False + self.add_step(TutorialStep("Dynamic Simulation with PhysX Rigid Bodies", """

    Greetings!

    Rigid bodies are dynamic solid objects that simulate reactions to collisions and other physical forces.

    In this tutorial, we'll edit an @@ -173,41 +182,309 @@ def on_tutorial_end(self): def simulate(self): #if simulate_clicked is true step_count = self.current_step_index - # for loop with steps - # for int i = 1 i is num tutorial steps i ++ - #list_of_steps = ['step_one(self)', 'step_two(self)', 'step_three(self)'] - #list_of_steps = ['print("Hello")', 'print("Now")', 'print("Goodbye")'] + + class RigidBody: + gravity_enabled = False + collison_occued = False + mass = 0.0 + + if step_count == 2: #Up to Delete the Shader Ball + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + elif step_count == 3: #Up to position the Dice Prefab + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + elif step_count == 5: #Up To Open Focus View + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + elif step_count == 6: #Up to add a PhysX Collider Component + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - #for step in list_of_steps: - # eval(step) - # print("Reached Eval Step") - # if step == step_count: - # break - #Delete the Shader Ball entity - - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - entityId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', entityId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - - # Find created dice entity - search_filter = entity.SearchFilter() - search_filter.names = ["20-sided-dice"] - diceEntity = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)[0] - - #Add PhysX Collider - #Get Component Type for PhysX Collider - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game - )[0] - #newEntityId = - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', EntityId, [colliderComponentTypeId]) \ No newline at end of file + #Add PhysX Collider Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + elif step_count == 8 : #Up to Add a Rigid Body Component + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + elif step_count == 9 : + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + + elif step_count == 10 : + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + helper.enter_game_mode(Tests.enter_game_mode) + elif step_count == 9 : + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + + elif step_count == 9 : + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + + + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + helper.enter_game_mode(Tests.enter_game_mode) \ No newline at end of file From 09846757925ba62d11e8c116fa27c0488b2be11e Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 15 Jul 2022 08:01:14 -0700 Subject: [PATCH 10/16] simulate steps aligned with actual steps Signed-off-by: Song --- Editor/Scripts/bush.prefab | 129 --------------- Editor/Scripts/rigid_body_tutorial.py | 226 ++++++++++++++++++++++++-- 2 files changed, 210 insertions(+), 145 deletions(-) delete mode 100644 Editor/Scripts/bush.prefab diff --git a/Editor/Scripts/bush.prefab b/Editor/Scripts/bush.prefab deleted file mode 100644 index ef497bd..0000000 --- a/Editor/Scripts/bush.prefab +++ /dev/null @@ -1,129 +0,0 @@ -{ - "ContainerEntity": { - "Id": "ContainerEntity", - "Name": "Bush", - "Components": { - "Component_[1140272189295067758]": { - "$type": "EditorInspectorComponent", - "Id": 1140272189295067758 - }, - "Component_[13437832196484687256]": { - "$type": "EditorOnlyEntityComponent", - "Id": 13437832196484687256 - }, - "Component_[1553903646452669645]": { - "$type": "EditorDisabledCompositionComponent", - "Id": 1553903646452669645 - }, - "Component_[15914009348632444632]": { - "$type": "EditorEntitySortComponent", - "Id": 15914009348632444632, - "Child Entity Order": [ - "Entity_[7511491868318]" - ] - }, - "Component_[18046340308818780248]": { - "$type": "EditorPrefabComponent", - "Id": 18046340308818780248 - }, - "Component_[1948833233489872938]": { - "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", - "Id": 1948833233489872938, - "Parent Entity": "" - }, - "Component_[2903632350157981339]": { - "$type": "SelectionComponent", - "Id": 2903632350157981339 - }, - "Component_[48827510535192710]": { - "$type": "EditorPendingCompositionComponent", - "Id": 48827510535192710 - }, - "Component_[5609536793322429681]": { - "$type": "EditorLockComponent", - "Id": 5609536793322429681 - }, - "Component_[5859168386298620990]": { - "$type": "EditorEntityIconComponent", - "Id": 5859168386298620990 - }, - "Component_[6604616929271524505]": { - "$type": "EditorVisibilityComponent", - "Id": 6604616929271524505 - } - } - }, - "Entities": { - "Entity_[7511491868318]": { - "Id": "Entity_[7511491868318]", - "Name": "Bush", - "Components": { - "Component_[10227459330338484901]": { - "$type": "EditorInspectorComponent", - "Id": 10227459330338484901, - "ComponentOrderEntryArray": [ - { - "ComponentId": 4998941225335869157 - }, - { - "ComponentId": 9922994635792843826, - "SortIndex": 1 - } - ] - }, - "Component_[10972351222359420947]": { - "$type": "EditorOnlyEntityComponent", - "Id": 10972351222359420947 - }, - "Component_[12101122374155214392]": { - "$type": "EditorPendingCompositionComponent", - "Id": 12101122374155214392 - }, - "Component_[1535264614652988260]": { - "$type": "SelectionComponent", - "Id": 1535264614652988260 - }, - "Component_[16367811417907891218]": { - "$type": "EditorVisibilityComponent", - "Id": 16367811417907891218 - }, - "Component_[17044216787716682880]": { - "$type": "EditorEntitySortComponent", - "Id": 17044216787716682880 - }, - "Component_[2129822594969629430]": { - "$type": "EditorEntityIconComponent", - "Id": 2129822594969629430 - }, - "Component_[2838015156782745450]": { - "$type": "EditorLockComponent", - "Id": 2838015156782745450 - }, - "Component_[4998941225335869157]": { - "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", - "Id": 4998941225335869157, - "Parent Entity": "ContainerEntity" - }, - "Component_[8773358049076362578]": { - "$type": "EditorDisabledCompositionComponent", - "Id": 8773358049076362578 - }, - "Component_[9922994635792843826]": { - "$type": "AZ::Render::EditorMeshComponent", - "Id": 9922994635792843826, - "Controller": { - "Configuration": { - "ModelAsset": { - "assetId": { - "guid": "{1201406D-FB20-5B5F-B9B5-6A6E8DE00A14}", - "subId": 276506120 - }, - "assetHint": "assets/objects/foliage/bush_privet_01.azmodel" - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index 335a72a..41ec0bf 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -188,7 +188,7 @@ class RigidBody: collison_occued = False mass = 0.0 - if step_count == 2: #Up to Delete the Shader Ball + if step_count == 3: #Up to Delete the Shader Ball #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -196,7 +196,7 @@ class RigidBody: searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) shaderBallId = searchResult[0] editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - elif step_count == 3: #Up to position the Dice Prefab + elif step_count == 5 or 6: #Up to position the Dice Prefab #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -211,7 +211,23 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - elif step_count == 5: #Up To Open Focus View + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + elif step_count == 7: #Up to open focus view + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + elif step_count == 9: #Up To Open Dice Entity #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -238,7 +254,7 @@ class RigidBody: editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - elif step_count == 6: #Up to add a PhysX Collider Component + elif step_count == 10: #Up to add a PhysX Collider Component #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -268,7 +284,7 @@ class RigidBody: #Add PhysX Collider Component colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - elif step_count == 8 : #Up to Add a Rigid Body Component + elif step_count == 11 : #Up to Add a Rigid Body Component #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -300,7 +316,7 @@ class RigidBody: editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - elif step_count == 9 : + elif step_count == 12 : #Up to Edit Linear Velocity #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -335,17 +351,100 @@ class RigidBody: #Edit Linear Velocity #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") rigid_body_id = general.find_game_entity(newEntity5) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + elif step_count == 13 : #Up to Edit Angular Velocity + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - elif step_count == 10 : + rigid_body_id = general.find_game_entity(newEntity5) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) + elif step_count == 16 : #Add new Entity + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) + + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + elif step_count == 14 : #Up to Exit Focus Mode #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -394,7 +493,51 @@ class RigidBody: position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) helper.enter_game_mode(Tests.enter_game_mode) - elif step_count == 9 : + elif step_count == 16 : #Add a Collider + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) + + editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + elif step_count == 17 : #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -437,9 +580,10 @@ class RigidBody: azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - - elif step_count == 9 : + collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) + elif step_count == 17 or 18 or 19 : #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -482,9 +626,59 @@ class RigidBody: azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) + elif step_count == 21 : #enter game mode + #Delete Shader Ball + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + #Add PhysX Collider Component and Rigid Body Component + colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) + rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) + + #Edit Linear Velocity + #Edit Angular Velocity + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + + rigid_body_id = general.find_game_entity(newEntity5) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) + azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) transform = math.Transform_CreateIdentity() position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) helper.enter_game_mode(Tests.enter_game_mode) \ No newline at end of file From 2dc5eb961bb845e29702b0cd79a01116eccafa70 Mon Sep 17 00:00:00 2001 From: Song Date: Fri, 15 Jul 2022 08:23:04 -0700 Subject: [PATCH 11/16] simulate tutorial button Signed-off-by: Song --- Editor/Scripts/interactivetutorials_dialog.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index 51e3bfb..bb8f415 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -143,6 +143,13 @@ def __init__(self, parent=None): self.button_box.addButton(self.simulate_button, QDialogButtonBox.ActionRole) self.tutorial_layout.addWidget(self.button_box) + self.button_box = QDialogButtonBox(self) + self.simulate_all_button = QPushButton("View the Final Simulation", self) + self.simulate_all_button.setDefault(True) + self.simulate_all_button.clicked.connect(self.simulate_tutorial) + self.button_box.addButton(self.simulate_all_button, QDialogButtonBox.ActionRole) + self.tutorial_layout.addWidget(self.button_box) + self.tutorial_widget.setLayout(self.tutorial_layout) self.stacked_widget.addWidget(self.tutorial_widget) @@ -257,6 +264,13 @@ def simulate_step(self): RigidBodyTutorial.set_simulate_on(self) RigidBodyTutorial.simulate(self) + def simulate_tutorial(self): + if self.tutorial_list.currentIndex().row() == 3: + #print("Rigid Body Tutorial identified") + RigidBodyTutorial.set_step_number(self, self.current_tutorial_num_steps) + RigidBodyTutorial.set_simulate_on(self) + RigidBodyTutorial.simulate(self) + def on_start_button_clicked(self): tutorial_index = self.tutorial_list.currentIndex().row() self.load_tutorial(tutorial_index) From 23978faa320e8337ac2ee6302713b986be90f181 Mon Sep 17 00:00:00 2001 From: Song Date: Mon, 18 Jul 2022 08:46:34 -0700 Subject: [PATCH 12/16] just one part of the if-statement for fxns Signed-off-by: Song --- Editor/Scripts/rigid_body_tutorial.py | 58 +++++++++++++++++++++------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index 41ec0bf..3f1a3b8 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -188,6 +188,9 @@ class RigidBody: collison_occued = False mass = 0.0 + """ + + if step_count == 3: #Up to Delete the Shader Ball #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) @@ -227,6 +230,7 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) elif step_count == 9: #Up To Open Dice Entity #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) @@ -241,7 +245,8 @@ class RigidBody: position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) @@ -269,7 +274,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -299,7 +305,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -331,7 +338,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -373,7 +381,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -415,7 +424,7 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -459,7 +468,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -508,6 +518,7 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) @@ -552,7 +563,8 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -598,7 +610,7 @@ class RigidBody: transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -632,7 +644,11 @@ class RigidBody: collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - elif step_count == 21 : #enter game mode + + """ + if step_count == 10000 : + pass + elif step_count != 21 : #enter game mode #Delete Shader Ball editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) searchFilter = entity.SearchFilter() @@ -646,8 +662,19 @@ class RigidBody: position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + search_filter2 = entity.SearchFilter() + search_filter2.names = ["20-sided-dice"] + prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter2)[0] + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) + + """ + search_filter = entity.SearchFilter() + search_filter.names = ["LandscapeCanvas"] + helper.wait_for_condition(lambda: len(entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)) > 0, 5.0) + prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)[0] + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) + """ #Create new Dice Entity parented to Prefab newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") @@ -675,10 +702,15 @@ class RigidBody: azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) + search_filter3 = entity.SearchFilter() + search_filter3.names = ["Sun"] + prefab_lc_root3 = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter3)[0] + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root3) + transform = math.Transform_CreateIdentity() position = math.Vector3(0.0, 0.0, 5.0) transform.invoke('SetPosition', position) collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - helper.enter_game_mode(Tests.enter_game_mode) \ No newline at end of file + #helper.enter_game_mode(Tests.enter_game_mode) \ No newline at end of file From 8d632ed418cb027f83c9a6eb7f6f1ad31f9373a1 Mon Sep 17 00:00:00 2001 From: Song Date: Thu, 21 Jul 2022 11:01:27 -0700 Subject: [PATCH 13/16] steps serialized Signed-off-by: Song --- Editor/Scripts/hashtable.py | 0 Editor/Scripts/rigid_body_tutorial.py | 597 ++++---------------------- 2 files changed, 83 insertions(+), 514 deletions(-) create mode 100644 Editor/Scripts/hashtable.py diff --git a/Editor/Scripts/hashtable.py b/Editor/Scripts/hashtable.py new file mode 100644 index 0000000..e69de29 diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index 3f1a3b8..c2122a4 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -16,7 +16,7 @@ from azlmbr.entity import EntityId import azlmbr.asset as asset from editor_python_test_tools.utils import TestHelper as helper - +from hashtable import HashTable from PySide2.QtWidgets import QMenuBar from PySide2.QtWidgets import QDialog from tutorial import Tutorial, TutorialStep @@ -160,8 +160,6 @@ def __init__(self): fly in the air an spin, then fall and collide with the ground plane. Press ESC to exit.

    """)) - - def on_tutorial_start(self): print("Starting PhysX Rigid Body tutorial.") @@ -178,410 +176,63 @@ def set_simulate_off(self): def on_tutorial_end(self): print("PhysX Rigid Body tutorial complete!") - - def simulate(self): - #if simulate_clicked is true - step_count = self.current_step_index - - class RigidBody: - gravity_enabled = False - collison_occued = False - mass = 0.0 - - """ - - - if step_count == 3: #Up to Delete the Shader Ball - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - elif step_count == 5 or 6: #Up to position the Dice Prefab - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - elif step_count == 7: #Up to open focus view - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - elif step_count == 9: #Up To Open Dice Entity - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - elif step_count == 10: #Up to add a PhysX Collider Component - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + + """ + per-step functions + """ + def delete_shader_ball(self): + editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) + searchFilter = entity.SearchFilter() + searchFilter.names = ['Shader Ball'] + searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) + shaderBallId = searchResult[0] + editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) + + + def focus_dice_prefab(self): + # Instantiate and position the prefab + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") + dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) + search_filter2 = entity.SearchFilter() + search_filter2.names = ["20-sided-dice"] + prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter2)[0] + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) + + def edit_dice_components(self): + e1_reached = False #Add Rigid Body and Collider Components + e2_reached = False #Edit Rigid Body and Collider Components - #Add PhysX Collider Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - elif step_count == 11 : #Up to Add a Rigid Body Component - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - elif step_count == 12 : #Up to Edit Linear Velocity - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - + #Create new Dice Entity parented to Prefab + newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") + #Add Mesh, Material, and Position Components to the Entity + meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] + materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] + positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) + if e1_reached == True: #Add PhysX Collider Component and Rigid Body Component colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - elif step_count == 13 : #Up to Edit Angular Velocity - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - #Edit Linear Velocity #Edit Angular Velocity - #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) - elif step_count == 16 : #Add new Entity - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) - - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - elif step_count == 14 : #Up to Exit Focus Mode - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - + if e2_reached == True: + #todo: break these into subfunctions rigid_body_id = general.find_game_entity(newEntity5) azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - helper.enter_game_mode(Tests.enter_game_mode) - elif step_count == 16 : #Add a Collider - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - #test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - #azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetAngularVelocity", rigidBodyComponentTypeId, 1.0) - - editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - elif step_count == 17 : - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - #Edit Linear Velocity #Edit Angular Velocity test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") @@ -591,126 +242,44 @@ class RigidBody: azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - - collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - elif step_count == 17 or 18 or 19 : - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", dice_prefab) - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - - """ - if step_count == 10000 : - pass - elif step_count != 21 : #enter game mode - #Delete Shader Ball - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - search_filter2 = entity.SearchFilter() - search_filter2.names = ["20-sided-dice"] - prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter2)[0] - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) - - """ - search_filter = entity.SearchFilter() - search_filter.names = ["LandscapeCanvas"] - helper.wait_for_condition(lambda: len(entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)) > 0, 5.0) - prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter)[0] - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) - """ - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - - search_filter3 = entity.SearchFilter() - search_filter3.names = ["Sun"] - prefab_lc_root3 = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter3)[0] - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root3) - - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - #helper.enter_game_mode(Tests.enter_game_mode) \ No newline at end of file + def create_new_collider(self): + search_filter3 = entity.SearchFilter() + search_filter3.names = ["Sun"] + prefab_lc_root3 = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter3)[0] + azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root3) + + transform = math.Transform_CreateIdentity() + position = math.Vector3(0.0, 0.0, 5.0) + transform.invoke('SetPosition', position) + collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) + shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] + editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) + + def enter_game_mode(self): + helper.enter_game_mode(Tests.enter_game_mode) + + def simulate(self): + #if simulate_clicked is true + step_count = self.current_step_index + print(step_count) + class RigidBody: + gravity_enabled = False + collison_occued = False + mass = 0.0 + Steps = [0, 0, 0, 0, 0] + for x in Steps: + pass + print("entered simulate") + self.delete_shader_ball + print("entered simulate") + + Steps[0] = 1 + self.focus_dice_prefab + Steps[1] = 1 + #self.edit_dice_components + Steps[2] = 1 + #self.create_new_collider + Steps[3] = 1 + #self.enter_game_mode + Steps[4] = 1 \ No newline at end of file From de86a4fa20bfeb8396fcc96cd3160d94e0e32366 Mon Sep 17 00:00:00 2001 From: Song Date: Tue, 26 Jul 2022 08:29:47 -0700 Subject: [PATCH 14/16] highlights Signed-off-by: Song --- Editor/Scripts/finding_ui_objects.py | 80 +++ Editor/Scripts/hashtable.py | 0 Editor/Scripts/interactivetutorials_dialog.py | 76 +-- Editor/Scripts/rigid_body_tutorial.py | 192 +----- Editor/Scripts/tutorial.py | 27 +- Editor/Scripts/tutorial_for_scripts.py | 47 -- Editor/Scripts/utils.py | 568 ------------------ 7 files changed, 151 insertions(+), 839 deletions(-) create mode 100644 Editor/Scripts/finding_ui_objects.py delete mode 100644 Editor/Scripts/hashtable.py delete mode 100644 Editor/Scripts/tutorial_for_scripts.py delete mode 100644 Editor/Scripts/utils.py diff --git a/Editor/Scripts/finding_ui_objects.py b/Editor/Scripts/finding_ui_objects.py new file mode 100644 index 0000000..875a478 --- /dev/null +++ b/Editor/Scripts/finding_ui_objects.py @@ -0,0 +1,80 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +import sys +import azlmbr + +from PySide2 import QtWidgets +import editor_python_test_tools.pyside_utils as pyside_utils + +from tutorial import Tutorial, TutorialStep + +class FindingUIObjectsTutorial(Tutorial): + def __init__(self): + super(FindingUIObjectsTutorial, self).__init__() + + self.title = "Highlighting UI Objects" + + self.add_step(TutorialStep("Highlighting UI Objects", + """

    Greetings!

    This tutorial demonstrates methods to find UI + objects to highlight in tutorial steps. You can use the Object Tree tool to find named UI + objects or a named parent of a UI object. Then, use one of methods in this tutorial to specify the + widget to highlight in a TutorialStep + .

    The available methods are:

    • Highlight by name - Specify the name of the + UI object to highlight.
    • Highlight by parent - Specify a named parent and a pattern for + the UI object. The UI object is the first child of the parent that matches the specified pattern.
    • +
    • Highlight from hierarchy with index - Specify an index to select a specific child UI object + when the parent has multiple unnamed children of the same type.

    """)) + self.add_step(TutorialStep("Highlight by name", """

    This step highlights the + Entity Outliner, finding it by its name, + "EntityOutlinerWidgetUI".

    """, "EntityOutlinerWidgetUI")) + self.add_step(TutorialStep("Highlight by pattern", """

    This step highlights + the Console Variables tool button in the lower-left corner of the Editor. The Console Variables + tool button has a name, but is found through a dictionary that contains text (the widget name) and its + type, {"text": "button", "type": + QtWidgets.QToolButton}.

    """, {"text": "button", + "type": QtWidgets.QToolButton})) + self.add_step(TutorialStep("Highlight by pattern", """

    This step + highlights the Play tool button in the upper-right corner of the Editor. The Play tool button is + unnamed, but it has a text attribute for a tooltip. We can use a dictionary to find this UI + element by providing the widget type and the tooltip text, {"type": QtWidgets.QToolButton, "text": "Play Game"}.

    """, + {"type": QtWidgets.QToolButton, "text": "Play Game"})) + self.add_step(TutorialStep("Highlight from named parent", """

    This step highlights + the Transform Toolbar in the upper-left corner of the viewport. The Transform Toolbar widget is + unnamed and has no tooltip text. We can find it by its type through its named parent. It's the first + child of type QtWidgets.QToolBar of a + widget named "ViewportUiOverlay". +

    """, QtWidgets.QToolBar, "ViewportUiOverlay")) + self.add_step(TutorialStep("Highlight from named parent with index", """

    In + the upper-right corner of the viewport is the Transform Space Toolbar. It is an unnamed widget + of type QtWidgets.QToolBar. It's also + the second child of the "ViewportUiOverlay" + widget we referenced in the previous step. To highlight the Transform Space Toolbar, we use + the same method as the previous step, but provide an optional index value of + 1 to select the second child.

    """, + QtWidgets.QToolBar, "ViewportUiOverlay", 1)) + item_parent = pyside_utils.find_child_by_pattern(None, "ViewportUiOverlay") + item = pyside_utils.find_child_by_hierarchy(item_parent, QtWidgets.QToolBar, child_index=1) + self.add_step(TutorialStep("Highlight from named parent with index - advanced", """

    + What if you want to highlight a UI element, but its nearest named parent is a couple of levels and + indices away?

    + The Transform Space Toolbar has three buttons: World, Parent, and Local space. Suppose we want to + highlight the Local space button. It's the fourth + QtWidgets.QToolButton in the second + QtWidgets.QToolBar beneath a parent named + ViewportUiOverlay.

    + We can get the local space button by finding it's direct unnamed parent prior to defining the step, and + supplying the direct parent and an index to the tutorial step. See this step in this tutorial's Python + script for an example.

    """, + QtWidgets.QToolButton, item, 3)) + + + def on_tutorial_start(self): + print("Starting Widget By Hierarchy tutorial.") + + def on_tutorial_end(self): + print("Widget By Hierarchy tutorial complete!") \ No newline at end of file diff --git a/Editor/Scripts/hashtable.py b/Editor/Scripts/hashtable.py deleted file mode 100644 index e69de29..0000000 diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index bb8f415..d7663e1 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -1,7 +1,6 @@ """ 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 """ # ------------------------------------------------------------------------- @@ -23,8 +22,10 @@ from demo_tutorial import DemoTutorial, IntroTutorial from rigid_body_tutorial import RigidBodyTutorial +from finding_ui_objects import FindingUIObjectsTutorial from tutorial import Tutorial + class HighlightWidget(QWidget): def __init__(self, parent=None): super(HighlightWidget, self).__init__(parent) @@ -91,6 +92,10 @@ def __init__(self, parent=None): { "name": "PhysX Rigid Bodies", "tutorial": RigidBodyTutorial + }, + { + "name": "Highlighting UI Objects", + "tutorial": FindingUIObjectsTutorial } ] tutorial_names = [tutorial['name'] for tutorial in self.tutorials] @@ -123,9 +128,6 @@ def __init__(self, parent=None): self.content_area.setWordWrap(True) self.tutorial_layout.addWidget(self.content_area, 1) - self.step_label = QLabel(self) - self.tutorial_layout.addWidget(self.step_label) - self.button_box = QDialogButtonBox(self) self.next_button = QPushButton("Next", self) self.next_button.setDefault(True) @@ -136,20 +138,6 @@ def __init__(self, parent=None): self.button_box.addButton(self.back_button, QDialogButtonBox.ResetRole) self.tutorial_layout.addWidget(self.button_box) - self.button_box = QDialogButtonBox(self) - self.simulate_button = QPushButton("Simulate This Step", self) - self.simulate_button.setDefault(True) - self.simulate_button.clicked.connect(self.simulate_step) - self.button_box.addButton(self.simulate_button, QDialogButtonBox.ActionRole) - self.tutorial_layout.addWidget(self.button_box) - - self.button_box = QDialogButtonBox(self) - self.simulate_all_button = QPushButton("View the Final Simulation", self) - self.simulate_all_button.setDefault(True) - self.simulate_all_button.clicked.connect(self.simulate_tutorial) - self.button_box.addButton(self.simulate_all_button, QDialogButtonBox.ActionRole) - self.tutorial_layout.addWidget(self.button_box) - self.tutorial_widget.setLayout(self.tutorial_layout) self.stacked_widget.addWidget(self.tutorial_widget) @@ -163,8 +151,6 @@ def load_tutorial(self, index): tutorial_factory = self.tutorials[index]["tutorial"] self.current_tutorial = tutorial_factory() - - self.current_tutorial_num_steps = len(self.current_tutorial.get_steps()) # Invoke the tutorial start method self.current_tutorial.on_tutorial_start() @@ -173,10 +159,9 @@ def load_tutorial(self, index): self.setWindowTitle("InteractiveTutorials - " + self.current_tutorial.get_title()) # Reset initial state and load first step - self.current_step_index = 0 self.current_step = None first_step = self.current_tutorial.get_first_step() - self.load_step(first_step) + self.load_step(first_step) def end_tutorial(self): if not self.current_step: @@ -205,7 +190,7 @@ def update_step_view(self): self.title_label.setText(self.current_step.get_title()) self.content_area.setText(self.current_step.get_content()) - self.step_label.setText(f"Step {self.current_step_index} of {self.current_tutorial_num_steps}") + # If there are no steps remaining in the tutorial, then # update the Next button text to "End" next_button_text = "Next" @@ -215,14 +200,27 @@ def update_step_view(self): # If a highlight pattern was set for this step, then find that widget/item # and highlight it + highlight_item = None highlight_pattern = self.current_step.get_highlight_pattern() - if highlight_pattern: - item = pyside_utils.find_child_by_pattern(None, highlight_pattern) - self.highlight_widget.update_widget(item) - if not item: - print(f"Couldn't find widget or item matching pattern: { highlight_pattern }") + if not highlight_pattern: + self.highlight_widget.update_widget(None) + return + + highlight_parent = self.current_step.get_highlight_parent() + if not highlight_parent: + highlight_item = pyside_utils.find_child_by_pattern(None, highlight_pattern) else: + if isinstance(highlight_parent, str): + highlight_parent = pyside_utils.find_child_by_pattern(None, highlight_parent) + highlight_item = pyside_utils.find_child_by_hierarchy(highlight_parent, highlight_pattern, + child_index=self.current_step.get_highlight_index()) + + if not highlight_item: self.highlight_widget.update_widget(None) + print(f"Couldn't find widget or item matching pattern: { highlight_pattern }") + return + + self.highlight_widget.update_widget(highlight_item) def load_step(self, step): # If there was a step already loaded, call its ending method @@ -230,7 +228,6 @@ def load_step(self, step): self.current_step.on_step_end() self.current_step = step - self.current_step_index += 1 # Invoke the method for the beginning of this step self.current_step.on_step_start() @@ -249,30 +246,13 @@ def load_next_step(self): def load_previous_step(self): if self.current_step: - self.current_step_index -= 1 prev_step = self.current_step.prev_step if prev_step: - self.current_step_index -= 1 self.load_step(prev_step) - - def simulate_step(self): - #print("Entered simulate step") - if self.tutorial_list.currentIndex().row() == 3: - #print("Rigid Body Tutorial identified") - RigidBodyTutorial.set_step_number(self, self.current_step_index) - RigidBodyTutorial.set_simulate_on(self) - RigidBodyTutorial.simulate(self) - - def simulate_tutorial(self): - if self.tutorial_list.currentIndex().row() == 3: - #print("Rigid Body Tutorial identified") - RigidBodyTutorial.set_step_number(self, self.current_tutorial_num_steps) - RigidBodyTutorial.set_simulate_on(self) - RigidBodyTutorial.simulate(self) - def on_start_button_clicked(self): tutorial_index = self.tutorial_list.currentIndex().row() + self.load_tutorial(tutorial_index) @@ -281,4 +261,4 @@ def on_start_button_clicked(self): # which allows for quick iteration without having to close/re-launch the Editor test_dialog = InteractiveTutorialsDialog() test_dialog.show() - test_dialog.adjustSize() + test_dialog.adjustSize() \ No newline at end of file diff --git a/Editor/Scripts/rigid_body_tutorial.py b/Editor/Scripts/rigid_body_tutorial.py index c2122a4..824c576 100644 --- a/Editor/Scripts/rigid_body_tutorial.py +++ b/Editor/Scripts/rigid_body_tutorial.py @@ -1,41 +1,21 @@ """ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. - SPDX-License-Identifier: Apache-2.0 OR MIT """ -import os + import sys import azlmbr -import azlmbr.bus as bus -import azlmbr.editor as editor -import azlmbr.legacy.general as general -import azlmbr.entity as entity -import azlmbr.math as math -import azlmbr.prefab as prefab -from azlmbr.entity import EntityId -import azlmbr.asset as asset -from editor_python_test_tools.utils import TestHelper as helper -from hashtable import HashTable -from PySide2.QtWidgets import QMenuBar -from PySide2.QtWidgets import QDialog -from tutorial import Tutorial, TutorialStep - -class Tests(): - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") -# fmt: on +from PySide2.QtWidgets import QMenuBar +from tutorial import Tutorial, TutorialStep class RigidBodyTutorial(Tutorial): def __init__(self): super(RigidBodyTutorial, self).__init__() self.title = "PhysX Rigid Bodies" - self.current_step_index = 0 - self.simulate_clicked = False - self.add_step(TutorialStep("Dynamic Simulation with PhysX Rigid Bodies", """

    Greetings!

    Rigid bodies are dynamic solid objects that @@ -49,44 +29,45 @@ def __init__(self): all the entities in the level. Each entity has a collection of components that provide some functionality. The Sun entity, for example, has a Directional Light component the simulates a very bright distant light with parallel rays. It also has a Transform component that - places it in the level in relation to it's parent, the Atom Default Environment entity. -

    """, "EntityOutlinerWidgetUI")) + places it in the level in realtion to it's parent, the Atom Default Environment entity. +


    Step 1 of 20

    """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Delete the Shader Ball entity", """

    Let's tidy up a bit.

    In Entity Outliner, click the Shader Ball entity to select it and press - DEL to remove it from the level.

    """, + DEL to remove it from the level.


    Step 2 of 20

    """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Find a prefab in Asset Browser", """

    A prefab is a reusable asset that has been saved to disk and that can be added to a level as an instance or spawned at runtime. Let's find a prefab to use the basis of the rigid body.

    In Asset Browser, use the Search... filter to find the 20-sided-dice.prefab. -

    """, "AzAssetBrowserWindowClass")) +


    Step 3 of 20

    """, "AzAssetBrowserWindowClass")) self.add_step(TutorialStep("Instantiate a prefab", """

    To use the prefab in our level we need to create an instance, or instantiate the prefab in the level.

    Drag the 20-sided-dice.prefab into the - viewport to instatiate it in the level.

    """, "renderOverlay")) + viewport to instatiate it in the level.


    Step 4 of 20

    """, "renderOverlay")) self.add_step(TutorialStep("Position the prefab", """

    Use the Move tool to position the dice prefab instance in the camera's view.

    • In the viewport, ensure the prefab instance is selected by clicking it.
    • Use the translate handles to drag the prefab instance so that it is in the yellow wireframe that represents the camera's view frustum.
    • Drag the prefab instance up so that it is a few units above the ground plane.
    • -

      """, "renderOverlay")) +


      Step 5 of 20

      """, "renderOverlay")) self.add_step(TutorialStep("Open the prefab instance in Focus Mode", """

      We'll need to add a couple components to the prefab. The prefab in the level is an instance so it has to be opened for editing in Focus Mode

      In Entity Outliner double-click the dice prefab instance to open it for editing.

      Note that the prefab instance is highlighted in a light blue frame and that the viewport also has a light blue frame titled Focus Mode. In Focus Mode, any modifications we make happen in the context of the prefab instance that is open for editing.


      -

      """, "EntityOutlinerWidgetUI")) + Step 6 of 20

      """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Edit the dice entity", """

      With the dice prefab instance open for editing, we can see it contains an enity (denoted by a white box icon). We'll add some PhysX components to this entity.

      In Enity Outliner click the 20-sided-dice entity - to select it.

      """, "EntityOutlinerWidgetUI")) + to select it.


      Step 7 of 20

      """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Inspect the dice entity", """

      Entity Inspector displays the dice entity's three components.

      • Transform - Places and orients the entity in the local space of the prefab instance.
      • Mesh - Supplies a render mesh for the entity.
      • Material - Applies shaders and textures to the render mesh.
      In the - next steps, we'll add and configure components in Entity Inspector.

      """, "InspectorMainWindow")) + next steps, we'll add and configure components in Entity Inspector.


      Step 8 of + 20

      """, "InspectorMainWindow")) self.add_step(TutorialStep("Add a PhysX Collider", """

      A collider is a simplified mesh that defines a solid surface that can register collisions and overlaps with other colliders. To add a collider to the entity, we'll add a PhysX Collider component.

      In @@ -99,187 +80,72 @@ def __init__(self): collider component, notice that the Shape property is set to PhysicsAsset and that the PhysX Mesh property is set to - 20-sided-dice, the generated convex mesh.

      """, + 20-sided-dice, the generated convex mesh.


      Step 9 of 20

      """, "m_addComponentButton")) self.add_step(TutorialStep("Add a PhysX Rigid Body", """

      A rigid body is a dynamic hard surface that responds to collisions and forces. Currently, the enity can be collided with, but it won't respond when a simulation runs. We need to add a PhysX Rigid Body component.

      In Entity Inspector click the Add Component button and select the PhysX Rigid Body component from the list.

      The entity can now be simulated, but - there's a couple more things we need to do before we test it.

      """, + there's a couple more things we need to do before we test it.


      Step 10 of 20

      """, "m_addComponentButton")) self.add_step(TutorialStep("Add an initial linear velocity", """

      The PhysX Rigid Body component has a lot of properties. The most important one, Gravity enabled, is turned on by default. When simulated, the enity will fall due to gravity. Let's add a small upward initial velocity to the rigid body so we see an interesting result when we run the simulation.

      In the PhysX Rigid Body component, set the Initial linear velocity property's Z value to - 10.

      """, "InspectorMainWindow")) + 10.


      Step 11 of + 20

      """, "InspectorMainWindow")) self.add_step(TutorialStep("Add an initial angular velocity", """

      We can also spin the dice entity. Let's add a small inital angular velocity to the rigid body as well.

      In the PhysX Rigid Body component, set the Initial angular velocity property's Y value to 15.

      With these two properties - set, the entity will pop straight up and spin when the entity is activated.

      """, "InspectorMainWindow")) + set, the entity will pop straight up and spin when the entity is activated.


      Step 12 of + 20

      """, "InspectorMainWindow")) self.add_step(TutorialStep("Exit Focus Mode", """

      We're done editing the prefab instance, so let's exit Focus Mode.

      In Entity Outliner double click the dice prefab - instance to close it.

      """, "EntityOutlinerWidgetUI")) + instance to close it.


      Step 13 of 20

      """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Save the prefab", """

      In Entity Outliner, note the * asterisk next to the dice prefab file name on the left. This tells us the prefab instance has been edited and there are unsaved changes. Let's save the prefab.

      In Entity Outliner, right-click the dice prefab instance and choose Save Prefab to file.

      Saving the prefab overwrites the prefab file on disk. Any new instances of the prefab will contain the PhysX - Collider and PhysX Rigid Body components we added in the previous setps.

      """, "EntityOutlinerWidgetUI")) + Collider and PhysX Rigid Body components we added in the previous setps.


      Step 14 + of 20

      """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Add a ground plane collider", """

      We need to add a collider to the ground plane so that the dice prefab instance has a surface to collide against in the simulation.

      In Entity Outliner, right-click and select Create Entity from the - context menu.

      """, "EntityOutlinerWidgetUI")) + context menu.


      Step 15 of 20

      """, "EntityOutlinerWidgetUI")) self.add_step(TutorialStep("Add a PhysX Shape Collider", """

      With the new enity selected, we'll add a PhysX Shape Collider componnet to create a collision surface for the ground plane.

      In Entity Inspector click the Add Component button and select the - PhysX Shape Collider component from the list.

      """, + PhysX Shape Collider component from the list.


      Step 16 of 20

      """, "m_addComponentButton")) self.add_step(TutorialStep("Add a Shape for the ground collider", """

      In the PhysX Shape Collider component, there is a warning about a missing component service. The shape collider requires a shape component to define the collider. Let's add a shape component.

      Click the Add Required Component button and select Quad Shape from the - shape component list.

      """, "InspectorMainWindow")) + shape component list.


      Step 17 of 20

      """, "InspectorMainWindow")) self.add_step(TutorialStep("Set the ground collider size", """

      The Quad Shape component is a flat plane with a default size of one meter by one meter. We need to make it much larger to ensure the dice prefab instance collides with it.

      In the Quad Shape component, set both the Width and Height properties to 512 so that the collision plane - matches the size of the gorund plane.

      """, "InspectorMainWindow")) + matches the size of the gorund plane.


      Step 18 of 20

      """, "InspectorMainWindow")) self.add_step(TutorialStep("Position the ground collider", """

      The ground collider we just created is not aligned with the ground mesh. Let's move the collider so that it aligns better with the ground plane.

      With the ground collider entity selected, in the viewpoort, use the Z handle of the move tool to move the ground collider just enough for - the quad shape to disappear below the ground plane.

      """, + the quad shape to disappear below the ground plane.


      Step 19 of 20

      """, "renderOverlay")) self.add_step(TutorialStep("Run the simulation", """

      We can now run the simualtion and view the results. Press CTRL+G to enter game mode and see the dice prefab instance fly in the air an spin, then fall and collide with the ground plane. Press ESC to exit. -

      """)) +


      Step 20 of 20

      """)) def on_tutorial_start(self): print("Starting PhysX Rigid Body tutorial.") - - def set_step_number(self, x): - self.current_step_index = x - - def set_simulate_on(self): - """ simulate button has been clicked """ - self.simulate_clicked = True - - def set_simulate_off(self): - self.simulate_clicked = False - def on_tutorial_end(self): - print("PhysX Rigid Body tutorial complete!") - - """ - per-step functions - """ - def delete_shader_ball(self): - editor.EditorComponentAPIBus(bus.Broadcast, 'SetVisibleEnforcement', True) - searchFilter = entity.SearchFilter() - searchFilter.names = ['Shader Ball'] - searchResult = entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) - shaderBallId = searchResult[0] - editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', shaderBallId) - - - def focus_dice_prefab(self): - # Instantiate and position the prefab - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - dice_prefab = prefab.PrefabPublicRequestBus(bus.Broadcast, 'InstantiatePrefab', test_prefab_path, entity.EntityId(), position) - search_filter2 = entity.SearchFilter() - search_filter2.names = ["20-sided-dice"] - prefab_lc_root = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter2)[0] - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root) - - def edit_dice_components(self): - e1_reached = False #Add Rigid Body and Collider Components - e2_reached = False #Edit Rigid Body and Collider Components - - #Create new Dice Entity parented to Prefab - newEntity5 = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - editor.EditorEntityAPIBus(bus.Event, 'SetName', newEntity5, "20-sided-dice-2") - #Add Mesh, Material, and Position Components to the Entity - meshComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Position"], entity.EntityType().Game)[0] - materialComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Material"], entity.EntityType().Game)[0] - positionComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["Mesh"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [meshComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [materialComponentTypeId]) - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [positionComponentTypeId]) - if e1_reached == True: - #Add PhysX Collider Component and Rigid Body Component - colliderComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [colliderComponentTypeId]) - rigidBodyComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Rigid Body"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', newEntity5, [rigidBodyComponentTypeId]) - - #Edit Linear Velocity - #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - if e2_reached == True: - #todo: break these into subfunctions - rigid_body_id = general.find_game_entity(newEntity5) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - - #Edit Linear Velocity - #Edit Angular Velocity - test_prefab_path = os.path.relpath("20-sided-dice/20-sided-dice.prefab") - - rigid_body_id = general.find_game_entity(newEntity5) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetGravityEnabled", newEntity5, True) - azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", rigidBodyComponentTypeId, 0.06) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigid_body_id, 1.0) - azlmbr.physics.RigidBodyRequestBus(bus.Event, "SetLinearVelocity", rigidBodyComponentTypeId, 3.0) - - def create_new_collider(self): - search_filter3 = entity.SearchFilter() - search_filter3.names = ["Sun"] - prefab_lc_root3 = entity.SearchBus(bus.Broadcast, 'SearchEntities', search_filter3)[0] - azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", prefab_lc_root3) - - transform = math.Transform_CreateIdentity() - position = math.Vector3(0.0, 0.0, 5.0) - transform.invoke('SetPosition', position) - collider_entity = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - shapeComponentTypeId = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', ["PhysX Shape Collider"], entity.EntityType().Game)[0] - editor.EditorComponentAPIBus(bus.Broadcast, 'AddComponentsOfType', collider_entity, [shapeComponentTypeId]) - - def enter_game_mode(self): - helper.enter_game_mode(Tests.enter_game_mode) - - def simulate(self): - #if simulate_clicked is true - step_count = self.current_step_index - print(step_count) - class RigidBody: - gravity_enabled = False - collison_occued = False - mass = 0.0 - Steps = [0, 0, 0, 0, 0] - for x in Steps: - pass - print("entered simulate") - self.delete_shader_ball - print("entered simulate") - - Steps[0] = 1 - self.focus_dice_prefab - Steps[1] = 1 - #self.edit_dice_components - Steps[2] = 1 - #self.create_new_collider - Steps[3] = 1 - #self.enter_game_mode - Steps[4] = 1 \ No newline at end of file + print("PhysX Rigid Body tutorial complete!") \ No newline at end of file diff --git a/Editor/Scripts/tutorial.py b/Editor/Scripts/tutorial.py index 2ba7201..5bad28c 100644 --- a/Editor/Scripts/tutorial.py +++ b/Editor/Scripts/tutorial.py @@ -1,16 +1,17 @@ """ Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. - SPDX-License-Identifier: Apache-2.0 OR MIT """ +from asyncio.windows_events import NULL import json import os + # Class that describes a step in the tutorial class TutorialStep: - def __init__(self, title, content, highlight_pattern=None): + def __init__(self, title, content, highlight_pattern=None, highlight_parent=None, highlight_index=0): self.title = title self.content = content @@ -19,17 +20,16 @@ def __init__(self, title, content, highlight_pattern=None): # This pattern will be passed to `editor_python_test_tools.pyside_utils` # to find the widget/item. See its documentation for supported patterns self.highlight_pattern = highlight_pattern + self.highlight_parent = highlight_parent + self.highlight_index = highlight_index self.prev_step = None self.next_step = None - self.current_step_index = 0 - self.simulate_clicked = False # Method that will be called when the step starts # A step class can override this method if they need # to setup any special handling/listeners def on_step_start(self): - # Automated testing scripts go here pass # Method that will be called after a step has ended @@ -38,9 +38,6 @@ def on_step_start(self): def on_step_end(self): pass - def simulate(self): - pass - def get_title(self): return self.title @@ -50,15 +47,17 @@ def get_content(self): def get_highlight_pattern(self): return self.highlight_pattern + def get_highlight_parent(self): + return self.highlight_parent + + def get_highlight_index(self): + return self.highlight_index - # Top-level entry point for describing a tutorial which is made up of a series of steps class Tutorial: def __init__(self): self.steps = [] self.title = "" - self.current_step_index = 0 - self.simulate_clicked = False @classmethod def create_from_json_file(cls, file_path): @@ -82,7 +81,9 @@ def create_from_json_file(cls, file_path): title = step["title"] content = step["content"] highlight_pattern = step.get("highlight_pattern", None) - new_step = TutorialStep(title, content, highlight_pattern) + highlight_parent = step.get("highlight_parent", None) + highlight_index = step.get("highlight_index", None) + new_step = TutorialStep(title, content, highlight_pattern, highlight_parent, highlight_index) new_tutorial.add_step(new_step) return new_tutorial @@ -118,4 +119,4 @@ def get_first_step(self): return None def get_steps(self): - return self.steps + return self.steps \ No newline at end of file diff --git a/Editor/Scripts/tutorial_for_scripts.py b/Editor/Scripts/tutorial_for_scripts.py deleted file mode 100644 index 49a2db3..0000000 --- a/Editor/Scripts/tutorial_for_scripts.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" -import os, sys -import azlmbr.bus as bus # type: ignore -import azlmbr.entity as entity # type: ignore -import azlmbr.editor as editor # type: ignore -from azlmbr.entity import EntityId # type: ignore -import azlmbr.paths as paths -from tutorial import Tutorial, TutorialStep # type: ignore -import editor_python_test_tools.pyside_utils as pyside_utils - -class TutorialForScripts(Tutorial): - FILE_PATH = os.path.join(paths.projectroot, "TestAssets", "test_file.scriptevents") - - def __init__(self): - super(TutorialForScripts, self).__init__() - - self.title = "Entity Creating and Naming" - - self.add_step(TutorialStep("Entity Creating and Naming", - """

      My goals for this tutorial are to use scripts - to create an entity, to name an entity, and to delete an entity. - Future goals: open a window (tools-> ___), - create a script to apply the 'result' of a tutorial to an entity

      """)) - - def on_step_start(self): - print("Starting the select Entity step") - rootEntityId = editor.ToolsApplicationRequestBus(bus.Broadcast, 'CreateNewEntity', EntityId()) - #editor.ToolsApplicationRequestBus(bus.Broadcast, 'DeleteEntityAndAllDescendants', rootEntityId) - - # editor.EditorComponentAPIBus(bus.Broadcast, 'HasComponentOfType', newEntityId, meshComponentTypeId) - # editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH) - # SCRIPTS I HAVE TRIED - # self.generate_variable_test_output(self, "create") - # get_action_for_menu_path - # _get_children --> the direct descendants from a given PySide object. - - def on_tutorial_start(self): - print("Starting tutorial for scripts tutorial.") - - def on_tutorial_end(self): - print("Tutorial for scripts complete!") - diff --git a/Editor/Scripts/utils.py b/Editor/Scripts/utils.py deleted file mode 100644 index a4b7f8e..0000000 --- a/Editor/Scripts/utils.py +++ /dev/null @@ -1,568 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -import json -import math -import os -import time -import traceback -from typing import Callable, Tuple - -import azlmbr -import azlmbr.bus as bus -import azlmbr.editor as editor -import azlmbr.legacy.general as general -import azlmbr.multiplayer as multiplayer -import azlmbr.debug -import ly_test_tools.environment.waiter as waiter -import ly_test_tools.environment.process_utils as process_utils -import azlmbr.entity -from azlmbr.entity import EntityId - -class FailFast(Exception): - """ - Raise to stop proceeding through test steps. - """ - pass - - -class TestHelper: - @staticmethod - def init_idle(): - general.idle_enable(True) - - @staticmethod - def create_level(level_name: str) -> bool: - """ - :param level_name: The name of the level to be created - :return: True if ECreateLevelResult returns 0, False otherwise with logging to report reason - """ - Report.info(f"Creating level {level_name}") - - # Use these hardcoded values to pass expected values for old terrain system until new create_level API is - # available - heightmap_resolution = 1024 - heightmap_meters_per_pixel = 1 - terrain_texture_resolution = 4096 - use_terrain = False - - result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel, - terrain_texture_resolution, use_terrain) - - # Result codes are ECreateLevelResult defined in CryEdit.h - if result == 1: - Report.info(f"{level_name} level already exists") - elif result == 2: - Report.info("Failed to create directory") - elif result == 3: - Report.info("Directory length is too long") - elif result != 0: - Report.info("Unknown error, failed to create level") - else: - Report.info(f"{level_name} level created successfully") - - return result == 0 - - @staticmethod - def open_level(directory : str, level : str, no_prompt: bool = True): - # type: (str, str) -> None - """ - :param level: the name of the level folder in AutomatedTesting\\Physics\\ - :return: None - """ - # Make sure we are not in game mode - if general.is_in_game_mode(): - general.exit_game_mode() - TestHelper.wait_for_condition(lambda : not general.is_in_game_mode(), 1.0) - assert not general.is_in_game_mode(), "Editor was in gamemode when opening the level and was unable to exit from it" - - Report.info("Open level {}/{}".format(directory, level)) - if no_prompt: - success = general.open_level_no_prompt(os.path.join(directory, level)) - else: - success = general.open_level(os.path.join(directory, level)) - - if not success: - open_level_name = general.get_current_level_name() - if open_level_name == level: - Report.info("{} was already opened".format(level)) - else: - assert False, "Failed to open level: {} does not exist or is invalid".format(level) - - # FIX-ME: Expose call for checking when has been finished loading and change this frame waiting - # Jira: LY-113761 - general.idle_wait_frames(200) - - @staticmethod - def enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None: - """ - :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. - :return: None - """ - Report.info("Entering game mode") - general.enter_game_mode() - - TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0) - Report.critical_result(msgtuple_success_fail, general.is_in_game_mode()) - - @staticmethod - def find_line(window, line, print_infos): - """ - Looks for an expected line in a list of tracer log lines - :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. - :param line: The log message to search for. - :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints - :return: True if the line is found, otherwise false. - """ - for printInfo in print_infos: - if printInfo.window == window.strip() and printInfo.message.strip() == line: - return True - return False - - @staticmethod - def succeed_if_log_line_found(window, line, print_infos, time_out): - """ - Looks for a line in a list of tracer log lines and reports success if found. - :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. - :param line: The log message we're hoping to find. - :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints - :param time_out: The total amount of time to wait before giving up looking for the expected line. - :return: No return value, but if the message is found, a successful critical result is reported; otherwise failure. - """ - TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out) - Report.critical_result(("Found expected line: " + line, "Failed to find expected line: " + line), TestHelper.find_line(window, line, print_infos)) - - @staticmethod - def fail_if_log_line_found(window, line, print_infos, time_out): - """ - Reports a failure if a log line in a list of tracer log lines is found. - :param window: The log's window name. For example, logs printed via script-canvas use the "Script" window. - :param line: The log message we're hoping to not find. - :param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints - :param time_out: The total amount of time to wait before giving up looking for the unexpected line. If time runs out and we don't see the unexpected line then report a success. - :return: No return value, but if the line is found, a failed critical result is reported; otherwise success. - """ - TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out) - Report.critical_result(("Unexpected line not found: " + line, "Unexpected line found: " + line), not TestHelper.find_line(window, line, print_infos)) - - @staticmethod - def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None: - """ - :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. - :return: None - """ - Report.info("Entering game mode") - - with Tracer() as section_tracer: - # enter game-mode. - # game-mode in multiplayer will also launch ServerLauncher.exe and connect to the editor - multiplayer.PythonEditorFuncs_enter_game_mode() - - # make sure the server launcher binary exists - TestHelper.fail_if_log_line_found("MultiplayerEditor", "LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5) - - # make sure the server launcher is running - waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0) - - TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor has connected to the editor-server.", section_tracer.prints, 120.0) - - TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0) - - TestHelper.succeed_if_log_line_found("EditorServer", "System: Editor Server completed receiving the editor's level assets, responding to Editor...", section_tracer.prints, 5.0) - - TestHelper.succeed_if_log_line_found("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0) - - TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0) - Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode()) - - @staticmethod - def exit_game_mode(msgtuple_success_fail : Tuple[str, str]): - # type: (tuple) -> None - """ - :param msgtuple_success_fail: The tuple with the expected/unexpected messages for exiting game mode. - :return: None - """ - Report.info("Exiting game mode") - general.exit_game_mode() - - TestHelper.wait_for_condition(lambda : not general.is_in_game_mode(), 1.0) - Report.critical_result(msgtuple_success_fail, not general.is_in_game_mode()) - - @staticmethod - def close_editor(): - general.exit_no_prompt() - - @staticmethod - def fail_fast(message=None): - # type: (str) -> None - """ - A state has been reached where progressing in the test is not viable. - raises FailFast - :return: None - """ - if message: - Report.info("Fail fast message: {}".format(message)) - raise FailFast() - - @staticmethod - def wait_for_condition(function, timeout_in_seconds): - # type: (function, float) -> bool - """ - **** Will be replaced by a function of the same name exposed in the Engine***** - a function to run until it returns True or timeout is reached - the function can have no parameters and - waiting idle__wait_* is handled here not in the function - :param function: a function that returns a boolean indicating a desired condition is achieved - :param timeout_in_seconds: when reached, function execution is abandoned and False is returned - """ - - with Timeout(timeout_in_seconds) as t: - while True: - try: - general.idle_wait_frames(1) - except: - Report.info("WARNING: Couldn't wait for frame") - - if t.timed_out: - return False - - ret = function() - if not isinstance(ret, bool): - raise TypeError("return value for wait_for_condition function must be a bool") - if ret: - return True - - @staticmethod - def close_error_windows(): - """ - Closes Error Report and Error Log windows that block focus if they are visible. - :return: None - """ - if general.is_pane_visible("Error Report"): - general.close_pane("Error Report") - if general.is_pane_visible("Error Log"): - general.close_pane("Error Log") - - @staticmethod - def close_display_helpers(): - """ - Closes helper gizmos, anti-aliasing, and FPS meters. - :return: None - """ - if general.is_helpers_shown(): - general.toggle_helpers() - general.idle_wait(1.0) - general.idle_wait(1.0) - general.run_console("r_displayInfo=0") - general.idle_wait(1.0) - - -class Timeout: - # type: (float) -> None - """ - contextual timeout - :param seconds: float seconds to allow before timed_out is True - """ - - def __init__(self, seconds): - self.seconds = seconds - - def __enter__(self): - self.die_after = time.time() + self.seconds - return self - - def __exit__(self, type, value, traceback): - pass - - @property - def timed_out(self): - return time.time() > self.die_after - - -class Report: - _results = [] - _exception = None - - @staticmethod - def start_test(test_function : Callable): - """ - Runs the test, outputs the report and asserts in case of failure. - @param: The test function to execute - """ - Report._results = [] - Report._exception = None - general.test_output(f"Starting test {test_function.__name__}...\n") - try: - test_function() - except Exception as ex: - Report._exception = traceback.format_exc() - - success, report_str = Report.get_report(test_function) - # Print on the o3de console, for debugging purpuses - print(report_str) - # Print the report on the piped stdout of the application - general.test_output(report_str) - assert success, f"Test {test_function.__name__} failed" - - @staticmethod - def get_report(test_function : Callable) -> (bool, str): - """ - Outputs infomation on the editor console for the test - @param msg: message to output - @return: (success, report_information) tuple - """ - success = True - report = f"Test {test_function.__name__} finished.\nReport:\n" - # report_dict is a JSON that can be used to parse test run information from a external runner - # The regular report string is intended to be used for manual debugging - filename = os.path.splitext(os.path.basename(test_function.__code__.co_filename))[0] - report_dict = {'name' : filename, 'success' : True, 'exception' : None} - for result in Report._results: - passed, info = result - success = success and passed - test_result_info = "" - if passed: - test_result_info = f"[SUCCESS] {info}" - else: - test_result_info = f"[FAILED ] {info}" - report += f"{test_result_info}\n" - if Report._exception: - exception_str = Report._exception[:-1].replace("\n", "\n ") - report += "EXCEPTION raised:\n %s\n" % exception_str - report_dict['exception'] = exception_str - success = False - report += "Test result: " + ("SUCCESS" if success else "FAILURE") - report_dict['success'] = success - report_dict['output'] = report - report_json_str = json.dumps(report_dict) - # For helping parsing, the json will be always contained between JSON_START JSON_END - report += f"\nJSON_START({report_json_str})JSON_END\n" - return success, report - - @staticmethod - def info(msg : str): - """ - Outputs infomation on the editor console for the test - @param msg: message to output - """ - print("Info: {}".format(msg)) - - @staticmethod - def success(msgtuple_success_fail : Tuple[str, str]): - """ - Given a test string tuple (success_string, failure_string), registers the test result as success - @param msgtuple_success_fail: Two element tuple of success and failure strings - """ - outcome = "Success: {}".format(msgtuple_success_fail[0]) - print(outcome) - Report._results.append((True, outcome)) - - @staticmethod - def failure(msgtuple_success_fail : Tuple[str, str]): - """ - Given a test string tuple (success_string, failure_string), registers the test result as failed - @param msgtuple_success_fail: Two element tuple of success and failure strings - """ - outcome = "Failure: {}".format(msgtuple_success_fail[1]) - print(outcome) - Report._results.append((False, outcome)) - - @staticmethod - def result(msgtuple_success_fail : Tuple[str, str], outcome : bool): - """ - Given a test string tuple (success_string, failure_string), registers the test result based on the - given outcome - @param msgtuple_success_fail: Two element tuple of success and failure strings - @param outcome: True or False if the result has been a sucess or failure - """ - if not isinstance(outcome, bool): - raise TypeError("outcome argument must be a bool") - - if outcome: - Report.success(msgtuple_success_fail) - else: - Report.failure(msgtuple_success_fail) - return outcome - - @staticmethod - def critical_result(msgtuple_success_fail : Tuple[str, str], outcome : bool, fast_fail_message : str = None): - # type: (tuple, bool, str) -> None - """ - if outcome is False we will fail fast - :param msgtuple_success_fail: messages to print based on the outcome - :param outcome: success (True) or failure (False) - :param fast_fail_message: [optional] message to include on fast fail - """ - if not isinstance(outcome, bool): - raise TypeError("outcome argument must be a bool") - - if not Report.result(msgtuple_success_fail, outcome): - TestHelper.fail_fast(fast_fail_message) - - # DEPRECATED: Use vector3_str() - @staticmethod - def info_vector3(vector3 : azlmbr.math.Vector3, label : str ="", magnitude : float =None): - # type: (azlmbr.math.Vector3, str, float) -> None - """ - prints the vector to the Report.info log. If applied, label will print first, - followed by the vector's values (x, y, z,) to 2 decimal places. Lastly if the - magnitude is supplied, it will print on the third line. - :param vector3: a azlmbr.math.Vector3 object to print - prints in [x: , y: , z: ] format. - :param label: [optional] A string to print before printing the vector3's contents - :param magnitude: [optional] the vector's magnitude to print after the vector's contents - :return: None - """ - if label != "": - Report.info(label) - Report.info(" x: {:.2f}, y: {:.2f}, z: {:.2f}".format(vector3.x, vector3.y, vector3.z)) - if magnitude is not None: - Report.info(" magnitude: {:.2f}".format(magnitude)) - - -''' -Utility for scope tracing errors and warnings. -Usage: - ... - with Tracer() as section_tracer: - # section were we are interested in capturing errors/warnings/asserts - ... - Report.result(Tests.warnings_not_found_in_section, not section_tracer.has_warnings) -''' -class Tracer: - def __init__(self): - self.warnings = [] - self.errors = [] - self.asserts = [] - self.prints = [] - self.has_warnings = False - self.has_errors = False - self.has_asserts = False - self.handler = None - - class WarningInfo: - def __init__(self, args): - self.window = args[0] - self.filename = args[1] - self.line = args[2] - self.function = args[3] - self.message = args[4] - - def __str__(self): - return f"Warning: [{self.filename}:{self.function}:{self.line}]: [{self.window}] {self.message}" - - def __repr__(self): - return f"[Warning: {self.message}]" - - class ErrorInfo: - def __init__(self, args): - self.window = args[0] - self.filename = args[1] - self.line = args[2] - self.function = args[3] - self.message = args[4] - - def __str__(self): - return f"Error: [{self.filename}:{self.function}:{self.line}]: [{self.window}] {self.message}" - - def __repr__(self): - return f"[Error: {self.message}]" - - class AssertInfo: - def __init__(self, args): - self.filename = args[0] - self.line = args[1] - self.function = args[2] - self.message = args[3] - - def __str__(self): - return f"Assert: [{self.filename}:{self.function}:{self.line}]: {self.message}" - - def __repr__(self): - return f"[Assert: {self.message}]" - - class PrintInfo: - def __init__(self, args): - self.window = args[0] - self.message = args[1] - - def _on_warning(self, args): - warningInfo = Tracer.WarningInfo(args) - self.warnings.append(warningInfo) - Report.info("Tracer caught Warning: %s" % warningInfo.message) - self.has_warnings = True - return False - - def _on_error(self, args): - errorInfo = Tracer.ErrorInfo(args) - self.errors.append(errorInfo) - Report.info("Tracer caught Error: %s" % errorInfo.message) - self.has_errors = True - return False - - def _on_assert(self, args): - assertInfo = Tracer.AssertInfo(args) - self.asserts.append(assertInfo) - Report.info("Tracer caught Assert: %s:%i[%s] \"%s\"" % (assertInfo.filename, assertInfo.line, assertInfo.function, assertInfo.message)) - self.has_asserts = True - return False - - def _on_printf(self, args): - printInfo = Tracer.PrintInfo(args) - self.prints.append(printInfo) - return False - - def __enter__(self): - self.handler = azlmbr.debug.TraceMessageBusHandler() - self.handler.connect(None) - self.handler.add_callback("OnPreAssert", self._on_assert) - self.handler.add_callback("OnPreWarning", self._on_warning) - self.handler.add_callback("OnPreError", self._on_error) - self.handler.add_callback("OnPrintf", self._on_printf) - return self - - def __exit__(self, type, value, traceback): - self.handler.disconnect() - self.handler = None - return False - - -class AngleHelper: - @staticmethod - def is_angle_close(x_rad, y_rad, tolerance): - # type: (float, float , float) -> bool - """ - compare if 2 angles measured in radians are close - :param x_rad: angle in radians - :param y_rad: angle in radians - :param tolerance: the tolerance to define close - :return: bool - """ - sinx_sub_siny = math.sin(x_rad) - math.sin(y_rad) - cosx_sub_cosy = math.cos(x_rad) - math.cos(y_rad) - r = sinx_sub_siny * sinx_sub_siny + cosx_sub_cosy * cosx_sub_cosy - diff = math.acos((2.0 - r) / 2.0) - return abs(diff) <= tolerance - - @staticmethod - def is_angle_close_deg(x_deg, y_deg, tolerance): - # type: (float, float , float) -> bool - """ - compare if 2 angles measured in degrees are close - :param x_deg: angle in degrees - :param y_deg: angle in degrees - :param tolerance: the tolerance to define close - :return: bool - """ - return AngleHelper.is_angle_close(math.radians(x_deg), math.radians(y_deg), tolerance) - - -def vector3_str(vector3): - return "(x: {:.2f}, y: {:.2f}, z: {:.2f})".format(vector3.x, vector3.y, vector3.z) - - -def aabb_str(aabb): - return "[Min: %s, Max: %s]" % (vector3_str(aabb.min), vector3_str(aabb.max)) \ No newline at end of file From 32199eb54214952108a6f13c21dba7b1e229253c Mon Sep 17 00:00:00 2001 From: Madeleine Date: Thu, 4 Aug 2022 08:09:01 -0700 Subject: [PATCH 15/16] new strategy Signed-off-by: Madeleine --- Editor/Scripts/interactivetutorials_dialog.py | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index d7663e1..a800e53 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -8,8 +8,8 @@ Generated from O3DE PythonToolGem Template""" from PySide2 import QtCore -from PySide2.QtCore import QMargins, QStringListModel, Qt -from PySide2.QtGui import QColor, QPainter, QPen +from PySide2.QtCore import QMargins, QStringListModel, Qt, QVariantAnimation +from PySide2.QtGui import QColor, QPainter, QPen, QBrush from PySide2.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QListView, QMessageBox, QPushButton, QStackedWidget, QTextEdit, QVBoxLayout, QWidget ) @@ -31,8 +31,8 @@ def __init__(self, parent=None): super(HighlightWidget, self).__init__(parent) self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool | Qt.WindowTransparentForInput | Qt.WindowDoesNotAcceptFocus | Qt.WindowStaysOnTopHint) - self.setAttribute(Qt.WA_TranslucentBackground); - self.setAttribute(Qt.WA_NoSystemBackground); + self.setAttribute(Qt.WA_TranslucentBackground) + self.setAttribute(Qt.WA_NoSystemBackground) self.setAttribute(Qt.WA_TransparentForMouseEvents) self.border_width = 5 @@ -43,6 +43,30 @@ def paintEvent(self, event): pen.setWidth(self.border_width) painter.setPen(pen) painter.drawRect(event.rect()) + #print(self.width) + #print(self.height) + #highlights widget blue + if self.width < 50 and self.height < 50: + painter.setOpacity(0.3) + painter.fillRect(event.rect(), QBrush(QColor ("blue"))) + + #todo: highlight parent area + #highlight parent widget without highlighting the content of the widget itself + #1 highlight parent widget + #2 redraw filled rectangle regular color + #or + #1 fill 4 rectangles around the parent rectangle + if self.width < 50 and self.height < 50: + painter.setOpacity(0.3) + #draw 4 rectangles around "self" + #painter.fillRect() + #painter.fillRect() + #painter.fillRect() + #painter.fillRect() + painter.fillRect(event.rect(), QBrush(QColor ("blue"))) + + #todo: pulse effect + #edit the scale property as this will make the blob shrink and grow back gently def update_widget(self, item): if item: From b12421c092c406cc11f5fee4a5930150e83ad303 Mon Sep 17 00:00:00 2001 From: Madeleine Date: Thu, 4 Aug 2022 11:56:46 -0700 Subject: [PATCH 16/16] breaking animate effect Signed-off-by: Madeleine --- Editor/Scripts/interactivetutorials_dialog.py | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Editor/Scripts/interactivetutorials_dialog.py b/Editor/Scripts/interactivetutorials_dialog.py index a800e53..aa32013 100644 --- a/Editor/Scripts/interactivetutorials_dialog.py +++ b/Editor/Scripts/interactivetutorials_dialog.py @@ -8,7 +8,7 @@ Generated from O3DE PythonToolGem Template""" from PySide2 import QtCore -from PySide2.QtCore import QMargins, QStringListModel, Qt, QVariantAnimation +from PySide2.QtCore import QMargins, QStringListModel, Qt, QVariantAnimation, QRect from PySide2.QtGui import QColor, QPainter, QPen, QBrush from PySide2.QtWidgets import (QDialog, QDialogButtonBox, QLabel, QListView, QMessageBox, QPushButton, QStackedWidget, QTextEdit, QVBoxLayout, QWidget @@ -45,28 +45,34 @@ def paintEvent(self, event): painter.drawRect(event.rect()) #print(self.width) #print(self.height) - #highlights widget blue + + #option 1: highlight the content of the widget if self.width < 50 and self.height < 50: painter.setOpacity(0.3) painter.fillRect(event.rect(), QBrush(QColor ("blue"))) - #todo: highlight parent area - #highlight parent widget without highlighting the content of the widget itself - #1 highlight parent widget - #2 redraw filled rectangle regular color - #or - #1 fill 4 rectangles around the parent rectangle + #option 2: highlight the area around the widget not including the widget if self.width < 50 and self.height < 50: painter.setOpacity(0.3) + topLeftX = event.rect().topLeft().x + #topRightX = event.rect().topRight().x + bottomLeftX = event.rect().bottomLeft().x + bottomRightX = event.rect().bottomRight().x + topLeftY = event.rect().topLeft().y + #topRightY = event.rect().topRight().y + bottomLeftY = event.rect().bottomLeft().y + #bottomRightY = event.rect().bottomRight().y #draw 4 rectangles around "self" - #painter.fillRect() - #painter.fillRect() - #painter.fillRect() - #painter.fillRect() - painter.fillRect(event.rect(), QBrush(QColor ("blue"))) + painter.fillRect(bottomLeftX - self.width, bottomLeftY, self.width, self.height, QBrush(QColor ("blue"))) + painter.fillRect(bottomRightX, bottomLeftY, self.width, self.height, QBrush(QColor ("blue"))) + painter.fillRect(topLeftX - self.width, topLeftY, self.width * 3, self.height, QBrush(QColor ("blue"))) + painter.fillRect(topLeftX - self.width, bottomLeftY - self.height, self.width * 3, self.height, QBrush(QColor ("blue"))) + + #option 3: add a pulse effect + if self.width < 50 and self.height < 50: + update_widget(self, event) + #edit the outline scale property as this will make the widget shrink and grow back gently - #todo: pulse effect - #edit the scale property as this will make the blob shrink and grow back gently def update_widget(self, item): if item: @@ -82,6 +88,14 @@ def update_widget(self, item): else: self.hide() + blinking = False + if blinking: + painter = QPainter(self) + pen = QPen(QColor(255, 0, 152)) #Rhodamine Red + pen.setWidth(self.border_width + 10) + painter.setPen(pen) + painter.drawRect(item) + class InteractiveTutorialsDialog(QDialog): def __init__(self, parent=None): super(InteractiveTutorialsDialog, self).__init__(parent)