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

Removed sys.path.append in RPC module #70

Merged
merged 4 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions docs/send2ue/customize/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,9 @@ There is a submodule within `send2ue` that can be used to make your own rpc call
a basic example of how you can force an asset to be renamed in the `post_import` method of an extension.
```python
from send2ue.core.extension import ExtensionBase
from send2ue.dependencies.unreal import remote_unreal_decorator
from send2ue.dependencies.rpc.factory import make_remote


@remote_unreal_decorator
def rename_unreal_asset(source_asset_path, destination_asset_path):
if unreal.EditorAssetLibrary.does_asset_exist(destination_asset_path):
unreal.EditorAssetLibrary.delete_asset(destination_asset_path)
Expand All @@ -343,11 +342,10 @@ class ExampleExtension(ExtensionBase):
name = 'example'
def post_import(self):
asset_path = self.asset_data[self.asset_id]['asset_path']
rename_unreal_asset(asset_path, f'{asset_path}_renamed_again')
remote_rename_unreal_asset = make_remote(rename_unreal_asset)
remote_rename_unreal_asset(asset_path, f'{asset_path}_renamed_again')
```
Notice how you can define remote unreal functions on the fly by just wrapping your function
with the `remote_unreal_decorator`. The RPC library has a factory that takes care of teleporting
your code and imports over to the open unreal editor.
Notice how you can define remote unreal functions on the fly by just passing a function reference to the `make_remote` function. The RPC library has a factory that takes care of teleporting your code and imports over to the open unreal editor.

!!! note

Expand Down
6 changes: 5 additions & 1 deletion src/addons/send2ue/core/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import os
import bpy
from ..dependencies.unreal import UnrealRemoteCalls, is_connected
from ..dependencies.unreal import is_connected
from ..dependencies.unreal import UnrealRemoteCalls as UnrealCalls
from ..dependencies.rpc.factory import make_remote

UnrealRemoteCalls = make_remote(UnrealCalls)


def set_property_error_message(property_name, error_message):
Expand Down
5 changes: 4 additions & 1 deletion src/addons/send2ue/core/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import bpy
from . import settings, extension
from ..constants import PathModes, ExtensionTasks, UnrealTypes
from ..dependencies.unreal import UnrealRemoteCalls
from ..dependencies.unreal import UnrealRemoteCalls as UnrealCalls
from .utilities import track_progress, get_asset_id
from ..dependencies.rpc.factory import make_remote

UnrealRemoteCalls = make_remote(UnrealCalls)


@track_progress(message='Importing asset "{attribute}"...', attribute='file_path')
Expand Down
5 changes: 4 additions & 1 deletion src/addons/send2ue/core/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import os
import bpy
from . import utilities, formatting, extension
from ..dependencies.unreal import UnrealRemoteCalls
from ..constants import BlenderTypes, PathModes, ToolInfo, Extensions, ExtensionTasks, RegexPresets
from ..dependencies.unreal import UnrealRemoteCalls as UnrealCalls
from ..dependencies.rpc.factory import make_remote

UnrealRemoteCalls = make_remote(UnrealCalls)


class ValidationManager:
Expand Down
105 changes: 105 additions & 0 deletions src/addons/send2ue/dependencies/rpc/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import inspect
import textwrap
import unittest
from typing import Any, Iterator, Optional, Union, List, Tuple, Callable
from xmlrpc.client import Fault

from .client import RPCClient
Expand Down Expand Up @@ -265,6 +266,110 @@ def decorate(cls):
return cls
return decorate

def execute_remotely(
port: int,
function: Callable,
args: Optional[Union[List, Tuple]] = None,
kwargs: Optional[dict] = None,
remap_pairs: Optional[List[Tuple[str, str]]] = None,
default_imports: Optional[List[str]] = None,
):
"""
Executes the given function remotely.
"""
if not args:
args = []

validate_file_is_saved(function)
validate_key_word_parameters(function, kwargs)
rpc_factory = RPCFactory(
rpc_client=RPCClient(port),
remap_pairs=remap_pairs,
default_imports=default_imports
)
return rpc_factory.run_function_remotely(function, args)

def _make_remote(
port: int,
remap_pairs: Optional[List[Tuple[str, str]]] = None,
default_imports: Optional[List[str]] = None
):
def decorator(function):
def wrapper(*args, **kwargs):
validate_key_word_parameters(function, kwargs)
return execute_remotely(
function=function,
args=args,
kwargs=kwargs,
port=port,
remap_pairs=remap_pairs,
default_imports=default_imports
)

return wrapper

return decorator


def get_all_parent_classes(cls) -> Iterator[Any]:
"""
Gets all parent classes recursively upward from the given class.
"""
for _cls in cls.__bases__:
if object not in _cls.__bases__ and len(_cls.__bases__) >= 1:
yield from get_all_parent_classes(_cls)
yield _cls

def make_remote(
reference: Any,
port: Optional[int] = None,
default_imports: Optional[List[str]] = None,
) -> Callable:
"""
Makes the given class or function run remotely when invoked.
"""
if not default_imports:
default_imports = ['import unreal']
remap_pairs = []
unreal_port = int(os.environ.get('UNREAL_PORT', 9998))

# use a different remap pairs when inside a container
if os.environ.get('TEST_ENVIRONMENT'):
unreal_port = int(os.environ.get('UNREAL_PORT', 8998))
remap_pairs = [(os.environ.get('HOST_REPO_FOLDER', ''), os.environ.get('CONTAINER_REPO_FOLDER', ''))]

if not port:
port = unreal_port

# if this is not a class then decorate it
if not inspect.isclass(reference):
return _make_remote(
port=port,
remap_pairs=remap_pairs,
default_imports=default_imports
)(reference)

# if this is a class then decorate all its methods
methods = {}
for _cls in [*get_all_parent_classes(reference), reference]:
for attribute, value in _cls.__dict__.items():
# dont look at magic methods
if not attribute.startswith('__'):
validate_class_method(_cls, value)
# note that we use getattr instead of passing the value directly.
methods[attribute] = _make_remote(
port=port,
remap_pairs=remap_pairs,
default_imports=default_imports
)(getattr(_cls, attribute))

# return a new class with the decorated methods all in the same class
return type(
reference.__name__,
(object,),
methods
)


class RPCTestCase(unittest.TestCase):
"""
Expand Down
9 changes: 1 addition & 8 deletions src/addons/send2ue/dependencies/rpc/server.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import os
import sys
sys.path.append(os.path.dirname(__file__))

from base_server import BaseRPCServerManager
from .base_server import BaseRPCServerManager


class RPCServer(BaseRPCServerManager):
Expand All @@ -13,8 +11,3 @@ def __init__(self):
super(RPCServer, self).__init__()
self.name = 'RPCServer'
self.port = int(os.environ.get('RPC_PORT', 9998))


if __name__ == '__main__':
rpc_server = RPCServer()
rpc_server.start(threaded=False)
23 changes: 7 additions & 16 deletions src/addons/send2ue/dependencies/unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,17 @@
from xmlrpc.client import ProtocolError
from http.client import RemoteDisconnected

sys.path.append(os.path.dirname(__file__))
import rpc.factory
import remote_execution

try:
import unreal
except ModuleNotFoundError:
pass

REMAP_PAIRS = []
UNREAL_PORT = int(os.environ.get('UNREAL_PORT', 9998))

# use a different remap pairs when inside a container
if os.environ.get('TEST_ENVIRONMENT'):
UNREAL_PORT = int(os.environ.get('UNREAL_PORT', 8998))
REMAP_PAIRS = [(os.environ.get('HOST_REPO_FOLDER'), os.environ.get('CONTAINER_REPO_FOLDER'))]

# this defines a the decorator that makes function run as remote call in unreal
remote_unreal_decorator = rpc.factory.remote_call(
port=UNREAL_PORT,
default_imports=['import unreal'],
remap_pairs=REMAP_PAIRS,
)

unreal_response = ''


Expand Down Expand Up @@ -152,6 +140,8 @@ def run_commands(commands):
:param list commands: A formatted string of python commands that will be run by unreal engine.
:return str: The stdout produced by the remote python command.
"""
from . import remote_execution

# wrap the commands in a try except so that all exceptions can be logged in the output
commands = ['try:'] + add_indent(commands, '\t') + ['except Exception as error:', '\tprint(error)']

Expand All @@ -168,7 +158,8 @@ def is_connected():
Checks the rpc server connection
"""
try:
rpc_client = rpc.client.RPCClient(port=UNREAL_PORT)
from .rpc import client
rpc_client = client.RPCClient(port=UNREAL_PORT)
return rpc_client.proxy.is_running()
except (RemoteDisconnected, ConnectionRefusedError, ProtocolError):
return False
Expand All @@ -178,7 +169,8 @@ def set_rpc_env(key, value):
"""
Sets an env value on the unreal RPC server.
"""
rpc_client = rpc.client.RPCClient(port=UNREAL_PORT)
from .rpc import client
rpc_client = client.RPCClient(port=UNREAL_PORT)
rpc_client.proxy.set_env(key, value)


Expand Down Expand Up @@ -986,7 +978,6 @@ def run_import(self, import_type='control_rig'):
)


@rpc.factory.remote_class(remote_unreal_decorator)
class UnrealRemoteCalls:
@staticmethod
def get_lod_count(asset_path):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from send2ue.core.extension import ExtensionBase
from send2ue.core import utilities
from send2ue.constants import UnrealTypes
from send2ue.dependencies.unreal import UnrealRemoteCalls
from send2ue.dependencies.unreal import UnrealRemoteCalls as UnrealCalls
from send2ue.dependencies.rpc.factory import make_remote


class CreatePostImportAssetsForGroom(ExtensionBase):
Expand Down Expand Up @@ -56,6 +57,7 @@ def post_import(self, asset_data, properties):
mesh_asset_data = utilities.get_related_mesh_asset_data_from_groom_asset_data(asset_data)
groom_asset_path = asset_data.get('asset_path', '')
mesh_asset_path = mesh_asset_data.get('asset_path', '')
UnrealRemoteCalls = make_remote(UnrealCalls)

if not UnrealRemoteCalls.asset_exists(groom_asset_path):
return
Expand Down
5 changes: 3 additions & 2 deletions src/addons/send2ue/resources/extensions/instance_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
import os
from send2ue.constants import UnrealTypes
from send2ue.core.extension import ExtensionBase
from send2ue.dependencies.unreal import UnrealRemoteCalls
from send2ue.core.utilities import (
convert_blender_rotation_to_unreal_rotation,
convert_blender_to_unreal_location,
get_armature_modifier_rig_object,
get_asset_name
)
from send2ue.dependencies.unreal import UnrealRemoteCalls as UnrealCalls
from send2ue.dependencies.rpc.factory import make_remote

STATIC_MESH_INSTANCE_NAMES = []
SKELETAL_MESH_INSTANCE_NAMES = []
Expand Down Expand Up @@ -162,7 +163,7 @@ def post_import(self, asset_data, properties):
break

if unique_name:
UnrealRemoteCalls.instance_asset(
make_remote(UnrealCalls).instance_asset(
asset_data['asset_path'],
convert_blender_to_unreal_location(location),
convert_blender_rotation_to_unreal_rotation(rotation),
Expand Down
6 changes: 3 additions & 3 deletions tests/test_files/send2ue_extensions/example_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import bpy
from pprint import pprint
from send2ue.core.extension import ExtensionBase
from send2ue.dependencies.unreal import remote_unreal_decorator
from send2ue.dependencies.rpc.factory import make_remote


@remote_unreal_decorator
def rename_unreal_asset(source_asset_path, destination_asset_path):
if unreal.EditorAssetLibrary.does_asset_exist(destination_asset_path):
unreal.EditorAssetLibrary.delete_asset(destination_asset_path)
Expand Down Expand Up @@ -99,7 +98,8 @@ def post_import(self, asset_data, properties):
print('After the import task')
asset_path = asset_data.get('asset_path')
if asset_path:
rename_unreal_asset(asset_path, f'{asset_path}_renamed_again')
remote_rename_unreal_asset = make_remote(rename_unreal_asset)
remote_rename_unreal_asset(asset_path, f'{asset_path}_renamed_again')

def post_operation(self, properties):
"""
Expand Down
3 changes: 2 additions & 1 deletion tests/utils/base_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ def __init__(self, *args, **kwargs):

from utils.blender import BlenderRemoteCalls
from send2ue.dependencies.unreal import UnrealRemoteCalls
from send2ue.dependencies.rpc.factory import make_remote
self.blender = BlenderRemoteCalls
self.unreal = UnrealRemoteCalls
self.unreal = make_remote(UnrealRemoteCalls)

def setUp(self):
# load in the object from the file you will run tests with
Expand Down
Loading