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,124 @@
|
|
|
1
|
+
"""MCP Query window — the mcp_query.py collectors, run live in the studio.
|
|
2
|
+
|
|
3
|
+
The five MCP tools (`find_views`, `describe_view`, `hit_test`,
|
|
4
|
+
`param_sources`, `tile_cache`) are thin wrappers around one collector each:
|
|
5
|
+
the server hands `mcp_eval.request_call` a no-arg closure, the render thread
|
|
6
|
+
runs it between frames, and the answer is JSON-dumped back to the client.
|
|
7
|
+
This window is a second front end onto exactly those collectors — a
|
|
8
|
+
render_func body ALREADY runs on the render thread, so it calls
|
|
9
|
+
`mcp_query.collect_*` directly, with no queue and no server in the way. What
|
|
10
|
+
you read here is what an MCP client reads: the JSON tab dumps the answer the
|
|
11
|
+
way `run_query` does, so its bytes are the tool's bytes.
|
|
12
|
+
|
|
13
|
+
Pick a tool, edit its arguments, and the answer re-collects on every edit
|
|
14
|
+
(and on every Run press, since the studio moves under a fixed set of
|
|
15
|
+
arguments). The collector is looked up by NAME per run, so a hotswap of
|
|
16
|
+
mcp_query.py lands on the next frame — this window is the fast loop for
|
|
17
|
+
iterating on the collectors themselves.
|
|
18
|
+
|
|
19
|
+
`follow pointer` (hit_test only) re-collects at the live mouse position
|
|
20
|
+
every frame, which is the quickest way to sanity-check the BVH stack and the
|
|
21
|
+
subscription table: park the pointer over anything and read what claims it.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import time
|
|
26
|
+
import traceback
|
|
27
|
+
|
|
28
|
+
import meltygui_imgui as imgui
|
|
29
|
+
|
|
30
|
+
import meltygui.core.automation.mcp_query as mcp_query
|
|
31
|
+
from meltygui.core.melty import Melty
|
|
32
|
+
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
33
|
+
from meltygui.core.rendering.render_funcs import RenderFuncs
|
|
34
|
+
from meltygui.core.core_render import render_func
|
|
35
|
+
from meltygui.core.rendering.window_decoration import window
|
|
36
|
+
|
|
37
|
+
# Tool name -> its arguments and their defaults, in the order the real tool
|
|
38
|
+
# declares them. This table IS the API: an argument dict is rendered by
|
|
39
|
+
# draw_collection, so adding an argument to a collector means adding it here
|
|
40
|
+
# and nothing else. The collector itself is resolved by name at call time
|
|
41
|
+
# (`collect_<tool>`), never bound here, so hotswapping mcp_query.py takes
|
|
42
|
+
# effect immediately instead of pinning the function objects this module
|
|
43
|
+
# imported. Same reason studio_data.py keeps _SOURCES module-level: a
|
|
44
|
+
# dictionary the window reads, not a tuning constant (those live in Toggles).
|
|
45
|
+
_TOOL_ARGS = {
|
|
46
|
+
"find_views": {"func": "", "name": "", "window": "", "include_closed": False,
|
|
47
|
+
"limit": 50},
|
|
48
|
+
"describe_view": {"view": "", "children_depth": 1},
|
|
49
|
+
"hit_test": {"x": 0.0, "y": 0.0},
|
|
50
|
+
"param_sources": {"view": "", "param": ""},
|
|
51
|
+
"tile_cache": {"view": "", "history_frames": 200, "limit": 50},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _seed_args(panel_state):
|
|
56
|
+
"""Reconcile the persisted arguments against `_TOOL_ARGS`.
|
|
57
|
+
|
|
58
|
+
A tool or an argument added since the state was saved appears with its
|
|
59
|
+
default; one that no longer exists is dropped. This is what lets a
|
|
60
|
+
hotswap that adds an argument show it on the next frame.
|
|
61
|
+
"""
|
|
62
|
+
args = panel_state.args
|
|
63
|
+
for tool, defaults in _TOOL_ARGS.items():
|
|
64
|
+
current = args.get(tool)
|
|
65
|
+
if not isinstance(current, dict):
|
|
66
|
+
current = args[tool] = {}
|
|
67
|
+
for key, value in defaults.items():
|
|
68
|
+
current.setdefault(key, value)
|
|
69
|
+
for key in [key for key in current if key not in defaults]:
|
|
70
|
+
del current[key]
|
|
71
|
+
for tool in [tool for tool in args if tool not in _TOOL_ARGS]:
|
|
72
|
+
del args[tool]
|
|
73
|
+
if panel_state.tool not in _TOOL_ARGS:
|
|
74
|
+
panel_state.tool = next(iter(_TOOL_ARGS))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _pointer():
|
|
78
|
+
"""Where the input handler thinks the mouse is.
|
|
79
|
+
|
|
80
|
+
The same numbers `collect_hit_test` reports as `pointer`, so a followed
|
|
81
|
+
point reads `pointer_matches_point: true` and the subscription table is
|
|
82
|
+
populated. imgui's own mouse position is the fallback for a frame before
|
|
83
|
+
the handler has one.
|
|
84
|
+
"""
|
|
85
|
+
handler = getattr(Melty, "event_handler", None)
|
|
86
|
+
x = getattr(handler, "_cursor_x", None) if handler is not None else None
|
|
87
|
+
y = getattr(handler, "_cursor_y", None) if handler is not None else None
|
|
88
|
+
if x is None or y is None:
|
|
89
|
+
x, y = imgui.get_mouse_pos()
|
|
90
|
+
return round(float(x), 1), round(float(y), 1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _run(panel_state, tool, args):
|
|
94
|
+
"""Collect once, into the SAME result dict.
|
|
95
|
+
|
|
96
|
+
The answer is copied into the held dict rather than replacing it so the
|
|
97
|
+
result tree keeps its identity across runs — expanded rows stay expanded
|
|
98
|
+
while you edit an argument, which is the whole point of following the
|
|
99
|
+
"mutate in place" rule here. A collector that raises is reported in the
|
|
100
|
+
window instead of taking the frame down with it: these walk live state
|
|
101
|
+
and a half-built draw_state is exactly the case worth seeing.
|
|
102
|
+
"""
|
|
103
|
+
collect = getattr(mcp_query, "collect_" + tool, None)
|
|
104
|
+
started = time.perf_counter()
|
|
105
|
+
if collect is None:
|
|
106
|
+
answer, error = {}, f"mcp_query has no collect_{tool}"
|
|
107
|
+
else:
|
|
108
|
+
try:
|
|
109
|
+
answer, error = collect(**args), None
|
|
110
|
+
except Exception:
|
|
111
|
+
answer, error = {}, traceback.format_exc()
|
|
112
|
+
elapsed_ms = (time.perf_counter() - started) * 1000.0
|
|
113
|
+
panel_state._result.clear()
|
|
114
|
+
panel_state._result.update(answer)
|
|
115
|
+
panel_state._error = error
|
|
116
|
+
panel_state._json = json.dumps(answer, indent=1, default=str)
|
|
117
|
+
panel_state._stamp = (f"{elapsed_ms:.2f} ms · {len(panel_state._json)} B"
|
|
118
|
+
f" · frame {Melty.frame_count}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# disable_scroll=False: the window is a scrolling box of panes, and the
|
|
122
|
+
# @window code path defaults it to True.
|
|
123
|
+
from meltygui.view.query_view import draw_mcp_query
|
|
124
|
+
draw_mcp_query = window(icon='\uf188', display_name='MCP Query', tint=(0.1, 0.2, 0.28), initial={'width': 820, 'height': 700}, disable_scroll=False)(draw_mcp_query)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Search core functions and supporting definitions."""
|
|
2
|
+
import types
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def search_activate_target(node):
|
|
6
|
+
"""The draw_state Ctrl+Enter should 'click' for the current search match
|
|
7
|
+
`node` (meltygui.search_current_node). When the match is one of a collection's
|
|
8
|
+
keys, that's the child at the current key; for a leaf content match it's the
|
|
9
|
+
node itself."""
|
|
10
|
+
if node is None:
|
|
11
|
+
return None
|
|
12
|
+
key = getattr(node, '_search_current_key', None)
|
|
13
|
+
if key is not None:
|
|
14
|
+
child = node._children.get(key)
|
|
15
|
+
if child is not None:
|
|
16
|
+
return child
|
|
17
|
+
# Views that draw their matches as raw draw-list items (no child draw_states
|
|
18
|
+
# - e.g. the Fast Dock) publish the current match's row rect instead; hand
|
|
19
|
+
# back a geometry shim so the caller's center-of-rect click lands on the
|
|
20
|
+
# row rather than the view's center.
|
|
21
|
+
rect = getattr(node, '_search_current_rect', None)
|
|
22
|
+
if rect is not None:
|
|
23
|
+
return types.SimpleNamespace(abs_left=rect[0], abs_top=rect[1],
|
|
24
|
+
width=rect[2], height=rect[3],
|
|
25
|
+
_tile_id=node._tile_id)
|
|
26
|
+
return node
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""The UI path language: leaf-anchored selectors over the live draw_state
|
|
2
|
+
tree, used by change_value / orchestration cues to name a target the way a
|
|
3
|
+
person would — by the minimal distinguishing tail of its ancestry.
|
|
4
|
+
|
|
5
|
+
A PATH is a sequence of SEGMENTS matched as a SUBSEQUENCE of the target's
|
|
6
|
+
ancestor chain, anchored at the leaf: the last segment matches the target
|
|
7
|
+
itself, the earlier ones match ancestors in order with any depth of
|
|
8
|
+
unnamed/unmatched levels between (descendant matching is the default, so
|
|
9
|
+
nesting is never the caller's problem unless it is the distinguisher).
|
|
10
|
+
|
|
11
|
+
Segment kinds — each one is a data-description level:
|
|
12
|
+
"alpha" name (display name, the "##"-stripped str(ds.name))
|
|
13
|
+
1 ordinal: the nth match (visual order: abs_top, abs_left)
|
|
14
|
+
of the REMAINING path within the current scope —
|
|
15
|
+
"Loras/[1]/alpha" = the 2nd alpha under Loras. (A
|
|
16
|
+
per-node "nth child of its parent" reading was tried
|
|
17
|
+
and rejected: single-child wrapper levels made it
|
|
18
|
+
match nearly everything.)
|
|
19
|
+
Key("33071d") exact full name (no "##" strip) — collection keys
|
|
20
|
+
Lora type of the value the draw_state renders
|
|
21
|
+
callable predicate fn(ds) -> bool
|
|
22
|
+
|
|
23
|
+
Resolution requires UNIQUENESS: zero matches raise NoMatch, several raise
|
|
24
|
+
Ambiguous listing each candidate's minimal_path — the error is the
|
|
25
|
+
disambiguation. String sugar: "Loras/[1]/alpha" parses to ("Loras", 1,
|
|
26
|
+
"alpha"); tuples are the full-power form (Key/type/callable segments).
|
|
27
|
+
|
|
28
|
+
Everything takes an optional `universe` (iterable of draw_states) so the
|
|
29
|
+
resolver is headless-testable on fakes; the default universe is the live
|
|
30
|
+
cache (Melty.cache.key_to_draw_state), with draw_states under a CLOSED
|
|
31
|
+
window filtered out (their tiles persist across sessions but are not on
|
|
32
|
+
screen). Persisted-but-collapsed subtrees DO resolve — deciding whether a
|
|
33
|
+
resolved target is actually hittable is the gate check's job, not the
|
|
34
|
+
resolver's.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Key:
|
|
39
|
+
"""Exact full-name segment (collection keys): matches str(ds.name)
|
|
40
|
+
verbatim, no "##" stripping."""
|
|
41
|
+
|
|
42
|
+
__slots__ = ("key",)
|
|
43
|
+
|
|
44
|
+
def __init__(self, key):
|
|
45
|
+
self.key = key
|
|
46
|
+
|
|
47
|
+
def __repr__(self):
|
|
48
|
+
return f"Key({self.key!r})"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NoMatch(LookupError):
|
|
52
|
+
def __init__(self, path, hint=""):
|
|
53
|
+
self.path = path
|
|
54
|
+
super().__init__(f"no draw_state matches {format_path(path)}"
|
|
55
|
+
+ (f" ({hint})" if hint else ""))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Ambiguous(LookupError):
|
|
59
|
+
def __init__(self, path, candidates):
|
|
60
|
+
self.path = path
|
|
61
|
+
self.candidates = candidates
|
|
62
|
+
options = ", ".join(format_path(c) for c in candidates[:8])
|
|
63
|
+
super().__init__(f"{format_path(path)} matches {len(candidates)} targets — "
|
|
64
|
+
f"use one of: {options}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def format_path(path):
|
|
68
|
+
if isinstance(path, str):
|
|
69
|
+
return repr(path)
|
|
70
|
+
parts = []
|
|
71
|
+
for segment in path:
|
|
72
|
+
if isinstance(segment, int):
|
|
73
|
+
parts.append(f"[{segment}]")
|
|
74
|
+
elif isinstance(segment, Key):
|
|
75
|
+
parts.append(f"{{{segment.key}}}")
|
|
76
|
+
elif isinstance(segment, type):
|
|
77
|
+
parts.append(segment.__name__)
|
|
78
|
+
elif callable(segment):
|
|
79
|
+
parts.append(getattr(segment, "__name__", "<predicate>"))
|
|
80
|
+
else:
|
|
81
|
+
parts.append(str(segment))
|
|
82
|
+
return "/".join(parts)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse(path):
|
|
86
|
+
"""String sugar -> segment tuple. "Loras/[1]/alpha" -> ("Loras", 1,
|
|
87
|
+
"alpha"). Tuples/lists pass through. Key/type/callable segments exist
|
|
88
|
+
only in tuple form."""
|
|
89
|
+
if not isinstance(path, str):
|
|
90
|
+
return tuple(path)
|
|
91
|
+
segments = []
|
|
92
|
+
for raw in path.split("/"):
|
|
93
|
+
raw = raw.strip()
|
|
94
|
+
if not raw:
|
|
95
|
+
continue
|
|
96
|
+
if raw.startswith("[") and raw.endswith("]"):
|
|
97
|
+
segments.append(int(raw[1:-1]))
|
|
98
|
+
elif raw.startswith("{") and raw.endswith("}"):
|
|
99
|
+
segments.append(Key(raw[1:-1]))
|
|
100
|
+
else:
|
|
101
|
+
segments.append(raw)
|
|
102
|
+
if not segments:
|
|
103
|
+
raise ValueError("empty path")
|
|
104
|
+
return tuple(segments)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def display_name(ds):
|
|
108
|
+
name = getattr(ds, "name", None)
|
|
109
|
+
return str(name).split("##")[0] if name is not None else ""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def full_name(ds):
|
|
113
|
+
name = getattr(ds, "name", None)
|
|
114
|
+
return str(name) if name is not None else ""
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def ancestor_chain(ds, cap=64):
|
|
118
|
+
"""ds's ancestors, nearest first (ds itself excluded). Follows `_parent`;
|
|
119
|
+
the root's _parent is ITSELF, which terminates the walk."""
|
|
120
|
+
chain = []
|
|
121
|
+
node = getattr(ds, "_parent", None)
|
|
122
|
+
while node is not None and node is not ds and len(chain) < cap:
|
|
123
|
+
chain.append(node)
|
|
124
|
+
parent = getattr(node, "_parent", None)
|
|
125
|
+
if parent is node:
|
|
126
|
+
break
|
|
127
|
+
node, ds = parent, node
|
|
128
|
+
return chain
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _under_closed_window(ds):
|
|
132
|
+
node, steps = ds, 0
|
|
133
|
+
while node is not None and steps < 64:
|
|
134
|
+
if getattr(node, "closed", False):
|
|
135
|
+
return True
|
|
136
|
+
window = getattr(node, "parent_window", None)
|
|
137
|
+
if window is None or window is node:
|
|
138
|
+
return False
|
|
139
|
+
node, steps = window, steps + 1
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# Per-frame memo of the live universe and its name index:
|
|
144
|
+
# (frame_count, cache size, nodes, index). The orchestrator's precondition
|
|
145
|
+
# refresh resolves a few hundred paths per pass (minimal_path tries every
|
|
146
|
+
# suffix of every candidate), and each used to build the universe (an
|
|
147
|
+
# _under_closed_window walk per draw_state) and scan it once per segment
|
|
148
|
+
# until it - ~5.6 M segment_matches, 480 ms every 20 frames on a session
|
|
149
|
+
# with 5.5k draw_states (profiled 09-01). Now: one build per frame, and a
|
|
150
|
+
# name segment is a dict lookup.
|
|
151
|
+
_UNIVERSE_MEMO = None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _Index:
|
|
155
|
+
"""Name lookups over one universe list: display name (the "##"-stripped
|
|
156
|
+
str(ds.name)) and full name → the nodes carrying it, in universe order.
|
|
157
|
+
Type / predicate segments still scan the nodes."""
|
|
158
|
+
__slots__ = ("nodes", "by_display", "by_full")
|
|
159
|
+
|
|
160
|
+
def __init__(self, nodes):
|
|
161
|
+
self.nodes = nodes
|
|
162
|
+
by_display, by_full = {}, {}
|
|
163
|
+
for ds in nodes:
|
|
164
|
+
name = getattr(ds, "name", None)
|
|
165
|
+
if name is None:
|
|
166
|
+
continue
|
|
167
|
+
full = str(name)
|
|
168
|
+
by_full.setdefault(full, []).append(ds)
|
|
169
|
+
by_display.setdefault(full.split("##")[0], []).append(ds)
|
|
170
|
+
self.by_display, self.by_full = by_display, by_full
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def default_universe():
|
|
174
|
+
global _UNIVERSE_MEMO
|
|
175
|
+
from meltygui.core.melty import Melty
|
|
176
|
+
cache = getattr(Melty, "cache", None)
|
|
177
|
+
if cache is None:
|
|
178
|
+
return []
|
|
179
|
+
key = (Melty.frame_count, len(cache.key_to_draw_state))
|
|
180
|
+
memo = _UNIVERSE_MEMO
|
|
181
|
+
if memo is not None and memo[0] == key:
|
|
182
|
+
return memo[2]
|
|
183
|
+
nodes = [ds for ds in cache.key_to_draw_state.values()
|
|
184
|
+
if ds is not None and not _under_closed_window(ds)]
|
|
185
|
+
_UNIVERSE_MEMO = (key, None, nodes, None)
|
|
186
|
+
return nodes
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _index_for(universe):
|
|
190
|
+
"""The _Index over `universe` — memoized beside the per-frame default
|
|
191
|
+
universe (identity match), built per call for a caller-supplied list."""
|
|
192
|
+
global _UNIVERSE_MEMO
|
|
193
|
+
memo = _UNIVERSE_MEMO
|
|
194
|
+
if memo is not None and memo[2] is universe:
|
|
195
|
+
if memo[3] is None:
|
|
196
|
+
_UNIVERSE_MEMO = memo = (memo[0], None, universe, _Index(universe))
|
|
197
|
+
return memo[3]
|
|
198
|
+
return _Index(universe)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def segment_matches(segment, ds):
|
|
202
|
+
if isinstance(segment, str):
|
|
203
|
+
return display_name(ds) == segment
|
|
204
|
+
if isinstance(segment, (bool, int)): # ordinals are handled by the caller
|
|
205
|
+
return False
|
|
206
|
+
if isinstance(segment, Key):
|
|
207
|
+
return full_name(ds) == segment.key
|
|
208
|
+
if isinstance(segment, type):
|
|
209
|
+
value = getattr(ds, "_raw_input_value", None)
|
|
210
|
+
return value is not None and isinstance(value, segment)
|
|
211
|
+
if callable(segment):
|
|
212
|
+
try:
|
|
213
|
+
return bool(segment(ds))
|
|
214
|
+
except Exception:
|
|
215
|
+
return False
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _visual_order(matches):
|
|
220
|
+
return sorted(matches, key=lambda ds: (getattr(ds, "abs_top", 0) or 0,
|
|
221
|
+
getattr(ds, "abs_left", 0) or 0))
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _matches_in_scope(segment, scope, universe, index=None):
|
|
225
|
+
"""Every universe node matching `segment` with `scope` among its
|
|
226
|
+
ancestors (scope None = anywhere). One hop of the path = descendant
|
|
227
|
+
matching at any depth, which is what makes nesting the caller's
|
|
228
|
+
non-problem. With an `index`, a name / Key segment reads its
|
|
229
|
+
candidates off the name tables instead of scanning the universe."""
|
|
230
|
+
found = []
|
|
231
|
+
if index is not None and isinstance(segment, str):
|
|
232
|
+
candidates = index.by_display.get(segment, ())
|
|
233
|
+
elif index is not None and isinstance(segment, Key):
|
|
234
|
+
candidates = index.by_full.get(segment.key, ())
|
|
235
|
+
else:
|
|
236
|
+
candidates = universe
|
|
237
|
+
for ds in candidates:
|
|
238
|
+
if not segment_matches(segment, ds):
|
|
239
|
+
continue
|
|
240
|
+
# STRICT descendants only: a scope never matches its own next
|
|
241
|
+
# segment. A path is a subsequence of the ANCESTOR chain (module
|
|
242
|
+
# docstring), so "name/name" names a child called name below a
|
|
243
|
+
# parent called name - draw_str's row "name" wrapping the editor
|
|
244
|
+
# "name##innder" - and must not also match the parent alone (it
|
|
245
|
+
# did, and the text field's chain resolved byously, 09-01).
|
|
246
|
+
if scope is not None and not any(a is scope for a in ancestor_chain(ds)):
|
|
247
|
+
continue
|
|
248
|
+
found.append(ds)
|
|
249
|
+
return found
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _resolve_set(segments, scopes, universe, index=None):
|
|
253
|
+
"""Segment-at-a-time descent (equivalent to subsequence matching for
|
|
254
|
+
named segments). An int segment indexes the VISUALLY-ORDERED result of
|
|
255
|
+
the remaining path within the current scopes and returns that single
|
|
256
|
+
node — "the nth ⟨rest⟩ here"."""
|
|
257
|
+
if not segments:
|
|
258
|
+
return scopes
|
|
259
|
+
head, rest = segments[0], segments[1:]
|
|
260
|
+
if isinstance(head, int) and not isinstance(head, bool):
|
|
261
|
+
results = _visual_order(_resolve_set(rest, scopes, universe, index))
|
|
262
|
+
return [results[head]] if 0 <= head < len(results) else []
|
|
263
|
+
next_scopes, seen = [], set()
|
|
264
|
+
for scope in scopes:
|
|
265
|
+
for ds in _matches_in_scope(head, scope, universe, index):
|
|
266
|
+
if id(ds) not in seen:
|
|
267
|
+
seen.add(id(ds))
|
|
268
|
+
next_scopes.append(ds)
|
|
269
|
+
return _resolve_set(rest, next_scopes, universe, index)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def find_all(path, within=None, universe=None):
|
|
273
|
+
segments = parse(path)
|
|
274
|
+
if universe is None:
|
|
275
|
+
universe = default_universe()
|
|
276
|
+
elif not isinstance(universe, list):
|
|
277
|
+
universe = list(universe)
|
|
278
|
+
return _resolve_set(segments, [within], universe, _index_for(universe))
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def resolve(path, within=None, universe=None):
|
|
282
|
+
"""The path's unique live target, or a teaching error: NoMatch names the
|
|
283
|
+
path, Ambiguous lists each candidate as its own minimal_path so the
|
|
284
|
+
error message IS the disambiguation."""
|
|
285
|
+
matches = find_all(path, within=within, universe=universe)
|
|
286
|
+
if len(matches) == 1:
|
|
287
|
+
return matches[0]
|
|
288
|
+
if not matches:
|
|
289
|
+
raise NoMatch(parse(path))
|
|
290
|
+
exact = _exact_chain_matches(parse(path), matches)
|
|
291
|
+
if len(exact) == 1:
|
|
292
|
+
return exact[0]
|
|
293
|
+
if universe is None:
|
|
294
|
+
universe = default_universe()
|
|
295
|
+
raise Ambiguous(parse(path),
|
|
296
|
+
[minimal_path(ds, universe=universe) for ds in matches])
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _exact_chain_matches(segments, matches):
|
|
300
|
+
"""The tie-break behind an ambiguous path: candidates whose name chain
|
|
301
|
+
ends with the path CONTIGUOUSLY (no skipped levels). A recorded chain
|
|
302
|
+
is a node's MAXIMAL capture, so a live node whose chain is exactly
|
|
303
|
+
that is the node it was captured from — while a same-named descendant
|
|
304
|
+
(a live-view window `scores` holding a voxel view `scores`: the
|
|
305
|
+
window's own chain also matches the child as a subsequence) is only a
|
|
306
|
+
loose match. Name segments only; a path with an ordinal / Key / type
|
|
307
|
+
segment has no chain to compare and gets no tie-break."""
|
|
308
|
+
if not segments or not all(isinstance(seg, str) for seg in segments):
|
|
309
|
+
return []
|
|
310
|
+
tail = tuple(segments)
|
|
311
|
+
return [ds for ds in matches if name_chain(ds)[-len(tail):] == tail]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def name_chain(ds, cap=64):
|
|
315
|
+
"""Root-first display names of ds's ancestry + itself, unnamed levels
|
|
316
|
+
skipped. This is what cues CAPTURE (maximal); minimal_path trims LATE."""
|
|
317
|
+
names = [display_name(node) for node in reversed(ancestor_chain(ds, cap))]
|
|
318
|
+
names.append(display_name(ds))
|
|
319
|
+
return tuple(name for name in names if name)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def minimal_path(ds, universe=None):
|
|
323
|
+
"""Shortest name-suffix of ds's chain that resolves uniquely in
|
|
324
|
+
`universe` — computed against the tree it must disambiguate within,
|
|
325
|
+
never at capture time. Falls back to the full chain when even that is
|
|
326
|
+
ambiguous (twins: caller needs an ordinal segment)."""
|
|
327
|
+
if universe is None:
|
|
328
|
+
universe = default_universe()
|
|
329
|
+
elif not isinstance(universe, list):
|
|
330
|
+
universe = list(universe)
|
|
331
|
+
chain = name_chain(ds)
|
|
332
|
+
if not chain:
|
|
333
|
+
return chain
|
|
334
|
+
index = _index_for(universe) # same index for every suffix tried
|
|
335
|
+
for length in range(1, len(chain) + 1):
|
|
336
|
+
suffix = chain[-length:]
|
|
337
|
+
matches = _resolve_set(suffix, [None], universe, index)
|
|
338
|
+
if len(matches) == 1:
|
|
339
|
+
return suffix
|
|
340
|
+
return chain
|