chemvas 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (685) hide show
  1. chemvas-0.1.0/LICENSE +21 -0
  2. chemvas-0.1.0/PKG-INFO +175 -0
  3. chemvas-0.1.0/README.md +157 -0
  4. chemvas-0.1.0/app/chemvas/__init__.py +4 -0
  5. chemvas-0.1.0/app/chemvas/__main__.py +5 -0
  6. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-128.png +0 -0
  7. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-16.png +0 -0
  8. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-256.png +0 -0
  9. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-32.png +0 -0
  10. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-512.png +0 -0
  11. chemvas-0.1.0/app/chemvas/assets/icon/chemvas-64.png +0 -0
  12. chemvas-0.1.0/app/chemvas/assets/icon/chemvas.svg +6 -0
  13. chemvas-0.1.0/app/chemvas/branding.py +40 -0
  14. chemvas-0.1.0/app/chemvas/file_open.py +60 -0
  15. chemvas-0.1.0/app/chemvas/main.py +91 -0
  16. chemvas-0.1.0/app/chemvas.egg-info/PKG-INFO +175 -0
  17. chemvas-0.1.0/app/chemvas.egg-info/SOURCES.txt +683 -0
  18. chemvas-0.1.0/app/chemvas.egg-info/dependency_links.txt +1 -0
  19. chemvas-0.1.0/app/chemvas.egg-info/entry_points.txt +2 -0
  20. chemvas-0.1.0/app/chemvas.egg-info/requires.txt +11 -0
  21. chemvas-0.1.0/app/chemvas.egg-info/top_level.txt +3 -0
  22. chemvas-0.1.0/app/core/__init__.py +0 -0
  23. chemvas-0.1.0/app/core/bond_tool_logic.py +109 -0
  24. chemvas-0.1.0/app/core/document_io.py +110 -0
  25. chemvas-0.1.0/app/core/document_state.py +1543 -0
  26. chemvas-0.1.0/app/core/history.py +1605 -0
  27. chemvas-0.1.0/app/core/model.py +81 -0
  28. chemvas-0.1.0/app/core/molfile.py +233 -0
  29. chemvas-0.1.0/app/core/perspective_drag_logic.py +57 -0
  30. chemvas-0.1.0/app/core/rdkit_adapter.py +206 -0
  31. chemvas-0.1.0/app/core/rdkit_conversion.py +809 -0
  32. chemvas-0.1.0/app/core/rdkit_import.py +188 -0
  33. chemvas-0.1.0/app/core/rdkit_types.py +48 -0
  34. chemvas-0.1.0/app/core/renderer.py +73 -0
  35. chemvas-0.1.0/app/core/style_acs1996.py +31 -0
  36. chemvas-0.1.0/app/core/svg_roundtrip.py +247 -0
  37. chemvas-0.1.0/app/core/template_geometry.py +322 -0
  38. chemvas-0.1.0/app/core/text_tool_logic.py +127 -0
  39. chemvas-0.1.0/app/core/tool_overlay_logic.py +22 -0
  40. chemvas-0.1.0/app/ui/__init__.py +0 -0
  41. chemvas-0.1.0/app/ui/active_tool_reference.py +19 -0
  42. chemvas-0.1.0/app/ui/atom_coords_access.py +81 -0
  43. chemvas-0.1.0/app/ui/atom_label_access.py +112 -0
  44. chemvas-0.1.0/app/ui/atom_label_history_recorder.py +642 -0
  45. chemvas-0.1.0/app/ui/atom_label_merge_service.py +143 -0
  46. chemvas-0.1.0/app/ui/atom_label_service.py +353 -0
  47. chemvas-0.1.0/app/ui/benzene_preview_access.py +23 -0
  48. chemvas-0.1.0/app/ui/benzene_preview_renderer.py +108 -0
  49. chemvas-0.1.0/app/ui/benzene_preview_scene_access.py +28 -0
  50. chemvas-0.1.0/app/ui/benzene_preview_service.py +76 -0
  51. chemvas-0.1.0/app/ui/benzene_tool.py +21 -0
  52. chemvas-0.1.0/app/ui/bond_dotted_geometry.py +58 -0
  53. chemvas-0.1.0/app/ui/bond_geometry_primitives.py +137 -0
  54. chemvas-0.1.0/app/ui/bond_geometry_update_service.py +269 -0
  55. chemvas-0.1.0/app/ui/bond_graphics_access.py +241 -0
  56. chemvas-0.1.0/app/ui/bond_graphics_build_service.py +190 -0
  57. chemvas-0.1.0/app/ui/bond_graphics_draw_service.py +293 -0
  58. chemvas-0.1.0/app/ui/bond_graphics_factory.py +55 -0
  59. chemvas-0.1.0/app/ui/bond_graphics_logic.py +50 -0
  60. chemvas-0.1.0/app/ui/bond_hover_preview_service.py +81 -0
  61. chemvas-0.1.0/app/ui/bond_label_geometry_access.py +37 -0
  62. chemvas-0.1.0/app/ui/bond_length_graphics_refresh.py +235 -0
  63. chemvas-0.1.0/app/ui/bond_line_geometry_service.py +275 -0
  64. chemvas-0.1.0/app/ui/bond_preview_access.py +166 -0
  65. chemvas-0.1.0/app/ui/bond_preview_geometry.py +82 -0
  66. chemvas-0.1.0/app/ui/bond_preview_renderer.py +248 -0
  67. chemvas-0.1.0/app/ui/bond_preview_scene_items.py +55 -0
  68. chemvas-0.1.0/app/ui/bond_renderer.py +285 -0
  69. chemvas-0.1.0/app/ui/bond_renderer_access.py +5 -0
  70. chemvas-0.1.0/app/ui/bond_ring_double_geometry_service.py +244 -0
  71. chemvas-0.1.0/app/ui/bond_stereo_geometry.py +83 -0
  72. chemvas-0.1.0/app/ui/bond_style_logic.py +129 -0
  73. chemvas-0.1.0/app/ui/bond_tool.py +225 -0
  74. chemvas-0.1.0/app/ui/bracket_types.py +49 -0
  75. chemvas-0.1.0/app/ui/canvas_arrow_build_service.py +178 -0
  76. chemvas-0.1.0/app/ui/canvas_atom_graphics_state.py +85 -0
  77. chemvas-0.1.0/app/ui/canvas_atom_mutation_service.py +154 -0
  78. chemvas-0.1.0/app/ui/canvas_auxiliary_service_bundle.py +53 -0
  79. chemvas-0.1.0/app/ui/canvas_background_painter.py +31 -0
  80. chemvas-0.1.0/app/ui/canvas_bond_graphics_state.py +59 -0
  81. chemvas-0.1.0/app/ui/canvas_bond_mutation_service.py +101 -0
  82. chemvas-0.1.0/app/ui/canvas_bond_renderer_state.py +25 -0
  83. chemvas-0.1.0/app/ui/canvas_callback_state.py +23 -0
  84. chemvas-0.1.0/app/ui/canvas_chemdraw_shortcut_service.py +300 -0
  85. chemvas-0.1.0/app/ui/canvas_color_mutation_service.py +2430 -0
  86. chemvas-0.1.0/app/ui/canvas_delete_transaction.py +1655 -0
  87. chemvas-0.1.0/app/ui/canvas_document_export_access.py +34 -0
  88. chemvas-0.1.0/app/ui/canvas_document_metadata_state.py +72 -0
  89. chemvas-0.1.0/app/ui/canvas_document_service_bundle.py +51 -0
  90. chemvas-0.1.0/app/ui/canvas_document_session_service.py +2927 -0
  91. chemvas-0.1.0/app/ui/canvas_document_state.py +495 -0
  92. chemvas-0.1.0/app/ui/canvas_format_access.py +22 -0
  93. chemvas-0.1.0/app/ui/canvas_geometry_access.py +50 -0
  94. chemvas-0.1.0/app/ui/canvas_geometry_controller.py +549 -0
  95. chemvas-0.1.0/app/ui/canvas_geometry_logic.py +90 -0
  96. chemvas-0.1.0/app/ui/canvas_graph_service.py +322 -0
  97. chemvas-0.1.0/app/ui/canvas_graph_service_bundle.py +22 -0
  98. chemvas-0.1.0/app/ui/canvas_graph_state.py +41 -0
  99. chemvas-0.1.0/app/ui/canvas_group_state.py +75 -0
  100. chemvas-0.1.0/app/ui/canvas_handle_controller.py +124 -0
  101. chemvas-0.1.0/app/ui/canvas_history_recording_service.py +1146 -0
  102. chemvas-0.1.0/app/ui/canvas_history_service.py +1019 -0
  103. chemvas-0.1.0/app/ui/canvas_history_state.py +25 -0
  104. chemvas-0.1.0/app/ui/canvas_hit_testing_scene_access.py +18 -0
  105. chemvas-0.1.0/app/ui/canvas_hit_testing_service.py +241 -0
  106. chemvas-0.1.0/app/ui/canvas_hover_refresh.py +55 -0
  107. chemvas-0.1.0/app/ui/canvas_hover_state.py +70 -0
  108. chemvas-0.1.0/app/ui/canvas_input_controller.py +207 -0
  109. chemvas-0.1.0/app/ui/canvas_input_service_bundle.py +76 -0
  110. chemvas-0.1.0/app/ui/canvas_insert_state.py +33 -0
  111. chemvas-0.1.0/app/ui/canvas_interaction_service_bundle.py +51 -0
  112. chemvas-0.1.0/app/ui/canvas_lifecycle.py +38 -0
  113. chemvas-0.1.0/app/ui/canvas_mark_registry.py +34 -0
  114. chemvas-0.1.0/app/ui/canvas_mark_scene_service.py +100 -0
  115. chemvas-0.1.0/app/ui/canvas_model_access.py +226 -0
  116. chemvas-0.1.0/app/ui/canvas_model_state.py +18 -0
  117. chemvas-0.1.0/app/ui/canvas_move_controller.py +242 -0
  118. chemvas-0.1.0/app/ui/canvas_note_controller.py +1004 -0
  119. chemvas-0.1.0/app/ui/canvas_pointer_controller.py +277 -0
  120. chemvas-0.1.0/app/ui/canvas_rdkit_state.py +22 -0
  121. chemvas-0.1.0/app/ui/canvas_renderer_state.py +18 -0
  122. chemvas-0.1.0/app/ui/canvas_ring_fill_scene_access.py +55 -0
  123. chemvas-0.1.0/app/ui/canvas_ring_fill_scene_service.py +160 -0
  124. chemvas-0.1.0/app/ui/canvas_rotation_preview_controller.py +61 -0
  125. chemvas-0.1.0/app/ui/canvas_rotation_preview_state.py +36 -0
  126. chemvas-0.1.0/app/ui/canvas_rotation_state.py +61 -0
  127. chemvas-0.1.0/app/ui/canvas_runtime_state.py +142 -0
  128. chemvas-0.1.0/app/ui/canvas_scene_decoration_build_service.py +426 -0
  129. chemvas-0.1.0/app/ui/canvas_scene_items_state.py +135 -0
  130. chemvas-0.1.0/app/ui/canvas_scene_reset_access.py +10 -0
  131. chemvas-0.1.0/app/ui/canvas_scene_reset_service.py +2874 -0
  132. chemvas-0.1.0/app/ui/canvas_scene_state.py +15 -0
  133. chemvas-0.1.0/app/ui/canvas_scene_view_service_bundle.py +60 -0
  134. chemvas-0.1.0/app/ui/canvas_service_access.py +31 -0
  135. chemvas-0.1.0/app/ui/canvas_service_composer.py +180 -0
  136. chemvas-0.1.0/app/ui/canvas_service_ports.py +162 -0
  137. chemvas-0.1.0/app/ui/canvas_service_types.py +100 -0
  138. chemvas-0.1.0/app/ui/canvas_services.py +57 -0
  139. chemvas-0.1.0/app/ui/canvas_smiles_input_state.py +41 -0
  140. chemvas-0.1.0/app/ui/canvas_state_lookup.py +63 -0
  141. chemvas-0.1.0/app/ui/canvas_style_controller.py +164 -0
  142. chemvas-0.1.0/app/ui/canvas_text_style_state.py +61 -0
  143. chemvas-0.1.0/app/ui/canvas_tool_mode_controller.py +233 -0
  144. chemvas-0.1.0/app/ui/canvas_tool_settings_state.py +58 -0
  145. chemvas-0.1.0/app/ui/canvas_view.py +86 -0
  146. chemvas-0.1.0/app/ui/canvas_view_event_router.py +127 -0
  147. chemvas-0.1.0/app/ui/canvas_view_ports.py +31 -0
  148. chemvas-0.1.0/app/ui/canvas_view_setup.py +57 -0
  149. chemvas-0.1.0/app/ui/canvas_window_access.py +68 -0
  150. chemvas-0.1.0/app/ui/curved_arrow_path_service.py +43 -0
  151. chemvas-0.1.0/app/ui/delete_tool_logic.py +96 -0
  152. chemvas-0.1.0/app/ui/edit_tools.py +250 -0
  153. chemvas-0.1.0/app/ui/export_dialog_logic.py +99 -0
  154. chemvas-0.1.0/app/ui/export_painting.py +30 -0
  155. chemvas-0.1.0/app/ui/export_plan_logic.py +74 -0
  156. chemvas-0.1.0/app/ui/export_raster_renderer.py +69 -0
  157. chemvas-0.1.0/app/ui/export_render_service.py +161 -0
  158. chemvas-0.1.0/app/ui/export_scene_scope.py +105 -0
  159. chemvas-0.1.0/app/ui/export_vector_renderer.py +148 -0
  160. chemvas-0.1.0/app/ui/graph_algorithms.py +290 -0
  161. chemvas-0.1.0/app/ui/graph_index_operations.py +215 -0
  162. chemvas-0.1.0/app/ui/graph_rotation_policy.py +237 -0
  163. chemvas-0.1.0/app/ui/graphics_items.py +379 -0
  164. chemvas-0.1.0/app/ui/handle_interaction_logic.py +173 -0
  165. chemvas-0.1.0/app/ui/handle_mutation_access.py +121 -0
  166. chemvas-0.1.0/app/ui/handle_mutation_service.py +110 -0
  167. chemvas-0.1.0/app/ui/handle_overlay_access.py +27 -0
  168. chemvas-0.1.0/app/ui/handle_overlay_scene_access.py +15 -0
  169. chemvas-0.1.0/app/ui/handle_overlay_service.py +111 -0
  170. chemvas-0.1.0/app/ui/handle_service_bundle.py +43 -0
  171. chemvas-0.1.0/app/ui/handle_state.py +43 -0
  172. chemvas-0.1.0/app/ui/history_atom_position_restore.py +82 -0
  173. chemvas-0.1.0/app/ui/history_canvas_access.py +524 -0
  174. chemvas-0.1.0/app/ui/history_command_snapshot.py +381 -0
  175. chemvas-0.1.0/app/ui/history_commands.py +2968 -0
  176. chemvas-0.1.0/app/ui/history_push_failure_recovery.py +610 -0
  177. chemvas-0.1.0/app/ui/history_recording_access.py +30 -0
  178. chemvas-0.1.0/app/ui/history_recovery_note.py +26 -0
  179. chemvas-0.1.0/app/ui/history_restore_retry.py +59 -0
  180. chemvas-0.1.0/app/ui/history_stack_snapshot.py +416 -0
  181. chemvas-0.1.0/app/ui/hover_highlight_access.py +23 -0
  182. chemvas-0.1.0/app/ui/hover_highlight_logic.py +75 -0
  183. chemvas-0.1.0/app/ui/hover_interaction_access.py +91 -0
  184. chemvas-0.1.0/app/ui/hover_interaction_service.py +159 -0
  185. chemvas-0.1.0/app/ui/hover_scene_access.py +32 -0
  186. chemvas-0.1.0/app/ui/hover_scene_renderer.py +121 -0
  187. chemvas-0.1.0/app/ui/hover_scene_service.py +83 -0
  188. chemvas-0.1.0/app/ui/hover_service_bundle.py +78 -0
  189. chemvas-0.1.0/app/ui/input_view_access.py +444 -0
  190. chemvas-0.1.0/app/ui/input_view_state.py +26 -0
  191. chemvas-0.1.0/app/ui/insert_commit_rollback.py +255 -0
  192. chemvas-0.1.0/app/ui/insert_commit_service.py +122 -0
  193. chemvas-0.1.0/app/ui/insert_controller.py +191 -0
  194. chemvas-0.1.0/app/ui/insert_mode_logic.py +97 -0
  195. chemvas-0.1.0/app/ui/insert_session_access.py +47 -0
  196. chemvas-0.1.0/app/ui/insert_smiles_commit_service.py +218 -0
  197. chemvas-0.1.0/app/ui/insert_smiles_service.py +643 -0
  198. chemvas-0.1.0/app/ui/insert_smiles_transaction.py +211 -0
  199. chemvas-0.1.0/app/ui/insert_template_commit_service.py +201 -0
  200. chemvas-0.1.0/app/ui/insert_template_service.py +296 -0
  201. chemvas-0.1.0/app/ui/interaction_tools.py +150 -0
  202. chemvas-0.1.0/app/ui/label_layout_logic.py +297 -0
  203. chemvas-0.1.0/app/ui/main_window.py +50 -0
  204. chemvas-0.1.0/app/ui/main_window_about_dialog.py +122 -0
  205. chemvas-0.1.0/app/ui/main_window_action_availability_service.py +38 -0
  206. chemvas-0.1.0/app/ui/main_window_active_canvas_ui_service.py +120 -0
  207. chemvas-0.1.0/app/ui/main_window_app.py +90 -0
  208. chemvas-0.1.0/app/ui/main_window_arrow_icon_renderer.py +98 -0
  209. chemvas-0.1.0/app/ui/main_window_bond_icon_renderer.py +177 -0
  210. chemvas-0.1.0/app/ui/main_window_bootstrap.py +87 -0
  211. chemvas-0.1.0/app/ui/main_window_canvas_document_service.py +191 -0
  212. chemvas-0.1.0/app/ui/main_window_canvas_logic.py +115 -0
  213. chemvas-0.1.0/app/ui/main_window_canvas_tab_styles.py +54 -0
  214. chemvas-0.1.0/app/ui/main_window_canvas_tab_ui_service.py +19 -0
  215. chemvas-0.1.0/app/ui/main_window_chrome_styles.py +96 -0
  216. chemvas-0.1.0/app/ui/main_window_config.py +133 -0
  217. chemvas-0.1.0/app/ui/main_window_context_bar_page_factories.py +444 -0
  218. chemvas-0.1.0/app/ui/main_window_context_bar_pages.py +151 -0
  219. chemvas-0.1.0/app/ui/main_window_context_bar_service.py +265 -0
  220. chemvas-0.1.0/app/ui/main_window_context_bar_widgets.py +438 -0
  221. chemvas-0.1.0/app/ui/main_window_context_page_state_service.py +45 -0
  222. chemvas-0.1.0/app/ui/main_window_design_icon_renderer.py +204 -0
  223. chemvas-0.1.0/app/ui/main_window_document_action_service.py +391 -0
  224. chemvas-0.1.0/app/ui/main_window_document_dialogs.py +232 -0
  225. chemvas-0.1.0/app/ui/main_window_form_styles.py +145 -0
  226. chemvas-0.1.0/app/ui/main_window_icon_canvas_style.py +59 -0
  227. chemvas-0.1.0/app/ui/main_window_icon_factory.py +334 -0
  228. chemvas-0.1.0/app/ui/main_window_icon_geometry.py +111 -0
  229. chemvas-0.1.0/app/ui/main_window_icon_pixmap_factory.py +69 -0
  230. chemvas-0.1.0/app/ui/main_window_menu_bar.py +42 -0
  231. chemvas-0.1.0/app/ui/main_window_palette.py +45 -0
  232. chemvas-0.1.0/app/ui/main_window_panel_service.py +68 -0
  233. chemvas-0.1.0/app/ui/main_window_panel_toolbar.py +314 -0
  234. chemvas-0.1.0/app/ui/main_window_path_logic.py +25 -0
  235. chemvas-0.1.0/app/ui/main_window_ports.py +331 -0
  236. chemvas-0.1.0/app/ui/main_window_preview_window.py +56 -0
  237. chemvas-0.1.0/app/ui/main_window_scrollbar_styles.py +75 -0
  238. chemvas-0.1.0/app/ui/main_window_services.py +286 -0
  239. chemvas-0.1.0/app/ui/main_window_state.py +29 -0
  240. chemvas-0.1.0/app/ui/main_window_status_service.py +286 -0
  241. chemvas-0.1.0/app/ui/main_window_status_styles.py +59 -0
  242. chemvas-0.1.0/app/ui/main_window_stylesheet.py +26 -0
  243. chemvas-0.1.0/app/ui/main_window_tab_references.py +65 -0
  244. chemvas-0.1.0/app/ui/main_window_tab_setup.py +43 -0
  245. chemvas-0.1.0/app/ui/main_window_template_icon_renderer.py +84 -0
  246. chemvas-0.1.0/app/ui/main_window_text_style_service.py +63 -0
  247. chemvas-0.1.0/app/ui/main_window_theme.py +33 -0
  248. chemvas-0.1.0/app/ui/main_window_tool_action_service.py +100 -0
  249. chemvas-0.1.0/app/ui/main_window_tool_icon_renderer.py +365 -0
  250. chemvas-0.1.0/app/ui/main_window_tool_routing_service.py +133 -0
  251. chemvas-0.1.0/app/ui/main_window_tool_state_service.py +93 -0
  252. chemvas-0.1.0/app/ui/main_window_toolbar_buttons.py +189 -0
  253. chemvas-0.1.0/app/ui/main_window_toolbar_logic.py +118 -0
  254. chemvas-0.1.0/app/ui/main_window_toolbar_styles.py +139 -0
  255. chemvas-0.1.0/app/ui/main_window_ui_assembly_service.py +164 -0
  256. chemvas-0.1.0/app/ui/main_window_ui_references.py +53 -0
  257. chemvas-0.1.0/app/ui/main_window_utility_icon_renderer.py +117 -0
  258. chemvas-0.1.0/app/ui/mark_hover_preview_service.py +68 -0
  259. chemvas-0.1.0/app/ui/mark_item_access.py +101 -0
  260. chemvas-0.1.0/app/ui/move_access.py +65 -0
  261. chemvas-0.1.0/app/ui/move_tool.py +205 -0
  262. chemvas-0.1.0/app/ui/note_html_sanitizer.py +337 -0
  263. chemvas-0.1.0/app/ui/note_item.py +35 -0
  264. chemvas-0.1.0/app/ui/note_item_access.py +83 -0
  265. chemvas-0.1.0/app/ui/note_selection_box.py +16 -0
  266. chemvas-0.1.0/app/ui/perspective_tool.py +93 -0
  267. chemvas-0.1.0/app/ui/perspective_tool_controller.py +52 -0
  268. chemvas-0.1.0/app/ui/pick_radius_access.py +23 -0
  269. chemvas-0.1.0/app/ui/preview_3d.py +483 -0
  270. chemvas-0.1.0/app/ui/preview_3d_interaction.py +39 -0
  271. chemvas-0.1.0/app/ui/preview_3d_layout.py +65 -0
  272. chemvas-0.1.0/app/ui/preview_3d_molecule_renderer.py +75 -0
  273. chemvas-0.1.0/app/ui/preview_3d_painter.py +179 -0
  274. chemvas-0.1.0/app/ui/preview_3d_projection.py +102 -0
  275. chemvas-0.1.0/app/ui/preview_3d_renderer.py +246 -0
  276. chemvas-0.1.0/app/ui/preview_3d_state.py +113 -0
  277. chemvas-0.1.0/app/ui/preview_3d_worker.py +61 -0
  278. chemvas-0.1.0/app/ui/preview_scene_access.py +84 -0
  279. chemvas-0.1.0/app/ui/preview_scene_renderer.py +190 -0
  280. chemvas-0.1.0/app/ui/preview_tools.py +140 -0
  281. chemvas-0.1.0/app/ui/rdkit_adapter_access.py +56 -0
  282. chemvas-0.1.0/app/ui/rdkit_async_jobs.py +204 -0
  283. chemvas-0.1.0/app/ui/rdkit_export_job_state.py +205 -0
  284. chemvas-0.1.0/app/ui/renderer_style_access.py +135 -0
  285. chemvas-0.1.0/app/ui/ring_occupancy_logic.py +66 -0
  286. chemvas-0.1.0/app/ui/rotate_tool.py +38 -0
  287. chemvas-0.1.0/app/ui/scene_clipboard_access.py +145 -0
  288. chemvas-0.1.0/app/ui/scene_clipboard_controller.py +197 -0
  289. chemvas-0.1.0/app/ui/scene_clipboard_copy_io.py +95 -0
  290. chemvas-0.1.0/app/ui/scene_clipboard_copy_service.py +65 -0
  291. chemvas-0.1.0/app/ui/scene_clipboard_logic.py +241 -0
  292. chemvas-0.1.0/app/ui/scene_clipboard_paste_service.py +275 -0
  293. chemvas-0.1.0/app/ui/scene_clipboard_selection.py +78 -0
  294. chemvas-0.1.0/app/ui/scene_clipboard_state.py +19 -0
  295. chemvas-0.1.0/app/ui/scene_clipboard_transaction_logic.py +234 -0
  296. chemvas-0.1.0/app/ui/scene_decoration_access.py +138 -0
  297. chemvas-0.1.0/app/ui/scene_decoration_build_access.py +70 -0
  298. chemvas-0.1.0/app/ui/scene_decoration_service.py +201 -0
  299. chemvas-0.1.0/app/ui/scene_decoration_service_bundle.py +39 -0
  300. chemvas-0.1.0/app/ui/scene_delete_apply_logic.py +100 -0
  301. chemvas-0.1.0/app/ui/scene_delete_controller.py +1321 -0
  302. chemvas-0.1.0/app/ui/scene_delete_logic.py +143 -0
  303. chemvas-0.1.0/app/ui/scene_flip_geometry.py +105 -0
  304. chemvas-0.1.0/app/ui/scene_flip_grouping.py +122 -0
  305. chemvas-0.1.0/app/ui/scene_flip_state.py +118 -0
  306. chemvas-0.1.0/app/ui/scene_group_operations.py +523 -0
  307. chemvas-0.1.0/app/ui/scene_item_access.py +325 -0
  308. chemvas-0.1.0/app/ui/scene_item_attach_snapshot.py +1800 -0
  309. chemvas-0.1.0/app/ui/scene_item_controller.py +215 -0
  310. chemvas-0.1.0/app/ui/scene_item_lifecycle_service.py +290 -0
  311. chemvas-0.1.0/app/ui/scene_item_restore.py +259 -0
  312. chemvas-0.1.0/app/ui/scene_item_state.py +317 -0
  313. chemvas-0.1.0/app/ui/scene_item_state_serialization.py +285 -0
  314. chemvas-0.1.0/app/ui/scene_operation_service_bundle.py +73 -0
  315. chemvas-0.1.0/app/ui/scene_paste_apply_logic.py +180 -0
  316. chemvas-0.1.0/app/ui/scene_rect_snapshot.py +2540 -0
  317. chemvas-0.1.0/app/ui/scene_rotation_state.py +115 -0
  318. chemvas-0.1.0/app/ui/scene_selectability.py +10 -0
  319. chemvas-0.1.0/app/ui/scene_signal_blocking.py +187 -0
  320. chemvas-0.1.0/app/ui/scene_single_item_mutation_logic.py +267 -0
  321. chemvas-0.1.0/app/ui/scene_transform_apply_logic.py +122 -0
  322. chemvas-0.1.0/app/ui/scene_transform_controller.py +552 -0
  323. chemvas-0.1.0/app/ui/scene_transform_logic.py +29 -0
  324. chemvas-0.1.0/app/ui/select_all_access.py +53 -0
  325. chemvas-0.1.0/app/ui/select_tool.py +402 -0
  326. chemvas-0.1.0/app/ui/selection_access.py +71 -0
  327. chemvas-0.1.0/app/ui/selection_center_logic.py +45 -0
  328. chemvas-0.1.0/app/ui/selection_collection_access.py +289 -0
  329. chemvas-0.1.0/app/ui/selection_controller.py +160 -0
  330. chemvas-0.1.0/app/ui/selection_drag_tool.py +2764 -0
  331. chemvas-0.1.0/app/ui/selection_geometry_access.py +32 -0
  332. chemvas-0.1.0/app/ui/selection_highlight_styler.py +58 -0
  333. chemvas-0.1.0/app/ui/selection_hit_logic.py +190 -0
  334. chemvas-0.1.0/app/ui/selection_hit_test_service.py +97 -0
  335. chemvas-0.1.0/app/ui/selection_info_access.py +116 -0
  336. chemvas-0.1.0/app/ui/selection_info_state.py +36 -0
  337. chemvas-0.1.0/app/ui/selection_note_service.py +162 -0
  338. chemvas-0.1.0/app/ui/selection_outline_items.py +89 -0
  339. chemvas-0.1.0/app/ui/selection_outline_paths.py +129 -0
  340. chemvas-0.1.0/app/ui/selection_outline_service.py +299 -0
  341. chemvas-0.1.0/app/ui/selection_outline_state.py +43 -0
  342. chemvas-0.1.0/app/ui/selection_preference_service.py +98 -0
  343. chemvas-0.1.0/app/ui/selection_press_logic.py +33 -0
  344. chemvas-0.1.0/app/ui/selection_rotation_access.py +280 -0
  345. chemvas-0.1.0/app/ui/selection_rotation_controller.py +1154 -0
  346. chemvas-0.1.0/app/ui/selection_rotation_geometry.py +198 -0
  347. chemvas-0.1.0/app/ui/selection_rotation_history.py +37 -0
  348. chemvas-0.1.0/app/ui/selection_rotation_logic.py +51 -0
  349. chemvas-0.1.0/app/ui/selection_rotation_planarity.py +132 -0
  350. chemvas-0.1.0/app/ui/selection_rotation_preview_transaction.py +2572 -0
  351. chemvas-0.1.0/app/ui/selection_rotation_session.py +285 -0
  352. chemvas-0.1.0/app/ui/selection_scene_access.py +2417 -0
  353. chemvas-0.1.0/app/ui/selection_service_access.py +103 -0
  354. chemvas-0.1.0/app/ui/selection_service_bundle.py +73 -0
  355. chemvas-0.1.0/app/ui/selection_structure_service.py +135 -0
  356. chemvas-0.1.0/app/ui/selection_structure_targets.py +53 -0
  357. chemvas-0.1.0/app/ui/selection_style_access.py +112 -0
  358. chemvas-0.1.0/app/ui/selection_style_state.py +26 -0
  359. chemvas-0.1.0/app/ui/shape_geometry.py +83 -0
  360. chemvas-0.1.0/app/ui/sheet_setup_access.py +1011 -0
  361. chemvas-0.1.0/app/ui/sheet_setup_logic.py +64 -0
  362. chemvas-0.1.0/app/ui/sheet_setup_state.py +58 -0
  363. chemvas-0.1.0/app/ui/smiles_insert_logic.py +266 -0
  364. chemvas-0.1.0/app/ui/spatial_index_state.py +88 -0
  365. chemvas-0.1.0/app/ui/structure_benzene_build_service.py +111 -0
  366. chemvas-0.1.0/app/ui/structure_benzene_logic.py +48 -0
  367. chemvas-0.1.0/app/ui/structure_bond_build_service.py +200 -0
  368. chemvas-0.1.0/app/ui/structure_build_access.py +81 -0
  369. chemvas-0.1.0/app/ui/structure_build_committer.py +636 -0
  370. chemvas-0.1.0/app/ui/structure_build_service.py +323 -0
  371. chemvas-0.1.0/app/ui/structure_fragment_build_service.py +351 -0
  372. chemvas-0.1.0/app/ui/structure_geometry_access.py +251 -0
  373. chemvas-0.1.0/app/ui/structure_geometry_logic.py +233 -0
  374. chemvas-0.1.0/app/ui/structure_growth_build_actions.py +53 -0
  375. chemvas-0.1.0/app/ui/structure_growth_build_service.py +198 -0
  376. chemvas-0.1.0/app/ui/structure_growth_logic.py +104 -0
  377. chemvas-0.1.0/app/ui/structure_insert_access.py +384 -0
  378. chemvas-0.1.0/app/ui/structure_insert_service.py +219 -0
  379. chemvas-0.1.0/app/ui/structure_mutation_access.py +47 -0
  380. chemvas-0.1.0/app/ui/structure_payload_access.py +82 -0
  381. chemvas-0.1.0/app/ui/structure_payload_logic.py +206 -0
  382. chemvas-0.1.0/app/ui/structure_service_bundle.py +64 -0
  383. chemvas-0.1.0/app/ui/structure_template_build_service.py +148 -0
  384. chemvas-0.1.0/app/ui/structure_template_commands.py +130 -0
  385. chemvas-0.1.0/app/ui/template_geometry_resolver_service.py +114 -0
  386. chemvas-0.1.0/app/ui/template_insert_logic.py +193 -0
  387. chemvas-0.1.0/app/ui/template_preview_logic.py +106 -0
  388. chemvas-0.1.0/app/ui/text_tool.py +103 -0
  389. chemvas-0.1.0/app/ui/tool_base.py +38 -0
  390. chemvas-0.1.0/app/ui/tool_context.py +457 -0
  391. chemvas-0.1.0/app/ui/tool_controller.py +84 -0
  392. chemvas-0.1.0/app/ui/tool_service_bundle.py +71 -0
  393. chemvas-0.1.0/app/ui/tools.py +39 -0
  394. chemvas-0.1.0/pyproject.toml +82 -0
  395. chemvas-0.1.0/setup.cfg +4 -0
  396. chemvas-0.1.0/tests/test_active_tool_reference.py +26 -0
  397. chemvas-0.1.0/tests/test_architecture_boundaries.py +3501 -0
  398. chemvas-0.1.0/tests/test_atom_coords_access.py +78 -0
  399. chemvas-0.1.0/tests/test_atom_label_access.py +87 -0
  400. chemvas-0.1.0/tests/test_atom_label_service.py +1346 -0
  401. chemvas-0.1.0/tests/test_benzene_preview_renderer.py +222 -0
  402. chemvas-0.1.0/tests/test_benzene_preview_service.py +114 -0
  403. chemvas-0.1.0/tests/test_bond_dotted_geometry.py +50 -0
  404. chemvas-0.1.0/tests/test_bond_graphics_logic.py +136 -0
  405. chemvas-0.1.0/tests/test_bond_hover_preview_service.py +165 -0
  406. chemvas-0.1.0/tests/test_bond_preview_access.py +31 -0
  407. chemvas-0.1.0/tests/test_bond_preview_geometry.py +37 -0
  408. chemvas-0.1.0/tests/test_bond_preview_renderer.py +787 -0
  409. chemvas-0.1.0/tests/test_bond_renderer_unit.py +1018 -0
  410. chemvas-0.1.0/tests/test_bond_stereo_geometry.py +40 -0
  411. chemvas-0.1.0/tests/test_bond_style_logic.py +81 -0
  412. chemvas-0.1.0/tests/test_bond_tool_logic.py +290 -0
  413. chemvas-0.1.0/tests/test_canvas_arrow_build_service.py +196 -0
  414. chemvas-0.1.0/tests/test_canvas_atom_graphics_state.py +69 -0
  415. chemvas-0.1.0/tests/test_canvas_atom_mutation_service.py +357 -0
  416. chemvas-0.1.0/tests/test_canvas_auxiliary_service_bundle.py +58 -0
  417. chemvas-0.1.0/tests/test_canvas_background_painter.py +31 -0
  418. chemvas-0.1.0/tests/test_canvas_bond_graphics_state.py +55 -0
  419. chemvas-0.1.0/tests/test_canvas_bond_mutation_service.py +320 -0
  420. chemvas-0.1.0/tests/test_canvas_bond_renderer_state.py +31 -0
  421. chemvas-0.1.0/tests/test_canvas_chemdraw_shortcut_service.py +326 -0
  422. chemvas-0.1.0/tests/test_canvas_color_mutation_service.py +2961 -0
  423. chemvas-0.1.0/tests/test_canvas_document_groups.py +148 -0
  424. chemvas-0.1.0/tests/test_canvas_document_service_bundle.py +56 -0
  425. chemvas-0.1.0/tests/test_canvas_document_session_service.py +3956 -0
  426. chemvas-0.1.0/tests/test_canvas_document_state.py +481 -0
  427. chemvas-0.1.0/tests/test_canvas_format_access.py +19 -0
  428. chemvas-0.1.0/tests/test_canvas_geometry_controller.py +201 -0
  429. chemvas-0.1.0/tests/test_canvas_geometry_logic.py +81 -0
  430. chemvas-0.1.0/tests/test_canvas_graph_service.py +474 -0
  431. chemvas-0.1.0/tests/test_canvas_graph_service_bundle.py +36 -0
  432. chemvas-0.1.0/tests/test_canvas_group_state.py +87 -0
  433. chemvas-0.1.0/tests/test_canvas_handle_controller.py +140 -0
  434. chemvas-0.1.0/tests/test_canvas_history_recording_service.py +1541 -0
  435. chemvas-0.1.0/tests/test_canvas_hit_testing_service.py +234 -0
  436. chemvas-0.1.0/tests/test_canvas_hover_state.py +45 -0
  437. chemvas-0.1.0/tests/test_canvas_input_controller.py +468 -0
  438. chemvas-0.1.0/tests/test_canvas_input_service_bundle.py +86 -0
  439. chemvas-0.1.0/tests/test_canvas_interaction_service_bundle.py +54 -0
  440. chemvas-0.1.0/tests/test_canvas_lifecycle.py +45 -0
  441. chemvas-0.1.0/tests/test_canvas_mark_scene_service.py +143 -0
  442. chemvas-0.1.0/tests/test_canvas_model_access.py +119 -0
  443. chemvas-0.1.0/tests/test_canvas_model_state.py +27 -0
  444. chemvas-0.1.0/tests/test_canvas_note_controller_unit.py +1979 -0
  445. chemvas-0.1.0/tests/test_canvas_rdkit_state.py +31 -0
  446. chemvas-0.1.0/tests/test_canvas_renderer_state.py +39 -0
  447. chemvas-0.1.0/tests/test_canvas_ring_fill_scene_service.py +250 -0
  448. chemvas-0.1.0/tests/test_canvas_scene_decoration_build_service.py +225 -0
  449. chemvas-0.1.0/tests/test_canvas_scene_items_state.py +97 -0
  450. chemvas-0.1.0/tests/test_canvas_scene_reset_service.py +2381 -0
  451. chemvas-0.1.0/tests/test_canvas_scene_state.py +21 -0
  452. chemvas-0.1.0/tests/test_canvas_scene_view_service_bundle.py +65 -0
  453. chemvas-0.1.0/tests/test_canvas_service_access.py +47 -0
  454. chemvas-0.1.0/tests/test_canvas_service_ports.py +53 -0
  455. chemvas-0.1.0/tests/test_canvas_services.py +534 -0
  456. chemvas-0.1.0/tests/test_canvas_smiles_input_state.py +49 -0
  457. chemvas-0.1.0/tests/test_canvas_state_lookup.py +92 -0
  458. chemvas-0.1.0/tests/test_canvas_text_style_state.py +63 -0
  459. chemvas-0.1.0/tests/test_canvas_tool_settings_state.py +51 -0
  460. chemvas-0.1.0/tests/test_canvas_view_additional.py +2061 -0
  461. chemvas-0.1.0/tests/test_canvas_view_center_transform_helpers.py +292 -0
  462. chemvas-0.1.0/tests/test_canvas_view_delete_and_bond_style.py +336 -0
  463. chemvas-0.1.0/tests/test_canvas_view_event_fallthrough.py +295 -0
  464. chemvas-0.1.0/tests/test_canvas_view_event_router.py +128 -0
  465. chemvas-0.1.0/tests/test_canvas_view_event_shortcuts.py +156 -0
  466. chemvas-0.1.0/tests/test_canvas_view_event_wrappers.py +389 -0
  467. chemvas-0.1.0/tests/test_canvas_view_handle_helpers.py +363 -0
  468. chemvas-0.1.0/tests/test_canvas_view_hover_helpers.py +414 -0
  469. chemvas-0.1.0/tests/test_canvas_view_hover_preview.py +370 -0
  470. chemvas-0.1.0/tests/test_canvas_view_keypress_routing.py +55 -0
  471. chemvas-0.1.0/tests/test_canvas_view_label_geometry_helpers.py +273 -0
  472. chemvas-0.1.0/tests/test_canvas_view_mark_helpers.py +203 -0
  473. chemvas-0.1.0/tests/test_canvas_view_move_helpers.py +405 -0
  474. chemvas-0.1.0/tests/test_canvas_view_note_wrapper_contract.py +187 -0
  475. chemvas-0.1.0/tests/test_canvas_view_ports.py +44 -0
  476. chemvas-0.1.0/tests/test_canvas_view_projection_math.py +1585 -0
  477. chemvas-0.1.0/tests/test_canvas_view_ring_fill_rotation.py +146 -0
  478. chemvas-0.1.0/tests/test_canvas_view_rotation_axis_helpers.py +180 -0
  479. chemvas-0.1.0/tests/test_canvas_view_rotation_preview_helpers.py +210 -0
  480. chemvas-0.1.0/tests/test_canvas_view_scene_ops_rebuild.py +88 -0
  481. chemvas-0.1.0/tests/test_canvas_view_selection_clipboard_wrappers.py +313 -0
  482. chemvas-0.1.0/tests/test_canvas_view_selection_highlight.py +329 -0
  483. chemvas-0.1.0/tests/test_canvas_view_setup.py +62 -0
  484. chemvas-0.1.0/tests/test_canvas_view_transform_helpers.py +369 -0
  485. chemvas-0.1.0/tests/test_canvas_view_unit.py +766 -0
  486. chemvas-0.1.0/tests/test_canvas_view_wheel_and_scroll.py +130 -0
  487. chemvas-0.1.0/tests/test_clipboard_payload_validation.py +330 -0
  488. chemvas-0.1.0/tests/test_core_document_groups.py +122 -0
  489. chemvas-0.1.0/tests/test_core_document_io.py +481 -0
  490. chemvas-0.1.0/tests/test_core_document_state.py +1100 -0
  491. chemvas-0.1.0/tests/test_core_history.py +3751 -0
  492. chemvas-0.1.0/tests/test_core_history_ui_atomicity.py +1668 -0
  493. chemvas-0.1.0/tests/test_core_model.py +82 -0
  494. chemvas-0.1.0/tests/test_core_molfile.py +191 -0
  495. chemvas-0.1.0/tests/test_core_rdkit_adapter.py +2115 -0
  496. chemvas-0.1.0/tests/test_core_renderer.py +44 -0
  497. chemvas-0.1.0/tests/test_core_tail_coverage.py +595 -0
  498. chemvas-0.1.0/tests/test_core_template_geometry.py +310 -0
  499. chemvas-0.1.0/tests/test_curved_arrow_path_service.py +45 -0
  500. chemvas-0.1.0/tests/test_delete_tool_logic.py +216 -0
  501. chemvas-0.1.0/tests/test_export_dialog_logic.py +47 -0
  502. chemvas-0.1.0/tests/test_export_plan_logic.py +44 -0
  503. chemvas-0.1.0/tests/test_export_render_service.py +229 -0
  504. chemvas-0.1.0/tests/test_export_scene_scope.py +129 -0
  505. chemvas-0.1.0/tests/test_file_open.py +118 -0
  506. chemvas-0.1.0/tests/test_graph_algorithms.py +127 -0
  507. chemvas-0.1.0/tests/test_graph_index_operations.py +276 -0
  508. chemvas-0.1.0/tests/test_graph_rotation_policy.py +162 -0
  509. chemvas-0.1.0/tests/test_graphics_items.py +113 -0
  510. chemvas-0.1.0/tests/test_group_note_selection_integration.py +94 -0
  511. chemvas-0.1.0/tests/test_gui_document_template.py +1097 -0
  512. chemvas-0.1.0/tests/test_gui_handle_interaction.py +99 -0
  513. chemvas-0.1.0/tests/test_gui_preview_3d_recovery.py +591 -0
  514. chemvas-0.1.0/tests/test_gui_smoke.py +2329 -0
  515. chemvas-0.1.0/tests/test_handle_interaction_logic.py +136 -0
  516. chemvas-0.1.0/tests/test_handle_mutation_service.py +160 -0
  517. chemvas-0.1.0/tests/test_handle_overlay_service.py +208 -0
  518. chemvas-0.1.0/tests/test_handle_service_bundle.py +41 -0
  519. chemvas-0.1.0/tests/test_history_command_snapshot.py +57 -0
  520. chemvas-0.1.0/tests/test_history_restore_retry.py +80 -0
  521. chemvas-0.1.0/tests/test_history_stack_snapshot.py +739 -0
  522. chemvas-0.1.0/tests/test_hover_highlight_logic.py +196 -0
  523. chemvas-0.1.0/tests/test_hover_interaction_service.py +267 -0
  524. chemvas-0.1.0/tests/test_hover_scene_access.py +51 -0
  525. chemvas-0.1.0/tests/test_hover_scene_renderer.py +131 -0
  526. chemvas-0.1.0/tests/test_hover_scene_service.py +166 -0
  527. chemvas-0.1.0/tests/test_hover_service_bundle.py +121 -0
  528. chemvas-0.1.0/tests/test_input_view_access.py +216 -0
  529. chemvas-0.1.0/tests/test_insert_commit_service.py +1324 -0
  530. chemvas-0.1.0/tests/test_insert_controller.py +1425 -0
  531. chemvas-0.1.0/tests/test_insert_mode_logic.py +119 -0
  532. chemvas-0.1.0/tests/test_insert_smiles_service.py +1090 -0
  533. chemvas-0.1.0/tests/test_insert_smiles_transaction.py +213 -0
  534. chemvas-0.1.0/tests/test_insert_template_service.py +237 -0
  535. chemvas-0.1.0/tests/test_label_layout_logic.py +191 -0
  536. chemvas-0.1.0/tests/test_main_stderr_filter.py +262 -0
  537. chemvas-0.1.0/tests/test_main_window_about_dialog.py +69 -0
  538. chemvas-0.1.0/tests/test_main_window_action_availability_service.py +74 -0
  539. chemvas-0.1.0/tests/test_main_window_active_canvas_ui_service.py +301 -0
  540. chemvas-0.1.0/tests/test_main_window_app.py +82 -0
  541. chemvas-0.1.0/tests/test_main_window_arrow_icon_renderer.py +92 -0
  542. chemvas-0.1.0/tests/test_main_window_bond_icon_renderer.py +146 -0
  543. chemvas-0.1.0/tests/test_main_window_bootstrap.py +147 -0
  544. chemvas-0.1.0/tests/test_main_window_canvas_document_service.py +117 -0
  545. chemvas-0.1.0/tests/test_main_window_canvas_logic.py +124 -0
  546. chemvas-0.1.0/tests/test_main_window_canvas_tab_ui_service.py +16 -0
  547. chemvas-0.1.0/tests/test_main_window_canvas_tabs.py +75 -0
  548. chemvas-0.1.0/tests/test_main_window_context_bar_pages.py +252 -0
  549. chemvas-0.1.0/tests/test_main_window_context_bar_service.py +88 -0
  550. chemvas-0.1.0/tests/test_main_window_context_page_state_service.py +117 -0
  551. chemvas-0.1.0/tests/test_main_window_dialog_actions.py +127 -0
  552. chemvas-0.1.0/tests/test_main_window_document_action_service.py +205 -0
  553. chemvas-0.1.0/tests/test_main_window_document_dialogs.py +150 -0
  554. chemvas-0.1.0/tests/test_main_window_icon_canvas_style.py +91 -0
  555. chemvas-0.1.0/tests/test_main_window_icon_pixmap_factory.py +94 -0
  556. chemvas-0.1.0/tests/test_main_window_icons.py +268 -0
  557. chemvas-0.1.0/tests/test_main_window_panel_actions.py +321 -0
  558. chemvas-0.1.0/tests/test_main_window_panel_service.py +100 -0
  559. chemvas-0.1.0/tests/test_main_window_panel_toolbar.py +302 -0
  560. chemvas-0.1.0/tests/test_main_window_path_logic.py +58 -0
  561. chemvas-0.1.0/tests/test_main_window_preview_panel.py +54 -0
  562. chemvas-0.1.0/tests/test_main_window_services.py +93 -0
  563. chemvas-0.1.0/tests/test_main_window_state.py +30 -0
  564. chemvas-0.1.0/tests/test_main_window_status_service.py +146 -0
  565. chemvas-0.1.0/tests/test_main_window_tab_references.py +57 -0
  566. chemvas-0.1.0/tests/test_main_window_tab_setup.py +53 -0
  567. chemvas-0.1.0/tests/test_main_window_template_icon_renderer.py +125 -0
  568. chemvas-0.1.0/tests/test_main_window_text_style_service.py +76 -0
  569. chemvas-0.1.0/tests/test_main_window_theme.py +97 -0
  570. chemvas-0.1.0/tests/test_main_window_tool_action_service.py +150 -0
  571. chemvas-0.1.0/tests/test_main_window_tool_icon_renderer.py +124 -0
  572. chemvas-0.1.0/tests/test_main_window_tool_routing_service.py +200 -0
  573. chemvas-0.1.0/tests/test_main_window_tool_state_service.py +200 -0
  574. chemvas-0.1.0/tests/test_main_window_toolbar_actions.py +507 -0
  575. chemvas-0.1.0/tests/test_main_window_toolbar_buttons.py +148 -0
  576. chemvas-0.1.0/tests/test_main_window_toolbar_logic.py +91 -0
  577. chemvas-0.1.0/tests/test_main_window_ui_assembly_service.py +429 -0
  578. chemvas-0.1.0/tests/test_main_window_ui_references.py +70 -0
  579. chemvas-0.1.0/tests/test_main_window_utility_icon_renderer.py +87 -0
  580. chemvas-0.1.0/tests/test_mark_hover_preview_service.py +161 -0
  581. chemvas-0.1.0/tests/test_note_html_sanitizer.py +101 -0
  582. chemvas-0.1.0/tests/test_note_item_access.py +60 -0
  583. chemvas-0.1.0/tests/test_perspective_drag_logic.py +82 -0
  584. chemvas-0.1.0/tests/test_perspective_tool_controller.py +452 -0
  585. chemvas-0.1.0/tests/test_preview_3d_interaction.py +41 -0
  586. chemvas-0.1.0/tests/test_preview_3d_layout.py +46 -0
  587. chemvas-0.1.0/tests/test_preview_3d_projection.py +75 -0
  588. chemvas-0.1.0/tests/test_preview_3d_renderer.py +51 -0
  589. chemvas-0.1.0/tests/test_preview_3d_state.py +93 -0
  590. chemvas-0.1.0/tests/test_preview_3d_worker.py +70 -0
  591. chemvas-0.1.0/tests/test_preview_scene_access.py +95 -0
  592. chemvas-0.1.0/tests/test_preview_scene_renderer.py +250 -0
  593. chemvas-0.1.0/tests/test_rdkit_adapter_access.py +48 -0
  594. chemvas-0.1.0/tests/test_rdkit_async_jobs.py +437 -0
  595. chemvas-0.1.0/tests/test_renderer_canvas_tail_coverage.py +428 -0
  596. chemvas-0.1.0/tests/test_renderer_style_access.py +94 -0
  597. chemvas-0.1.0/tests/test_ring_occupancy_logic.py +82 -0
  598. chemvas-0.1.0/tests/test_safe_note_helpers.py +147 -0
  599. chemvas-0.1.0/tests/test_scene_clipboard_access.py +139 -0
  600. chemvas-0.1.0/tests/test_scene_clipboard_copy_io.py +92 -0
  601. chemvas-0.1.0/tests/test_scene_clipboard_logic.py +663 -0
  602. chemvas-0.1.0/tests/test_scene_clipboard_selection.py +79 -0
  603. chemvas-0.1.0/tests/test_scene_clipboard_transaction_logic.py +244 -0
  604. chemvas-0.1.0/tests/test_scene_decoration_build_service.py +177 -0
  605. chemvas-0.1.0/tests/test_scene_decoration_service.py +794 -0
  606. chemvas-0.1.0/tests/test_scene_decoration_service_bundle.py +40 -0
  607. chemvas-0.1.0/tests/test_scene_delete_apply_logic.py +269 -0
  608. chemvas-0.1.0/tests/test_scene_delete_initial_atomicity.py +2524 -0
  609. chemvas-0.1.0/tests/test_scene_delete_logic.py +380 -0
  610. chemvas-0.1.0/tests/test_scene_group_operations.py +701 -0
  611. chemvas-0.1.0/tests/test_scene_item_access.py +423 -0
  612. chemvas-0.1.0/tests/test_scene_item_controller.py +1889 -0
  613. chemvas-0.1.0/tests/test_scene_item_lifecycle_note_removal.py +160 -0
  614. chemvas-0.1.0/tests/test_scene_item_restore.py +163 -0
  615. chemvas-0.1.0/tests/test_scene_item_state_codec.py +102 -0
  616. chemvas-0.1.0/tests/test_scene_item_state_serialization.py +109 -0
  617. chemvas-0.1.0/tests/test_scene_item_state_unit.py +648 -0
  618. chemvas-0.1.0/tests/test_scene_operation_service_bundle.py +73 -0
  619. chemvas-0.1.0/tests/test_scene_ops_controller.py +1299 -0
  620. chemvas-0.1.0/tests/test_scene_ops_controller_additional.py +555 -0
  621. chemvas-0.1.0/tests/test_scene_ops_controller_clipboard_payload.py +397 -0
  622. chemvas-0.1.0/tests/test_scene_ops_controller_delete_flip_edges.py +113 -0
  623. chemvas-0.1.0/tests/test_scene_ops_controller_paste_edges.py +502 -0
  624. chemvas-0.1.0/tests/test_scene_paste_apply_hardening.py +151 -0
  625. chemvas-0.1.0/tests/test_scene_paste_apply_logic.py +264 -0
  626. chemvas-0.1.0/tests/test_scene_rect_snapshot.py +2698 -0
  627. chemvas-0.1.0/tests/test_scene_rotation_state.py +117 -0
  628. chemvas-0.1.0/tests/test_scene_signal_blocking.py +157 -0
  629. chemvas-0.1.0/tests/test_scene_single_item_mutation_logic.py +286 -0
  630. chemvas-0.1.0/tests/test_scene_transform_apply_logic.py +125 -0
  631. chemvas-0.1.0/tests/test_scene_transform_logic.py +474 -0
  632. chemvas-0.1.0/tests/test_select_all_access.py +101 -0
  633. chemvas-0.1.0/tests/test_selection_access_status.py +156 -0
  634. chemvas-0.1.0/tests/test_selection_center_logic.py +26 -0
  635. chemvas-0.1.0/tests/test_selection_controller_additional.py +910 -0
  636. chemvas-0.1.0/tests/test_selection_highlight_styler.py +133 -0
  637. chemvas-0.1.0/tests/test_selection_hit_logic.py +262 -0
  638. chemvas-0.1.0/tests/test_selection_hit_membership_logic.py +130 -0
  639. chemvas-0.1.0/tests/test_selection_hit_test_service.py +107 -0
  640. chemvas-0.1.0/tests/test_selection_note_service.py +191 -0
  641. chemvas-0.1.0/tests/test_selection_outline_items.py +82 -0
  642. chemvas-0.1.0/tests/test_selection_outline_paths.py +103 -0
  643. chemvas-0.1.0/tests/test_selection_outline_service.py +284 -0
  644. chemvas-0.1.0/tests/test_selection_outline_state.py +43 -0
  645. chemvas-0.1.0/tests/test_selection_preference_service.py +140 -0
  646. chemvas-0.1.0/tests/test_selection_press_logic.py +68 -0
  647. chemvas-0.1.0/tests/test_selection_rotation_controller.py +3849 -0
  648. chemvas-0.1.0/tests/test_selection_rotation_geometry.py +138 -0
  649. chemvas-0.1.0/tests/test_selection_rotation_history.py +61 -0
  650. chemvas-0.1.0/tests/test_selection_rotation_logic.py +41 -0
  651. chemvas-0.1.0/tests/test_selection_rotation_planarity.py +38 -0
  652. chemvas-0.1.0/tests/test_selection_rotation_ring_cache.py +98 -0
  653. chemvas-0.1.0/tests/test_selection_rotation_session.py +393 -0
  654. chemvas-0.1.0/tests/test_selection_scene_access.py +2258 -0
  655. chemvas-0.1.0/tests/test_selection_service_bundle.py +83 -0
  656. chemvas-0.1.0/tests/test_selection_structure_service.py +153 -0
  657. chemvas-0.1.0/tests/test_shapes.py +238 -0
  658. chemvas-0.1.0/tests/test_sheet_setup_access.py +819 -0
  659. chemvas-0.1.0/tests/test_sheet_setup_logic.py +30 -0
  660. chemvas-0.1.0/tests/test_sheet_setup_state.py +28 -0
  661. chemvas-0.1.0/tests/test_smiles_insert_logic.py +196 -0
  662. chemvas-0.1.0/tests/test_structure_benzene_build_service.py +140 -0
  663. chemvas-0.1.0/tests/test_structure_benzene_logic.py +168 -0
  664. chemvas-0.1.0/tests/test_structure_bond_build_service.py +83 -0
  665. chemvas-0.1.0/tests/test_structure_build_service.py +2821 -0
  666. chemvas-0.1.0/tests/test_structure_geometry_logic.py +353 -0
  667. chemvas-0.1.0/tests/test_structure_growth_build_service.py +171 -0
  668. chemvas-0.1.0/tests/test_structure_growth_logic.py +80 -0
  669. chemvas-0.1.0/tests/test_structure_insert_service.py +625 -0
  670. chemvas-0.1.0/tests/test_structure_payload_logic.py +195 -0
  671. chemvas-0.1.0/tests/test_structure_service_bundle.py +64 -0
  672. chemvas-0.1.0/tests/test_structure_template_build_service.py +109 -0
  673. chemvas-0.1.0/tests/test_structure_template_commands.py +161 -0
  674. chemvas-0.1.0/tests/test_svg_roundtrip.py +513 -0
  675. chemvas-0.1.0/tests/test_template_geometry_resolver_service.py +77 -0
  676. chemvas-0.1.0/tests/test_template_insert_logic.py +304 -0
  677. chemvas-0.1.0/tests/test_template_preview_logic.py +107 -0
  678. chemvas-0.1.0/tests/test_text_tool_logic.py +125 -0
  679. chemvas-0.1.0/tests/test_tool_context.py +870 -0
  680. chemvas-0.1.0/tests/test_tool_overlay_logic.py +107 -0
  681. chemvas-0.1.0/tests/test_tool_service_bundle.py +65 -0
  682. chemvas-0.1.0/tests/test_tools_additional.py +1330 -0
  683. chemvas-0.1.0/tests/test_tools_unit.py +3139 -0
  684. chemvas-0.1.0/tests/test_ui_history_command_atomicity.py +4972 -0
  685. chemvas-0.1.0/tests/test_ui_service_tail_coverage.py +499 -0
chemvas-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 daehyupsohn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
chemvas-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: chemvas
3
+ Version: 0.1.0
4
+ Summary: A PyQt-based chemical drawing canvas.
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: Pillow>=10
9
+ Requires-Dist: PyQt6>=6.7
10
+ Provides-Extra: dev
11
+ Requires-Dist: coverage[toml]>=7; extra == "dev"
12
+ Requires-Dist: mypy>=1.19; extra == "dev"
13
+ Requires-Dist: pytest>=8; extra == "dev"
14
+ Requires-Dist: ruff>=0.8; extra == "dev"
15
+ Provides-Extra: rdkit
16
+ Requires-Dist: rdkit>=2024.9; extra == "rdkit"
17
+ Dynamic: license-file
18
+
19
+ <p align="center">
20
+ <img src="docs/images/banner.png" alt="Chemvas — 2D chemical structure drawing canvas" width="680">
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://github.com/dhsohn/Chemvas/actions/workflows/ci.yml"><img src="https://github.com/dhsohn/Chemvas/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
25
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
26
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg" alt="Python 3.12+"></a>
27
+ </p>
28
+
29
+ <p align="center"><b>English</b> · <a href="README.ko.md">한국어</a></p>
30
+
31
+ A lightweight, PyQt6-based 2D chemical structure drawing app for quickly sketching
32
+ molecules and reaction schemes — and exporting publication-ready figures.
33
+
34
+ ![Chemvas — a Base/THF reaction scheme and several organocatalyst structures drawn on the canvas](docs/images/demo.png)
35
+
36
+ Chemvas lets you combine molecular bonds/rings/labels, arrows, and bracket
37
+ annotations on a single canvas. The default style follows the ACS 1996 conventions,
38
+ and the goal is to draft figures for lab notebooks or papers fast. RDKit is an
39
+ optional backend used for SMILES import, formula/weight calculation, and 2D→3D
40
+ conversion — Chemvas runs without it.
41
+
42
+ ## Features
43
+
44
+ - **Bonds** — single / double / triple, bold, wedge & hash; 30° angle snapping and
45
+ a consistent default bond length.
46
+ - **Rings & templates** — benzene, cycloalkanes, chair/boat conformers placed by
47
+ live preview and click-to-insert.
48
+ - **Arrows** — reaction, equilibrium, resonance, curved, and dashed arrows with
49
+ adjustable width and head scale.
50
+ - **Brackets & annotations** — square / round / curly brackets, dagger (`†`) and
51
+ double dagger (`‡`) annotation objects.
52
+ - **Atom labels** — elements, charges, radicals, and common alias labels
53
+ (`Me`, `Et`, `OH`, `Ph`, `OMe`, `Boc`, `CO2Me`, `t-Bu`, `i-Pr`).
54
+ - **SMILES import** _(RDKit)_ — type a SMILES string, preview it under the cursor,
55
+ and click to place it on the canvas.
56
+ - **Molecule Info window** _(RDKit)_ — 3D preview (drag to rotate, scroll to zoom)
57
+ plus molecular formula and weight for the current selection.
58
+ - **Figure export** — plain SVG / PDF / PNG / TIFF with outlined glyphs (so screen,
59
+ vector, and raster output never diverge) and deterministic physical sizing
60
+ (bond-length or 84 / 174 mm column fit), independent of zoom. Editable Chemvas
61
+ SVG is opt-in and embeds the source document in SVG metadata.
62
+ - **2D→3D `.xyz` export** _(RDKit)_ — convert the current molecule or atom/bond
63
+ selection into 3D coordinates; charges/radicals and wedge/hash stereo are carried
64
+ through, and common alias labels are expanded into explicit fragments.
65
+ - **Editing** — select / move, horizontal & vertical flip, perspective rotation,
66
+ and delta-based undo/redo.
67
+ - **ChemDraw-compatible shortcuts** — a substantial subset (see below).
68
+ - **Save / load** — `.chemvas` JSON documents preserve the full working state.
69
+
70
+ ## Install
71
+
72
+ Requires **Python 3.12+** and **PyQt6**.
73
+
74
+ ```bash
75
+ # from a clone of this repo
76
+ python -m pip install -e .
77
+
78
+ # optional: enable SMILES / formula / 3D features
79
+ python -m pip install -e ".[rdkit]"
80
+ ```
81
+
82
+ > A PyPI release and prebuilt binaries are on the roadmap (see below). For now,
83
+ > install from source.
84
+
85
+ ## Running
86
+
87
+ ```bash
88
+ python app/main.py # development tree
89
+ chemvas # after install
90
+ ```
91
+
92
+ Pick a tool from the left toolbar and click/drag on the canvas to draw. Enter a
93
+ SMILES string in the top input and press **Render** to enter placement mode: move
94
+ the mouse to preview, click to insert, `Esc` to cancel. Templates work the same
95
+ preview-and-click way.
96
+
97
+ ## Examples
98
+
99
+ Open [`examples/template1.chemvas`](examples/template1.chemvas) via **File ▸ Open** to
100
+ explore the document shown above — a reaction scheme plus several organocatalyst
101
+ structures.
102
+
103
+ ## File format
104
+
105
+ File ▸ Save / Open works with `.chemvas` files — a JSON-based format holding the
106
+ molecule model, annotations, arrows, bracket annotations, and settings:
107
+
108
+ ```json
109
+ { "type": "chemvas", "version": 1, "state": { /* ... */ } }
110
+ ```
111
+
112
+ Figure export defaults to plain SVG without Chemvas source metadata. Choose
113
+ **Editable Chemvas SVG** only when you want the SVG to carry the original
114
+ document payload for round-tripping back into Chemvas.
115
+
116
+ ## 3D export & Molecule Info
117
+
118
+ - Export scope is the current chemical graph or the current atom/bond selection.
119
+ Arrows, bracket annotations, and free text are **not** included in `.xyz`.
120
+ - `+`/`-`/radical marks become formal charges / radical electrons; wedge/hash bonds
121
+ on single bonds become RDKit stereochemistry hints.
122
+ - Unsupported labels, mis-connected aliases, and invalid wedge/hash use fail with an
123
+ explicit error message instead of guessing.
124
+ - `.xyz` stores element symbols and 3D coordinates only — it is **not** a full
125
+ round-trip of bond orders, stereochemistry, or reaction semantics.
126
+
127
+ ## Keyboard shortcuts
128
+
129
+ Chemvas supports a major subset of ChemDraw-compatible shortcuts.
130
+
131
+ - **Empty canvas (tool hotkeys):** Select/Marquee `Space`, Bond `X`, Atom/Text `T`,
132
+ Arrow `E`, Benzene `J`, Brackets `Shift+T`, Orbitals `Shift+G`,
133
+ Chemical symbols `Shift+E`, Perspective `Alt+D`
134
+ - **Atom hotkeys (hover over an atom):** element/alias labels
135
+ `c n o s p f h b i l m e r x d` and `Shift+f/p/a/b/s/n/e/z/m/l/o/q/h/y`, charge `+`/`-`,
136
+ edit label `Enter`, sprout `0/1/2/3/a/4/5/6/7/8/9/z/v/u` (`9` = gem-dimethyl)
137
+ - **Bond hotkeys (hover over a bond):** Single `1`, Double `2`, Triple `3`,
138
+ Bold `b`/`Shift+B`, Wedge `w`, Hash `h`/`Shift+H`, Dashed `d`/`Shift+D`,
139
+ double-bond position `l`/`c`/`r`, Benzene fusion `a`,
140
+ Ring fusion `4/5/6/7/8`, Chair fusion `9/0`
141
+ - **Objects:** Flip Horizontal `Ctrl+Shift+H`, Flip Vertical `Ctrl+Shift+V`,
142
+ Rotate selection `Alt+Up/Down` (15°) and `Alt+Left/Right` (1°),
143
+ Nudge selection `Shift+Arrows` (10 pt)
144
+ - **View:** Actual size `F5`, Fit to window `F6`, Magnify `F7`, Reduce `F8`
145
+ - **File / edit:** Save / Open / Undo / Redo (platform defaults), `Ctrl+A` (select
146
+ all, switches to the Select tool), `Ctrl+C` (copy
147
+ selection as image), `Ctrl+X` (cut selection), `Ctrl+G` / `Ctrl+Shift+G`
148
+ (group / ungroup selection), `Delete`/`Backspace` (delete
149
+ selection, or edit/delete the hovered atom/bond), `Esc` (cancel template /
150
+ SMILES insertion)
151
+
152
+ ## Development
153
+
154
+ CI runs `ruff`, `mypy`, and the test suite headlessly (`QT_QPA_PLATFORM=offscreen`).
155
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, how to run the tests, and the
156
+ **architecture conventions** — the codebase is split into many small
157
+ `*_ports` / `*_access` / `*_state` / `*_service` modules **on purpose**, and that
158
+ boundary is enforced by a test. Read CONTRIBUTING before restructuring anything.
159
+
160
+ The high-level design is in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
161
+
162
+ ## Roadmap / not yet supported
163
+
164
+ These are known gaps, not bugs — contributions welcome:
165
+
166
+ - **Bond-aware interchange:** MOL / SDF **import**, and SDF (multi-molecule) export.
167
+ `.mol` export, SMILES export ("copy as SMILES"), and InChI / InChIKey have landed.
168
+ - **Vector clipboard:** `Ctrl+C` currently copies a PNG only; PDF/SVG clipboard
169
+ flavors (for pasting into Illustrator / Office as vector) are planned.
170
+ - **Distribution:** PyPI release and one-file desktop binaries.
171
+ - **Multi-molecule / reaction-scheme 3D export** and richer template libraries.
172
+
173
+ ## License
174
+
175
+ [MIT License](LICENSE)
@@ -0,0 +1,157 @@
1
+ <p align="center">
2
+ <img src="docs/images/banner.png" alt="Chemvas — 2D chemical structure drawing canvas" width="680">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/dhsohn/Chemvas/actions/workflows/ci.yml"><img src="https://github.com/dhsohn/Chemvas/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
7
+ <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
8
+ <a href="https://www.python.org/"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg" alt="Python 3.12+"></a>
9
+ </p>
10
+
11
+ <p align="center"><b>English</b> · <a href="README.ko.md">한국어</a></p>
12
+
13
+ A lightweight, PyQt6-based 2D chemical structure drawing app for quickly sketching
14
+ molecules and reaction schemes — and exporting publication-ready figures.
15
+
16
+ ![Chemvas — a Base/THF reaction scheme and several organocatalyst structures drawn on the canvas](docs/images/demo.png)
17
+
18
+ Chemvas lets you combine molecular bonds/rings/labels, arrows, and bracket
19
+ annotations on a single canvas. The default style follows the ACS 1996 conventions,
20
+ and the goal is to draft figures for lab notebooks or papers fast. RDKit is an
21
+ optional backend used for SMILES import, formula/weight calculation, and 2D→3D
22
+ conversion — Chemvas runs without it.
23
+
24
+ ## Features
25
+
26
+ - **Bonds** — single / double / triple, bold, wedge & hash; 30° angle snapping and
27
+ a consistent default bond length.
28
+ - **Rings & templates** — benzene, cycloalkanes, chair/boat conformers placed by
29
+ live preview and click-to-insert.
30
+ - **Arrows** — reaction, equilibrium, resonance, curved, and dashed arrows with
31
+ adjustable width and head scale.
32
+ - **Brackets & annotations** — square / round / curly brackets, dagger (`†`) and
33
+ double dagger (`‡`) annotation objects.
34
+ - **Atom labels** — elements, charges, radicals, and common alias labels
35
+ (`Me`, `Et`, `OH`, `Ph`, `OMe`, `Boc`, `CO2Me`, `t-Bu`, `i-Pr`).
36
+ - **SMILES import** _(RDKit)_ — type a SMILES string, preview it under the cursor,
37
+ and click to place it on the canvas.
38
+ - **Molecule Info window** _(RDKit)_ — 3D preview (drag to rotate, scroll to zoom)
39
+ plus molecular formula and weight for the current selection.
40
+ - **Figure export** — plain SVG / PDF / PNG / TIFF with outlined glyphs (so screen,
41
+ vector, and raster output never diverge) and deterministic physical sizing
42
+ (bond-length or 84 / 174 mm column fit), independent of zoom. Editable Chemvas
43
+ SVG is opt-in and embeds the source document in SVG metadata.
44
+ - **2D→3D `.xyz` export** _(RDKit)_ — convert the current molecule or atom/bond
45
+ selection into 3D coordinates; charges/radicals and wedge/hash stereo are carried
46
+ through, and common alias labels are expanded into explicit fragments.
47
+ - **Editing** — select / move, horizontal & vertical flip, perspective rotation,
48
+ and delta-based undo/redo.
49
+ - **ChemDraw-compatible shortcuts** — a substantial subset (see below).
50
+ - **Save / load** — `.chemvas` JSON documents preserve the full working state.
51
+
52
+ ## Install
53
+
54
+ Requires **Python 3.12+** and **PyQt6**.
55
+
56
+ ```bash
57
+ # from a clone of this repo
58
+ python -m pip install -e .
59
+
60
+ # optional: enable SMILES / formula / 3D features
61
+ python -m pip install -e ".[rdkit]"
62
+ ```
63
+
64
+ > A PyPI release and prebuilt binaries are on the roadmap (see below). For now,
65
+ > install from source.
66
+
67
+ ## Running
68
+
69
+ ```bash
70
+ python app/main.py # development tree
71
+ chemvas # after install
72
+ ```
73
+
74
+ Pick a tool from the left toolbar and click/drag on the canvas to draw. Enter a
75
+ SMILES string in the top input and press **Render** to enter placement mode: move
76
+ the mouse to preview, click to insert, `Esc` to cancel. Templates work the same
77
+ preview-and-click way.
78
+
79
+ ## Examples
80
+
81
+ Open [`examples/template1.chemvas`](examples/template1.chemvas) via **File ▸ Open** to
82
+ explore the document shown above — a reaction scheme plus several organocatalyst
83
+ structures.
84
+
85
+ ## File format
86
+
87
+ File ▸ Save / Open works with `.chemvas` files — a JSON-based format holding the
88
+ molecule model, annotations, arrows, bracket annotations, and settings:
89
+
90
+ ```json
91
+ { "type": "chemvas", "version": 1, "state": { /* ... */ } }
92
+ ```
93
+
94
+ Figure export defaults to plain SVG without Chemvas source metadata. Choose
95
+ **Editable Chemvas SVG** only when you want the SVG to carry the original
96
+ document payload for round-tripping back into Chemvas.
97
+
98
+ ## 3D export & Molecule Info
99
+
100
+ - Export scope is the current chemical graph or the current atom/bond selection.
101
+ Arrows, bracket annotations, and free text are **not** included in `.xyz`.
102
+ - `+`/`-`/radical marks become formal charges / radical electrons; wedge/hash bonds
103
+ on single bonds become RDKit stereochemistry hints.
104
+ - Unsupported labels, mis-connected aliases, and invalid wedge/hash use fail with an
105
+ explicit error message instead of guessing.
106
+ - `.xyz` stores element symbols and 3D coordinates only — it is **not** a full
107
+ round-trip of bond orders, stereochemistry, or reaction semantics.
108
+
109
+ ## Keyboard shortcuts
110
+
111
+ Chemvas supports a major subset of ChemDraw-compatible shortcuts.
112
+
113
+ - **Empty canvas (tool hotkeys):** Select/Marquee `Space`, Bond `X`, Atom/Text `T`,
114
+ Arrow `E`, Benzene `J`, Brackets `Shift+T`, Orbitals `Shift+G`,
115
+ Chemical symbols `Shift+E`, Perspective `Alt+D`
116
+ - **Atom hotkeys (hover over an atom):** element/alias labels
117
+ `c n o s p f h b i l m e r x d` and `Shift+f/p/a/b/s/n/e/z/m/l/o/q/h/y`, charge `+`/`-`,
118
+ edit label `Enter`, sprout `0/1/2/3/a/4/5/6/7/8/9/z/v/u` (`9` = gem-dimethyl)
119
+ - **Bond hotkeys (hover over a bond):** Single `1`, Double `2`, Triple `3`,
120
+ Bold `b`/`Shift+B`, Wedge `w`, Hash `h`/`Shift+H`, Dashed `d`/`Shift+D`,
121
+ double-bond position `l`/`c`/`r`, Benzene fusion `a`,
122
+ Ring fusion `4/5/6/7/8`, Chair fusion `9/0`
123
+ - **Objects:** Flip Horizontal `Ctrl+Shift+H`, Flip Vertical `Ctrl+Shift+V`,
124
+ Rotate selection `Alt+Up/Down` (15°) and `Alt+Left/Right` (1°),
125
+ Nudge selection `Shift+Arrows` (10 pt)
126
+ - **View:** Actual size `F5`, Fit to window `F6`, Magnify `F7`, Reduce `F8`
127
+ - **File / edit:** Save / Open / Undo / Redo (platform defaults), `Ctrl+A` (select
128
+ all, switches to the Select tool), `Ctrl+C` (copy
129
+ selection as image), `Ctrl+X` (cut selection), `Ctrl+G` / `Ctrl+Shift+G`
130
+ (group / ungroup selection), `Delete`/`Backspace` (delete
131
+ selection, or edit/delete the hovered atom/bond), `Esc` (cancel template /
132
+ SMILES insertion)
133
+
134
+ ## Development
135
+
136
+ CI runs `ruff`, `mypy`, and the test suite headlessly (`QT_QPA_PLATFORM=offscreen`).
137
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, how to run the tests, and the
138
+ **architecture conventions** — the codebase is split into many small
139
+ `*_ports` / `*_access` / `*_state` / `*_service` modules **on purpose**, and that
140
+ boundary is enforced by a test. Read CONTRIBUTING before restructuring anything.
141
+
142
+ The high-level design is in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
143
+
144
+ ## Roadmap / not yet supported
145
+
146
+ These are known gaps, not bugs — contributions welcome:
147
+
148
+ - **Bond-aware interchange:** MOL / SDF **import**, and SDF (multi-molecule) export.
149
+ `.mol` export, SMILES export ("copy as SMILES"), and InChI / InChIKey have landed.
150
+ - **Vector clipboard:** `Ctrl+C` currently copies a PNG only; PDF/SVG clipboard
151
+ flavors (for pasting into Illustrator / Office as vector) are planned.
152
+ - **Distribution:** PyPI release and one-file desktop binaries.
153
+ - **Multi-molecule / reaction-scheme 3D export** and richer template libraries.
154
+
155
+ ## License
156
+
157
+ [MIT License](LICENSE)
@@ -0,0 +1,4 @@
1
+ """Chemvas application package."""
2
+
3
+ __version__ = "0.1.0"
4
+
@@ -0,0 +1,5 @@
1
+ from chemvas.main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
5
+
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
2
+ <rect x="88" y="88" width="848" height="848" rx="190" fill="#0d9488"/>
3
+ <polygon points="512,277 715.5,394.5 715.5,629.5 512,747 308.5,629.5 308.5,394.5"
4
+ fill="none" stroke="#ffffff" stroke-width="46" stroke-linejoin="round"/>
5
+ <circle cx="512" cy="512" r="112" fill="none" stroke="#ffffff" stroke-width="36"/>
6
+ </svg>
@@ -0,0 +1,40 @@
1
+ """Application branding: identity strings and the window/app icon.
2
+
3
+ The icon assets live in ``assets/icon`` inside this package (so they ship in the
4
+ wheel) and are regenerated from ``chemvas.svg`` by ``scripts/generate_icons.py``.
5
+ ``app_icon`` assembles a multi-resolution ``QIcon`` from the runtime PNG set,
6
+ falling back to the master SVG if the PNGs are missing. A ``QApplication`` must
7
+ already exist before this is called.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+ from PyQt6.QtCore import QSize
15
+ from PyQt6.QtGui import QIcon
16
+
17
+ from chemvas import __version__
18
+
19
+ APP_NAME = "Chemvas"
20
+ APP_VERSION = __version__
21
+
22
+ _ICON_DIR = Path(__file__).resolve().parent / "assets" / "icon"
23
+ _ICON_PNG_SIZES = (16, 32, 64, 128, 256, 512)
24
+
25
+
26
+ def app_icon() -> QIcon:
27
+ """A multi-resolution app icon, or an empty ``QIcon`` if assets are absent."""
28
+ icon = QIcon()
29
+ for size in _ICON_PNG_SIZES:
30
+ png = _ICON_DIR / f"chemvas-{size}.png"
31
+ if png.exists():
32
+ icon.addFile(str(png), QSize(size, size))
33
+ if icon.isNull():
34
+ svg = _ICON_DIR / "chemvas.svg"
35
+ if svg.exists():
36
+ icon.addFile(str(svg))
37
+ return icon
38
+
39
+
40
+ __all__ = ["APP_NAME", "APP_VERSION", "app_icon"]
@@ -0,0 +1,60 @@
1
+ """macOS 'open document' handling.
2
+
3
+ On macOS, double-clicking a ``.chemvas`` file (or dropping it on the Dock icon)
4
+ delivers a ``QEvent.FileOpen`` Apple Event rather than a command-line argument;
5
+ Windows and Linux pass the path in ``argv``, which ``main()`` reads directly via
6
+ ``_startup_document_path``. Install :class:`FileOpenEventFilter` on the
7
+ ``QApplication`` (with :func:`open_document` as its handler) so those events reach
8
+ the document loader too.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Callable
14
+ from typing import cast
15
+
16
+ from PyQt6.QtCore import QEvent, QObject
17
+ from PyQt6.QtGui import QFileOpenEvent
18
+
19
+
20
+ class FileOpenEventFilter(QObject):
21
+ def __init__(self, handler: Callable[[str], None]) -> None:
22
+ super().__init__()
23
+ self._handler = handler
24
+
25
+ def eventFilter(self, obj: QObject | None, event: QEvent | None) -> bool:
26
+ if event is not None and event.type() == QEvent.Type.FileOpen:
27
+ path = cast(QFileOpenEvent, event).file()
28
+ if path:
29
+ self._handler(path)
30
+ return True
31
+ return False
32
+
33
+
34
+ def open_document(path: str) -> None:
35
+ """Open ``path`` honoring Chemvas's single-document-per-window model.
36
+
37
+ Reuses a blank, unsaved, single-canvas window (the empty window opened at
38
+ startup) when one is available, so a cold-start file open doesn't leave an
39
+ extra empty window behind. Otherwise opens the document in a fresh window —
40
+ matching the File ▸ Open action rather than adding a tab to an existing,
41
+ already-occupied window. The destination window is resolved only after the
42
+ file reads successfully, so an unreadable file never spawns an empty window.
43
+ """
44
+ from ui.main_window_app import open_new_window, open_windows
45
+ from ui.main_window_ports import services_for_window
46
+
47
+ windows = open_windows()
48
+ reference = windows[-1] if windows else open_new_window()
49
+ services = services_for_window(reference)
50
+ documents = services.canvas_document_service
51
+
52
+ def target_provider() -> object:
53
+ if documents.reusable_open_target(reference) is not None:
54
+ return reference
55
+ return open_new_window(reference)
56
+
57
+ services.document_action_service.load_canvas_from_path(reference, path, target_provider=target_provider)
58
+
59
+
60
+ __all__ = ["FileOpenEventFilter", "open_document"]
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ import threading
6
+ from collections.abc import Iterator
7
+ from contextlib import contextmanager
8
+ from pathlib import Path
9
+
10
+ IGNORED_STDERR_SUBSTRINGS = (
11
+ "TSM AdjustCapsLockLEDForKeyTransitionHandling",
12
+ "error messaging the mach port for IMKCFRunLoopWakeUpReliable",
13
+ "qt.qpa.keymapper: Mismatch between Cocoa",
14
+ )
15
+
16
+ STARTUP_DOCUMENT_SUFFIXES = frozenset((".chemvas", ".json", ".svg"))
17
+
18
+
19
+ def _should_filter_stderr(platform: str | None = None) -> bool:
20
+ return (platform or sys.platform) == "darwin"
21
+
22
+
23
+ def _startup_document_path(argv: list[str]) -> str | None:
24
+ for argument in argv[1:]:
25
+ if argument.startswith("-"):
26
+ continue
27
+ if Path(argument).suffix.lower() in STARTUP_DOCUMENT_SUFFIXES:
28
+ return argument
29
+ return None
30
+
31
+
32
+ def _stderr_filter_loop(
33
+ read_fd: int,
34
+ write_fd: int,
35
+ ignored_substrings: tuple[str, ...] = IGNORED_STDERR_SUBSTRINGS,
36
+ ) -> None:
37
+ with os.fdopen(read_fd, "r", buffering=1) as reader, os.fdopen(write_fd, "w", buffering=1) as writer:
38
+ for line in reader:
39
+ if any(fragment in line for fragment in ignored_substrings):
40
+ continue
41
+ writer.write(line)
42
+ writer.flush()
43
+
44
+
45
+ @contextmanager
46
+ def _filtered_stderr(stderr_fd: int = 2, platform: str | None = None) -> Iterator[None]:
47
+ if not _should_filter_stderr(platform):
48
+ yield
49
+ return
50
+
51
+ restore_stderr_fd = os.dup(stderr_fd)
52
+ forward_stderr_fd = os.dup(stderr_fd)
53
+ read_fd, write_fd = os.pipe()
54
+ thread = threading.Thread(target=_stderr_filter_loop, args=(read_fd, forward_stderr_fd), daemon=True)
55
+ os.dup2(write_fd, stderr_fd)
56
+ os.close(write_fd)
57
+ thread.start()
58
+ try:
59
+ yield
60
+ finally:
61
+ os.dup2(restore_stderr_fd, stderr_fd)
62
+ os.close(restore_stderr_fd)
63
+ thread.join(timeout=1.0)
64
+
65
+
66
+ def main() -> None:
67
+ with _filtered_stderr():
68
+ from PyQt6.QtWidgets import QApplication
69
+ from ui.main_window_app import open_new_window
70
+
71
+ from chemvas.branding import APP_NAME, APP_VERSION, app_icon
72
+ from chemvas.file_open import FileOpenEventFilter, open_document
73
+
74
+ app = QApplication(sys.argv)
75
+ app.setApplicationName(APP_NAME)
76
+ app.setApplicationDisplayName(APP_NAME)
77
+ app.setApplicationVersion(APP_VERSION)
78
+ app.setOrganizationName(APP_NAME)
79
+ app.setDesktopFileName("chemvas")
80
+ app.setWindowIcon(app_icon())
81
+
82
+ file_open_filter = FileOpenEventFilter(open_document)
83
+ app.installEventFilter(file_open_filter)
84
+
85
+ window = open_new_window()
86
+ startup_document_path = _startup_document_path(sys.argv)
87
+ if startup_document_path is not None:
88
+ from ui.main_window_ports import services_for_window
89
+
90
+ services_for_window(window).document_action_service.load_canvas_from_path(window, startup_document_path)
91
+ app.exec()