Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structure viewer for 0d hide the cell tab #522

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 3 additions & 30 deletions aiidalab_widgets_base/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,32 +376,6 @@ def __init__(
children=[self.file_upload, supported_formats, self._status_message]
)

def _validate_and_fix_ase_cell(self, ase_structure, vacuum_ang=10.0):
"""
Checks if the ase Atoms object has a cell set,
otherwise sets it to bounding box plus specified "vacuum" space
"""
if not ase_structure:
return None

cell = ase_structure.cell

# TODO: Since AiiDA 2.0, zero cell is possible if PBC=false
# so we should honor that here and do not put artificial cell
# around gas phase molecules.
if (
np.linalg.norm(cell[0]) < 0.1
or np.linalg.norm(cell[1]) < 0.1
or np.linalg.norm(cell[2]) < 0.1
):
# if any of the cell vectors is too short, consider it faulty
# set cell as bounding box + vacuum_ang
bbox = np.ptp(ase_structure.positions, axis=0)
new_structure = ase_structure.copy()
new_structure.cell = bbox + vacuum_ang
return new_structure
return ase_structure

def _on_file_upload(self, change=None):
"""When file upload button is pressed."""
for fname, item in change["new"].items():
Expand Down Expand Up @@ -444,9 +418,9 @@ def _read_structure(self, fname, content):
return TrajectoryData(
structurelist=[
StructureData(
ase=self._validate_and_fix_ase_cell(ase_struct)
ase=ase_structure,
)
for ase_struct in structures
for ase_structure in structures
]
)
else:
Expand All @@ -455,7 +429,7 @@ def _read_structure(self, fname, content):
"""
return None

return self._validate_and_fix_ase_cell(structures[0])
return structures[0]


class StructureExamplesWidget(ipw.VBox):
Expand Down Expand Up @@ -720,7 +694,6 @@ def _make_ase(self, species, positions, smiles):
if len(species) > 2:
positions = PCA(n_components=3).fit_transform(positions)
atoms = ase.Atoms(species, positions=positions, pbc=False)
atoms.cell = np.ptp(atoms.positions, axis=0) + 10
atoms.center()
# We're attaching this info so that it
# can be later stored as an extra on AiiDA Structure node.
Expand Down
121 changes: 75 additions & 46 deletions aiidalab_widgets_base/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,47 +169,74 @@ def __init__(
self._viewer.observe(self._on_atom_click, names="picked")
self._viewer.stage.set_parameters(mouse_preset="pymol")

view_box = ipw.VBox([self._viewer])
self.view_box = ipw.VBox([self._viewer])

configuration_tabs_map = {
"Cell": self._cell_tab(),
"Selection": self._selection_tab(),
"Appearance": self._appearance_tab(),
"Download": self._download_tab(),
}
# Constructing configuration box
if configuration_tabs is None:
self._configuration_tabs = [
"Selection",
"Appearance",
"Lattice",
"Download",
]
else:
self._configuration_tabs = configuration_tabs

if configure_view is not True:
# For backward compatibility, where the Lattice tab was called Cell.
if "Cell" in self._configuration_tabs:
warnings.warn(
"`configure_view` is deprecated, please use `configuration_tabs` instead.",
"`Cell` tab is deprecated, please use `Lattice` instead. Will be removed in the version 3.0.0",
DeprecationWarning,
stacklevel=2,
)
if not configure_view:
configuration_tabs.clear()
self._configuration_tabs.remove("Cell")
self._configuration_tabs.append("Lattice")

# Constructing configuration box
if configuration_tabs is None:
configuration_tabs = ["Selection", "Appearance", "Cell", "Download"]
if len(configuration_tabs) != 0:
self.configuration_box = ipw.Tab(
layout=ipw.Layout(flex="1 1 auto", width="auto")
if configure_view is not True:
warnings.warn(
"`configure_view` is deprecated, please use `configuration_tabs` instead. Will be removed in the version 3.0.0",
DeprecationWarning,
stacklevel=2,
)
self.configuration_box.children = [
configuration_tabs_map[tab_title] for tab_title in configuration_tabs
]
if not configure_view:
self._configuration_tabs.clear()

for i, title in enumerate(configuration_tabs):
self.configuration_box.set_title(i, title)
children = [ipw.HBox([view_box, self.configuration_box])]
view_box.layout = {"width": "60%"}
if len(self._configuration_tabs) != 0:
configuration_box = self._create_tabs(self._configuration_tabs)
children = [ipw.HBox([self.view_box, configuration_box])]
self.view_box.layout = {"width": "60%"}
else:
children = [view_box]
children = [self.view_box]

if "children" in kwargs:
warnings.warn(
"defining `children` is deprecated, will be removed in the version 3.0.0",
DeprecationWarning,
stacklevel=2,
)
children += kwargs.pop("children")

super().__init__(children, **kwargs)

def _create_tabs(self, configuration_tabs):
"""Create tabs for configuration box."""
configuration_tabs_map = {
"Lattice": self._cell_tab(),
"Selection": self._selection_tab(),
"Appearance": self._appearance_tab(),
"Download": self._download_tab(),
}

configuration_box = ipw.Tab(layout=ipw.Layout(flex="1 1 auto", width="auto"))
configuration_box.children = [
configuration_tabs_map[tab_title] for tab_title in configuration_tabs
]

for i, title in enumerate(self._configuration_tabs):
configuration_box.set_title(i, title)

return configuration_box

def _selection_tab(self):
"""Defining the selection tab."""

Expand Down Expand Up @@ -317,7 +344,24 @@ def change_camera(change):

@tl.observe("cell")
def _observe_cell(self, _=None):
# Updtate the Cell and Periodicity.
# Update the widget layout view.
if not self.cell and "Lattice" in self._configuration_tabs:
self._configuration_tabs.remove("Lattice")

if self.cell and "Lattice" not in self._configuration_tabs:
self._configuration_tabs = ["Lattice"] + self._configuration_tabs

if len(self._configuration_tabs) != 0:
configuration_box = self._create_tabs(self._configuration_tabs)
self.children = [ipw.HBox([self.view_box, configuration_box])]
self.view_box.layout = {"width": "60%"}
else:
self.children = [self.view_box]

# Updtate the Lattice tab and Periodicity.

# For molecules, the cell is Cell([0, 0, 0]) and in the condition is still # `False``. That's why it is able to use `if self.cell` to cover both None and
# not cell defined cases.
if self.cell:
self.cell_a.value = "<i><b>a</b></i>: {:.4f} {:.4f} {:.4f}".format(
*self.cell.array[0]
Expand Down Expand Up @@ -363,22 +407,6 @@ def _observe_cell(self, _=None):
self.periodicity.value = (
f"Periodicity: {periodicity_map[tuple(self.structure.pbc)]}"
)
else:
self.cell_a.value = "<i><b>a</b></i>:"
self.cell_b.value = "<i><b>b</b></i>:"
self.cell_c.value = "<i><b>c</b></i>:"

self.cell_a_length.value = "|<i><b>a</b></i>|:"
self.cell_b_length.value = "|<i><b>b</b></i>|:"
self.cell_c_length.value = "|<i><b>c</b></i>|:"

self.cell_alpha.value = "&alpha;:"
self.cell_beta.value = "&beta;:"
self.cell_gamma.value = "&gamma;:"

self.cell_spacegroup.value = ""
self.cell_hall.value = ""
self.periodicity.value = ""

def _cell_tab(self):
self.cell_a = ipw.HTML()
Expand All @@ -397,8 +425,6 @@ def _cell_tab(self):
self.cell_hall = ipw.HTML()
self.periodicity = ipw.HTML()

self._observe_cell()

return ipw.VBox(
[
ipw.HTML("Length unit: angstrom (Å)"),
Expand Down Expand Up @@ -789,7 +815,9 @@ def __init__(self, structure=None, **kwargs):

@tl.observe("supercell")
def repeat(self, _=None):
if self.structure is not None:
# need to check if cell is defined, otherwise it will throw an error
# since it makes no sense to repeat a molecule
if self.structure is not None and self.cell:
self.set_trait("displayed_structure", self.structure.repeat(self.supercell))

@tl.validate("structure")
Expand Down Expand Up @@ -1096,7 +1124,8 @@ def add_info(indx, atom):

@tl.observe("displayed_selection")
def _observe_displayed_selection_2(self, _=None):
self.selection_info.value = self.create_selection_info()
if self.displayed_selection:
self.selection_info.value = self.create_selection_info()


@register_viewer_widget("data.core.folder.FolderData.")
Expand Down
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ def structure_data_object():
return structure


@pytest.fixture
def molecule_ase_object():
"""Return an ase molecule object."""
from ase.build import molecule

return molecule("H2O")


@pytest.fixture
def bands_data_object():
BandsData = plugins.DataFactory("core.array.bands") # noqa: N806
Expand Down
6 changes: 6 additions & 0 deletions tests/test_viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@ def test_structure_data_viwer(structure_data_object):
for fmt, out in format_cases:
v.file_format.label = fmt
assert v._prepare_payload() == out


def test_structure_viewer_for_molecule(molecule_ase_object):
"""Test the structure viewer widget for a molecule."""
v = viewers.StructureDataViewer(molecule_ase_object)
assert isinstance(v, viewers.StructureDataViewer)
Loading