Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unreal: decouple UIs to separate process #4075

Open
wants to merge 63 commits into
base: develop
Choose a base branch
from

Conversation

simonebarbieri
Copy link
Member

Brief description

Run unreal commands from OP through websocket communication.

Description

UE has the ability to have its python to be called remotely. This PR allow us to run all UIs as separete processes that are comunicating with UE. We'll no longer need to install PySide2 to Unreal.

@github-actions github-actions bot added this to the next-patch milestone Nov 7, 2022
@simonebarbieri simonebarbieri marked this pull request as ready for review July 21, 2023 11:30
Copy link
Member

@Minkiu Minkiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested it, but code looks good, there are some docstrings missing here and ther though.

And was wondering (in another PR) would be worth wrapping all the functions we pass to send_request the same way we do with containerise.

@classmethod
def client(cls):
if not cls.communicator:
return None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a warning here?


@classmethod
def execute_george(cls, george_script):
"""Execute passed goerge script in TVPaint."""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

george or goerge ? Anyway what's this exactly? Seems to be TVPaint related?

class BaseUnrealRpc(JsonRpc):
def __init__(self, communication_obj, route_name="", **kwargs):
super().__init__(**kwargs)
self.requests_ids = collections.defaultdict(lambda: 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be this the same as doing collections.defaultdict(int) ? Im not that versed in defaultdict, so not sure what this is initializing it as?

)
result = future.result()

not_found = object()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this could be: response = not_found = object()

log.warning("- item is already processed")
return

callback = self.callback
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these variable assignments necessary?


def update_context_data(self, data, changes):
content_path = unreal.Paths.project_content_dir()
op_ctx = content_path + CONTEXT_CONTAINER
attempts = 3
for i in range(attempts):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use enumerate here so we don't need to "calculate" the index (really a subjective thing):

for i, _ in enumerate(range(attempts)):
    try:
        with open(op_ctx, "w+") as f:
            json.dump(data, f)
        break
    except IOError as e:
        if i == attempts - 1:
            raise IOError(
                        "Failed to write context data. Aborting.") from e
        unreal_log(
            "Failed to write context data. Retrying...",
            "warning"
        )
        time.sleep(3)
        continue

@@ -152,67 +154,54 @@ def _register_events():
pass


def send_request(request: str, params: dict = None):
communicator = CommunicationWrapper.communicator
if ret_value := ast.literal_eval(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we jsut in case wrap this with try...except? In case it's not able to eval...

@@ -314,8 +314,8 @@ def create_unreal_project(project_name: str,
raise NotImplementedError("Unsupported platform")
if not python_path.exists():
raise RuntimeError(f"Unreal Python not found at {python_path}")
subprocess.check_call(
[python_path.as_posix(), "-m", "pip", "install", "pyside2"])
# subprocess.check_call(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is not needed, shall we delete it?

@@ -49,178 +44,48 @@ def create_with_new_sequence(
):
# If the option to create a new level sequence is selected,
# create a new level sequence and a master level.
root = "/Game/Ayon/Sequences"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be root = f"{self.root}/Sequences" ?

return (
next(
(
loader for loader in loaders if loader.__name__ == name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably unpack this so it's easier to follow/readable

@moonyuet
Copy link
Member

launcher works as expected to launch Unreal with AYON
image

But the Unreal can't open the ayon publisher as it hits the Attribute Error.

LogPython: Warning: Ayon: showing tools popup
LogScript: Error: Script Msg: Traceback (most recent call last):
  File "C:/Program Files/Epic Games/UE_5.1/Engine/Plugins/Marketplace/Ayon/Content/Python/init_unreal.py", line 24, in RunInPython_Popup
    ayon_host.show_tools_popup()
AttributeError: 'UnrealHost' object has no attribute 'show_tools_popup'
LogScript: Error: Script call stack:
    /Engine/PythonTypes.AyonIntegration.RunInPython_Popup

The detail log command from UE shown below:
Unreal_LOG.txt

Copy link
Member

@antirotor antirotor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I've been testing it in UE 5.3 (so I've made some changes in ynput/ayon-unreal-plugin). Anyway, I've hit few issues:

Publisher

Creating camera

When running camera creator:

Traceback (most recent call last):
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\pipeline\create\context.py", line 2030, in _create_with_unified_error
    result = creator.create(*args, **kwargs)
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\plugins\create\create_camera.py", line 27, in create
    instance_data["level"] = send_request("get_editor_world")
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\api\pipeline.py", line 162, in send_request
    return ret_value.get("return")
AttributeError: 'str' object has no attribute 'get'

Creating Layout

When running layout creator:

DEBUG:openpype.hosts.unreal.api.communication_server:Sending request to client localhost (get_editor_world, {}) id: 36
WARNING:CreateContext:Failed to run Creator with identifier "io.ayon.creators.unreal.layout". Creator error: 'str' object has no attribute 'get'

Creating Look

When running Look creator:

WARNING:CreateContext:Failed to run Creator with identifier "io.ayon.creators.unreal.look".
Traceback (most recent call last):
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\pipeline\create\context.py", line 2030, in _create_with_unified_error
    result = creator.create(*args, **kwargs)
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\plugins\create\create_look.py", line 40, in create
    pre_create_data["members"] = send_request(
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\api\pipeline.py", line 160, in send_request
    communicator.send_request(request, params)
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\api\communication_server.py", line 605, in send_request
    return self.websocket_rpc.send_request(
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\api\communication_server.py", line 291, in send_request
    raise Exception("Error happened: {}".format(error))
Exception: Error happened: {'code': -32000, 'message': 'Python Execution in Unreal Failed!', 'data': ''}

Creating Renders:

This happens probably because of json serialization:

WARNING:CreateContext:Failed to run Creator with identifier "io.ayon.creators.unreal.render".
Traceback (most recent call last):
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\pipeline\create\context.py", line 2030, in _create_with_unified_error
    result = creator.create(*args, **kwargs)
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\plugins\create\create_render.py", line 97, in create
    self.create_with_new_sequence(
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\plugins\create\create_render.py", line 50, in create_with_new_sequence
    master_lvl, sequence, seq_data = send_request(
  File "C:\Users\annat\Documents\Projects\OpenPype\sources\ayon-desktop\openpype\hosts\unreal\api\pipeline.py", line 159, in send_request
    if ret_value := ast.literal_eval(
  File "C:\Users\annat\.pyenv\pyenv-win\versions\3.9.13\lib\ast.py", line 62, in literal_eval
    node_or_string = parse(node_or_string, mode='eval')
  File "C:\Users\annat\.pyenv\pyenv-win\versions\3.9.13\lib\ast.py", line 50, in parse
    return compile(source, filename, mode, flags,
  File "<unknown>", line 1
    ('/Game/Ayon/Sequences/renderGenericMain/renderGenericMain_MasterLevel', '/Game/Ayon/Sequences/renderGenericMain/renderGenericMain.renderGenericMain', {'sequence': <Object '/Game/Ayon/Sequences/renderGenericMain/renderGenericMain.renderGenericMain' (0x00000B2D4B1E8700) Class 'LevelSequence'>, 'output': 'renderGenericMain', 'frame_range': (0, 150)})

                                                ^
SyntaxError: invalid syntax

Scene Inventory

This doesn't work - it won't list any loaded instances (ls() returning empty list). This might be caused by merging changes in ynput/ayon-unreal-plugin though.

Loading

Loading works, as far as I could test it - I would just add timestamp to server console.

TODO:

  • We need to test it in AYON
  • Resolve conflicts (merge 3.17 in)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
host: UE port to AYON size/XXL Denotes a PR changes 2500+ lines, ignoring general files sponsored Client endorsed or requested target: AYON target: OpenPype type: enhancement Enhancements to existing functionality type: feature Larger, user affecting changes and completely new things
Projects
Status: Change Requested
Development

Successfully merging this pull request may close these issues.

None yet

8 participants