def get_molecule_graph_scene(
    self,
    origin=(0, 0, 0),
    explicitly_calculate_polyhedra_hull=False,
    draw_polyhedra=True,
    **kwargs
) -> Scene:

    primitives = defaultdict(list)

    for idx, site in enumerate(self.molecule):

        connected_sites = self.get_connected_sites(idx)

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            all_connected_sites_present=draw_polyhedra,
            origin=origin,
            explicitly_calculate_polyhedra_hull=explicitly_calculate_polyhedra_hull,
        )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    return Scene(
        name=self.molecule.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
    )
def get_molecule_graph_scene(
    self,
    origin=None,
    explicitly_calculate_polyhedra_hull=False,
    legend=None,
    draw_polyhedra=False,
) -> Scene:

    legend = legend or Legend(self.molecule)

    primitives = defaultdict(list)

    for idx, site in enumerate(self.molecule):

        connected_sites = self.get_connected_sites(idx)

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            origin=origin,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
            draw_polyhedra=draw_polyhedra,
        )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    return Scene(
        name=self.molecule.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin if origin else (0, 0, 0),
    )
Beispiel #3
0
def get_scene_from_molecule(self,
                            origin=None,
                            legend: Optional[Legend] = None):
    """
    Create CTK objects for the lattice and sties
    Args:
        self:  Structure object
        origin: fractional coordinate of the origin
        legend: Legend for the sites

    Returns:
        CTK scene object to be rendered
    """

    origin = origin if origin else (0, 0, 0)

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    for idx, site in enumerate(self):
        site_scene = site.get_scene(
            origin=origin,
            legend=legend,
        )

        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    return Scene(
        name=self.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
Beispiel #4
0
def get_structure_scene(
    self,
    draw_image_atoms=True,
    legend: Optional[Legend] = None,
    origin=None,
) -> Scene:

    origin = origin or list(
        -self.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(draw_image_atoms=draw_image_atoms)

    for (idx, jimage) in sites_to_draw:

        site_scene = self[idx].get_scene(origin=origin, legend=legend)
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.lattice.get_scene(origin=origin))

    return Scene(
        name=self.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
Beispiel #5
0
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        name,
        color_scheme=DEFAULTS["color_scheme"],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
    ) -> Tuple[Scene, Dict[str, str]]:

        # default scene name will be name of component, "_ct_..."
        # strip leading _ since this will cause problems in JavaScript land
        scene = Scene(name=name[1:])

        if graph is None:
            return scene, {}

        struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph)

        # TODO: add radius_scale
        legend = Legend(
            struct_or_mol,
            color_scheme=color_scheme,
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        if isinstance(graph, StructureGraph):
            scene = graph.get_scene(
                draw_image_atoms=draw_image_atoms,
                bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
                hide_incomplete_edges=hide_incomplete_bonds,
                explicitly_calculate_polyhedra_hull=
                explicitly_calculate_polyhedra_hull,
                legend=legend,
            )
        elif isinstance(graph, MoleculeGraph):
            scene = graph.get_scene(legend=legend)

        scene.name = name

        if hasattr(struct_or_mol, "lattice"):
            axes = struct_or_mol.lattice._axes_from_lattice()
            # TODO: fix pop-in ?
            axes.visible = show_compass
            scene.contents.append(axes)

        if scene_additions:
            # TODO: need a Scene.from_json() to make this work
            raise NotImplementedError
            scene["contents"].append(scene_additions)

        return scene.to_json(), legend.get_legend()
Beispiel #6
0
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        color_scheme=DEFAULTS["color_scheme"],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
        group_by_site_property=None,
    ) -> Tuple[Scene, Dict[str, str]]:

        scene = Scene(name="StructureMoleculeComponentScene")

        if graph is None:
            return scene, {}

        struct_or_mol = StructureMoleculeComponent._get_struct_or_mol(graph)

        # TODO: add radius_scale
        legend = Legend(
            struct_or_mol,
            color_scheme=color_scheme,
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        if isinstance(graph, StructureGraph):
            scene = graph.get_scene(
                draw_image_atoms=draw_image_atoms,
                bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
                hide_incomplete_edges=hide_incomplete_bonds,
                explicitly_calculate_polyhedra_hull=
                explicitly_calculate_polyhedra_hull,
                group_by_site_property=group_by_site_property,
                legend=legend,
            )
        elif isinstance(graph, MoleculeGraph):
            scene = graph.get_scene(legend=legend)

        scene.name = "StructureMoleculeComponentScene"

        if hasattr(struct_or_mol, "lattice"):
            axes = struct_or_mol.lattice._axes_from_lattice()
            axes.visible = show_compass
            scene.contents.append(axes)

        scene_json = scene.to_json()

        if scene_additions:
            # TODO: this might be cleaner if we had a Scene.from_json() method
            scene_json["contents"].append(scene_additions)

        return scene_json, legend.get_legend()
Beispiel #7
0
 def scenes(self):
     result = {}
     if len(self.scene_dicts) == 0:
         return {"empty_scene": Scene("empty", contents=[])}
     for k, v in self.scene_dicts.items():
         pos = [
             vert for triangle in v["vertices"][v["faces"]].tolist()
             for vert in triangle
         ]
         result[k] = Scene(k, contents=[Surface(positions=pos)])
     return result
def get_structure_scene(
    self,
    origin: List[float] = None,
    legend: Optional[Legend] = None,
    draw_image_atoms: bool = True,
) -> Scene:
    """
    Create CTK objects for the lattice and sties
    Args:
        self:  Structure object
        origin: fractional coordinate of the origin
        legend: Legend for the sites
        draw_image_atoms: If true draw image atoms that are just outside the
        periodic boundary

    Returns:
        CTK scene object to be rendered
    """

    origin = origin or list(
        -self.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms, )

    for (idx, jimage) in sites_to_draw:

        site = self[idx]
        if jimage != (0, 0, 0):
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )

        site_scene = site.get_scene(legend=legend, )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.lattice.get_scene())

    return Scene(
        name="Structure",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
    def get_scene_and_legend(
        graph: Optional[Union[StructureGraph, MoleculeGraph]],
        color_scale=None,
        radius_strategy=DEFAULTS["radius_strategy"],
        draw_image_atoms=DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell=DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds=DEFAULTS["hide_incomplete_bonds"],
        explicitly_calculate_polyhedra_hull=False,
        scene_additions=None,
        show_compass=DEFAULTS["show_compass"],
    ) -> Tuple[Scene, Dict[str, str]]:

        scene = Scene(name="AceStructureMoleculeComponentScene")

        if graph is None:
            return scene, {}

        structure = StructureComponent._get_structure(graph)

        # TODO: add radius_scale
        legend = Legend(
            structure,
            color_scheme="VESTA",
            radius_scheme=radius_strategy,
            cmap_range=color_scale,
        )

        scene = graph.get_scene(
            draw_image_atoms=draw_image_atoms,
            bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
            hide_incomplete_edges=hide_incomplete_bonds,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )

        scene.name = "StructureComponentScene"

        if hasattr(structure, "lattice"):
            axes = structure.lattice._axes_from_lattice()
            axes.visible = show_compass
            scene.contents.append(axes)

        scene = scene.to_json()
        if scene_additions:
            # TODO: need a Scene.from_json() to make this work
            # raise NotImplementedError
            scene["contents"].append(scene_additions)

        return scene, legend.get_legend()
Beispiel #10
0
    def get_preview_layout(self, struct_in, struct_out):

        if struct_in.lattice == struct_out.lattice:
            return html.Div()

        lattice_in = struct_in.lattice.get_scene()
        lattice_out = struct_out.lattice.get_scene(color="red")

        scene = Scene("lattices", contents=[lattice_in, lattice_out])

        return html.Div(
            [Simple3DScene(data=scene.to_json())],
            style={"width": "100px", "height": "100px"},
        )
def get_isosurface_scene(self,
                         data_key="total",
                         isolvl=0.5,
                         step_size=3,
                         origin=(0.0, 0.0, 0.0),
                         **kwargs):
    """Get the isosurface from a VolumetricData object

    Args:
        data_key (str, optional): Use the volumetric data from self.data[data_key]. Defaults to 'total'.
        isolvl (float, optional): The cutoff for the isosurface to using the same units as VESTA so
        e/bhor and kept grid size independent
        step_size (int, optional): step_size parameter for marching_cubes_lewiner. Defaults to 3.

    Returns:
        [type]: [description]
    """
    vol_data = np.copy(self.data[data_key])
    vol = self.structure.volume
    vol_data = vol_data / vol / _ANGS2_TO_BOHR3

    padded_data = np.pad(vol_data, (0, 1), "wrap")
    vertices, faces, normals, values = measure.marching_cubes_lewiner(
        padded_data, level=isolvl, step_size=step_size)
    # transform to fractional coordinates
    vertices = vertices / (vol_data.shape[0], vol_data.shape[1],
                           vol_data.shape[2])
    vertices = np.dot(vertices,
                      self.structure.lattice.matrix)  # transform to cartesian
    pos = [vert for triangle in vertices[faces].tolist() for vert in triangle]
    return Scene("isosurface",
                 origin=origin,
                 contents=[Surface(pos, show_edges=False, **kwargs)])
def get_volumetric_scene(self,
                         data_key="total",
                         isolvl=0.5,
                         step_size=3,
                         **kwargs):
    """Get the Scene object which contains a structure and a isosurface components

    Args:
        data_key (str, optional): Use the volumetric data from self.data[data_key]. Defaults to 'total'.
        isolvl (float, optional): The cuoff for the isosurface to using the same units as VESTA so e/bhor
        and kept grid size independent
        step_size (int, optional): step_size parameter for marching_cubes_lewiner. Defaults to 3.
        **kwargs: kwargs for the Structure.get_scene function

    Returns:
        [type]: [description]
    """

    struct_scene = self.structure.get_scene(**kwargs)
    iso_scene = self.get_isosurface_scene(
        data_key=data_key,
        isolvl=isolvl,
        step_size=step_size,
        origin=struct_scene.origin,
    )
    return Scene(
        name=self.structure.composition.reduced_formula,
        origin=struct_scene.origin,
        contents=[struct_scene, iso_scene],
    )
Beispiel #13
0
 def scene(self, name=""):
     faces = self.scene_dict["faces"]
     pos = [
         vert for triangle in self.scene_dict["vertices"][faces].tolist()
         for vert in triangle
     ]
     return Scene(name=name, contents=[Surface(positions=pos)])
def get_structure_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=False,
    legend: Optional[Legend] = None,
) -> Scene:

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        site_scene = site.get_scene(origin=origin, legend=legend)
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(
        self.structure.lattice.get_scene(origin=origin))

    return Scene(
        name=self.structure.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
        origin=origin,
    )
Beispiel #15
0
    def _compass_from_lattice(
        lattice,
        origin=(0, 0, 0),
        scale=0.7,
        offset=0.15,
        compass_style="corner",
        **kwargs,
    ):
        # TODO: add along lattice
        """
        Get the display components of the compass
        :param lattice: the pymatgen Lattice object that contains the primitive lattice vectors
        :param origin: the reference position to place the compass
        :param scale: scale all the geometric objects that makes up the compass the lattice vectors are normalized before the scaling so everything should be the same size
        :param offset: shift the compass from the origin by a ratio of the diagonal of the cell relative the size 
        :return: list of cystal_toolkit.helper.scene objects that makes up the compass
        """
        o = -np.array(origin)
        o = o - offset * (lattice.matrix[0] + lattice.matrix[1] + lattice.matrix[2])
        a = lattice.matrix[0] / np.linalg.norm(lattice.matrix[0]) * scale
        b = lattice.matrix[1] / np.linalg.norm(lattice.matrix[1]) * scale
        c = lattice.matrix[2] / np.linalg.norm(lattice.matrix[2]) * scale
        a_arrow = [[o, o + a]]
        b_arrow = [[o, o + b]]
        c_arrow = [[o, o + c]]

        o_sphere = Spheres(positions=[o], color="black", radius=0.1 * scale)

        return Scene(name='compass', contents=[
            Arrows(
                a_arrow,
                color="red",
                radius=0.7 * scale,
                headLength=2.3 * scale,
                headWidth=1.4 * scale,
                **kwargs,
            ),
            Arrows(
                b_arrow,
                color="blue",
                radius=0.7 * scale,
                headLength=2.3 * scale,
                headWidth=1.4 * scale,
                **kwargs,
            ),
            Arrows(
                c_arrow,
                color="green",
                radius=0.7 * scale,
                headLength=2.3 * scale,
                headWidth=1.4 * scale,
                **kwargs,
            ),
            o_sphere,
        ])
 def update_structure(orbital):
     # orbital is ["0125_up"]
     scene_additions = Scene(
         name=orbital[0],
         contents=[self.scene_dicts.scenes[orbital[0]]])
     # Arrows(positionPairs=[[[0,0,0], [s,s,s]]],
     #        radius=0.1,
     #        headLength=0.6,
     #        clickable=True,
     #        headWidth=0.3)])
     scene, _ = self.get_scene_and_legend(
         scene_additions=scene_additions)
     return scene
Beispiel #17
0
def get_volumetric_scene(self,
                         origin=(0, 0, 0),
                         data_key='total',
                         isolvl=2.0,
                         step_size=3,
                         **kwargs):
    o = -np.array(origin)
    vertices, faces, normals, values = measure.marching_cubes_lewiner(
        self.data[data_key], level=isolvl, step_size=step_size)
    vertices = vertices / self.data[
        data_key].shape  # transform to fractional coordinates
    vertices = np.dot(o + vertices,
                      self.structure.lattice.matrix)  # transform to cartesian
    return Scene("volumetric-data",
                 contents=[Surface(vertices, normals, **kwargs)])
    def __init__(self,
                 potential_plotter: SitePotentialPlotlyPlotter,
                 eigenvalue_plotter: EigenvaluePlotlyPlotter,
                 scene_dicts: SceneDicts,
                 name: str,
                 vacancy_sites: List[Dict[str, List[float]]] = None,
                 id: str = None,
                 scene_settings: Optional[dict] = None,
                 **kwargs):

        self.scene_dicts = scene_dicts
        self.potential_plotter = potential_plotter
        self.eigenvalue_plotter = eigenvalue_plotter
        super().__init__(id=id, **kwargs)
        self.options = [{
            "label": i,
            "value": i
        } for i in scene_dicts.scenes.keys()]
        self.create_store("scene_dicts", initial_data=scene_dicts)
        self.graph = scene_dicts.structure_graph
        self.vacancy_sites = vacancy_sites or []

        #        default_data=scene_dicts.structure_graph,
        self.initial_scene_settings = {
            "extractAxis": True,
            "renderer": "webgl",
            "defaultZoom": 3.0
        }
        if scene_settings:
            self.initial_scene_settings.update(scene_settings)

        # self.create_store("scene_settings",
        #                   initial_data=self.initial_scene_settings)

        initial_scene_additions = Scene(
            name="parchg", contents=[list(scene_dicts.scenes.values())[0]])
        self.create_store("scene_additions",
                          initial_data=initial_scene_additions)

        scene, legend = self.get_scene_and_legend(
            scene_additions=initial_scene_additions)
        self.create_store("legend_data", initial_data=legend)
        self.create_store("graph", initial_data=scene_dicts.structure_graph)

        self.name = name

        # this is used by a Simple3DScene component, not a dcc.Store
        self._initial_data["scene"] = scene
Beispiel #19
0
def get_lattice_scene(self, origin=None, show_axes=False, **kwargs):

    o = -np.array((0, 0, 0))
    a, b, c = self.matrix[0], self.matrix[1], self.matrix[2]
    line_pairs = [
        o,
        o + a,
        o,
        o + b,
        o,
        o + c,
        o + a,
        o + a + b,
        o + a,
        o + a + c,
        o + b,
        o + b + a,
        o + b,
        o + b + c,
        o + c,
        o + c + a,
        o + c,
        o + c + b,
        o + a + b,
        o + a + b + c,
        o + a + c,
        o + a + b + c,
        o + b + c,
        o + a + b + c,
    ]
    line_pairs = [line.tolist() for line in line_pairs]

    name = (
        f"a={self.a}, b={self.b}, c={self.c}, "
        f"alpha={self.alpha}, beta={self.beta}, gamma={self.gamma}"
    )

    contents = [Lines(line_pairs, **kwargs)]

    if show_axes:
        contents.append(self._axes_from_lattice(origin=origin))

    return Scene(name, contents, origin=origin)
Beispiel #20
0
def get_lattice_scene(self, origin=(0, 0, 0), **kwargs):

    o = -np.array(origin)
    a, b, c = self.matrix[0], self.matrix[1], self.matrix[2]
    line_pairs = [
        o,
        o + a,
        o,
        o + b,
        o,
        o + c,
        o + a,
        o + a + b,
        o + a,
        o + a + c,
        o + b,
        o + b + a,
        o + b,
        o + b + c,
        o + c,
        o + c + a,
        o + c,
        o + c + b,
        o + a + b,
        o + a + b + c,
        o + a + c,
        o + a + b + c,
        o + b + c,
        o + a + b + c,
    ]
    line_pairs = [line.tolist() for line in line_pairs]

    name = (f"a={self.a}, b={self.b}, c={self.c}, "
            f"alpha={self.alpha}, beta={self.beta}, gamma={self.gamma}")

    return Scene(name, contents=[Lines(line_pairs, **kwargs)])
def get_structure_graph_scene(
    self,
    origin=(0, 0, 0),
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=True,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
) -> Scene:

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            origin=origin,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
        )
        for scene in site_scene.contents:
            primitives[scene.name] += scene.contents

    # we are here ...
    # select polyhedra
    # split by atom type at center
    # see if any intersect, if yes split further
    # order sets, with each choice, go to add second set etc if don't intersect
    # they intersect if centre atom forms vertex of another atom (caveat: centre atom may not actually be inside polyhedra! not checking for this, add todo)
    # def _set_intersects() ->bool:
    # def _split_set() ->List: (by type, then..?)
    # def _order_sets()... pick 1, ask can add 2? etc

    primitives["unit_cell"].append(
        self.structure.lattice.get_scene(origin=origin))

    return Scene(
        name=self.structure.composition.reduced_formula,
        contents=[Scene(name=k, contents=v) for k, v in primitives.items()],
    )
Beispiel #22
0
    def __init__(
        self,
        struct_or_mol: Optional[Union[Structure, StructureGraph, Molecule,
                                      MoleculeGraph]] = None,
        id: str = None,
        scene_additions: Optional[Scene] = None,
        bonding_strategy: str = DEFAULTS["bonding_strategy"],
        bonding_strategy_kwargs: Optional[dict] = None,
        color_scheme: str = DEFAULTS["color_scheme"],
        color_scale: Optional[str] = None,
        radius_strategy: str = DEFAULTS["radius_strategy"],
        unit_cell_choice: str = DEFAULTS["unit_cell_choice"],
        draw_image_atoms: bool = DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell: bool = DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds: bool = DEFAULTS["hide_incomplete_bonds"],
        show_compass: bool = DEFAULTS["show_compass"],
        scene_settings: Optional[Dict] = None,
        **kwargs,
    ):

        super().__init__(id=id, default_data=struct_or_mol, **kwargs)

        self.initial_scene_settings = self.default_scene_settings.copy()
        if scene_settings:
            self.initial_scene_settings.update(scene_settings)

        self.create_store("scene_settings",
                          initial_data=self.initial_scene_settings)

        # unit cell choice and bonding algorithms need to come from a settings
        # object (in a dcc.Store) guaranteed to be present in layout, rather
        # than from the controls themselves -- since these are optional and
        # may not be present in the layout
        self.create_store(
            "graph_generation_options",
            initial_data={
                "bonding_strategy": bonding_strategy,
                "bonding_strategy_kwargs": bonding_strategy_kwargs,
                "unit_cell_choice": unit_cell_choice,
            },
        )

        self.create_store(
            "display_options",
            initial_data={
                "color_scheme": color_scheme,
                "color_scale": color_scale,
                "radius_strategy": radius_strategy,
                "draw_image_atoms": draw_image_atoms,
                "bonded_sites_outside_unit_cell":
                bonded_sites_outside_unit_cell,
                "hide_incomplete_bonds": hide_incomplete_bonds,
                "show_compass": show_compass,
            },
        )

        if scene_additions:
            initial_scene_additions = Scene(
                name="scene_additions", contents=scene_additions).to_json()
        else:
            initial_scene_additions = None
        self.create_store("scene_additions",
                          initial_data=initial_scene_additions)

        if struct_or_mol:
            # graph is cached explicitly, this isn't necessary but is an
            # optimization so that graph is only re-generated if bonding
            # algorithm changes
            graph = self._preprocess_input_to_graph(
                struct_or_mol,
                bonding_strategy=bonding_strategy,
                bonding_strategy_kwargs=bonding_strategy_kwargs,
            )
            scene, legend = self.get_scene_and_legend(
                graph,
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )
            if hasattr(struct_or_mol, "lattice"):
                self._lattice = struct_or_mol.lattice
        else:
            # component could be initialized without a structure, in which case
            # an empty scene should be displayed
            graph = None
            scene, legend = self.get_scene_and_legend(
                None,
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )

        self.create_store("legend_data", initial_data=legend)
        self.create_store("graph", initial_data=graph)

        # this is used by a Simple3DScene component, not a dcc.Store
        self._initial_data["scene"] = scene

        # hide axes inset for molecules
        if isinstance(struct_or_mol, Molecule) or isinstance(
                struct_or_mol, MoleculeGraph):
            self.scene_kwargs = {"axisView": "HIDDEN"}
        else:
            self.scene_kwargs = {}
Beispiel #23
0
def get_structure_graph_scene(
    self,
    origin=None,
    draw_image_atoms=True,
    bonded_sites_outside_unit_cell=True,
    hide_incomplete_edges=False,
    incomplete_edge_length_scale=0.3,
    color_edges_by_edge_weight=True,
    edge_weight_color_scale="coolwarm",
    explicitly_calculate_polyhedra_hull=False,
    legend: Optional[Legend] = None,
    group_by_symmetry: bool = True,
) -> Scene:

    origin = origin or list(
        -self.structure.lattice.get_cartesian_coords([0.5, 0.5, 0.5]))

    legend = legend or Legend(self.structure)

    primitives = defaultdict(list)

    sites_to_draw = self._get_sites_to_draw(
        draw_image_atoms=draw_image_atoms,
        bonded_sites_outside_unit_cell=bonded_sites_outside_unit_cell,
    )

    color_edges = False
    if color_edges_by_edge_weight:

        weights = [e[2].get("weight") for e in self.graph.edges(data=True)]
        weights = np.array([w for w in weights if w])

        if any(weights):

            cmap = get_cmap(edge_weight_color_scale)

            # try to keep color scheme symmetric around 0
            weight_max = max([abs(min(weights)), max(weights)])
            weight_min = -weight_max

            def get_weight_color(weight):
                if not weight:
                    weight = 0
                x = (weight - weight_min) / (weight_max - weight_min)
                return "#{:02x}{:02x}{:02x}".format(
                    *[int(c * 255) for c in cmap(x)[0:3]])

            color_edges = True

    idx_to_wyckoff = {}
    if group_by_symmetry:
        sga = SpacegroupAnalyzer(self.structure)
        struct_sym = sga.get_symmetrized_structure()
        for equiv_idxs, wyckoff in zip(struct_sym.equivalent_indices,
                                       struct_sym.wyckoff_symbols):
            for idx in equiv_idxs:
                idx_to_wyckoff[idx] = wyckoff

    for (idx, jimage) in sites_to_draw:

        site = self.structure[idx]
        if jimage != (0, 0, 0):
            connected_sites = self.get_connected_sites(idx, jimage=jimage)
            site = PeriodicSite(
                site.species,
                np.add(site.frac_coords, jimage),
                site.lattice,
                properties=site.properties,
            )
        else:
            connected_sites = self.get_connected_sites(idx)

        connected_sites = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) in sites_to_draw
        ]
        connected_sites_not_drawn = [
            cs for cs in connected_sites
            if (cs.index, cs.jimage) not in sites_to_draw
        ]

        if color_edges:

            connected_sites_colors = [
                get_weight_color(cs.weight) for cs in connected_sites
            ]
            connected_sites_not_drawn_colors = [
                get_weight_color(cs.weight) for cs in connected_sites_not_drawn
            ]

        else:

            connected_sites_colors = None
            connected_sites_not_drawn_colors = None

        site_scene = site.get_scene(
            connected_sites=connected_sites,
            connected_sites_not_drawn=connected_sites_not_drawn,
            hide_incomplete_edges=hide_incomplete_edges,
            incomplete_edge_length_scale=incomplete_edge_length_scale,
            connected_sites_colors=connected_sites_colors,
            connected_sites_not_drawn_colors=connected_sites_not_drawn_colors,
            explicitly_calculate_polyhedra_hull=
            explicitly_calculate_polyhedra_hull,
            legend=legend,
        )
        for scene in site_scene.contents:
            if group_by_symmetry and scene.name == "atoms" and idx in idx_to_wyckoff:
                # will rename to e.g. atoms_N_4e
                scene.name = f"atoms_{site_scene.name}_{idx_to_wyckoff[idx]}"
                # this is a proof-of-concept to demonstrate hover labels, could create label
                # automatically from site properties instead
                scene.contents[
                    0].tooltip = f"{site_scene.name} ({idx_to_wyckoff[idx]})"
            primitives[scene.name] += scene.contents

    primitives["unit_cell"].append(self.structure.lattice.get_scene())

    return Scene(
        name="StructureGraph",
        origin=origin,
        contents=[
            Scene(name=k, contents=v, origin=origin)
            for k, v in primitives.items()
        ],
    )
Beispiel #24
0
    def get_brillouin_zone_scene(bs: BandStructureSymmLine) -> Scene:

        if not bs:
            return Scene(name="brillouin_zone", contents=[])

        # TODO: from BSPlotter, merge back into BSPlotter
        # Brillouin zone
        bz_lattice = bs.structure.lattice.reciprocal_lattice
        bz = bz_lattice.get_wigner_seitz_cell()
        lines = []
        for iface in range(len(bz)):  # pylint: disable=C0200
            for line in itertools.combinations(bz[iface], 2):
                for jface in range(len(bz)):
                    if (iface < jface
                            and any(np.all(line[0] == x) for x in bz[jface])
                            and any(np.all(line[1] == x) for x in bz[jface])):
                        lines += [list(line[0]), list(line[1])]

        zone_lines = Lines(positions=lines)
        zone_surface = Convex(positions=lines, opacity=0.05, color="#000000")

        # - Strip latex math wrapping for labels
        # TODO: add to string utils in pymatgen
        str_replace = {
            "$": "",
            "\\mid": "|",
            "\\Gamma": "Γ",
            "\\Sigma": "Σ",
            "GAMMA": "Γ",
            "_1": "₁",
            "_2": "₂",
            "_3": "₃",
            "_4": "₄",
            "_{1}": "₁",
            "_{2}": "₂",
            "_{3}": "₃",
            "_{4}": "₄",
            "^{*}": "*",
        }

        labels = {}
        for k in bs.kpoints:
            if k.label:
                label = k.label
                for orig, new in str_replace.items():
                    label = label.replace(orig, new)
                labels[label] = bz_lattice.get_cartesian_coords(k.frac_coords)
        labels = [
            Spheres(positions=[coords],
                    tooltip=label,
                    radius=0.03,
                    color="#5EB1BF") for label, coords in labels.items()
        ]

        path = []
        cylinder_pairs = []
        for b in bs.branches:
            start = bz_lattice.get_cartesian_coords(
                bs.kpoints[b["start_index"]].frac_coords)
            end = bz_lattice.get_cartesian_coords(
                bs.kpoints[b["end_index"]].frac_coords)
            path += [start, end]
            cylinder_pairs += [[start, end]]
        # path_lines = Lines(positions=path, color="#ff4b5c",)
        path_lines = Cylinders(positionPairs=cylinder_pairs,
                               color="#5EB1BF",
                               radius=0.01)
        ibz_region = Convex(positions=path, opacity=0.2, color="#5EB1BF")

        contents = [zone_lines, zone_surface, path_lines, ibz_region, *labels]

        cbm = bs.get_cbm()["kpoint"]
        vbm = bs.get_vbm()["kpoint"]

        if cbm and vbm:

            if cbm.label:
                cbm_label = cbm.label
                for orig, new in str_replace.items():
                    cbm_label = cbm_label.replace(orig, new)
                cbm_label = f"CBM at {cbm_label}"
            else:
                cbm_label = "CBM"

            if cbm == vbm:
                cbm_label = f"VBM and {cbm_label}"

            cbm_coords = bz_lattice.get_cartesian_coords(cbm.frac_coords)
            cbm = Spheres(positions=[cbm_coords],
                          tooltip=cbm_label,
                          radius=0.05,
                          color="#7E259B")

            contents.append(cbm)

            if cbm != vbm:
                if vbm.label:
                    vbm_label = vbm.label
                    for orig, new in str_replace.items():
                        vbm_label = vbm_label.replace(orig, new)
                    vbm_label = f"VBM at {vbm_label}"
                else:
                    vbm_label = "VBM"

                vbm_coords = bz_lattice.get_cartesian_coords(vbm.frac_coords)
                vbm = Spheres(
                    positions=[vbm_coords],
                    tooltip=vbm_label,
                    radius=0.05,
                    color="#7E259B",
                )

                contents.append(vbm)

        return Scene(name="brillouin_zone", contents=contents)
Beispiel #25
0
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx] for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [Convex(positions=all_positions, color=site_color)]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
            Scene("magmoms", contents=magmoms),
        ],
        origin=origin,
    )


Site.get_scene = get_site_scene
Beispiel #26
0
    def plot_crystal_toolkit(
        self,
        spin: Optional[Spin] = None,
        colors: Optional[Union[str, dict, list]] = None,
        opacity: float = 1.0,
    ) -> "Scene":
        """
        Get a crystal toolkit Scene showing the Fermi surface. The Scene can be
        displayed in an interactive web app using Crystal Toolkit, can be shown
        interactively in Jupyter Lab using the crystal-toolkit lab extension, or
        can be converted to JSON to store for future use.

        Args:
            spin: Which spin channel to plot. By default plot both spin channels if
                available.
            colors: See the docstring for ``get_isosurfaces_and_colors()`` for the
                available options.
            opacity: Opacity of surface. Note that due to limitations of WebGL,
                overlapping semi-transparent surfaces might result in visual artefacts.
        """

        # The implementation here is very similar to the plotly implementation, except
        # the crystal toolkit scene is constructed using the scene primitives from
        # crystal toolkit (Spheres, Surface, Lines, etc.)

        scene_contents = []

        isosurfaces, colors = self.get_isosurfaces_and_colors(spin=spin,
                                                              colors=colors)

        if isinstance(colors, np.ndarray):
            colors = (colors * 255).astype(int)
            colors = ["rgb({},{},{})".format(*c) for c in colors]

        # create a mesh for each electron band which has an isosurfaces at the Fermi
        # energy mesh data is generated by a marching cubes algorithm when the
        # FermiSurface object is created.
        surfaces = []
        for c, (verts, faces) in zip(colors, isosurfaces):
            positions = verts[faces].reshape(-1, 3).tolist()
            surface = Surface(positions=positions, color=c, opacity=opacity)
            surfaces.append(surface)
        fermi_surface = Scene("fermi_surface", contents=surfaces)
        scene_contents.append(fermi_surface)

        # add the cell outline to the plot
        lines = Lines(positions=list(self.reciprocal_space.lines.flatten()))
        # alternatively,
        # cylinders have finite width and are lighted, but no strong reason to choose
        # one over the other
        # cylinders = Cylinders(positionPairs=self.reciprocal_space.lines.tolist(),
        #                       radius=0.01, color="rgb(0,0,0)")
        scene_contents.append(lines)

        spheres = []
        for position, label in zip(self._symmetry_pts[0],
                                   self._symmetry_pts[1]):
            sphere = Spheres(
                positions=[list(position)],
                tooltip=label,
                radius=0.05,
                color="rgb(0, 0, 0)",
            )
            spheres.append(sphere)
        label_scene = Scene("labels", contents=spheres)
        scene_contents.append(label_scene)

        return Scene("ifermi", contents=scene_contents)
Beispiel #27
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    # connected_site_metadata: None,
    # connected_sites_to_draw,
    connected_sites_not_drawn: List[ConnectedSite] = None,
    hide_incomplete_edges: bool = False,
    incomplete_edge_length_scale: Optional[float] = 1.0,
    connected_sites_colors: Optional[List[str]] = None,
    connected_sites_not_drawn_colors: Optional[List[str]] = None,
    origin: Optional[List[float]] = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
    bond_radius: float = 0.1,
    legend: Optional[Legend] = None,
) -> Scene:
    """

    Args:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        explicitly_calculate_polyhedra_hull:
        legend:

    Returns:

    """

    atoms = []
    bonds = []
    polyhedron = []

    legend = legend or Legend(self)

    # for disordered structures
    is_ordered = self.is_ordered
    phiStart, phiEnd = None, None
    occu_start = 0.0

    position = self.coords.tolist()

    for idx, (sp, occu) in enumerate(self.species.items()):

        if isinstance(sp, DummySpecie):

            cube = Cubes(positions=[position],
                         color=legend.get_color(sp, site=self),
                         width=0.4)
            atoms.append(cube)

        else:

            color = legend.get_color(sp, site=self)
            radius = legend.get_radius(sp, site=self)

            # TODO: make optional/default to None
            # in disordered structures, we fractionally color-code spheres,
            # drawing a sphere segment from phi_end to phi_start
            # (think a sphere pie chart)
            if not is_ordered:
                phi_frac_end = occu_start + occu
                phi_frac_start = occu_start
                occu_start = phi_frac_end
                phiStart = phi_frac_start * np.pi * 2
                phiEnd = phi_frac_end * np.pi * 2

            name = str(sp)
            if occu != 1.0:
                name += " ({}% occupancy)".format(occu)
            name += f" ({position[0]:.3f}, {position[1]:.3f}, {position[2]:.3f})"

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                clickable=True,
                tooltip=name,
            )
            atoms.append(sphere)

    if not is_ordered and not np.isclose(phiEnd, np.pi * 2):
        # if site occupancy doesn't sum to 100%, cap sphere
        sphere = Spheres(
            positions=[position],
            color="#ffffff",
            radius=self.properties["display_radius"][0],
            phiStart=phiEnd,
            phiEnd=np.pi * 2,
        )
        atoms.append(sphere)

    if connected_sites:

        # TODO: more graceful solution here
        # if ambiguous (disordered), re-use last color used
        site_color = color

        # TODO: can cause a bug if all vertices almost co-planar
        # necessary to include center site in case it's outside polyhedra
        all_positions = [self.coords]

        for idx, connected_site in enumerate(connected_sites):

            connected_position = connected_site.site.coords
            bond_midpoint = np.add(position, connected_position) / 2

            if connected_sites_colors:
                color = connected_sites_colors[idx]
            else:
                color = site_color

            cylinder = Cylinders(
                positionPairs=[[position, bond_midpoint.tolist()]],
                color=color,
                radius=bond_radius,
            )
            bonds.append(cylinder)
            all_positions.append(connected_position.tolist())

        if connected_sites_not_drawn and not hide_incomplete_edges:

            for idx, connected_site in enumerate(connected_sites_not_drawn):

                connected_position = connected_site.site.coords
                bond_midpoint = (incomplete_edge_length_scale *
                                 np.add(position, connected_position) / 2)

                if connected_sites_not_drawn_colors:
                    color = connected_sites_not_drawn_colors[idx]
                else:
                    color = site_color

                cylinder = Cylinders(
                    positionPairs=[[position, bond_midpoint.tolist()]],
                    color=color,
                    radius=bond_radius,
                )
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())

        # ensure intersecting polyhedra are not shown, defaults to choose by electronegativity
        not_most_electro_negative = map(
            lambda x:
            (x.site.specie < self.specie) or (x.site.specie == self.specie),
            connected_sites,
        )

        all_positions = [list(p) for p in all_positions]

        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn
                and not any(not_most_electro_negative)):
            if explicitly_calculate_polyhedra_hull:

                try:

                    # all_positions = [[0, 0, 0], [0, 0, 10], [0, 10, 0], [10, 0, 0]]
                    # gives...
                    # .convex_hull = [[2, 3, 0], [1, 3, 0], [1, 2, 0], [1, 2, 3]]
                    # .vertex_neighbor_vertices = [1, 2, 3, 2, 3, 0, 1, 3, 0, 1, 2, 0]

                    vertices_indices = Delaunay(all_positions).convex_hull
                except Exception as e:
                    vertices_indices = []

                vertices = [
                    all_positions[idx]
                    for idx in chain.from_iterable(vertices_indices)
                ]

                polyhedron = [Surface(positions=vertices, color=site_color)]

            else:

                polyhedron = [
                    Convex(positions=all_positions, color=site_color)
                ]

    return Scene(
        self.species_string,
        [
            Scene("atoms", contents=atoms),
            Scene("bonds", contents=bonds),
            Scene("polyhedra", contents=polyhedron),
        ],
        origin=origin,
    )
Beispiel #28
0
    def __init__(
        self,
        struct_or_mol: Optional[Union[Structure, StructureGraph, Molecule,
                                      MoleculeGraph]] = None,
        id: str = None,
        className: str = "box",
        scene_additions: Optional[Scene] = None,
        bonding_strategy: str = DEFAULTS["bonding_strategy"],
        bonding_strategy_kwargs: Optional[dict] = None,
        color_scheme: str = DEFAULTS["color_scheme"],
        color_scale: Optional[str] = None,
        radius_strategy: str = DEFAULTS["radius_strategy"],
        unit_cell_choice: str = DEFAULTS["unit_cell_choice"],
        draw_image_atoms: bool = DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell: bool = DEFAULTS[
            "bonded_sites_outside_unit_cell"],
        hide_incomplete_bonds: bool = DEFAULTS["hide_incomplete_bonds"],
        show_compass: bool = DEFAULTS["show_compass"],
        scene_settings: Optional[Dict] = None,
        group_by_site_property: Optional[str] = None,
        show_legend: bool = DEFAULTS["show_legend"],
        show_settings: bool = DEFAULTS["show_settings"],
        show_controls: bool = DEFAULTS["show_controls"],
        show_expand_button: bool = DEFAULTS["show_expand_button"],
        show_image_button: bool = DEFAULTS["show_image_button"],
        show_export_button: bool = DEFAULTS["show_export_button"],
        show_position_button: bool = DEFAULTS["show_position_button"],
        **kwargs,
    ):
        """
        Create a StructureMoleculeComponent from a structure or molecule.

        :param struct_or_mol: input structure or molecule
        :param id: canonical id
        :param scene_additions: extra geometric elements to add to the 3D scene
        :param bonding_strategy: bonding strategy from pymatgen NearNeighbors class
        :param bonding_strategy_kwargs: options for the bonding strategy
        :param color_scheme: color scheme, see Legend class
        :param color_scale: color scale, see Legend class
        :param radius_strategy: radius strategy, see Legend class
        :param draw_image_atoms: whether to draw repeats of atoms on periodic images
        :param bonded_sites_outside_unit_cell: whether to draw sites bonded outside the unit cell
        :param hide_incomplete_bonds: whether to hide or show incomplete bonds
        :param show_compass: whether to hide or show the compass
        :param scene_settings: scene settings (lighting etc.) to pass to CrystalToolkitScene
        :param group_by_site_property: a site property used for grouping of atoms for mouseover/interaction,
        :param show_legend: show or hide legend panel within the scene
        :param show_controls: show or hide scene control bar
        :param show_expand_button: show or hide the full screen button within the scene control bar
        :param show_image_button: show or hide the image download button within the scene control bar
        :param show_export_button: show or hide the file export button within the scene control bar
        :param show_position_button: show or hide the revert position button within the scene control bar
        e.g. Wyckoff label
        :param kwargs: extra keyword arguments to pass to MPComponent
        """

        super().__init__(id=id, default_data=struct_or_mol, **kwargs)
        self.className = className
        self.show_legend = show_legend
        self.show_settings = show_settings
        self.show_controls = show_controls
        self.show_expand_button = show_expand_button
        self.show_image_button = show_image_button
        self.show_export_button = show_export_button
        self.show_position_button = show_position_button

        self.initial_scene_settings = self.default_scene_settings.copy()
        if scene_settings:
            self.initial_scene_settings.update(scene_settings)

        self.create_store("scene_settings",
                          initial_data=self.initial_scene_settings)

        # unit cell choice and bonding algorithms need to come from a settings
        # object (in a dcc.Store) guaranteed to be present in layout, rather
        # than from the controls themselves -- since these are optional and
        # may not be present in the layout
        self.create_store(
            "graph_generation_options",
            initial_data={
                "bonding_strategy": bonding_strategy,
                "bonding_strategy_kwargs": bonding_strategy_kwargs,
                "unit_cell_choice": unit_cell_choice,
            },
        )

        self.create_store(
            "display_options",
            initial_data={
                "color_scheme": color_scheme,
                "color_scale": color_scale,
                "radius_strategy": radius_strategy,
                "draw_image_atoms": draw_image_atoms,
                "bonded_sites_outside_unit_cell":
                bonded_sites_outside_unit_cell,
                "hide_incomplete_bonds": hide_incomplete_bonds,
                "show_compass": show_compass,
                "group_by_site_property": group_by_site_property,
            },
        )

        if scene_additions:
            initial_scene_additions = Scene(
                name="scene_additions", contents=scene_additions).to_json()
        else:
            initial_scene_additions = None
        self.create_store("scene_additions",
                          initial_data=initial_scene_additions)

        if struct_or_mol:
            # graph is cached explicitly, this isn't necessary but is an
            # optimization so that graph is only re-generated if bonding
            # algorithm changes
            struct_or_mol = self._preprocess_structure(
                struct_or_mol, unit_cell_choice=unit_cell_choice)
            graph = self._preprocess_input_to_graph(
                struct_or_mol,
                bonding_strategy=bonding_strategy,
                bonding_strategy_kwargs=bonding_strategy_kwargs,
            )
            scene, legend = self.get_scene_and_legend(
                graph,
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )
            if hasattr(struct_or_mol, "lattice"):
                self._lattice = struct_or_mol.lattice
        else:
            # component could be initialized without a structure, in which case
            # an empty scene should be displayed
            graph = None
            scene, legend = self.get_scene_and_legend(
                None,
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )

        self.create_store("legend_data", initial_data=legend)
        self.create_store("graph", initial_data=graph)

        # this is used by a CrystalToolkitScene component, not a dcc.Store
        self._initial_data["scene"] = scene

        # hide axes inset for molecules
        if isinstance(struct_or_mol, Molecule) or isinstance(
                struct_or_mol, MoleculeGraph):
            self.scene_kwargs = {"axisView": "HIDDEN"}
        else:
            self.scene_kwargs = {}
Beispiel #29
0
def _axes_from_lattice(self, origin=None, scale=1, offset=0, **kwargs):
    """
    Get the display components of the compass
    :param lattice: the pymatgen Lattice object that contains the primitive
    lattice vectors
    :param origin: the reference position to place the compass
    :param scale: scale all the geometric objects that makes up the compass
    the lattice vectors are normalized before the scaling so everything should
    be the same size
    :param offset: shift the compass from the origin by a ratio of the diagonal
    of the cell relative the size
    :param **kwargs: keyword args to pass to the Arrows initializer
    :return: Scene object
    """

    origin = origin or [0, 0, 0]

    o = np.array(origin)
    # o = -self.get_cartesian_coords([0.5, 0.5, 0.5])
    # o = o - offset * (self.matrix[0] + self.matrix[1] + self.matrix[2])
    a = self.matrix[0] / np.linalg.norm(self.matrix[0]) * scale
    b = self.matrix[1] / np.linalg.norm(self.matrix[1]) * scale
    c = self.matrix[2] / np.linalg.norm(self.matrix[2]) * scale
    a_arrow = [[o, o + a]]
    b_arrow = [[o, o + b]]
    c_arrow = [[o, o + c]]

    radius_scale = 0.07
    head_scale = 0.24
    head_width = 0.14

    o_sphere = Spheres(positions=[o],
                       color="white",
                       radius=2 * radius_scale * scale)

    return Scene(
        name="axes",
        contents=[
            Arrows(
                a_arrow,
                color="red",
                radius=radius_scale * scale,
                headLength=head_scale * scale,
                headWidth=head_width * scale,
                **kwargs,
            ),
            Arrows(
                b_arrow,
                color="green",
                radius=radius_scale * scale,
                headLength=head_scale * scale,
                headWidth=head_width * scale,
                **kwargs,
            ),
            Arrows(
                c_arrow,
                color="blue",
                radius=radius_scale * scale,
                headLength=head_scale * scale,
                headWidth=head_width * scale,
                **kwargs,
            ),
            o_sphere,
        ],
        origin=origin,
    )
Beispiel #30
0
    def __init__(
        self,
        struct_or_mol: Optional[
            Union[Structure, StructureGraph, Molecule, MoleculeGraph]
        ] = None,
        id: str = None,
        scene_additions: Optional[Scene] = None,
        bonding_strategy: str = DEFAULTS["bonding_strategy"],
        bonding_strategy_kwargs: Optional[dict] = None,
        color_scheme: str = DEFAULTS["color_scheme"],
        color_scale: Optional[str] = None,
        radius_strategy: str = DEFAULTS["radius_strategy"],
        unit_cell_choice: str = DEFAULTS["unit_cell_choice"],
        draw_image_atoms: bool = DEFAULTS["draw_image_atoms"],
        bonded_sites_outside_unit_cell: bool = DEFAULTS[
            "bonded_sites_outside_unit_cell"
        ],
        hide_incomplete_bonds: bool = DEFAULTS["hide_incomplete_bonds"],
        show_compass: bool = DEFAULTS["show_compass"],
        scene_settings: Optional[Dict] = None,
        **kwargs,
    ):
        """
        Create a StructureMoleculeComponent from a structure or molecule.

        :param struct_or_mol: input structure or molecule
        :param id: canonical id
        :param scene_additions: extra geometric elements to add to the 3D scene
        :param bonding_strategy: bonding strategy from pymatgen NearNeighbors class
        :param bonding_strategy_kwargs: options for the bonding strategy
        :param color_scheme: color scheme, see Legend class
        :param color_scale: color scale, see Legend class
        :param radius_strategy: radius strategy, see Legend class
        :param draw_image_atoms: whether to draw repeats of atoms on periodic images
        :param bonded_sites_outside_unit_cell: whether to draw sites bonded outside the unit cell
        :param hide_incomplete_bonds: whether to hide or show incomplete bonds
        :param show_compass: whether to hide or show the compass
        :param scene_settings: scene settings (lighting etc.) to pass to Simple3DScene
        :param kwargs: extra keyword arguments to pass to MPComponent
        """

        super().__init__(id=id, default_data=struct_or_mol, **kwargs)

        # what to show for the title_layout if structure/molecule not loaded
        self.default_title = "Crystal Toolkit"

        self.initial_scene_settings = self.default_scene_settings.copy()
        if scene_settings:
            self.initial_scene_settings.update(scene_settings)

        self.create_store("scene_settings", initial_data=self.initial_scene_settings)

        # unit cell choice and bonding algorithms need to come from a settings
        # object (in a dcc.Store) guaranteed to be present in layout, rather
        # than from the controls themselves -- since these are optional and
        # may not be present in the layout
        self.create_store(
            "graph_generation_options",
            initial_data={
                "bonding_strategy": bonding_strategy,
                "bonding_strategy_kwargs": bonding_strategy_kwargs,
                "unit_cell_choice": unit_cell_choice,
            },
        )

        self.create_store(
            "display_options",
            initial_data={
                "color_scheme": color_scheme,
                "color_scale": color_scale,
                "radius_strategy": radius_strategy,
                "draw_image_atoms": draw_image_atoms,
                "bonded_sites_outside_unit_cell": bonded_sites_outside_unit_cell,
                "hide_incomplete_bonds": hide_incomplete_bonds,
                "show_compass": show_compass,
            },
        )

        if scene_additions:
            initial_scene_additions = Scene(
                name="scene_additions", contents=scene_additions
            ).to_json()
        else:
            initial_scene_additions = None
        self.create_store("scene_additions", initial_data=initial_scene_additions)

        if struct_or_mol:
            # graph is cached explicitly, this isn't necessary but is an
            # optimization so that graph is only re-generated if bonding
            # algorithm changes
            graph = self._preprocess_input_to_graph(
                struct_or_mol,
                bonding_strategy=bonding_strategy,
                bonding_strategy_kwargs=bonding_strategy_kwargs,
            )
            scene, legend = self.get_scene_and_legend(
                graph,
                name=self.id(),
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )
            if hasattr(struct_or_mol, "lattice"):
                self._lattice = struct_or_mol.lattice
        else:
            # component could be initialized without a structure, in which case
            # an empty scene should be displayed
            graph = None
            scene, legend = self.get_scene_and_legend(
                None,
                name=self.id(),
                scene_additions=self.initial_data["scene_additions"],
                **self.initial_data["display_options"],
            )

        self.create_store("legend_data", initial_data=legend)
        self.create_store("graph", initial_data=graph)

        # this is used by a Simple3DScene component, not a dcc.Store
        self._initial_data["scene"] = scene