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

socketiosample #27

Closed
wants to merge 1 commit into from
Closed
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
1 change: 1 addition & 0 deletions src/packages/socketioclient/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Copyright (c) 2020 Autodesk, all rights reserved.
168 changes: 168 additions & 0 deletions src/packages/socketioclient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# HowTo: socketioclient

This example shows how to connect to a socket.io server from 3dsMax.

The sample uses the following PyPi pacakges:

- `python-socketio` the socketio support for python
- `websocket-client` the websocket client for socketio

It also uses the following package (from another sample in this repo): `mxtread`


The code of this sample is very concise and two things are important to
know:

## socketio events are not handled on the main thread

This handler is not called on the main thread:

```python
@sio.on("chat message")
def my_message(data):
mxthread.run_on_main_thread(print, f"message received {data}")
# We could emit something here
# sio.emit('my response', {'response': 'my response'})

# We could disconnect ourself (so we only handle one message)
sio.disconnect()
```

But 3dsMax will not print on separate threads and none of the pymxs
functions (and properties) can be used on a thread (other than the
main thread).


This is why we use mxthread. This module lets "execute code on the main
thread". So here we call print on the main thread. We could write our own
custom function that does various things with pymxs and call it on the main
thread here. As long as the main thread itself is not blocked this will work.

## you typically call sio.wait() on the socket io client

This, if called on the main thread, blocks the main thread (this will
freeze the 3dsMax ui as long as sio.wait does not return). So we create
the socket io client from a subthread.

```python
x = threading.Thread(target=connect_socketio)
x.start()
```
## Server code

We need a nodejs server (using express and socket.io) to run the sample.
We pretty much use the [chat](https://socket.io/get-started/chat) sample provided on the socket.io site.

You will need to install nodejs and express to make that work. You can
follow the instructions provided on the socket.io

- index.html
```html
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }

#form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
#input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
#input:focus { outline: none; }
#form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }

#messages { list-style-type: none; margin: 0; padding: 0; }
#messages > li { padding: 0.5rem 1rem; }
#messages > li:nth-child(odd) { background: #efefef; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>

<script>
var socket = io();

var messages = document.getElementById('messages');
var form = document.getElementById('form');
var input = document.getElementById('input');

form.addEventListener('submit', function(e) {
e.preventDefault();
if (input.value) {
socket.emit('chat message', input.value);
input.value = '';
}
});

socket.on('chat message', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
```

- index.js

The code is here:
```javascript
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
socket.on('chat message', msg => {
io.emit('chat message', msg);
});
});

http.listen(port, () => {
console.log(`Socket.IO server running at http://localhost:${port}/`);
});
```

- package.json

You will also need the package.json file:

```json
{
"name": "socket-chat-example",
"version": "0.0.1",
"description": "my first socket.io app",
"dependencies": {
"express": "^4.17.3",
"socket.io": "^4.4.1"
}
}
```

### Installing the server
npm install

### Running the server
node index.js

### Running the max sample
When the serever run you can point your browser to localhost:3000 and
type some stuff.

You can then import socketioclient from the python console in max and.
After it is imported (the code automatically calls sio.disconnect after
the first message is received) you will be able to see events printed
on the console.
19 changes: 19 additions & 0 deletions src/packages/socketioclient/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import setuptools

with open("README.md", "r") as fh:
long_description = fh.read()

setuptools.setup(
name="socketioclient-autodesk",
version="0.0.1",
description="socketioclient sample",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://git.autodesk.com/windish/maxpythontutorials",
packages=setuptools.find_packages(),
install_requires=[
'python-socketio',
'websocket-client'
],
python_requires='>=3.7'
)
41 changes: 41 additions & 0 deletions src/packages/socketioclient/socketioclient/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
Demonstrate the use of of a socket.io client in 3dsMax
"""
import socketio
import pymxs
import threading
import mxthread

def connect_socketio():
sio = socketio.Client()

@sio.event
def connect():
"""Handle the connection"""
# note: socketio calls us on a different thread.
# 3dsMax (pymxs, the print function) don't work from a thread.
# We use mxthread (from a different example) to execute code on
# 3dsMax's main thread to handle the event. Note that this will
# deadlock if the main thread is blocked (we cannot execute something
# on the main thread if the main thread is blocked).
mxthread.run_on_main_thread(print, f"connection established")

@sio.on("chat message")
def my_message(data):
mxthread.run_on_main_thread(print, f"message received {data}")
# We could emit something here
# sio.emit('my response', {'response': 'my response'})

# We could disconnect ourself (so we only handle one message)
sio.disconnect()

@sio.event
def disconnect():
mxthread.run_on_main_thread(print, f"disconnected from server")

sio.connect('http://localhost:3000')
sio.wait()
mxthread.run_on_main_thread(print, f"no longer waiting")

x = threading.Thread(target=connect_socketio)
x.start()