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 [
            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,
        ]
Example #2
0
def get_extra_scene(pairs, s_radius=0.5, c_radius=2.5):
    '''
    Takes in position pairs and draw a path
    '''
    extra_scene=[]

    [ini_color, final_color]=[[240, 240, 240], [0, 0, 0]]
    div = len(pairs) - 1
    if div == 0:
        rgb_list =  [tuple(ini_color), tuple(final_color)]
    else:
        step_size = [int((final_color[i] - ini_color[i])/(div+1)) for i in range(0,3)]
        rgb_list = [(ini_color[0] + u*step_size[0], ini_color[1] + u*step_size[1], ini_color[2] + u*step_size[2]) for u in range(1, div+1)]
        rgb_list.insert(0, tuple(ini_color))
        rgb_list.append(tuple(final_color))
    rgb_to_html = lambda rgb: '#%02x%02x%02x' % rgb
    html_colors = [rgb_to_html(i) for i in rgb_list]

    extra_scene.append(Spheres(positions=[pairs[0][0]], radius=s_radius, color='#00ff88')) #for now all colors are designated as Li light green
    for i in range(0, len(pairs)):
        extra_scene.append(Spheres(positions=[pairs[i][1]], radius=s_radius, color='#00ff88'))
        extra_scene.append(Cylinders(positionPairs=[pairs[i]], radius=c_radius, color='#00ff88'))
    return extra_scene
    def test_convert_object_to_pythreejs(self):
        # take different crystal toolkit objects and convert them into pythreejs objects
        sphere = Spheres(positions=[[0, 0, 0]], color="#00ab24", radius=1.0)
        assert ("SphereBufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=sphere)[0].__repr__())

        cylinder = Cylinders(positionPairs=[[[0, 0, 0], [0, 1, 1]]],
                             color="#00ab24",
                             radius=1.0)
        assert ("CylinderBufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=cylinder)[0].__repr__())

        surface = Surface([[0, 0, 0], [1, 0, 0], [0, 1, 0]])
        _convert_object_to_pythreejs(scene_obj=surface)[0].__repr__()
        assert ("BufferGeometry" in _convert_object_to_pythreejs(
            scene_obj=surface)[0].__repr__())
Example #4
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,
    )
Example #5
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)
Example #6
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,
    draw_magmoms: bool = True,
    magmom_scale: float = 1.0,
    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 = []
    magmoms = []

    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()

    radii = [legend.get_radius(sp, site=self) for sp in self.species.keys()]
    max_radius = float(min(radii))

    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})"

            if self.properties:
                for k, v in self.properties.items():
                    name += f" ({k} = {v})"

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

        # Add magmoms
        if draw_magmoms:
            if magmom := self.properties.get("magmom"):
                # enforce type
                magmom = np.array(Magmom(magmom).get_moment())
                magmom = 2 * magmom_scale * max_radius * magmom
                tail = np.array(position) - 0.5 * np.array(magmom)
                head = np.array(position) + 0.5 * np.array(magmom)

                arrow = Arrows(
                    positionPairs=[[tail, head]],
                    color="red",
                    radius=0.20,
                    headLength=0.5,
                    headWidth=0.4,
                    clickable=True,
                )
                magmoms.append(arrow)
Example #7
0
                arrow = Arrows(
                    positionPairs=[[tail, head]],
                    color="red",
                    radius=0.20,
                    headLength=0.5,
                    headWidth=0.4,
                    clickable=True,
                )
                magmoms.append(arrow)

    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=max_radius,
            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]
Example #8
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,
    )
Example #9
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)
Example #10
0
def get_site_scene(
    self,
    connected_sites: List[ConnectedSite] = None,
    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: List[float] = (0, 0, 0),
    ellipsoid_site_prop: str = None,
    draw_polyhedra: bool = True,
    explicitly_calculate_polyhedra_hull: bool = False,
) -> Scene:
    """

    Args:
        self:
        connected_sites:
        connected_sites_not_drawn:
        hide_incomplete_edges:
        incomplete_edge_length_scale:
        connected_sites_colors:
        connected_sites_not_drawn_colors:
        origin:
        ellipsoid_site_prop:
        explicitly_calculate_polyhedra_hull:

    Returns:

    """

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

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

    # for thermal ellipsoids etc.
    def _get_ellipsoids_from_matrix(matrix):
        raise NotImplementedError
        # matrix = np.array(matrix)
        # eigenvalues, eigenvectors = np.linalg.eig(matrix)

    if ellipsoid_site_prop:
        matrix = self.properties[ellipsoid_site_prop]
        ellipsoids = _get_ellipsoids_from_matrix(matrix)
    else:
        ellipsoids = None

    position = np.subtract(self.coords, origin).tolist()

    # site_color is used for bonds and polyhedra, if multiple colors are
    # defined for site (e.g. a disordered site), then we use grey
    all_colors = set(self.properties["display_color"])
    if len(all_colors) > 1:
        site_color = "#555555"
    else:
        site_color = list(all_colors)[0]

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

        if isinstance(sp, DummySpecie):

            cube = Cubes(
                positions=[position],
                color=self.properties["display_color"][idx],
                width=0.4,
            )
            atoms.append(cube)

        else:

            color = self.properties["display_color"][idx]
            radius = self.properties["display_radius"][idx]

            # 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

            # TODO: add names for labels
            # name = "{}".format(sp)
            # if occu != 1.0:
            #    name += " ({}% occupancy)".format(occu)

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
            )
            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:

        all_positions = []
        for idx, connected_site in enumerate(connected_sites):

            connected_position = np.subtract(connected_site.site.coords,
                                             origin)
            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)
            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 = np.subtract(connected_site.site.coords,
                                                 origin)
                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)
                bonds.append(cylinder)
                all_positions.append(connected_position.tolist())
        if (draw_polyhedra and len(connected_sites) > 3
                and not connected_sites_not_drawn):
            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=self.properties["display_color"][0],
                    )
                ]

            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),
        ],
    )
Example #11
0
def get_site_scene(
    self,
    connected_sites=None,
    connected_site_color=None,
    origin=(0, 0, 0),
    ellipsoid_site_prop=None,
    all_connected_sites_present=True,
    explicitly_calculate_polyhedra_hull=False,
) -> Scene:
    """
        Sites must have display_radius and display_color, display_vector,
        display_ellipsoid site properties.

        TODO: add bond colours to connected_site_properties
        :param site:
        :param connected_sites:
        :param origin:
        :param all_connected_sites_present: if False, will not calculate
        polyhedra since this would be misleading
        :param explicitly_calculate_polyhedra_hull:
        :return:
        """

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

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

    # for thermal ellipsoids etc.
    def _get_ellipsoids_from_matrix(matrix):
        raise NotImplementedError
        # matrix = np.array(matrix)
        # eigenvalues, eigenvectors = np.linalg.eig(matrix)

    if ellipsoid_site_prop:
        matrix = self.properties[ellipsoid_site_prop]
        ellipsoids = _get_ellipsoids_from_matrix(matrix)
    else:
        ellipsoids = None

    position = np.subtract(self.coords, origin).tolist()

    # site_color is used for bonds and polyhedra, if multiple colors are
    # defined for site (e.g. a disordered site), then we use grey
    all_colors = set(self.properties["display_color"])
    if len(all_colors) > 1:
        site_color = "#555555"
    else:
        site_color = list(all_colors)[0]

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

        if isinstance(sp, DummySpecie):

            cube = Cubes(
                positions=[position],
                color=self.properties["display_color"][idx],
                width=0.4,
            )
            atoms.append(cube)

        else:

            color = self.properties["display_color"][idx]
            radius = self.properties["display_radius"][idx]

            # 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

            # TODO: add names for labels
            # name = "{}".format(sp)
            # if occu != 1.0:
            #    name += " ({}% occupancy)".format(occu)

            sphere = Spheres(
                positions=[position],
                color=color,
                radius=radius,
                phiStart=phiStart,
                phiEnd=phiEnd,
                ellipsoids=ellipsoids,
            )
            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,
            ellipsoids=ellipsoids,
        )
        atoms.append(sphere)

    if connected_sites:

        all_positions = []
        for connected_site in connected_sites:

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

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

        if len(connected_sites) > 3 and all_connected_sites_present:
            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).vertex_neighbor_vertices
                    vertices = [all_positions[idx] for idx in vertices_indices]

                    polyhedron = [
                        Surface(
                            positions=vertices,
                            color=self.properties["display_color"][0],
                        )
                    ]

                except Exception as e:

                    polyhedron = []

            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),
        ],
    )