spacenav-ws 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/PKG-INFO +1 -1
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/pyproject.toml +1 -1
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/controller.py +55 -19
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/main.py +2 -13
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/spacenav.py +2 -2
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/wamp.py +5 -4
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/uv.lock +1 -1
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/.gitignore +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/LICENSE +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/README.md +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/readme.md +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/__init__.py +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/certs/ip.cnf +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/certs/ip.crt +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/certs/ip.csr +0 -0
- {spacenav_ws-0.1.1 → spacenav_ws-0.1.2}/src/spacenav_ws/certs/ip.key +0 -0
@@ -7,8 +7,8 @@ from typing import Any
|
|
7
7
|
import numpy as np
|
8
8
|
from scipy.spatial import transform
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
from spacenav_ws.spacenav import MotionEvent, ButtonEvent, from_message
|
11
|
+
from spacenav_ws.wamp import WampSession, Prefix, Call, Subscribe, CallResult
|
12
12
|
|
13
13
|
|
14
14
|
class Mouse3d:
|
@@ -44,8 +44,9 @@ class Controller:
|
|
44
44
|
def __post_init__(self):
|
45
45
|
self.affine = np.asarray(self.affine).reshape([4, 4])
|
46
46
|
|
47
|
-
def __init__(self, reader: asyncio.StreamReader, mouse: Mouse3d, wamp_state_handler:
|
47
|
+
def __init__(self, reader: asyncio.StreamReader, mouse: Mouse3d, wamp_state_handler: WampSession, client_metadata: dict):
|
48
48
|
self.id = "controller0"
|
49
|
+
self.client_metadata = client_metadata
|
49
50
|
self.reader = reader
|
50
51
|
self._mouse = mouse
|
51
52
|
self.wamp_state_handler = wamp_state_handler
|
@@ -57,9 +58,9 @@ class Controller:
|
|
57
58
|
self.coordinate_system = None
|
58
59
|
self.subscribed = False
|
59
60
|
|
60
|
-
async def subscribe(self):
|
61
|
+
async def subscribe(self, msg: Subscribe):
|
61
62
|
"""When a subscription request for self.controller_uri comes in we start broadcasting!"""
|
62
|
-
|
63
|
+
logging.info("handling subscribe %s", msg)
|
63
64
|
self.subscribed = True
|
64
65
|
|
65
66
|
async def client_update(self, controller_id: str, args: dict[str, Any]):
|
@@ -82,20 +83,25 @@ class Controller:
|
|
82
83
|
while True:
|
83
84
|
mouse_event = await self.reader.read(32)
|
84
85
|
nums = struct.unpack("iiiiiiii", mouse_event)
|
85
|
-
event =
|
86
|
-
if isinstance(event,
|
86
|
+
event = from_message(list(nums))
|
87
|
+
if isinstance(event, ButtonEvent):
|
87
88
|
logging.warning("Button presses are discarded for now! %s", event)
|
88
|
-
elif isinstance(event,
|
89
|
+
elif isinstance(event, MotionEvent):
|
89
90
|
if self.subscribed:
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
if self.client_metadata["name"] == "Onshape":
|
92
|
+
await self.update_onshape_client(event)
|
93
|
+
elif self.client_metadata["name"] == "WebThreeJS Sample":
|
94
|
+
await self.update_3dconnexion_client(event)
|
95
|
+
else:
|
96
|
+
logging.warning("Unknown client! Cannot send mouse events, client_metadata:%s", self.client_metadata)
|
97
|
+
|
98
|
+
async def update_onshape_client(self, event: MotionEvent):
|
93
99
|
# 1) pull down the current extents and model matrix
|
94
100
|
extents = await self.remote_read("view.extents")
|
95
101
|
flat = await self.remote_read("view.affine")
|
96
102
|
curr_affine = np.asarray(flat, dtype=np.float32).reshape(4, 4)
|
97
103
|
|
98
|
-
# TODO:
|
104
|
+
# TODO: This is not correct
|
99
105
|
# 2) Handle rotation
|
100
106
|
angles = np.array([event.pitch, event.yaw, -event.roll], dtype=np.float32) * 0.008
|
101
107
|
rot_cam = transform.Rotation.from_euler("xyz", angles, degrees=True).as_matrix()
|
@@ -125,29 +131,59 @@ class Controller:
|
|
125
131
|
await self.remote_write("view.affine", new_affine.reshape(-1).tolist())
|
126
132
|
await self.remote_write("view.extents", new_extents)
|
127
133
|
|
134
|
+
async def update_3dconnexion_client(self, event: MotionEvent):
|
135
|
+
# 1) pull down the current extents and model matrix
|
136
|
+
flat = await self.remote_read("view.affine")
|
137
|
+
curr_affine = np.asarray(flat, dtype=np.float32).reshape(4, 4)
|
138
|
+
|
139
|
+
# 2) Handle rotation
|
140
|
+
# Rotate the model in the cameras perspective
|
141
|
+
# angles = np.array([event.pitch, event.yaw, -event.roll], dtype=np.float32) * 0.008
|
142
|
+
# rot_cam = transform.Rotation.from_euler("xyz", angles, degrees=True).as_matrix()
|
143
|
+
# rot_delta = np.eye(4, dtype=np.float32)
|
144
|
+
# rot_delta[:3, :3] = rot_cam
|
145
|
+
# rotated = rot_delta @ curr_affine
|
146
|
+
|
147
|
+
# Rotate the model from _its_ perspective
|
148
|
+
angles = np.array([event.pitch, event.yaw, -event.roll], dtype=np.float32) * 0.008
|
149
|
+
rot_cam = transform.Rotation.from_euler("xyz", angles, degrees=True).as_matrix()
|
150
|
+
rot_delta = np.eye(4, dtype=np.float32)
|
151
|
+
rot_delta[:3, :3] = rot_cam
|
152
|
+
rotated = curr_affine @ rot_delta
|
153
|
+
|
154
|
+
# 3) Handle translations
|
155
|
+
trans_delta = np.eye(4, dtype=np.float32)
|
156
|
+
# Probably event.y doesn't do anything at all here!
|
157
|
+
trans_delta[3, :3] = np.array([-event.x, -event.z, event.y], dtype=np.float32) * 0.001
|
158
|
+
new_affine = trans_delta @ rotated
|
159
|
+
|
160
|
+
# Write back changes
|
161
|
+
await self.remote_write("motion", True)
|
162
|
+
await self.remote_write("view.affine", new_affine.reshape(-1).tolist())
|
163
|
+
|
128
164
|
|
129
|
-
async def create_mouse_controller(wamp_state_handler:
|
165
|
+
async def create_mouse_controller(wamp_state_handler: WampSession, spacenav_reader: asyncio.StreamReader):
|
130
166
|
await wamp_state_handler.wamp.begin()
|
131
167
|
# The first three messages are typically prefix setters!
|
132
168
|
msg = await wamp_state_handler.wamp.next_message()
|
133
|
-
while isinstance(msg,
|
169
|
+
while isinstance(msg, Prefix):
|
134
170
|
await wamp_state_handler.wamp.run_message_handler(msg)
|
135
171
|
msg = await wamp_state_handler.wamp.next_message()
|
136
172
|
|
137
173
|
# The first call after the prefixes must be 'create mouse'
|
138
|
-
assert isinstance(msg,
|
174
|
+
assert isinstance(msg, Call)
|
139
175
|
assert msg.proc_uri == "3dx_rpc:create" and msg.args[0] == "3dconnexion:3dmouse"
|
140
176
|
mouse = Mouse3d()
|
141
177
|
logging.info(f'Created 3d mouse "{mouse.id}" for version {msg.args[1]}')
|
142
|
-
await wamp_state_handler.wamp.send_message(
|
178
|
+
await wamp_state_handler.wamp.send_message(CallResult(msg.call_id, {"connexion": mouse.id}))
|
143
179
|
|
144
180
|
# And the second call after the prefixes must be 'create controller'
|
145
181
|
msg = await wamp_state_handler.wamp.next_message()
|
146
|
-
assert isinstance(msg,
|
182
|
+
assert isinstance(msg, Call)
|
147
183
|
assert msg.proc_uri == "3dx_rpc:create" and msg.args[0] == "3dconnexion:3dcontroller" and msg.args[1] == mouse.id
|
148
184
|
metadata = msg.args[2]
|
149
|
-
ctrl = Controller(spacenav_reader, mouse, wamp_state_handler)
|
185
|
+
ctrl = Controller(spacenav_reader, mouse, wamp_state_handler, metadata)
|
150
186
|
logging.info(f'Created controller "{ctrl.id}" for mouse "{mouse.id}", for client "{metadata["name"]}", version "{metadata["version"]}"')
|
151
187
|
|
152
|
-
await wamp_state_handler.wamp.send_message(
|
188
|
+
await wamp_state_handler.wamp.send_message(CallResult(msg.call_id, {"instance": ctrl.id}))
|
153
189
|
return ctrl
|
@@ -22,18 +22,7 @@ ORIGINS = [
|
|
22
22
|
"https://3dconnexion.com",
|
23
23
|
"https://cad.onshape.com",
|
24
24
|
]
|
25
|
-
|
26
|
-
|
27
|
-
# # Build a Traversable pointing to spacenav_ws/certs/ip.crt
|
28
|
-
# resource = files(__package__).joinpath("certs", "ip.crt")
|
29
|
-
# # as_file() ensures we have a real filesystem path even if inside a zip/wheel
|
30
|
-
# with as_file(resource) as cert_path:
|
31
|
-
# CERT_FILE = str(cert_path)
|
32
|
-
|
33
|
-
# # Same for the key
|
34
|
-
# key_res = files(__package__).joinpath("certs", "ip.key")
|
35
|
-
# with as_file(key_res) as key_path:
|
36
|
-
# KEY_FILE = str(key_path)
|
25
|
+
|
37
26
|
CERT_FILE = Path(__file__).parent / "certs" / "ip.crt"
|
38
27
|
KEY_FILE = Path(__file__).parent / "certs" / "ip.key"
|
39
28
|
|
@@ -114,7 +103,7 @@ async def read_mouse_stream():
|
|
114
103
|
logging.info("Start moving your mouse!")
|
115
104
|
async for event in get_mouse_event_generator():
|
116
105
|
logging.info(event.strip())
|
117
|
-
|
106
|
+
|
118
107
|
|
119
108
|
@cli.command()
|
120
109
|
def read_mouse():
|
@@ -17,9 +17,9 @@ def get_sync_spacenav_socket():
|
|
17
17
|
async def get_async_spacenav_socket_reader() -> tuple[asyncio.StreamReader, asyncio.StreamWriter]:
|
18
18
|
try:
|
19
19
|
return await asyncio.open_unix_connection(SPACENAV_SOCKET_PATH)
|
20
|
-
except FileNotFoundError
|
20
|
+
except (FileNotFoundError, ConnectionRefusedError):
|
21
21
|
logging.exception("Space mouse not found!")
|
22
|
-
|
22
|
+
exit(1)
|
23
23
|
|
24
24
|
|
25
25
|
@dataclass
|
@@ -5,7 +5,8 @@ import logging
|
|
5
5
|
import random
|
6
6
|
import string
|
7
7
|
from enum import IntEnum
|
8
|
-
from
|
8
|
+
from types import CoroutineType
|
9
|
+
from typing import Any, ClassVar, Dict, NamedTuple, Optional, Type, Callable
|
9
10
|
|
10
11
|
from fastapi import WebSocket
|
11
12
|
|
@@ -102,8 +103,8 @@ class WampProtocol:
|
|
102
103
|
self._session_id = _rand_id(16)
|
103
104
|
|
104
105
|
self.prefixes = {}
|
105
|
-
self.call_handlers = {}
|
106
|
-
self.subscribe_handlers = {}
|
106
|
+
self.call_handlers: dict[str, Callable[..., CoroutineType[Any, Any, None]]] = {}
|
107
|
+
self.subscribe_handlers: dict[str, Callable[[Subscribe], CoroutineType[Any, Any, None]]] = {}
|
107
108
|
|
108
109
|
async def begin(self):
|
109
110
|
await self._socket.accept(subprotocol="wamp")
|
@@ -148,7 +149,7 @@ class WampProtocol:
|
|
148
149
|
logging.warning("Unknown subscribable: %s", topic)
|
149
150
|
else:
|
150
151
|
logging.debug(f"handle subscribe to '{topic}' by calling: {handler}")
|
151
|
-
await handler()
|
152
|
+
await handler(msg)
|
152
153
|
|
153
154
|
async def handle_callresult(self, msg: CallResult):
|
154
155
|
logging.warning("No callresult handler for msg: %s", msg)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|