From 7af47067416110fdda6d3787d3d8f6506da2eec8 Mon Sep 17 00:00:00 2001 From: "peter.mitrano" Date: Sat, 20 Jun 2026 13:01:07 +0200 Subject: [PATCH 1/9] update examples and examples docs - use HID_Z_UP convention everywhere it is sensible - rework the last two examples showing how custom configs and axes inversion works - update docs to avoid copy pasting --- CONTRIBUTING.md | 2 +- README.md | 3 +- docs/README.md | 5 +- docs/mouseApi/examples.md | 144 ++++++++++++++++------------- examples/01_basic.py | 3 +- examples/02_callbacks.py | 2 + examples/03_multi_device.py | 5 +- examples/04_open_by_path.py | 6 +- examples/06_axis_callbacks.py | 6 +- examples/07_led.py | 3 +- examples/08_buttons.py | 6 +- examples/09_custom_config.py | 139 ---------------------------- examples/09_invert_rotations.py | 52 +++++++++++ examples/10_custom_config_unity.py | 61 ++++++++++++ mkdocs.yml | 2 + 15 files changed, 225 insertions(+), 214 deletions(-) delete mode 100644 examples/09_custom_config.py create mode 100644 examples/09_invert_rotations.py create mode 100644 examples/10_custom_config_unity.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9fef41..167feb3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,7 +107,7 @@ Once your device works correctly: 1. Create a branch: `git checkout -b add-device-` 2. Add your device entry to `devices.toml` -3. Test thoroughly with the examples in `examples/` +3. Test thoroughly with the examples in `examples/`. If you add a new example, update the README and `docs/mouseApi/examples.md`. 4. Submit a PR with your device name and any notes about testing ### Using Custom Configuration (Without Modifying Library) diff --git a/README.md b/README.md index a34cfb0..c722db1 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,8 @@ See the [examples/](https://github.com/JakubAndrysek/PySpaceMouse/tree/master/ex | `06_axis_callbacks.py` | Per-axis callbacks with filtering | | `07_led.py` | LED control | | `08_buttons.py` | Button names and handling | -| `09_custom_config.py` | Custom axis mappings | +| `09_invert_rotations.py` | Invert rotations | +| `10_custom_config_unity.py` | A totally custom device config using the Unity axis convention | ## Dependencies diff --git a/docs/README.md b/docs/README.md index 0733fd1..c82238a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -133,8 +133,8 @@ with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device: ```

- Raw HID axis convention HID Z-up axis convention + Raw HID axis convention Legacy PySpaceMouse axis convention

@@ -225,7 +225,8 @@ See the [examples/](https://github.com/JakubAndrysek/PySpaceMouse/tree/master/ex | `06_axis_callbacks.py` | Per-axis callbacks with filtering | | `07_led.py` | LED control | | `08_buttons.py` | Button names and handling | -| `09_custom_config.py` | Custom axis mappings | +| `09_invert_rotations.py` | Invert rotations | +| `10_custom_config_unity.py` | A totally custom device config using the Unity axis convention | ## Dependencies diff --git a/docs/mouseApi/examples.md b/docs/mouseApi/examples.md index aecea91..20c9906 100644 --- a/docs/mouseApi/examples.md +++ b/docs/mouseApi/examples.md @@ -1,96 +1,112 @@ # Examples +- [01. Basic](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/01_basic.py) +- [02. Callbacks](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/02_callbacks.py) +- [03. Multi Device](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/03_multi_device.py) +- [04. Open By Path](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/04_open_by_path.py) +- [05. Discovery](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/05_discovery.py) +- [06. Axis Callbacks](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/06_axis_callbacks.py) +- [07. Led](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/07_led.py) +- [08. Buttons](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/08_buttons.py) +- [09. Invert Rotations](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/09_invert_rotations.py) +- [10. Custom Config Unity](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/10_custom_config_unity.py) -## Basic usage -[basicExample.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/basicExample.py) -````py -import pyspacemouse -import time +## 01. Basic -success = pyspacemouse.open() -if success: - while 1: - state = pyspacemouse.read() - print(state.x, state.y, state.z) - time.sleep(0.01) -```` +- File: [01_basic.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/01_basic.py) +- Summary: Basic example: Read SpaceMouse input with context manager. +- Run: `python examples/01_basic.py` +```py title="examples/01_basic.py" +--8<-- "examples/01_basic.py" +``` -## Usage with callback -[callbackExample.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/callbackExample.py) -````py -import pyspacemouse -import time +## 02. Callbacks +- File: [02_callbacks.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/02_callbacks.py) +- Summary: Callbacks example: React to button presses and axis movements. +- Run: `python examples/02_callbacks.py` -def button_0(state, buttons, pressed_buttons): - print("Button:", pressed_buttons) +```py title="examples/02_callbacks.py" +--8<-- "examples/02_callbacks.py" +``` +## 03. Multi Device -def button_0_1(state, buttons, pressed_buttons): - print("Buttons:", pressed_buttons) +- File: [03_multi_device.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/03_multi_device.py) +- Summary: Multi-device example: Connect to multiple SpaceMouse devices. +- Run: `python examples/03_multi_device.py` +```py title="examples/03_multi_device.py" +--8<-- "examples/03_multi_device.py" +``` -def someButton(state, buttons): - print("Some button") +## 04. Open By Path +- File: [04_open_by_path.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/04_open_by_path.py) +- Summary: Open by path example: Connect to a specific HID device by filesystem path. +- Run: `python examples/04_open_by_path.py` -def callback(): - button_arr = [pyspacemouse.ButtonCallback(0, button_0), - pyspacemouse.ButtonCallback([1], lambda state, buttons, pressed_buttons: print("Button: 1")), - pyspacemouse.ButtonCallback([0, 1], button_0_1), ] +```py title="examples/04_open_by_path.py" +--8<-- "examples/04_open_by_path.py" +``` - success = pyspacemouse.open(dof_callback=pyspacemouse.print_state, button_callback=someButton, - button_callback_arr=button_arr) - if success: - while True: - pyspacemouse.read() - time.sleep(0.01) +## 05. Discovery +- File: [05_discovery.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/05_discovery.py) +- Summary: Device discovery example: List and inspect available devices. +- Run: `python examples/05_discovery.py` -if __name__ == '__main__': - callback() -```` +```py title="examples/05_discovery.py" +--8<-- "examples/05_discovery.py" +``` +## 06. Axis Callbacks -### Callback: print_state +- File: [06_axis_callbacks.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/06_axis_callbacks.py) +- Summary: Axis callbacks example: React to specific axis movements. +- Run: `python examples/06_axis_callbacks.py` -Print all axis states +```py title="examples/06_axis_callbacks.py" +--8<-- "examples/06_axis_callbacks.py" +``` - x +0.00 y +0.00 z +0.00 roll +0.00 pitch +0.00 yaw +0.00 t +0.0 +## 07. Led -### Callback: print_buttons +- File: [07_led.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/07_led.py) +- Summary: LED control example: Blink the SpaceMouse LED. +- Run: `python examples/07_led.py` -Print all buttons states +```py title="examples/07_led.py" +--8<-- "examples/07_led.py" +``` - [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ] +## 08. Buttons +- File: [08_buttons.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/08_buttons.py) +- Summary: Button names example: Show button names when pressed. +- Run: `python examples/08_buttons.py` -## Custom Device Configuration +```py title="examples/08_buttons.py" +--8<-- "examples/08_buttons.py" +``` -[09_custom_config.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/09_custom_config.py) +## 09. Invert Rotations -Customize axis mappings for different coordinate conventions (ROS, OpenGL, etc.): +- File: [09_invert_rotations.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/09_invert_rotations.py) +- Summary: Example: Modification of device specs +- Run: `python examples/09_invert_rotations.py` -````py -import pyspacemouse +```py title="examples/09_invert_rotations.py" +--8<-- "examples/09_invert_rotations.py" +``` -# Get existing device spec and modify it -specs = pyspacemouse.get_device_specs() -base = specs["SpaceNavigator"] +## 10. Custom Config Unity -# Invert axes for ROS conventions -ros_spec = pyspacemouse.modify_device_info( - base, - name="SpaceNavigator (ROS)", - invert_axes=["y", "z", "roll", "yaw"], -) +- File: [10_custom_config_unity.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/10_custom_config_unity.py) +- Summary: Example: Custom device configuration with axis remapping. +- Run: `python examples/10_custom_config_unity.py` -# Open with custom configuration -with pyspacemouse.open(device_spec=ros_spec) as device: - while True: - state = device.read() - print(f"x={state.x:.2f} y={state.y:.2f} z={state.z:.2f}") -```` - -See also: `create_device_info()` for creating completely custom device specs. +```py title="examples/10_custom_config_unity.py" +--8<-- "examples/10_custom_config_unity.py" +``` diff --git a/examples/01_basic.py b/examples/01_basic.py index 550733d..11c3db4 100644 --- a/examples/01_basic.py +++ b/examples/01_basic.py @@ -5,9 +5,10 @@ """ import pyspacemouse +from pyspacemouse import AxisConvention # Using context manager (recommended) -with pyspacemouse.open() as device: +with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device: print(f"Connected to: {device.name}") print("Move the SpaceMouse to see values (Ctrl+C to exit)") diff --git a/examples/02_callbacks.py b/examples/02_callbacks.py index f30175c..776997e 100644 --- a/examples/02_callbacks.py +++ b/examples/02_callbacks.py @@ -8,6 +8,7 @@ import time import pyspacemouse +from pyspacemouse import AxisConvention # Button callbacks receive (state, buttons, pressed_buttons) @@ -39,6 +40,7 @@ def on_any_button(state, buttons): # Open with callbacks with pyspacemouse.open( + axis_convention=AxisConvention.HID_Z_UP, dof_callback=pyspacemouse.print_state, # Built-in DOF printer button_callback=on_any_button, button_callbacks=button_callbacks, diff --git a/examples/03_multi_device.py b/examples/03_multi_device.py index a027323..89bfea3 100644 --- a/examples/03_multi_device.py +++ b/examples/03_multi_device.py @@ -7,6 +7,7 @@ import time import pyspacemouse +from pyspacemouse import AxisConvention def main(): @@ -24,8 +25,8 @@ def main(): path1 = list(connected.keys())[1] # Open two devices by path - with pyspacemouse.open_by_path(path0) as left_hand: - with pyspacemouse.open_by_path(path1) as right_hand: + with pyspacemouse.open_by_path(path0, axis_convention=AxisConvention.HID_Z_UP) as left_hand: + with pyspacemouse.open_by_path(path1, axis_convention=AxisConvention.HID_Z_UP) as right_hand: print(f"Left hand: {left_hand.name}") print(f"Right hand: {right_hand.name}") print() diff --git a/examples/04_open_by_path.py b/examples/04_open_by_path.py index f1af695..2425cc5 100644 --- a/examples/04_open_by_path.py +++ b/examples/04_open_by_path.py @@ -10,6 +10,7 @@ """ import pyspacemouse +from pyspacemouse import AxisConvention def main(): @@ -25,7 +26,10 @@ def main(): device_path = "/dev/hidraw0" try: - with pyspacemouse.open_by_path(device_path) as device: + with pyspacemouse.open_by_path( + device_path, + axis_convention=AxisConvention.HID_Z_UP, + ) as device: print(f"Connected to: {device.name} at {device_path}") print("Move the device (Ctrl+C to exit)") print() diff --git a/examples/06_axis_callbacks.py b/examples/06_axis_callbacks.py index 9aebd6a..02f5e63 100644 --- a/examples/06_axis_callbacks.py +++ b/examples/06_axis_callbacks.py @@ -9,6 +9,7 @@ import time import pyspacemouse +from pyspacemouse import AxisConvention def on_x_positive(state, value): @@ -46,7 +47,10 @@ def on_z_move(state, value): ), ] -with pyspacemouse.open(dof_callbacks=dof_callbacks) as device: +with pyspacemouse.open( + dof_callbacks=dof_callbacks, + axis_convention=AxisConvention.HID_Z_UP, +) as device: print(f"Connected to: {device.name}") print("Move X axis (left/right) or Z axis (up/down)") print("Ctrl+C to exit") diff --git a/examples/07_led.py b/examples/07_led.py index b68e882..00edf48 100644 --- a/examples/07_led.py +++ b/examples/07_led.py @@ -9,9 +9,10 @@ import time import pyspacemouse +from pyspacemouse import AxisConvention # Using context manager (recommended) -with pyspacemouse.open() as device: +with pyspacemouse.open(axis_convention=AxisConvention.HID_Z_UP) as device: print(f"Connected to: {device.name}") print("LED will blink every 0.5 seconds (Ctrl+C to exit)") print() diff --git a/examples/08_buttons.py b/examples/08_buttons.py index c998a5a..ecf27ac 100644 --- a/examples/08_buttons.py +++ b/examples/08_buttons.py @@ -7,6 +7,7 @@ import time import pyspacemouse +from pyspacemouse import AxisConvention def on_button_change(state, buttons): @@ -21,7 +22,10 @@ def on_button_change(state, buttons): # Open device -with pyspacemouse.open(button_callback=on_button_change) as device: +with pyspacemouse.open( + button_callback=on_button_change, + axis_convention=AxisConvention.HID_Z_UP, +) as device: print(f"Connected to: {device.name}") print(f"Device has {len(device.info.button_names)} buttons:") for i, name in enumerate(device.info.button_names): diff --git a/examples/09_custom_config.py b/examples/09_custom_config.py deleted file mode 100644 index 02279d7..0000000 --- a/examples/09_custom_config.py +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env python3 -"""Example: Custom device configuration with axis remapping. - -This example shows how to: -1. Get existing device specs -2. Modify axis directions for ROS/other conventions -3. Create entirely custom device configurations -""" - -import time - -import pyspacemouse - - -def example_modify_existing(): - """Modify an existing device spec to invert axes.""" - print("=" * 60) - print("Example 1: Modify existing device spec") - print("=" * 60) - - # Get all device specs from TOML - specs = pyspacemouse.get_device_specs() - print(f"Available devices: {list(specs.keys())}") - - # Get connected devices - connected = pyspacemouse.get_connected_devices_by_path() - if not connected: - print("No devices connected!") - return - if len(connected) > 1: - print("This example only works with one device connected.") - return - - device_name = list(connected.values())[0] - print(f"Using device: {device_name}") - - # Get base spec and create modified version - base_spec = specs[device_name] - print(f"Original mappings: y scale = {base_spec.mappings['y'].scale}") - - # Create modified spec with inverted Y and Z (common for ROS) - ros_spec = pyspacemouse.modify_device_info( - base_spec, - name=f"{device_name} (ROS)", - invert_axes=["y", "z"], # Invert these axes - ) - print(f"Modified mappings: y scale = {ros_spec.mappings['y'].scale}") - - # Open with custom spec - with pyspacemouse.open(device_spec=ros_spec) as device: - print(f"\nConnected to: {device.name}") - print("Move the SpaceMouse (Ctrl+C to exit)") - print("Y and Z axes are now inverted!\n") - - for _ in range(500): # Run for ~5 seconds - state = device.read() - if state.has_motion(): - print(f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} (Y/Z inverted)") - time.sleep(0.01) - - -def example_invert_rotations(): - """Show how to fix rotation axes for specific conventions.""" - print("\n" + "=" * 60) - print("Example 2: Fix rotation conventions") - print("=" * 60) - - connected = pyspacemouse.get_connected_devices_by_path() - if not connected: - print("No devices connected!") - return - if len(connected) > 1: - print("This example only works with one device connected.") - return - - device_name = list(connected.values())[0] - specs = pyspacemouse.get_device_specs() - base_spec = specs[device_name] - - # Invert roll and yaw for right-handed coordinate system - fixed_spec = pyspacemouse.modify_device_info( - base_spec, - name=f"{device_name} (Fixed Rotations)", - invert_axes=["roll", "yaw"], - ) - - with pyspacemouse.open(device_spec=fixed_spec) as device: - print(f"Connected to: {device.name}") - print("Roll and Yaw are now inverted!\n") - - for _ in range(500): - state = device.read() - if state.has_motion(): - print(f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}") - time.sleep(0.01) - - -def example_create_custom(): - """Create entirely custom device configuration.""" - print("\n" + "=" * 60) - print("Example 3: Create custom device spec (reference)") - print("=" * 60) - - # This shows how to create a completely custom device spec - # Useful for unsupported devices or complete remapping - custom_spec = pyspacemouse.create_device_info( - name="CustomSpaceMouse", - vendor_id=0x256F, # 3Dconnexion - product_id=0xC635, # SpaceMouse Compact - mappings={ - # Each mapping: (channel, byte1, byte2, scale) - # Scale: 1 = normal direction, -1 = inverted - "x": (1, 1, 2, 1), - "y": (1, 3, 4, 1), # NOT inverted (unlike default) - "z": (1, 5, 6, 1), # NOT inverted (unlike default) - "pitch": (2, 1, 2, 1), # NOT inverted - "roll": (2, 3, 4, 1), # NOT inverted - "yaw": (2, 5, 6, 1), - }, - buttons={ - "LEFT": (3, 1, 0), - "RIGHT": (3, 1, 1), - }, - ) - - print(f"Created custom spec: {custom_spec.name}") - print(f" VID/PID: {custom_spec.vendor_id:#06x}/{custom_spec.product_id:#06x}") - print(f" Axes: {list(custom_spec.mappings.keys())}") - print(f" Buttons: {custom_spec.button_names}") - print("\n (Not opening - this is just to show the API)") - - -if __name__ == "__main__": - try: - example_modify_existing() - example_invert_rotations() - example_create_custom() - except KeyboardInterrupt: - print("\nExiting...") diff --git a/examples/09_invert_rotations.py b/examples/09_invert_rotations.py new file mode 100644 index 0000000..f3842e2 --- /dev/null +++ b/examples/09_invert_rotations.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 +"""Example: Modification of device specs + +This example shows how to invert translation/rotation on specific axes +""" + +import time + +import pyspacemouse +from pyspacemouse import AxisConvention + + +def example_invert_rotations(): + """Show how to fix rotation axes for specific conventions.""" + print("\n" + "=" * 60) + print("Example 2: Invert rotation conventions") + print("=" * 60) + + connected = pyspacemouse.get_connected_devices() + if not connected: + print("No devices connected!") + return + + specs = pyspacemouse.get_device_specs() + base_spec_legacy = specs[connected[0]] + base_spec = pyspacemouse.apply_axis_convention(base_spec_legacy, AxisConvention.HID_Z_UP) + + # Invert roll, pitch, and yaw so rotations are left-handed + fixed_spec = pyspacemouse.modify_device_info( + base_spec, + name=f"{connected[0]} (Fixed Rotations)", + # Translation axes (x, y, z) can also be inverted here + invert_axes=["roll", "pitch", "yaw"], + ) + + with pyspacemouse.open(device_spec=fixed_spec) as device: + print(f"Connected to: {device.name}") + print("Rotations are now inverted!\n") + + for _ in range(500): + state = device.read() + if any([state.roll, state.pitch, state.yaw]): + print(f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " + f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}") + time.sleep(0.01) + + +if __name__ == "__main__": + try: + example_invert_rotations() + except KeyboardInterrupt: + print("\nExiting...") diff --git a/examples/10_custom_config_unity.py b/examples/10_custom_config_unity.py new file mode 100644 index 0000000..042f871 --- /dev/null +++ b/examples/10_custom_config_unity.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Example: Custom device configuration with axis remapping. + +This example shows how to create entirely custom device configurations, +in this case following the left-handed Unity convention (Z forward, X right, Y up). +""" + +import time + +import pyspacemouse + + +def example_unity_convention(): + """Create entirely custom device configuration with the Unity convention.""" + print("\n" + "=" * 60) + print("Example 3: Create custom device spec (Unity convention)") + print("=" * 60) + + # This shows how to create a completely custom device spec + # Useful for unsupported devices or complete remapping + custom_spec = pyspacemouse.create_device_info( + name="CustomSpaceMouse", + vendor_id=0x256F, # 3Dconnexion + product_id=0xC63A, # SpaceMouse Compact + mappings={ + # Each mapping: (channel, byte1, byte2, scale) + # Scale: 1 = normal direction, -1 = inverted + "y": (1, 1, 2, 1), + "z": (1, 3, 4, -1), + "x": (1, 5, 6, 1), + "yaw": (1, 7, 8, -1), + "roll": (1, 9, 10, -1), + "pitch": (1, 11, 12, 1), + }, + buttons={ + "LEFT": (3, 1, 0), + "RIGHT": (3, 1, 1), + }, + ) + + print(f"Created custom Unity spec: {custom_spec.name}") + print(f" VID/PID: {custom_spec.vendor_id:#06x}/{custom_spec.product_id:#06x}") + print(f" Axes: {list(custom_spec.mappings.keys())}") + print(f" Buttons: {custom_spec.button_names}") + + with pyspacemouse.open(device_spec=custom_spec) as device: + print(f"Connected to: {device.name} with Unity convention") + + for _ in range(5000): + state = device.read() + if any([state.x, state.y, state.z, state.roll, state.pitch, state.yaw]): + print(f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " + f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}") + time.sleep(0.01) + + +if __name__ == "__main__": + try: + example_unity_convention() + except KeyboardInterrupt: + print("\nExiting...") diff --git a/mkdocs.yml b/mkdocs.yml index 10c4657..73fd5dd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,6 +90,8 @@ plugins: markdown_extensions: - pymdownx.highlight - pymdownx.superfences + - pymdownx.snippets: + check_paths: true - admonition - pymdownx.details - pymdownx.superfences From 46449a7193592222763dc3c5b403d6f8f395442e Mon Sep 17 00:00:00 2001 From: "peter.mitrano" Date: Sat, 20 Jun 2026 16:33:31 +0200 Subject: [PATCH 2/9] format --- examples/09_invert_rotations.py | 4 ++-- examples/10_custom_config_unity.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/09_invert_rotations.py b/examples/09_invert_rotations.py index f3842e2..5fa173d 100644 --- a/examples/09_invert_rotations.py +++ b/examples/09_invert_rotations.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""Example: Modification of device specs +"""Example: Inverting axes -This example shows how to invert translation/rotation on specific axes +This example shows how to invert specific axes (e.g. x, roll, ... ) """ import time diff --git a/examples/10_custom_config_unity.py b/examples/10_custom_config_unity.py index 042f871..8c9bf58 100644 --- a/examples/10_custom_config_unity.py +++ b/examples/10_custom_config_unity.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -"""Example: Custom device configuration with axis remapping. +"""Example: Custom device configuration This example shows how to create entirely custom device configurations, -in this case following the left-handed Unity convention (Z forward, X right, Y up). +In this case following the left-handed Unity convention (Z forward, X right, Y up). """ import time From 3246c1f372fec287f3498efdc5fe05fb3894058c Mon Sep 17 00:00:00 2001 From: "peter.mitrano" Date: Sat, 20 Jun 2026 16:38:35 +0200 Subject: [PATCH 3/9] remove sourcery reviews -- too fluffy --- .sourcery.yaml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .sourcery.yaml diff --git a/.sourcery.yaml b/.sourcery.yaml deleted file mode 100644 index 3926064..0000000 --- a/.sourcery.yaml +++ /dev/null @@ -1,3 +0,0 @@ -refactor: - skip: - - use-named-expression # Python 3.8+ From 16ee2db6bf0590e3c05e261d259029e9eb084914 Mon Sep 17 00:00:00 2001 From: "peter.mitrano" Date: Sat, 20 Jun 2026 16:43:16 +0200 Subject: [PATCH 4/9] format --- .envrc | 2 ++ docs/mouseApi/examples.md | 11 ----------- examples/09_invert_rotations.py | 6 ++++-- examples/10_custom_config_unity.py | 6 ++++-- 4 files changed, 10 insertions(+), 15 deletions(-) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..77665aa --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +export PIP_CONFIG_FILE=/dev/null +hatch shell diff --git a/docs/mouseApi/examples.md b/docs/mouseApi/examples.md index 20c9906..f28991d 100644 --- a/docs/mouseApi/examples.md +++ b/docs/mouseApi/examples.md @@ -1,16 +1,5 @@ # Examples -- [01. Basic](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/01_basic.py) -- [02. Callbacks](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/02_callbacks.py) -- [03. Multi Device](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/03_multi_device.py) -- [04. Open By Path](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/04_open_by_path.py) -- [05. Discovery](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/05_discovery.py) -- [06. Axis Callbacks](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/06_axis_callbacks.py) -- [07. Led](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/07_led.py) -- [08. Buttons](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/08_buttons.py) -- [09. Invert Rotations](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/09_invert_rotations.py) -- [10. Custom Config Unity](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/10_custom_config_unity.py) - ## 01. Basic - File: [01_basic.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/01_basic.py) diff --git a/examples/09_invert_rotations.py b/examples/09_invert_rotations.py index 5fa173d..2d05f05 100644 --- a/examples/09_invert_rotations.py +++ b/examples/09_invert_rotations.py @@ -40,8 +40,10 @@ def example_invert_rotations(): for _ in range(500): state = device.read() if any([state.roll, state.pitch, state.yaw]): - print(f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " - f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}") + print( + f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " + f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}" + ) time.sleep(0.01) diff --git a/examples/10_custom_config_unity.py b/examples/10_custom_config_unity.py index 8c9bf58..414551f 100644 --- a/examples/10_custom_config_unity.py +++ b/examples/10_custom_config_unity.py @@ -49,8 +49,10 @@ def example_unity_convention(): for _ in range(5000): state = device.read() if any([state.x, state.y, state.z, state.roll, state.pitch, state.yaw]): - print(f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " - f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}") + print( + f"x={state.x:+.2f} y={state.y:+.2f} z={state.z:+.2f} " + f"roll={state.roll:+.2f} pitch={state.pitch:+.2f} yaw={state.yaw:+.2f}" + ) time.sleep(0.01) From 2132d8ec709d45633fd6c83ba2eabc083a4b6ea3 Mon Sep 17 00:00:00 2001 From: "peter.mitrano" Date: Sat, 20 Jun 2026 16:49:55 +0200 Subject: [PATCH 5/9] cleanup, remove envrc --- .envrc | 2 -- docs/mouseApi/examples.md | 2 +- examples/09_invert_rotations.py | 4 ++-- examples/10_custom_config_unity.py | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) delete mode 100644 .envrc diff --git a/.envrc b/.envrc deleted file mode 100644 index 77665aa..0000000 --- a/.envrc +++ /dev/null @@ -1,2 +0,0 @@ -export PIP_CONFIG_FILE=/dev/null -hatch shell diff --git a/docs/mouseApi/examples.md b/docs/mouseApi/examples.md index f28991d..8bdea96 100644 --- a/docs/mouseApi/examples.md +++ b/docs/mouseApi/examples.md @@ -83,7 +83,7 @@ ## 09. Invert Rotations - File: [09_invert_rotations.py](https://github.com/JakubAndrysek/PySpaceMouse/blob/master/examples/09_invert_rotations.py) -- Summary: Example: Modification of device specs +- Summary: Example: Invert rotation axes - Run: `python examples/09_invert_rotations.py` ```py title="examples/09_invert_rotations.py" diff --git a/examples/09_invert_rotations.py b/examples/09_invert_rotations.py index 2d05f05..fdb60a4 100644 --- a/examples/09_invert_rotations.py +++ b/examples/09_invert_rotations.py @@ -11,9 +11,9 @@ def example_invert_rotations(): - """Show how to fix rotation axes for specific conventions.""" + """Show how to fix rotation axes""" print("\n" + "=" * 60) - print("Example 2: Invert rotation conventions") + print("Invert rotations") print("=" * 60) connected = pyspacemouse.get_connected_devices() diff --git a/examples/10_custom_config_unity.py b/examples/10_custom_config_unity.py index 414551f..b8637f0 100644 --- a/examples/10_custom_config_unity.py +++ b/examples/10_custom_config_unity.py @@ -13,7 +13,7 @@ def example_unity_convention(): """Create entirely custom device configuration with the Unity convention.""" print("\n" + "=" * 60) - print("Example 3: Create custom device spec (Unity convention)") + print("Create custom device spec (Unity convention)") print("=" * 60) # This shows how to create a completely custom device spec @@ -21,7 +21,7 @@ def example_unity_convention(): custom_spec = pyspacemouse.create_device_info( name="CustomSpaceMouse", vendor_id=0x256F, # 3Dconnexion - product_id=0xC63A, # SpaceMouse Compact + product_id=0xC63A, # SpaceMouse Wireless New mappings={ # Each mapping: (channel, byte1, byte2, scale) # Scale: 1 = normal direction, -1 = inverted From c8978ed8ff5743ff67077e8253a07383ef69db4f Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 29 Jun 2026 20:30:31 +0200 Subject: [PATCH 6/9] upgrade hatch to fix docs deploy --- .github/workflows/deploy-web.yaml | 2 +- README.md | 5 +++-- docs/README.md | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-web.yaml b/.github/workflows/deploy-web.yaml index ef7c969..e64b6ed 100644 --- a/.github/workflows/deploy-web.yaml +++ b/.github/workflows/deploy-web.yaml @@ -20,7 +20,7 @@ jobs: with: python-version: "3.12" - name: Install Hatch - run: pip install hatch==1.15.1 + run: pip install hatch - uses: actions/cache@v4 with: key: mkdocs-${{ github.ref }} diff --git a/README.md b/README.md index c722db1..ab333fc 100644 --- a/README.md +++ b/README.md @@ -261,8 +261,9 @@ This project includes a `Makefile` with commands for creating a virtual environm You will need `hatch` and `pre-commit` for this. You can get these by using -``` -pipx install hatch==1.15.1 pre-commit +```bash +# Most recently tested with hatch 1.17.0 +pipx install hatch pre-commit ``` If you're not familiar with pipx, it lets you install python tools into isolated environments in `~/.local`. diff --git a/docs/README.md b/docs/README.md index c82238a..5dd34ea 100644 --- a/docs/README.md +++ b/docs/README.md @@ -261,8 +261,9 @@ This project includes a `Makefile` with commands for creating a virtual environm You will need `hatch` and `pre-commit` for this. You can get these by using -``` -pipx install hatch==1.15.1 pre-commit +```bash +# Most recently tested with hatch 1.17.0 +pipx install hatch pre-commit ``` If you're not familiar with pipx, it lets you install python tools into isolated environments in `~/.local`. From 7bd2219c0eac2eab17796619625f3e66a4fd858b Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 29 Jun 2026 20:42:00 +0200 Subject: [PATCH 7/9] address review about how example 9 is odd --- docs/CONTRIBUTING.md | 2 +- examples/09_invert_rotations.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index e9fef41..167feb3 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -107,7 +107,7 @@ Once your device works correctly: 1. Create a branch: `git checkout -b add-device-` 2. Add your device entry to `devices.toml` -3. Test thoroughly with the examples in `examples/` +3. Test thoroughly with the examples in `examples/`. If you add a new example, update the README and `docs/mouseApi/examples.md`. 4. Submit a PR with your device name and any notes about testing ### Using Custom Configuration (Without Modifying Library) diff --git a/examples/09_invert_rotations.py b/examples/09_invert_rotations.py index fdb60a4..b42d0f4 100644 --- a/examples/09_invert_rotations.py +++ b/examples/09_invert_rotations.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 -"""Example: Inverting axes +"""Example: Inverting rotations -This example shows how to invert specific axes (e.g. x, roll, ... ) +This example shows how to invert axes, in this case the rotation axes (roll, pitch, yaw). +Just for demonstration purposes, this would be pretty weird :) """ import time @@ -11,7 +12,7 @@ def example_invert_rotations(): - """Show how to fix rotation axes""" + """Show how to invert rotation axes""" print("\n" + "=" * 60) print("Invert rotations") print("=" * 60) @@ -25,7 +26,7 @@ def example_invert_rotations(): base_spec_legacy = specs[connected[0]] base_spec = pyspacemouse.apply_axis_convention(base_spec_legacy, AxisConvention.HID_Z_UP) - # Invert roll, pitch, and yaw so rotations are left-handed + # Invert roll, pitch, and yaw fixed_spec = pyspacemouse.modify_device_info( base_spec, name=f"{connected[0]} (Fixed Rotations)", From e2dbb67340eb6fa090a509e7b9875b395aa4d3d6 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 29 Jun 2026 20:59:49 +0200 Subject: [PATCH 8/9] fix unity config and clarify its for for space mouse wirless new MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: https://docs.unity3d.com/6000.3/Documentation/Manual/QuaternionAndEulerRotationsInUnity.html > Unity uses a left-handed coordinate system: the positive x-axis points to the right, the positive y-axis points up, and the positive z-axis points forward. Unity’s left-handed coordinate system means that the direction of rotation from the positive x-axis to the positive y-axis is counterclockwise when looking along the positive z-axis. --- examples/10_custom_config_unity.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/10_custom_config_unity.py b/examples/10_custom_config_unity.py index b8637f0..aefc7c7 100644 --- a/examples/10_custom_config_unity.py +++ b/examples/10_custom_config_unity.py @@ -2,7 +2,8 @@ """Example: Custom device configuration This example shows how to create entirely custom device configurations, -In this case following the left-handed Unity convention (Z forward, X right, Y up). +In this case for a "Spacemouse Wireless New", following the left-handed Unity convention (Z forward, X right, Y up). +If you have a totally custom HID device, you just need to know the byte layout of the device and you can create a custom configuration for it. """ import time @@ -25,12 +26,12 @@ def example_unity_convention(): mappings={ # Each mapping: (channel, byte1, byte2, scale) # Scale: 1 = normal direction, -1 = inverted - "y": (1, 1, 2, 1), + "x": (1, 1, 2, 1), + "y": (1, 5, 6, -1), "z": (1, 3, 4, -1), - "x": (1, 5, 6, 1), - "yaw": (1, 7, 8, -1), - "roll": (1, 9, 10, -1), + "yaw": (1, 9, 10, 1), "pitch": (1, 11, 12, 1), + "roll": (1, 7, 8, -1), }, buttons={ "LEFT": (3, 1, 0), @@ -44,7 +45,7 @@ def example_unity_convention(): print(f" Buttons: {custom_spec.button_names}") with pyspacemouse.open(device_spec=custom_spec) as device: - print(f"Connected to: {device.name} with Unity convention") + print(f"Connected to: {device.name} with custom spec") for _ in range(5000): state = device.read() From 5afc5eea22ec7973681702057813fd209fbc5ced Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 29 Jun 2026 21:10:58 +0200 Subject: [PATCH 9/9] pre-commit --- docs/assets/hid_axes.svg | 2 +- docs/assets/hid_z_up_axes.svg | 2 +- docs/assets/legacy_axis.svg | 2 +- examples/03_multi_device.py | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/assets/hid_axes.svg b/docs/assets/hid_axes.svg index 775cea2..be48db7 100644 --- a/docs/assets/hid_axes.svg +++ b/docs/assets/hid_axes.svg @@ -459,4 +459,4 @@ - \ No newline at end of file + diff --git a/docs/assets/hid_z_up_axes.svg b/docs/assets/hid_z_up_axes.svg index 6998330..65532a2 100644 --- a/docs/assets/hid_z_up_axes.svg +++ b/docs/assets/hid_z_up_axes.svg @@ -453,4 +453,4 @@ - \ No newline at end of file + diff --git a/docs/assets/legacy_axis.svg b/docs/assets/legacy_axis.svg index 9ac7c08..7f1f099 100644 --- a/docs/assets/legacy_axis.svg +++ b/docs/assets/legacy_axis.svg @@ -445,4 +445,4 @@ - \ No newline at end of file + diff --git a/examples/03_multi_device.py b/examples/03_multi_device.py index 89bfea3..98cd699 100644 --- a/examples/03_multi_device.py +++ b/examples/03_multi_device.py @@ -26,7 +26,9 @@ def main(): # Open two devices by path with pyspacemouse.open_by_path(path0, axis_convention=AxisConvention.HID_Z_UP) as left_hand: - with pyspacemouse.open_by_path(path1, axis_convention=AxisConvention.HID_Z_UP) as right_hand: + with pyspacemouse.open_by_path( + path1, axis_convention=AxisConvention.HID_Z_UP + ) as right_hand: print(f"Left hand: {left_hand.name}") print(f"Right hand: {right_hand.name}") print()