From a7ac57c9e4ddb90ebc0446f8f4104b8f7cf741ce Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 20 Apr 2020 17:23:21 +0200 Subject: [PATCH 1/5] CodeDropdown: hidden codes and codes on disabled computers. (#68) This PR adds two traits: * allow_hidden_codes: to look for hidden codes or not. * allow_disabled_computers: to look for disabled_computers or not. It also fixes a bug of not showing the description message. --- aiidalab_widgets_base/codes.py | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/aiidalab_widgets_base/codes.py b/aiidalab_widgets_base/codes.py index 9c62154c9..b45d4f5de 100644 --- a/aiidalab_widgets_base/codes.py +++ b/aiidalab_widgets_base/codes.py @@ -4,7 +4,7 @@ import ipywidgets as ipw from IPython.display import clear_output -from traitlets import Dict, Instance, Unicode, Union, link, validate +from traitlets import Bool, Dict, Instance, Unicode, Union, link, validate from aiida.orm import Code, QueryBuilder, User @@ -30,35 +30,43 @@ class CodeDropdown(ipw.VBox): codes(Dict): Trait that contains a dictionary (label => Code instance) for all codes found in the AiiDA database for the selected plugin. It is linked to the 'options' trait of the `self.dropdown` widget. + + allow_hidden_codes(Bool): Trait that defines whether to show hidden codes or not. + + allow_disabled_computers(Bool): Trait that defines whether to show codes on disabled + computers. """ selected_code = Union([Unicode(), Instance(Code)], allow_none=True) codes = Dict(allow_none=True) + allow_hidden_codes = Bool(False) + allow_disabled_computers = Bool(False) - def __init__(self, input_plugin, text='Select code:', path_to_root='../', **kwargs): + def __init__(self, input_plugin, description='Select code:', path_to_root='../', **kwargs): """Dropdown for Codes for one input plugin. - :param input_plugin: Input plugin of codes to show - :type input_plugin: str - :param text: Text to display before dropdown - :type text: str + input_plugin (str): Input plugin of codes to show. + + description (str): Description to display before the dropdown. """ + self.output = ipw.Output() self.input_plugin = input_plugin - self.output = ipw.Output() - self.dropdown = ipw.Dropdown(optionsdescription=text, disabled=True, value=None) + self.dropdown = ipw.Dropdown(description=description, disabled=True, value=None) link((self, 'codes'), (self.dropdown, 'options')) link((self.dropdown, 'value'), (self, 'selected_code')) - self._btn_refresh = ipw.Button(description="Refresh", layout=ipw.Layout(width="70px")) - self._btn_refresh.on_click(self.refresh) + btn_refresh = ipw.Button(description="Refresh", layout=ipw.Layout(width="70px")) + btn_refresh.on_click(self.refresh) + + self.observe(self.refresh, names=['allow_disabled_computers', 'allow_hidden_codes']) # FOR LATER: use base_url here, when it will be implemented in the appmode. self._setup_another = ipw.HTML(value="""Setup new code""".format( path_to_root=path_to_root, label=input_plugin, plugin=input_plugin)) - children = [ipw.HBox([self.dropdown, self._btn_refresh, self._setup_another]), self.output] + children = [ipw.HBox([self.dropdown, btn_refresh, self._setup_another]), self.output] super().__init__(children=children, **kwargs) @@ -67,23 +75,15 @@ def __init__(self, input_plugin, text='Select code:', path_to_root='../', **kwar def _get_codes(self): """Query the list of available codes.""" - querybuild = QueryBuilder() - querybuild.append(Code, - filters={ - 'attributes.input_plugin': { - '==': self.input_plugin - }, - 'extras.hidden': { - "~==": True - } - }, - project=['*']) - - # Only codes on computers configured for the current user. + user = User.objects.get_default() + return { self._full_code_label(c[0]): c[0] - for c in querybuild.all() - if c[0].computer.is_user_configured(User.objects.get_default()) + for c in QueryBuilder().append(Code, filters={ + 'attributes.input_plugin': self.input_plugin + }).all() + if c[0].computer.is_user_configured(user) and (self.allow_hidden_codes or not c[0].hidden) and + (self.allow_disabled_computers or c[0].computer.is_user_enabled(user)) } @staticmethod From 8947246aba7a06508e265eee8f5b1013429f51f9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 20 Apr 2020 19:03:33 +0200 Subject: [PATCH 2/5] ComputerDropdown widget modifications: (#76) * Describe allow_select_disabled trait in class docstring. * Rename `text` constructor parameter with `description`. * Remove unnecessary trait setup in the constructor. --- aiidalab_widgets_base/computers.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/aiidalab_widgets_base/computers.py b/aiidalab_widgets_base/computers.py index 8a1d97dd4..2182b231c 100644 --- a/aiidalab_widgets_base/computers.py +++ b/aiidalab_widgets_base/computers.py @@ -7,7 +7,7 @@ import pexpect import ipywidgets as ipw from IPython.display import clear_output -from traitlets import Bool, Dict, Instance, Int, Unicode, Union, link, observe, validate +from traitlets import Bool, Dict, Instance, Int, Unicode, Union, link, validate from aiida.common import NotExistent from aiida.orm import Computer, QueryBuilder, User @@ -663,28 +663,27 @@ class ComputerDropdown(ipw.VBox): computers(Dict): Trait that contains a dictionary (label => Computer instance) for all computers found in the AiiDA database. It is linked to the 'options' trait of `self._dropdown` widget. + + allow_select_disabled(Bool): Trait that defines whether to show disabled computers. """ selected_computer = Union([Unicode(), Instance(Computer)], allow_none=True) computers = Dict(allow_none=True) allow_select_disabled = Bool(False) - def __init__(self, text='Select computer:', path_to_root='../', **kwargs): + def __init__(self, description='Select computer:', path_to_root='../', **kwargs): """Dropdown for configured AiiDA Computers. - text (str): Text to display before dropdown. + description (str): Text to display before dropdown. path_to_root (str): Path to the app's root folder. """ self.output = ipw.Output() - if 'allow_select_disabled' in kwargs: - self.allow_select_disabled = kwargs['allow_select_disabled'] - self._dropdown = ipw.Dropdown(options={}, value=None, - description=text, + description=description, style={'description_width': 'initial'}, disabled=True) link((self, 'computers'), (self._dropdown, 'options')) @@ -693,6 +692,8 @@ def __init__(self, text='Select computer:', path_to_root='../', **kwargs): btn_refresh = ipw.Button(description="Refresh", layout=ipw.Layout(width="70px")) btn_refresh.on_click(self.refresh) + self.observe(self.refresh, names='allow_select_disabled') + self._setup_another = ipw.HTML( value=""" Setup new computer""".format(path_to_root=path_to_root)) @@ -713,7 +714,6 @@ def _get_computers(self): if c[0].is_user_configured(user) and (self.allow_select_disabled or c[0].is_user_enabled(user)) } - @observe('allow_select_disabled') def refresh(self, _=None): """Refresh the list of configured computers.""" From 27742fe5e9a3bb3f710f49a57934b7e06c81a79c Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Mon, 4 May 2020 14:50:49 +0200 Subject: [PATCH 3/5] Add a possibility to set structure label. (#77) Display the label in the `StructureBrowserWidget`. --- aiidalab_widgets_base/structures.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 7bc289b86..071758a9b 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -70,7 +70,8 @@ def __init__(self, importers, storable=True, node_class=None, **kwargs): link((data_format, 'label'), (self, 'node_class')) # Description that is stored along with the new structure. - self.structure_description = ipw.Text(placeholder="Description (optional)") + self.structure_label = ipw.Text(description='Label') + self.structure_description = ipw.Text(description='Description') # Displaying structure importers. if len(importers) == 1: @@ -100,6 +101,7 @@ def __init__(self, importers, storable=True, node_class=None, **kwargs): list(self.SUPPORTED_DATA_FORMATS.keys()))) self.output = ipw.HTML('') + store_and_description.append(self.structure_label) store_and_description.append(self.structure_description) store_and_description = ipw.HBox(store_and_description) @@ -109,7 +111,7 @@ def __init__(self, importers, storable=True, node_class=None, **kwargs): def _on_click_store(self, change): # pylint: disable=unused-argument self.store_structure() - def store_structure(self, label=None, description=None): + def store_structure(self): """Stores the structure in AiiDA database.""" if self.structure_node is None: @@ -117,10 +119,8 @@ def store_structure(self, label=None, description=None): if self.structure_node.is_stored: self.output.value = "Already stored in AiiDA [{}], skipping...".format(self.structure_node) return - if label: - self.structure_node.label = label - if description: - self.structure_node.description = description + self.structure_node.label = self.structure_label.value + self.structure_node.description = self.structure_description.value self.structure_node.store() self.output.value = "Stored in AiiDA [{}]".format(self.structure_node) @@ -133,10 +133,6 @@ def _default_node_class(): def _change_structure_node(self, _=None): self._structure_changed() - @default('structure') - def _default_structure(self): - return None - @validate('structure') def _valid_structure(self, change): """Returns ASE atoms object and sets structure_node trait.""" @@ -189,8 +185,7 @@ def _structure_changed(self, _=None): # Setting the structure_node trait. self.set_trait('structure_node', structure_node) - self.structure_node.description = self.structure_description.value - self.structure_node.label = self.structure.get_chemical_formula() + self.structure_label.value = self.structure.get_chemical_formula() @default('structure_node') def _default_structure_node(self): @@ -382,6 +377,7 @@ def search(self, _=None): label += " | " + mch.ctime.strftime("%Y-%m-%d %H:%M") label += " | " + mch.get_extra("formula") label += " | " + mch.node_type.split('.')[-2] + label += " | " + mch.label label += " | " + mch.description options[label] = mch From 9d2322a41060cd62070d20775c3658569e32a2f4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 6 May 2020 23:23:42 +0200 Subject: [PATCH 4/5] Structure editor. (#74) This PR adds a possibility to specify a structure editor for the StructureManager. In addition, it provides a basic editor that allows performing common operations such as adding atom/group, removing atom(s), moving and rotating selected atoms around an axis. Co-authored-by: Carlo Pignedoli --- aiidalab_widgets_base/__init__.py | 1 + aiidalab_widgets_base/data/__init__.py | 61 +++++ aiidalab_widgets_base/structures.py | 294 ++++++++++++++++++++++++- structures.ipynb | 52 ++++- 4 files changed, 385 insertions(+), 23 deletions(-) create mode 100644 aiidalab_widgets_base/data/__init__.py diff --git a/aiidalab_widgets_base/__init__.py b/aiidalab_widgets_base/__init__.py index 585a3defb..855f0cf0d 100644 --- a/aiidalab_widgets_base/__init__.py +++ b/aiidalab_widgets_base/__init__.py @@ -13,6 +13,7 @@ from .process import ProcessFollowerWidget, ProgressBarWidget, RunningCalcJobOutputWidget, SubmitButtonWidget # noqa from .structures import StructureManagerWidget # noqa from .structures import StructureBrowserWidget, StructureExamplesWidget, StructureUploadWidget, SmilesWidget # noqa +from .structures import BasicStructureEditor # noqa from .structures_multi import MultiStructureUploadWidget # noqa from .viewers import viewer # noqa diff --git a/aiidalab_widgets_base/data/__init__.py b/aiidalab_widgets_base/data/__init__.py new file mode 100644 index 000000000..33841fffc --- /dev/null +++ b/aiidalab_widgets_base/data/__init__.py @@ -0,0 +1,61 @@ +"""Useful functions that provide access to some data. """ +from ase import Atom, Atoms +import numpy as np +import ipywidgets as ipw + +# The first atom is anchoring, so the new bond will be connecting it +# The direction of the new bond is (-1, -1, -1). +LIGANDS = { + 'Select ligand': + 0, + 'Methyl -CH3': [('C', 0, 0, 0), ('H', 0.23962342, -0.47699124, 0.78585262), + ('H', 0.78584986, 0.23962732, -0.47698795), ('H', -0.47699412, 0.78585121, 0.23962671)], + 'Methylene =CH2': [('C', 0, 0, 0), ('H', -0.39755349, 0.59174911, 0.62728004), + ('H', 0.94520686, -0.04409933, -0.07963039)], + 'Hydroxy -OH': [('O', 0, 0, 0), ('H', 0.87535922, -0.3881659, 0.06790889)], + 'Amine -NH2': [('N', 0, 0, 0), ('H', 0.7250916, -0.56270993, 0.42151063), + ('H', -0.56261958, 0.4215284, 0.72515241)], +} + + +class LigandSelectorWidget(ipw.Dropdown): + """Class to select ligands that are returned as `Atoms` object""" + + def __init__(self, value=0, description="Select ligand", **kwargs): + self.style = {'description_width': 'initial'} + self.layout = {'width': 'initial'} + super().__init__(value=value, description=description, options=LIGANDS, **kwargs) + + def rotate(self, align_to=(0, 0, 1), remove_anchor=False): + """Rotate group in such a way that vector which was (-1,-1,-1) + is alligned with align_to.""" + + vect = np.array(align_to) + norm = np.linalg.norm(vect) + + if self.value == 0: + return None + + mol = Atoms() + for atm in self.value: + mol.append(Atom(atm[0], atm[1:])) + + # Bad cases. + if norm == 0.0: + vect = np.array((1, 1, 1)) / np.sqrt(3) + else: + vect = vect / norm + + mol.rotate((1, 1, 1), vect) + + if remove_anchor: + del mol[0] + + return mol + + @property + def anchoring_atom(self): + """Return anchoring atom chemical symbol.""" + if self.value == 0: + return None + return self.value[0][0] diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 071758a9b..6aac1e114 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -7,17 +7,20 @@ from collections import OrderedDict import numpy as np import ipywidgets as ipw -from traitlets import Instance, Unicode, Union, link, default, observe, validate +from traitlets import Instance, Int, Set, Unicode, Union, link, default, observe, validate # ASE imports from ase import Atom, Atoms -from ase.data import chemical_symbols +from ase.data import chemical_symbols, covalent_radii # AiiDA and AiiDA lab imports from aiida.orm import CalcFunctionNode, CalcJobNode, Data, QueryBuilder, Node, WorkChainNode from aiida.plugins import DataFactory from .utils import get_ase_from_file from .viewers import StructureDataViewer +from .data import LigandSelectorWidget + +SYMBOL_RADIUS = {key: covalent_radii[i] for i, key in enumerate(chemical_symbols)} class StructureManagerWidget(ipw.VBox): @@ -35,7 +38,7 @@ class StructureManagerWidget(ipw.VBox): SUPPORTED_DATA_FORMATS = {'CifData': 'cif', 'StructureData': 'structure'} - def __init__(self, importers, storable=True, node_class=None, **kwargs): + def __init__(self, importers, editors=None, storable=True, node_class=None, **kwargs): """ Arguments: importers(list): list of tuples each containing the displayed name of importer and the @@ -87,11 +90,22 @@ def __init__(self, importers, storable=True, node_class=None, **kwargs): self._structure_sources_tab.set_title(i, label) link((self, 'structure'), (importer, 'structure')) + # Displaying structure editors + if editors and len(editors) == 1: + structure_editors_tab = editors[0][1] + structure_editors_tab.manager = self + elif editors: + structure_editors_tab = ipw.Tab() + structure_editors_tab.children = [i[1] for i in editors] + for i, (label, editor) in enumerate(editors): + structure_editors_tab.set_title(i, label) + editor.manager = self + else: + structure_editors_tab = None + # Store button, store class selector, description. - store_and_description = [] + store_and_description = [self.btn_store] if storable else [] - if storable: - store_and_description.append(self.btn_store) if node_class is None: store_and_description.append(data_format) elif node_class in self.SUPPORTED_DATA_FORMATS: @@ -101,12 +115,17 @@ def __init__(self, importers, storable=True, node_class=None, **kwargs): list(self.SUPPORTED_DATA_FORMATS.keys()))) self.output = ipw.HTML('') - store_and_description.append(self.structure_label) - store_and_description.append(self.structure_description) - store_and_description = ipw.HBox(store_and_description) + store_and_description = ipw.HBox(store_and_description + [self.structure_label, self.structure_description]) - super().__init__(children=[self._structure_sources_tab, self.viewer, store_and_description, self.output], - **kwargs) + children = [self._structure_sources_tab, self.viewer, store_and_description] + if structure_editors_tab: + accordion = ipw.Accordion([structure_editors_tab]) + accordion.selected_index = None + accordion.set_title(0, 'Edit Structure') + children += [accordion] + + children += [self.output] + super().__init__(children=children, **kwargs) def _on_click_store(self, change): # pylint: disable=unused-argument self.store_structure() @@ -470,3 +489,256 @@ def _on_button_pressed(self, change): # pylint: disable=unused-argument @default('structure') def _default_structure(self): return None + + +class BasicStructureEditor(ipw.VBox): + """Widget that allows for the basic structure editing.""" + + manager = Instance(StructureManagerWidget, allow_none=True) + structure = Instance(Atoms, allow_none=True) + selection = Set(Int) + + def __init__(self): + + # Define action vector. + self.axis_p1 = ipw.Text(description='Starting point', value='0 0 0', layout={'width': 'initial'}) + self.axis_p2 = ipw.Text(description='Ending point', value='0 0 1', layout={'width': 'initial'}) + btn_def_atom1 = ipw.Button(description='From selection', layout={'width': 'initial'}) + btn_def_atom1.on_click(self.def_axis_p1) + btn_def_atom2 = ipw.Button(description='From selection', layout={'width': 'initial'}) + btn_def_atom2.on_click(self.def_axis_p2) + btn_get_from_camera = ipw.Button(description='Perp. to screen', + button_style='warning', + layout={'width': 'initial'}) + btn_get_from_camera.on_click(self.def_perpendicular_to_screen) + + # Define action point. + self.point = ipw.Text(description='Action point', value='0 0 0', layout={'width': 'initial'}) + btn_def_pnt = ipw.Button(description='From selection', layout={'width': 'initial'}) + btn_def_pnt.on_click(self.def_point) + + # Move atoms. + btn_move_dr = ipw.Button(description='Move', layout={'width': 'initial'}) + btn_move_dr.on_click(self.translate_dr) + self.displacement = ipw.FloatText(description='Move along the action vector', + value=1, + step=0.1, + style={'description_width': 'initial'}, + layout={'width': 'initial'}) + + btn_move_dxyz = ipw.Button(description='Move', layout={'width': 'initial'}) + btn_move_dxyz.on_click(self.translate_dxdydz) + self.dxyz = ipw.Text(description='Move along (XYZ)', + value='0 0 0', + style={'description_width': 'initial'}, + layout={ + 'width': 'initial', + 'margin': '0px 0px 0px 20px' + }) + # Rotate atoms. + btn_rotate = ipw.Button(description='Rotate', layout={'width': '10%'}) + btn_rotate.on_click(self.rotate) + self.phi = ipw.FloatText(description='Rotate around the action vector which starts from the action point', + value=0, + step=5, + style={'description_width': 'initial'}, + layout={'width': 'initial'}) + + # Atoms selection. + self.element = ipw.Dropdown( + description="Select element", + options=chemical_symbols[1:], + value="H", + style={'description_width': 'initial'}, + layout={'width': 'initial'}, + ) + + def disable_element(_=None): + if self.ligand.value == 0: + self.element.disabled = False + else: + self.element.disabled = True + + # Ligand selection. + self.ligand = LigandSelectorWidget() + self.ligand.observe(disable_element, names='value') + + # Add atom. + btn_add = ipw.Button(description='Add to selected', layout={'width': 'initial'}) + btn_add.on_click(self.add) + self.bond_length = ipw.FloatText(description="Bond lenght.", value=1.0, layout={'width': '140px'}) + self.use_covalent_radius = ipw.Checkbox( + value=False, + description='Use covalent radius', + style={'description_width': 'initial'}, + ) + self.use_covalent_radius.observe(self._observe_use_cov_radius, names='value') + + # Modify atom. + btn_modify = ipw.Button(description='Modify selected', button_style='warning', layout={'width': 'initial'}) + btn_modify.on_click(self.mod_element) + + # Remove atom. + btn_remove = ipw.Button(description='Remove selected', button_style='danger', layout={'width': 'initial'}) + btn_remove.on_click(self.remove) + + super().__init__(children=[ + ipw.HTML("Action vector and point:", layout={'margin': '20px 0px 10px 0px'}), + ipw.HBox([self.axis_p1, btn_def_atom1, self.axis_p2, btn_def_atom2, btn_get_from_camera], + layout={'margin': '0px 0px 0px 20px'}), + ipw.HBox([self.point, btn_def_pnt], layout={'margin': '0px 0px 0px 20px'}), + ipw.HTML("Move atom(s):", layout={'margin': '20px 0px 10px 0px'}), + ipw.HBox([self.displacement, btn_move_dr, self.dxyz, btn_move_dxyz], layout={'margin': '0px 0px 0px 20px'}), + ipw.HBox([self.phi, btn_rotate], layout={'margin': '0px 0px 0px 20px'}), + ipw.HTML("Modify atom(s):", layout={'margin': '20px 0px 10px 0px'}), + ipw.HBox([self.element, self.ligand], layout={'margin': '0px 0px 0px 20px'}), + ipw.HBox([ + btn_modify, + btn_add, + self.bond_length, + self.use_covalent_radius, + ], + layout={'margin': '0px 0px 0px 20px'}), + ipw.HBox([btn_remove], layout={'margin': '0px 0px 0px 20px'}), + ]) + + @observe('manager') + def _change_manager(self, value): + """Set structure manager trait.""" + manager = value['new'] + if manager is None: + return + link((manager, 'structure'), (self, 'structure')) + link((self, 'selection'), (manager.viewer, 'selection')) + + def _observe_use_cov_radius(self, _=None): + if self.use_covalent_radius.value: + self.bond_length.disabled = True + else: + self.bond_length.disabled = False + + def str2vec(self, string): + return np.array(list(map(float, string.split()))) + + def vec2str(self, vector): + return str(round(vector[0], 2)) + ' ' + str(round(vector[1], 2)) + ' ' + str(round(vector[2], 2)) + + def sel2com(self): + """Get center of mass of the selection.""" + selection = list(self.selection) + if selection: + com = self.structure[selection].get_center_of_mass() + else: + com = [0, 0, 0] + + return com + + @property + def action_vector(self): + normal = self.str2vec(self.axis_p2.value) - self.str2vec(self.axis_p1.value) + return normal / np.linalg.norm(normal) + + def def_point(self, _=None): + self.point.value = self.vec2str(self.sel2com()) + + def def_axis_p1(self, _=None): + self.axis_p1.value = self.vec2str(self.sel2com()) + + def def_axis_p2(self, _=None): + com = self.structure[list(self.selection)].get_center_of_mass() if self.selection else [0, 0, 1] + self.axis_p2.value = self.vec2str(com) + + def def_perpendicular_to_screen(self, _=None): + cmr = self.manager.viewer._viewer._camera_orientation # pylint: disable=protected-access + if cmr: + self.axis_p1.value = "0 0 0" + self.axis_p2.value = self.vec2str([-cmr[2], -cmr[6], -cmr[10]]) + + def translate_dr(self, _=None): + """Translate by dr along the selected vector.""" + atoms = self.structure.copy() + selection = self.selection + + atoms.positions[list(self.selection)] += np.array(self.action_vector * self.displacement.value) + + self.structure = atoms + self.selection = selection + + def translate_dxdydz(self, _=None): + """Translate along the selected vector.""" + selection = self.selection + atoms = self.structure.copy() + + # The action. + atoms.positions[list(self.selection)] += np.array(self.str2vec(self.dxyz.value)) + + self.structure = atoms + self.selection = selection + + def rotate(self, _=None): + """Rotate atoms around selected point in space and vector.""" + + selection = self.selection + atoms = self.structure.copy() + + # The action. + rotated_subset = atoms[list(self.selection)] + vec = self.str2vec(self.vec2str(self.action_vector)) + center = self.str2vec(self.point.value) + rotated_subset.rotate(self.phi.value, v=vec, center=center, rotate_cell=False) + atoms.positions[list(self.selection)] = rotated_subset.positions + + self.structure = atoms + self.selection = selection + + def mod_element(self, _=None): + """Modify selected atoms into the given element.""" + atoms = self.structure.copy() + selection = self.selection + + if self.ligand.value == 0: + for idx in self.selection: + atoms[idx].symbol = self.element.value + else: + initial_ligand = self.ligand.rotate(align_to=self.action_vector, remove_anchor=True) + for idx in self.selection: + position = self.structure.positions[idx].copy() + lgnd = initial_ligand.copy() + lgnd.translate(position) + atoms += lgnd + + self.structure = atoms + self.selection = selection + + def add(self, _=None): + """Add atoms.""" + atoms = self.structure.copy() + selection = self.selection + + if self.ligand.value == 0: + initial_ligand = Atoms([Atom(self.element.value, [0, 0, 0])]) + rad = SYMBOL_RADIUS[self.element.value] + else: + initial_ligand = self.ligand.rotate(align_to=self.action_vector) + rad = SYMBOL_RADIUS[self.ligand.anchoring_atom] + + for idx in self.selection: + # It is important to copy, otherwise the initial structure will be modified + position = self.structure.positions[idx].copy() + lgnd = initial_ligand.copy() + + if self.use_covalent_radius.value: + lgnd.translate(position + self.action_vector * (SYMBOL_RADIUS[self.structure.symbols[idx]] + rad)) + else: + lgnd.translate(position + self.action_vector * self.bond_length.value) + + atoms += lgnd + + self.structure = atoms + self.selection = selection + + def remove(self, _): + """Remove selected atoms.""" + atoms = self.structure.copy() + del [atoms[list(self.selection)]] + self.structure = atoms diff --git a/structures.ipynb b/structures.ipynb index 2c4ea7bbc..8ae627260 100644 --- a/structures.ipynb +++ b/structures.ipynb @@ -1,5 +1,18 @@ { "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%javascript\n", + "\n", + "IPython.OutputArea.prototype._should_scroll = function(lines) {\n", + " return false;\n", + "}" + ] + }, { "cell_type": "code", "execution_count": null, @@ -8,18 +21,33 @@ "source": [ "from aiidalab_widgets_base import CodQueryWidget, SmilesWidget, StructureExamplesWidget\n", "from aiidalab_widgets_base import StructureBrowserWidget, StructureManagerWidget, StructureUploadWidget\n", - "\n", - "widget = StructureManagerWidget(importers=[\n", - " (\"From computer\", StructureUploadWidget()),\n", - " (\"COD\", CodQueryWidget()),\n", - " (\"AiiDA database\", StructureBrowserWidget()),\n", - " (\"SMILES\", SmilesWidget()), # requires OpenBabel! \n", - " (\"From Examples\", StructureExamplesWidget(\n", - " examples=[\n", - " (\"Silicon oxide\", \"miscellaneous/structures/SiO2.xyz\")\n", - " ])\n", - " ),\n", - "])\n", + "from aiidalab_widgets_base import BasicStructureEditor" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "widget = StructureManagerWidget(\n", + " importers=[\n", + " (\"From computer\", StructureUploadWidget()),\n", + " (\"COD\", CodQueryWidget()),\n", + " (\"AiiDA database\", StructureBrowserWidget()),\n", + " (\"SMILES\", SmilesWidget()), # requires OpenBabel!\n", + " (\"From Examples\", StructureExamplesWidget(\n", + " examples=[\n", + " (\"Silicon oxide\", \"miscellaneous/structures/SiO2.xyz\")\n", + " ])\n", + " ),\n", + " ],\n", + " editors = [\n", + " (\"Basic Editor\", BasicStructureEditor()),\n", + " ],\n", + ")\n", "# widget = StructureUploadWidget()\n", "# Enforce node format to be CifData:\n", "# widget = StructureManagerWidget(importers = [(\"From computer\", StructureUploadWidget())], node_class=\"CifData\")\n", From c0bf29a360768ae8b1a2b31f77a948e01f2f3536 Mon Sep 17 00:00:00 2001 From: Aliaksandr Yakutovich Date: Wed, 6 May 2020 23:34:49 +0200 Subject: [PATCH 5/5] Prepare release 1.0.0b5 --- aiidalab_widgets_base/__init__.py | 2 +- setup.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aiidalab_widgets_base/__init__.py b/aiidalab_widgets_base/__init__.py index 855f0cf0d..cad144c9b 100644 --- a/aiidalab_widgets_base/__init__.py +++ b/aiidalab_widgets_base/__init__.py @@ -17,4 +17,4 @@ from .structures_multi import MultiStructureUploadWidget # noqa from .viewers import viewer # noqa -__version__ = "1.0.0b4" +__version__ = "1.0.0b5" diff --git a/setup.json b/setup.json index ac3cdb4e9..22b7cccaf 100644 --- a/setup.json +++ b/setup.json @@ -1,5 +1,5 @@ { - "version": "1.0.0b4", + "version": "1.0.0b5", "name": "aiidalab-widgets-base", "author_email": "aiidalab@materialscloud.org", "url": "https://github.com/aiidalab/aiidalab-widgets-base",