meltygui 0.1.0__py3-none-any.whl
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.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
3D mouse (3Dconnexion SpaceMouse) input for the InputHandler.
|
|
3
|
+
|
|
4
|
+
The device is read through spacenavd's Unix socket, not the evdev node:
|
|
5
|
+
spacenavd grabs /dev/input/eventN exclusively (so the puck can't double as
|
|
6
|
+
a pointer), needs no group membership on our side, and applies the user's
|
|
7
|
+
/etc/spnavrc sensitivity / dead zone / axis inversion before we see anything.
|
|
8
|
+
The wire format is spacenavd's protocol v0 — the one every client gets
|
|
9
|
+
without a handshake: a stream of 32-byte frames, eight native int32s each:
|
|
10
|
+
|
|
11
|
+
[0, x, y, z, rx, ry, rz, period_ms] motion (raw units, ±FULL_DEFLECTION)
|
|
12
|
+
[1, button, 0, ...] button press
|
|
13
|
+
[2, button, 0, ...] button release
|
|
14
|
+
|
|
15
|
+
Three pieces, deliberately separate:
|
|
16
|
+
|
|
17
|
+
- `_Reader`: one daemon thread per PROCESS (parked on sys._lsd_space_mouse
|
|
18
|
+
so a studio restart or a hotswap never spawns a second one) that keeps the
|
|
19
|
+
socket open, reconnects while spacenavd is away, and holds the LEVEL
|
|
20
|
+
state — the latest deflection of the six axes, stamped with its arrival
|
|
21
|
+
time — plus a queue of button edges. It never touches the handler.
|
|
22
|
+
- `pump(handler)`: called once per frame beside the GLFW backend's pump. It
|
|
23
|
+
samples the level state like the cursor position is sampled (level
|
|
24
|
+
state, per frame: nothing is lost by not queueing every report) and
|
|
25
|
+
feeds ONE axes event holding the deflection integrated over the frame —
|
|
26
|
+
full-deflection-seconds, so a view's sensitivity is "per second at full
|
|
27
|
+
push" and frame rate never changes how fast the camera moves.
|
|
28
|
+
- The mapping helpers (`normalize`) — dead zone, axis swap / inversion,
|
|
29
|
+
unit scale — pure functions over the raw tuple, tested offline.
|
|
30
|
+
|
|
31
|
+
Every knob is in Toggles.SpaceMouse (device side); what a view DOES with the
|
|
32
|
+
axes is that view's business (draw_voxels: the navigation knobs in the same Toggles.SpaceMouse).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
import socket
|
|
37
|
+
import struct
|
|
38
|
+
import sys
|
|
39
|
+
import threading
|
|
40
|
+
import time
|
|
41
|
+
import weakref
|
|
42
|
+
from collections import deque
|
|
43
|
+
|
|
44
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
45
|
+
|
|
46
|
+
# The input_id a view subscribes to: `space_mouse_changed(event` on a
|
|
47
|
+
# render_func signature. Buttons are `space_mouse_button_0_down` etc.
|
|
48
|
+
INPUT_ID = "space_mouse"
|
|
49
|
+
BUTTON_ID = "space_mouse_button_{}"
|
|
50
|
+
# Axis order on the wire and in event.axes.
|
|
51
|
+
AXES = ("tx", "ty", "tz", "rx", "ry", "rz")
|
|
52
|
+
# Blender's hand, measured against it on 09-04 (Lukas, spacenavd's stream on
|
|
53
|
+
# a SpaceMouse Wireless): translation passes straight through - push right,
|
|
54
|
+
# the volume shifts right; push forward, it comes closer - and every ROTATION
|
|
55
|
+
# is negated relative to the raw stream. Applied in normalize() so the axes
|
|
56
|
+
# reach views in OBJECT terms (voxel_camera.apply_space_mouse).
|
|
57
|
+
BLENDER_SIGNS = (1.0, 1.0, 1.0, -1.0, -1.0, -1.0)
|
|
58
|
+
_FRAME = struct.Struct("@8i")
|
|
59
|
+
FRAME_SIZE = _FRAME.size
|
|
60
|
+
_MOTION, _PRESS, _RELEASE = 0, 1, 2
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_frame(data: bytes):
|
|
64
|
+
"""One protocol-v0 frame → ("motion", (x, y, z, rx, ry, rz)) or
|
|
65
|
+
("button", index, pressed) or None for a frame type we don't know
|
|
66
|
+
(skipped, the stream stays aligned since every frame is FRAME_SIZE)."""
|
|
67
|
+
kind, a, b, c, d, e, f, _period = _FRAME.unpack(data)
|
|
68
|
+
if kind == _MOTION:
|
|
69
|
+
return "motion", (a, b, c, d, e, f)
|
|
70
|
+
if kind == _PRESS or kind == _RELEASE:
|
|
71
|
+
return "button", a, kind == _PRESS
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def axis_sensitivities():
|
|
76
|
+
"""Toggles.SpaceMouse's six per-axis floats, in AXES order."""
|
|
77
|
+
return (float(Toggles.SpaceMouse.tx_sensitivity), float(Toggles.SpaceMouse.ty_sensitivity),
|
|
78
|
+
float(Toggles.SpaceMouse.tz_sensitivity), float(Toggles.SpaceMouse.rx_sensitivity),
|
|
79
|
+
float(Toggles.SpaceMouse.ry_sensitivity), float(Toggles.SpaceMouse.rz_sensitivity))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize(raw, *, full_deflection=None, dead_zone=None, swap_yz=None,
|
|
83
|
+
invert_axes=None, sensitivities=None):
|
|
84
|
+
"""Raw device units → (tx, ty, tz, rx, ry, rz) in full-deflection units
|
|
85
|
+
(±1 at full push before the per-axis sensitivity), with
|
|
86
|
+
Toggles.SpaceMouse's dead zone, y/z swap, Blender's hand (BLENDER_SIGNS),
|
|
87
|
+
per-axis inversion and per-axis sensitivity applied (keyword overrides
|
|
88
|
+
exist for the tests). The result is in OBJECT terms — the motion the
|
|
89
|
+
view's content should make — so no consumer needs to know the hand."""
|
|
90
|
+
full = float(Toggles.SpaceMouse.full_deflection if full_deflection is None else full_deflection)
|
|
91
|
+
dead = float(Toggles.SpaceMouse.dead_zone if dead_zone is None else dead_zone)
|
|
92
|
+
swap = Toggles.SpaceMouse.swap_yz if swap_yz is None else swap_yz
|
|
93
|
+
invert = Toggles.SpaceMouse.invert_axes if invert_axes is None else invert_axes
|
|
94
|
+
sens = axis_sensitivities() if sensitivities is None else sensitivities
|
|
95
|
+
v = [float(r) / full for r in raw]
|
|
96
|
+
if swap:
|
|
97
|
+
v[1], v[2], v[4], v[5] = v[2], v[1], v[5], v[4]
|
|
98
|
+
out = []
|
|
99
|
+
for name, x, hand, gain in zip(AXES, v, BLENDER_SIGNS, sens):
|
|
100
|
+
# Dead zone re-scaled so the live range still starts at 0 (no jump
|
|
101
|
+
# across the threshold) and still reaches 1.0 at full push.
|
|
102
|
+
mag = abs(x)
|
|
103
|
+
if mag <= dead:
|
|
104
|
+
x = 0.0
|
|
105
|
+
elif dead > 0.0:
|
|
106
|
+
x = (mag - dead) / (1.0 - dead) * (1.0 if x > 0 else -1.0)
|
|
107
|
+
x = max(-1.0, min(1.0, x * hand)) * gain
|
|
108
|
+
if name in invert:
|
|
109
|
+
x = -x
|
|
110
|
+
out.append(x)
|
|
111
|
+
return tuple(out)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class _Reader:
|
|
115
|
+
"""The socket thread + level state. One per process."""
|
|
116
|
+
|
|
117
|
+
def __init__(self):
|
|
118
|
+
self.lock = threading.Lock()
|
|
119
|
+
self.raw = (0, 0, 0, 0, 0, 0) # latest motion frame, raw units
|
|
120
|
+
self.stamp = 0.0 # perf_counter of latest frame
|
|
121
|
+
self.buttons = [] # button (index, pressed) edges
|
|
122
|
+
self.connected = False
|
|
123
|
+
self.error = None
|
|
124
|
+
self.frames = 0
|
|
125
|
+
self._sock = None
|
|
126
|
+
self._thread = threading.Thread(target=self._run, name="space-mouse", daemon=True)
|
|
127
|
+
self._thread.start()
|
|
128
|
+
|
|
129
|
+
# ── the thread ──────────────────────────────────────────────────────────
|
|
130
|
+
def _run(self):
|
|
131
|
+
while True:
|
|
132
|
+
if not Toggles.SpaceMouse.enabled:
|
|
133
|
+
self._drop()
|
|
134
|
+
time.sleep(0.5)
|
|
135
|
+
continue
|
|
136
|
+
if self._sock is None and not self._connect():
|
|
137
|
+
time.sleep(float(Toggles.SpaceMouse.retry_s))
|
|
138
|
+
continue
|
|
139
|
+
try:
|
|
140
|
+
data = self._sock.recv(FRAME_SIZE * 64)
|
|
141
|
+
except socket.timeout:
|
|
142
|
+
continue
|
|
143
|
+
except OSError as e:
|
|
144
|
+
self.error = str(e)
|
|
145
|
+
self._drop()
|
|
146
|
+
continue
|
|
147
|
+
if not data:
|
|
148
|
+
self.error = "spacenavd closed the socket"
|
|
149
|
+
self._drop()
|
|
150
|
+
continue
|
|
151
|
+
self._feed(data)
|
|
152
|
+
|
|
153
|
+
def _connect(self):
|
|
154
|
+
if not hasattr(socket, "AF_UNIX"): # Windows: spacenavd is unix-socket only
|
|
155
|
+
self.error = "spacenavd needs unix sockets, which this platform lacks"
|
|
156
|
+
return False
|
|
157
|
+
try:
|
|
158
|
+
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
159
|
+
s.settimeout(1.0)
|
|
160
|
+
s.connect(str(Toggles.SpaceMouse.socket_path))
|
|
161
|
+
except OSError as e:
|
|
162
|
+
self.error = str(e)
|
|
163
|
+
return False
|
|
164
|
+
self._sock = s
|
|
165
|
+
self._buffer = b""
|
|
166
|
+
self.connected = True
|
|
167
|
+
self.error = None
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
def _drop(self):
|
|
171
|
+
if self._sock is not None:
|
|
172
|
+
try:
|
|
173
|
+
self._sock.close()
|
|
174
|
+
except OSError:
|
|
175
|
+
pass
|
|
176
|
+
self._sock = None
|
|
177
|
+
self.connected = False
|
|
178
|
+
with self.lock:
|
|
179
|
+
self.raw = (0, 0, 0, 0, 0, 0)
|
|
180
|
+
|
|
181
|
+
def _feed(self, data: bytes):
|
|
182
|
+
buf = self._buffer + data
|
|
183
|
+
pos = 0
|
|
184
|
+
now = time.perf_counter()
|
|
185
|
+
moved = False
|
|
186
|
+
while len(buf) - pos >= FRAME_SIZE:
|
|
187
|
+
frame = parse_frame(buf[pos:pos + FRAME_SIZE])
|
|
188
|
+
pos += FRAME_SIZE
|
|
189
|
+
self.frames += 1
|
|
190
|
+
if frame is None:
|
|
191
|
+
continue
|
|
192
|
+
if frame[0] == "motion":
|
|
193
|
+
with self.lock:
|
|
194
|
+
self.raw = frame[1]
|
|
195
|
+
self.stamp = now
|
|
196
|
+
moved = True
|
|
197
|
+
else:
|
|
198
|
+
with self.lock:
|
|
199
|
+
self.buttons.append((frame[1], frame[2]))
|
|
200
|
+
moved = True
|
|
201
|
+
self._buffer = buf[pos:]
|
|
202
|
+
if moved:
|
|
203
|
+
# Wake the render loop (glfw.wait_events otherwise sleeps until
|
|
204
|
+
# a pointer/keyboard event): the next frame's pump samples us.
|
|
205
|
+
_wake()
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _wake():
|
|
209
|
+
try:
|
|
210
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
211
|
+
request_render()
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def reader() -> _Reader:
|
|
217
|
+
"""The process-lifetime reader, created on first use."""
|
|
218
|
+
r = getattr(sys, "_lsd_space_mouse", None)
|
|
219
|
+
if r is None:
|
|
220
|
+
r = _Reader()
|
|
221
|
+
sys._lsd_space_mouse = r
|
|
222
|
+
return r
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def start():
|
|
226
|
+
"""Start reading (idempotent). Called from Melty.init_input_backend."""
|
|
227
|
+
reader()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Per-frame sampling state: the last pump time and the axes last fed, so a
|
|
231
|
+
# release still delivers one final zero event (the view sees a gesture end).
|
|
232
|
+
_PUMP = globals().get("_PUMP") or {"last": 0.0, "active": False, "buttons_down": set(),
|
|
233
|
+
"status": None, "frame_ms": 0.0}
|
|
234
|
+
# (pump time, reader.frames) samples for the report-rate estimate in stats().
|
|
235
|
+
# Counted on the PUMP side, not in the reader thread: the reader is a
|
|
236
|
+
# process-lifetime object bound to whatever class it was born with, so a
|
|
237
|
+
# reload swap of this file never changes the code its thread runs: a stamp field
|
|
238
|
+
# filled by _feed caused 0 Hz after a swap (Lukas 09-04). The frames counter is
|
|
239
|
+
# the one thing every reader version has always kept.
|
|
240
|
+
_RATE = globals().get("_RATE") or deque(maxlen=128)
|
|
241
|
+
# Views that show the reading location (draw_space_mouse) without being the
|
|
242
|
+
# hovered event target: their draw_states, invalidated in the pump on every
|
|
243
|
+
# active frame, once more on release, and when the connection status flips.
|
|
244
|
+
_WATCHERS: "weakref.WeakSet" = globals().get("_WATCHERS") or weakref.WeakSet()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def watch(draw_state):
|
|
248
|
+
"""Register a draw_state to be invalidated whenever the reading moves."""
|
|
249
|
+
_WATCHERS.add(draw_state)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _invalidate_watchers():
|
|
253
|
+
for draw_state in list(_WATCHERS):
|
|
254
|
+
try:
|
|
255
|
+
draw_state.invalidate()
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def pump(handler, now: float = None):
|
|
261
|
+
"""Per frame: integrate the level state over the frame and feed the
|
|
262
|
+
handler. `now` is injectable for the tests."""
|
|
263
|
+
r = getattr(sys, "_lsd_space_mouse", None)
|
|
264
|
+
if r is None:
|
|
265
|
+
return
|
|
266
|
+
now = time.perf_counter() if now is None else now
|
|
267
|
+
dt = now - _PUMP["last"]
|
|
268
|
+
_PUMP["last"] = now
|
|
269
|
+
_PUMP["frame_ms"] = dt * 1000.0
|
|
270
|
+
_RATE.append((now, int(getattr(r, "frames", 0))))
|
|
271
|
+
dt = max(0.0, min(dt, float(Toggles.SpaceMouse.max_frame_dt)))
|
|
272
|
+
with r.lock:
|
|
273
|
+
raw, stamp = r.raw, r.stamp
|
|
274
|
+
buttons, r.buttons = r.buttons, []
|
|
275
|
+
# A stuck level (spacedavd died mid-push, a dropped release frame) should
|
|
276
|
+
# never keep the camera moving: past stale_s the reading is zero.
|
|
277
|
+
if now - stamp > float(Toggles.SpaceMouse.stale_s):
|
|
278
|
+
raw = (0, 0, 0, 0, 0, 0)
|
|
279
|
+
axes = normalize(raw)
|
|
280
|
+
active = any(a != 0.0 for a in axes)
|
|
281
|
+
if active or _PUMP["active"]:
|
|
282
|
+
handler.feed_axes(INPUT_ID, tuple(a * dt for a in axes), now)
|
|
283
|
+
_invalidate_watchers()
|
|
284
|
+
_PUMP["active"] = active
|
|
285
|
+
current_status = (r.connected, r.error)
|
|
286
|
+
if current_status != _PUMP["status"]:
|
|
287
|
+
_PUMP["status"] = current_status
|
|
288
|
+
_invalidate_watchers()
|
|
289
|
+
if active:
|
|
290
|
+
_wake() # keep frames coming while the puck is pressed
|
|
291
|
+
for index, pressed in buttons:
|
|
292
|
+
input_id = BUTTON_ID.format(index)
|
|
293
|
+
if pressed:
|
|
294
|
+
_PUMP["buttons_down"].add(input_id)
|
|
295
|
+
handler.feed_down(input_id, t=now)
|
|
296
|
+
elif input_id in _PUMP["buttons_down"]:
|
|
297
|
+
_PUMP["buttons_down"].discard(input_id)
|
|
298
|
+
handler.feed_up(input_id, t=now)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def active() -> bool:
|
|
302
|
+
"""True while the cap is deflected (as of the last pump) — the studio's
|
|
303
|
+
gesture flag: Melty folds it into on_drag so the churn suppression a
|
|
304
|
+
mouse drag gets (hover invalidation, occluder diffs, content-height
|
|
305
|
+
commits, RenderHost draws) holds for a 3D-mouse flight too."""
|
|
306
|
+
return bool(_PUMP["active"])
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def stats(now: float = None) -> dict:
|
|
310
|
+
"""Latency diagnostics: `rate_hz` = the device's motion-report rate over
|
|
311
|
+
the last reports, `age_ms` = how old the newest report is at this call,
|
|
312
|
+
`frame_ms` = the interval between the last two pumps (the studio's frame
|
|
313
|
+
time while the cap is held). Latency = age + the frame + presentation."""
|
|
314
|
+
r = getattr(sys, "_lsd_space_mouse", None)
|
|
315
|
+
now = time.perf_counter() if now is None else now
|
|
316
|
+
if r is None:
|
|
317
|
+
return {"rate_hz": 0.0, "age_ms": 0.0, "frame_ms": _PUMP["frame_ms"]}
|
|
318
|
+
with r.lock:
|
|
319
|
+
stamp = r.stamp
|
|
320
|
+
# Reports per second in the pump samples of the last second: the
|
|
321
|
+
# counter delta over the time delta, so reports between the frames all
|
|
322
|
+
# count (a per-frame stamp compare would fold them into one).
|
|
323
|
+
rate = 0.0
|
|
324
|
+
recent = [(t, n) for t, n in _RATE if now - t <= 1.0]
|
|
325
|
+
if len(recent) >= 2 and recent[-1][0] > recent[0][0]:
|
|
326
|
+
rate = (recent[-1][1] - recent[0][1]) / (recent[-1][0] - recent[0][0])
|
|
327
|
+
age = (now - stamp) * 1000.0 if stamp else 0.0
|
|
328
|
+
return {"rate_hz": rate, "age_ms": age, "frame_ms": _PUMP["frame_ms"]}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def status() -> str:
|
|
332
|
+
"""One line for a status row / the console."""
|
|
333
|
+
r = getattr(sys, "_lsd_space_mouse", None)
|
|
334
|
+
if r is None:
|
|
335
|
+
return "not started"
|
|
336
|
+
if r.connected:
|
|
337
|
+
return f"connected ({r.frames} frames)"
|
|
338
|
+
return f"disconnected: {r.error or 'connecting'}"
|