Coverage for /builds/ase/ase/ase/gui/add.py: 59.38%
96 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import os
5import numpy as np
7import ase.gui.ui as ui
8from ase import Atoms
9from ase.data import atomic_numbers, chemical_symbols
10from ase.gui.i18n import _
12current_selection_string = _('(selection)')
15class AddAtoms:
16 def __init__(self, gui):
17 self.gui = gui
18 win = self.win = ui.Window(_('Add atoms'), wmtype='utility')
19 win.add(_('Specify chemical symbol, formula, or filename.'))
21 def choose_file():
22 chooser = ui.ASEFileChooser(self.win.win)
23 filename = chooser.go()
24 if filename is None: # No file selected
25 return
27 self.combobox.value = filename
29 # Load the file immediately, so we can warn now in case of error
30 self.readfile(filename, format=chooser.format)
32 if self.gui.images.selected.any():
33 default = current_selection_string
34 else:
35 default = 'H2'
37 self._filename = None
38 self._atoms_from_file = None
40 from ase.collections import g2
41 labels = sorted(name for name in g2.names
42 if len(g2[name]) > 1)
43 values = labels
45 combobox = ui.ComboBox(labels, values)
46 win.add([_('Add:'), combobox,
47 ui.Button(_('File ...'), callback=choose_file)])
48 ui.bind_enter(combobox.widget, lambda e: self.add())
50 combobox.value = default
51 self.combobox = combobox
53 spinners = [ui.SpinBox(0.0, -1e3, 1e3, 0.1, rounding=2, width=3)
54 for __ in range(3)]
56 win.add([_('Coordinates:')] + spinners)
57 self.spinners = spinners
58 win.add(_('Coordinates are relative to the center of the selection, '
59 'if any, else absolute.'))
60 self.picky = ui.CheckButton(_('Check positions'), True)
61 win.add([ui.Button(_('Add'), self.add),
62 self.picky])
63 self.focus()
65 def readfile(self, filename, format=None):
66 if filename == self._filename:
67 # We have this file already
68 return self._atoms_from_file
70 from ase.io import read
71 try:
72 atoms = read(filename)
73 except Exception as err:
74 ui.show_io_error(filename, err)
75 atoms = None
76 filename = None
78 # Cache selected Atoms/filename (or None) for future calls
79 self._atoms_from_file = atoms
80 self._filename = filename
81 return atoms
83 def get_atoms(self):
84 # Get the text, whether it's a combobox item or not
85 val = self.combobox.widget.get()
87 if val == current_selection_string:
88 selection = self.gui.images.selected.copy()
89 if selection.any():
90 atoms = self.gui.atoms.copy()
91 return atoms[selection[:len(self.gui.atoms)]]
93 if val in atomic_numbers: # Note: This means val is a symbol!
94 return Atoms(val)
96 if val.isdigit() and int(val) < len(chemical_symbols):
97 return Atoms(numbers=[int(val)])
99 from ase.collections import g2
100 if val in g2.names:
101 return g2[val]
103 if os.path.exists(val):
104 return self.readfile(val) # May show UI error
106 ui.showerror(_('Cannot add atoms'),
107 _('{} is neither atom, molecule, nor file')
108 .format(val))
110 return None
112 def getcoords(self):
113 addcoords = np.array([spinner.value for spinner in self.spinners])
115 pos = self.gui.atoms.positions
116 if self.gui.images.selected[:len(pos)].any():
117 pos = pos[self.gui.images.selected[:len(pos)]]
118 center = pos.mean(0)
119 addcoords += center
121 return addcoords
123 def focus(self):
124 self.combobox.widget.focus_set()
126 def add(self):
127 newatoms = self.get_atoms()
128 if newatoms is None: # Error dialog was shown
129 return
131 newcenter = self.getcoords()
133 # Not newatoms.center() because we want the same centering method
134 # used for adding atoms relative to selections (mean).
135 previous_center = newatoms.positions.mean(0)
136 newatoms.positions += newcenter - previous_center
138 atoms = self.gui.atoms
139 if len(atoms) and self.picky.value:
140 from ase.geometry import get_distances
141 _disps, dists = get_distances(atoms.positions,
142 newatoms.positions)
143 mindist = dists.min()
144 if mindist < 0.5:
145 ui.showerror(_('Bad positions'),
146 _('Atom would be less than 0.5 Å from '
147 'an existing atom. To override, '
148 'uncheck the check positions option.'))
149 return
151 self.gui.add_atoms_and_select(newatoms)