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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,1419 @@
1
+ import inspect
2
+ import os
3
+ from collections import defaultdict
4
+ from enum import Enum
5
+ from traceback import _parse_value_tb
6
+
7
+ import meltygui.core.windowing.window_api as glfw
8
+ import meltygui_imgui as imgui
9
+ from meltygui_imgui import ImGuiError
10
+
11
+ from meltygui.state.model_enums import RelaxedEnum
12
+ from meltygui.core.windowing.glfw_utils import _needs_render
13
+ from meltygui.core.windowing.glfw_utils import print_stack_trace
14
+ from meltygui.core.rendering.core_decoration import Core
15
+ from meltygui.core.runtime.singleton import singleton
16
+
17
+
18
+ class GroupType(Enum):
19
+ WINDOW = 0
20
+ CHILD = 1
21
+ FRAME = 2
22
+ STYLE = 3
23
+ COLOR = 4
24
+
25
+
26
+ @singleton
27
+ class LSDView:
28
+ vis = None
29
+
30
+ def __init__(self):
31
+ self.group_stack = []
32
+ self.style_stack = []
33
+ self.color_stack = []
34
+ self.style_manager = None
35
+ self.obj_types = set()
36
+ self.vis = None
37
+
38
+
39
+
40
+ def is_key_release(self, key=glfw.KEY_ESCAPE):
41
+ return (glfw.get_key(Core.melty.vis.window, key) == glfw.RELEASE and
42
+ key in Core.melty.vis.last_frame_keys)
43
+
44
+ def set_style_manager(self, style_manager):
45
+ """
46
+ Set the style manager for this view.
47
+ :param style_manager: The style manager to set.
48
+ """
49
+ self.style_manager = style_manager
50
+
51
+ def set_vis(self, vis):
52
+ """
53
+ Set the visualizer for this view.
54
+ :param vis: The visualizer to set.
55
+ """
56
+ self.vis = vis
57
+
58
+ def clear_unstack(self):
59
+ """
60
+ Clear the current style and group stacks.
61
+ This is used to reset the view state.
62
+ """
63
+ self.style_stack.clear()
64
+ self.color_stack.clear()
65
+ self.group_stack.clear()
66
+
67
+ def unstack_group(self):
68
+
69
+ try:
70
+ for group_type in reversed(self.style_stack):
71
+ if group_type == GroupType.WINDOW:
72
+ imgui.end()
73
+ elif group_type == GroupType.CHILD:
74
+ imgui.end_child()
75
+ elif group_type == GroupType.FRAME:
76
+ imgui.end_frame()
77
+ elif group_type == GroupType.STYLE:
78
+ imgui.pop_style_var(1)
79
+ elif group_type == GroupType.COLOR:
80
+ imgui.pop_style_color(1)
81
+ except Exception as e:
82
+ print(f"Error unstacking styles: {e}")
83
+ print(self.style_stack)
84
+ print_colored_traceback(*sys.exc_info(), limit=50)
85
+
86
+ self.style_stack.clear()
87
+ raise
88
+
89
+ try:
90
+ for group_type in reversed(self.color_stack):
91
+ if group_type == GroupType.WINDOW:
92
+ imgui.end()
93
+ elif group_type == GroupType.CHILD:
94
+ imgui.end_child()
95
+ elif group_type == GroupType.FRAME:
96
+ imgui.end_frame()
97
+ elif group_type == GroupType.STYLE:
98
+ imgui.pop_style_var(1)
99
+ elif group_type == GroupType.COLOR:
100
+ imgui.pop_style_color(1)
101
+ except Exception as e:
102
+ print(f"Error unstacking colors: {e}")
103
+
104
+ print(self.color_stack)
105
+ print_colored_traceback(*sys.exc_info(), limit=50)
106
+
107
+ self.color_stack.clear()
108
+ raise
109
+
110
+ try:
111
+ for group_type in reversed(self.group_stack):
112
+ if group_type == GroupType.WINDOW:
113
+ imgui.end()
114
+ elif group_type == GroupType.CHILD:
115
+ imgui.end_child()
116
+ elif group_type == GroupType.FRAME:
117
+ imgui.end_frame()
118
+ elif group_type == GroupType.STYLE:
119
+ imgui.pop_style_var(1)
120
+ elif group_type == GroupType.COLOR:
121
+ imgui.pop_style_color(1)
122
+ except Exception as e:
123
+ print(f"Error unstacking group: {e}")
124
+ # Print out the stack
125
+ print_colored_traceback(*sys.exc_info(), limit=50)
126
+
127
+ print(self.group_stack)
128
+
129
+ self.group_stack.clear()
130
+ # Raise
131
+ raise
132
+
133
+ self.style_stack.clear()
134
+ self.group_stack.clear()
135
+
136
+
137
+ def list_width(str_list):
138
+ max_width = 0
139
+ for string in str_list:
140
+ width = imgui.calc_text_size(string).x
141
+ if width > max_width:
142
+ max_width = width
143
+ return max_width
144
+
145
+ def background(color, width=0, height=0):
146
+ """
147
+ Draws a background rectangle with the specified color.
148
+ :param color: The color to fill the rectangle with.
149
+ :param width: The width of the rectangle. If 0, uses the available width.
150
+ :param height: The height of the rectangle. If 0, uses the available height.
151
+ """
152
+ # Store position to restore later
153
+ cursor_pos = imgui.get_cursor_pos()
154
+
155
+ if width <= 0:
156
+ width = imgui.get_content_region_available().x
157
+ if height <= 0:
158
+ height = imgui.get_content_region_available().y
159
+
160
+ push_style_color(imgui.COLOR_WINDOW_BACKGROUND, *color)
161
+ begin_child("background", width, height, border=False, flags=imgui.WINDOW_NO_SCROLLBAR)
162
+ end_child()
163
+ pop_style_color(1)
164
+
165
+ # Restore cursor position
166
+ imgui.set_cursor_pos(cursor_pos)
167
+
168
+
169
+ def radio_buttons_enum(vis, name, selected_enum: RelaxedEnum, label_width=0, grey_out=False):
170
+ imgui.set_next_item_width(imgui.get_content_region_available().x)
171
+ selected_idx = 0
172
+ visible_name = name.split("##")[0]
173
+ changed = False
174
+
175
+ if grey_out:
176
+ push_style_var(imgui.STYLE_ALPHA, 0.5)
177
+
178
+ left_edge = imgui.get_cursor_pos_x()
179
+ margin = imgui.get_style().item_spacing.x * 2
180
+ if len(visible_name) > 0:
181
+ imgui.text(visible_name)
182
+ imgui.same_line()
183
+ if label_width > 0:
184
+ imgui.set_cursor_pos_x(left_edge + label_width + margin)
185
+
186
+ push_style_var(imgui.STYLE_ITEM_SPACING, (2, 4))
187
+ for i, option in enumerate(selected_enum.__class__):
188
+ pretty_name = option.name.replace("_", " ").capitalize()
189
+ if vis.square_radio_button(f"{pretty_name}##{name.split('##')[1]}", selected_enum.value == i):
190
+ selected_idx = i
191
+ changed = True
192
+ imgui.same_line()
193
+ imgui.new_line()
194
+ enum_class = selected_enum.__class__
195
+ selected_enum = enum_class(selected_idx)
196
+ pop_style_var(1)
197
+
198
+ if grey_out:
199
+ pop_style_var(1)
200
+
201
+ return changed, selected_enum
202
+
203
+
204
+ def radio_buttons(vis, name, options, selected_idx):
205
+ imgui.set_next_item_width(imgui.get_content_region_available().x)
206
+ changed = False
207
+
208
+ visible_name = name.split("##")[0]
209
+ if len(visible_name) > 0:
210
+ imgui.text(visible_name)
211
+ push_style_var(imgui.STYLE_ITEM_SPACING, (2, 5))
212
+ for i, option in enumerate(options):
213
+ if vis.square_radio_button(f"{option}##{name}", selected_idx == i):
214
+ selected_idx = i
215
+ changed = True
216
+ imgui.same_line()
217
+ imgui.new_line()
218
+
219
+ pop_style_var(1)
220
+ return changed, selected_idx
221
+
222
+
223
+ def radio_buttons_str(vis, name, options, selected_str):
224
+ imgui.set_next_item_width(imgui.get_content_region_available().x)
225
+ changed = False
226
+
227
+ visible_name = name.split("##")[0]
228
+ if len(visible_name) > 0:
229
+ imgui.text(visible_name)
230
+ push_style_var(imgui.STYLE_ITEM_SPACING, (2, 5))
231
+ for i, option in enumerate(options):
232
+ if vis.square_radio_button(f"{option}##{name}{i}", selected_str == option):
233
+ selected_str = option
234
+ changed = True
235
+ print(f"Selected: {selected_str}")
236
+ imgui.same_line()
237
+ imgui.new_line()
238
+ pop_style_var(1)
239
+ return changed, selected_str
240
+
241
+
242
+ def button(text, width=0, height=0):
243
+ return imgui.button(text, width=width, height=height)
244
+
245
+
246
+ def text_wrapped(text, wrap_width=None):
247
+ """
248
+ Alternative using internal text functions if available.
249
+ """
250
+
251
+ # Get the current window
252
+ start_pos = imgui.get_cursor_screen_pos()
253
+
254
+ # Render the text
255
+ imgui.text_wrapped(text)
256
+
257
+ # Try to access the last rendered position
258
+ # Some Python bindings expose these methods:
259
+ try:
260
+ # Get the draw list that was just used
261
+ draw_list = imgui.get_window_draw_list()
262
+
263
+ # The last vertex position might tell us where the text ended
264
+ # This is very implementation-specific
265
+ vtx_buffer = draw_list.vtx_buffer
266
+ if vtx_buffer:
267
+ # Last vertex might be the bottom-right of the last character
268
+ last_vtx = vtx_buffer[-1]
269
+ return (last_vtx.pos.x, last_vtx.pos.y)
270
+ except:
271
+ pass
272
+
273
+ # Fallback to item rect
274
+ rect_max = imgui.get_item_rect_max()
275
+ return (rect_max[0], rect_max[1] - imgui.get_text_line_height())
276
+
277
+
278
+ def delete_button(text, width=0, height=0, white_text=True):
279
+ push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 2)
280
+
281
+ if LSDView().style_manager is not None:
282
+ if white_text:
283
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.5, 0.0, 0.0)
284
+ push_style_color(imgui.COLOR_BUTTON, r, g, b)
285
+
286
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.55, 0.0, 0.0, saturation_scale=1.0)
287
+ push_style_color(imgui.COLOR_BORDER, r, g, b)
288
+
289
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.75, 0.0, 0.0, saturation_scale=1.0)
290
+ push_style_color(imgui.COLOR_BUTTON_HOVERED, r, g, b)
291
+
292
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.9, 0.0, 0.0, saturation_scale=1.0)
293
+ push_style_color(imgui.COLOR_BUTTON_ACTIVE, r, g, b)
294
+
295
+ else:
296
+ r, g, b, a = LSDView().style_manager.make_color_rgb(1.0, 1.0, 1.0, saturation_scale=1.0)
297
+ push_style_color(imgui.COLOR_BUTTON, r, g, b)
298
+
299
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.55, 0.0, 0.0, saturation_scale=1.0)
300
+ push_style_color(imgui.COLOR_BORDER, r, g, b)
301
+
302
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.75, 0.0, 0.0, saturation_scale=1.0)
303
+ push_style_color(imgui.COLOR_BUTTON_HOVERED, r, g, b)
304
+
305
+ r, g, b, a = LSDView().style_manager.make_color_rgb(0.9, 0.0, 0.0, saturation_scale=1.0)
306
+ push_style_color(imgui.COLOR_BUTTON_ACTIVE, r, g, b)
307
+
308
+ r, g, b, a = LSDView().style_manager.make_color_rgb(1.0, 0.1, 0.1, saturation_scale=1.0)
309
+ push_style_color(imgui.COLOR_TEXT, r, g, b)
310
+
311
+
312
+ else:
313
+ push_style_color(imgui.COLOR_BUTTON, 0.5, 0.2, 0.2)
314
+ push_style_color(imgui.COLOR_BUTTON, 0.55, 0.2, 0.2)
315
+ push_style_color(imgui.COLOR_BUTTON_HOVERED, 0.9, 0.3, 0.3)
316
+ push_style_color(imgui.COLOR_BUTTON_ACTIVE, 1.0, 0.4, 0.4)
317
+
318
+ original_cursor_pos = imgui.get_cursor_pos()
319
+ offset_width = width
320
+
321
+ if text.split("##")[0] == "":
322
+ val = imgui.button(f"\uf1f8{text}", width=width, height=height)
323
+ else:
324
+ val = imgui.button(f"\uf1f8 {text}", width=width, height=height)
325
+
326
+ if white_text:
327
+ pop_style_color(4)
328
+ else:
329
+ pop_style_color(5)
330
+
331
+ pop_style_var(1)
332
+
333
+ return val
334
+
335
+ def button_red(text, width=0, height=0):
336
+ push_style_color(imgui.COLOR_BUTTON, 0.6, 0.2, 0.2)
337
+ push_style_color(imgui.COLOR_BUTTON_HOVERED, 0.6, 0.3, 0.4)
338
+ push_style_color(imgui.COLOR_BUTTON_ACTIVE, 1.0, 0.4, 0.4)
339
+ push_style_color(imgui.COLOR_TEXT, 1.0, 1.0, 1.0)
340
+ val = imgui.button(text, width=width, height=height)
341
+ pop_style_color(4)
342
+ return val
343
+
344
+ # noinspection PyArgumentList
345
+ def tree(text, open=True, width=0, height=0):
346
+ ## Returns 'true' if the node is drawn
347
+ if open:
348
+ flags = (imgui.TREE_NODE_DEFAULT_OPEN |
349
+ imgui.TREE_NODE_COLLAPSING_HEADER | imgui.TREE_NODE_ALLOW_ITEM_OVERLAP)
350
+ else:
351
+ flags = imgui.TREE_NODE_COLLAPSING_HEADER | imgui.TREE_NODE_ALLOW_ITEM_OVERLAP
352
+
353
+ imgui.set_next_item_open(open)
354
+ if width > 0:
355
+ imgui.set_next_item_width(width)
356
+
357
+ imgui.push_style_color(imgui.COLOR_HEADER_HOVERED, 0,0,0,0)
358
+ opened = imgui.tree_node(text, flags=flags)
359
+ imgui.pop_style_color(1)
360
+
361
+ return opened
362
+
363
+
364
+ def does_need_render():
365
+ return _needs_render.is_set()
366
+
367
+
368
+ def new_frame():
369
+ if Core.melty.imgui_crashed:
370
+ return
371
+
372
+ LSDView().group_stack.append(GroupType.FRAME)
373
+ return imgui.new_frame()
374
+
375
+
376
+ def end_frame():
377
+ if Core.melty.imgui_crashed:
378
+ return
379
+
380
+ # Merge the foreground/overlay channels before ImGui finalizes the frame because
381
+ # ImGui's render path requires merged channels, and this is the last frame
382
+ # after all user draws.
383
+ Core.melty.finalize_overlay_channels()
384
+
385
+ if LSDView().group_stack[-1] == GroupType.FRAME:
386
+ LSDView().group_stack.pop()
387
+ return imgui.end_frame()
388
+ else:
389
+ print_stack_trace()
390
+ print("Error: end_frame() called without matching new_frame()")
391
+ return imgui.end_frame()
392
+
393
+
394
+ def begin_child(signatures, *args, **kwargs):
395
+ if Core.melty.imgui_crashed:
396
+ return False
397
+
398
+ LSDView().group_stack.append(GroupType.CHILD)
399
+ return imgui.begin_child(signatures, *args, **kwargs)
400
+
401
+
402
+ def end_child():
403
+ if Core.melty.imgui_crashed:
404
+ return
405
+
406
+ if LSDView().group_stack[-1] == GroupType.CHILD:
407
+ LSDView().group_stack.pop()
408
+ return imgui.end_child()
409
+ else:
410
+ print_stack_trace()
411
+ print("Error: end_child() called without matching begin_child()")
412
+ return imgui.end_child()
413
+
414
+
415
+ def begin(str_label, closable=False, flags=0):
416
+ if Core.melty.imgui_crashed:
417
+ return False, False
418
+
419
+ LSDView().group_stack.append(GroupType.WINDOW)
420
+ return imgui.begin(str_label, closable, flags)
421
+
422
+
423
+ def end():
424
+ if Core.melty.imgui_crashed:
425
+ return
426
+
427
+ if LSDView().group_stack[-1] == GroupType.WINDOW:
428
+ LSDView().group_stack.pop()
429
+ return imgui.end()
430
+ else:
431
+ print_stack_trace()
432
+ print("Error: end() called without matching begin()")
433
+ return imgui.end()
434
+
435
+
436
+ def push_style_color(ImGuiCol_variable, float_r, float_g, float_b, float_a=1.):
437
+ if Core.melty.imgui_crashed:
438
+ return
439
+ LSDView().color_stack.append(GroupType.COLOR)
440
+ return imgui.push_style_color(ImGuiCol_variable, float_r, float_g, float_b, float_a)
441
+
442
+
443
+ def pop_style_color(size=1):
444
+ if Core.melty.imgui_crashed:
445
+ return
446
+
447
+ for _ in range(size):
448
+ if LSDView().color_stack[-1] == GroupType.COLOR:
449
+ LSDView().color_stack.pop()
450
+ imgui.pop_style_color(1)
451
+ else:
452
+ print("Error: pop_style_color() called without matching push_style_color()")
453
+ # Print stack trace to help debug
454
+ print_stack_trace()
455
+ imgui.pop_style_color(1)
456
+
457
+ def push_style_var(ImGuiStyleVar_variable, value):
458
+ if Core.melty.imgui_crashed:
459
+ return
460
+
461
+ LSDView().style_stack.append(GroupType.STYLE)
462
+ return imgui.push_style_var(ImGuiStyleVar_variable, value)
463
+
464
+ def pop_style_var(size=1):
465
+ if Core.melty.imgui_crashed:
466
+ return
467
+
468
+ for _ in range(size):
469
+ if LSDView().style_stack[-1] == GroupType.STYLE:
470
+ LSDView().style_stack.pop()
471
+ imgui.pop_style_var(1)
472
+ else:
473
+ print_stack_trace()
474
+ print(f"Error: {LSDView().style_stack[-1]} pop_style_var() called without matching push_style_var()")
475
+ imgui.pop_style_var(1)
476
+
477
+
478
+ import re
479
+
480
+ # ANSI color codes inspired by IntelliJ IDEA's default color scheme
481
+ COLORS = {
482
+ # Structural elements
483
+ 'HEADER': '\033[95m',
484
+ 'RESET': '\033[0m',
485
+ 'BOLD': '\033[1m',
486
+ 'UNDERLINE': '\033[4m',
487
+
488
+ # IntelliJ-like syntax colors
489
+ 'KEYWORD': '\033[38;5;204m', # Pink/purple for keywords like def, class, import
490
+ 'METHOD': '\033[38;5;75m', # Blue for method names
491
+ 'STRING': '\033[38;5;113m', # Green for strings
492
+ 'NUMBER': '\033[38;5;141m', # Purple for numbers
493
+ 'COMMENT': '\033[38;5;102m', # Gray for comments
494
+ 'CONSTANT': '\033[38;5;174m', # Light red for constants
495
+
496
+ # Traceback specific colors
497
+ 'FILENAME': '\033[38;5;186m', # Light yellow for filenames
498
+ 'LINENO': '\033[38;5;37m', # Teal for line numbers
499
+ 'ERROR': '\033[38;5;196m', # Bright red for errors
500
+ 'WARNING': '\033[38;5;214m', # Orange for warnings
501
+ 'EXCEPTION': '\033[38;5;203m' # Red for exception names
502
+ }
503
+
504
+ COLORS = {
505
+ 'HEADER': '\033[95m',
506
+ 'BLUE': '\033[94m',
507
+ 'CYAN': '\033[96m',
508
+ 'GREEN': '\033[92m',
509
+ 'YELLOW': '\033[93m',
510
+ 'PURPLE': '\033[95m',
511
+ 'RED': '\033[91m',
512
+ 'BOLD': '\033[1m',
513
+ 'UNDERLINE': '\033[4m',
514
+ 'RESET': '\033[0m'
515
+ }
516
+
517
+ import sys
518
+ import traceback
519
+ # Create a console for rich output
520
+
521
+ def stack_trace():
522
+ print_colored_traceback(*sys.exc_info(), limit=50)
523
+
524
+
525
+
526
+ def print_colored_traceback(exc_type=None, exc_value=None, exc_traceback=None, limit=None, file=None, color=None):
527
+ """
528
+ Print the traceback with colors and clickable links that open in IntelliJ IDEA.
529
+
530
+ Args:
531
+ exc_type: Exception type
532
+ exc_value: Exception value
533
+ exc_traceback: Exception traceback
534
+ limit: Maximum number of stack frames to show
535
+ file: File to write the traceback to
536
+ """
537
+ if exc_type == None:
538
+ exc_type, exc_value, exc_traceback = sys.exc_info()
539
+
540
+ if file is None:
541
+ file = sys.stdout
542
+
543
+ if color is None:
544
+ color = "CYAN"
545
+
546
+ e = exc_value
547
+ if not Core.melty.imgui_crashed:
548
+ if isinstance(e, ImGuiError):
549
+ if Core.melty.vis is not None:
550
+ Core.melty.imgui_crashed = True
551
+ if Core.melty.vis.imgui_ctx is not None:
552
+
553
+ # if Core.melty.vis._impl is not None:
554
+ # Core.melty.vis._impl.shutdown()
555
+ # Core.melty.vis.impl = None
556
+ RED_BOLD = "\033[1;31m"
557
+ RESET = "\033[0m"
558
+ print("-" * 80)
559
+ info = sys.exc_info()
560
+ print(f"{RED_BOLD}ImGui Crashed! Recreating context from print\n{RESET}: {e}")
561
+ stack_from_e = traceback.extract_tb(exc_traceback)
562
+ print("-" * 80)
563
+
564
+ Core.melty.vis.exception_raised = True
565
+ Core.melty.imgui_crashed = True
566
+ # Stack trace
567
+ print_colored_traceback(*info, limit=50)
568
+
569
+
570
+ def extract_vars(exc_traceback):
571
+ """
572
+ Extract the stack trace from the traceback object.
573
+ """
574
+ attr_names = []
575
+ configs = []
576
+ if exc_traceback is not None:
577
+ while exc_traceback.tb_next is not None:
578
+ frame = exc_traceback.tb_frame
579
+ locals = frame.f_locals
580
+ attr_name = locals.get('attr_name', "")
581
+ if attr_name == "":
582
+ if 'input_value' in locals:
583
+ if hasattr(locals['input_value'], 'name'):
584
+ attr_name = locals['input_value'].name
585
+ else:
586
+ if locals['input_value'] is not None:
587
+ attr_name = locals['input_value'].__class__.__name__
588
+
589
+ else:
590
+ if 'self' in locals:
591
+ attr_name = f"self is {locals['self'].__class__.__name__}"
592
+
593
+ config = locals.get('config', None)
594
+ attr_names.append(attr_name)
595
+ configs.append(config)
596
+ exc_traceback = exc_traceback.tb_next
597
+
598
+ return attr_names, configs
599
+
600
+ exp_vars, configs = extract_vars(exc_traceback)
601
+ if exc_traceback is None:
602
+ stack = []
603
+ else:
604
+ stack = traceback.extract_stack(exc_traceback.tb_frame)
605
+
606
+ value, tb = _parse_value_tb(exc_type, exc_value, exc_traceback)
607
+ te = traceback.TracebackException(type(value), value, tb, limit=limit, compact=True)
608
+ exception_stack = te.stack
609
+
610
+ if value is None:
611
+ print_stack_trace()
612
+
613
+ for idx, frame in enumerate(stack[:-1]):
614
+ line_number = frame.lineno
615
+ filename = frame.filename
616
+ line = frame.line
617
+ function_name = frame.name
618
+ green = COLORS['GREEN']
619
+ blue = COLORS['BLUE']
620
+ yellow = COLORS['YELLOW']
621
+
622
+ print(f"{yellow}File \"{filename}\", line {line_number}{COLORS['RESET']}{COLORS['BOLD']}{blue} {function_name}{COLORS['RESET']}")
623
+ print(f" {yellow}{line}{COLORS['RESET']}")
624
+
625
+ yellow = COLORS['YELLOW']
626
+ red = COLORS['RED']
627
+ print(f"{yellow}-------- Error Caught -------{COLORS['RESET']}")
628
+
629
+
630
+ for idx, frame in enumerate(exception_stack):
631
+ line_number = frame.lineno
632
+ filename = frame.filename
633
+ line = frame.line
634
+ function_name = frame.name
635
+ green = COLORS['GREEN']
636
+ blue = COLORS['BLUE']
637
+ attr_name = exp_vars[idx] if idx < len(exp_vars) else ""
638
+ config = configs[idx] if idx < len(configs) else None
639
+
640
+ print(
641
+ f"{yellow}File \"{filename}\", line {line_number}{COLORS['RESET']}{COLORS['BOLD']}{blue} {function_name}{COLORS['RESET']} {green}{attr_name}{COLORS['RESET']}")
642
+ # if attr_name is not None:
643
+ # print(f" {yellow}attr_name{COLORS['RESET']} {COLORS['BOLD']}{green}{attr_name}{COLORS['RESET']}")
644
+ # if input_value is not None:
645
+ # print(f" {yellow}input_value{COLORS['RESET']} {COLORS['BOLD']}{yellow}{input_value}{COLORS['RESET']}")
646
+ # if datatype is not None:
647
+ # print(f" {yellow}datatype.name{COLORS['RESET']} {COLORS['BOLD']}{yellow}{datatype}{COLORS['RESET']}")
648
+
649
+ if idx == len(exception_stack) - 1:
650
+ print(f" {COLORS['BOLD']}{red}{line}{COLORS['RESET']}")
651
+ else:
652
+ print(f" {yellow}{line}{COLORS['RESET']}")
653
+
654
+ traceback_lines = traceback.format_exception_only(exc_value)
655
+ for line in traceback_lines:
656
+ line = f"{yellow}{line}{COLORS['RESET']}"
657
+ file.write(line)
658
+
659
+ if file is None:
660
+ file = sys.stdout
661
+
662
+ # NOTE: this used to os._exit(1) when Melty.imgui_crashed was latched.
663
+ # The latch persists across studio runs inside the launcher process, so a
664
+ # recovered ImGui hiccup earlier in the session turned any later traceback
665
+ # print (launcher, restart, studio teardown) into a silent hard-kill of
666
+ # the whole launcher. A crash printer should never kill the process.
667
+
668
+ #
669
+ # traceback_lines = traceback.format_exception(exc_type, exc_value, exc_traceback, limit=limit)
670
+ #
671
+ # for line in traceback_lines:
672
+ # line = f"{COLORS['BOLD']}{COLORS[color]}{line}{COLORS['RESET']}"
673
+ # file.write(line)
674
+
675
+ import gc
676
+
677
+
678
+ def memory_flame_chart(scope=None, threshold_kb=1, depth=10000, width=80, color=True, aggregate_by_type=True,
679
+ include_cuda=True, scan_all_objects=True, max_objects=50000000):
680
+ """
681
+ Generate a flame chart visualization of memory usage by variables.
682
+
683
+ Args:
684
+ scope: The scope/namespace to analyze. If None, uses the caller's globals and locals.
685
+ threshold_kb: Minimum size in KB to include in the chart (default: 1KB)
686
+ depth: Maximum depth for nested objects to traverse (default: 3)
687
+ width: Width of the terminal output (default: 80 characters)
688
+ color: Whether to use ANSI colors in output (default: True)
689
+ aggregate_by_type: Whether to aggregate memory usage by class type (default: True)
690
+ include_cuda: Whether to include CUDA memory in the chart (default: True)
691
+ scan_all_objects: Whether to scan all objects in memory (default: True)
692
+ max_objects: Maximum number of objects to scan (default: 500000)
693
+
694
+ Returns:
695
+ None: Prints the flame chart to stdout
696
+ """
697
+ # ANSI color codes
698
+ colors = {
699
+ 'reset': '\033[0m',
700
+ 'red': '\033[91m',
701
+ 'yellow': '\033[93m',
702
+ 'green': '\033[92m',
703
+ 'blue': '\033[94m',
704
+ 'cyan': '\033[96m',
705
+ 'magenta': '\033[95m',
706
+ }
707
+
708
+ if not color:
709
+ # Disable colors if not wanted
710
+ for k in colors:
711
+ colors[k] = ''
712
+
713
+ # Get the namespace to analyze
714
+ if scope is None:
715
+ # Get caller's frame to access its variables
716
+ caller_frame = inspect.currentframe().f_back
717
+ global_vars = caller_frame.f_globals
718
+ local_vars = caller_frame.f_locals
719
+ else:
720
+ global_vars = scope
721
+ local_vars = {}
722
+
723
+ # Track already seen objects to avoid cycles
724
+ seen_ids = set()
725
+
726
+ # Store type-specific information when aggregating
727
+ type_sizes = defaultdict(int)
728
+ type_counts = defaultdict(int)
729
+ type_examples = {}
730
+
731
+ # Flag to detect if PyTorch is available
732
+ has_pytorch = False
733
+ try:
734
+ import torch
735
+ has_pytorch = True
736
+ except ImportError:
737
+ pass
738
+
739
+ # Flag to detect if NumPy is available
740
+ has_numpy = False
741
+ try:
742
+ import numpy as np
743
+ has_numpy = True
744
+ except ImportError:
745
+ pass
746
+
747
+ # Stores the size of each variable and its path when not aggregating
748
+ var_sizes = []
749
+ threshold_bytes = threshold_kb * 1024
750
+
751
+ # For tracking tensor memory
752
+ cpu_tensor_size = 0
753
+ numpy_array_size = 0
754
+
755
+ # Risky module patterns to avoid
756
+ risky_modules_patterns = [
757
+ r'transformers\.models\.auto',
758
+ r'transformers\.utils\.import_utils',
759
+ r'transformers\.deepspeed',
760
+ r'importlib\._bootstrap',
761
+ r'lazy_loader',
762
+ r'google\.protobuf',
763
+ ]
764
+
765
+ def is_risky_object(obj):
766
+ """Check if an object is from a module that might cause import or recursion issues"""
767
+ try:
768
+ if not hasattr(obj, '__class__'):
769
+ return False
770
+
771
+ module_name = obj.__class__.__module__
772
+ # Check if module name matches any risky pattern
773
+ if any(re.search(pattern, module_name) for pattern in risky_modules_patterns):
774
+ return True
775
+
776
+ # Check for specific object types
777
+ if hasattr(obj, '__getattr__') and not isinstance(obj, dict) and not hasattr(obj, 'items'):
778
+ # Objects with custom __getattr__ might trigger imports
779
+ return True
780
+
781
+ # Dynamic attribute objects that might trigger imports
782
+ risky_class_names = ['LazyLoader', 'LazyImport', 'DynamicModule', '_LazyModule']
783
+ if obj.__class__.__name__ in risky_class_names:
784
+ return True
785
+
786
+ return False
787
+ except:
788
+ # If any error occurs while checking, consider it risky
789
+ return True
790
+
791
+ def estimate_tensor_memory(tensor):
792
+ """Estimate memory used by a tensor"""
793
+ try:
794
+ if hasattr(tensor, 'element_size') and hasattr(tensor, 'nelement'):
795
+ return tensor.element_size() * tensor.nelement()
796
+ elif hasattr(tensor, 'itemsize') and hasattr(tensor, 'size'):
797
+ # For numpy arrays
798
+ return tensor.itemsize * tensor.size
799
+ return 0
800
+ except:
801
+ return 0
802
+
803
+ def get_size(obj, name, current_depth=0, path=""):
804
+ """Recursively find the size of objects and their attributes"""
805
+ nonlocal cpu_tensor_size, numpy_array_size
806
+
807
+ if current_depth > depth:
808
+ return 0
809
+
810
+ # Skip already seen objects
811
+ obj_id = id(obj)
812
+ if obj_id in seen_ids:
813
+ return 0
814
+
815
+ seen_ids.add(obj_id)
816
+
817
+ # Skip risky objects
818
+ if is_risky_object(obj):
819
+ # Just estimate the basic size without recursion
820
+ try:
821
+ obj_size = sys.getsizeof(obj)
822
+
823
+ # Record basic type information
824
+ if aggregate_by_type:
825
+ obj_type = obj.__class__.__name__
826
+ type_sizes[obj_type] += obj_size
827
+ type_counts[obj_type] += 1
828
+ if obj_type not in type_examples:
829
+ type_examples[obj_type] = path if path else name
830
+
831
+ return obj_size
832
+ except:
833
+ return 0
834
+
835
+ # Default object size
836
+ obj_size = 0
837
+ tensor_data_size = 0
838
+
839
+ # Special handling for PyTorch tensors
840
+ if has_pytorch and isinstance(obj, torch.Tensor):
841
+ try:
842
+ # Get base object size
843
+ obj_size = sys.getsizeof(obj)
844
+
845
+ if obj.is_cuda:
846
+ # For CUDA tensors, only count the object size
847
+ # Record custom size information for CUDA tensors
848
+ if aggregate_by_type:
849
+ cuda_type = f"torch.Tensor(CUDA)"
850
+ type_sizes[cuda_type] += obj_size
851
+ type_counts[cuda_type] += 1
852
+ if cuda_type not in type_examples:
853
+ type_examples[cuda_type] = path
854
+ else:
855
+ # For CPU tensors, estimate memory
856
+ tensor_data_size = estimate_tensor_memory(obj)
857
+ obj_size += tensor_data_size
858
+
859
+ # Track CPU tensor memory separately
860
+ cpu_tensor_size += tensor_data_size
861
+
862
+ # Record CPU tensor information
863
+ if aggregate_by_type:
864
+ cpu_type = f"torch.Tensor(CPU)"
865
+ type_sizes[cpu_type] += obj_size
866
+ type_counts[cpu_type] += 1
867
+ if cpu_type not in type_examples:
868
+ type_examples[cpu_type] = path
869
+ except:
870
+ obj_size = sys.getsizeof(obj)
871
+
872
+ # Special handling for NumPy arrays
873
+ elif has_numpy and isinstance(obj, np.ndarray):
874
+ try:
875
+ # Get base object size
876
+ obj_size = sys.getsizeof(obj)
877
+
878
+ # Add size of array data
879
+ array_data_size = estimate_tensor_memory(obj)
880
+ obj_size += array_data_size
881
+
882
+ # Track numpy array memory separately
883
+ numpy_array_size += array_data_size
884
+
885
+ # Record numpy array information
886
+ if aggregate_by_type:
887
+ numpy_type = f"numpy.ndarray"
888
+ type_sizes[numpy_type] += obj_size
889
+ type_counts[numpy_type] += 1
890
+ if numpy_type not in type_examples:
891
+ type_examples[numpy_type] = path
892
+ except:
893
+ obj_size = sys.getsizeof(obj)
894
+ else:
895
+ try:
896
+ # Get the object's size for other objects
897
+ obj_size = sys.getsizeof(obj)
898
+ except Exception:
899
+ # Some objects don't support getsizeof
900
+ obj_size = 0
901
+
902
+ current_path = f"{path}.{name}" if path else name
903
+
904
+ # Record size information if aggregating
905
+ if aggregate_by_type:
906
+ if has_pytorch and isinstance(obj, torch.Tensor):
907
+ if obj.is_cuda:
908
+ obj_type = "torch.Tensor(CUDA)"
909
+ else:
910
+ obj_type = "torch.Tensor(CPU)"
911
+ elif has_numpy and isinstance(obj, np.ndarray):
912
+ obj_type = "numpy.ndarray"
913
+ else:
914
+ obj_type = type(obj).__name__
915
+
916
+ type_sizes[obj_type] += obj_size
917
+ type_counts[obj_type] += 1
918
+ if obj_type not in type_examples:
919
+ type_examples[obj_type] = current_path
920
+
921
+ # Store the size info for display if not aggregating
922
+ if not aggregate_by_type and obj_size >= threshold_bytes:
923
+ var_sizes.append((current_path, obj_size, type(obj).__name__))
924
+
925
+ # Skip recursive inspection of tensors and special types
926
+ if (has_pytorch and isinstance(obj, torch.Tensor)) or (has_numpy and isinstance(obj, np.ndarray)):
927
+ return obj_size
928
+
929
+ # For some collection types, add the size of their items
930
+ try:
931
+ if isinstance(obj, (list, tuple, set, frozenset)):
932
+ try:
933
+ for i, item in enumerate(obj):
934
+ if current_depth < depth: # Respect depth limit
935
+ item_path = f"{current_path}[{i}]"
936
+ obj_size += get_size(item, f"[{i}]", current_depth + 1, current_path)
937
+ except:
938
+ pass
939
+
940
+ elif isinstance(obj, dict):
941
+ try:
942
+ # Safe iteration over dictionary items
943
+ safe_items = list(obj.items())
944
+ for k, v in safe_items:
945
+ if current_depth < depth: # Respect depth limit
946
+ # Convert key to string representation
947
+ try:
948
+ k_str = str(k) if len(str(k)) < 20 else f"{str(k)[:17]}..."
949
+ except:
950
+ k_str = "?"
951
+ item_path = f"{current_path}[{k_str}]"
952
+ obj_size += get_size(v, f"[{k_str}]", current_depth + 1, current_path)
953
+ except:
954
+ pass
955
+
956
+ # For custom objects, inspect attributes
957
+ elif hasattr(obj, '__dict__') and not isinstance(obj, type):
958
+ try:
959
+ # For PyTorch modules, handle parameters specially
960
+ if has_pytorch and hasattr(obj, 'parameters') and callable(getattr(obj, 'parameters', None)):
961
+ try:
962
+ for name, param in list(obj.named_parameters()):
963
+ if current_depth < depth:
964
+ param_path = f"{current_path}.{name}"
965
+ obj_size += get_size(param, name, current_depth + 1, current_path)
966
+ except:
967
+ pass
968
+
969
+ # Get standard attributes
970
+ safe_dict = dict(obj.__dict__)
971
+ for attr, value in safe_dict.items():
972
+ if not attr.startswith('__') and current_depth < depth:
973
+ attr_path = f"{current_path}.{attr}"
974
+ obj_size += get_size(value, attr, current_depth + 1, current_path)
975
+ except:
976
+ pass
977
+ except:
978
+ # If any exception occurs during traversal, just use the object's own size
979
+ pass
980
+
981
+ return obj_size
982
+
983
+ # Get total memory of this process as a comparison
984
+ import psutil # lazy: only this profiler needs it
985
+ process = psutil.Process(os.getpid())
986
+ total_process_memory = process.memory_info().rss
987
+
988
+ # Analyze memory usage
989
+ print(f"\n{colors['cyan']}===== Memory Usage Flame Chart ====={colors['reset']}")
990
+ print(f"Process total: {total_process_memory / (1024 * 1024):.2f} MB")
991
+ print(f"Threshold: {threshold_kb} KB\n")
992
+
993
+ # Process variables from both globals and locals
994
+ all_vars = {}
995
+ all_vars.update(global_vars)
996
+ all_vars.update(local_vars)
997
+
998
+ # Start memory analysis from explicit variables
999
+ print(f"{colors['blue']}Analyzing {len(all_vars)} variables in current scope...{colors['reset']}")
1000
+ for name, obj in all_vars.items():
1001
+ try:
1002
+ # Skip modules, functions, and other non-data objects for direct analysis
1003
+ if name.startswith('__') or inspect.ismodule(obj) or inspect.isfunction(obj) or inspect.isbuiltin(obj):
1004
+ continue
1005
+
1006
+ get_size(obj, name)
1007
+ except Exception as e:
1008
+ # Skip objects that can't be inspected
1009
+ continue
1010
+
1011
+ # If scanning all objects, use garbage collector to find objects not directly accessible
1012
+ total_objects_scanned = len(seen_ids)
1013
+ if scan_all_objects:
1014
+ print(f"{colors['blue']}Scanning all objects in memory (this may take a while)...{colors['reset']}")
1015
+
1016
+ # Get all objects from garbage collector
1017
+ gc.collect() # Force collection to free up unreferenced objects
1018
+
1019
+ try:
1020
+ all_objects = gc.get_objects()
1021
+
1022
+ # Skip some problematic types
1023
+ skip_types = set([type, type(None), type(NotImplemented), type(Ellipsis)])
1024
+ if has_pytorch:
1025
+ try:
1026
+ # Skip tensor storage types
1027
+ import torch.storage
1028
+ skip_types.add(type(torch.storage.TypedStorage))
1029
+ skip_types.add(type(torch.storage._TypedStorage))
1030
+ except:
1031
+ pass
1032
+
1033
+ print(f"{colors['blue']}Found {len(all_objects)} total objects. Analyzing...{colors['reset']}")
1034
+
1035
+ # Process a subset of objects to avoid taking too long
1036
+ objects_to_process = min(len(all_objects), max_objects)
1037
+ for i, obj in enumerate(all_objects[:objects_to_process]):
1038
+ if i % 50000 == 0 and i > 0:
1039
+ print(f"{colors['blue']}Processed {i}/{objects_to_process} objects...{colors['reset']}")
1040
+
1041
+ try:
1042
+ # Skip if already seen
1043
+ if id(obj) in seen_ids:
1044
+ continue
1045
+
1046
+ # Skip problematic types
1047
+ if type(obj) in skip_types:
1048
+ continue
1049
+
1050
+ # Skip modules, functions, etc.
1051
+ if inspect.ismodule(obj) or inspect.isfunction(obj) or inspect.isbuiltin(obj):
1052
+ continue
1053
+
1054
+ # Skip risky objects right away
1055
+ if is_risky_object(obj):
1056
+ # Just get a basic size estimate without traversing
1057
+ try:
1058
+ obj_size = sys.getsizeof(obj)
1059
+
1060
+ # Record basic type information
1061
+ if aggregate_by_type:
1062
+ obj_type = obj.__class__.__name__
1063
+ type_sizes[obj_type] += obj_size
1064
+ type_counts[obj_type] += 1
1065
+ if obj_type not in type_examples:
1066
+ type_examples[obj_type] = f"<{obj_type}>"
1067
+
1068
+ seen_ids.add(id(obj))
1069
+ except:
1070
+ pass
1071
+ continue
1072
+
1073
+ # Process this object - use its type name as an identifier
1074
+ obj_type = type(obj).__name__
1075
+ get_size(obj, f"<{obj_type}>")
1076
+
1077
+ except:
1078
+ # Skip problematic objects
1079
+ continue
1080
+
1081
+ total_objects_scanned = len(seen_ids)
1082
+ print(f"{colors['blue']}Scanned {total_objects_scanned} unique objects.{colors['reset']}")
1083
+ except Exception as e:
1084
+ print(f"{colors['red']}Error during object scanning: {str(e)}{colors['reset']}")
1085
+
1086
+ # Collect CUDA memory info if available
1087
+ cuda_mem_total = 0
1088
+ cuda_entries = []
1089
+ if has_pytorch and torch.cuda.is_available() and include_cuda:
1090
+ try:
1091
+ # Get CUDA memory stats
1092
+ for device_idx in range(torch.cuda.device_count()):
1093
+ cuda_mem = torch.cuda.memory_reserved(device_idx)
1094
+ if cuda_mem > 0:
1095
+ cuda_name = f"CUDA:{device_idx} Memory"
1096
+ cuda_entries.append((cuda_name, cuda_mem, "cuda_memory"))
1097
+ cuda_mem_total += cuda_mem
1098
+ except:
1099
+ # In case of errors accessing CUDA memory info
1100
+ pass
1101
+
1102
+ # Prepare the data for display
1103
+ if aggregate_by_type:
1104
+ # Convert type data to the same format as var_sizes
1105
+ var_sizes = [] # Clear and rebuild with type data
1106
+ for type_name, size in type_sizes.items():
1107
+ if size >= threshold_bytes:
1108
+ example = type_examples.get(type_name, "<unknown>")
1109
+ count = type_counts[type_name]
1110
+ display_name = f"{type_name} ({count} instances)"
1111
+ var_sizes.append((display_name, size, example))
1112
+
1113
+ # Get the total accounted memory (excluding CUDA - counted separately)
1114
+ ram_accounted = sum(size for _, size, _ in var_sizes)
1115
+
1116
+ # Add CUDA entries to the display list
1117
+ if include_cuda:
1118
+ var_sizes.extend(cuda_entries)
1119
+
1120
+ # Sort by size (largest first)
1121
+ var_sizes.sort(key=lambda x: x[1], reverse=True)
1122
+
1123
+ # Calculate max name length for formatting
1124
+ max_name_len = min(max((len(name) for name, _, _ in var_sizes), default=20), 50)
1125
+
1126
+ # Print the flame chart
1127
+ if not var_sizes:
1128
+ print(f"{colors['yellow']}No variables found above the threshold of {threshold_kb} KB{colors['reset']}")
1129
+ return
1130
+
1131
+ # Report memory statistics
1132
+ print(f"RAM memory accounted for: {ram_accounted / (1024 * 1024):.2f} MB " +
1133
+ f"({ram_accounted / total_process_memory * 100:.1f}% of process total)")
1134
+
1135
+ if cpu_tensor_size > 0:
1136
+ print(f"CPU tensor data: {cpu_tensor_size / (1024 * 1024):.2f} MB " +
1137
+ f"({cpu_tensor_size / total_process_memory * 100:.1f}% of process total)")
1138
+
1139
+ if numpy_array_size > 0:
1140
+ print(f"NumPy array data: {numpy_array_size / (1024 * 1024):.2f} MB " +
1141
+ f"({numpy_array_size / total_process_memory * 100:.1f}% of process total)")
1142
+
1143
+ if include_cuda and cuda_mem_total > 0:
1144
+ print(f"CUDA memory: {cuda_mem_total / (1024 * 1024):.2f} MB")
1145
+ print(f"Total (RAM + CUDA): {(ram_accounted + cuda_mem_total) / (1024 * 1024):.2f} MB")
1146
+
1147
+ # For bar width calculation based on the largest object
1148
+ max_size = max(size for _, size, _ in var_sizes)
1149
+
1150
+ # Print header
1151
+ if aggregate_by_type:
1152
+ print(
1153
+ f"\n{colors['magenta']}{'Type (instances)':<{max_name_len}} | {'Size':>10} | {'Example':>50} | Usage{colors['reset']}")
1154
+ else:
1155
+ print(
1156
+ f"\n{colors['magenta']}{'Variable':<{max_name_len}} | {'Size':>10} | {'Type':>50} | Usage{colors['reset']}")
1157
+ print("-" * (max_name_len + 33 + width))
1158
+
1159
+ # Print each variable with a bar representing its size
1160
+ for name, size, type_info in var_sizes:
1161
+ # Format name (truncate if too long)
1162
+ if len(name) > max_name_len:
1163
+ name = name[:max_name_len - 3] + "..."
1164
+
1165
+ # Calculate the bar width
1166
+ bar_width = int((size / max_size) * (width - 10))
1167
+
1168
+ # Choose color based on size and type
1169
+ if type_info == "cuda_memory":
1170
+ color_code = colors['blue'] # CUDA memory in blue
1171
+ elif "torch.Tensor(CPU)" in name:
1172
+ color_code = colors['cyan'] # CPU tensors in cyan
1173
+ elif "numpy.ndarray" in name:
1174
+ color_code = colors['magenta'] # NumPy arrays in magenta
1175
+ elif size > 100 * 1024 * 1024: # >100MB
1176
+ color_code = colors['red']
1177
+ elif size > 10 * 1024 * 1024: # >10MB
1178
+ color_code = colors['yellow']
1179
+ else:
1180
+ color_code = colors['green']
1181
+
1182
+ # Format size
1183
+ if size > 1024 * 1024 * 1024: # GB range
1184
+ size_str = f"{size / (1024 * 1024 * 1024):.2f} GB"
1185
+ elif size > 1024 * 1024: # MB range
1186
+ size_str = f"{size / (1024 * 1024):.2f} MB"
1187
+ else:
1188
+ size_str = f"{size / 1024:.2f} KB"
1189
+
1190
+ # Truncate type_info if it's too long
1191
+ if len(str(type_info)) > 50:
1192
+ type_info = str(type_info)[:46] + "..."
1193
+
1194
+ # Print the bar
1195
+ bar = "█" * bar_width
1196
+ print(f"{name:<{max_name_len}} | {size_str:>10} | {type_info:>50} | {color_code}{bar}{colors['reset']}")
1197
+
1198
+ # Display memory that couldn't be accounted for (for RAM only)
1199
+ unaccounted = total_process_memory - ram_accounted
1200
+ if unaccounted > 0:
1201
+ print("\n" + "-" * (max_name_len + 33 + width))
1202
+ print(f"{colors['yellow']}RAM memory not accounted for: {unaccounted / (1024 * 1024):.2f} MB " +
1203
+ f"({unaccounted / total_process_memory * 100:.1f}% of process total){colors['reset']}")
1204
+ print(
1205
+ f"{colors['yellow']}This includes memory used by C extensions, memory fragmentation, and system overhead.{colors['reset']}")
1206
+
1207
+ # If we've scanned all objects and still missing a lot, suggest reasons
1208
+ if scan_all_objects and unaccounted > 0.5 * total_process_memory:
1209
+ print(f"{colors['yellow']}Possible reasons for large unaccounted memory:{colors['reset']}")
1210
+ print(f"{colors['yellow']}1. Memory allocated in C/C++ extensions not visible to Python{colors['reset']}")
1211
+ print(f"{colors['yellow']}2. Memory fragmentation due to many allocations/deallocations{colors['reset']}")
1212
+ print(
1213
+ f"{colors['yellow']}3. Tensors in modules not fully traversed due to safety measures{colors['reset']}")
1214
+
1215
+ # Add note about additional memory profiling
1216
+ print(
1217
+ f"\n{colors['blue']}Note: For a more complete memory profile, consider using specialized tools like:{colors['reset']}")
1218
+ print(f" - memory_profiler: pip install memory_profiler")
1219
+ print(f" - py-spy: pip install py-spy")
1220
+ if has_pytorch:
1221
+ print(f" - pytorch_memlab: for PyTorch memory analysis")
1222
+ print(f" - torch.cuda.memory_summary(): for detailed CUDA memory breakdown")
1223
+
1224
+ print("\n")
1225
+
1226
+
1227
+ def object_combo(label, current_object, objects, width=None):
1228
+ """
1229
+ Custom implementation of imgui.combo with the same signature.
1230
+
1231
+ Args:
1232
+ label (str): The label for the combo box
1233
+ current_item (int): The index of the currently selected item
1234
+ items (list): List of strings containing the items
1235
+ height_in_items (int, optional): Number of items to display in the dropdown. Defaults to -1 (auto).
1236
+
1237
+ Returns:
1238
+ tuple: (changed, new_current_item)
1239
+ """
1240
+ # Remember the initial current_item to detect changes
1241
+ changed = False
1242
+
1243
+ visible_label = label.split("##")[0]
1244
+ if current_object is None and len(objects) > 0:
1245
+ current_model = list(objects.values())[0]
1246
+
1247
+ # Begin the combo widget
1248
+ LSDView().style_manager.save_style()
1249
+
1250
+ # vis.style_manager.set_imgui_tint(*sub_layers[current_item].tint)
1251
+ if current_object is None:
1252
+ if len(objects) == 0:
1253
+ imgui.text(f"No {visible_label} available")
1254
+ return False, None
1255
+
1256
+ # Get first object from dict
1257
+ current_object = list(objects.values())[0]
1258
+ changed = True
1259
+
1260
+ if width is not None:
1261
+ imgui.set_next_item_width(width)
1262
+
1263
+ if current_object is not None:
1264
+ name = current_object.name
1265
+ else:
1266
+ name = "Unset"
1267
+
1268
+ if imgui.begin_combo(f"##{label}", name, imgui.COMBO_HEIGHT_LARGEST):
1269
+ # Loop through each item in the list
1270
+ current_tint = LSDView().style_manager.get_tint()
1271
+ for i, (name, object) in enumerate(objects.items()):
1272
+ # Check if this item is selected
1273
+ is_selected = (object == current_object)
1274
+ if hasattr(object, 'tint') and object.tint is not None:
1275
+ LSDView().style_manager.set_imgui_tint(*object.tint)
1276
+ text_color = LSDView().style_manager.make_color_unpacked(value=0.7, saturation_scale=1.0)
1277
+ push_style_color(imgui.COLOR_TEXT, *text_color)
1278
+ # Create a selectable item for each option
1279
+
1280
+ if imgui.selectable(f"{object.name}##{object.id}", is_selected)[0]:
1281
+ # Update current item if user selects a different one
1282
+ current_object = object
1283
+ changed = True
1284
+
1285
+ if hasattr(object, 'tint') and object.tint is not None:
1286
+ pop_style_color()
1287
+
1288
+ # Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
1289
+ if is_selected:
1290
+ imgui.set_item_default_focus()
1291
+
1292
+ # Restore the tint after each selectable
1293
+ LSDView().style_manager.set_imgui_tint(*current_tint)
1294
+
1295
+ # End the combo widget
1296
+ imgui.end_combo()
1297
+ # vis.style_manager.restore_style()
1298
+
1299
+ # Return whether the selection changed and the (possibly new) current item
1300
+ return changed, current_object
1301
+
1302
+
1303
+ def cleanup_cuda_memory(verbose=False, vis=None):
1304
+ """
1305
+ Clean up unreferenced CUDA memory that might not be automatically released by PyTorch.
1306
+
1307
+ Parameters:
1308
+ verbose (bool): Whether to print memory usage information before and after cleanup
1309
+
1310
+ Returns:
1311
+ tuple: (initial_allocated, final_allocated, freed_memory) in MB
1312
+ """
1313
+ # Check if CUDA is available
1314
+ print_stack_trace(1)
1315
+
1316
+ import torch
1317
+ if not torch.cuda.is_available():
1318
+ print("CUDA is not available")
1319
+ return (0, 0, 0)
1320
+
1321
+ if vis is not None:
1322
+ for view in vis.root.tensor_views.values():
1323
+ parent = view
1324
+ subview = parent.sub()
1325
+ if view.tensor is not None:
1326
+ del view.tensor
1327
+ view.tensor = None
1328
+ if view.tensor_b is not None:
1329
+ del view.tensor_b
1330
+ view.tensor_b = None
1331
+ if view.tensor_c is not None:
1332
+ del view.tensor_c
1333
+ view.tensor_c = None
1334
+ torch.cuda.empty_cache()
1335
+
1336
+ vis.root.selected_views = {}
1337
+
1338
+ # Get initial memory usage
1339
+ initial_allocated = torch.cuda.memory_allocated() / (1024 * 1024) # Convert to MB
1340
+ initial_reserved = torch.cuda.memory_reserved() / (1024 * 1024) # Convert to MB
1341
+
1342
+ if verbose:
1343
+ print(f"Initial CUDA memory allocated: {initial_allocated:.2f} MB")
1344
+ print(f"Initial CUDA memory reserved: {initial_reserved:.2f} MB")
1345
+
1346
+
1347
+ # Get final memory usage
1348
+ final_allocated = torch.cuda.memory_allocated() / (1024 * 1024) # Convert to MB
1349
+ final_reserved = torch.cuda.memory_reserved() / (1024 * 1024) # Convert to MB
1350
+
1351
+ freed_memory = initial_allocated - final_allocated
1352
+
1353
+ if verbose:
1354
+ print(f"Final CUDA memory allocated: {final_allocated:.2f} MB")
1355
+ print(f"Final CUDA memory reserved: {final_reserved:.2f} MB")
1356
+ print(f"Freed memory: {freed_memory:.2f} MB")
1357
+
1358
+ if final_reserved > final_allocated:
1359
+ print(
1360
+ f"Note: {final_reserved - final_allocated:.2f} MB is still reserved by PyTorch but not allocated to tensors")
1361
+ print("This memory can be used by PyTorch without additional GPU memory allocation")
1362
+
1363
+ torch.cuda.empty_cache()
1364
+
1365
+ # Clear PyTorch cache
1366
+ torch.cuda.empty_cache()
1367
+
1368
+ # No gc.collect here: on first boot this walked (and reaped) the whole
1369
+ # PREVIOUS session - a multi-second stall. The launcher runs that by
1370
+ # itself once the session has ended and its UI is up (server_gui.render_gui).
1371
+
1372
+ # Force CUDA synchronization - ensures all operations are complete
1373
+ torch.cuda.synchronize()
1374
+
1375
+ # Clear cache again after collecting garbage
1376
+ torch.cuda.empty_cache()
1377
+
1378
+ return (initial_allocated, final_allocated, freed_memory)
1379
+
1380
+
1381
+ def find_cuda_tensors():
1382
+ """
1383
+ Find and print information about all CUDA tensors currently in memory
1384
+
1385
+ Returns:
1386
+ int: Count of CUDA tensors found
1387
+ """
1388
+ cuda_tensors = []
1389
+
1390
+ import torch
1391
+ # Get all objects in memory
1392
+ for obj in gc.get_objects():
1393
+ try:
1394
+ # Check if it is a torch tensor and on CUDA
1395
+ if torch.is_tensor(obj) and obj.device.type == 'cuda':
1396
+ cuda_tensors.append(obj)
1397
+ except:
1398
+ # Some objects might raise exceptions when checking attributes
1399
+ pass
1400
+
1401
+ # Print summary
1402
+ print(f"Found {len(cuda_tensors)} CUDA tensors in memory")
1403
+
1404
+ # Group by shape for better overview
1405
+ shape_count = {}
1406
+
1407
+ for tensor in cuda_tensors:
1408
+ shape = str(tensor.shape)
1409
+ if shape in shape_count:
1410
+ shape_count[shape] += 1
1411
+ else:
1412
+ shape_count[shape] = 1
1413
+
1414
+ # Print shape statistics
1415
+ print("\nTensor shapes:")
1416
+ for shape, count in sorted(shape_count.items(), key=lambda x: x[1], reverse=True):
1417
+ print(f" {shape}: {count} tensors")
1418
+
1419
+ return len(cuda_tensors)