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

Send to Unreal - macos and linux support #34

Merged
merged 11 commits into from
Jul 11, 2024
Binary file modified docs/send2ue/introduction/images/8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/send2ue/introduction/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Once you have enabled the plugins and project settings, you'll be prompted to re

![7](./images/7.png)

Search for "python" and then enable remote execution. Now Send to Unreal will work with your new Unreal project.
Search for "python" and then enable `remote execution` and set the Multicast bind address to `0.0.0.0`. Now Send to Unreal will work with your new Unreal project.

![8](./images/8.png)

Expand Down
2 changes: 1 addition & 1 deletion send2ue/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
modules = [
export,
ingest,
settings,
unreal,
utilities,
formatting,
validations,
dialog,
file_browser,
settings,
operators,
properties,
constants,
Expand Down
58 changes: 33 additions & 25 deletions send2ue/dependencies/remote_execution.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Copyright Epic Games, Inc. All Rights Reserved.

import os
import sys as _sys
import json as _json
import uuid as _uuid
import time as _time
import socket as _socket
import logging as _logging
import threading as _threading
import bpy

# Protocol constants (see PythonScriptRemoteExecution.cpp for the full protocol definition)
_PROTOCOL_VERSION = 1 # Protocol version number
Expand All @@ -22,10 +22,6 @@
_NODE_PING_SECONDS = 1 # Number of seconds to wait before sending another "ping" message to discover remote notes
_NODE_TIMEOUT_SECONDS = 5 # Number of seconds to wait before timing out a remote node that was discovered via UDP and has stopped sending "pong" responses

DEFAULT_MULTICAST_TTL = 0 # Multicast TTL (0 is limited to the local host, 1 is limited to the local subnet)
DEFAULT_MULTICAST_GROUP_ENDPOINT = ('239.0.0.1', 6766) # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin)
DEFAULT_MULTICAST_BIND_ADDRESS = '127.0.0.1' # The adapter address that the UDP multicast socket should bind to, or 127.0.0.1 to bind to all adapters (must match the "Multicast Bind Address" setting in the Python plugin)
DEFAULT_COMMAND_ENDPOINT = ('127.0.0.1', 6776) # The endpoint tuple for the TCP command connection hosted by this client (that the remote client will connect to)
DEFAULT_RECEIVE_BUFFER_SIZE = 8192 # The default receive buffer size

# Execution modes (these must match the names given to LexToString for EPythonCommandExecutionMode in IPythonScriptPlugin.h)
Expand All @@ -35,22 +31,34 @@

class RemoteExecutionConfig(object):
'''
Configuration data for establishing a remote connection with a UE4 instance running Python.
Configuration data for establishing a remote connection with a Unreal Editor instance running Python.
'''
def __init__(self):
self.multicast_ttl = DEFAULT_MULTICAST_TTL
self.multicast_group_endpoint = DEFAULT_MULTICAST_GROUP_ENDPOINT
self.multicast_bind_address = DEFAULT_MULTICAST_BIND_ADDRESS
self.command_endpoint = DEFAULT_COMMAND_ENDPOINT
# The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin)
self.multicast_ttl = bpy.context.preferences.addons["send2ue"].preferences.multicast_ttl

# The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin)
host, port = bpy.context.preferences.addons["send2ue"].preferences.multicast_group_endpoint.split(':')
self.multicast_group_endpoint = (host, int(port))

# The endpoint tuple for the TCP command connection hosted by this client (that the remote client will connect to)
host, port = bpy.context.preferences.addons["send2ue"].preferences.command_endpoint.split(':')
self.command_endpoint = (host, int(port))

# The adapter address that the UDP multicast socket should bind to, or 0.0.0.0 to bind to all adapters (must match the "Multicast Bind Address" setting in the Python plugin)
self.multicast_bind_address = host

class RemoteExecution(object):
'''
A remote execution session. This class can discover remote "nodes" (UE4 instances running Python), and allow you to open a command channel to a particular instance.
A remote execution session. This class can discover remote "nodes" (Unreal Editor instances running Python), and allow you to open a command channel to a particular instance.

Args:
config (RemoteExecutionConfig): Configuration controlling the connection settings for this session.
'''
def __init__(self, config=RemoteExecutionConfig()):
def __init__(self, config=None):
if not config:
config = RemoteExecutionConfig()

self._config = config
self._broadcast_connection = None
self._command_connection = None
Expand All @@ -59,23 +67,23 @@ def __init__(self, config=RemoteExecutionConfig()):
@property
def remote_nodes(self):
'''
Get the current set of discovered remote "nodes" (UE4 instances running Python).
Get the current set of discovered remote "nodes" (Unreal Editor instances running Python).

Returns:
list: A list of dicts containg the node ID and the other data.
list: A list of dicts containing the node ID and the other data.
'''
return self._broadcast_connection.remote_nodes if self._broadcast_connection else []

def start(self):
'''
Start the remote execution session. This will begin the discovey process for remote "nodes" (UE4 instances running Python).
Start the remote execution session. This will begin the discovery process for remote "nodes" (Unreal Editor instances running Python).
'''
self._broadcast_connection = _RemoteExecutionBroadcastConnection(self._config, self._node_id)
self._broadcast_connection.open()

def stop(self):
'''
Stop the remote execution session. This will end the discovey process for remote "nodes" (UE4 instances running Python), and close any open command connection.
Stop the remote execution session. This will end the discovery process for remote "nodes" (Unreal Editor instances running Python), and close any open command connection.
'''
self.close_command_connection()
if self._broadcast_connection:
Expand All @@ -93,7 +101,7 @@ def has_command_connection(self):

def open_command_connection(self, remote_node_id):
'''
Open a command connection to the given remote "node" (a UE4 instance running Python), closing any command connection that may currently be open.
Open a command connection to the given remote "node" (a Unreal Editor instance running Python), closing any command connection that may currently be open.

Args:
remote_node_id (string): The ID of the remote node (this can be obtained by querying `remote_nodes`).
Expand Down Expand Up @@ -129,7 +137,7 @@ def run_command(self, command, unattended=True, exec_mode=MODE_EXEC_FILE, raise_

class _RemoteExecutionNode(object):
'''
A discovered remote "node" (aka, a UE4 instance running Python).
A discovered remote "node" (aka, a Unreal Editor instance running Python).

Args:
data (dict): The data representing this node (from its "pong" reponse).
Expand All @@ -153,7 +161,7 @@ def should_timeout(self, now=None):

class _RemoteExecutionBroadcastNodes(object):
'''
A thread-safe set of remote execution "nodes" (UE4 instances running Python).
A thread-safe set of remote execution "nodes" (Unreal Editor instances running Python).
'''
def __init__(self):
self._remote_nodes = {}
Expand All @@ -162,7 +170,7 @@ def __init__(self):
@property
def remote_nodes(self):
'''
Get the current set of discovered remote "nodes" (UE4 instances running Python).
Get the current set of discovered remote "nodes" (Unreal Editor instances running Python).

Returns:
list: A list of dicts containg the node ID and the other data.
Expand Down Expand Up @@ -223,7 +231,7 @@ def __init__(self, config, node_id):
@property
def remote_nodes(self):
'''
Get the current set of discovered remote "nodes" (UE4 instances running Python).
Get the current set of discovered remote "nodes" (Unreal Editor instances running Python).

Returns:
list: A list of dicts containg the node ID and the other data.
Expand All @@ -232,7 +240,7 @@ def remote_nodes(self):

def open(self):
'''
Open the UDP based messaging and discovery connection. This will begin the discovey process for remote "nodes" (UE4 instances running Python).
Open the UDP based messaging and discovery connection. This will begin the discovey process for remote "nodes" (Unreal Editor instances running Python).
'''
self._running = True
self._last_ping = None
Expand All @@ -242,7 +250,7 @@ def open(self):

def close(self):
'''
Close the UDP based messaging and discovery connection. This will end the discovey process for remote "nodes" (UE4 instances running Python).
Close the UDP based messaging and discovery connection. This will end the discovey process for remote "nodes" (Unreal Editor instances running Python).
'''
self._running = False
if self._broadcast_listen_thread:
Expand Down Expand Up @@ -380,7 +388,7 @@ class _RemoteExecutionCommandConnection(object):
Args:
config (RemoteExecutionConfig): Configuration controlling the connection settings.
node_id (string): The ID of the local "node" (this session).
remote_node_id (string): The ID of the remote "node" (the UE4 instance running Python).
remote_node_id (string): The ID of the remote "node" (the Unreal Editor instance running Python).
'''
def __init__(self, config, node_id, remote_node_id):
self._config = config
Expand Down Expand Up @@ -541,7 +549,7 @@ def to_json(self):
if self.data:
json_obj['data'] = self.data
return _json.dumps(json_obj, ensure_ascii=False)

def to_json_bytes(self):
'''
Convert this message to its JSON representation as UTF-8 bytes.
Expand Down
3 changes: 2 additions & 1 deletion send2ue/dependencies/rpc/base_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def run_in_main_thread(callable_instance, *args):
globals().pop(ERROR_VALUE_NAME, None)
EXECUTION_QUEUE.put((callable_instance, args))

for attempt in range(timeout * 10):
start_time = time.time()
while time.time() - start_time < timeout:
if RETURN_VALUE_NAME in globals():
return globals().get(RETURN_VALUE_NAME)
elif ERROR_VALUE_NAME in globals():
Expand Down
1 change: 1 addition & 0 deletions send2ue/dependencies/rpc/client.py
jack-yao91 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import platform
import re
import logging
import inspect
Expand Down
1 change: 1 addition & 0 deletions send2ue/dependencies/unreal.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
default_imports=['import unreal'],
remap_pairs=REMAP_PAIRS,
)

rpc_client = rpc.client.RPCClient(port=UNREAL_PORT)
unreal_response = ''

Expand Down
40 changes: 33 additions & 7 deletions send2ue/properties.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright Epic Games, Inc. All Rights Reserved.

import os
import sys
import uuid
import bpy
from .constants import ToolInfo, PathModes, Template
Expand All @@ -16,6 +17,15 @@ class Send2UeAddonProperties:
default=True,
description=f"This automatically creates the pre-defined collection (Export)"
)
extensions_repo_path: bpy.props.StringProperty(
name="Extensions Repo Path",
default="",
description=(
"Set this path to the folder that contains your Send to Unreal python extensions. All extensions "
"in this folder will be automatically loaded"
)
)
# ------------- Remote Execution settings ------------------
rpc_response_timeout: bpy.props.IntProperty(
name="RPC Response Timeout",
default=60,
Expand All @@ -27,14 +37,30 @@ class Send2UeAddonProperties:
set=settings.set_rpc_response_timeout,
get=settings.get_rpc_response_timeout
)
extensions_repo_path: bpy.props.StringProperty(
name="Extensions Repo Path",
default="",
description=(
"Set this path to the folder that contains your Send to Unreal python extensions. All extensions "
"in this folder will be automatically loaded"
)
multicast_ttl: bpy.props.IntProperty(
name="Multicast TTL",
default=0,
description=(
"Limits packet propagation for multicast connections. 0 restricts to local computer, 1 restricts to "
"local network. Default '0'"
)
)
multicast_group_endpoint: bpy.props.StringProperty(
name="Multicast Group Endpoint",
default="239.0.0.1:6766",
description=(
"The multicast group endpoint that the UDP multicast socket should join. Must match setting "
"in Unreal."
)
)
command_endpoint: bpy.props.StringProperty(
name="Command Endpoint",
default="127.0.0.1:6776" if sys.platform == 'win32' else "0.0.0.0:6776",
description=(
"IP for UDP multicast to bind to and TCP command connection hosted by this client. "
"Must match setting in Unreal."
)
)


class Send2UeWindowMangerProperties(bpy.types.PropertyGroup):
Expand Down
11 changes: 11 additions & 0 deletions send2ue/ui/addon_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ def draw(self, context):
row.label(text='RPC Response Timeout')
row.prop(self, 'rpc_response_timeout', text='')
row = self.layout.row()

row.label(text="Multicast TTL")
row.prop(self, 'multicast_ttl', text='')
row = self.layout.row()
row.label(text="Multicast Group Endpoint")
row.prop(self, 'multicast_group_endpoint', text='')
row = self.layout.row()
row.label(text="Command Endpoint")
row.prop(self, 'command_endpoint', text='')
row = self.layout.row()

row.label(text='Extensions Repo Path:')
row = self.layout.row()
row = row.split(factor=0.95, align=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ ManualIPAddress=

[/Script/PythonScriptPlugin.PythonScriptPluginSettings]
bRemoteExecution=True
RemoteExecutionMulticastBindAddress=127.0.0.1
RemoteExecutionMulticastBindAddress=0.0.0.0

[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_Default
Expand Down
Loading