├── .gitignore ├── .gitattributes ├── Fonts ├── cour.ttf └── cour.ttf.import ├── Images ├── 2D.png ├── 3D.png ├── MainScreen.png ├── 2D.png.import ├── 3D.png.import └── MainScreen.png.import ├── sminav_logo.ico ├── sminav_logo.png ├── Scenes ├── Atoms │ ├── circle.png │ ├── highlight.png │ ├── circle.png.import │ ├── highlight.png.import │ ├── base_atom_3d.tscn │ ├── base_atom.tscn │ ├── base_atom.gd │ └── base_atom_3d.gd ├── 2D Visualizer │ ├── selected.tres │ ├── 2d_smile_visualizer.tscn │ └── 2d_smile_visualizer.gd ├── Bonds │ ├── single_bond.tscn │ ├── single_bond_3d.tscn │ ├── double_bond.tscn │ ├── triple_bond.tscn │ ├── double_bond_3d.tscn │ ├── bond.gd │ ├── triple_bond_3d.tscn │ ├── bond_3D.gd │ ├── arromatic_bond.tscn │ └── arromatic_bond_3d.tscn ├── Python Installation │ ├── python_installer.tscn │ └── python_installer.gd ├── Main Menu │ ├── Python_Runner.gd │ └── python_runner.tscn └── 3D Visualizer │ ├── 3d_smile_visualizer.tscn │ └── 3d_smile_visualizer.gd ├── Utils ├── Branches Button │ └── branch_button.tscn ├── Element Button │ ├── element_button.gd │ └── element_button.tscn └── globals.gd ├── sminav_logo.png.import ├── LICENSE ├── project.godot ├── export_presets.cfg ├── README.md └── rdkit_script.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | .venv/ 4 | *.txt 5 | Exports/ -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /Fonts/cour.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Fonts/cour.ttf -------------------------------------------------------------------------------- /Images/2D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Images/2D.png -------------------------------------------------------------------------------- /Images/3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Images/3D.png -------------------------------------------------------------------------------- /sminav_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/sminav_logo.ico -------------------------------------------------------------------------------- /sminav_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/sminav_logo.png -------------------------------------------------------------------------------- /Images/MainScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Images/MainScreen.png -------------------------------------------------------------------------------- /Scenes/Atoms/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Scenes/Atoms/circle.png -------------------------------------------------------------------------------- /Scenes/Atoms/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UA-Libraries-Research-Data-Services/SmiNav/main/Scenes/Atoms/highlight.png -------------------------------------------------------------------------------- /Scenes/2D Visualizer/selected.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=3 uid="uid://u23q72jyd77v"] 2 | 3 | [resource] 4 | bg_color = Color(0.921569, 0, 0.262745, 1) 5 | -------------------------------------------------------------------------------- /Scenes/Bonds/single_bond.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://dw370la16xok8"] 2 | 3 | [node name="Single Bond" type="Node2D"] 4 | 5 | [node name="Line2D" type="Line2D" parent="."] 6 | points = PackedVector2Array(0, 0, 1, 0) 7 | width = 2.5 8 | 9 | [node name="Highlight" type="Polygon2D" parent="."] 10 | z_index = -1 11 | z_as_relative = false 12 | color = Color(1, 1, 0, 1) 13 | polygon = PackedVector2Array(0, -10, 0, 10, 1, 10, 1, -10) 14 | -------------------------------------------------------------------------------- /Utils/Branches Button/branch_button.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://s2awnjpvpn11"] 2 | 3 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="1_njfn7"] 4 | 5 | [node name="Branch Button" type="CheckBox"] 6 | custom_minimum_size = Vector2(275, 0) 7 | offset_right = 24.0 8 | offset_bottom = 24.0 9 | theme_override_fonts/font = ExtResource("1_njfn7") 10 | theme_override_font_sizes/font_size = 25 11 | alignment = 2 12 | text_overrun_behavior = 1 13 | -------------------------------------------------------------------------------- /Scenes/Bonds/single_bond_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bmnaocdp6enqt"] 2 | 3 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vy5hj"] 4 | albedo_color = Color(1, 1, 0, 1) 5 | 6 | [node name="Single Bond 3D" type="Node3D"] 7 | 8 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="."] 9 | radius = 0.1 10 | height = 1.0 11 | 12 | [node name="Highlight" type="Node3D" parent="."] 13 | 14 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="Highlight"] 15 | transparency = 0.8 16 | radius = 0.2 17 | height = 1.0 18 | material = SubResource("StandardMaterial3D_vy5hj") 19 | -------------------------------------------------------------------------------- /Scenes/Bonds/double_bond.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://d3pl26sckemkt"] 2 | 3 | [node name="Double Bond" type="Node2D"] 4 | 5 | [node name="Line2D" type="Line2D" parent="."] 6 | position = Vector2(0, 2.5) 7 | points = PackedVector2Array(0, 0, 1, 0) 8 | width = 2.5 9 | 10 | [node name="Line2D2" type="Line2D" parent="."] 11 | position = Vector2(0, -2.5) 12 | points = PackedVector2Array(0, 0, 1, 0) 13 | width = 2.5 14 | 15 | [node name="Highlight" type="Polygon2D" parent="."] 16 | z_index = -1 17 | z_as_relative = false 18 | color = Color(1, 1, 0, 1) 19 | polygon = PackedVector2Array(0, -5, 0, 5, 1, 5, 1, -5) 20 | -------------------------------------------------------------------------------- /Scenes/Bonds/triple_bond.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://ck8ujhb3utsb"] 2 | 3 | [node name="Triple Bond" type="Node2D"] 4 | 5 | [node name="Line2D" type="Line2D" parent="."] 6 | points = PackedVector2Array(0, 0, 1, 0) 7 | width = 1.5 8 | 9 | [node name="Line2D2" type="Line2D" parent="."] 10 | position = Vector2(0, -2.5) 11 | points = PackedVector2Array(0, 0, 1, 0) 12 | width = 1.5 13 | 14 | [node name="Line2D3" type="Line2D" parent="."] 15 | position = Vector2(0, 2.5) 16 | points = PackedVector2Array(0, 0, 1, 0) 17 | width = 1.5 18 | 19 | [node name="Highlight" type="Polygon2D" parent="."] 20 | z_index = -1 21 | z_as_relative = false 22 | color = Color(1, 1, 0, 1) 23 | polygon = PackedVector2Array(0, -5, 0, 5, 1, 5, 1, -5) 24 | -------------------------------------------------------------------------------- /Fonts/cour.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://d4bw7aq2ggi5w" 6 | path="res://.godot/imported/cour.ttf-deda2f4201130ace5d76bc9ceffc2760.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://Fonts/cour.ttf" 11 | dest_files=["res://.godot/imported/cour.ttf-deda2f4201130ace5d76bc9ceffc2760.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | multichannel_signed_distance_field=false 19 | msdf_pixel_range=8 20 | msdf_size=48 21 | allow_system_fallback=true 22 | force_autohinter=false 23 | hinting=1 24 | subpixel_positioning=1 25 | oversampling=0.0 26 | Fallbacks=null 27 | fallbacks=[] 28 | Compress=null 29 | compress=true 30 | preload=[] 31 | language_support={} 32 | script_support={} 33 | opentype_features={} 34 | -------------------------------------------------------------------------------- /Utils/Element Button/element_button.gd: -------------------------------------------------------------------------------- 1 | extends Button 2 | 3 | var element_name: String 4 | var element_index: int 5 | @onready var element_index_text = $"Element Index Text" 6 | 7 | # Called when the node enters the scene tree for the first time. 8 | func _ready(): 9 | self.add_theme_stylebox_override("pressed", generate_theme_stylebox()) 10 | self.text = str(element_name) 11 | self.element_index_text.text = str(element_index) 12 | turn_off_index() 13 | 14 | func generate_theme_stylebox(): 15 | var new_stylebox = StyleBoxFlat.new() 16 | new_stylebox.bg_color = Color(Globals.selected_color, .5) 17 | return new_stylebox 18 | 19 | func turn_off_index(): 20 | if element_index_text.text != "": 21 | element_index_text.hide() 22 | 23 | func turn_on_index(): 24 | if element_index_text.text != "": 25 | element_index_text.show() 26 | -------------------------------------------------------------------------------- /Images/2D.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://lipo6p5jcsm0" 6 | path="res://.godot/imported/2D.png-44afb2896737906a22e4f7a909e598de.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Images/2D.png" 14 | dest_files=["res://.godot/imported/2D.png-44afb2896737906a22e4f7a909e598de.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /Images/3D.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://jdi1ag61rsk3" 6 | path="res://.godot/imported/3D.png-1a671e482ae87c200426de54bf73f737.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Images/3D.png" 14 | dest_files=["res://.godot/imported/3D.png-1a671e482ae87c200426de54bf73f737.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /sminav_logo.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://kel2rl4oqqes" 6 | path="res://.godot/imported/sminav_logo.png-ec2eb9032eb1ac7b7f1c8d3e1563d919.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://sminav_logo.png" 14 | dest_files=["res://.godot/imported/sminav_logo.png-ec2eb9032eb1ac7b7f1c8d3e1563d919.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /Scenes/Atoms/circle.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d24oiyt1w8krr" 6 | path="res://.godot/imported/circle.png-3ef6f736add1256eacd8c5e81d8bd060.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Scenes/Atoms/circle.png" 14 | dest_files=["res://.godot/imported/circle.png-3ef6f736add1256eacd8c5e81d8bd060.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /Utils/Element Button/element_button.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://bvn5whr4v3ox2"] 2 | 3 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="1_782np"] 4 | [ext_resource type="Script" path="res://Utils/Element Button/element_button.gd" id="1_i1ho4"] 5 | 6 | [node name="Element Button" type="Button"] 7 | offset_right = 38.0 8 | offset_bottom = 66.0 9 | theme_override_fonts/font = ExtResource("1_782np") 10 | theme_override_font_sizes/font_size = 50 11 | toggle_mode = true 12 | script = ExtResource("1_i1ho4") 13 | 14 | [node name="Element Index Text" type="Label" parent="."] 15 | layout_mode = 1 16 | anchors_preset = 7 17 | anchor_left = 0.5 18 | anchor_top = 1.0 19 | anchor_right = 0.5 20 | anchor_bottom = 1.0 21 | offset_left = -5.5 22 | offset_top = -23.0 23 | offset_right = 5.5 24 | grow_horizontal = 2 25 | grow_vertical = 0 26 | theme_override_fonts/font = ExtResource("1_782np") 27 | -------------------------------------------------------------------------------- /Images/MainScreen.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dqfo4tyw4w4oo" 6 | path="res://.godot/imported/MainScreen.png-087796ec6e0a60692f1376636c1cfd88.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Images/MainScreen.png" 14 | dest_files=["res://.godot/imported/MainScreen.png-087796ec6e0a60692f1376636c1cfd88.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /Scenes/Atoms/highlight.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://psq8t3ocnca2" 6 | path.s3tc="res://.godot/imported/highlight.png-42ed1ef274efef6c12e23c2896b8bdb4.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://Scenes/Atoms/highlight.png" 15 | dest_files=["res://.godot/imported/highlight.png-42ed1ef274efef6c12e23c2896b8bdb4.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 University of Alabama Libraries 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. 22 | -------------------------------------------------------------------------------- /Scenes/Bonds/double_bond_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b03b4fi80e1kk"] 2 | 3 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_huptu"] 4 | albedo_color = Color(1, 1, 0, 1) 5 | 6 | [node name="Double Bond 3D" type="Node3D"] 7 | 8 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="."] 9 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0) 10 | radius = 0.1 11 | height = 1.0 12 | 13 | [node name="CSGCylinder3D2" type="CSGCylinder3D" parent="."] 14 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, 0, 0) 15 | radius = 0.1 16 | height = 1.0 17 | 18 | [node name="Highlight" type="Node3D" parent="."] 19 | 20 | [node name="Highlight" type="CSGCylinder3D" parent="Highlight"] 21 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0) 22 | transparency = 0.8 23 | radius = 0.2 24 | height = 1.0 25 | material = SubResource("StandardMaterial3D_huptu") 26 | 27 | [node name="Highlight2" type="CSGCylinder3D" parent="Highlight"] 28 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, 0, 0) 29 | transparency = 0.8 30 | radius = 0.2 31 | height = 1.0 32 | material = SubResource("StandardMaterial3D_huptu") 33 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="SmiNav" 14 | config/version="1.0" 15 | run/main_scene="res://Scenes/Main Menu/python_runner.tscn" 16 | config/features=PackedStringArray("4.2", "Forward Plus") 17 | boot_splash/bg_color=Color(0, 0, 0, 1) 18 | boot_splash/image="res://sminav_logo.png" 19 | config/icon="res://sminav_logo.png" 20 | 21 | [autoload] 22 | 23 | Globals="*res://Utils/globals.gd" 24 | 25 | [display] 26 | 27 | window/size/viewport_width=1280 28 | window/size/viewport_height=720 29 | window/size/mode=2 30 | window/stretch/mode="canvas_items" 31 | window/stretch/aspect="expand" 32 | 33 | [input] 34 | 35 | multi-select={ 36 | "deadzone": 0.5, 37 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194326,"key_label":0,"unicode":0,"echo":false,"script":null) 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /Scenes/Bonds/bond.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name Bond 3 | 4 | var bond : Node2D = null 5 | var connected_atoms : Array = [] 6 | var highlight_node : Node2D = null 7 | 8 | 9 | func _init(itself, atom1, atom2): 10 | bond = itself 11 | connected_atoms.append(atom1) 12 | connected_atoms.append(atom2) 13 | Globals.modulate_highlight.connect(_on_modulate_highlight) 14 | highlight_node = bond.get_node("Highlight") 15 | if highlight_node: 16 | highlight_node.hide() 17 | 18 | func _on_modulate_highlight(): 19 | if highlight_node != null: 20 | highlight_node.color = Color(Globals.selected_color, .5) 21 | 22 | func check_if_connected_atoms(atoms: Array): 23 | if connected_atoms[0] in atoms and connected_atoms[1] in atoms: 24 | return true 25 | return false 26 | 27 | func check_if_contains_atoms(atoms: Array): 28 | if connected_atoms[0] in atoms or connected_atoms[1] in atoms: 29 | return true 30 | return false 31 | 32 | func check_if_hydrogen_connected(atoms: Array): 33 | if connected_atoms[0] in atoms and connected_atoms[1].atom_symbol.text == "H": 34 | return connected_atoms[1] 35 | elif connected_atoms[1] in atoms and connected_atoms[0].atom_symbol.text == "H": 36 | return connected_atoms[0] 37 | return null 38 | 39 | 40 | func turn_on_highlight(): 41 | if highlight_node != null: 42 | highlight_node.show() 43 | 44 | 45 | func turn_off_highlight(): 46 | if highlight_node != null: 47 | highlight_node.hide() 48 | -------------------------------------------------------------------------------- /Scenes/Bonds/triple_bond_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://v1xihdq0ypp7"] 2 | 3 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ke0st"] 4 | albedo_color = Color(1, 1, 0, 1) 5 | 6 | [node name="Triple Bond 3D" type="Node3D"] 7 | 8 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="."] 9 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0, 0) 10 | radius = 0.1 11 | height = 1.0 12 | 13 | [node name="CSGCylinder3D2" type="CSGCylinder3D" parent="."] 14 | radius = 0.1 15 | height = 1.0 16 | 17 | [node name="CSGCylinder3D3" type="CSGCylinder3D" parent="."] 18 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0, 0) 19 | radius = 0.1 20 | height = 1.0 21 | 22 | [node name="Highlight" type="Node3D" parent="."] 23 | 24 | [node name="Highlight" type="CSGCylinder3D" parent="Highlight"] 25 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0, 0) 26 | transparency = 0.8 27 | radius = 0.2 28 | height = 1.0 29 | material = SubResource("StandardMaterial3D_ke0st") 30 | 31 | [node name="Highlight2" type="CSGCylinder3D" parent="Highlight"] 32 | transparency = 0.8 33 | radius = 0.2 34 | height = 1.0 35 | material = SubResource("StandardMaterial3D_ke0st") 36 | 37 | [node name="Highlight3" type="CSGCylinder3D" parent="Highlight"] 38 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0, 0) 39 | transparency = 0.8 40 | radius = 0.2 41 | height = 1.0 42 | material = SubResource("StandardMaterial3D_ke0st") 43 | -------------------------------------------------------------------------------- /Scenes/Bonds/bond_3D.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name Bond_3D 3 | 4 | var bond : Node3D = null 5 | var connected_atoms : Array = [] 6 | var highlight_node : Node3D = null 7 | 8 | 9 | func _init(itself, atom1, atom2): 10 | bond = itself 11 | connected_atoms.append(atom1) 12 | connected_atoms.append(atom2) 13 | Globals.modulate_highlight.connect(_on_modulate_highlight) 14 | highlight_node = bond.get_node("Highlight") 15 | for child in highlight_node.get_children(): 16 | child.material = child.material.duplicate() 17 | child.transparency = .8 18 | if highlight_node: 19 | highlight_node.hide() 20 | 21 | func _on_modulate_highlight(): 22 | if highlight_node != null: 23 | for child in highlight_node.get_children(): 24 | child.material.albedo_color = Globals.selected_color 25 | child.transparency = .8 26 | 27 | func check_if_connected_atoms(atoms: Array): 28 | if connected_atoms[0] in atoms and connected_atoms[1] in atoms: 29 | return true 30 | return false 31 | 32 | func check_if_contains_atoms(atoms: Array): 33 | if connected_atoms[0] in atoms or connected_atoms[1] in atoms: 34 | return true 35 | return false 36 | 37 | func check_if_hydrogen_connected(atoms: Array): 38 | if connected_atoms[0] in atoms and connected_atoms[1].atom_symbol.text == "H": 39 | return connected_atoms[1] 40 | elif connected_atoms[1] in atoms and connected_atoms[0].atom_symbol.text == "H": 41 | return connected_atoms[0] 42 | return null 43 | 44 | func turn_on_highlight(): 45 | if highlight_node != null: 46 | highlight_node.show() 47 | 48 | 49 | func turn_off_highlight(): 50 | if highlight_node != null: 51 | highlight_node.hide() 52 | -------------------------------------------------------------------------------- /Utils/globals.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | @onready var base_path = ProjectSettings.globalize_path("res://") 4 | 5 | const NEON_GREEN = Color8(15, 255, 80) 6 | const NEON_RED = Color8(255, 49, 49) 7 | const NEON_YELLOW = Color8(255, 255, 51) 8 | const NEON_PURPLE = Color8(157, 0, 255) 9 | 10 | const RED = Color8(255, 0, 0) 11 | const GREEN = Color8(0, 255, 0) 12 | const PURPLE = Color8(160, 32, 240) 13 | const YELLOW = Color8(255,255,0) 14 | 15 | signal modulate_highlight(color_code: Color) 16 | signal camera_position(global_pos: Vector3) 17 | signal node_clicked(clicked_node) 18 | 19 | var selected_color = NEON_GREEN 20 | 21 | var venv_exists = false 22 | # Called when the node enters the scene tree for the first time. 23 | func _ready(): 24 | setup_venv() 25 | 26 | func setup_venv(): 27 | if check_if_venv_exists(): 28 | print("Virtual environment already exists.") 29 | venv_exists = true 30 | return 31 | 32 | 33 | func check_if_venv_exists() -> bool: 34 | var current_directory = DirAccess.open(base_path + ".venv/") 35 | if current_directory: 36 | return true 37 | return false 38 | 39 | 40 | func run_python_script(python_script_path, arguments): 41 | var python_executable 42 | if OS.get_name() == "Windows": 43 | python_executable = base_path + ".venv/Scripts/python" 44 | else: 45 | python_executable = base_path + ".venv/bin/python" 46 | var script_path = base_path + python_script_path 47 | var python_arguments = arguments 48 | var args = [script_path] + python_arguments 49 | var output = [] 50 | var error = OS.execute(python_executable, args, output, true, false) 51 | for line in output: 52 | print(line) 53 | if error == OK: 54 | print("Command executed successfully.") 55 | for line in output: 56 | print(line) 57 | else: 58 | print("Failed to execute command.") 59 | print(error) 60 | return false 61 | return output 62 | -------------------------------------------------------------------------------- /Scenes/Bonds/arromatic_bond.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=3 uid="uid://dv1hnpkjm80hc"] 2 | 3 | [node name="Arromatic Bond" type="Node2D"] 4 | 5 | [node name="Line2D2" type="Line2D" parent="."] 6 | position = Vector2(0, 2) 7 | points = PackedVector2Array(0, 0, 1, 0) 8 | width = 2.5 9 | 10 | [node name="Dotted Line" type="Node2D" parent="."] 11 | position = Vector2(0, -2) 12 | 13 | [node name="Line2D" type="Line2D" parent="Dotted Line"] 14 | points = PackedVector2Array(0.05, 0, 0.1, 0) 15 | width = 2.5 16 | 17 | [node name="Line2D2" type="Line2D" parent="Dotted Line"] 18 | points = PackedVector2Array(0.15, 0, 0.2, 0) 19 | width = 2.5 20 | 21 | [node name="Line2D3" type="Line2D" parent="Dotted Line"] 22 | points = PackedVector2Array(0.25, 0, 0.3, 0) 23 | width = 2.5 24 | 25 | [node name="Line2D4" type="Line2D" parent="Dotted Line"] 26 | points = PackedVector2Array(0.35, 0, 0.4, 0) 27 | width = 2.5 28 | 29 | [node name="Line2D5" type="Line2D" parent="Dotted Line"] 30 | points = PackedVector2Array(0.45, 0, 0.5, 0) 31 | width = 2.5 32 | 33 | [node name="Line2D6" type="Line2D" parent="Dotted Line"] 34 | points = PackedVector2Array(0.55, 0, 0.6, 0) 35 | width = 2.5 36 | 37 | [node name="Line2D7" type="Line2D" parent="Dotted Line"] 38 | points = PackedVector2Array(0.65, 0, 0.7, 0) 39 | width = 2.5 40 | 41 | [node name="Line2D8" type="Line2D" parent="Dotted Line"] 42 | points = PackedVector2Array(0.75, 0, 0.8, 0) 43 | width = 2.5 44 | 45 | [node name="Line2D9" type="Line2D" parent="Dotted Line"] 46 | points = PackedVector2Array(0.85, 0, 0.9, 0) 47 | width = 2.5 48 | 49 | [node name="Line2D10" type="Line2D" parent="Dotted Line"] 50 | points = PackedVector2Array(0.95, 0, 1, 0) 51 | width = 2.5 52 | 53 | [node name="Highlight" type="Polygon2D" parent="."] 54 | z_index = -1 55 | z_as_relative = false 56 | color = Color(1, 1, 0, 1) 57 | polygon = PackedVector2Array(0, -5, 0, 5, 1, 5, 1, -5) 58 | -------------------------------------------------------------------------------- /Scenes/Atoms/base_atom_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=3 uid="uid://bohb6gjvdnu4y"] 2 | 3 | [ext_resource type="Script" path="res://Scenes/Atoms/base_atom_3d.gd" id="1_snnk8"] 4 | [ext_resource type="Texture2D" uid="uid://psq8t3ocnca2" path="res://Scenes/Atoms/highlight.png" id="2_33pcd"] 5 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="2_tl2cg"] 6 | 7 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_kxjlh"] 8 | 9 | [sub_resource type="SphereShape3D" id="SphereShape3D_qxeea"] 10 | radius = 0.6 11 | 12 | [node name="Base Atom 3D" type="CSGSphere3D"] 13 | rings = 12 14 | material = SubResource("StandardMaterial3D_kxjlh") 15 | script = ExtResource("1_snnk8") 16 | 17 | [node name="Atom Symbol" type="Label3D" parent="."] 18 | transform = Transform3D(-1, 0, 1.60936e-07, 0, 1, 0, -1.60936e-07, 0, -1, 0, 0, -0.5) 19 | pixel_size = 0.01 20 | text = "CA" 21 | font = ExtResource("2_tl2cg") 22 | 23 | [node name="Atom Charge" type="Label3D" parent="Atom Symbol"] 24 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.25, 0.15, 0) 25 | pixel_size = 0.01 26 | text = "+3" 27 | font = ExtResource("2_tl2cg") 28 | font_size = 24 29 | 30 | [node name="Atom Index" type="Label3D" parent="Atom Symbol"] 31 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, -0.2, 0) 32 | pixel_size = 0.01 33 | text = "0" 34 | font = ExtResource("2_tl2cg") 35 | font_size = 24 36 | 37 | [node name="Highlight" type="Sprite3D" parent="."] 38 | transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0.5) 39 | modulate = Color(1, 1, 1, 0.447059) 40 | texture = ExtResource("2_33pcd") 41 | 42 | [node name="StaticBody3D" type="StaticBody3D" parent="."] 43 | 44 | [node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] 45 | shape = SubResource("SphereShape3D_qxeea") 46 | 47 | [connection signal="input_event" from="StaticBody3D" to="." method="_on_static_body_3d_input_event"] 48 | -------------------------------------------------------------------------------- /Scenes/Atoms/base_atom.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://fau0b8o80622"] 2 | 3 | [ext_resource type="Script" path="res://Scenes/Atoms/base_atom.gd" id="1_ntb6r"] 4 | [ext_resource type="Texture2D" uid="uid://psq8t3ocnca2" path="res://Scenes/Atoms/highlight.png" id="2_1r31h"] 5 | 6 | [sub_resource type="CircleShape2D" id="CircleShape2D_fblk3"] 7 | radius = 20.0 8 | 9 | [node name="Base Atom" type="Node2D"] 10 | z_index = 1 11 | z_as_relative = false 12 | script = ExtResource("1_ntb6r") 13 | 14 | [node name="Atom Symbol" type="Label" parent="."] 15 | anchors_preset = 15 16 | anchor_right = 1.0 17 | anchor_bottom = 1.0 18 | offset_left = -6.0 19 | offset_top = -12.0 20 | offset_right = 6.0 21 | offset_bottom = 10.0 22 | grow_horizontal = 2 23 | grow_vertical = 2 24 | theme_override_font_sizes/font_size = 15 25 | text = "O" 26 | horizontal_alignment = 1 27 | vertical_alignment = 1 28 | 29 | [node name="Atom Charge" type="Label" parent="Atom Symbol"] 30 | layout_mode = 1 31 | anchors_preset = 8 32 | anchor_left = 0.5 33 | anchor_top = 0.5 34 | anchor_right = 0.5 35 | anchor_bottom = 0.5 36 | offset_left = 6.0 37 | offset_top = -16.0 38 | offset_right = 18.0 39 | offset_bottom = 6.0 40 | grow_horizontal = 2 41 | grow_vertical = 2 42 | theme_override_font_sizes/font_size = 10 43 | text = "+3" 44 | horizontal_alignment = 1 45 | vertical_alignment = 1 46 | 47 | [node name="Atom Index" type="Label" parent="Atom Symbol"] 48 | layout_mode = 1 49 | anchors_preset = 8 50 | anchor_left = 0.5 51 | anchor_top = 0.5 52 | anchor_right = 0.5 53 | anchor_bottom = 0.5 54 | offset_left = 6.0 55 | offset_top = -4.0 56 | offset_right = 18.0 57 | offset_bottom = 18.0 58 | grow_horizontal = 2 59 | grow_vertical = 2 60 | theme_override_colors/font_color = Color(0, 0, 0, 1) 61 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 62 | theme_override_constants/outline_size = 4 63 | theme_override_font_sizes/font_size = 10 64 | text = "0" 65 | horizontal_alignment = 1 66 | vertical_alignment = 1 67 | 68 | [node name="Highlight" type="Sprite2D" parent="."] 69 | modulate = Color(1, 1, 0, 1) 70 | z_index = -1 71 | z_as_relative = false 72 | scale = Vector2(0.1, 0.1) 73 | texture = ExtResource("2_1r31h") 74 | 75 | [node name="StaticBody2D" type="StaticBody2D" parent="."] 76 | input_pickable = true 77 | 78 | [node name="CollisionShape2D" type="CollisionShape2D" parent="StaticBody2D"] 79 | shape = SubResource("CircleShape2D_fblk3") 80 | 81 | [connection signal="input_event" from="StaticBody2D" to="." method="_on_static_body_2d_input_event"] 82 | -------------------------------------------------------------------------------- /Scenes/Python Installation/python_installer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://722y3kprgv2k"] 2 | 3 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="1_k4bvx"] 4 | [ext_resource type="Script" path="res://Scenes/Python Installation/python_installer.gd" id="1_r74va"] 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tfdvm"] 7 | bg_color = Color(0, 0, 0, 1) 8 | border_width_left = 2 9 | border_width_top = 2 10 | border_width_right = 2 11 | border_width_bottom = 2 12 | border_color = Color(1, 1, 1, 1) 13 | 14 | [node name="Python Installer" type="Control"] 15 | layout_mode = 3 16 | anchors_preset = 15 17 | anchor_right = 1.0 18 | anchor_bottom = 1.0 19 | grow_horizontal = 2 20 | grow_vertical = 2 21 | script = ExtResource("1_r74va") 22 | 23 | [node name="ColorRect" type="ColorRect" parent="."] 24 | layout_mode = 1 25 | anchors_preset = 15 26 | anchor_right = 1.0 27 | anchor_bottom = 1.0 28 | grow_horizontal = 2 29 | grow_vertical = 2 30 | color = Color(0, 0, 0, 1) 31 | 32 | [node name="Text" type="VBoxContainer" parent="."] 33 | layout_mode = 0 34 | offset_left = 30.0 35 | offset_top = 30.0 36 | offset_right = 1110.0 37 | offset_bottom = 196.0 38 | 39 | [node name="Title" type="Label" parent="Text"] 40 | layout_mode = 2 41 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 42 | theme_override_fonts/font = ExtResource("1_k4bvx") 43 | theme_override_font_sizes/font_size = 50 44 | text = "Installing Python..." 45 | 46 | [node name="RichTextLabel" type="RichTextLabel" parent="Text"] 47 | custom_minimum_size = Vector2(1220, 500) 48 | layout_mode = 2 49 | theme_override_colors/default_color = Color(0.0588235, 1, 0.192157, 1) 50 | theme_override_fonts/normal_font = ExtResource("1_k4bvx") 51 | theme_override_font_sizes/normal_font_size = 25 52 | scroll_following = true 53 | 54 | [node name="Back" type="Button" parent="."] 55 | custom_minimum_size = Vector2(100, 50) 56 | layout_mode = 1 57 | anchors_preset = 3 58 | anchor_left = 1.0 59 | anchor_top = 1.0 60 | anchor_right = 1.0 61 | anchor_bottom = 1.0 62 | offset_left = -160.0 63 | offset_top = -80.0 64 | offset_right = -30.0 65 | offset_bottom = -30.0 66 | grow_horizontal = 0 67 | grow_vertical = 0 68 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 69 | theme_override_fonts/font = ExtResource("1_k4bvx") 70 | theme_override_font_sizes/font_size = 30 71 | theme_override_styles/normal = SubResource("StyleBoxFlat_tfdvm") 72 | theme_override_styles/hover = SubResource("StyleBoxFlat_tfdvm") 73 | theme_override_styles/pressed = SubResource("StyleBoxFlat_tfdvm") 74 | theme_override_styles/disabled = SubResource("StyleBoxFlat_tfdvm") 75 | theme_override_styles/focus = SubResource("StyleBoxFlat_tfdvm") 76 | text = "Go Back" 77 | 78 | [connection signal="pressed" from="Back" to="." method="_on_back_pressed"] 79 | -------------------------------------------------------------------------------- /Scenes/Bonds/arromatic_bond_3d.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://bs6jwt5iefgeh"] 2 | 3 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_skd1d"] 4 | albedo_color = Color(1, 1, 0, 1) 5 | 6 | [node name="Arromatic Bond 3D" type="Node3D"] 7 | 8 | [node name="Node3D" type="Node3D" parent="."] 9 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0) 10 | 11 | [node name="CSGCylinder3D" type="CSGCylinder3D" parent="Node3D"] 12 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.35, 0) 13 | radius = 0.1 14 | height = 0.05 15 | 16 | [node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Node3D"] 17 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.15, 0) 18 | radius = 0.1 19 | height = 0.05 20 | 21 | [node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Node3D"] 22 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0) 23 | radius = 0.1 24 | height = 0.05 25 | 26 | [node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Node3D"] 27 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.25, 0) 28 | radius = 0.1 29 | height = 0.05 30 | 31 | [node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Node3D"] 32 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0) 33 | radius = 0.1 34 | height = 0.05 35 | 36 | [node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Node3D"] 37 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.45, 0) 38 | radius = 0.1 39 | height = 0.05 40 | 41 | [node name="CSGCylinder3D7" type="CSGCylinder3D" parent="Node3D"] 42 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0) 43 | radius = 0.1 44 | height = 0.05 45 | 46 | [node name="CSGCylinder3D8" type="CSGCylinder3D" parent="Node3D"] 47 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0) 48 | radius = 0.1 49 | height = 0.05 50 | 51 | [node name="CSGCylinder3D9" type="CSGCylinder3D" parent="Node3D"] 52 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.15, 0) 53 | radius = 0.1 54 | height = 0.05 55 | 56 | [node name="CSGCylinder3D10" type="CSGCylinder3D" parent="Node3D"] 57 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.35, 0) 58 | radius = 0.1 59 | height = 0.05 60 | 61 | [node name="CSGCylinder3D2" type="CSGCylinder3D" parent="."] 62 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, 0, 0) 63 | radius = 0.1 64 | height = 1.0 65 | 66 | [node name="Highlight" type="Node3D" parent="."] 67 | 68 | [node name="Highlight" type="CSGCylinder3D" parent="Highlight"] 69 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, 0, 0) 70 | transparency = 0.8 71 | radius = 0.2 72 | height = 1.0 73 | material = SubResource("StandardMaterial3D_skd1d") 74 | 75 | [node name="Highlight2" type="CSGCylinder3D" parent="Highlight"] 76 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, 0, 0) 77 | transparency = 0.8 78 | radius = 0.2 79 | height = 1.0 80 | material = SubResource("StandardMaterial3D_skd1d") 81 | -------------------------------------------------------------------------------- /Scenes/Python Installation/python_installer.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | @onready var title = $Text/Title 4 | @onready var rich_text_label = $Text/RichTextLabel 5 | @onready var back = $Back 6 | 7 | var venv_thread = Thread.new() 8 | 9 | # Called when the node enters the scene tree for the first time. 10 | func _ready(): 11 | Globals.modulate_highlight.connect(_on_update_colors) 12 | Globals.modulate_highlight.emit() 13 | venv_thread.start(_setup_venv_thread) 14 | 15 | func _on_update_colors(): 16 | title.add_theme_color_override("font_color", Globals.selected_color) 17 | rich_text_label.add_theme_color_override("default_color", Globals.selected_color) 18 | back.add_theme_color_override("font_color", Globals.selected_color) 19 | 20 | # Called every frame. 'delta' is the elapsed time since the previous frame. 21 | func _process(delta): 22 | pass 23 | 24 | func _setup_venv_thread(): 25 | #var executables = ["python", "python3", "python3.11"] 26 | var executables = ["python3.11", "python3", "python"] 27 | var args = ["-m", "venv", ".venv"] 28 | var output = [] 29 | var error = 1 # Initialize with a non-OK value 30 | for executable in executables: 31 | output.clear() # Clear previous output 32 | rich_text_label.add_text.call_deferred("Installing Python via " + executable + " " + " ".join(args) + "\n") 33 | error = OS.execute(executable, args, output, true, false) 34 | for line in output: 35 | rich_text_label.add_text.call_deferred(line) 36 | if error == OK: 37 | print(executable + " used to create virtual environment.") 38 | rich_text_label.add_text.call_deferred(executable + " used to create virtual environment.\n") 39 | install_libraries(executable) 40 | Globals.venv_exists = true 41 | rich_text_label.add_text.call_deferred("Virtual environment created successfully.\n") 42 | return # Exit the function upon success 43 | else: 44 | print("Attempt with " + executable + " failed.") 45 | rich_text_label.add_text.call_deferred("Attempt with " + executable + " failed.\n") 46 | print(output) 47 | for line in output: 48 | if "ensurepip" in line: 49 | return 50 | if error != OK: 51 | print("Failed to create virtual environment with any Python executable.\n") 52 | rich_text_label.add_text.call_deferred("Failed to create virtual environment with any Python executable.\n") 53 | 54 | 55 | func install_libraries(executable): 56 | var venv_path = ProjectSettings.globalize_path("res://.venv") 57 | var os_name = OS.get_name() 58 | var pip_path = "" 59 | if os_name == "Windows": 60 | pip_path = venv_path + "/Scripts/pip" 61 | else: 62 | pip_path = venv_path + "/bin/pip" 63 | var libraries = ["rdkit"] 64 | var args = ["install"] + libraries 65 | var output = [] 66 | rich_text_label.add_text.call_deferred("Installing libraries via " + pip_path + " " + " ".join(args) + "\n") 67 | var error = OS.execute(pip_path, args, output, true, false) 68 | for line in output: 69 | rich_text_label.add_text.call_deferred(line) 70 | if error == OK: 71 | print("Libraries installed successfully.") 72 | rich_text_label.add_text.call_deferred("Libraries installed successfully.\n") 73 | for line in output: 74 | print(line) 75 | else: 76 | print("Failed to install libraries.") 77 | rich_text_label.add_text.call_deferred("Failed to install libraries.\n") 78 | print(error) 79 | print(output) 80 | 81 | 82 | func _on_back_pressed(): 83 | get_tree().change_scene_to_file("res://Scenes/Main Menu/python_runner.tscn") 84 | -------------------------------------------------------------------------------- /Scenes/Atoms/base_atom.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var atom_symbol = $"Atom Symbol" 4 | @onready var atom_charge = $"Atom Symbol/Atom Charge" 5 | @onready var atom_index = $"Atom Symbol/Atom Index" 6 | @onready var highlight = $Highlight 7 | 8 | 9 | # Colors 10 | const WHITE = Color8(255,255,255) 11 | const BLACK = Color8(0,0,0) 12 | const RED = Color8(255, 0, 0) 13 | const GREEN = Color8(0, 255, 0) 14 | const BLUE = Color8(0, 0, 255) 15 | const DARK_RED = Color8(139,0,0) 16 | const DARK_VIOLET = Color8(148,0,211) 17 | const CYAN = Color8(0,255,255) 18 | const ORANGE = Color8(255,165,0) 19 | const YELLOW = Color8(255,255,0) 20 | const BEIGE = Color8(245,245,220) 21 | const VIOLET = Color8(238,130,238) 22 | const DARK_GREEN = Color8(0,100,0) 23 | const GRAY = Color8(128,128,128) 24 | const DARK_ORANGE = Color8(255,140,0) 25 | const PINK = Color8(255,192,203) 26 | 27 | func _ready(): 28 | highlight.hide() 29 | atom_index.hide() 30 | Globals.modulate_highlight.connect(_on_modulate_highlight) 31 | 32 | func _on_modulate_highlight(): 33 | highlight.modulate = Color(Globals.selected_color, .5) 34 | 35 | func update_atom(atom_type:String, charge: int, index): 36 | atom_charge.visible = false 37 | atom_index.text = str(index) 38 | if atom_type == "C": 39 | atom_symbol.text = "" 40 | return 41 | atom_symbol.text = atom_type 42 | atom_type = atom_type.capitalize().strip_edges() 43 | # Color text 44 | if atom_type in ["H"]: 45 | atom_symbol.add_theme_color_override("font_color", WHITE) 46 | elif atom_type in ["N"]: 47 | atom_symbol.add_theme_color_override("font_color", BLUE) 48 | elif atom_type in ["O"]: 49 | atom_symbol.add_theme_color_override("font_color", RED) 50 | elif atom_type in ["F", "Cl"]: 51 | atom_symbol.add_theme_color_override("font_color", GREEN) 52 | elif atom_type in ["Br"]: 53 | atom_symbol.add_theme_color_override("font_color", DARK_RED) 54 | elif atom_type in ["I"]: 55 | atom_symbol.add_theme_color_override("font_color", DARK_VIOLET) 56 | elif atom_type in ["He", "Ne", "Ar", "Kr", "Xe"]: 57 | atom_symbol.add_theme_color_override("font_color", CYAN) 58 | elif atom_type in ["P"]: 59 | atom_symbol.add_theme_color_override("font_color", ORANGE) 60 | elif atom_type in ["S"]: 61 | atom_symbol.add_theme_color_override("font_color", YELLOW) 62 | elif atom_type in "B": 63 | atom_symbol.add_theme_color_override("font_color", BEIGE) 64 | elif atom_type in ["Li", "Na", "K", "Rb", "Cs", "Fr"]: 65 | atom_symbol.add_theme_color_override("font_color", VIOLET) 66 | elif atom_type in ["Be", "Mg", "Ca", "Sr", "Ba", "Ra"]: 67 | atom_symbol.add_theme_color_override("font_color", DARK_GREEN) 68 | elif atom_type in ["Ti"]: 69 | atom_symbol.add_theme_color_override("font_color", GRAY) 70 | elif atom_type in ["Fe"]: 71 | atom_symbol.add_theme_color_override("font_color", DARK_ORANGE) 72 | else: 73 | atom_symbol.add_theme_color_override("font_color", PINK) 74 | atom_charge.visible = true 75 | if charge == 0: 76 | atom_charge.visible = false 77 | elif charge == -1: 78 | atom_charge.text = "-" 79 | elif charge == 1: 80 | atom_charge.text = "+" 81 | else: 82 | atom_charge.text = str(charge) 83 | 84 | func turn_on_highlight(): 85 | highlight.show() 86 | 87 | func turn_off_highlight(): 88 | highlight.hide() 89 | 90 | 91 | func _on_static_body_2d_input_event(_viewport, event, _shape_idx): 92 | if event is InputEventMouseButton and event.pressed == true: 93 | Globals.node_clicked.emit(self) 94 | 95 | func turn_off_index(): 96 | if atom_index.text != "": 97 | atom_index.hide() 98 | 99 | func turn_on_index(): 100 | if atom_index.text != "": 101 | atom_index.show() 102 | -------------------------------------------------------------------------------- /Scenes/Atoms/base_atom_3d.gd: -------------------------------------------------------------------------------- 1 | extends CSGSphere3D 2 | 3 | @onready var atom_symbol = $"Atom Symbol" 4 | @onready var atom_charge = $"Atom Symbol/Atom Charge" 5 | @onready var atom_index = $"Atom Symbol/Atom Index" 6 | @onready var highlight = $Highlight 7 | 8 | # Colors 9 | const WHITE = Color8(255,255,255) 10 | const BLACK = Color8(0,0,0) 11 | const RED = Color8(255, 0, 0) 12 | const GREEN = Color8(0, 255, 0) 13 | const BLUE = Color8(0, 0, 255) 14 | const DARK_RED = Color8(139,0,0) 15 | const DARK_VIOLET = Color8(148,0,211) 16 | const CYAN = Color8(0,255,255) 17 | const ORANGE = Color8(255,165,0) 18 | const YELLOW = Color8(255,255,0) 19 | const BEIGE = Color8(245,245,220) 20 | const VIOLET = Color8(238,130,238) 21 | const DARK_GREEN = Color8(0,100,0) 22 | const GRAY = Color8(128,128,128) 23 | const DARK_ORANGE = Color8(255,140,0) 24 | const PINK = Color8(255,192,203) 25 | 26 | const BASE_COLOR = Color(Globals.NEON_GREEN, .5) 27 | var highlight_color = Color(Globals.NEON_GREEN, .5) 28 | 29 | func _ready(): 30 | highlight.hide() 31 | atom_index.hide() 32 | Globals.modulate_highlight.connect(_on_modulate_highlight) 33 | Globals.camera_position.connect(_on_camera_position) 34 | material = material.duplicate() 35 | 36 | func _on_modulate_highlight(): 37 | highlight.modulate = Color(Globals.selected_color, .1) 38 | 39 | func update_atom(atom_type:String, charge: int, index: int): 40 | atom_charge.visible = false 41 | atom_index.text = str(index) 42 | var atom_material: Material = get_material() 43 | if atom_type == "C": 44 | atom_symbol.text = "" 45 | atom_material.albedo_color = WHITE 46 | return 47 | atom_symbol.text = atom_type 48 | atom_type = atom_type.capitalize().strip_edges() 49 | # Color text 50 | if atom_type in ["H"]: 51 | atom_symbol.modulate = WHITE 52 | atom_material.albedo_color = WHITE 53 | elif atom_type in ["N"]: 54 | atom_symbol.modulate = BLUE 55 | atom_material.albedo_color = BLUE 56 | elif atom_type in ["O"]: 57 | atom_symbol.modulate = RED 58 | atom_material.albedo_color = RED 59 | elif atom_type in ["F", "Cl"]: 60 | atom_symbol.modulate = GREEN 61 | atom_material.albedo_color = GREEN 62 | elif atom_type in ["Br"]: 63 | atom_symbol.modulate = DARK_RED 64 | atom_material.albedo_color = DARK_RED 65 | elif atom_type in ["I"]: 66 | atom_symbol.modulate = DARK_VIOLET 67 | atom_material.albedo_color = DARK_VIOLET 68 | elif atom_type in ["He", "Ne", "Ar", "Kr", "Xe"]: 69 | atom_symbol.modulate = CYAN 70 | atom_material.albedo_color = CYAN 71 | elif atom_type in ["P"]: 72 | atom_symbol.modulate = ORANGE 73 | atom_material.albedo_color = ORANGE 74 | elif atom_type in ["S"]: 75 | atom_symbol.modulate = YELLOW 76 | atom_material.albedo_color = YELLOW 77 | elif atom_type in "B": 78 | atom_symbol.modulate = BEIGE 79 | atom_material.albedo_color = BEIGE 80 | elif atom_type in ["Li", "Na", "K", "Rb", "Cs", "Fr"]: 81 | atom_symbol.modulate = VIOLET 82 | atom_material.albedo_color = VIOLET 83 | elif atom_type in ["Be", "Mg", "Ca", "Sr", "Ba", "Ra"]: 84 | atom_symbol.modulate = DARK_GREEN 85 | atom_material.albedo_color = DARK_GREEN 86 | elif atom_type in ["Ti"]: 87 | atom_symbol.modulate = GRAY 88 | atom_material.albedo_color = GRAY 89 | elif atom_type in ["Fe"]: 90 | atom_symbol.modulate = DARK_ORANGE 91 | atom_material.albedo_color = DARK_ORANGE 92 | else: 93 | atom_symbol.modulate = PINK 94 | atom_material.albedo_color = PINK 95 | atom_charge.visible = true 96 | if charge == 0: 97 | atom_charge.visible = false 98 | elif charge == -1: 99 | atom_charge.text = "-" 100 | elif charge == 1: 101 | atom_charge.text = "+" 102 | else: 103 | atom_charge.text = str(charge) 104 | 105 | func turn_on_highlight(): 106 | highlight.show() 107 | 108 | func turn_off_highlight(): 109 | highlight.hide() 110 | 111 | func _on_camera_position(camera_pos: Vector3): 112 | look_at(camera_pos) 113 | 114 | 115 | func _on_static_body_3d_input_event(_camera, event, _position, _normal, _shape_idx): 116 | if event is InputEventMouseButton and event.button_index == 1 and event.pressed == true: 117 | Globals.node_clicked.emit(self) 118 | 119 | func turn_off_index(): 120 | if atom_index.text != "": 121 | atom_index.hide() 122 | 123 | func turn_on_index(): 124 | if atom_index.text != "": 125 | atom_index.show() 126 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Linux/X11" 4 | platform="Linux/X11" 5 | runnable=true 6 | dedicated_server=false 7 | custom_features="" 8 | export_filter="all_resources" 9 | include_filter="" 10 | exclude_filter="" 11 | export_path="Exports/SmiNav_Linux/SmiNav.x86_64" 12 | encryption_include_filters="" 13 | encryption_exclude_filters="" 14 | encrypt_pck=false 15 | encrypt_directory=false 16 | 17 | [preset.0.options] 18 | 19 | custom_template/debug="" 20 | custom_template/release="" 21 | debug/export_console_wrapper=1 22 | binary_format/embed_pck=false 23 | texture_format/bptc=true 24 | texture_format/s3tc=true 25 | texture_format/etc=false 26 | texture_format/etc2=false 27 | binary_format/architecture="x86_64" 28 | ssh_remote_deploy/enabled=false 29 | ssh_remote_deploy/host="user@host_ip" 30 | ssh_remote_deploy/port="22" 31 | ssh_remote_deploy/extra_args_ssh="" 32 | ssh_remote_deploy/extra_args_scp="" 33 | ssh_remote_deploy/run_script="#!/usr/bin/env bash 34 | export DISPLAY=:0 35 | unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" 36 | \"{temp_dir}/{exe_name}\" {cmd_args}" 37 | ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash 38 | kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") 39 | rm -rf \"{temp_dir}\"" 40 | 41 | [preset.1] 42 | 43 | name="Linux Arm" 44 | platform="Linux/X11" 45 | runnable=false 46 | dedicated_server=false 47 | custom_features="" 48 | export_filter="all_resources" 49 | include_filter="" 50 | exclude_filter="" 51 | export_path="Exports/SmiNav_Linux_Arm/SmiNav.arm64" 52 | encryption_include_filters="" 53 | encryption_exclude_filters="" 54 | encrypt_pck=false 55 | encrypt_directory=false 56 | 57 | [preset.1.options] 58 | 59 | custom_template/debug="" 60 | custom_template/release="" 61 | debug/export_console_wrapper=1 62 | binary_format/embed_pck=false 63 | texture_format/bptc=true 64 | texture_format/s3tc=true 65 | texture_format/etc=false 66 | texture_format/etc2=false 67 | binary_format/architecture="arm64" 68 | ssh_remote_deploy/enabled=false 69 | ssh_remote_deploy/host="user@host_ip" 70 | ssh_remote_deploy/port="22" 71 | ssh_remote_deploy/extra_args_ssh="" 72 | ssh_remote_deploy/extra_args_scp="" 73 | ssh_remote_deploy/run_script="#!/usr/bin/env bash 74 | export DISPLAY=:0 75 | unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" 76 | \"{temp_dir}/{exe_name}\" {cmd_args}" 77 | ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash 78 | kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") 79 | rm -rf \"{temp_dir}\"" 80 | 81 | [preset.2] 82 | 83 | name="Windows Desktop" 84 | platform="Windows Desktop" 85 | runnable=true 86 | dedicated_server=false 87 | custom_features="" 88 | export_filter="all_resources" 89 | include_filter="" 90 | exclude_filter="" 91 | export_path="Exports/SmiNav_Windows/SmiNav.exe" 92 | encryption_include_filters="" 93 | encryption_exclude_filters="" 94 | encrypt_pck=false 95 | encrypt_directory=false 96 | 97 | [preset.2.options] 98 | 99 | custom_template/debug="" 100 | custom_template/release="" 101 | debug/export_console_wrapper=1 102 | binary_format/embed_pck=false 103 | texture_format/bptc=true 104 | texture_format/s3tc=true 105 | texture_format/etc=false 106 | texture_format/etc2=false 107 | binary_format/architecture="x86_64" 108 | codesign/enable=false 109 | codesign/timestamp=true 110 | codesign/timestamp_server_url="" 111 | codesign/digest_algorithm=1 112 | codesign/description="" 113 | codesign/custom_options=PackedStringArray() 114 | application/modify_resources=true 115 | application/icon="res://sminav_logo.ico" 116 | application/console_wrapper_icon="" 117 | application/icon_interpolation=4 118 | application/file_version="" 119 | application/product_version="" 120 | application/company_name="" 121 | application/product_name="" 122 | application/file_description="" 123 | application/copyright="" 124 | application/trademarks="" 125 | application/export_angle=0 126 | ssh_remote_deploy/enabled=false 127 | ssh_remote_deploy/host="user@host_ip" 128 | ssh_remote_deploy/port="22" 129 | ssh_remote_deploy/extra_args_ssh="" 130 | ssh_remote_deploy/extra_args_scp="" 131 | ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' 132 | $action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' 133 | $trigger = New-ScheduledTaskTrigger -Once -At 00:00 134 | $settings = New-ScheduledTaskSettingsSet 135 | $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings 136 | Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true 137 | Start-ScheduledTask -TaskName godot_remote_debug 138 | while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } 139 | Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" 140 | ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue 141 | Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue 142 | Remove-Item -Recurse -Force '{temp_dir}'" 143 | -------------------------------------------------------------------------------- /Scenes/Main Menu/Python_Runner.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | var system_ready = false 4 | @onready var smiles_input = $"Options/Inputs/Smiles Input" 5 | @onready var _3d = $"Options/Inputs/VBoxContainer/3D" 6 | @onready var _2d = $"Options/Inputs/VBoxContainer/2D" 7 | @onready var python_install = $"Python Install" 8 | @onready var title = $Text/Title 9 | @onready var github = $Github 10 | @onready var output = $Options/Output 11 | @onready var exit = $Options/Exit 12 | 13 | @onready var color_buttons = [$Options/Colors/Red, $Options/Colors/Yellow, $Options/Colors/Green, $Options/Colors/Purple] 14 | # Called when the node enters the scene tree for the first time. 15 | func _ready(): 16 | if not Globals.venv_exists: 17 | _3d.disabled = true 18 | _2d.disabled = true 19 | python_install.visible = true 20 | output.text = "No python virtual environment\nfound" 21 | Globals.modulate_highlight.connect(_on_update_colors) 22 | Globals.modulate_highlight.emit() 23 | _on_enter_colors() 24 | 25 | func _on_enter_colors(): 26 | var selected_button = null 27 | if Globals.selected_color == Globals.NEON_RED: 28 | selected_button = color_buttons[0] 29 | elif Globals.selected_color == Globals.NEON_YELLOW: 30 | selected_button = color_buttons[1] 31 | elif Globals.selected_color == Globals.NEON_GREEN: 32 | selected_button = color_buttons[2] 33 | elif Globals.selected_color == Globals.NEON_PURPLE: 34 | selected_button = color_buttons[3] 35 | else: 36 | selected_button = color_buttons[2] 37 | toggle_off_color_buttons(selected_button) 38 | 39 | 40 | func _on_update_colors(): 41 | title.add_theme_color_override("font_color", Globals.selected_color) 42 | smiles_input.add_theme_color_override("font_color", Globals.selected_color) 43 | smiles_input.add_theme_color_override("font_placeholder_color", Globals.selected_color) 44 | _2d.add_theme_color_override("font_color", Globals.selected_color) 45 | _3d.add_theme_color_override("font_color", Globals.selected_color) 46 | python_install.add_theme_color_override("font_color", Globals.selected_color) 47 | github.add_theme_color_override("font_color", Globals.selected_color) 48 | exit.add_theme_color_override("font_color", Globals.selected_color) 49 | 50 | 51 | # Called every frame. 'delta' is the elapsed time since the previous frame. 52 | func _process(_delta): 53 | if not system_ready and Globals.venv_exists: 54 | _2d.disabled = false 55 | _3d.disabled = false 56 | system_ready = true 57 | python_install.visible = false 58 | output.text = "" 59 | 60 | func _on_two_d_pressed(): 61 | if smiles_input.text == "": 62 | output.text = "Enter a smiles" 63 | return 64 | if Globals.venv_exists: 65 | _2d.disabled = false 66 | var ran = await Globals.run_python_script("rdkit_script.py", [smiles_input.text]) 67 | if ran is bool and ran == false: 68 | output.text = "Invalid SMILES String" 69 | else: 70 | get_tree().change_scene_to_file("res://Scenes/2D Visualizer/2d_smile_visualizer.tscn") 71 | else: 72 | output.text = "Wait, Venv is not ready yet" 73 | 74 | func _on_three_d_pressed(): 75 | if smiles_input.text == "": 76 | output.text = "Enter a smiles" 77 | return 78 | if Globals.venv_exists: 79 | _3d.disabled = false 80 | await Globals.run_python_script("rdkit_script.py", [smiles_input.text]) 81 | # Check file for elements 82 | #var base_path = ProjectSettings.globalize_path("res://") 83 | #var elements_file = FileAccess.open(base_path + "elements.txt",FileAccess.READ) 84 | var ran = await Globals.run_python_script("rdkit_script.py", [smiles_input.text]) 85 | if ran is bool and ran == false: 86 | output.text = "Invalid SMILES String" 87 | else: 88 | get_tree().change_scene_to_file("res://Scenes/3D Visualizer/3d_smile_visualizer.tscn") 89 | else: 90 | output.text = "Wait, Venv is not ready yet" 91 | 92 | func _on_python_install_pressed(): 93 | get_tree().change_scene_to_file("res://Scenes/Python Installation/python_installer.tscn") 94 | 95 | func _on_red_pressed(): 96 | var selected_button = color_buttons[0] 97 | Globals.selected_color = Globals.NEON_RED 98 | toggle_off_color_buttons(selected_button) 99 | Globals.modulate_highlight.emit() 100 | 101 | func _on_yellow_pressed(): 102 | var selected_button = color_buttons[1] 103 | Globals.selected_color = Globals.NEON_YELLOW 104 | toggle_off_color_buttons(selected_button) 105 | Globals.modulate_highlight.emit() 106 | 107 | 108 | func _on_green_pressed(): 109 | var selected_button = color_buttons[2] 110 | Globals.selected_color = Globals.NEON_GREEN 111 | toggle_off_color_buttons(selected_button) 112 | Globals.modulate_highlight.emit() 113 | 114 | 115 | func _on_purple_pressed(): 116 | var selected_button = color_buttons[3] 117 | Globals.selected_color = Globals.NEON_PURPLE 118 | toggle_off_color_buttons(selected_button) 119 | Globals.modulate_highlight.emit() 120 | 121 | func toggle_off_color_buttons(selected_button): 122 | for button in color_buttons: 123 | if button == selected_button: 124 | button.button_pressed = true 125 | continue 126 | button.button_pressed = false 127 | 128 | 129 | func _on_exit_pressed(): 130 | get_tree().quit() 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SmiNav 2 | ### SMILES visualization with Godot 3 | 4 | > [!CAUTION] 5 | > We did this for fun and to learn how to create interfaces in Godot. This program is not thoroughly tested and there are likely to be a variety of SMILES parsing issues and edge-cases that do not visualize correctly. We do not have immediate plans to continue this project, though any feedback and bug reports are welcome. 6 | 7 | SmiNav (SMILES Navigator) is a GUI visualizer for parts of the SMILES syntax (atoms, neighbors, and rings). It was inspired by Andrew Dalke's [smiview](https://hg.sr.ht/~dalke/smiview). It uses the [RDKit](https://www.rdkit.org/) library in Python to parse the SMILES string and return mol coordinates and properties that are then used to create a 2D or 3D visualization of the molecule. The visualization is done using the [Godot game engine](https://godotengine.org/). 8 | 9 | > Please see the [Godot License](https://godotengine.org/license/) 10 | > 11 | > Please see the [RDkit License](https://github.com/rdkit/rdkit?tab=BSD-3-Clause-1-ov-file#readme) 12 | > 13 | > Please see the [SmiView by Andrew Dalke License](https://hg.sr.ht/~dalke/smiview/browse/LICENSE.txt?rev=tip) 14 | 15 | To use SmiNav, input a SMILES string, then click the visualization dimension you wish to view. Upon clicking on an atom in the string, the corresponding atom in the depiction will then get highlighted. The reverse works as well; that is, click on an atom in the depiction and the corresponding atom in the SMILES string will get highlighted. 16 | 17 | ## Features 18 | * 2D and 3D visualization of SMILES strings 19 | * Interactive highlighting of atoms in the SMILES string and depiction 20 | * Atom properties (e.g., charge, SMILES Index) are shown in the SMILES string and depiction 21 | * Ring properties (e.g., aromaticity) and ring closures are shown in the SMILES string and depiction 22 | * Branches of the SMILES string are shown in the depiction 23 | * Highlighting of explicit hydrogens in the SMILES string and depiction 24 | 25 | ## Main Screen 26 | 27 | ![](Images/MainScreen.png) 28 | 29 | ## 2D 30 | 31 | ![](Images/2D.png) 32 | 33 | ## 3D 34 | 35 | ![](Images/3D.png) 36 | 37 | ## Installation of source code in godot 38 | 39 | > [!NOTE] 40 | > For the program to run, you must have Python 3.7 or greater installed and Python virtual environments installed. 41 | 42 | 1. Download the source code from the repository 43 | `git clone https://github.com/UA-Libraries-Research-Data-Services/SmiNav.git` 44 | 2. Open Godot and click on `Import` 45 | 3. Navigate to the `SmiNav` folder and click on `Open` on the `project.godot` file 46 | 47 | ### To run the program 48 | 49 | In the Godot editor, click on the `Play` button to run `►` the program on the top right corner of the window. 50 | 51 | ## Exporting Binaries with Godot 52 | 53 | To create standalone binaries of the SmiNav application for your operating system: 54 | 55 | 1. **Install Export Templates**: 56 | - In Godot, go to `Editor > Manage Export Templates` and download the official templates for your Godot version. 57 | 58 | 2. **Install Export Presets**: 59 | - Open your SmiNav project in Godot. 60 | - Go to `Project > Export`. 61 | - Click `Add` and select the platform you want to export for (e.g., Windows, Linux, MacOS). 62 | - Set the export path and other relevant options (e.g., binary name, icon, etc.). 63 | 64 | 3. **Configure Virtual Environment Requirements**: 65 | - Make sure that Python and RDKit dependencies are met as outlined in the section below. 66 | 67 | 4. **Export**: 68 | - Click `Export Project` to create the binary. 69 | - The output binary will be placed in the directory you specified in the export preset. 70 | 71 | > **Note:** If the exported binary does not detect Python automatically, ensure your `.venv` folder exists and is properly configured in the application directory. 72 | 73 | > **Important:** You must also move the `rdkit_script.py` file into the same directory as the exported binary for the program to work correctly. 74 | 75 | --- 76 | 77 | 78 | ## Advanced Installation 79 | 80 | ### Manually Setting Up a Virtual Environment 81 | 82 | If you prefer to manually set up a virtual environment within the SmiNav folder, follow these steps: 83 | 84 | 1. **Create a Virtual Environment**: 85 | - Open a terminal or command prompt and navigate to the project directory. 86 | - Run the following command to create a virtual environment (Windows users should replace `python3` with `python`): 87 | ```bash 88 | python3 -m venv .venv 89 | ``` 90 | - This will create a new directory called `.venv` in your project folder. This directory contains a local Python environment that is isolated from your system-wide Python installation. 91 | 92 | 2. **Activate the Virtual Environment**: 93 | - Once the virtual environment is created, activate it: 94 | - On Ubuntu/Debian: 95 | ```bash 96 | source .venv/bin/activate 97 | ``` 98 | - On Windows: 99 | ```cmd 100 | .venv\Scripts\activate 101 | ``` 102 | 103 | 3. **Install Dependencies**: 104 | - With the virtual environment activated, install the required Python packages: 105 | ```bash 106 | pip install rdkit 107 | ``` 108 | 109 | ## FAQ 110 | 111 | 1. If Python dependencies installation fails with SmiNav, delete the created `.venv` folder before trying again. Typical failure is not having Python 3 or Python venv installed. 112 | 113 | 2. Testing - We last tested SmiNav with Godot v.4.2.1, RDKit 2023.09.05, and Python 3.11. See also the Godot minimum requirements: https://docs.godotengine.org/en/stable/about/system_requirements.html 114 | 115 | ## Known Limitations 116 | 117 | 1. In the 2D depictions, stereochemistry is not shown 118 | 2. Currently only the atom Symbols are interactive in the SMILES string 119 | 3. Only explicit hydrogens are shown in the depictions. Valences are not automatically filled with H (e.g., an alcohol functional group would show up as -O, and not -OH) 120 | 4. Does not support individual explicit hydrogens in the SMILES string (e.g., [H] or [H][O][H]). However, explicit hydrogens are supported when attached to atoms within brackets (e.g., [CH4] as an atom property) 121 | 122 | -------------------------------------------------------------------------------- /rdkit_script.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from rdkit import rdBase, Chem, Geometry 3 | from rdkit.Chem import AllChem, Draw 4 | import re 5 | print('RDKit version: ',rdBase.rdkitVersion) 6 | 7 | elements_filename = 'elements.txt' 8 | connections_filename = 'connections.txt' 9 | alt_elements_filename = 'two_d_elements.txt' 10 | alt_connections_filename = 'two_d_connections.txt' 11 | rings_filename = 'rings.txt' 12 | branches_filename = 'branches.txt' 13 | 14 | # Thanks to Andrew Dalke for pointing this out for us 15 | # Code from https://hg.sr.ht/~dalke/smiview 16 | _smiles_lexer = re.compile(r""" 17 | (?P # These will be processed by 'tokenize_atom' 18 | \*| 19 | Cl|Br|[cnospBCNOFPSI]| # organic subset 20 | \[[^]]+\] # bracket atom 21 | ) | 22 | (?P 23 | [=#$/\\:-] 24 | ) | 25 | (?P 26 | [0-9]| # single digit 27 | %[0-9][0-9]| # two digits 28 | %\([0-9]+\) # more than two digits 29 | ) | 30 | (?P 31 | \( 32 | ) | 33 | (?P 34 | \) 35 | ) | 36 | (?P 37 | \. 38 | ) 39 | """, re.X).match 40 | 41 | 42 | def smi_tokenizer(smi): 43 | """ 44 | Tokenize a SMILES molecule or reaction 45 | """ 46 | tokens = [] 47 | pos = 0 48 | while pos < len(smi): 49 | match = _smiles_lexer(smi, pos) 50 | if not match: 51 | raise ValueError("Invalid SMILES string") 52 | pos = match.end() 53 | tokens.append(match.group()) 54 | assert smi == ''.join(tokens) 55 | return ' '.join(tokens) 56 | 57 | def check_if_smiles_contains_aromatic_atoms(smi): 58 | """ 59 | Check if a SMILES string contains aromatic atoms 60 | """ 61 | pattern = "b|c|n|o|p|s|se|as" 62 | regex = re.compile(pattern) 63 | tokens = [token for token in regex.findall(smi)] 64 | if len(tokens) > 0: 65 | return True 66 | return False 67 | 68 | 69 | def smiles_to_files(smiles): 70 | mol = Chem.MolFromSmiles(smiles) 71 | if not mol: 72 | raise ValueError("Invalid SMILES string") 73 | mol = Chem.rdmolops.AddHs(mol, explicitOnly = True) 74 | 75 | if not check_if_smiles_contains_aromatic_atoms(smiles): 76 | Chem.Kekulize(mol) 77 | 78 | 79 | AllChem.EmbedMolecule(mol) 80 | 81 | smiles_tokens = smi_tokenizer(smiles) 82 | 83 | 84 | # extract the smiles string from the molecule 85 | # smi_string = Chem.MolToSmiles(mol) 86 | with open('smiles.txt', 'w') as smiles_file: 87 | # print the original SMILES string 88 | smiles_file.write(smiles_tokens + '\n') 89 | 90 | non_existent_atoms = [] 91 | 92 | # Output elements and their positions in 3d space 93 | with open(elements_filename, 'w') as elements_file: 94 | # C1=CN=C(N=C1)[NH2+]S(=[OH+])(=O)C2=CC=CC=C2N.[Ag+] 95 | 96 | # Get the largest fragment 97 | frags = Chem.GetMolFrags(mol, asMols=False) 98 | largest_frag_atoms = max(frags, key=len) 99 | 100 | 101 | for atom in mol.GetAtoms(): 102 | position = mol.GetConformer().GetAtomPosition(atom.GetIdx()) 103 | atom_charge = atom.GetFormalCharge() 104 | if atom.GetIdx() in largest_frag_atoms: 105 | elements_file.write(f"{atom.GetSymbol()} {atom.GetIdx()} {position.x} {position.y} {position.z} {atom_charge}\n") 106 | else: 107 | # if the atom is not in the largest fragment, set its position to -1 108 | elements_file.write(f"{atom.GetSymbol()} {atom.GetIdx()} -1 -1 -1 {atom_charge}\n") 109 | non_existent_atoms.append(atom.GetIdx()) 110 | 111 | # Output connections 112 | with open(connections_filename, 'w') as connections_file: 113 | for bond in mol.GetBonds(): 114 | # Check if the bond is between atoms that don't exist in the largest fragment 115 | if bond.GetBeginAtomIdx() in non_existent_atoms or bond.GetEndAtomIdx() in non_existent_atoms: 116 | continue 117 | connections_file.write(f"{bond.GetBeginAtomIdx()} - {bond.GetEndAtomIdx()} - {bond.GetBondTypeAsDouble()}\n") 118 | 119 | # Output elements and their positions in 2d space 120 | Chem.rdCoordGen.AddCoords(mol) 121 | drawer = Draw.MolDraw2DCairo(1280, 720) 122 | drawer.drawOptions().fixedBondLength = 40 123 | drawer.DrawMolecule(mol) 124 | drawer.FinishDrawing() 125 | 126 | conf = mol.GetConformer() 127 | with open(alt_elements_filename, 'w') as alt_elements_file: 128 | for atom in mol.GetAtoms(): 129 | position = conf.GetAtomPosition(atom.GetIdx()) 130 | atom_charge = atom.GetFormalCharge() 131 | pos_point = Geometry.Point2D(position.x, position.y) 132 | dpos = drawer.GetDrawCoords(pos_point) 133 | alt_elements_file.write(f"{atom.GetSymbol()} {atom.GetIdx()} {dpos.x} {dpos.y} {atom_charge}\n") 134 | 135 | # Output connections 136 | with open(alt_connections_filename, 'w') as connections_file: 137 | for bond in mol.GetBonds(): 138 | # Check if the bond is between atoms that don't exist in the largest fragment 139 | connections_file.write(f"{bond.GetBeginAtomIdx()} - {bond.GetEndAtomIdx()} - {bond.GetBondTypeAsDouble()}\n") 140 | 141 | # Output the rings 142 | with open(rings_filename, 'w') as rings_file: 143 | for ring in mol.GetRingInfo().AtomRings(): 144 | rings_file.write(' '.join(map(str, ring)) + '\n') 145 | 146 | # Output the branches 147 | with open(branches_filename, 'w') as branches_file: 148 | branches = [] 149 | pos = 0 150 | atom_index = 0 151 | final_branches = [] 152 | while pos < len(smiles): 153 | match = _smiles_lexer(smiles, pos) 154 | if not match: 155 | raise ValueError("Invalid SMILES string") 156 | results = match.groupdict() 157 | if results['open_branch']: 158 | branches.append({ 159 | "smiles": "", 160 | "atom_indices": [], 161 | }) 162 | for branch in branches: 163 | branch["smiles"] += results["open_branch"] 164 | elif results['close_branch']: 165 | for branch in branches: 166 | branch["smiles"] += results["close_branch"] 167 | most_recent_branch = branches.pop() 168 | if most_recent_branch: 169 | final_branches.append(most_recent_branch) 170 | elif results['atom']: 171 | for branch in branches: 172 | branch["smiles"] += results["atom"] 173 | branch["atom_indices"].append(atom_index) 174 | atom_index += 1 175 | elif results['bond']: 176 | for branch in branches: 177 | branch["smiles"] += results["bond"] 178 | elif results['closure']: 179 | for branch in branches: 180 | branch["smiles"] += results["closure"] 181 | elif results['dot']: 182 | for branch in branches: 183 | branch["smiles"] += results["dot"] 184 | pos = match.end() 185 | for branch in final_branches: 186 | branches_file.write(f"{branch['smiles']}\t{branch['atom_indices']}\n") 187 | 188 | 189 | if __name__ == "__main__": 190 | args = sys.argv[1:] 191 | print("Runtime arguments:", args) 192 | smiles_to_files(args[0].strip()) -------------------------------------------------------------------------------- /Scenes/2D Visualizer/2d_smile_visualizer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://bpubkwguuyxa1"] 2 | 3 | [ext_resource type="Script" path="res://Scenes/2D Visualizer/2d_smile_visualizer.gd" id="1_8mkie"] 4 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="2_165ru"] 5 | 6 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_hb4i8"] 7 | bg_color = Color(0, 0, 0, 1) 8 | border_width_left = 2 9 | border_width_top = 2 10 | border_width_right = 2 11 | border_width_bottom = 2 12 | border_color = Color(1, 1, 1, 1) 13 | 14 | [node name="2D SMILE Visualizer" type="Node2D"] 15 | script = ExtResource("1_8mkie") 16 | 17 | [node name="CanvasLayer" type="CanvasLayer" parent="."] 18 | layer = -1 19 | 20 | [node name="Control" type="Control" parent="CanvasLayer"] 21 | custom_minimum_size = Vector2(1280, 720) 22 | layout_mode = 3 23 | anchors_preset = 15 24 | anchor_right = 1.0 25 | anchor_bottom = 1.0 26 | grow_horizontal = 2 27 | grow_vertical = 2 28 | mouse_filter = 1 29 | 30 | [node name="ColorRect" type="ColorRect" parent="CanvasLayer/Control"] 31 | z_index = -10 32 | z_as_relative = false 33 | layout_mode = 1 34 | anchors_preset = 15 35 | anchor_right = 1.0 36 | anchor_bottom = 1.0 37 | grow_horizontal = 2 38 | grow_vertical = 2 39 | mouse_filter = 1 40 | color = Color(0, 0, 0, 1) 41 | metadata/_edit_lock_ = true 42 | 43 | [node name="SMILES Container" type="VBoxContainer" parent="CanvasLayer/Control"] 44 | layout_mode = 1 45 | anchors_preset = 5 46 | anchor_left = 0.5 47 | anchor_right = 0.5 48 | offset_left = -93.5 49 | offset_right = 93.5 50 | offset_bottom = 117.0 51 | grow_horizontal = 2 52 | alignment = 1 53 | 54 | [node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/Control/SMILES Container"] 55 | custom_minimum_size = Vector2(700, 90) 56 | layout_mode = 2 57 | size_flags_horizontal = 4 58 | size_flags_vertical = 4 59 | 60 | [node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/Control/SMILES Container/ScrollContainer"] 61 | custom_minimum_size = Vector2(700, 90) 62 | layout_mode = 2 63 | size_flags_horizontal = 4 64 | size_flags_vertical = 4 65 | alignment = 1 66 | 67 | [node name="Label" type="Label" parent="CanvasLayer/Control/SMILES Container"] 68 | layout_mode = 2 69 | theme_override_colors/font_color = Color(1, 1, 1, 1) 70 | theme_override_fonts/font = ExtResource("2_165ru") 71 | text = "Hold Ctrl To Multi-Select" 72 | horizontal_alignment = 1 73 | vertical_alignment = 1 74 | 75 | [node name="Branches Container" type="VBoxContainer" parent="CanvasLayer/Control"] 76 | layout_mode = 1 77 | anchors_preset = 1 78 | anchor_left = 1.0 79 | anchor_right = 1.0 80 | offset_left = -250.0 81 | offset_bottom = 150.0 82 | grow_horizontal = 0 83 | 84 | [node name="Label" type="Label" parent="CanvasLayer/Control/Branches Container"] 85 | layout_mode = 2 86 | theme_override_colors/font_color = Color(1, 1, 1, 1) 87 | theme_override_fonts/font = ExtResource("2_165ru") 88 | theme_override_font_sizes/font_size = 30 89 | text = "Branches " 90 | horizontal_alignment = 2 91 | vertical_alignment = 1 92 | 93 | [node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/Control/Branches Container"] 94 | custom_minimum_size = Vector2(275, 150) 95 | layout_mode = 2 96 | 97 | [node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/Control/Branches Container/ScrollContainer"] 98 | layout_mode = 2 99 | 100 | [node name="Options" type="VBoxContainer" parent="CanvasLayer/Control"] 101 | layout_mode = 1 102 | anchors_preset = 2 103 | anchor_top = 1.0 104 | anchor_bottom = 1.0 105 | offset_left = 30.0 106 | offset_top = -123.0 107 | offset_right = 212.0 108 | offset_bottom = -30.0 109 | grow_vertical = 0 110 | 111 | [node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/Control/Options"] 112 | layout_mode = 2 113 | 114 | [node name="Label" type="Label" parent="CanvasLayer/Control/Options/HBoxContainer"] 115 | layout_mode = 2 116 | theme_override_colors/font_color = Color(1, 1, 1, 1) 117 | theme_override_fonts/font = ExtResource("2_165ru") 118 | text = "Select Color: " 119 | 120 | [node name="OptionButton" type="OptionButton" parent="CanvasLayer/Control/Options/HBoxContainer"] 121 | layout_mode = 2 122 | theme_override_colors/font_color = Color(0.0588235, 1, 0.192157, 1) 123 | theme_override_fonts/font = ExtResource("2_165ru") 124 | flat = true 125 | item_count = 4 126 | selected = 2 127 | popup/item_0/text = "Red" 128 | popup/item_0/id = 0 129 | popup/item_1/text = "Purple" 130 | popup/item_1/id = 1 131 | popup/item_2/text = "Green" 132 | popup/item_2/id = 2 133 | popup/item_3/text = "Yellow" 134 | popup/item_3/id = 3 135 | 136 | [node name="Neighbors" type="CheckBox" parent="CanvasLayer/Control/Options"] 137 | layout_mode = 2 138 | theme_override_colors/font_color = Color(1, 1, 1, 1) 139 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 140 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 141 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 142 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 143 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 144 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 145 | theme_override_constants/h_separation = 10 146 | theme_override_constants/check_v_offset = 2 147 | theme_override_fonts/font = ExtResource("2_165ru") 148 | text = "Highlight Neighbors" 149 | 150 | [node name="Rings" type="CheckBox" parent="CanvasLayer/Control/Options"] 151 | layout_mode = 2 152 | theme_override_colors/font_color = Color(1, 1, 1, 1) 153 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 154 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 155 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 156 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 157 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 158 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 159 | theme_override_constants/h_separation = 10 160 | theme_override_constants/check_v_offset = 2 161 | theme_override_fonts/font = ExtResource("2_165ru") 162 | text = "Show Containing Ring(s)" 163 | 164 | [node name="Index Toggle" type="CheckBox" parent="CanvasLayer/Control/Options"] 165 | layout_mode = 2 166 | theme_override_colors/font_color = Color(1, 1, 1, 1) 167 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 168 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 169 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 170 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 171 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 172 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 173 | theme_override_constants/h_separation = 10 174 | theme_override_constants/check_v_offset = 2 175 | theme_override_fonts/font = ExtResource("2_165ru") 176 | text = "Show SMILES Index" 177 | 178 | [node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/Control"] 179 | layout_mode = 1 180 | anchors_preset = 3 181 | anchor_left = 1.0 182 | anchor_top = 1.0 183 | anchor_right = 1.0 184 | anchor_bottom = 1.0 185 | offset_left = -160.0 186 | offset_top = -80.0 187 | offset_right = -30.0 188 | offset_bottom = -30.0 189 | grow_horizontal = 0 190 | grow_vertical = 0 191 | 192 | [node name="3D Button" type="Button" parent="CanvasLayer/Control/VBoxContainer"] 193 | custom_minimum_size = Vector2(100, 50) 194 | layout_mode = 2 195 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 196 | theme_override_fonts/font = ExtResource("2_165ru") 197 | theme_override_font_sizes/font_size = 30 198 | theme_override_styles/normal = SubResource("StyleBoxFlat_hb4i8") 199 | theme_override_styles/hover = SubResource("StyleBoxFlat_hb4i8") 200 | theme_override_styles/pressed = SubResource("StyleBoxFlat_hb4i8") 201 | theme_override_styles/disabled = SubResource("StyleBoxFlat_hb4i8") 202 | theme_override_styles/focus = SubResource("StyleBoxFlat_hb4i8") 203 | text = "View 3D" 204 | 205 | [node name="Back" type="Button" parent="CanvasLayer/Control/VBoxContainer"] 206 | custom_minimum_size = Vector2(100, 50) 207 | layout_mode = 2 208 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 209 | theme_override_fonts/font = ExtResource("2_165ru") 210 | theme_override_font_sizes/font_size = 30 211 | theme_override_styles/normal = SubResource("StyleBoxFlat_hb4i8") 212 | theme_override_styles/hover = SubResource("StyleBoxFlat_hb4i8") 213 | theme_override_styles/pressed = SubResource("StyleBoxFlat_hb4i8") 214 | theme_override_styles/disabled = SubResource("StyleBoxFlat_hb4i8") 215 | theme_override_styles/focus = SubResource("StyleBoxFlat_hb4i8") 216 | text = "Go Back" 217 | 218 | [node name="Structure" type="Node2D" parent="."] 219 | z_index = 10 220 | 221 | [node name="Camera2D" type="Camera2D" parent="."] 222 | metadata/_edit_lock_ = true 223 | 224 | [connection signal="item_selected" from="CanvasLayer/Control/Options/HBoxContainer/OptionButton" to="." method="_on_option_button_item_selected"] 225 | [connection signal="pressed" from="CanvasLayer/Control/Options/Neighbors" to="." method="_on_neighbors_pressed"] 226 | [connection signal="pressed" from="CanvasLayer/Control/Options/Rings" to="." method="_on_rings_pressed"] 227 | [connection signal="toggled" from="CanvasLayer/Control/Options/Index Toggle" to="." method="_on_index_toggle_toggled"] 228 | [connection signal="pressed" from="CanvasLayer/Control/VBoxContainer/3D Button" to="." method="_on_d_button_pressed"] 229 | [connection signal="pressed" from="CanvasLayer/Control/VBoxContainer/Back" to="." method="_on_back_pressed"] 230 | -------------------------------------------------------------------------------- /Scenes/3D Visualizer/3d_smile_visualizer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=3 uid="uid://dohtrelox64e1"] 2 | 3 | [ext_resource type="Script" path="res://Scenes/3D Visualizer/3d_smile_visualizer.gd" id="1_t3wey"] 4 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="2_73ak5"] 5 | 6 | [sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_kwslx"] 7 | sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 8 | ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) 9 | 10 | [sub_resource type="Sky" id="Sky_4b45j"] 11 | sky_material = SubResource("ProceduralSkyMaterial_kwslx") 12 | 13 | [sub_resource type="Environment" id="Environment_jv7ab"] 14 | background_mode = 2 15 | sky = SubResource("Sky_4b45j") 16 | tonemap_mode = 2 17 | glow_enabled = true 18 | 19 | [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6ewh1"] 20 | albedo_color = Color(0, 0, 0, 1) 21 | 22 | [sub_resource type="PlaneMesh" id="PlaneMesh_bfiiu"] 23 | material = SubResource("StandardMaterial3D_6ewh1") 24 | size = Vector2(100000, 100000) 25 | orientation = 2 26 | 27 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_d23yw"] 28 | bg_color = Color(0, 0, 0, 1) 29 | border_width_left = 2 30 | border_width_top = 2 31 | border_width_right = 2 32 | border_width_bottom = 2 33 | border_color = Color(1, 1, 1, 1) 34 | 35 | [node name="3D SMILE Visualizer" type="Node3D"] 36 | script = ExtResource("1_t3wey") 37 | 38 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 39 | environment = SubResource("Environment_jv7ab") 40 | 41 | [node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] 42 | transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 0, 0, 0) 43 | shadow_enabled = true 44 | 45 | [node name="Camera3D" type="Camera3D" parent="."] 46 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 10) 47 | 48 | [node name="Structure" type="Node3D" parent="."] 49 | 50 | [node name="CSGMesh3D" type="CSGMesh3D" parent="."] 51 | transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -15.1236) 52 | mesh = SubResource("PlaneMesh_bfiiu") 53 | 54 | [node name="Control" type="Control" parent="."] 55 | custom_minimum_size = Vector2(1280, 720) 56 | layout_mode = 3 57 | anchors_preset = 15 58 | anchor_right = 1.0 59 | anchor_bottom = 1.0 60 | grow_horizontal = 2 61 | grow_vertical = 2 62 | mouse_filter = 1 63 | metadata/_edit_lock_ = true 64 | 65 | [node name="SMILES Container" type="VBoxContainer" parent="Control"] 66 | layout_mode = 1 67 | anchors_preset = 5 68 | anchor_left = 0.5 69 | anchor_right = 0.5 70 | offset_left = -93.5 71 | offset_right = 93.5 72 | offset_bottom = 117.0 73 | grow_horizontal = 2 74 | alignment = 1 75 | 76 | [node name="ScrollContainer" type="ScrollContainer" parent="Control/SMILES Container"] 77 | custom_minimum_size = Vector2(700, 90) 78 | layout_mode = 2 79 | size_flags_horizontal = 4 80 | size_flags_vertical = 4 81 | 82 | [node name="HBoxContainer" type="HBoxContainer" parent="Control/SMILES Container/ScrollContainer"] 83 | custom_minimum_size = Vector2(700, 90) 84 | layout_mode = 2 85 | size_flags_horizontal = 4 86 | size_flags_vertical = 4 87 | alignment = 1 88 | 89 | [node name="Label" type="Label" parent="Control/SMILES Container"] 90 | layout_mode = 2 91 | theme_override_colors/font_color = Color(1, 1, 1, 1) 92 | theme_override_fonts/font = ExtResource("2_73ak5") 93 | text = "Hold Ctrl To Multi-Select" 94 | horizontal_alignment = 1 95 | vertical_alignment = 1 96 | 97 | [node name="Label2" type="Label" parent="Control/SMILES Container"] 98 | layout_mode = 2 99 | theme_override_colors/font_color = Color(1, 1, 1, 1) 100 | theme_override_fonts/font = ExtResource("2_73ak5") 101 | text = "Hold Right Click To Rotate" 102 | horizontal_alignment = 1 103 | vertical_alignment = 1 104 | 105 | [node name="Fragment_text" type="Label" parent="Control/SMILES Container"] 106 | layout_mode = 2 107 | theme_override_colors/font_color = Color(1, 1, 1, 1) 108 | theme_override_fonts/font = ExtResource("2_73ak5") 109 | text = "Only Largest Fragment Is Displayed" 110 | horizontal_alignment = 1 111 | vertical_alignment = 1 112 | 113 | [node name="Branches Container" type="VBoxContainer" parent="Control"] 114 | layout_mode = 1 115 | anchors_preset = 1 116 | anchor_left = 1.0 117 | anchor_right = 1.0 118 | offset_left = -275.0 119 | offset_bottom = 189.0 120 | grow_horizontal = 0 121 | 122 | [node name="Label" type="Label" parent="Control/Branches Container"] 123 | layout_mode = 2 124 | theme_override_colors/font_color = Color(1, 1, 1, 1) 125 | theme_override_fonts/font = ExtResource("2_73ak5") 126 | theme_override_font_sizes/font_size = 30 127 | text = "Branches " 128 | horizontal_alignment = 2 129 | vertical_alignment = 1 130 | 131 | [node name="ScrollContainer" type="ScrollContainer" parent="Control/Branches Container"] 132 | custom_minimum_size = Vector2(275, 150) 133 | layout_mode = 2 134 | 135 | [node name="VBoxContainer" type="VBoxContainer" parent="Control/Branches Container/ScrollContainer"] 136 | layout_mode = 2 137 | 138 | [node name="Options" type="VBoxContainer" parent="Control"] 139 | layout_mode = 1 140 | anchors_preset = 2 141 | anchor_top = 1.0 142 | anchor_bottom = 1.0 143 | offset_left = 30.0 144 | offset_top = -123.0 145 | offset_right = 212.0 146 | offset_bottom = -30.0 147 | grow_vertical = 0 148 | 149 | [node name="HBoxContainer" type="HBoxContainer" parent="Control/Options"] 150 | layout_mode = 2 151 | 152 | [node name="Label" type="Label" parent="Control/Options/HBoxContainer"] 153 | layout_mode = 2 154 | theme_override_colors/font_color = Color(1, 1, 1, 1) 155 | theme_override_fonts/font = ExtResource("2_73ak5") 156 | text = "Select Color: " 157 | 158 | [node name="OptionButton" type="OptionButton" parent="Control/Options/HBoxContainer"] 159 | layout_mode = 2 160 | theme_override_colors/font_color = Color(0.0588235, 1, 0.192157, 1) 161 | theme_override_fonts/font = ExtResource("2_73ak5") 162 | flat = true 163 | item_count = 4 164 | selected = 2 165 | popup/item_0/text = "Red" 166 | popup/item_0/id = 0 167 | popup/item_1/text = "Purple" 168 | popup/item_1/id = 1 169 | popup/item_2/text = "Green" 170 | popup/item_2/id = 2 171 | popup/item_3/text = "Yellow" 172 | popup/item_3/id = 3 173 | 174 | [node name="Neighbors" type="CheckBox" parent="Control/Options"] 175 | layout_mode = 2 176 | theme_override_colors/font_color = Color(1, 1, 1, 1) 177 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 178 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 179 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 180 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 181 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 182 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 183 | theme_override_constants/h_separation = 10 184 | theme_override_constants/check_v_offset = 2 185 | theme_override_fonts/font = ExtResource("2_73ak5") 186 | text = "Highlight Neighbors" 187 | 188 | [node name="Rings" type="CheckBox" parent="Control/Options"] 189 | layout_mode = 2 190 | theme_override_colors/font_color = Color(1, 1, 1, 1) 191 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 192 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 193 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 194 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 195 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 196 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 197 | theme_override_constants/h_separation = 10 198 | theme_override_constants/check_v_offset = 2 199 | theme_override_fonts/font = ExtResource("2_73ak5") 200 | text = "Show Containing Ring(s)" 201 | 202 | [node name="Index Toggle" type="CheckBox" parent="Control/Options"] 203 | layout_mode = 2 204 | theme_override_colors/font_color = Color(1, 1, 1, 1) 205 | theme_override_colors/font_pressed_color = Color(0.494118, 0.494118, 0.494118, 1) 206 | theme_override_colors/font_hover_color = Color(1, 1, 1, 1) 207 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 1, 1) 208 | theme_override_colors/font_focus_color = Color(1, 1, 1, 1) 209 | theme_override_colors/font_disabled_color = Color(1, 1, 1, 1) 210 | theme_override_colors/font_outline_color = Color(1, 1, 1, 1) 211 | theme_override_constants/h_separation = 10 212 | theme_override_constants/check_v_offset = 2 213 | theme_override_fonts/font = ExtResource("2_73ak5") 214 | text = "Show SMILES Index" 215 | 216 | [node name="VBoxContainer" type="VBoxContainer" parent="Control"] 217 | layout_mode = 1 218 | anchors_preset = 3 219 | anchor_left = 1.0 220 | anchor_top = 1.0 221 | anchor_right = 1.0 222 | anchor_bottom = 1.0 223 | offset_left = -160.0 224 | offset_top = -80.0 225 | offset_right = -30.0 226 | offset_bottom = -30.0 227 | grow_horizontal = 0 228 | grow_vertical = 0 229 | 230 | [node name="2D Button" type="Button" parent="Control/VBoxContainer"] 231 | custom_minimum_size = Vector2(100, 50) 232 | layout_mode = 2 233 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 234 | theme_override_fonts/font = ExtResource("2_73ak5") 235 | theme_override_font_sizes/font_size = 30 236 | theme_override_styles/normal = SubResource("StyleBoxFlat_d23yw") 237 | theme_override_styles/hover = SubResource("StyleBoxFlat_d23yw") 238 | theme_override_styles/pressed = SubResource("StyleBoxFlat_d23yw") 239 | theme_override_styles/disabled = SubResource("StyleBoxFlat_d23yw") 240 | theme_override_styles/focus = SubResource("StyleBoxFlat_d23yw") 241 | text = "View 2D" 242 | 243 | [node name="Back" type="Button" parent="Control/VBoxContainer"] 244 | custom_minimum_size = Vector2(100, 50) 245 | layout_mode = 2 246 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 247 | theme_override_fonts/font = ExtResource("2_73ak5") 248 | theme_override_font_sizes/font_size = 30 249 | theme_override_styles/normal = SubResource("StyleBoxFlat_d23yw") 250 | theme_override_styles/hover = SubResource("StyleBoxFlat_d23yw") 251 | theme_override_styles/pressed = SubResource("StyleBoxFlat_d23yw") 252 | theme_override_styles/disabled = SubResource("StyleBoxFlat_d23yw") 253 | theme_override_styles/focus = SubResource("StyleBoxFlat_d23yw") 254 | text = "Go Back" 255 | 256 | [connection signal="item_selected" from="Control/Options/HBoxContainer/OptionButton" to="." method="_on_option_button_item_selected"] 257 | [connection signal="pressed" from="Control/Options/Neighbors" to="." method="_on_neighbors_pressed"] 258 | [connection signal="pressed" from="Control/Options/Rings" to="." method="_on_rings_pressed"] 259 | [connection signal="toggled" from="Control/Options/Index Toggle" to="." method="_on_index_toggle_toggled"] 260 | [connection signal="pressed" from="Control/VBoxContainer/2D Button" to="." method="_on_d_button_pressed"] 261 | [connection signal="pressed" from="Control/VBoxContainer/Back" to="." method="_on_button_pressed"] 262 | -------------------------------------------------------------------------------- /Scenes/Main Menu/python_runner.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://ceen3nr6v4ul2"] 2 | 3 | [ext_resource type="Script" path="res://Scenes/Main Menu/Python_Runner.gd" id="1_emusl"] 4 | [ext_resource type="FontFile" uid="uid://d4bw7aq2ggi5w" path="res://Fonts/cour.ttf" id="2_2r588"] 5 | 6 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_jj6wb"] 7 | 8 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_cgebo"] 9 | 10 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_hhrkn"] 11 | 12 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fn4xh"] 13 | 14 | [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_a318i"] 15 | 16 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s1acq"] 17 | bg_color = Color(0, 0, 0, 1) 18 | border_width_left = 2 19 | border_width_top = 2 20 | border_width_right = 2 21 | border_width_bottom = 2 22 | border_color = Color(1, 1, 1, 1) 23 | 24 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_upbyd"] 25 | bg_color = Color(0, 0, 0, 1) 26 | border_width_left = 2 27 | border_width_top = 2 28 | border_width_right = 2 29 | border_width_bottom = 2 30 | border_color = Color(1, 1, 1, 1) 31 | 32 | [node name="Python_Runner" type="Control"] 33 | layout_mode = 3 34 | anchors_preset = 15 35 | anchor_right = 1.0 36 | anchor_bottom = 1.0 37 | grow_horizontal = 2 38 | grow_vertical = 2 39 | script = ExtResource("1_emusl") 40 | 41 | [node name="ColorRect" type="ColorRect" parent="."] 42 | layout_mode = 1 43 | anchors_preset = 15 44 | anchor_right = 1.0 45 | anchor_bottom = 1.0 46 | grow_horizontal = 2 47 | grow_vertical = 2 48 | color = Color(0, 0, 0, 1) 49 | 50 | [node name="Text" type="VBoxContainer" parent="."] 51 | layout_mode = 0 52 | offset_left = 30.0 53 | offset_top = 30.0 54 | offset_right = 1110.0 55 | offset_bottom = 196.0 56 | 57 | [node name="Title" type="Label" parent="Text"] 58 | layout_mode = 2 59 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 60 | theme_override_fonts/font = ExtResource("2_2r588") 61 | theme_override_font_sizes/font_size = 100 62 | text = "SmiNav" 63 | 64 | [node name="Description" type="Label" parent="Text"] 65 | layout_mode = 2 66 | theme_override_colors/font_color = Color(1, 1, 1, 1) 67 | theme_override_fonts/font = ExtResource("2_2r588") 68 | theme_override_font_sizes/font_size = 40 69 | text = "A SMILES syntax:depiction visualizer in Godot" 70 | 71 | [node name="Options" type="VBoxContainer" parent="."] 72 | layout_mode = 1 73 | anchors_preset = 8 74 | anchor_left = 0.5 75 | anchor_top = 0.5 76 | anchor_right = 0.5 77 | anchor_bottom = 0.5 78 | offset_left = -357.5 79 | offset_top = -99.0 80 | offset_right = 357.5 81 | offset_bottom = 249.0 82 | grow_horizontal = 2 83 | grow_vertical = 2 84 | 85 | [node name="Colors" type="HBoxContainer" parent="Options"] 86 | layout_mode = 2 87 | theme_override_constants/separation = 20 88 | 89 | [node name="Label" type="Label" parent="Options/Colors"] 90 | layout_mode = 2 91 | theme_override_colors/font_color = Color(1, 1, 1, 1) 92 | theme_override_fonts/font = ExtResource("2_2r588") 93 | theme_override_font_sizes/font_size = 25 94 | text = "Select Color Theme:" 95 | 96 | [node name="Red" type="Button" parent="Options/Colors"] 97 | layout_mode = 2 98 | theme_override_colors/font_color = Color(1, 1, 1, 1) 99 | theme_override_colors/font_pressed_color = Color(1, 0.192157, 0.192157, 1) 100 | theme_override_colors/font_hover_color = Color(1, 0.192157, 0.192157, 1) 101 | theme_override_colors/font_focus_color = Color(1, 0.192157, 0.192157, 1) 102 | theme_override_colors/font_hover_pressed_color = Color(1, 0.192157, 0.192157, 1) 103 | theme_override_colors/font_disabled_color = Color(1, 0.192157, 0.192157, 1) 104 | theme_override_colors/font_outline_color = Color(1, 0.192157, 0.192157, 1) 105 | theme_override_fonts/font = ExtResource("2_2r588") 106 | theme_override_font_sizes/font_size = 25 107 | theme_override_styles/normal = SubResource("StyleBoxEmpty_jj6wb") 108 | theme_override_styles/hover = SubResource("StyleBoxEmpty_cgebo") 109 | theme_override_styles/pressed = SubResource("StyleBoxEmpty_hhrkn") 110 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_fn4xh") 111 | theme_override_styles/focus = SubResource("StyleBoxEmpty_a318i") 112 | toggle_mode = true 113 | text = "red" 114 | 115 | [node name="Yellow" type="Button" parent="Options/Colors"] 116 | layout_mode = 2 117 | theme_override_colors/font_color = Color(1, 1, 1, 1) 118 | theme_override_colors/font_pressed_color = Color(1, 1, 0.2, 1) 119 | theme_override_colors/font_hover_color = Color(1, 1, 0.2, 1) 120 | theme_override_colors/font_focus_color = Color(1, 1, 0.2, 1) 121 | theme_override_colors/font_hover_pressed_color = Color(1, 1, 0.2, 1) 122 | theme_override_colors/font_disabled_color = Color(1, 1, 0.2, 1) 123 | theme_override_colors/font_outline_color = Color(1, 1, 0.2, 1) 124 | theme_override_fonts/font = ExtResource("2_2r588") 125 | theme_override_font_sizes/font_size = 25 126 | theme_override_styles/normal = SubResource("StyleBoxEmpty_jj6wb") 127 | theme_override_styles/hover = SubResource("StyleBoxEmpty_cgebo") 128 | theme_override_styles/pressed = SubResource("StyleBoxEmpty_hhrkn") 129 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_fn4xh") 130 | theme_override_styles/focus = SubResource("StyleBoxEmpty_a318i") 131 | toggle_mode = true 132 | text = "yellow" 133 | 134 | [node name="Green" type="Button" parent="Options/Colors"] 135 | layout_mode = 2 136 | theme_override_colors/font_color = Color(1, 1, 1, 1) 137 | theme_override_colors/font_pressed_color = Color(0.0588235, 1, 0.313726, 1) 138 | theme_override_colors/font_hover_color = Color(0.0588235, 1, 0.313726, 1) 139 | theme_override_colors/font_focus_color = Color(0.0588235, 1, 0.313726, 1) 140 | theme_override_colors/font_hover_pressed_color = Color(0.0588235, 1, 0.313726, 1) 141 | theme_override_colors/font_disabled_color = Color(0.0588235, 1, 0.313726, 1) 142 | theme_override_colors/font_outline_color = Color(0.0588235, 1, 0.313726, 1) 143 | theme_override_fonts/font = ExtResource("2_2r588") 144 | theme_override_font_sizes/font_size = 25 145 | theme_override_styles/normal = SubResource("StyleBoxEmpty_jj6wb") 146 | theme_override_styles/hover = SubResource("StyleBoxEmpty_cgebo") 147 | theme_override_styles/pressed = SubResource("StyleBoxEmpty_hhrkn") 148 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_fn4xh") 149 | theme_override_styles/focus = SubResource("StyleBoxEmpty_a318i") 150 | toggle_mode = true 151 | button_pressed = true 152 | text = "green" 153 | 154 | [node name="Purple" type="Button" parent="Options/Colors"] 155 | layout_mode = 2 156 | theme_override_colors/font_color = Color(1, 1, 1, 1) 157 | theme_override_colors/font_pressed_color = Color(0.615686, 0, 1, 1) 158 | theme_override_colors/font_hover_color = Color(0.615686, 0, 1, 1) 159 | theme_override_colors/font_focus_color = Color(0.615686, 0, 1, 1) 160 | theme_override_colors/font_hover_pressed_color = Color(0.615686, 0, 1, 1) 161 | theme_override_colors/font_disabled_color = Color(0.615686, 0, 1, 1) 162 | theme_override_colors/font_outline_color = Color(0.615686, 0, 1, 1) 163 | theme_override_fonts/font = ExtResource("2_2r588") 164 | theme_override_font_sizes/font_size = 25 165 | theme_override_styles/normal = SubResource("StyleBoxEmpty_jj6wb") 166 | theme_override_styles/hover = SubResource("StyleBoxEmpty_cgebo") 167 | theme_override_styles/pressed = SubResource("StyleBoxEmpty_hhrkn") 168 | theme_override_styles/disabled = SubResource("StyleBoxEmpty_fn4xh") 169 | theme_override_styles/focus = SubResource("StyleBoxEmpty_a318i") 170 | toggle_mode = true 171 | text = "purple" 172 | 173 | [node name="Inputs" type="HBoxContainer" parent="Options"] 174 | layout_mode = 2 175 | theme_override_constants/separation = 15 176 | 177 | [node name="Smiles Input" type="TextEdit" parent="Options/Inputs"] 178 | custom_minimum_size = Vector2(600, 0) 179 | layout_mode = 2 180 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 181 | theme_override_colors/font_placeholder_color = Color(0.0588235, 1, 0.313726, 1) 182 | theme_override_constants/line_spacing = 15 183 | theme_override_constants/caret_width = 3 184 | theme_override_fonts/font = ExtResource("2_2r588") 185 | theme_override_font_sizes/font_size = 35 186 | theme_override_styles/normal = SubResource("StyleBoxFlat_s1acq") 187 | placeholder_text = " Enter SMILES" 188 | caret_blink = true 189 | 190 | [node name="VBoxContainer" type="VBoxContainer" parent="Options/Inputs"] 191 | layout_mode = 2 192 | 193 | [node name="2D" type="Button" parent="Options/Inputs/VBoxContainer"] 194 | custom_minimum_size = Vector2(100, 50) 195 | layout_mode = 2 196 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 197 | theme_override_fonts/font = ExtResource("2_2r588") 198 | theme_override_font_sizes/font_size = 30 199 | theme_override_styles/normal = SubResource("StyleBoxFlat_upbyd") 200 | theme_override_styles/hover = SubResource("StyleBoxFlat_upbyd") 201 | theme_override_styles/pressed = SubResource("StyleBoxFlat_upbyd") 202 | theme_override_styles/disabled = SubResource("StyleBoxFlat_upbyd") 203 | theme_override_styles/focus = SubResource("StyleBoxFlat_upbyd") 204 | text = "2D" 205 | 206 | [node name="3D" type="Button" parent="Options/Inputs/VBoxContainer"] 207 | custom_minimum_size = Vector2(100, 50) 208 | layout_mode = 2 209 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 210 | theme_override_fonts/font = ExtResource("2_2r588") 211 | theme_override_font_sizes/font_size = 30 212 | theme_override_styles/normal = SubResource("StyleBoxFlat_upbyd") 213 | theme_override_styles/hover = SubResource("StyleBoxFlat_upbyd") 214 | theme_override_styles/pressed = SubResource("StyleBoxFlat_upbyd") 215 | theme_override_styles/disabled = SubResource("StyleBoxFlat_upbyd") 216 | theme_override_styles/focus = SubResource("StyleBoxFlat_upbyd") 217 | text = "3D" 218 | 219 | [node name="Output" type="Label" parent="Options"] 220 | custom_minimum_size = Vector2(0, 100) 221 | layout_mode = 2 222 | theme_override_colors/font_color = Color(1, 1, 1, 1) 223 | theme_override_fonts/font = ExtResource("2_2r588") 224 | theme_override_font_sizes/font_size = 25 225 | horizontal_alignment = 1 226 | vertical_alignment = 1 227 | autowrap_mode = 2 228 | 229 | [node name="Exit" type="Button" parent="Options"] 230 | custom_minimum_size = Vector2(100, 50) 231 | layout_mode = 2 232 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 233 | theme_override_fonts/font = ExtResource("2_2r588") 234 | theme_override_font_sizes/font_size = 30 235 | theme_override_styles/normal = SubResource("StyleBoxFlat_upbyd") 236 | theme_override_styles/hover = SubResource("StyleBoxFlat_upbyd") 237 | theme_override_styles/pressed = SubResource("StyleBoxFlat_upbyd") 238 | theme_override_styles/disabled = SubResource("StyleBoxFlat_upbyd") 239 | theme_override_styles/focus = SubResource("StyleBoxFlat_upbyd") 240 | text = "EXIT" 241 | 242 | [node name="Python Install" type="Button" parent="."] 243 | layout_mode = 1 244 | anchors_preset = 3 245 | anchor_left = 1.0 246 | anchor_top = 1.0 247 | anchor_right = 1.0 248 | anchor_bottom = 1.0 249 | offset_left = -578.0 250 | offset_top = -67.0 251 | offset_right = -30.0 252 | offset_bottom = -30.0 253 | grow_horizontal = 0 254 | grow_vertical = 0 255 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 256 | theme_override_fonts/font = ExtResource("2_2r588") 257 | theme_override_font_sizes/font_size = 25 258 | text = "Setup: Click to install requirements" 259 | flat = true 260 | 261 | [node name="Github" type="Button" parent="."] 262 | layout_mode = 1 263 | anchors_preset = 2 264 | anchor_top = 1.0 265 | anchor_bottom = 1.0 266 | offset_left = 30.0 267 | offset_top = -67.0 268 | offset_right = 428.0 269 | offset_bottom = -30.0 270 | grow_vertical = 0 271 | theme_override_colors/font_color = Color(0.0588235, 1, 0.313726, 1) 272 | theme_override_fonts/font = ExtResource("2_2r588") 273 | theme_override_font_sizes/font_size = 25 274 | text = "github.com/ualibweb/sminav" 275 | flat = true 276 | 277 | [connection signal="pressed" from="Options/Colors/Red" to="." method="_on_red_pressed"] 278 | [connection signal="pressed" from="Options/Colors/Yellow" to="." method="_on_yellow_pressed"] 279 | [connection signal="pressed" from="Options/Colors/Green" to="." method="_on_green_pressed"] 280 | [connection signal="pressed" from="Options/Colors/Purple" to="." method="_on_purple_pressed"] 281 | [connection signal="pressed" from="Options/Inputs/VBoxContainer/2D" to="." method="_on_two_d_pressed"] 282 | [connection signal="pressed" from="Options/Inputs/VBoxContainer/3D" to="." method="_on_three_d_pressed"] 283 | [connection signal="pressed" from="Options/Exit" to="." method="_on_exit_pressed"] 284 | [connection signal="pressed" from="Python Install" to="." method="_on_python_install_pressed"] 285 | -------------------------------------------------------------------------------- /Scenes/2D Visualizer/2d_smile_visualizer.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | # Constants for preloading scenes for different types of chemical bonds and a base atom scene. 4 | const DOUBLE_BOND = preload("res://Scenes/Bonds/double_bond.tscn") 5 | const SINGLE_BOND = preload("res://Scenes/Bonds/single_bond.tscn") 6 | const TRIPLE_BOND = preload("res://Scenes/Bonds/triple_bond.tscn") 7 | const ARROMATIC_BOND = preload("res://Scenes/Bonds/arromatic_bond.tscn") 8 | const BASE_ATOM = preload("res://Scenes/Atoms/base_atom.tscn") 9 | 10 | # Node references using 'onready' to ensure they are initialized when the script is ready. 11 | @onready var structure = $Structure 12 | @onready var h_box_container = $"CanvasLayer/Control/SMILES Container/ScrollContainer/HBoxContainer" 13 | @onready var v_box_container = $"CanvasLayer/Control/Branches Container/ScrollContainer/VBoxContainer" 14 | 15 | @onready var neighbors_checkbox = $CanvasLayer/Control/Options/Neighbors 16 | @onready var rings_checkbox = $CanvasLayer/Control/Options/Rings 17 | 18 | # Gets the viewport size for layout calculations. 19 | @onready var viewport = get_viewport_rect().size 20 | 21 | # UI elements for user interaction. 22 | @onready var _3d_button = $"CanvasLayer/Control/VBoxContainer/3D Button" 23 | @onready var back = $CanvasLayer/Control/VBoxContainer/Back 24 | @onready var option_button = $CanvasLayer/Control/Options/HBoxContainer/OptionButton 25 | const COUR = preload("res://Fonts/cour.ttf") 26 | const ELEMENT_BUTTON = preload("res://Utils/Element Button/element_button.tscn") 27 | const BRANCH_BUTTON = preload("res://Utils/Branches Button/branch_button.tscn") 28 | 29 | # Variables to store atoms, bonds, rings, and UI elements related to atoms. 30 | var atoms = [] 31 | var bonds = [] 32 | var rings = [] 33 | 34 | # Variables for highlighting atoms and bonds based on user selection. 35 | var highlighted_atoms = [] 36 | var highlighted_bonds = [] 37 | var highlighted_connected_atoms = [] 38 | var highlighted_connected_bonds = [] 39 | var atom_buttons = [] 40 | var branch_buttons = [] 41 | 42 | # A boolean to check if the control key is pressed, used for multi-selection. 43 | var ctrl_pressed = false 44 | 45 | # Initializes elements, connections, and rings on node entry, and connects signals for UI updates. 46 | func _ready(): 47 | await add_elements() 48 | await add_connections() 49 | await add_rings() 50 | await add_branches() 51 | await generate_smiles_array() 52 | Globals.modulate_highlight.connect(_on_update_colors) 53 | Globals.node_clicked.connect(_on_node_pressed) 54 | Globals.modulate_highlight.emit() 55 | _on_enter_colors() 56 | 57 | # Updates the UI based on the globally selected color. 58 | func _on_enter_colors(): 59 | # Selects the appropriate option button based on the globally selected color. 60 | if Globals.selected_color == Globals.NEON_RED: 61 | option_button.selected = 0 62 | elif Globals.selected_color == Globals.NEON_PURPLE: 63 | option_button.selected = 1 64 | elif Globals.selected_color == Globals.NEON_GREEN: 65 | option_button.selected = 2 66 | elif Globals.selected_color == Globals.NEON_YELLOW: 67 | option_button.selected = 3 68 | else: 69 | option_button.selected = 2 70 | 71 | # Updates colors of back and option_button to reflect the globally selected color. 72 | func _on_update_colors(): 73 | back.add_theme_color_override("font_color", Globals.selected_color) 74 | _3d_button.add_theme_color_override("font_color", Globals.selected_color) 75 | option_button.add_theme_color_override("font_color", Globals.selected_color) 76 | 77 | # Applies a new style to atom buttons based on the selected color. 78 | var stylebox = generate_theme_stylebox() 79 | for button in atom_buttons: 80 | button.add_theme_stylebox_override("pressed", stylebox) 81 | 82 | # Handles the multi-select functionality based on user input. 83 | func _process(_delta): 84 | if Input.is_action_just_pressed("multi-select"): 85 | ctrl_pressed = true 86 | if Input.is_action_just_released("multi-select"): 87 | ctrl_pressed = false 88 | 89 | # Checks if a string contains alphabetic characters. 90 | func contains_alpha_char(characters: String) -> bool: 91 | for character in characters: 92 | if is_alpha(character): 93 | return true 94 | return false 95 | 96 | # Determines if a character is an alphabetic letter. 97 | func is_alpha(character: String) -> bool: 98 | if character.length() == 0: 99 | return false 100 | var code = character[0].to_ascii_buffer()[0] 101 | return (code >= 'a'.to_ascii_buffer()[0] and code <= 'z'.to_ascii_buffer()[0]) or (code >= 'A'.to_ascii_buffer()[0] and code <= 'Z'.to_ascii_buffer()[0]) 102 | 103 | # Generates buttons for elements from a SMILES array, enabling dynamic UI for chemical structure manipulation. 104 | func generate_smiles_array(): 105 | var base_path = ProjectSettings.globalize_path("res://") 106 | var elements_file = FileAccess.open(base_path + "smiles.txt",FileAccess.READ) 107 | var elements_text = elements_file.get_as_text().strip_edges() 108 | var elements = elements_text.split(" ") 109 | 110 | var element_index = 0 111 | # Creates buttons for each element and adds them to the UI, disabling non-alphabetic ones. 112 | for element in elements: 113 | var new_button = ELEMENT_BUTTON.instantiate() 114 | new_button.element_name = str(element) 115 | if contains_alpha_char(element) or element == "*": 116 | atom_buttons.append(new_button) 117 | new_button.pressed.connect(update_buttons.bind(new_button)) 118 | new_button.element_index = element_index 119 | element_index += 1 120 | else: 121 | new_button.disabled = true 122 | h_box_container.add_child(new_button) 123 | 124 | # Generates a stylebox for theme customization based on the selected color. 125 | func generate_theme_stylebox(): 126 | var new_stylebox = StyleBoxFlat.new() 127 | new_stylebox.bg_color = Color(Globals.selected_color, .5) 128 | return new_stylebox 129 | 130 | # Handles node press events, updating the UI accordingly. 131 | func _on_node_pressed(atom_node): 132 | var atom_idx = atoms.find(atom_node) 133 | if atom_idx >= atom_buttons.size(): 134 | return 135 | var relative_button = atom_buttons[atom_idx] 136 | if relative_button: 137 | relative_button.button_pressed = true 138 | update_buttons(relative_button) 139 | 140 | # Updates button states and highlights based on the control key state and button presses. 141 | func update_buttons(button_node): 142 | for button in branch_buttons: 143 | button.button_pressed = false 144 | if ctrl_pressed: 145 | update_highlights() 146 | return 147 | for button in atom_buttons: 148 | if button == button_node: 149 | continue 150 | button.button_pressed = false 151 | update_highlights() 152 | 153 | # Updates which atoms and bonds are highlighted based on user selection. 154 | func update_highlights(): 155 | for atom in highlighted_atoms: 156 | atom.turn_off_highlight.call_deferred() 157 | for bond in highlighted_bonds: 158 | bond.turn_off_highlight.call_deferred() 159 | highlighted_atoms.clear() 160 | highlighted_bonds.clear() 161 | for idx in atom_buttons.size(): 162 | if atom_buttons[idx].button_pressed == true: 163 | var current_atom = atoms[idx] 164 | current_atom.turn_on_highlight.call_deferred() 165 | highlighted_atoms.append(current_atom) 166 | for bond in bonds: 167 | if bond.check_if_connected_atoms(highlighted_atoms): 168 | bond.turn_on_highlight.call_deferred() 169 | highlighted_bonds.append(bond) 170 | var adjacent_hydrogen = bond.check_if_hydrogen_connected(highlighted_atoms) 171 | if adjacent_hydrogen: 172 | bond.turn_on_highlight.call_deferred() 173 | highlighted_bonds.append(bond) 174 | adjacent_hydrogen.turn_on_highlight.call_deferred() 175 | highlighted_atoms.append(adjacent_hydrogen) 176 | clear_connected_highlights() 177 | if neighbors_checkbox.button_pressed: 178 | highlight_connected() 179 | if rings_checkbox.button_pressed: 180 | highlight_rings() 181 | 182 | # Highlights connected atoms and bonds. 183 | func highlight_connected(): 184 | highlighted_connected_atoms.clear() 185 | highlighted_connected_bonds.clear() 186 | for bond in bonds: 187 | if bond.check_if_contains_atoms(highlighted_atoms): 188 | bond.turn_on_highlight.call_deferred() 189 | highlighted_connected_bonds.append(bond) 190 | for atom in bond.connected_atoms: 191 | atom.turn_on_highlight.call_deferred() 192 | highlighted_connected_atoms.append(atom) 193 | for atom in highlighted_connected_atoms: 194 | var atom_idx = atoms.find(atom) 195 | if atom_idx < atom_buttons.size(): 196 | atom_buttons[atom_idx].button_pressed = true 197 | 198 | # Highlights all atoms and bonds in rings that contain any of the highlighted atoms. 199 | func highlight_rings(): 200 | highlighted_connected_atoms.clear() 201 | highlighted_connected_bonds.clear() 202 | for atom in highlighted_atoms: 203 | var atom_idx = atoms.find(atom) 204 | for ring in rings: 205 | if str(atom_idx) in ring: 206 | # Highlight all atoms in ring 207 | for atom_index in ring: 208 | atoms[int(atom_index)].turn_on_highlight.call_deferred() 209 | highlighted_connected_atoms.append(atoms[int(atom_index)]) 210 | for bond in bonds: 211 | if bond.check_if_connected_atoms(highlighted_connected_atoms): 212 | bond.turn_on_highlight.call_deferred() 213 | highlighted_connected_bonds.append(bond) 214 | for atom in highlighted_connected_atoms: 215 | var atom_idx = atoms.find(atom) 216 | if atom_idx < atom_buttons.size(): 217 | atom_buttons[atom_idx].button_pressed = true 218 | 219 | # Clears highlights from connected atoms and bonds that are not currently selected. 220 | func clear_connected_highlights(): 221 | for atom in highlighted_connected_atoms: 222 | if atom in highlighted_atoms: 223 | continue 224 | atom.turn_off_highlight() 225 | var atom_idx = atoms.find(atom) 226 | if atom_idx < atom_buttons.size(): 227 | atom_buttons[atom_idx].button_pressed = false 228 | for bond in highlighted_connected_bonds: 229 | if bond in highlighted_bonds: 230 | continue 231 | bond.turn_off_highlight() 232 | highlighted_connected_atoms.clear() 233 | highlighted_connected_bonds.clear() 234 | 235 | func turn_on_index(): 236 | for atom_button in atom_buttons: 237 | atom_button.turn_on_index() 238 | for atom in atoms: 239 | atom.turn_on_index() 240 | 241 | func turn_off_index(): 242 | for atom_button in atom_buttons: 243 | atom_button.turn_off_index() 244 | for atom in atoms: 245 | atom.turn_off_index() 246 | 247 | # Loads and adds elements to the structure based on a configuration file. 248 | func add_elements(): 249 | var base_path = ProjectSettings.globalize_path("res://") 250 | var elements_file = FileAccess.open(base_path + "two_d_elements.txt",FileAccess.READ) 251 | var elements = elements_file.get_as_text().split("\n") 252 | for element in elements: 253 | if element.length() == 0: 254 | continue 255 | var split_data = element.split(" ", false) 256 | var symbol = split_data[0] 257 | var index = split_data[1] 258 | var x_position = float(split_data[2]) - viewport.x * .5 259 | var y_position = float(split_data[3]) - viewport.y * .5 260 | var charge = int(split_data[4]) 261 | var atom_node = BASE_ATOM.instantiate() 262 | structure.add_child(atom_node) 263 | atom_node.global_position = Vector2(x_position, y_position) 264 | atom_node.update_atom(symbol, charge, index) 265 | atoms.append(atom_node) 266 | 267 | # Parses and adds connections between atoms to visualize chemical bonds. 268 | func add_connections(): 269 | var base_path = ProjectSettings.globalize_path("res://") 270 | var elements_file = FileAccess.open(base_path + "two_d_connections.txt",FileAccess.READ) 271 | var elements = elements_file.get_as_text().split("\n") 272 | for element in elements: 273 | if element.length() == 0: 274 | continue 275 | var split_data = element.split(" - ", false) 276 | var first_index = int(split_data[0]) 277 | var second_index = int(split_data[1]) 278 | var bond_type = split_data[2] 279 | var first_element_position = atoms[first_index].global_position 280 | var second_element_position = atoms[second_index].global_position 281 | 282 | var direction = second_element_position - first_element_position 283 | var distance = direction.length() 284 | 285 | var height = distance 286 | var new_connection = get_connection(bond_type) 287 | structure.add_child(new_connection) 288 | if atoms[first_index].get_node("Atom Symbol") and atoms[first_index].get_node("Atom Symbol").text != "": 289 | new_connection.global_position = first_element_position + direction * .2 290 | height -= distance * .2 291 | else: 292 | new_connection.global_position = first_element_position # + direction * .2 293 | if atoms[second_index].get_node("Atom Symbol") and atoms[second_index].get_node("Atom Symbol").text != "": 294 | height -= distance * .2 295 | new_connection.scale.x = height 296 | new_connection.look_at(second_element_position) 297 | 298 | var new_bond = Bond.new(new_connection, atoms[first_index], atoms[second_index]) 299 | bonds.append(new_bond) 300 | 301 | # Determines the appropriate scene to instantiate based on the bond type. 302 | func get_connection(bond_type): 303 | if bond_type == "1.0": 304 | return SINGLE_BOND.instantiate() 305 | if bond_type == "2.0": 306 | return DOUBLE_BOND.instantiate() 307 | if bond_type == "3.0": 308 | return TRIPLE_BOND.instantiate() 309 | if bond_type == "1.5": 310 | return ARROMATIC_BOND.instantiate() 311 | return SINGLE_BOND.instantiate() 312 | 313 | # Loads and adds rings based on a configuration file. 314 | func add_rings(): 315 | var base_path = ProjectSettings.globalize_path("res://") 316 | var rings_file = FileAccess.open(base_path + "rings.txt",FileAccess.READ) 317 | var rings_data = rings_file.get_as_text().split("\n") 318 | for ring in rings_data: 319 | var elements = ring.strip_edges().split(" ") 320 | rings.append(elements) 321 | 322 | func add_branches(): 323 | var base_path = ProjectSettings.globalize_path("res://") 324 | var branches_file = FileAccess.open(base_path + "branches.txt",FileAccess.READ) 325 | var branches_text = branches_file.get_as_text().strip_edges() 326 | var branches = branches_text.split("\n") 327 | 328 | for branch in branches: 329 | var new_button = BRANCH_BUTTON.instantiate() 330 | var segments = branch.split("\t") 331 | var smiles_string = segments[0] 332 | var element_array = str_to_var(segments[-1]) 333 | new_button.text = str(smiles_string) 334 | new_button.toggled.connect(update_fragments.bind(new_button, element_array)) 335 | branch_buttons.append(new_button) 336 | v_box_container.add_child(new_button) 337 | 338 | func update_fragments(toggled_on: bool, button_node, branches_array: Array): 339 | if toggled_on: 340 | for button in atom_buttons: 341 | button.button_pressed = false 342 | for button in branch_buttons: 343 | if button == button_node: 344 | button.button_pressed = true 345 | continue 346 | button.button_pressed = false 347 | for button_index in branches_array: 348 | atom_buttons[button_index].button_pressed = true 349 | else: 350 | for button_index in branches_array: 351 | atom_buttons[button_index].button_pressed = false 352 | update_highlights() 353 | 354 | 355 | 356 | # Updates global color selection based on user choice from an option button. 357 | func _on_option_button_item_selected(index): 358 | if index == 0: 359 | Globals.selected_color = Globals.NEON_RED 360 | if index == 1: 361 | Globals.selected_color = Globals.NEON_PURPLE 362 | 363 | if index == 2: 364 | Globals.selected_color = Globals.NEON_GREEN 365 | 366 | if index == 3: 367 | Globals.selected_color = Globals.NEON_YELLOW 368 | Globals.modulate_highlight.emit() 369 | 370 | # Toggles highlighting of connected atoms and bonds, ensuring exclusive selection with ring highlighting. 371 | func _on_neighbors_pressed(): 372 | clear_connected_highlights() 373 | if neighbors_checkbox.button_pressed: 374 | rings_checkbox.button_pressed = false 375 | highlight_connected() 376 | 377 | # Toggles highlighting of atoms and bonds in rings, ensuring exclusive selection with neighbor highlighting. 378 | func _on_rings_pressed(): 379 | clear_connected_highlights() 380 | if rings_checkbox.button_pressed: 381 | neighbors_checkbox.button_pressed = false 382 | highlight_rings() 383 | 384 | 385 | func _on_index_toggle_toggled(toggled_on): 386 | if toggled_on: 387 | turn_on_index() 388 | else: 389 | turn_off_index() 390 | 391 | 392 | func _on_d_button_pressed(): 393 | get_tree().change_scene_to_file("res://Scenes/3D Visualizer/3d_smile_visualizer.tscn") 394 | 395 | 396 | # Handler for button press actions, possibly to change scenes or trigger functions. 397 | func _on_back_pressed(): 398 | get_tree().change_scene_to_file("res://Scenes/Main Menu/python_runner.tscn") 399 | -------------------------------------------------------------------------------- /Scenes/3D Visualizer/3d_smile_visualizer.gd: -------------------------------------------------------------------------------- 1 | extends Node3D 2 | 3 | # Node references using `onready` to get the nodes from the scene 4 | @onready var structure = $Structure 5 | @onready var h_box_container = $"Control/SMILES Container/ScrollContainer/HBoxContainer" 6 | @onready var v_box_container = $"Control/Branches Container/ScrollContainer/VBoxContainer" 7 | @onready var neighbors_checkbox = $Control/Options/Neighbors 8 | @onready var rings_checkbox = $Control/Options/Rings 9 | @onready var back = $Control/VBoxContainer/Back 10 | @onready var _2d_button = $"Control/VBoxContainer/2D Button" 11 | @onready var option_button = $Control/Options/HBoxContainer/OptionButton 12 | @onready var fragment_text = $"Control/SMILES Container/Fragment_text" 13 | 14 | # Preloading the scenes for 3D representation of atoms and bonds 15 | const BASE_ATOM_3D = preload("res://Scenes/Atoms/base_atom_3d.tscn") 16 | const SINGLE_BOND_3D = preload("res://Scenes/Bonds/single_bond_3d.tscn") 17 | const DOUBLE_BOND_3D = preload("res://Scenes/Bonds/double_bond_3d.tscn") 18 | const TRIPLE_BOND_3D = preload("res://Scenes/Bonds/triple_bond_3d.tscn") 19 | const ARROMATIC_BOND_3D = preload("res://Scenes/Bonds/arromatic_bond_3d.tscn") 20 | 21 | # Preloading the font 22 | const COUR = preload("res://Fonts/cour.ttf") 23 | 24 | # Preload SMILES BUTTON and BRANCH BUTTON 25 | const ELEMENT_BUTTON = preload("res://Utils/Element Button/element_button.tscn") 26 | const BRANCH_BUTTON = preload("res://Utils/Branches Button/branch_button.tscn") 27 | 28 | # Variables to store the atoms, bonds, rings, and highlighted elements 29 | var atoms = [] 30 | var bonds = [] 31 | var rings = [] 32 | var highlighted_atoms = [] 33 | var highlighted_bonds = [] 34 | var highlighted_connected_atoms = [] 35 | var highlighted_connected_bonds = [] 36 | var atom_buttons = [] 37 | var branch_buttons = [] 38 | 39 | # Variables to store the state of the application 40 | var ctrl_pressed = false 41 | var fragment = false 42 | var holding_left_click = false 43 | 44 | # Initializes elements, connections, rings, hides or shows fragment text, and sets initial colors. 45 | func _ready(): 46 | fragment_text.visible = false 47 | await add_elements() 48 | await add_connections() 49 | await add_rings() 50 | await add_branches() 51 | await generate_smiles_array() 52 | Globals.modulate_highlight.connect(_on_update_colors) 53 | Globals.node_clicked.connect(_on_node_pressed) 54 | Globals.modulate_highlight.emit() 55 | _on_enter_colors() 56 | if fragment: 57 | fragment_text.visible = true 58 | 59 | # Sets the option button to the selected color 60 | func _on_enter_colors(): 61 | if Globals.selected_color == Globals.NEON_RED: 62 | option_button.selected = 0 63 | elif Globals.selected_color == Globals.NEON_PURPLE: 64 | option_button.selected = 1 65 | elif Globals.selected_color == Globals.NEON_GREEN: 66 | option_button.selected = 2 67 | elif Globals.selected_color == Globals.NEON_YELLOW: 68 | option_button.selected = 3 69 | else: 70 | option_button.selected = 2 71 | 72 | # Applies the selected color to the UI elements 73 | func _on_update_colors(): 74 | back.add_theme_color_override("font_color", Globals.selected_color) 75 | _2d_button.add_theme_color_override("font_color", Globals.selected_color) 76 | option_button.add_theme_color_override("font_color", Globals.selected_color) 77 | 78 | var stylebox = generate_theme_stylebox() 79 | for button in atom_buttons: 80 | if button is String: 81 | continue 82 | button.add_theme_stylebox_override("pressed", stylebox) 83 | 84 | 85 | # Emits the camera position and handles multi-select input. 86 | func _process(_delta): 87 | Globals.camera_position.emit($Camera3D.global_position) 88 | if Input.is_action_just_pressed("multi-select"): 89 | ctrl_pressed = true 90 | if Input.is_action_just_released("multi-select"): 91 | ctrl_pressed = false 92 | 93 | # Helper functions to determine if a string contains alphabetic characters and if a character is alphabetic. 94 | func contains_alpha_char(characters: String) -> bool: 95 | for character in characters: 96 | if is_alpha(character): 97 | return true 98 | return false 99 | 100 | # Helper function to determine if a character is alphabetic. 101 | func is_alpha(character: String) -> bool: 102 | if character.length() == 0: 103 | return false 104 | var code = character[0].to_ascii_buffer()[0] 105 | return (code >= 'a'.to_ascii_buffer()[0] and code <= 'z'.to_ascii_buffer()[0]) or (code >= 'A'.to_ascii_buffer()[0] and code <= 'Z'.to_ascii_buffer()[0]) 106 | 107 | # Generates an array of SMILES representation buttons. 108 | # The array is generated from a text file containing the SMILES representation of atoms. 109 | # The array is then added to the HBoxContainer. 110 | # The array is then connected to the update_buttons function. 111 | # Atom is checked for alpha characters and if it contains alpha characters, the atom is added to the array. 112 | func generate_smiles_array(): 113 | var base_path = ProjectSettings.globalize_path("res://") 114 | var elements_file = FileAccess.open(base_path + "smiles.txt",FileAccess.READ) 115 | var elements_text = elements_file.get_as_text().strip_edges() 116 | var elements = elements_text.split(" ") 117 | 118 | var element_index = 0 119 | var element_idx = 0 120 | for element in elements: 121 | var new_button = ELEMENT_BUTTON.instantiate() 122 | new_button.element_name = str(element) 123 | if contains_alpha_char(element) or element == "*": 124 | if atoms[element_idx] is String: 125 | new_button.disabled = true 126 | atom_buttons.append("") 127 | else: 128 | atom_buttons.append(new_button) 129 | new_button.pressed.connect(update_buttons.bind(new_button)) 130 | new_button.element_index = element_index 131 | element_index += 1 132 | element_idx += 1 133 | else: 134 | new_button.disabled = true 135 | h_box_container.add_child(new_button) 136 | 137 | # Generates a stylebox for the selected color. 138 | func generate_theme_stylebox(): 139 | var new_stylebox = StyleBoxFlat.new() 140 | new_stylebox.bg_color = Color(Globals.selected_color, .5) 141 | return new_stylebox 142 | 143 | # Updates the buttons and highlights the atoms and bonds. 144 | func _on_node_pressed(atom_node): 145 | var atom_idx = atoms.find(atom_node) 146 | if atom_idx >= atom_buttons.size(): 147 | return 148 | var relative_button = atom_buttons[atom_idx] 149 | if relative_button: 150 | relative_button.button_pressed = true 151 | update_buttons(relative_button) 152 | 153 | # Updates the buttons and highlights the atoms and bonds. 154 | func update_buttons(button_node): 155 | for button in branch_buttons: 156 | button.button_pressed = false 157 | if ctrl_pressed: 158 | update_highlights() 159 | return 160 | for button in atom_buttons: 161 | if button is String: 162 | continue 163 | if button == button_node: 164 | continue 165 | button.button_pressed = false 166 | update_highlights() 167 | 168 | # Updates the highlights of the atoms and bonds. 169 | func update_highlights(): 170 | for atom in highlighted_atoms: 171 | atom.turn_off_highlight.call_deferred() 172 | for bond in highlighted_bonds: 173 | bond.turn_off_highlight.call_deferred() 174 | highlighted_atoms.clear() 175 | highlighted_bonds.clear() 176 | for idx in atom_buttons.size(): 177 | if atom_buttons[idx] is String: 178 | continue 179 | if atom_buttons[idx].button_pressed == true: 180 | var current_atom = atoms[idx] 181 | current_atom.turn_on_highlight.call_deferred() 182 | highlighted_atoms.append(current_atom) 183 | for bond in bonds: 184 | if bond.check_if_connected_atoms(highlighted_atoms): 185 | bond.turn_on_highlight.call_deferred() 186 | highlighted_bonds.append(bond) 187 | var adjacent_hydrogen = bond.check_if_hydrogen_connected(highlighted_atoms) 188 | if adjacent_hydrogen: 189 | bond.turn_on_highlight.call_deferred() 190 | highlighted_bonds.append(bond) 191 | adjacent_hydrogen.turn_on_highlight.call_deferred() 192 | highlighted_atoms.append(adjacent_hydrogen) 193 | clear_connected_highlights() 194 | if neighbors_checkbox.button_pressed: 195 | highlight_connected() 196 | if rings_checkbox.button_pressed: 197 | highlight_rings() 198 | 199 | # Highlights the connected atoms and bonds. 200 | func highlight_connected(): 201 | highlighted_connected_atoms.clear() 202 | highlighted_connected_bonds.clear() 203 | for bond in bonds: 204 | if bond.check_if_contains_atoms(highlighted_atoms): 205 | bond.turn_on_highlight.call_deferred() 206 | highlighted_connected_bonds.append(bond) 207 | for atom in bond.connected_atoms: 208 | atom.turn_on_highlight.call_deferred() 209 | highlighted_connected_atoms.append(atom) 210 | for atom in highlighted_connected_atoms: 211 | var atom_idx = atoms.find(atom) 212 | if atom_idx < atom_buttons.size(): 213 | atom_buttons[atom_idx].button_pressed = true 214 | 215 | # Highlights the rings. 216 | func highlight_rings(): 217 | highlighted_connected_atoms.clear() 218 | highlighted_connected_bonds.clear() 219 | for atom in highlighted_atoms: 220 | var atom_idx = atoms.find(atom) 221 | for ring in rings: 222 | if str(atom_idx) in ring: 223 | # Highlight all atoms in ring 224 | for atom_index in ring: 225 | atoms[int(atom_index)].turn_on_highlight.call_deferred() 226 | highlighted_connected_atoms.append(atoms[int(atom_index)]) 227 | for bond in bonds: 228 | if bond.check_if_connected_atoms(highlighted_connected_atoms): 229 | bond.turn_on_highlight.call_deferred() 230 | highlighted_connected_bonds.append(bond) 231 | for atom in highlighted_connected_atoms: 232 | var atom_idx = atoms.find(atom) 233 | if atom_idx < atom_buttons.size(): 234 | atom_buttons[atom_idx].button_pressed = true 235 | 236 | # Clears the connected highlights. 237 | func clear_connected_highlights(): 238 | for atom in highlighted_connected_atoms: 239 | if atom in highlighted_atoms: 240 | continue 241 | atom.turn_off_highlight() 242 | var atom_idx = atoms.find(atom) 243 | if atom_idx < atom_buttons.size(): 244 | atom_buttons[atom_idx].button_pressed = false 245 | for bond in highlighted_connected_bonds: 246 | if bond in highlighted_bonds: 247 | continue 248 | bond.turn_off_highlight() 249 | highlighted_connected_atoms.clear() 250 | highlighted_connected_bonds.clear() 251 | 252 | func turn_on_index(): 253 | for atom_button in atom_buttons: 254 | if atom_button is String: 255 | continue 256 | else: 257 | atom_button.turn_on_index() 258 | for atom in atoms: 259 | if atom is String: 260 | continue 261 | else: 262 | atom.turn_on_index() 263 | 264 | func turn_off_index(): 265 | for atom_button in atom_buttons: 266 | atom_button.turn_off_index() 267 | for atom in atoms: 268 | atom.turn_off_index() 269 | 270 | # Adds the elements to the scene. 271 | # The elements are read from a text file and added to the scene. 272 | # The elements are added to the atoms array. 273 | # If the x, y, and z positions are -1, the element is a fragment and thus the atoms array is appended with an empty string. 274 | func add_elements(): 275 | var base_path = ProjectSettings.globalize_path("res://") 276 | var elements_file = FileAccess.open(base_path + "elements.txt",FileAccess.READ) 277 | var elements = elements_file.get_as_text().split("\n") 278 | for element in elements: 279 | if element.length() == 0: 280 | continue 281 | var split_data = element.split(" ", false) 282 | var symbol = split_data[0] 283 | var index = int(split_data[1]) 284 | var x_position = float(split_data[2]) * 1 285 | var y_position = float(split_data[3]) * 1 286 | var z_position = float(split_data[4]) * 1 287 | var charge = int(split_data[5]) 288 | if x_position == -1 and y_position == -1 and z_position == -1: 289 | atoms.append("") 290 | fragment = true 291 | continue 292 | var atom_node = BASE_ATOM_3D.instantiate() 293 | structure.add_child(atom_node) 294 | atom_node.global_position = Vector3(x_position, y_position, z_position) 295 | atom_node.update_atom(symbol, charge, index) 296 | atoms.append(atom_node) 297 | 298 | # Adds the connections to the scene. 299 | func add_connections(): 300 | var base_path = ProjectSettings.globalize_path("res://") 301 | var elements_file = FileAccess.open(base_path + "connections.txt",FileAccess.READ) 302 | var elements = elements_file.get_as_text().split("\n") 303 | for element in elements: 304 | if element.length() == 0: 305 | continue 306 | var split_data = element.split(" - ", false) 307 | var first_index = int(split_data[0]) 308 | var second_index = int(split_data[1]) 309 | var bond_type = split_data[2] 310 | var first_element_position = atoms[first_index].global_position 311 | var second_element_position = atoms[second_index].global_position 312 | 313 | var new_connection = get_connection(bond_type) 314 | structure.add_child(new_connection) 315 | 316 | var direction = second_element_position - first_element_position 317 | var distance = direction.length() 318 | 319 | #var height = distance 320 | 321 | var normalized_direction = direction.normalized() 322 | var connection_up_vector = Vector3(0, 1, 0) 323 | if normalized_direction.cross(connection_up_vector).length() == 0: 324 | connection_up_vector = Vector3(0, 0, 1) 325 | var connection_quaternion = Quaternion(connection_up_vector, normalized_direction) 326 | new_connection.transform = Transform3D(connection_quaternion, first_element_position + normalized_direction * distance * .5) 327 | #if atoms[first_index].get_node("Label") and atoms[first_index].get_node("Label").text != "": 328 | #new_connection.global_position = first_element_position + direction * .2 329 | #height -= distance * .2 330 | #else: 331 | #new_connection.global_position = first_element_position # + direction * .2 332 | #if atoms[second_index].get_node("Label") and atoms[second_index].get_node("Label").text != "": 333 | #height -= distance * .2 334 | new_connection.scale.y = distance 335 | 336 | var new_bond = Bond_3D.new(new_connection, atoms[first_index], atoms[second_index]) 337 | bonds.append(new_bond) 338 | 339 | # Adds the rings to the scene. 340 | func add_rings(): 341 | var base_path = ProjectSettings.globalize_path("res://") 342 | var rings_file = FileAccess.open(base_path + "rings.txt",FileAccess.READ) 343 | var rings_data = rings_file.get_as_text().split("\n") 344 | for ring in rings_data: 345 | var elements = ring.strip_edges().split(" ") 346 | rings.append(elements) 347 | 348 | # Returns the connection based on the bond type. 349 | func get_connection(bond_type): 350 | if bond_type == "1.0": 351 | return SINGLE_BOND_3D.instantiate() 352 | if bond_type == "2.0": 353 | return DOUBLE_BOND_3D.instantiate() 354 | if bond_type == "3.0": 355 | return TRIPLE_BOND_3D.instantiate() 356 | if bond_type == "1.5": 357 | return ARROMATIC_BOND_3D.instantiate() 358 | return SINGLE_BOND_3D.instantiate() 359 | 360 | func add_branches(): 361 | var base_path = ProjectSettings.globalize_path("res://") 362 | var branches_file = FileAccess.open(base_path + "branches.txt",FileAccess.READ) 363 | var branches_text = branches_file.get_as_text().strip_edges() 364 | var branches = branches_text.split("\n") 365 | 366 | for branch in branches: 367 | var new_button = BRANCH_BUTTON.instantiate() 368 | var segments = branch.split("\t") 369 | var smiles_string = segments[0] 370 | var element_array = str_to_var(segments[-1]) 371 | new_button.text = str(smiles_string) 372 | new_button.toggled.connect(update_fragments.bind(new_button, element_array)) 373 | branch_buttons.append(new_button) 374 | v_box_container.add_child(new_button) 375 | 376 | func update_fragments(toggled_on: bool, button_node, branches_array: Array): 377 | if toggled_on: 378 | for button in atom_buttons: 379 | if button is String: 380 | continue 381 | button.button_pressed = false 382 | for button in branch_buttons: 383 | if button == button_node: 384 | button.button_pressed = true 385 | continue 386 | button.button_pressed = false 387 | for button_index in branches_array: 388 | atom_buttons[button_index].button_pressed = true 389 | else: 390 | for button_index in branches_array: 391 | atom_buttons[button_index].button_pressed = false 392 | update_highlights() 393 | 394 | # Returns the connection based on the bond type. 395 | func _on_button_pressed(): 396 | get_tree().change_scene_to_file("res://Scenes/Main Menu/python_runner.tscn") 397 | 398 | # Sets the selected color based on the option button index. 399 | func _on_option_button_item_selected(index): 400 | if index == 0: 401 | Globals.selected_color = Globals.NEON_RED 402 | if index == 1: 403 | Globals.selected_color = Globals.NEON_PURPLE 404 | 405 | if index == 2: 406 | Globals.selected_color = Globals.NEON_GREEN 407 | 408 | if index == 3: 409 | Globals.selected_color = Globals.NEON_YELLOW 410 | Globals.modulate_highlight.emit() 411 | 412 | # Clears the connected highlights and highlights the neighbors or rings. 413 | func _on_neighbors_pressed(): 414 | clear_connected_highlights() 415 | if neighbors_checkbox.button_pressed: 416 | rings_checkbox.button_pressed = false 417 | highlight_connected() 418 | 419 | # Clears the connected highlights and highlights the neighbors or rings. 420 | func _on_rings_pressed(): 421 | clear_connected_highlights() 422 | if rings_checkbox.button_pressed: 423 | neighbors_checkbox.button_pressed = false 424 | highlight_rings() 425 | 426 | func _on_index_toggle_toggled(toggled_on): 427 | if toggled_on: 428 | turn_on_index() 429 | else: 430 | turn_off_index() 431 | 432 | # Rotates the structure based on the mouse position. 433 | func _input(event): 434 | if event is InputEventMouseButton: 435 | if event.button_index == 2 and event.pressed == true: 436 | holding_left_click = true 437 | if event.button_index == 2 and event.pressed == false: 438 | holding_left_click = false 439 | if event is InputEventMouseMotion: 440 | if not holding_left_click: 441 | return 442 | var mouse_position = event.relative 443 | structure.rotate_x(.01 * mouse_position.y) 444 | structure.rotate_y(.01 * mouse_position.x) 445 | 446 | 447 | func _on_d_button_pressed(): 448 | get_tree().change_scene_to_file("res://Scenes/2D Visualizer/2d_smile_visualizer.tscn") 449 | --------------------------------------------------------------------------------