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.
- chemvas-0.1.0/LICENSE +21 -0
- chemvas-0.1.0/PKG-INFO +175 -0
- chemvas-0.1.0/README.md +157 -0
- chemvas-0.1.0/app/chemvas/__init__.py +4 -0
- chemvas-0.1.0/app/chemvas/__main__.py +5 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-128.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-16.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-256.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-32.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-512.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas-64.png +0 -0
- chemvas-0.1.0/app/chemvas/assets/icon/chemvas.svg +6 -0
- chemvas-0.1.0/app/chemvas/branding.py +40 -0
- chemvas-0.1.0/app/chemvas/file_open.py +60 -0
- chemvas-0.1.0/app/chemvas/main.py +91 -0
- chemvas-0.1.0/app/chemvas.egg-info/PKG-INFO +175 -0
- chemvas-0.1.0/app/chemvas.egg-info/SOURCES.txt +683 -0
- chemvas-0.1.0/app/chemvas.egg-info/dependency_links.txt +1 -0
- chemvas-0.1.0/app/chemvas.egg-info/entry_points.txt +2 -0
- chemvas-0.1.0/app/chemvas.egg-info/requires.txt +11 -0
- chemvas-0.1.0/app/chemvas.egg-info/top_level.txt +3 -0
- chemvas-0.1.0/app/core/__init__.py +0 -0
- chemvas-0.1.0/app/core/bond_tool_logic.py +109 -0
- chemvas-0.1.0/app/core/document_io.py +110 -0
- chemvas-0.1.0/app/core/document_state.py +1543 -0
- chemvas-0.1.0/app/core/history.py +1605 -0
- chemvas-0.1.0/app/core/model.py +81 -0
- chemvas-0.1.0/app/core/molfile.py +233 -0
- chemvas-0.1.0/app/core/perspective_drag_logic.py +57 -0
- chemvas-0.1.0/app/core/rdkit_adapter.py +206 -0
- chemvas-0.1.0/app/core/rdkit_conversion.py +809 -0
- chemvas-0.1.0/app/core/rdkit_import.py +188 -0
- chemvas-0.1.0/app/core/rdkit_types.py +48 -0
- chemvas-0.1.0/app/core/renderer.py +73 -0
- chemvas-0.1.0/app/core/style_acs1996.py +31 -0
- chemvas-0.1.0/app/core/svg_roundtrip.py +247 -0
- chemvas-0.1.0/app/core/template_geometry.py +322 -0
- chemvas-0.1.0/app/core/text_tool_logic.py +127 -0
- chemvas-0.1.0/app/core/tool_overlay_logic.py +22 -0
- chemvas-0.1.0/app/ui/__init__.py +0 -0
- chemvas-0.1.0/app/ui/active_tool_reference.py +19 -0
- chemvas-0.1.0/app/ui/atom_coords_access.py +81 -0
- chemvas-0.1.0/app/ui/atom_label_access.py +112 -0
- chemvas-0.1.0/app/ui/atom_label_history_recorder.py +642 -0
- chemvas-0.1.0/app/ui/atom_label_merge_service.py +143 -0
- chemvas-0.1.0/app/ui/atom_label_service.py +353 -0
- chemvas-0.1.0/app/ui/benzene_preview_access.py +23 -0
- chemvas-0.1.0/app/ui/benzene_preview_renderer.py +108 -0
- chemvas-0.1.0/app/ui/benzene_preview_scene_access.py +28 -0
- chemvas-0.1.0/app/ui/benzene_preview_service.py +76 -0
- chemvas-0.1.0/app/ui/benzene_tool.py +21 -0
- chemvas-0.1.0/app/ui/bond_dotted_geometry.py +58 -0
- chemvas-0.1.0/app/ui/bond_geometry_primitives.py +137 -0
- chemvas-0.1.0/app/ui/bond_geometry_update_service.py +269 -0
- chemvas-0.1.0/app/ui/bond_graphics_access.py +241 -0
- chemvas-0.1.0/app/ui/bond_graphics_build_service.py +190 -0
- chemvas-0.1.0/app/ui/bond_graphics_draw_service.py +293 -0
- chemvas-0.1.0/app/ui/bond_graphics_factory.py +55 -0
- chemvas-0.1.0/app/ui/bond_graphics_logic.py +50 -0
- chemvas-0.1.0/app/ui/bond_hover_preview_service.py +81 -0
- chemvas-0.1.0/app/ui/bond_label_geometry_access.py +37 -0
- chemvas-0.1.0/app/ui/bond_length_graphics_refresh.py +235 -0
- chemvas-0.1.0/app/ui/bond_line_geometry_service.py +275 -0
- chemvas-0.1.0/app/ui/bond_preview_access.py +166 -0
- chemvas-0.1.0/app/ui/bond_preview_geometry.py +82 -0
- chemvas-0.1.0/app/ui/bond_preview_renderer.py +248 -0
- chemvas-0.1.0/app/ui/bond_preview_scene_items.py +55 -0
- chemvas-0.1.0/app/ui/bond_renderer.py +285 -0
- chemvas-0.1.0/app/ui/bond_renderer_access.py +5 -0
- chemvas-0.1.0/app/ui/bond_ring_double_geometry_service.py +244 -0
- chemvas-0.1.0/app/ui/bond_stereo_geometry.py +83 -0
- chemvas-0.1.0/app/ui/bond_style_logic.py +129 -0
- chemvas-0.1.0/app/ui/bond_tool.py +225 -0
- chemvas-0.1.0/app/ui/bracket_types.py +49 -0
- chemvas-0.1.0/app/ui/canvas_arrow_build_service.py +178 -0
- chemvas-0.1.0/app/ui/canvas_atom_graphics_state.py +85 -0
- chemvas-0.1.0/app/ui/canvas_atom_mutation_service.py +154 -0
- chemvas-0.1.0/app/ui/canvas_auxiliary_service_bundle.py +53 -0
- chemvas-0.1.0/app/ui/canvas_background_painter.py +31 -0
- chemvas-0.1.0/app/ui/canvas_bond_graphics_state.py +59 -0
- chemvas-0.1.0/app/ui/canvas_bond_mutation_service.py +101 -0
- chemvas-0.1.0/app/ui/canvas_bond_renderer_state.py +25 -0
- chemvas-0.1.0/app/ui/canvas_callback_state.py +23 -0
- chemvas-0.1.0/app/ui/canvas_chemdraw_shortcut_service.py +300 -0
- chemvas-0.1.0/app/ui/canvas_color_mutation_service.py +2430 -0
- chemvas-0.1.0/app/ui/canvas_delete_transaction.py +1655 -0
- chemvas-0.1.0/app/ui/canvas_document_export_access.py +34 -0
- chemvas-0.1.0/app/ui/canvas_document_metadata_state.py +72 -0
- chemvas-0.1.0/app/ui/canvas_document_service_bundle.py +51 -0
- chemvas-0.1.0/app/ui/canvas_document_session_service.py +2927 -0
- chemvas-0.1.0/app/ui/canvas_document_state.py +495 -0
- chemvas-0.1.0/app/ui/canvas_format_access.py +22 -0
- chemvas-0.1.0/app/ui/canvas_geometry_access.py +50 -0
- chemvas-0.1.0/app/ui/canvas_geometry_controller.py +549 -0
- chemvas-0.1.0/app/ui/canvas_geometry_logic.py +90 -0
- chemvas-0.1.0/app/ui/canvas_graph_service.py +322 -0
- chemvas-0.1.0/app/ui/canvas_graph_service_bundle.py +22 -0
- chemvas-0.1.0/app/ui/canvas_graph_state.py +41 -0
- chemvas-0.1.0/app/ui/canvas_group_state.py +75 -0
- chemvas-0.1.0/app/ui/canvas_handle_controller.py +124 -0
- chemvas-0.1.0/app/ui/canvas_history_recording_service.py +1146 -0
- chemvas-0.1.0/app/ui/canvas_history_service.py +1019 -0
- chemvas-0.1.0/app/ui/canvas_history_state.py +25 -0
- chemvas-0.1.0/app/ui/canvas_hit_testing_scene_access.py +18 -0
- chemvas-0.1.0/app/ui/canvas_hit_testing_service.py +241 -0
- chemvas-0.1.0/app/ui/canvas_hover_refresh.py +55 -0
- chemvas-0.1.0/app/ui/canvas_hover_state.py +70 -0
- chemvas-0.1.0/app/ui/canvas_input_controller.py +207 -0
- chemvas-0.1.0/app/ui/canvas_input_service_bundle.py +76 -0
- chemvas-0.1.0/app/ui/canvas_insert_state.py +33 -0
- chemvas-0.1.0/app/ui/canvas_interaction_service_bundle.py +51 -0
- chemvas-0.1.0/app/ui/canvas_lifecycle.py +38 -0
- chemvas-0.1.0/app/ui/canvas_mark_registry.py +34 -0
- chemvas-0.1.0/app/ui/canvas_mark_scene_service.py +100 -0
- chemvas-0.1.0/app/ui/canvas_model_access.py +226 -0
- chemvas-0.1.0/app/ui/canvas_model_state.py +18 -0
- chemvas-0.1.0/app/ui/canvas_move_controller.py +242 -0
- chemvas-0.1.0/app/ui/canvas_note_controller.py +1004 -0
- chemvas-0.1.0/app/ui/canvas_pointer_controller.py +277 -0
- chemvas-0.1.0/app/ui/canvas_rdkit_state.py +22 -0
- chemvas-0.1.0/app/ui/canvas_renderer_state.py +18 -0
- chemvas-0.1.0/app/ui/canvas_ring_fill_scene_access.py +55 -0
- chemvas-0.1.0/app/ui/canvas_ring_fill_scene_service.py +160 -0
- chemvas-0.1.0/app/ui/canvas_rotation_preview_controller.py +61 -0
- chemvas-0.1.0/app/ui/canvas_rotation_preview_state.py +36 -0
- chemvas-0.1.0/app/ui/canvas_rotation_state.py +61 -0
- chemvas-0.1.0/app/ui/canvas_runtime_state.py +142 -0
- chemvas-0.1.0/app/ui/canvas_scene_decoration_build_service.py +426 -0
- chemvas-0.1.0/app/ui/canvas_scene_items_state.py +135 -0
- chemvas-0.1.0/app/ui/canvas_scene_reset_access.py +10 -0
- chemvas-0.1.0/app/ui/canvas_scene_reset_service.py +2874 -0
- chemvas-0.1.0/app/ui/canvas_scene_state.py +15 -0
- chemvas-0.1.0/app/ui/canvas_scene_view_service_bundle.py +60 -0
- chemvas-0.1.0/app/ui/canvas_service_access.py +31 -0
- chemvas-0.1.0/app/ui/canvas_service_composer.py +180 -0
- chemvas-0.1.0/app/ui/canvas_service_ports.py +162 -0
- chemvas-0.1.0/app/ui/canvas_service_types.py +100 -0
- chemvas-0.1.0/app/ui/canvas_services.py +57 -0
- chemvas-0.1.0/app/ui/canvas_smiles_input_state.py +41 -0
- chemvas-0.1.0/app/ui/canvas_state_lookup.py +63 -0
- chemvas-0.1.0/app/ui/canvas_style_controller.py +164 -0
- chemvas-0.1.0/app/ui/canvas_text_style_state.py +61 -0
- chemvas-0.1.0/app/ui/canvas_tool_mode_controller.py +233 -0
- chemvas-0.1.0/app/ui/canvas_tool_settings_state.py +58 -0
- chemvas-0.1.0/app/ui/canvas_view.py +86 -0
- chemvas-0.1.0/app/ui/canvas_view_event_router.py +127 -0
- chemvas-0.1.0/app/ui/canvas_view_ports.py +31 -0
- chemvas-0.1.0/app/ui/canvas_view_setup.py +57 -0
- chemvas-0.1.0/app/ui/canvas_window_access.py +68 -0
- chemvas-0.1.0/app/ui/curved_arrow_path_service.py +43 -0
- chemvas-0.1.0/app/ui/delete_tool_logic.py +96 -0
- chemvas-0.1.0/app/ui/edit_tools.py +250 -0
- chemvas-0.1.0/app/ui/export_dialog_logic.py +99 -0
- chemvas-0.1.0/app/ui/export_painting.py +30 -0
- chemvas-0.1.0/app/ui/export_plan_logic.py +74 -0
- chemvas-0.1.0/app/ui/export_raster_renderer.py +69 -0
- chemvas-0.1.0/app/ui/export_render_service.py +161 -0
- chemvas-0.1.0/app/ui/export_scene_scope.py +105 -0
- chemvas-0.1.0/app/ui/export_vector_renderer.py +148 -0
- chemvas-0.1.0/app/ui/graph_algorithms.py +290 -0
- chemvas-0.1.0/app/ui/graph_index_operations.py +215 -0
- chemvas-0.1.0/app/ui/graph_rotation_policy.py +237 -0
- chemvas-0.1.0/app/ui/graphics_items.py +379 -0
- chemvas-0.1.0/app/ui/handle_interaction_logic.py +173 -0
- chemvas-0.1.0/app/ui/handle_mutation_access.py +121 -0
- chemvas-0.1.0/app/ui/handle_mutation_service.py +110 -0
- chemvas-0.1.0/app/ui/handle_overlay_access.py +27 -0
- chemvas-0.1.0/app/ui/handle_overlay_scene_access.py +15 -0
- chemvas-0.1.0/app/ui/handle_overlay_service.py +111 -0
- chemvas-0.1.0/app/ui/handle_service_bundle.py +43 -0
- chemvas-0.1.0/app/ui/handle_state.py +43 -0
- chemvas-0.1.0/app/ui/history_atom_position_restore.py +82 -0
- chemvas-0.1.0/app/ui/history_canvas_access.py +524 -0
- chemvas-0.1.0/app/ui/history_command_snapshot.py +381 -0
- chemvas-0.1.0/app/ui/history_commands.py +2968 -0
- chemvas-0.1.0/app/ui/history_push_failure_recovery.py +610 -0
- chemvas-0.1.0/app/ui/history_recording_access.py +30 -0
- chemvas-0.1.0/app/ui/history_recovery_note.py +26 -0
- chemvas-0.1.0/app/ui/history_restore_retry.py +59 -0
- chemvas-0.1.0/app/ui/history_stack_snapshot.py +416 -0
- chemvas-0.1.0/app/ui/hover_highlight_access.py +23 -0
- chemvas-0.1.0/app/ui/hover_highlight_logic.py +75 -0
- chemvas-0.1.0/app/ui/hover_interaction_access.py +91 -0
- chemvas-0.1.0/app/ui/hover_interaction_service.py +159 -0
- chemvas-0.1.0/app/ui/hover_scene_access.py +32 -0
- chemvas-0.1.0/app/ui/hover_scene_renderer.py +121 -0
- chemvas-0.1.0/app/ui/hover_scene_service.py +83 -0
- chemvas-0.1.0/app/ui/hover_service_bundle.py +78 -0
- chemvas-0.1.0/app/ui/input_view_access.py +444 -0
- chemvas-0.1.0/app/ui/input_view_state.py +26 -0
- chemvas-0.1.0/app/ui/insert_commit_rollback.py +255 -0
- chemvas-0.1.0/app/ui/insert_commit_service.py +122 -0
- chemvas-0.1.0/app/ui/insert_controller.py +191 -0
- chemvas-0.1.0/app/ui/insert_mode_logic.py +97 -0
- chemvas-0.1.0/app/ui/insert_session_access.py +47 -0
- chemvas-0.1.0/app/ui/insert_smiles_commit_service.py +218 -0
- chemvas-0.1.0/app/ui/insert_smiles_service.py +643 -0
- chemvas-0.1.0/app/ui/insert_smiles_transaction.py +211 -0
- chemvas-0.1.0/app/ui/insert_template_commit_service.py +201 -0
- chemvas-0.1.0/app/ui/insert_template_service.py +296 -0
- chemvas-0.1.0/app/ui/interaction_tools.py +150 -0
- chemvas-0.1.0/app/ui/label_layout_logic.py +297 -0
- chemvas-0.1.0/app/ui/main_window.py +50 -0
- chemvas-0.1.0/app/ui/main_window_about_dialog.py +122 -0
- chemvas-0.1.0/app/ui/main_window_action_availability_service.py +38 -0
- chemvas-0.1.0/app/ui/main_window_active_canvas_ui_service.py +120 -0
- chemvas-0.1.0/app/ui/main_window_app.py +90 -0
- chemvas-0.1.0/app/ui/main_window_arrow_icon_renderer.py +98 -0
- chemvas-0.1.0/app/ui/main_window_bond_icon_renderer.py +177 -0
- chemvas-0.1.0/app/ui/main_window_bootstrap.py +87 -0
- chemvas-0.1.0/app/ui/main_window_canvas_document_service.py +191 -0
- chemvas-0.1.0/app/ui/main_window_canvas_logic.py +115 -0
- chemvas-0.1.0/app/ui/main_window_canvas_tab_styles.py +54 -0
- chemvas-0.1.0/app/ui/main_window_canvas_tab_ui_service.py +19 -0
- chemvas-0.1.0/app/ui/main_window_chrome_styles.py +96 -0
- chemvas-0.1.0/app/ui/main_window_config.py +133 -0
- chemvas-0.1.0/app/ui/main_window_context_bar_page_factories.py +444 -0
- chemvas-0.1.0/app/ui/main_window_context_bar_pages.py +151 -0
- chemvas-0.1.0/app/ui/main_window_context_bar_service.py +265 -0
- chemvas-0.1.0/app/ui/main_window_context_bar_widgets.py +438 -0
- chemvas-0.1.0/app/ui/main_window_context_page_state_service.py +45 -0
- chemvas-0.1.0/app/ui/main_window_design_icon_renderer.py +204 -0
- chemvas-0.1.0/app/ui/main_window_document_action_service.py +391 -0
- chemvas-0.1.0/app/ui/main_window_document_dialogs.py +232 -0
- chemvas-0.1.0/app/ui/main_window_form_styles.py +145 -0
- chemvas-0.1.0/app/ui/main_window_icon_canvas_style.py +59 -0
- chemvas-0.1.0/app/ui/main_window_icon_factory.py +334 -0
- chemvas-0.1.0/app/ui/main_window_icon_geometry.py +111 -0
- chemvas-0.1.0/app/ui/main_window_icon_pixmap_factory.py +69 -0
- chemvas-0.1.0/app/ui/main_window_menu_bar.py +42 -0
- chemvas-0.1.0/app/ui/main_window_palette.py +45 -0
- chemvas-0.1.0/app/ui/main_window_panel_service.py +68 -0
- chemvas-0.1.0/app/ui/main_window_panel_toolbar.py +314 -0
- chemvas-0.1.0/app/ui/main_window_path_logic.py +25 -0
- chemvas-0.1.0/app/ui/main_window_ports.py +331 -0
- chemvas-0.1.0/app/ui/main_window_preview_window.py +56 -0
- chemvas-0.1.0/app/ui/main_window_scrollbar_styles.py +75 -0
- chemvas-0.1.0/app/ui/main_window_services.py +286 -0
- chemvas-0.1.0/app/ui/main_window_state.py +29 -0
- chemvas-0.1.0/app/ui/main_window_status_service.py +286 -0
- chemvas-0.1.0/app/ui/main_window_status_styles.py +59 -0
- chemvas-0.1.0/app/ui/main_window_stylesheet.py +26 -0
- chemvas-0.1.0/app/ui/main_window_tab_references.py +65 -0
- chemvas-0.1.0/app/ui/main_window_tab_setup.py +43 -0
- chemvas-0.1.0/app/ui/main_window_template_icon_renderer.py +84 -0
- chemvas-0.1.0/app/ui/main_window_text_style_service.py +63 -0
- chemvas-0.1.0/app/ui/main_window_theme.py +33 -0
- chemvas-0.1.0/app/ui/main_window_tool_action_service.py +100 -0
- chemvas-0.1.0/app/ui/main_window_tool_icon_renderer.py +365 -0
- chemvas-0.1.0/app/ui/main_window_tool_routing_service.py +133 -0
- chemvas-0.1.0/app/ui/main_window_tool_state_service.py +93 -0
- chemvas-0.1.0/app/ui/main_window_toolbar_buttons.py +189 -0
- chemvas-0.1.0/app/ui/main_window_toolbar_logic.py +118 -0
- chemvas-0.1.0/app/ui/main_window_toolbar_styles.py +139 -0
- chemvas-0.1.0/app/ui/main_window_ui_assembly_service.py +164 -0
- chemvas-0.1.0/app/ui/main_window_ui_references.py +53 -0
- chemvas-0.1.0/app/ui/main_window_utility_icon_renderer.py +117 -0
- chemvas-0.1.0/app/ui/mark_hover_preview_service.py +68 -0
- chemvas-0.1.0/app/ui/mark_item_access.py +101 -0
- chemvas-0.1.0/app/ui/move_access.py +65 -0
- chemvas-0.1.0/app/ui/move_tool.py +205 -0
- chemvas-0.1.0/app/ui/note_html_sanitizer.py +337 -0
- chemvas-0.1.0/app/ui/note_item.py +35 -0
- chemvas-0.1.0/app/ui/note_item_access.py +83 -0
- chemvas-0.1.0/app/ui/note_selection_box.py +16 -0
- chemvas-0.1.0/app/ui/perspective_tool.py +93 -0
- chemvas-0.1.0/app/ui/perspective_tool_controller.py +52 -0
- chemvas-0.1.0/app/ui/pick_radius_access.py +23 -0
- chemvas-0.1.0/app/ui/preview_3d.py +483 -0
- chemvas-0.1.0/app/ui/preview_3d_interaction.py +39 -0
- chemvas-0.1.0/app/ui/preview_3d_layout.py +65 -0
- chemvas-0.1.0/app/ui/preview_3d_molecule_renderer.py +75 -0
- chemvas-0.1.0/app/ui/preview_3d_painter.py +179 -0
- chemvas-0.1.0/app/ui/preview_3d_projection.py +102 -0
- chemvas-0.1.0/app/ui/preview_3d_renderer.py +246 -0
- chemvas-0.1.0/app/ui/preview_3d_state.py +113 -0
- chemvas-0.1.0/app/ui/preview_3d_worker.py +61 -0
- chemvas-0.1.0/app/ui/preview_scene_access.py +84 -0
- chemvas-0.1.0/app/ui/preview_scene_renderer.py +190 -0
- chemvas-0.1.0/app/ui/preview_tools.py +140 -0
- chemvas-0.1.0/app/ui/rdkit_adapter_access.py +56 -0
- chemvas-0.1.0/app/ui/rdkit_async_jobs.py +204 -0
- chemvas-0.1.0/app/ui/rdkit_export_job_state.py +205 -0
- chemvas-0.1.0/app/ui/renderer_style_access.py +135 -0
- chemvas-0.1.0/app/ui/ring_occupancy_logic.py +66 -0
- chemvas-0.1.0/app/ui/rotate_tool.py +38 -0
- chemvas-0.1.0/app/ui/scene_clipboard_access.py +145 -0
- chemvas-0.1.0/app/ui/scene_clipboard_controller.py +197 -0
- chemvas-0.1.0/app/ui/scene_clipboard_copy_io.py +95 -0
- chemvas-0.1.0/app/ui/scene_clipboard_copy_service.py +65 -0
- chemvas-0.1.0/app/ui/scene_clipboard_logic.py +241 -0
- chemvas-0.1.0/app/ui/scene_clipboard_paste_service.py +275 -0
- chemvas-0.1.0/app/ui/scene_clipboard_selection.py +78 -0
- chemvas-0.1.0/app/ui/scene_clipboard_state.py +19 -0
- chemvas-0.1.0/app/ui/scene_clipboard_transaction_logic.py +234 -0
- chemvas-0.1.0/app/ui/scene_decoration_access.py +138 -0
- chemvas-0.1.0/app/ui/scene_decoration_build_access.py +70 -0
- chemvas-0.1.0/app/ui/scene_decoration_service.py +201 -0
- chemvas-0.1.0/app/ui/scene_decoration_service_bundle.py +39 -0
- chemvas-0.1.0/app/ui/scene_delete_apply_logic.py +100 -0
- chemvas-0.1.0/app/ui/scene_delete_controller.py +1321 -0
- chemvas-0.1.0/app/ui/scene_delete_logic.py +143 -0
- chemvas-0.1.0/app/ui/scene_flip_geometry.py +105 -0
- chemvas-0.1.0/app/ui/scene_flip_grouping.py +122 -0
- chemvas-0.1.0/app/ui/scene_flip_state.py +118 -0
- chemvas-0.1.0/app/ui/scene_group_operations.py +523 -0
- chemvas-0.1.0/app/ui/scene_item_access.py +325 -0
- chemvas-0.1.0/app/ui/scene_item_attach_snapshot.py +1800 -0
- chemvas-0.1.0/app/ui/scene_item_controller.py +215 -0
- chemvas-0.1.0/app/ui/scene_item_lifecycle_service.py +290 -0
- chemvas-0.1.0/app/ui/scene_item_restore.py +259 -0
- chemvas-0.1.0/app/ui/scene_item_state.py +317 -0
- chemvas-0.1.0/app/ui/scene_item_state_serialization.py +285 -0
- chemvas-0.1.0/app/ui/scene_operation_service_bundle.py +73 -0
- chemvas-0.1.0/app/ui/scene_paste_apply_logic.py +180 -0
- chemvas-0.1.0/app/ui/scene_rect_snapshot.py +2540 -0
- chemvas-0.1.0/app/ui/scene_rotation_state.py +115 -0
- chemvas-0.1.0/app/ui/scene_selectability.py +10 -0
- chemvas-0.1.0/app/ui/scene_signal_blocking.py +187 -0
- chemvas-0.1.0/app/ui/scene_single_item_mutation_logic.py +267 -0
- chemvas-0.1.0/app/ui/scene_transform_apply_logic.py +122 -0
- chemvas-0.1.0/app/ui/scene_transform_controller.py +552 -0
- chemvas-0.1.0/app/ui/scene_transform_logic.py +29 -0
- chemvas-0.1.0/app/ui/select_all_access.py +53 -0
- chemvas-0.1.0/app/ui/select_tool.py +402 -0
- chemvas-0.1.0/app/ui/selection_access.py +71 -0
- chemvas-0.1.0/app/ui/selection_center_logic.py +45 -0
- chemvas-0.1.0/app/ui/selection_collection_access.py +289 -0
- chemvas-0.1.0/app/ui/selection_controller.py +160 -0
- chemvas-0.1.0/app/ui/selection_drag_tool.py +2764 -0
- chemvas-0.1.0/app/ui/selection_geometry_access.py +32 -0
- chemvas-0.1.0/app/ui/selection_highlight_styler.py +58 -0
- chemvas-0.1.0/app/ui/selection_hit_logic.py +190 -0
- chemvas-0.1.0/app/ui/selection_hit_test_service.py +97 -0
- chemvas-0.1.0/app/ui/selection_info_access.py +116 -0
- chemvas-0.1.0/app/ui/selection_info_state.py +36 -0
- chemvas-0.1.0/app/ui/selection_note_service.py +162 -0
- chemvas-0.1.0/app/ui/selection_outline_items.py +89 -0
- chemvas-0.1.0/app/ui/selection_outline_paths.py +129 -0
- chemvas-0.1.0/app/ui/selection_outline_service.py +299 -0
- chemvas-0.1.0/app/ui/selection_outline_state.py +43 -0
- chemvas-0.1.0/app/ui/selection_preference_service.py +98 -0
- chemvas-0.1.0/app/ui/selection_press_logic.py +33 -0
- chemvas-0.1.0/app/ui/selection_rotation_access.py +280 -0
- chemvas-0.1.0/app/ui/selection_rotation_controller.py +1154 -0
- chemvas-0.1.0/app/ui/selection_rotation_geometry.py +198 -0
- chemvas-0.1.0/app/ui/selection_rotation_history.py +37 -0
- chemvas-0.1.0/app/ui/selection_rotation_logic.py +51 -0
- chemvas-0.1.0/app/ui/selection_rotation_planarity.py +132 -0
- chemvas-0.1.0/app/ui/selection_rotation_preview_transaction.py +2572 -0
- chemvas-0.1.0/app/ui/selection_rotation_session.py +285 -0
- chemvas-0.1.0/app/ui/selection_scene_access.py +2417 -0
- chemvas-0.1.0/app/ui/selection_service_access.py +103 -0
- chemvas-0.1.0/app/ui/selection_service_bundle.py +73 -0
- chemvas-0.1.0/app/ui/selection_structure_service.py +135 -0
- chemvas-0.1.0/app/ui/selection_structure_targets.py +53 -0
- chemvas-0.1.0/app/ui/selection_style_access.py +112 -0
- chemvas-0.1.0/app/ui/selection_style_state.py +26 -0
- chemvas-0.1.0/app/ui/shape_geometry.py +83 -0
- chemvas-0.1.0/app/ui/sheet_setup_access.py +1011 -0
- chemvas-0.1.0/app/ui/sheet_setup_logic.py +64 -0
- chemvas-0.1.0/app/ui/sheet_setup_state.py +58 -0
- chemvas-0.1.0/app/ui/smiles_insert_logic.py +266 -0
- chemvas-0.1.0/app/ui/spatial_index_state.py +88 -0
- chemvas-0.1.0/app/ui/structure_benzene_build_service.py +111 -0
- chemvas-0.1.0/app/ui/structure_benzene_logic.py +48 -0
- chemvas-0.1.0/app/ui/structure_bond_build_service.py +200 -0
- chemvas-0.1.0/app/ui/structure_build_access.py +81 -0
- chemvas-0.1.0/app/ui/structure_build_committer.py +636 -0
- chemvas-0.1.0/app/ui/structure_build_service.py +323 -0
- chemvas-0.1.0/app/ui/structure_fragment_build_service.py +351 -0
- chemvas-0.1.0/app/ui/structure_geometry_access.py +251 -0
- chemvas-0.1.0/app/ui/structure_geometry_logic.py +233 -0
- chemvas-0.1.0/app/ui/structure_growth_build_actions.py +53 -0
- chemvas-0.1.0/app/ui/structure_growth_build_service.py +198 -0
- chemvas-0.1.0/app/ui/structure_growth_logic.py +104 -0
- chemvas-0.1.0/app/ui/structure_insert_access.py +384 -0
- chemvas-0.1.0/app/ui/structure_insert_service.py +219 -0
- chemvas-0.1.0/app/ui/structure_mutation_access.py +47 -0
- chemvas-0.1.0/app/ui/structure_payload_access.py +82 -0
- chemvas-0.1.0/app/ui/structure_payload_logic.py +206 -0
- chemvas-0.1.0/app/ui/structure_service_bundle.py +64 -0
- chemvas-0.1.0/app/ui/structure_template_build_service.py +148 -0
- chemvas-0.1.0/app/ui/structure_template_commands.py +130 -0
- chemvas-0.1.0/app/ui/template_geometry_resolver_service.py +114 -0
- chemvas-0.1.0/app/ui/template_insert_logic.py +193 -0
- chemvas-0.1.0/app/ui/template_preview_logic.py +106 -0
- chemvas-0.1.0/app/ui/text_tool.py +103 -0
- chemvas-0.1.0/app/ui/tool_base.py +38 -0
- chemvas-0.1.0/app/ui/tool_context.py +457 -0
- chemvas-0.1.0/app/ui/tool_controller.py +84 -0
- chemvas-0.1.0/app/ui/tool_service_bundle.py +71 -0
- chemvas-0.1.0/app/ui/tools.py +39 -0
- chemvas-0.1.0/pyproject.toml +82 -0
- chemvas-0.1.0/setup.cfg +4 -0
- chemvas-0.1.0/tests/test_active_tool_reference.py +26 -0
- chemvas-0.1.0/tests/test_architecture_boundaries.py +3501 -0
- chemvas-0.1.0/tests/test_atom_coords_access.py +78 -0
- chemvas-0.1.0/tests/test_atom_label_access.py +87 -0
- chemvas-0.1.0/tests/test_atom_label_service.py +1346 -0
- chemvas-0.1.0/tests/test_benzene_preview_renderer.py +222 -0
- chemvas-0.1.0/tests/test_benzene_preview_service.py +114 -0
- chemvas-0.1.0/tests/test_bond_dotted_geometry.py +50 -0
- chemvas-0.1.0/tests/test_bond_graphics_logic.py +136 -0
- chemvas-0.1.0/tests/test_bond_hover_preview_service.py +165 -0
- chemvas-0.1.0/tests/test_bond_preview_access.py +31 -0
- chemvas-0.1.0/tests/test_bond_preview_geometry.py +37 -0
- chemvas-0.1.0/tests/test_bond_preview_renderer.py +787 -0
- chemvas-0.1.0/tests/test_bond_renderer_unit.py +1018 -0
- chemvas-0.1.0/tests/test_bond_stereo_geometry.py +40 -0
- chemvas-0.1.0/tests/test_bond_style_logic.py +81 -0
- chemvas-0.1.0/tests/test_bond_tool_logic.py +290 -0
- chemvas-0.1.0/tests/test_canvas_arrow_build_service.py +196 -0
- chemvas-0.1.0/tests/test_canvas_atom_graphics_state.py +69 -0
- chemvas-0.1.0/tests/test_canvas_atom_mutation_service.py +357 -0
- chemvas-0.1.0/tests/test_canvas_auxiliary_service_bundle.py +58 -0
- chemvas-0.1.0/tests/test_canvas_background_painter.py +31 -0
- chemvas-0.1.0/tests/test_canvas_bond_graphics_state.py +55 -0
- chemvas-0.1.0/tests/test_canvas_bond_mutation_service.py +320 -0
- chemvas-0.1.0/tests/test_canvas_bond_renderer_state.py +31 -0
- chemvas-0.1.0/tests/test_canvas_chemdraw_shortcut_service.py +326 -0
- chemvas-0.1.0/tests/test_canvas_color_mutation_service.py +2961 -0
- chemvas-0.1.0/tests/test_canvas_document_groups.py +148 -0
- chemvas-0.1.0/tests/test_canvas_document_service_bundle.py +56 -0
- chemvas-0.1.0/tests/test_canvas_document_session_service.py +3956 -0
- chemvas-0.1.0/tests/test_canvas_document_state.py +481 -0
- chemvas-0.1.0/tests/test_canvas_format_access.py +19 -0
- chemvas-0.1.0/tests/test_canvas_geometry_controller.py +201 -0
- chemvas-0.1.0/tests/test_canvas_geometry_logic.py +81 -0
- chemvas-0.1.0/tests/test_canvas_graph_service.py +474 -0
- chemvas-0.1.0/tests/test_canvas_graph_service_bundle.py +36 -0
- chemvas-0.1.0/tests/test_canvas_group_state.py +87 -0
- chemvas-0.1.0/tests/test_canvas_handle_controller.py +140 -0
- chemvas-0.1.0/tests/test_canvas_history_recording_service.py +1541 -0
- chemvas-0.1.0/tests/test_canvas_hit_testing_service.py +234 -0
- chemvas-0.1.0/tests/test_canvas_hover_state.py +45 -0
- chemvas-0.1.0/tests/test_canvas_input_controller.py +468 -0
- chemvas-0.1.0/tests/test_canvas_input_service_bundle.py +86 -0
- chemvas-0.1.0/tests/test_canvas_interaction_service_bundle.py +54 -0
- chemvas-0.1.0/tests/test_canvas_lifecycle.py +45 -0
- chemvas-0.1.0/tests/test_canvas_mark_scene_service.py +143 -0
- chemvas-0.1.0/tests/test_canvas_model_access.py +119 -0
- chemvas-0.1.0/tests/test_canvas_model_state.py +27 -0
- chemvas-0.1.0/tests/test_canvas_note_controller_unit.py +1979 -0
- chemvas-0.1.0/tests/test_canvas_rdkit_state.py +31 -0
- chemvas-0.1.0/tests/test_canvas_renderer_state.py +39 -0
- chemvas-0.1.0/tests/test_canvas_ring_fill_scene_service.py +250 -0
- chemvas-0.1.0/tests/test_canvas_scene_decoration_build_service.py +225 -0
- chemvas-0.1.0/tests/test_canvas_scene_items_state.py +97 -0
- chemvas-0.1.0/tests/test_canvas_scene_reset_service.py +2381 -0
- chemvas-0.1.0/tests/test_canvas_scene_state.py +21 -0
- chemvas-0.1.0/tests/test_canvas_scene_view_service_bundle.py +65 -0
- chemvas-0.1.0/tests/test_canvas_service_access.py +47 -0
- chemvas-0.1.0/tests/test_canvas_service_ports.py +53 -0
- chemvas-0.1.0/tests/test_canvas_services.py +534 -0
- chemvas-0.1.0/tests/test_canvas_smiles_input_state.py +49 -0
- chemvas-0.1.0/tests/test_canvas_state_lookup.py +92 -0
- chemvas-0.1.0/tests/test_canvas_text_style_state.py +63 -0
- chemvas-0.1.0/tests/test_canvas_tool_settings_state.py +51 -0
- chemvas-0.1.0/tests/test_canvas_view_additional.py +2061 -0
- chemvas-0.1.0/tests/test_canvas_view_center_transform_helpers.py +292 -0
- chemvas-0.1.0/tests/test_canvas_view_delete_and_bond_style.py +336 -0
- chemvas-0.1.0/tests/test_canvas_view_event_fallthrough.py +295 -0
- chemvas-0.1.0/tests/test_canvas_view_event_router.py +128 -0
- chemvas-0.1.0/tests/test_canvas_view_event_shortcuts.py +156 -0
- chemvas-0.1.0/tests/test_canvas_view_event_wrappers.py +389 -0
- chemvas-0.1.0/tests/test_canvas_view_handle_helpers.py +363 -0
- chemvas-0.1.0/tests/test_canvas_view_hover_helpers.py +414 -0
- chemvas-0.1.0/tests/test_canvas_view_hover_preview.py +370 -0
- chemvas-0.1.0/tests/test_canvas_view_keypress_routing.py +55 -0
- chemvas-0.1.0/tests/test_canvas_view_label_geometry_helpers.py +273 -0
- chemvas-0.1.0/tests/test_canvas_view_mark_helpers.py +203 -0
- chemvas-0.1.0/tests/test_canvas_view_move_helpers.py +405 -0
- chemvas-0.1.0/tests/test_canvas_view_note_wrapper_contract.py +187 -0
- chemvas-0.1.0/tests/test_canvas_view_ports.py +44 -0
- chemvas-0.1.0/tests/test_canvas_view_projection_math.py +1585 -0
- chemvas-0.1.0/tests/test_canvas_view_ring_fill_rotation.py +146 -0
- chemvas-0.1.0/tests/test_canvas_view_rotation_axis_helpers.py +180 -0
- chemvas-0.1.0/tests/test_canvas_view_rotation_preview_helpers.py +210 -0
- chemvas-0.1.0/tests/test_canvas_view_scene_ops_rebuild.py +88 -0
- chemvas-0.1.0/tests/test_canvas_view_selection_clipboard_wrappers.py +313 -0
- chemvas-0.1.0/tests/test_canvas_view_selection_highlight.py +329 -0
- chemvas-0.1.0/tests/test_canvas_view_setup.py +62 -0
- chemvas-0.1.0/tests/test_canvas_view_transform_helpers.py +369 -0
- chemvas-0.1.0/tests/test_canvas_view_unit.py +766 -0
- chemvas-0.1.0/tests/test_canvas_view_wheel_and_scroll.py +130 -0
- chemvas-0.1.0/tests/test_clipboard_payload_validation.py +330 -0
- chemvas-0.1.0/tests/test_core_document_groups.py +122 -0
- chemvas-0.1.0/tests/test_core_document_io.py +481 -0
- chemvas-0.1.0/tests/test_core_document_state.py +1100 -0
- chemvas-0.1.0/tests/test_core_history.py +3751 -0
- chemvas-0.1.0/tests/test_core_history_ui_atomicity.py +1668 -0
- chemvas-0.1.0/tests/test_core_model.py +82 -0
- chemvas-0.1.0/tests/test_core_molfile.py +191 -0
- chemvas-0.1.0/tests/test_core_rdkit_adapter.py +2115 -0
- chemvas-0.1.0/tests/test_core_renderer.py +44 -0
- chemvas-0.1.0/tests/test_core_tail_coverage.py +595 -0
- chemvas-0.1.0/tests/test_core_template_geometry.py +310 -0
- chemvas-0.1.0/tests/test_curved_arrow_path_service.py +45 -0
- chemvas-0.1.0/tests/test_delete_tool_logic.py +216 -0
- chemvas-0.1.0/tests/test_export_dialog_logic.py +47 -0
- chemvas-0.1.0/tests/test_export_plan_logic.py +44 -0
- chemvas-0.1.0/tests/test_export_render_service.py +229 -0
- chemvas-0.1.0/tests/test_export_scene_scope.py +129 -0
- chemvas-0.1.0/tests/test_file_open.py +118 -0
- chemvas-0.1.0/tests/test_graph_algorithms.py +127 -0
- chemvas-0.1.0/tests/test_graph_index_operations.py +276 -0
- chemvas-0.1.0/tests/test_graph_rotation_policy.py +162 -0
- chemvas-0.1.0/tests/test_graphics_items.py +113 -0
- chemvas-0.1.0/tests/test_group_note_selection_integration.py +94 -0
- chemvas-0.1.0/tests/test_gui_document_template.py +1097 -0
- chemvas-0.1.0/tests/test_gui_handle_interaction.py +99 -0
- chemvas-0.1.0/tests/test_gui_preview_3d_recovery.py +591 -0
- chemvas-0.1.0/tests/test_gui_smoke.py +2329 -0
- chemvas-0.1.0/tests/test_handle_interaction_logic.py +136 -0
- chemvas-0.1.0/tests/test_handle_mutation_service.py +160 -0
- chemvas-0.1.0/tests/test_handle_overlay_service.py +208 -0
- chemvas-0.1.0/tests/test_handle_service_bundle.py +41 -0
- chemvas-0.1.0/tests/test_history_command_snapshot.py +57 -0
- chemvas-0.1.0/tests/test_history_restore_retry.py +80 -0
- chemvas-0.1.0/tests/test_history_stack_snapshot.py +739 -0
- chemvas-0.1.0/tests/test_hover_highlight_logic.py +196 -0
- chemvas-0.1.0/tests/test_hover_interaction_service.py +267 -0
- chemvas-0.1.0/tests/test_hover_scene_access.py +51 -0
- chemvas-0.1.0/tests/test_hover_scene_renderer.py +131 -0
- chemvas-0.1.0/tests/test_hover_scene_service.py +166 -0
- chemvas-0.1.0/tests/test_hover_service_bundle.py +121 -0
- chemvas-0.1.0/tests/test_input_view_access.py +216 -0
- chemvas-0.1.0/tests/test_insert_commit_service.py +1324 -0
- chemvas-0.1.0/tests/test_insert_controller.py +1425 -0
- chemvas-0.1.0/tests/test_insert_mode_logic.py +119 -0
- chemvas-0.1.0/tests/test_insert_smiles_service.py +1090 -0
- chemvas-0.1.0/tests/test_insert_smiles_transaction.py +213 -0
- chemvas-0.1.0/tests/test_insert_template_service.py +237 -0
- chemvas-0.1.0/tests/test_label_layout_logic.py +191 -0
- chemvas-0.1.0/tests/test_main_stderr_filter.py +262 -0
- chemvas-0.1.0/tests/test_main_window_about_dialog.py +69 -0
- chemvas-0.1.0/tests/test_main_window_action_availability_service.py +74 -0
- chemvas-0.1.0/tests/test_main_window_active_canvas_ui_service.py +301 -0
- chemvas-0.1.0/tests/test_main_window_app.py +82 -0
- chemvas-0.1.0/tests/test_main_window_arrow_icon_renderer.py +92 -0
- chemvas-0.1.0/tests/test_main_window_bond_icon_renderer.py +146 -0
- chemvas-0.1.0/tests/test_main_window_bootstrap.py +147 -0
- chemvas-0.1.0/tests/test_main_window_canvas_document_service.py +117 -0
- chemvas-0.1.0/tests/test_main_window_canvas_logic.py +124 -0
- chemvas-0.1.0/tests/test_main_window_canvas_tab_ui_service.py +16 -0
- chemvas-0.1.0/tests/test_main_window_canvas_tabs.py +75 -0
- chemvas-0.1.0/tests/test_main_window_context_bar_pages.py +252 -0
- chemvas-0.1.0/tests/test_main_window_context_bar_service.py +88 -0
- chemvas-0.1.0/tests/test_main_window_context_page_state_service.py +117 -0
- chemvas-0.1.0/tests/test_main_window_dialog_actions.py +127 -0
- chemvas-0.1.0/tests/test_main_window_document_action_service.py +205 -0
- chemvas-0.1.0/tests/test_main_window_document_dialogs.py +150 -0
- chemvas-0.1.0/tests/test_main_window_icon_canvas_style.py +91 -0
- chemvas-0.1.0/tests/test_main_window_icon_pixmap_factory.py +94 -0
- chemvas-0.1.0/tests/test_main_window_icons.py +268 -0
- chemvas-0.1.0/tests/test_main_window_panel_actions.py +321 -0
- chemvas-0.1.0/tests/test_main_window_panel_service.py +100 -0
- chemvas-0.1.0/tests/test_main_window_panel_toolbar.py +302 -0
- chemvas-0.1.0/tests/test_main_window_path_logic.py +58 -0
- chemvas-0.1.0/tests/test_main_window_preview_panel.py +54 -0
- chemvas-0.1.0/tests/test_main_window_services.py +93 -0
- chemvas-0.1.0/tests/test_main_window_state.py +30 -0
- chemvas-0.1.0/tests/test_main_window_status_service.py +146 -0
- chemvas-0.1.0/tests/test_main_window_tab_references.py +57 -0
- chemvas-0.1.0/tests/test_main_window_tab_setup.py +53 -0
- chemvas-0.1.0/tests/test_main_window_template_icon_renderer.py +125 -0
- chemvas-0.1.0/tests/test_main_window_text_style_service.py +76 -0
- chemvas-0.1.0/tests/test_main_window_theme.py +97 -0
- chemvas-0.1.0/tests/test_main_window_tool_action_service.py +150 -0
- chemvas-0.1.0/tests/test_main_window_tool_icon_renderer.py +124 -0
- chemvas-0.1.0/tests/test_main_window_tool_routing_service.py +200 -0
- chemvas-0.1.0/tests/test_main_window_tool_state_service.py +200 -0
- chemvas-0.1.0/tests/test_main_window_toolbar_actions.py +507 -0
- chemvas-0.1.0/tests/test_main_window_toolbar_buttons.py +148 -0
- chemvas-0.1.0/tests/test_main_window_toolbar_logic.py +91 -0
- chemvas-0.1.0/tests/test_main_window_ui_assembly_service.py +429 -0
- chemvas-0.1.0/tests/test_main_window_ui_references.py +70 -0
- chemvas-0.1.0/tests/test_main_window_utility_icon_renderer.py +87 -0
- chemvas-0.1.0/tests/test_mark_hover_preview_service.py +161 -0
- chemvas-0.1.0/tests/test_note_html_sanitizer.py +101 -0
- chemvas-0.1.0/tests/test_note_item_access.py +60 -0
- chemvas-0.1.0/tests/test_perspective_drag_logic.py +82 -0
- chemvas-0.1.0/tests/test_perspective_tool_controller.py +452 -0
- chemvas-0.1.0/tests/test_preview_3d_interaction.py +41 -0
- chemvas-0.1.0/tests/test_preview_3d_layout.py +46 -0
- chemvas-0.1.0/tests/test_preview_3d_projection.py +75 -0
- chemvas-0.1.0/tests/test_preview_3d_renderer.py +51 -0
- chemvas-0.1.0/tests/test_preview_3d_state.py +93 -0
- chemvas-0.1.0/tests/test_preview_3d_worker.py +70 -0
- chemvas-0.1.0/tests/test_preview_scene_access.py +95 -0
- chemvas-0.1.0/tests/test_preview_scene_renderer.py +250 -0
- chemvas-0.1.0/tests/test_rdkit_adapter_access.py +48 -0
- chemvas-0.1.0/tests/test_rdkit_async_jobs.py +437 -0
- chemvas-0.1.0/tests/test_renderer_canvas_tail_coverage.py +428 -0
- chemvas-0.1.0/tests/test_renderer_style_access.py +94 -0
- chemvas-0.1.0/tests/test_ring_occupancy_logic.py +82 -0
- chemvas-0.1.0/tests/test_safe_note_helpers.py +147 -0
- chemvas-0.1.0/tests/test_scene_clipboard_access.py +139 -0
- chemvas-0.1.0/tests/test_scene_clipboard_copy_io.py +92 -0
- chemvas-0.1.0/tests/test_scene_clipboard_logic.py +663 -0
- chemvas-0.1.0/tests/test_scene_clipboard_selection.py +79 -0
- chemvas-0.1.0/tests/test_scene_clipboard_transaction_logic.py +244 -0
- chemvas-0.1.0/tests/test_scene_decoration_build_service.py +177 -0
- chemvas-0.1.0/tests/test_scene_decoration_service.py +794 -0
- chemvas-0.1.0/tests/test_scene_decoration_service_bundle.py +40 -0
- chemvas-0.1.0/tests/test_scene_delete_apply_logic.py +269 -0
- chemvas-0.1.0/tests/test_scene_delete_initial_atomicity.py +2524 -0
- chemvas-0.1.0/tests/test_scene_delete_logic.py +380 -0
- chemvas-0.1.0/tests/test_scene_group_operations.py +701 -0
- chemvas-0.1.0/tests/test_scene_item_access.py +423 -0
- chemvas-0.1.0/tests/test_scene_item_controller.py +1889 -0
- chemvas-0.1.0/tests/test_scene_item_lifecycle_note_removal.py +160 -0
- chemvas-0.1.0/tests/test_scene_item_restore.py +163 -0
- chemvas-0.1.0/tests/test_scene_item_state_codec.py +102 -0
- chemvas-0.1.0/tests/test_scene_item_state_serialization.py +109 -0
- chemvas-0.1.0/tests/test_scene_item_state_unit.py +648 -0
- chemvas-0.1.0/tests/test_scene_operation_service_bundle.py +73 -0
- chemvas-0.1.0/tests/test_scene_ops_controller.py +1299 -0
- chemvas-0.1.0/tests/test_scene_ops_controller_additional.py +555 -0
- chemvas-0.1.0/tests/test_scene_ops_controller_clipboard_payload.py +397 -0
- chemvas-0.1.0/tests/test_scene_ops_controller_delete_flip_edges.py +113 -0
- chemvas-0.1.0/tests/test_scene_ops_controller_paste_edges.py +502 -0
- chemvas-0.1.0/tests/test_scene_paste_apply_hardening.py +151 -0
- chemvas-0.1.0/tests/test_scene_paste_apply_logic.py +264 -0
- chemvas-0.1.0/tests/test_scene_rect_snapshot.py +2698 -0
- chemvas-0.1.0/tests/test_scene_rotation_state.py +117 -0
- chemvas-0.1.0/tests/test_scene_signal_blocking.py +157 -0
- chemvas-0.1.0/tests/test_scene_single_item_mutation_logic.py +286 -0
- chemvas-0.1.0/tests/test_scene_transform_apply_logic.py +125 -0
- chemvas-0.1.0/tests/test_scene_transform_logic.py +474 -0
- chemvas-0.1.0/tests/test_select_all_access.py +101 -0
- chemvas-0.1.0/tests/test_selection_access_status.py +156 -0
- chemvas-0.1.0/tests/test_selection_center_logic.py +26 -0
- chemvas-0.1.0/tests/test_selection_controller_additional.py +910 -0
- chemvas-0.1.0/tests/test_selection_highlight_styler.py +133 -0
- chemvas-0.1.0/tests/test_selection_hit_logic.py +262 -0
- chemvas-0.1.0/tests/test_selection_hit_membership_logic.py +130 -0
- chemvas-0.1.0/tests/test_selection_hit_test_service.py +107 -0
- chemvas-0.1.0/tests/test_selection_note_service.py +191 -0
- chemvas-0.1.0/tests/test_selection_outline_items.py +82 -0
- chemvas-0.1.0/tests/test_selection_outline_paths.py +103 -0
- chemvas-0.1.0/tests/test_selection_outline_service.py +284 -0
- chemvas-0.1.0/tests/test_selection_outline_state.py +43 -0
- chemvas-0.1.0/tests/test_selection_preference_service.py +140 -0
- chemvas-0.1.0/tests/test_selection_press_logic.py +68 -0
- chemvas-0.1.0/tests/test_selection_rotation_controller.py +3849 -0
- chemvas-0.1.0/tests/test_selection_rotation_geometry.py +138 -0
- chemvas-0.1.0/tests/test_selection_rotation_history.py +61 -0
- chemvas-0.1.0/tests/test_selection_rotation_logic.py +41 -0
- chemvas-0.1.0/tests/test_selection_rotation_planarity.py +38 -0
- chemvas-0.1.0/tests/test_selection_rotation_ring_cache.py +98 -0
- chemvas-0.1.0/tests/test_selection_rotation_session.py +393 -0
- chemvas-0.1.0/tests/test_selection_scene_access.py +2258 -0
- chemvas-0.1.0/tests/test_selection_service_bundle.py +83 -0
- chemvas-0.1.0/tests/test_selection_structure_service.py +153 -0
- chemvas-0.1.0/tests/test_shapes.py +238 -0
- chemvas-0.1.0/tests/test_sheet_setup_access.py +819 -0
- chemvas-0.1.0/tests/test_sheet_setup_logic.py +30 -0
- chemvas-0.1.0/tests/test_sheet_setup_state.py +28 -0
- chemvas-0.1.0/tests/test_smiles_insert_logic.py +196 -0
- chemvas-0.1.0/tests/test_structure_benzene_build_service.py +140 -0
- chemvas-0.1.0/tests/test_structure_benzene_logic.py +168 -0
- chemvas-0.1.0/tests/test_structure_bond_build_service.py +83 -0
- chemvas-0.1.0/tests/test_structure_build_service.py +2821 -0
- chemvas-0.1.0/tests/test_structure_geometry_logic.py +353 -0
- chemvas-0.1.0/tests/test_structure_growth_build_service.py +171 -0
- chemvas-0.1.0/tests/test_structure_growth_logic.py +80 -0
- chemvas-0.1.0/tests/test_structure_insert_service.py +625 -0
- chemvas-0.1.0/tests/test_structure_payload_logic.py +195 -0
- chemvas-0.1.0/tests/test_structure_service_bundle.py +64 -0
- chemvas-0.1.0/tests/test_structure_template_build_service.py +109 -0
- chemvas-0.1.0/tests/test_structure_template_commands.py +161 -0
- chemvas-0.1.0/tests/test_svg_roundtrip.py +513 -0
- chemvas-0.1.0/tests/test_template_geometry_resolver_service.py +77 -0
- chemvas-0.1.0/tests/test_template_insert_logic.py +304 -0
- chemvas-0.1.0/tests/test_template_preview_logic.py +107 -0
- chemvas-0.1.0/tests/test_text_tool_logic.py +125 -0
- chemvas-0.1.0/tests/test_tool_context.py +870 -0
- chemvas-0.1.0/tests/test_tool_overlay_logic.py +107 -0
- chemvas-0.1.0/tests/test_tool_service_bundle.py +65 -0
- chemvas-0.1.0/tests/test_tools_additional.py +1330 -0
- chemvas-0.1.0/tests/test_tools_unit.py +3139 -0
- chemvas-0.1.0/tests/test_ui_history_command_atomicity.py +4972 -0
- 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
|
+

|
|
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)
|
chemvas-0.1.0/README.md
ADDED
|
@@ -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
|
+

|
|
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)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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()
|