molbuilder 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. molbuilder/__init__.py +8 -0
  2. molbuilder/__main__.py +6 -0
  3. molbuilder/atomic/__init__.py +4 -0
  4. molbuilder/atomic/bohr.py +235 -0
  5. molbuilder/atomic/quantum_atom.py +334 -0
  6. molbuilder/atomic/quantum_numbers.py +196 -0
  7. molbuilder/atomic/wavefunctions.py +297 -0
  8. molbuilder/bonding/__init__.py +4 -0
  9. molbuilder/bonding/covalent.py +442 -0
  10. molbuilder/bonding/lewis.py +347 -0
  11. molbuilder/bonding/vsepr.py +433 -0
  12. molbuilder/cli/__init__.py +1 -0
  13. molbuilder/cli/demos.py +516 -0
  14. molbuilder/cli/menu.py +127 -0
  15. molbuilder/cli/wizard.py +831 -0
  16. molbuilder/core/__init__.py +6 -0
  17. molbuilder/core/bond_data.py +170 -0
  18. molbuilder/core/constants.py +51 -0
  19. molbuilder/core/element_properties.py +183 -0
  20. molbuilder/core/elements.py +181 -0
  21. molbuilder/core/geometry.py +232 -0
  22. molbuilder/gui/__init__.py +2 -0
  23. molbuilder/gui/app.py +286 -0
  24. molbuilder/gui/canvas3d.py +115 -0
  25. molbuilder/gui/dialogs.py +117 -0
  26. molbuilder/gui/event_handler.py +118 -0
  27. molbuilder/gui/sidebar.py +105 -0
  28. molbuilder/gui/toolbar.py +71 -0
  29. molbuilder/io/__init__.py +1 -0
  30. molbuilder/io/json_io.py +146 -0
  31. molbuilder/io/mol_sdf.py +169 -0
  32. molbuilder/io/pdb.py +184 -0
  33. molbuilder/io/smiles_io.py +47 -0
  34. molbuilder/io/xyz.py +103 -0
  35. molbuilder/molecule/__init__.py +2 -0
  36. molbuilder/molecule/amino_acids.py +919 -0
  37. molbuilder/molecule/builders.py +257 -0
  38. molbuilder/molecule/conformations.py +70 -0
  39. molbuilder/molecule/functional_groups.py +484 -0
  40. molbuilder/molecule/graph.py +712 -0
  41. molbuilder/molecule/peptides.py +13 -0
  42. molbuilder/molecule/stereochemistry.py +6 -0
  43. molbuilder/process/__init__.py +3 -0
  44. molbuilder/process/conditions.py +260 -0
  45. molbuilder/process/costing.py +316 -0
  46. molbuilder/process/purification.py +285 -0
  47. molbuilder/process/reactor.py +297 -0
  48. molbuilder/process/safety.py +476 -0
  49. molbuilder/process/scale_up.py +427 -0
  50. molbuilder/process/solvent_systems.py +204 -0
  51. molbuilder/reactions/__init__.py +3 -0
  52. molbuilder/reactions/functional_group_detect.py +728 -0
  53. molbuilder/reactions/knowledge_base.py +1716 -0
  54. molbuilder/reactions/reaction_types.py +102 -0
  55. molbuilder/reactions/reagent_data.py +1248 -0
  56. molbuilder/reactions/retrosynthesis.py +1430 -0
  57. molbuilder/reactions/synthesis_route.py +377 -0
  58. molbuilder/reports/__init__.py +158 -0
  59. molbuilder/reports/cost_report.py +206 -0
  60. molbuilder/reports/molecule_report.py +279 -0
  61. molbuilder/reports/safety_report.py +296 -0
  62. molbuilder/reports/synthesis_report.py +283 -0
  63. molbuilder/reports/text_formatter.py +170 -0
  64. molbuilder/smiles/__init__.py +4 -0
  65. molbuilder/smiles/parser.py +487 -0
  66. molbuilder/smiles/tokenizer.py +291 -0
  67. molbuilder/smiles/writer.py +375 -0
  68. molbuilder/visualization/__init__.py +1 -0
  69. molbuilder/visualization/bohr_viz.py +166 -0
  70. molbuilder/visualization/molecule_viz.py +368 -0
  71. molbuilder/visualization/quantum_viz.py +434 -0
  72. molbuilder/visualization/theme.py +12 -0
  73. molbuilder-1.0.0.dist-info/METADATA +360 -0
  74. molbuilder-1.0.0.dist-info/RECORD +78 -0
  75. molbuilder-1.0.0.dist-info/WHEEL +5 -0
  76. molbuilder-1.0.0.dist-info/entry_points.txt +2 -0
  77. molbuilder-1.0.0.dist-info/licenses/LICENSE +21 -0
  78. molbuilder-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,516 @@
1
+ """Demo functions for each Molecule Builder module.
2
+
3
+ Each ``demo_*`` function exercises one logical area of the package and
4
+ prints a self-contained text report (plus optional interactive
5
+ visualisation for the two matplotlib-based demos).
6
+
7
+ Migrated from legacy/main.py.
8
+ """
9
+
10
+
11
+ # ===================================================================
12
+ # Module demos
13
+ # ===================================================================
14
+
15
+ def demo_bohr_model():
16
+ """Bohr atomic model: orbital radii, energies, spectral transitions."""
17
+ from molbuilder.atomic.bohr import BohrAtom, from_symbol
18
+
19
+ print("=" * 60)
20
+ print(" BOHR ATOMIC MODEL")
21
+ print("=" * 60)
22
+ print()
23
+
24
+ # Hydrogen
25
+ H = from_symbol("H")
26
+ print(H.summary())
27
+ print()
28
+
29
+ # Balmer series (visible transitions)
30
+ print(" Hydrogen Balmer Series (visible spectrum):")
31
+ print(f" {'Transition':<12} {'Wavelength':>12} {'Energy':>12}")
32
+ print(f" {'-' * 40}")
33
+ for ni in range(3, 7):
34
+ wl = H.transition_wavelength_nm(ni, 2)
35
+ e = H.transition_energy(ni, 2)
36
+ print(f" n={ni} -> n=2 {wl:>10.1f} nm {e:>10.3f} eV")
37
+ print()
38
+
39
+ # Multi-electron atoms
40
+ for sym in ["C", "Na", "Fe"]:
41
+ atom = from_symbol(sym)
42
+ print(f" {atom.name} (Z={atom.atomic_number}):")
43
+ print(f" Ground state energy: {atom.energy_level(1):.3f} eV")
44
+ print(f" Ionisation energy: {atom.ionization_energy():.3f} eV")
45
+ print(f" 1st orbital radius: {atom.orbital_radius_pm(1):.1f} pm")
46
+ print()
47
+
48
+
49
+ def demo_quantum_model():
50
+ """Quantum mechanical atom: electron configurations, Slater's rules."""
51
+ from molbuilder.atomic.quantum_atom import QuantumAtom, from_symbol, aufbau_order
52
+
53
+ print("=" * 60)
54
+ print(" QUANTUM MECHANICAL ATOM")
55
+ print("=" * 60)
56
+ print()
57
+
58
+ # Aufbau order (first 10 subshells)
59
+ order = aufbau_order()
60
+ labels = {0: "s", 1: "p", 2: "d", 3: "f"}
61
+ print(" Aufbau filling order:")
62
+ print(" ", " -> ".join(
63
+ f"{n}{labels[l]}" for n, l in order[:10]))
64
+ print()
65
+
66
+ # Example atoms including Aufbau exceptions
67
+ atoms = [
68
+ ("H", "simplest atom"),
69
+ ("C", "basis of organic chemistry"),
70
+ ("Fe", "transition metal"),
71
+ ("Cr", "Aufbau exception: half-filled 3d"),
72
+ ("Cu", "Aufbau exception: filled 3d"),
73
+ ]
74
+
75
+ print(f" {'Symbol':<6} {'Config':<28} {'Spin mult':>10}")
76
+ print(f" {'-' * 50}")
77
+ for sym, note in atoms:
78
+ atom = from_symbol(sym)
79
+ config = atom.electron_configuration_string()
80
+ mult = atom.spin_multiplicity()
81
+ print(f" {sym:<6} {config:<28} {mult:>10}")
82
+ print()
83
+
84
+ # Detailed view for carbon
85
+ C = from_symbol("C")
86
+ print(C.summary())
87
+ print()
88
+
89
+
90
+ def demo_element_data():
91
+ """Element property data: electronegativity, radii, CPK colours."""
92
+ from molbuilder.core.element_properties import (
93
+ electronegativity, covalent_radius_pm, cpk_color,
94
+ estimated_bond_length_pm, period, can_expand_octet,
95
+ )
96
+
97
+ print("=" * 60)
98
+ print(" ELEMENT DATA")
99
+ print("=" * 60)
100
+ print()
101
+
102
+ elements = ["H", "C", "N", "O", "F", "P", "S", "Cl", "Br", "I", "Xe"]
103
+ print(f" {'Sym':<4} {'EN':>5} {'Radius':>8} {'Period':>7} {'Expand':>7} {'Color'}")
104
+ print(f" {'-' * 48}")
105
+ for sym in elements:
106
+ en = electronegativity(sym)
107
+ r = covalent_radius_pm(sym)
108
+ p = period(sym)
109
+ exp = "yes" if can_expand_octet(sym) else "no"
110
+ col = cpk_color(sym)
111
+ print(f" {sym:<4} {en:>5.2f} {r:>6.0f} pm {p:>6} {exp:>7} {col}")
112
+ print()
113
+
114
+ # Bond lengths
115
+ print(" Estimated bond lengths:")
116
+ bonds = [("C", "H", 1), ("C", "C", 1), ("C", "C", 2),
117
+ ("C", "O", 2), ("N", "N", 3)]
118
+ for a, b, order in bonds:
119
+ sym = {1: "-", 2: "=", 3: "#"}[order]
120
+ bl = estimated_bond_length_pm(a, b, order)
121
+ print(f" {a}{sym}{b}: {bl:.0f} pm")
122
+ print()
123
+
124
+
125
+ def demo_lewis_structure():
126
+ """Lewis structures: bonding pairs, lone pairs, octets."""
127
+ from molbuilder.bonding.lewis import LewisStructure
128
+
129
+ print("=" * 60)
130
+ print(" LEWIS STRUCTURES")
131
+ print("=" * 60)
132
+ print()
133
+
134
+ molecules = [
135
+ ("H2O", 0, "bent, 2 lone pairs"),
136
+ ("CO2", 0, "linear, double bonds"),
137
+ ("NH3", 0, "trigonal pyramidal"),
138
+ ("CH4", 0, "tetrahedral"),
139
+ ("BF3", 0, "incomplete octet"),
140
+ ("SF6", 0, "expanded octet"),
141
+ ("PCl5", 0, "expanded octet"),
142
+ ("NH4", 1, "ammonium ion"),
143
+ ]
144
+
145
+ for formula, charge, note in molecules:
146
+ lewis = LewisStructure(formula, charge)
147
+ charge_str = f" (charge {charge:+d})" if charge else ""
148
+ print(f" {formula}{charge_str} -- {note}")
149
+ print(f" Valence e-: {lewis.total_valence_electrons}")
150
+ print(f" Central atom: {lewis.central_symbol}")
151
+ print(f" Bonding pairs: {lewis.bonding_pairs_on_central()}")
152
+ print(f" Lone pairs: {lewis.lone_pairs_on_central()}")
153
+ print(f" Steric number: {lewis.steric_number()}")
154
+
155
+ bond_strs = []
156
+ for bond in lewis.bonds:
157
+ sa = lewis.atoms[bond.atom_a]
158
+ sb = lewis.atoms[bond.atom_b]
159
+ sym = {1: "-", 2: "=", 3: "#"}.get(bond.order, "?")
160
+ bond_strs.append(f"{sa}{sym}{sb}")
161
+ print(f" Bonds: {', '.join(bond_strs)}")
162
+ print()
163
+
164
+
165
+ def demo_vsepr_model():
166
+ """VSEPR molecular geometry: AXnEm classification, 3D coordinates."""
167
+ from molbuilder.bonding.vsepr import VSEPRMolecule
168
+
169
+ print("=" * 60)
170
+ print(" VSEPR MOLECULAR GEOMETRY")
171
+ print("=" * 60)
172
+ print()
173
+
174
+ molecules = [
175
+ ("CO2", 0), # linear
176
+ ("BF3", 0), # trigonal planar
177
+ ("CH4", 0), # tetrahedral
178
+ ("NH3", 0), # trigonal pyramidal
179
+ ("H2O", 0), # bent
180
+ ("PCl5", 0), # trigonal bipyramidal
181
+ ("SF4", 0), # seesaw
182
+ ("SF6", 0), # octahedral
183
+ ("XeF4", 0), # square planar
184
+ ("ClF3", 0), # T-shaped
185
+ ("IF5", 0), # square pyramidal
186
+ ]
187
+
188
+ print(f" {'Formula':<8} {'AXE':>6} {'Electron Geom':<24} {'Molecular Geom':<24}")
189
+ print(f" {'-' * 64}")
190
+ for formula, charge in molecules:
191
+ mol = VSEPRMolecule(formula, charge)
192
+ axe = mol.axe
193
+ print(f" {formula:<8} {axe.axe_notation:>6} "
194
+ f"{axe.electron_geometry:<24} {axe.molecular_geometry:<24}")
195
+ print()
196
+
197
+ # Detailed view for water
198
+ water = VSEPRMolecule("H2O")
199
+ print(water.summary())
200
+ print()
201
+
202
+
203
+ def demo_covalent_bonds():
204
+ """Covalent bond analysis: polarity, orbital composition, BDE."""
205
+ from molbuilder.bonding.covalent import (
206
+ single_bond, double_bond, triple_bond,
207
+ MolecularBondAnalysis,
208
+ )
209
+
210
+ print("=" * 60)
211
+ print(" COVALENT BONDS")
212
+ print("=" * 60)
213
+ print()
214
+
215
+ # Individual bonds
216
+ bonds = [
217
+ single_bond("H", "H"),
218
+ single_bond("H", "Cl"),
219
+ double_bond("C", "O"),
220
+ triple_bond("N", "N"),
221
+ single_bond("C", "C"),
222
+ ]
223
+
224
+ print(f" {'Bond':<8} {'Order':>5} {'dEN':>6} {'Polarity':<20}"
225
+ f" {'BDE (kJ)':>9} {'Length':>8}")
226
+ print(f" {'-' * 62}")
227
+ for b in bonds:
228
+ sym = b.order_symbol
229
+ label = f"{b.symbol_a}{sym}{b.symbol_b}"
230
+ bde = f"{b.dissociation_energy_kj:.0f}" if b.dissociation_energy_kj else "---"
231
+ bl = f"{b.bond_length_pm:.0f} pm"
232
+ print(f" {label:<8} {b.bond_order:>5} {b.delta_en:>6.2f}"
233
+ f" {b.polarity.name:<20} {bde:>9} {bl:>8}")
234
+ print()
235
+
236
+ # Sigma/pi composition
237
+ print(" Orbital composition:")
238
+ for b in bonds:
239
+ label = f"{b.symbol_a}{b.order_symbol}{b.symbol_b}"
240
+ parts = ", ".join(f"{oc.orbital_type.name}" for oc in b.orbital_contributions)
241
+ print(f" {label}: {parts}")
242
+ print()
243
+
244
+ # Molecular polarity
245
+ print(" Molecular polarity analysis:")
246
+ formulas = ["H2", "HCl", "CO2", "H2O", "CH4", "NH3", "BF3", "CCl4"]
247
+ print(f" {'Formula':<10} {'Polarity':<20} {'sigma':>5} {'pi':>5}")
248
+ print(f" {'-' * 44}")
249
+ for f in formulas:
250
+ ma = MolecularBondAnalysis(f)
251
+ print(f" {f:<10} {ma.molecular_polarity:<20}"
252
+ f" {ma.total_sigma_bonds:>5} {ma.total_pi_bonds:>5}")
253
+ print()
254
+
255
+
256
+ def demo_molecular_conformations():
257
+ """Molecular conformations: ethane, butane, cyclohexane, stereochemistry."""
258
+ from molbuilder.molecule.builders import (
259
+ build_ethane, build_butane, build_cyclohexane, build_2_butene,
260
+ build_chiral_molecule,
261
+ )
262
+ from molbuilder.molecule.conformations import classify_conformation, scan_torsion
263
+ from molbuilder.molecule.graph import RingConformation
264
+
265
+ print("=" * 60)
266
+ print(" MOLECULAR CONFORMATIONS & STEREOCHEMISTRY")
267
+ print("=" * 60)
268
+ print()
269
+
270
+ # Ethane conformations
271
+ print(" --- Ethane: Staggered vs Eclipsed ---")
272
+ staggered = build_ethane(60.0)
273
+ eclipsed = build_ethane(0.0)
274
+ e_s = staggered.torsional_energy(0, 1).total_kj_per_mol
275
+ e_e = eclipsed.torsional_energy(0, 1).total_kj_per_mol
276
+ print(f" Staggered energy: {e_s:.2f} kJ/mol")
277
+ print(f" Eclipsed energy: {e_e:.2f} kJ/mol")
278
+ print(f" Barrier: {e_e - e_s:.2f} kJ/mol")
279
+ print()
280
+
281
+ # Newman projections
282
+ print(" Newman projection (staggered):")
283
+ np_s = staggered.newman_projection(0, 1)
284
+ for idx, sym, ang in np_s.front_substituents:
285
+ print(f" Front: {sym}[{idx}] at {ang:.1f} deg")
286
+ for idx, sym, ang in np_s.back_substituents:
287
+ print(f" Back: {sym}[{idx}] at {ang:.1f} deg")
288
+ print()
289
+
290
+ # Butane conformations
291
+ print(" --- Butane conformations ---")
292
+ print(f" {'Dihedral':>8} {'Energy':>12} {'Type'}")
293
+ print(f" {'-' * 36}")
294
+ for dih in [180, 60, 0, 120]:
295
+ mol = build_butane(dih)
296
+ energy = mol.torsional_energy(1, 2).total_kj_per_mol
297
+ conf = classify_conformation(dih)
298
+ print(f" {dih:>6} deg {energy:>8.2f} kJ/mol {conf.name}")
299
+ print()
300
+
301
+ # Butane torsion scan (condensed)
302
+ print(" --- Butane torsion scan (every 30 deg) ---")
303
+ butane = build_butane(180.0)
304
+ scan = scan_torsion(butane, 1, 2, 0, 3, steps=12)
305
+ print(f" {'Angle':>7} {'kJ/mol':>8} Bar")
306
+ for angle, energy in scan:
307
+ bar = "#" * max(0, int(energy / 2))
308
+ print(f" {angle:>7.0f} {energy:>8.2f} {bar}")
309
+ print()
310
+
311
+ # Cyclohexane
312
+ print(" --- Cyclohexane ---")
313
+ chair = build_cyclohexane(RingConformation.CHAIR)
314
+ boat = build_cyclohexane(RingConformation.BOAT)
315
+ dists_chair = [chair.distance(i, (i+1) % 6) for i in range(6)]
316
+ dists_boat = [boat.distance(i, (i+1) % 6) for i in range(6)]
317
+ print(f" Chair C-C: {min(dists_chair):.3f} - {max(dists_chair):.3f} A")
318
+ print(f" Boat C-C: {min(dists_boat):.3f} - {max(dists_boat):.3f} A")
319
+ print()
320
+
321
+ # E/Z isomerism
322
+ print(" --- E/Z Isomerism (2-butene) ---")
323
+ cis = build_2_butene(is_cis=True)
324
+ trans = build_2_butene(is_cis=False)
325
+ print(f" cis-2-butene: {cis.assign_ez(1, 2).name}")
326
+ print(f" trans-2-butene: {trans.assign_ez(1, 2).name}")
327
+ print()
328
+
329
+ # R/S chirality
330
+ print(" --- R/S Chirality (CHFClBr) ---")
331
+ chiral = build_chiral_molecule(["H", "F", "Cl", "Br"])
332
+ print(f" Configuration: {chiral.assign_rs(0).name}")
333
+ print()
334
+
335
+
336
+ def demo_amino_acids():
337
+ """Amino acids: all 20, functional groups, peptide bonds, chirality."""
338
+ from molbuilder.molecule.amino_acids import (
339
+ build_amino_acid, form_peptide_bond, build_peptide,
340
+ set_secondary_structure, add_hydroxyl, add_amino,
341
+ add_carboxyl, add_thiol, add_phenyl_ring,
342
+ AminoAcidType, SecondaryStructure, AMINO_ACID_DATA,
343
+ )
344
+ from molbuilder.molecule.graph import Molecule, Hybridization
345
+ from molbuilder.core.bond_data import SP3_ANGLE
346
+
347
+ print("=" * 60)
348
+ print(" AMINO ACIDS & FUNCTIONAL GROUPS")
349
+ print("=" * 60)
350
+ print()
351
+
352
+ # Functional groups
353
+ print(" --- Functional Group Builders ---")
354
+ demo_mol = Molecule("demo chain")
355
+ c0 = demo_mol.add_atom("C", [0.0, 0.0, 0.0], Hybridization.SP3)
356
+ c1 = demo_mol.add_atom_bonded("C", c0, hybridization=Hybridization.SP3)
357
+ c2 = demo_mol.add_atom_bonded("C", c1, angle_ref=c0,
358
+ bond_angle_deg=SP3_ANGLE,
359
+ hybridization=Hybridization.SP3)
360
+
361
+ oh = add_hydroxyl(demo_mol, c0)
362
+ nh2 = add_amino(demo_mol, c1, angle_ref=c0, dihedral_deg=60.0)
363
+ sh = add_thiol(demo_mol, c2, angle_ref=c1, dihedral_deg=60.0)
364
+
365
+ print(f" Carbon chain with -OH, -NH2, -SH attached")
366
+ print(f" Total atoms: {len(demo_mol.atoms)}, bonds: {len(demo_mol.bonds)}")
367
+ print()
368
+
369
+ # All 20 amino acids
370
+ print(" --- All 20 Standard Amino Acids ---")
371
+ print(f" {'Name':<16} {'1-letter':>8} {'3-letter':>8} {'Atoms':>6} {'Bonds':>6}")
372
+ print(f" {'-' * 50}")
373
+
374
+ all_aa = {}
375
+ for aa_type in AminoAcidType:
376
+ data = AMINO_ACID_DATA[aa_type]
377
+ mol = build_amino_acid(aa_type)
378
+ all_aa[aa_type] = mol
379
+ print(f" {data['name']:<16} {data['code1']:>8} {data['code3']:>8}"
380
+ f" {len(mol.atoms):>6} {len(mol.bonds):>6}")
381
+ print()
382
+
383
+ # Chirality check
384
+ print(" --- L-Chirality Verification ---")
385
+ print(f" {'Name':<16} {'CIP config':>10}")
386
+ print(f" {'-' * 28}")
387
+ for aa_type, mol in all_aa.items():
388
+ bb = mol.backbone
389
+ data = AMINO_ACID_DATA[aa_type]
390
+ if aa_type == AminoAcidType.GLY:
391
+ config = "achiral"
392
+ else:
393
+ config = mol.assign_rs(bb.CA).name
394
+ print(f" {data['name']:<16} {config:>10}")
395
+ print()
396
+
397
+ # Dipeptide
398
+ print(" --- Dipeptide: Ala-Gly ---")
399
+ ala = build_amino_acid(AminoAcidType.ALA)
400
+ gly = build_amino_acid(AminoAcidType.GLY)
401
+ dipeptide = form_peptide_bond(ala, gly)
402
+ print(f" Atoms: {len(dipeptide.atoms)}, Bonds: {len(dipeptide.bonds)}")
403
+ if hasattr(dipeptide, 'residues') and len(dipeptide.residues) >= 2:
404
+ c_i = dipeptide.residues[0].C
405
+ n_i = dipeptide.residues[1].N
406
+ print(f" Peptide bond: {dipeptide.distance(c_i, n_i):.3f} A")
407
+ ca1 = dipeptide.residues[0].CA
408
+ ca2 = dipeptide.residues[1].CA
409
+ omega = dipeptide.dihedral_angle(ca1, c_i, n_i, ca2)
410
+ print(f" Omega angle: {omega:.1f} deg (trans)")
411
+ print()
412
+
413
+ # Tripeptide with secondary structure
414
+ print(" --- Tripeptide: Ala-Ala-Ala (alpha helix) ---")
415
+ tripeptide = build_peptide(
416
+ [AminoAcidType.ALA, AminoAcidType.ALA, AminoAcidType.ALA])
417
+ if hasattr(tripeptide, 'residues'):
418
+ set_secondary_structure(tripeptide, tripeptide.residues,
419
+ SecondaryStructure.ALPHA_HELIX)
420
+ print(f" Atoms: {len(tripeptide.atoms)}, Bonds: {len(tripeptide.bonds)}")
421
+ print(f" Residues: {len(getattr(tripeptide, 'residues', []))}")
422
+ print(f" Phi/psi: alpha helix (-57, -47)")
423
+ print()
424
+
425
+
426
+ def demo_visualization():
427
+ """3D molecule visualization (requires matplotlib)."""
428
+ try:
429
+ import matplotlib
430
+ except ImportError:
431
+ print(" matplotlib is not installed. Skipping visualization.")
432
+ return
433
+
434
+ from molbuilder.bonding.vsepr import VSEPRMolecule
435
+ from molbuilder.visualization.molecule_viz import visualize_molecule, visualize_gallery
436
+
437
+ print("=" * 60)
438
+ print(" 3D MOLECULE VISUALIZATION")
439
+ print("=" * 60)
440
+ print()
441
+
442
+ choice = input(" Show: [1] Single molecule [2] Gallery [3] Skip: ").strip()
443
+
444
+ if choice == "1":
445
+ formula = input(" Enter formula (e.g. H2O, CH4, SF6): ").strip()
446
+ if not formula:
447
+ formula = "H2O"
448
+ try:
449
+ mol = VSEPRMolecule(formula)
450
+ print(f" Rendering {formula}...")
451
+ visualize_molecule(mol)
452
+ except Exception as e:
453
+ print(f" Error: {e}")
454
+
455
+ elif choice == "2":
456
+ formulas = ["H2O", "NH3", "CH4", "CO2", "BF3",
457
+ "SF6", "PCl5", "XeF4", "ClF3"]
458
+ mols = []
459
+ for f in formulas:
460
+ try:
461
+ mols.append(VSEPRMolecule(f))
462
+ except Exception as e:
463
+ print(f" Skipping {f}: {e}")
464
+ if mols:
465
+ print(f" Rendering gallery of {len(mols)} molecules...")
466
+ visualize_gallery(mols)
467
+ else:
468
+ print(" Skipped.")
469
+ print()
470
+
471
+
472
+ def demo_quantum_visualization():
473
+ """Quantum orbital visualization (requires matplotlib)."""
474
+ try:
475
+ import matplotlib
476
+ except ImportError:
477
+ print(" matplotlib is not installed. Skipping visualization.")
478
+ return
479
+
480
+ from molbuilder.atomic.quantum_atom import from_symbol as q_from_symbol
481
+ from molbuilder.visualization.quantum_viz import (
482
+ plot_orbital_3d,
483
+ plot_radial_probability,
484
+ plot_energy_levels,
485
+ visualize_atom,
486
+ )
487
+
488
+ print("=" * 60)
489
+ print(" QUANTUM ORBITAL VISUALIZATION")
490
+ print("=" * 60)
491
+ print()
492
+
493
+ choice = input(" Show: [1] Hydrogen 1s [2] Carbon 2p "
494
+ "[3] Full atom overview [4] Skip: ").strip()
495
+
496
+ if choice == "1":
497
+ print(" Rendering hydrogen 1s orbital...")
498
+ plot_orbital_3d(1, 0, 0, Z=1)
499
+
500
+ elif choice == "2":
501
+ print(" Rendering carbon 2p orbital...")
502
+ plot_orbital_3d(2, 1, 0, Z=6)
503
+
504
+ elif choice == "3":
505
+ element = input(" Enter element symbol (e.g. C, Fe, Na): ").strip()
506
+ if not element:
507
+ element = "C"
508
+ try:
509
+ atom = q_from_symbol(element)
510
+ print(f" Rendering full overview for {element}...")
511
+ visualize_atom(atom)
512
+ except Exception as e:
513
+ print(f" Error: {e}")
514
+ else:
515
+ print(" Skipped.")
516
+ print()
molbuilder/cli/menu.py ADDED
@@ -0,0 +1,127 @@
1
+ """Interactive menu system for Molecule Builder.
2
+
3
+ Provides a CLI with numbered options for each demo module,
4
+ an 'all' mode, and command-line argument support.
5
+
6
+ Migrated from legacy/main.py.
7
+ """
8
+
9
+ import sys
10
+
11
+ from molbuilder.cli.demos import (
12
+ demo_bohr_model,
13
+ demo_quantum_model,
14
+ demo_element_data,
15
+ demo_lewis_structure,
16
+ demo_vsepr_model,
17
+ demo_covalent_bonds,
18
+ demo_molecular_conformations,
19
+ demo_amino_acids,
20
+ demo_visualization,
21
+ demo_quantum_visualization,
22
+ )
23
+ from molbuilder.cli.wizard import wizard_main
24
+
25
+
26
+ # ===================================================================
27
+ # Menu
28
+ # ===================================================================
29
+
30
+ DEMOS = [
31
+ ("Bohr Atomic Model", demo_bohr_model),
32
+ ("Quantum Mechanical Atom", demo_quantum_model),
33
+ ("Element Data", demo_element_data),
34
+ ("Lewis Structures", demo_lewis_structure),
35
+ ("VSEPR Molecular Geometry", demo_vsepr_model),
36
+ ("Covalent Bonds", demo_covalent_bonds),
37
+ ("Molecular Conformations", demo_molecular_conformations),
38
+ ("Amino Acids & Functional Groups", demo_amino_acids),
39
+ ("3D Molecule Visualization", demo_visualization),
40
+ ("Quantum Orbital Visualization", demo_quantum_visualization),
41
+ ("Molecule Builder Wizard", wizard_main),
42
+ ]
43
+
44
+
45
+ def print_banner():
46
+ print()
47
+ print("=" * 60)
48
+ print(" MOLECULE BUILDER -- Alpha 1.0")
49
+ print(" Taylor C. Powell")
50
+ print("=" * 60)
51
+ print()
52
+
53
+
54
+ def print_menu():
55
+ print(" Available modules:")
56
+ print()
57
+ for i, (name, _) in enumerate(DEMOS, 1):
58
+ print(f" [{i:>2}] {name}")
59
+ print()
60
+ print(f" [ a] Run all text demos (1-8)")
61
+ print(f" [ q] Quit")
62
+ print()
63
+
64
+
65
+ def run_all_text():
66
+ """Run all non-visualization demos in sequence."""
67
+ for name, func in DEMOS[:8]:
68
+ try:
69
+ func()
70
+ except Exception as e:
71
+ print(f" ERROR in {name}: {e}")
72
+ print()
73
+
74
+
75
+ def main():
76
+ print_banner()
77
+
78
+ if len(sys.argv) > 1:
79
+ arg = sys.argv[1].lower()
80
+ if arg == "all":
81
+ run_all_text()
82
+ return
83
+ if arg == "q":
84
+ return
85
+ try:
86
+ idx = int(arg) - 1
87
+ if 0 <= idx < len(DEMOS):
88
+ DEMOS[idx][1]()
89
+ return
90
+ except ValueError:
91
+ pass
92
+ print(f" Unknown argument: {sys.argv[1]}")
93
+ print(f" Usage: python main.py [1-{len(DEMOS)} | all | q]")
94
+ return
95
+
96
+ # Interactive menu loop
97
+ while True:
98
+ print_menu()
99
+ choice = input(" Select option: ").strip().lower()
100
+
101
+ if choice == "q":
102
+ print(" Goodbye!")
103
+ break
104
+
105
+ if choice == "a":
106
+ run_all_text()
107
+ continue
108
+
109
+ try:
110
+ idx = int(choice) - 1
111
+ if 0 <= idx < len(DEMOS):
112
+ print()
113
+ try:
114
+ DEMOS[idx][1]()
115
+ except Exception as e:
116
+ print(f" ERROR: {e}")
117
+ import traceback
118
+ traceback.print_exc()
119
+ print()
120
+ else:
121
+ print(f" Please enter 1-{len(DEMOS)}, 'a', or 'q'.")
122
+ except ValueError:
123
+ print(f" Please enter 1-{len(DEMOS)}, 'a', or 'q'.")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()