• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

materialsproject / pymatgen / 4075885785

pending completion
4075885785

push

github

Shyue Ping Ong
Merge branch 'master' of github.com:materialsproject/pymatgen

96 of 96 new or added lines in 27 files covered. (100.0%)

81013 of 102710 relevant lines covered (78.88%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

9.31
/pymatgen/vis/structure_vtk.py
1
# Copyright (c) Pymatgen Development Team.
2
# Distributed under the terms of the MIT License.
3

4
"""
1✔
5
This module contains classes to wrap Python VTK to make nice molecular plots.
6
"""
7

8
from __future__ import annotations
1✔
9

10
import itertools
1✔
11
import math
1✔
12
import os
1✔
13
import subprocess
1✔
14
import time
1✔
15
from typing import Sequence
1✔
16

17
import numpy as np
1✔
18

19
try:
1✔
20
    import vtk
1✔
21
    from vtk import vtkInteractorStyleTrackballCamera
×
22
except ImportError:
1✔
23
    # VTK not present. The Camera is to set object to avoid errors in unittest.
24
    vtk = None
1✔
25
    vtkInteractorStyleTrackballCamera = object
1✔
26

27
from monty.dev import requires
1✔
28
from monty.serialization import loadfn
1✔
29

30
from pymatgen.core.periodic_table import Species
1✔
31
from pymatgen.core.sites import PeriodicSite
1✔
32
from pymatgen.core.structure import Structure
1✔
33
from pymatgen.util.coord import in_coord_list
1✔
34

35
module_dir = os.path.dirname(os.path.abspath(__file__))
1✔
36
EL_COLORS = loadfn(os.path.join(module_dir, "ElementColorSchemes.yaml"))
1✔
37

38

39
class StructureVis:
1✔
40
    """
41
    Provides Structure object visualization using VTK.
42
    """
43

44
    @requires(vtk, "Visualization requires the installation of VTK with Python bindings.")
1✔
45
    def __init__(
1✔
46
        self,
47
        element_color_mapping=None,
48
        show_unit_cell=True,
49
        show_bonds=False,
50
        show_polyhedron=True,
51
        poly_radii_tol_factor=0.5,
52
        excluded_bonding_elements=None,
53
    ):
54
        """
55
        Constructs a Structure Visualization.
56

57
        Args:
58
            element_color_mapping: Optional color mapping for the elements,
59
                as a dict of {symbol: rgb tuple}. For example, {"Fe": (255,
60
                123,0), ....} If None is specified, a default based on
61
                Jmol's color scheme is used.
62
            show_unit_cell: Set to False to not show the unit cell
63
                boundaries. Defaults to True.
64
            show_bonds: Set to True to show bonds. Defaults to True.
65
            show_polyhedron: Set to True to show polyhedrons. Defaults to
66
                False.
67
            poly_radii_tol_factor: The polyhedron and bonding code uses the
68
                ionic radii of the elements or species to determine if two
69
                atoms are bonded. This specifies a tolerance scaling factor
70
                such that atoms which are (1 + poly_radii_tol_factor) * sum
71
                of ionic radii apart are still considered as bonded.
72
            excluded_bonding_elements: List of atom types to exclude from
73
                bonding determination. Defaults to an empty list. Useful
74
                when trying to visualize a certain atom type in the
75
                framework (e.g., Li in a Li-ion battery cathode material).
76

77
        Useful keyboard shortcuts implemented.
78
            h : Show help
79
            A/a : Increase/decrease cell by one unit vector in a-direction
80
            B/b : Increase/decrease cell by one unit vector in b-direction
81
            C/c : Increase/decrease cell by one unit vector in c-direction
82
            # : Toggle showing of polyhedrons
83
            - : Toggle showing of bonds
84
            [ : Decrease poly_radii_tol_factor by 0.05
85
            ] : Increase poly_radii_tol_factor by 0.05
86
            r : Reset camera direction
87
            o : Orthogonalize structure
88
            Up/Down : Rotate view along Up direction by 90 clock/anticlockwise
89
            Left/right : Rotate view along camera direction by 90
90
            clock/anticlockwise
91
        """
92
        # create a rendering window and renderer
93
        self.ren = vtk.vtkRenderer()
×
94
        self.ren_win = vtk.vtkRenderWindow()
×
95
        self.ren_win.AddRenderer(self.ren)
×
96
        self.ren.SetBackground(1, 1, 1)
×
97
        self.title = "Structure Visualizer"
×
98
        self.iren = vtk.vtkRenderWindowInteractor()
×
99
        self.iren.SetRenderWindow(self.ren_win)
×
100
        self.mapper_map = {}
×
101
        self.structure = None
×
102

103
        if element_color_mapping:
×
104
            self.el_color_mapping = element_color_mapping
×
105
        else:
106
            self.el_color_mapping = EL_COLORS["VESTA"]
×
107
        self.show_unit_cell = show_unit_cell
×
108
        self.show_bonds = show_bonds
×
109
        self.show_polyhedron = show_polyhedron
×
110
        self.poly_radii_tol_factor = poly_radii_tol_factor
×
111
        self.excluded_bonding_elements = excluded_bonding_elements or []
×
112
        self.show_help = True
×
113
        self.supercell = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
×
114
        self.redraw()
×
115

116
        style = StructureInteractorStyle(self)
×
117
        self.iren.SetInteractorStyle(style)
×
118
        self.ren.parent = self
×
119

120
    def rotate_view(self, axis_ind=0, angle=0):
1✔
121
        """
122
        Rotate the camera view.
123

124
        Args:
125
            axis_ind: Index of axis to rotate. Defaults to 0, i.e., a-axis.
126
            angle: Angle to rotate by. Defaults to 0.
127
        """
128
        camera = self.ren.GetActiveCamera()
×
129
        if axis_ind == 0:
×
130
            camera.Roll(angle)
×
131
        elif axis_ind == 1:
×
132
            camera.Azimuth(angle)
×
133
        else:
134
            camera.Pitch(angle)
×
135
        self.ren_win.Render()
×
136

137
    def write_image(self, filename="image.png", magnification=1, image_format="png"):
1✔
138
        """
139
        Save render window to an image.
140

141
        Arguments:
142
            filename: file to save to. Defaults to image.png.
143
            magnification: Use it to render high res images.
144
            image_format: choose between jpeg, png. Defaults to 'png'.
145
        """
146
        render_large = vtk.vtkRenderLargeImage()
×
147
        render_large.SetInput(self.ren)
×
148
        if image_format == "jpeg":
×
149
            writer = vtk.vtkJPEGWriter()
×
150
            writer.SetQuality(80)
×
151
        else:
152
            writer = vtk.vtkPNGWriter()
×
153

154
        render_large.SetMagnification(magnification)
×
155
        writer.SetFileName(filename)
×
156

157
        writer.SetInputConnection(render_large.GetOutputPort())
×
158
        self.ren_win.Render()
×
159
        writer.Write()
×
160
        del render_large
×
161

162
    def redraw(self, reset_camera=False):
1✔
163
        """
164
        Redraw the render window.
165

166
        Args:
167
            reset_camera: Set to True to reset the camera to a
168
                pre-determined default for each structure. Defaults to False.
169
        """
170
        self.ren.RemoveAllViewProps()
×
171
        self.picker = None
×
172
        self.add_picker_fixed()
×
173
        self.helptxt_mapper = vtk.vtkTextMapper()
×
174
        tprops = self.helptxt_mapper.GetTextProperty()
×
175
        tprops.SetFontSize(14)
×
176
        tprops.SetFontFamilyToTimes()
×
177
        tprops.SetColor(0, 0, 0)
×
178

179
        if self.structure is not None:
×
180
            self.set_structure(self.structure, reset_camera)
×
181

182
        self.ren_win.Render()
×
183

184
    def orthongonalize_structure(self):
1✔
185
        """
186
        Orthogonalize the structure.
187
        """
188
        if self.structure is not None:
×
189
            self.set_structure(self.structure.copy(sanitize=True))
×
190
        self.ren_win.Render()
×
191

192
    def display_help(self):
1✔
193
        """
194
        Display the help for various keyboard shortcuts.
195
        """
196
        help_text = [
×
197
            "h : Toggle help",
198
            "A/a, B/b or C/c : Increase/decrease cell by one a, b or c unit vector",
199
            "# : Toggle showing of polyhedrons",
200
            "-: Toggle showing of bonds",
201
            "r : Reset camera direction",
202
            "[/]: Decrease or increase poly_radii_tol_factor by 0.05. Value = " + str(self.poly_radii_tol_factor),
203
            "Up/Down: Rotate view along Up direction by 90 clockwise/anticlockwise",
204
            "Left/right: Rotate view along camera direction by 90 clockwise/anticlockwise",
205
            "s: Save view to image.png",
206
            "o: Orthogonalize structure",
207
        ]
208
        self.helptxt_mapper.SetInput("\n".join(help_text))
×
209
        self.helptxt_actor.SetPosition(10, 10)
×
210
        self.helptxt_actor.VisibilityOn()
×
211

212
    def set_structure(self, structure: Structure, reset_camera=True, to_unit_cell=True):
1✔
213
        """
214
        Add a structure to the visualizer.
215

216
        Args:
217
            structure: structure to visualize
218
            reset_camera: Set to True to reset the camera to a default
219
                determined based on the structure.
220
            to_unit_cell: Whether or not to fall back sites into the unit cell.
221
        """
222
        self.ren.RemoveAllViewProps()
×
223

224
        has_lattice = hasattr(structure, "lattice")
×
225

226
        if has_lattice:
×
227
            s = Structure.from_sites(structure, to_unit_cell=to_unit_cell)
×
228
            s.make_supercell(self.supercell, to_unit_cell=to_unit_cell)
×
229
        else:
230
            s = structure
×
231

232
        inc_coords = []
×
233
        for site in s:
×
234
            self.add_site(site)
×
235
            inc_coords.append(site.coords)
×
236

237
        count = 0
×
238
        labels = ["a", "b", "c"]
×
239
        colors = [(1, 0, 0), (0, 1, 0), (0, 0, 1)]
×
240

241
        if has_lattice:
×
242
            matrix = s.lattice.matrix
×
243

244
        if self.show_unit_cell and has_lattice:
×
245
            # matrix = s.lattice.matrix
246
            self.add_text([0, 0, 0], "o")
×
247
            for vec in matrix:
×
248
                self.add_line((0, 0, 0), vec, colors[count])
×
249
                self.add_text(vec, labels[count], colors[count])
×
250
                count += 1
×
251
            for vec1, vec2 in itertools.permutations(matrix, 2):
×
252
                self.add_line(vec1, vec1 + vec2)
×
253
            for vec1, vec2, vec3 in itertools.permutations(matrix, 3):
×
254
                self.add_line(vec1 + vec2, vec1 + vec2 + vec3)
×
255

256
        if self.show_bonds or self.show_polyhedron:
×
257
            elements = sorted(s.composition.elements, key=lambda a: a.X)
×
258
            anion = elements[-1]
×
259

260
            def contains_anion(site):
×
261
                for sp in site.species:
×
262
                    if sp.symbol == anion.symbol:
×
263
                        return True
×
264
                return False
×
265

266
            anion_radius = anion.average_ionic_radius
×
267
            for site in s:
×
268
                exclude = False
×
269
                max_radius = 0
×
270
                color = np.array([0, 0, 0])
×
271
                for sp, occu in site.species.items():
×
272
                    if sp.symbol in self.excluded_bonding_elements or sp == anion:
×
273
                        exclude = True
×
274
                        break
×
275
                    max_radius = max(max_radius, sp.average_ionic_radius)
×
276
                    color = color + occu * np.array(self.el_color_mapping.get(sp.symbol, [0, 0, 0]))
×
277

278
                if not exclude:
×
279
                    max_radius = (1 + self.poly_radii_tol_factor) * (max_radius + anion_radius)
×
280
                    nn = structure.get_neighbors(site, float(max_radius))
×
281
                    nn_sites = []
×
282
                    for neighbor in nn:
×
283
                        if contains_anion(neighbor):
×
284
                            nn_sites.append(neighbor)
×
285
                            if not in_coord_list(inc_coords, neighbor.coords):
×
286
                                self.add_site(neighbor)
×
287
                    if self.show_bonds:
×
288
                        self.add_bonds(nn_sites, site)
×
289
                    if self.show_polyhedron:
×
290
                        color = np.array([i / 255 for i in color])
×
291
                        self.add_polyhedron(nn_sites, site, color)
×
292

293
        if self.show_help:
×
294
            self.helptxt_actor = vtk.vtkActor2D()
×
295
            self.helptxt_actor.VisibilityOn()
×
296
            self.helptxt_actor.SetMapper(self.helptxt_mapper)
×
297
            self.ren.AddActor(self.helptxt_actor)
×
298
            self.display_help()
×
299

300
        camera = self.ren.GetActiveCamera()
×
301
        if reset_camera:
×
302
            if has_lattice:
×
303
                # Adjust the camera for best viewing
304
                lengths = s.lattice.abc
×
305
                pos = (matrix[1] + matrix[2]) * 0.5 + matrix[0] * max(lengths) / lengths[0] * 3.5
×
306
                camera.SetPosition(pos)
×
307
                camera.SetViewUp(matrix[2])
×
308
                camera.SetFocalPoint((matrix[0] + matrix[1] + matrix[2]) * 0.5)
×
309
            else:
310
                origin = s.center_of_mass
×
311
                max_site = max(s, key=lambda site: site.distance_from_point(origin))
×
312
                camera.SetPosition(origin + 5 * (max_site.coords - origin))
×
313
                camera.SetFocalPoint(s.center_of_mass)
×
314

315
        self.structure = structure
×
316
        self.title = s.composition.formula
×
317

318
    def zoom(self, factor):
1✔
319
        """
320
        Zoom the camera view by a factor.
321
        """
322
        camera = self.ren.GetActiveCamera()
×
323
        camera.Zoom(factor)
×
324
        self.ren_win.Render()
×
325

326
    def show(self):
1✔
327
        """
328
        Display the visualizer.
329
        """
330
        self.iren.Initialize()
×
331
        self.ren_win.SetSize(800, 800)
×
332
        self.ren_win.SetWindowName(self.title)
×
333
        self.ren_win.Render()
×
334
        self.iren.Start()
×
335

336
    def add_site(self, site):
1✔
337
        """
338
        Add a site to the render window. The site is displayed as a sphere, the
339
        color of which is determined based on the element. Partially occupied
340
        sites are displayed as a single element color, though the site info
341
        still shows the partial occupancy.
342

343
        Args:
344
            site: Site to add.
345
        """
346
        start_angle = 0
×
347
        radius = 0
×
348
        total_occu = 0
×
349

350
        for specie, occu in site.species.items():
×
351
            radius += occu * (
×
352
                specie.ionic_radius
353
                if isinstance(specie, Species) and specie.ionic_radius
354
                else specie.average_ionic_radius
355
            )
356
            total_occu += occu
×
357

358
        vis_radius = 0.2 + 0.002 * radius
×
359

360
        for specie, occu in site.species.items():
×
361
            if not specie:
×
362
                color = (1, 1, 1)
×
363
            elif specie.symbol in self.el_color_mapping:
×
364
                color = [i / 255 for i in self.el_color_mapping[specie.symbol]]
×
365
            mapper = self.add_partial_sphere(site.coords, vis_radius, color, start_angle, start_angle + 360 * occu)
×
366
            self.mapper_map[mapper] = [site]
×
367
            start_angle += 360 * occu
×
368

369
        if total_occu < 1:
×
370
            mapper = self.add_partial_sphere(
×
371
                site.coords,
372
                vis_radius,
373
                (1, 1, 1),
374
                start_angle,
375
                start_angle + 360 * (1 - total_occu),
376
            )
377
            self.mapper_map[mapper] = [site]
×
378

379
    def add_partial_sphere(self, coords, radius, color, start=0, end=360, opacity=1.0):
1✔
380
        """
381
        Adding a partial sphere (to display partial occupancies.
382

383
        Args:
384
            coords (nd.array): Coordinates
385
            radius (float): Radius of sphere
386
            color (): Color of sphere.
387
            start (float): Starting angle.
388
            end (float): Ending angle.
389
            opacity (float): Opacity.
390
        """
391
        sphere = vtk.vtkSphereSource()
×
392
        sphere.SetCenter(coords)
×
393
        sphere.SetRadius(radius)
×
394
        sphere.SetThetaResolution(18)
×
395
        sphere.SetPhiResolution(18)
×
396
        sphere.SetStartTheta(start)
×
397
        sphere.SetEndTheta(end)
×
398

399
        mapper = vtk.vtkPolyDataMapper()
×
400
        mapper.SetInputConnection(sphere.GetOutputPort())
×
401
        actor = vtk.vtkActor()
×
402
        actor.SetMapper(mapper)
×
403
        actor.GetProperty().SetColor(color)
×
404
        actor.GetProperty().SetOpacity(opacity)
×
405
        self.ren.AddActor(actor)
×
406
        return mapper
×
407

408
    def add_text(self, coords, text, color=(0, 0, 0)):
1✔
409
        """
410
        Add text at a coordinate.
411

412
        Args:
413
            coords: Coordinates to add text at.
414
            text: Text to place.
415
            color: Color for text as RGB. Defaults to black.
416
        """
417
        source = vtk.vtkVectorText()
×
418
        source.SetText(text)
×
419
        mapper = vtk.vtkPolyDataMapper()
×
420
        mapper.SetInputConnection(source.GetOutputPort())
×
421
        follower = vtk.vtkFollower()
×
422
        follower.SetMapper(mapper)
×
423
        follower.GetProperty().SetColor(color)
×
424
        follower.SetPosition(coords)
×
425
        follower.SetScale(0.5)
×
426
        self.ren.AddActor(follower)
×
427
        follower.SetCamera(self.ren.GetActiveCamera())
×
428

429
    def add_line(self, start, end, color=(0.5, 0.5, 0.5), width=1):
1✔
430
        """
431
        Adds a line.
432

433
        Args:
434
            start: Starting coordinates for line.
435
            end: Ending coordinates for line.
436
            color: Color for text as RGB. Defaults to grey.
437
            width: Width of line. Defaults to 1.
438
        """
439
        source = vtk.vtkLineSource()
×
440
        source.SetPoint1(start)
×
441
        source.SetPoint2(end)
×
442

443
        vertexIDs = vtk.vtkStringArray()
×
444
        vertexIDs.SetNumberOfComponents(1)
×
445
        vertexIDs.SetName("VertexIDs")
×
446
        # Set the vertex labels
447
        vertexIDs.InsertNextValue("a")
×
448
        vertexIDs.InsertNextValue("b")
×
449
        source.GetOutput().GetPointData().AddArray(vertexIDs)
×
450

451
        mapper = vtk.vtkPolyDataMapper()
×
452
        mapper.SetInputConnection(source.GetOutputPort())
×
453
        actor = vtk.vtkActor()
×
454
        actor.SetMapper(mapper)
×
455
        actor.GetProperty().SetColor(color)
×
456
        actor.GetProperty().SetLineWidth(width)
×
457
        self.ren.AddActor(actor)
×
458

459
    def add_polyhedron(
1✔
460
        self,
461
        neighbors,
462
        center,
463
        color,
464
        opacity=1.0,
465
        draw_edges=False,
466
        edges_color=(0.0, 0.0, 0.0),
467
        edges_linewidth=2,
468
    ):
469
        """
470
        Adds a polyhedron.
471

472
        Args:
473
            neighbors: Neighbors of the polyhedron (the vertices).
474
            center: The atom in the center of the polyhedron.
475
            color: Color for text as RGB.
476
            opacity: Opacity of the polyhedron
477
            draw_edges: If set to True, the a line will be drawn at each edge
478
            edges_color: Color of the line for the edges
479
            edges_linewidth: Width of the line drawn for the edges
480
        """
481
        points = vtk.vtkPoints()
×
482
        conv = vtk.vtkConvexPointSet()
×
483
        for i, n in enumerate(neighbors):
×
484
            x, y, z = n.coords
×
485
            points.InsertPoint(i, x, y, z)
×
486
            conv.GetPointIds().InsertId(i, i)
×
487
        grid = vtk.vtkUnstructuredGrid()
×
488
        grid.Allocate(1, 1)
×
489
        grid.InsertNextCell(conv.GetCellType(), conv.GetPointIds())
×
490
        grid.SetPoints(points)
×
491

492
        dsm = vtk.vtkDataSetMapper()
×
493
        polysites = [center]
×
494
        polysites.extend(neighbors)
×
495
        self.mapper_map[dsm] = polysites
×
496
        if vtk.VTK_MAJOR_VERSION <= 5:
×
497
            dsm.SetInputConnection(grid.GetProducerPort())
×
498
        else:
499
            dsm.SetInputData(grid)
×
500
        ac = vtk.vtkActor()
×
501
        # ac.SetMapper(mapHull)
502
        ac.SetMapper(dsm)
×
503
        ac.GetProperty().SetOpacity(opacity)
×
504
        if color == "element":
×
505
            # If partial occupations are involved, the color of the specie with
506
            # the highest occupation is used
507
            myoccu = 0.0
×
508
            for specie, occu in center.species.items():
×
509
                if occu > myoccu:
×
510
                    myspecie = specie
×
511
                    myoccu = occu
×
512
            color = [i / 255 for i in self.el_color_mapping[myspecie.symbol]]
×
513
            ac.GetProperty().SetColor(color)
×
514
        else:
515
            ac.GetProperty().SetColor(color)
×
516
        if draw_edges:
×
517
            ac.GetProperty().SetEdgeColor(edges_color)
×
518
            ac.GetProperty().SetLineWidth(edges_linewidth)
×
519
            ac.GetProperty().EdgeVisibilityOn()
×
520
        self.ren.AddActor(ac)
×
521

522
    def add_triangle(
1✔
523
        self,
524
        neighbors,
525
        color,
526
        center=None,
527
        opacity=0.4,
528
        draw_edges=False,
529
        edges_color=(0.0, 0.0, 0.0),
530
        edges_linewidth=2,
531
    ):
532
        """
533
        Adds a triangular surface between three atoms.
534

535
        Args:
536
            neighbors: Atoms between which a triangle will be drawn.
537
            color: Color for triangle as RGB.
538
            center: The "central atom" of the triangle
539
            opacity: opacity of the triangle
540
            draw_edges: If set to True, the a line will be  drawn at each edge
541
            edges_color: Color of the line for the edges
542
            edges_linewidth: Width of the line drawn for the edges
543
        """
544
        points = vtk.vtkPoints()
×
545
        triangle = vtk.vtkTriangle()
×
546
        for ii in range(3):
×
547
            points.InsertNextPoint(neighbors[ii].x, neighbors[ii].y, neighbors[ii].z)
×
548
            triangle.GetPointIds().SetId(ii, ii)
×
549
        triangles = vtk.vtkCellArray()
×
550
        triangles.InsertNextCell(triangle)
×
551

552
        # polydata object
553
        trianglePolyData = vtk.vtkPolyData()
×
554
        trianglePolyData.SetPoints(points)
×
555
        trianglePolyData.SetPolys(triangles)
×
556

557
        # mapper
558
        mapper = vtk.vtkPolyDataMapper()
×
559
        mapper.SetInput(trianglePolyData)
×
560

561
        ac = vtk.vtkActor()
×
562
        ac.SetMapper(mapper)
×
563
        ac.GetProperty().SetOpacity(opacity)
×
564
        if color == "element":
×
565
            if center is None:
×
566
                raise ValueError(
×
567
                    "Color should be chosen according to the central atom, and central atom is not provided"
568
                )
569
            # If partial occupations are involved, the color of the specie with
570
            # the highest occupation is used
571
            myoccu = 0.0
×
572
            for specie, occu in center.species.items():
×
573
                if occu > myoccu:
×
574
                    myspecie = specie
×
575
                    myoccu = occu
×
576
            color = [i / 255 for i in self.el_color_mapping[myspecie.symbol]]
×
577
            ac.GetProperty().SetColor(color)
×
578
        else:
579
            ac.GetProperty().SetColor(color)
×
580
        if draw_edges:
×
581
            ac.GetProperty().SetEdgeColor(edges_color)
×
582
            ac.GetProperty().SetLineWidth(edges_linewidth)
×
583
            ac.GetProperty().EdgeVisibilityOn()
×
584
        self.ren.AddActor(ac)
×
585

586
    def add_faces(self, faces, color, opacity=0.35):
1✔
587
        """
588
        Adding face of polygon.
589

590
        Args:
591
            faces (): Coordinates of the faces.
592
            color (): Color.
593
            opacity (float): Opacity
594
        """
595
        for face in faces:
×
596
            if len(face) == 3:
×
597
                points = vtk.vtkPoints()
×
598
                triangle = vtk.vtkTriangle()
×
599
                for ii in range(3):
×
600
                    points.InsertNextPoint(face[ii][0], face[ii][1], face[ii][2])
×
601
                    triangle.GetPointIds().SetId(ii, ii)
×
602
                triangles = vtk.vtkCellArray()
×
603
                triangles.InsertNextCell(triangle)
×
604
                trianglePolyData = vtk.vtkPolyData()
×
605
                trianglePolyData.SetPoints(points)
×
606
                trianglePolyData.SetPolys(triangles)
×
607
                mapper = vtk.vtkPolyDataMapper()
×
608
                if vtk.VTK_MAJOR_VERSION <= 5:
×
609
                    mapper.SetInputConnection(trianglePolyData.GetProducerPort())
×
610
                else:
611
                    mapper.SetInputData(trianglePolyData)
×
612
                # mapper.SetInput(trianglePolyData)
613
                ac = vtk.vtkActor()
×
614
                ac.SetMapper(mapper)
×
615
                ac.GetProperty().SetOpacity(opacity)
×
616
                ac.GetProperty().SetColor(color)
×
617
                self.ren.AddActor(ac)
×
618
            elif len(face) > 3:
×
619
                center = np.zeros(3, np.float_)
×
620
                for site in face:
×
621
                    center += site
×
622
                center /= np.float_(len(face))
×
623
                for ii, f in enumerate(face):
×
624
                    points = vtk.vtkPoints()
×
625
                    triangle = vtk.vtkTriangle()
×
626
                    points.InsertNextPoint(f[0], f[1], f[2])
×
627
                    ii2 = np.mod(ii + 1, len(face))
×
628
                    points.InsertNextPoint(face[ii2][0], face[ii2][1], face[ii2][2])
×
629
                    points.InsertNextPoint(center[0], center[1], center[2])
×
630
                    for ii in range(3):
×
631
                        triangle.GetPointIds().SetId(ii, ii)
×
632
                    triangles = vtk.vtkCellArray()
×
633
                    triangles.InsertNextCell(triangle)
×
634
                    trianglePolyData = vtk.vtkPolyData()
×
635
                    trianglePolyData.SetPoints(points)
×
636
                    trianglePolyData.SetPolys(triangles)
×
637
                    mapper = vtk.vtkPolyDataMapper()
×
638
                    if vtk.VTK_MAJOR_VERSION <= 5:
×
639
                        mapper.SetInputConnection(trianglePolyData.GetProducerPort())
×
640
                    else:
641
                        mapper.SetInputData(trianglePolyData)
×
642
                    # mapper.SetInput(trianglePolyData)
643
                    ac = vtk.vtkActor()
×
644
                    ac.SetMapper(mapper)
×
645
                    ac.GetProperty().SetOpacity(opacity)
×
646
                    ac.GetProperty().SetColor(color)
×
647
                    self.ren.AddActor(ac)
×
648
            else:
649
                raise ValueError("Number of points for a face should be >= 3")
×
650

651
    def add_edges(self, edges, type="line", linewidth=2, color=(0.0, 0.0, 0.0)):
1✔
652
        """
653
        Args:
654
            edges (): List of edges
655
            type ():
656
            linewidth (): Width of line
657
            color (nd.array/tuple): RGB color.
658
        """
659
        points = vtk.vtkPoints()
×
660
        lines = vtk.vtkCellArray()
×
661
        for iedge, edge in enumerate(edges):
×
662
            points.InsertPoint(2 * iedge, edge[0])
×
663
            points.InsertPoint(2 * iedge + 1, edge[1])
×
664
            lines.InsertNextCell(2)
×
665
            lines.InsertCellPoint(2 * iedge)
×
666
            lines.InsertCellPoint(2 * iedge + 1)
×
667
        polydata = vtk.vtkPolyData()
×
668
        polydata.SetPoints(points)
×
669
        polydata.SetLines(lines)
×
670
        mapper = vtk.vtkPolyDataMapper()
×
671
        if vtk.VTK_MAJOR_VERSION <= 5:
×
672
            mapper.SetInputConnection(polydata.GetProducerPort())
×
673
        else:
674
            mapper.SetInputData(polydata)
×
675
        # mapper.SetInput(polydata)
676
        ac = vtk.vtkActor()
×
677
        ac.SetMapper(mapper)
×
678
        ac.GetProperty().SetColor(color)
×
679
        ac.GetProperty().SetLineWidth(linewidth)
×
680
        self.ren.AddActor(ac)
×
681

682
    def add_bonds(self, neighbors, center, color=None, opacity=None, radius=0.1):
1✔
683
        """
684
        Adds bonds for a site.
685

686
        Args:
687
            neighbors: Neighbors of the site.
688
            center: The site in the center for all bonds.
689
            color: Color of the tubes representing the bonds
690
            opacity: Opacity of the tubes representing the bonds
691
            radius: Radius of tube s representing the bonds
692
        """
693
        points = vtk.vtkPoints()
×
694
        points.InsertPoint(0, center.x, center.y, center.z)
×
695
        n = len(neighbors)
×
696
        lines = vtk.vtkCellArray()
×
697
        for i in range(n):
×
698
            points.InsertPoint(i + 1, neighbors[i].coords)
×
699
            lines.InsertNextCell(2)
×
700
            lines.InsertCellPoint(0)
×
701
            lines.InsertCellPoint(i + 1)
×
702
        pd = vtk.vtkPolyData()
×
703
        pd.SetPoints(points)
×
704
        pd.SetLines(lines)
×
705

706
        tube = vtk.vtkTubeFilter()
×
707
        if vtk.VTK_MAJOR_VERSION <= 5:
×
708
            tube.SetInputConnection(pd.GetProducerPort())
×
709
        else:
710
            tube.SetInputData(pd)
×
711
        tube.SetRadius(radius)
×
712

713
        mapper = vtk.vtkPolyDataMapper()
×
714
        mapper.SetInputConnection(tube.GetOutputPort())
×
715

716
        actor = vtk.vtkActor()
×
717
        actor.SetMapper(mapper)
×
718
        if opacity is not None:
×
719
            actor.GetProperty().SetOpacity(opacity)
×
720
        if color is not None:
×
721
            actor.GetProperty().SetColor(color)
×
722
        self.ren.AddActor(actor)
×
723

724
    def add_picker_fixed(self):
1✔
725
        """
726
        Create a cell picker.Returns:
727
        """
728
        picker = vtk.vtkCellPicker()
×
729

730
        # Create a Python function to create the text for the text mapper used
731
        # to display the results of picking.
732

733
        def annotate_pick(obj, event):
×
734
            if picker.GetCellId() < 0 and not self.show_help:
×
735
                self.helptxt_actor.VisibilityOff()
×
736
            else:
737
                mapper = picker.GetMapper()
×
738
                if mapper in self.mapper_map:
×
739
                    output = []
×
740
                    for site in self.mapper_map[mapper]:
×
741
                        row = [
×
742
                            f"{site.species_string} - ",
743
                            ", ".join(f"{c:.3f}" for c in site.frac_coords),
744
                            "[" + ", ".join(f"{c:.3f}" for c in site.coords) + "]",
745
                        ]
746
                        output.append("".join(row))
×
747
                    self.helptxt_mapper.SetInput("\n".join(output))
×
748
                    self.helptxt_actor.SetPosition(10, 10)
×
749
                    self.helptxt_actor.VisibilityOn()
×
750
                    self.show_help = False
×
751

752
        self.picker = picker
×
753
        picker.AddObserver("EndPickEvent", annotate_pick)
×
754
        self.iren.SetPicker(picker)
×
755

756
    def add_picker(self):
1✔
757
        """
758
        Create a cell picker.
759
        """
760
        picker = vtk.vtkCellPicker()
×
761
        # Create a Python function to create the text for the text mapper used
762
        # to display the results of picking.
763
        source = vtk.vtkVectorText()
×
764
        mapper = vtk.vtkPolyDataMapper()
×
765
        mapper.SetInputConnection(source.GetOutputPort())
×
766
        follower = vtk.vtkFollower()
×
767
        follower.SetMapper(mapper)
×
768
        follower.GetProperty().SetColor((0, 0, 0))
×
769
        follower.SetScale(0.2)
×
770
        self.ren.AddActor(follower)
×
771
        follower.SetCamera(self.ren.GetActiveCamera())
×
772
        follower.VisibilityOff()
×
773

774
        def annotate_pick(obj, event):
×
775
            if picker.GetCellId() < 0:
×
776
                follower.VisibilityOff()
×
777
            else:
778
                pick_pos = picker.GetPickPosition()
×
779
                mapper = picker.GetMapper()
×
780
                if mapper in self.mapper_map:
×
781
                    site = self.mapper_map[mapper]
×
782
                    output = [
×
783
                        site.species_string,
784
                        "Frac. coords: " + " ".join(f"{c:.4f}" for c in site.frac_coords),
785
                    ]
786
                    source.SetText("\n".join(output))
×
787
                    follower.SetPosition(pick_pos)
×
788
                    follower.VisibilityOn()
×
789

790
        picker.AddObserver("EndPickEvent", annotate_pick)
×
791
        self.picker = picker
×
792
        self.iren.SetPicker(picker)
×
793

794

795
class StructureInteractorStyle(vtkInteractorStyleTrackballCamera):
1✔
796
    """
797
    A custom interactor style for visualizing structures.
798
    """
799

800
    def __init__(self, parent):
1✔
801
        """
802
        Args:
803
            parent ():
804
        """
805
        self.parent = parent
×
806
        self.AddObserver("LeftButtonPressEvent", self.leftButtonPressEvent)
×
807
        self.AddObserver("MouseMoveEvent", self.mouseMoveEvent)
×
808
        self.AddObserver("LeftButtonReleaseEvent", self.leftButtonReleaseEvent)
×
809
        self.AddObserver("KeyPressEvent", self.keyPressEvent)
×
810

811
    def leftButtonPressEvent(self, obj, event):
1✔
812
        """
813
        Args:
814
            obj ():
815
            event ():
816
        """
817
        self.mouse_motion = 0
×
818
        self.OnLeftButtonDown()
×
819

820
    def mouseMoveEvent(self, obj, event):
1✔
821
        """
822
        Args:
823
            obj ():
824
            event ():
825
        """
826
        self.mouse_motion = 1
×
827
        self.OnMouseMove()
×
828

829
    def leftButtonReleaseEvent(self, obj, event):
1✔
830
        """
831
        Args:
832
            obj ():
833
            event ():
834
        """
835
        ren = obj.GetCurrentRenderer()
×
836
        iren = ren.GetRenderWindow().GetInteractor()
×
837
        if self.mouse_motion == 0:
×
838
            pos = iren.GetEventPosition()
×
839
            iren.GetPicker().Pick(pos[0], pos[1], 0, ren)
×
840
        self.OnLeftButtonUp()
×
841

842
    def keyPressEvent(self, obj, event):
1✔
843
        """
844
        Args:
845
            obj ():
846
            event ():
847
        """
848
        parent = obj.GetCurrentRenderer().parent
×
849
        sym = parent.iren.GetKeySym()
×
850

851
        if sym in "ABCabc":
×
852
            if sym == "A":
×
853
                parent.supercell[0][0] += 1
×
854
            elif sym == "B":
×
855
                parent.supercell[1][1] += 1
×
856
            elif sym == "C":
×
857
                parent.supercell[2][2] += 1
×
858
            elif sym == "a":
×
859
                parent.supercell[0][0] = max(parent.supercell[0][0] - 1, 1)
×
860
            elif sym == "b":
×
861
                parent.supercell[1][1] = max(parent.supercell[1][1] - 1, 1)
×
862
            elif sym == "c":
×
863
                parent.supercell[2][2] = max(parent.supercell[2][2] - 1, 1)
×
864
            parent.redraw()
×
865
        elif sym == "numbersign":
×
866
            parent.show_polyhedron = not parent.show_polyhedron
×
867
            parent.redraw()
×
868
        elif sym == "minus":
×
869
            parent.show_bonds = not parent.show_bonds
×
870
            parent.redraw()
×
871
        elif sym == "bracketleft":
×
872
            parent.poly_radii_tol_factor -= 0.05 if parent.poly_radii_tol_factor > 0 else 0
×
873
            parent.redraw()
×
874
        elif sym == "bracketright":
×
875
            parent.poly_radii_tol_factor += 0.05
×
876
            parent.redraw()
×
877
        elif sym == "h":
×
878
            parent.show_help = not parent.show_help
×
879
            parent.redraw()
×
880
        elif sym == "r":
×
881
            parent.redraw(True)
×
882
        elif sym == "s":
×
883
            parent.write_image("image.png")
×
884
        elif sym == "Up":
×
885
            parent.rotate_view(1, 90)
×
886
        elif sym == "Down":
×
887
            parent.rotate_view(1, -90)
×
888
        elif sym == "Left":
×
889
            parent.rotate_view(0, -90)
×
890
        elif sym == "Right":
×
891
            parent.rotate_view(0, 90)
×
892
        elif sym == "o":
×
893
            parent.orthongonalize_structure()
×
894
            parent.redraw()
×
895

896
        self.OnKeyPress()
×
897

898

899
def make_movie(structures, output_filename="movie.mp4", zoom=1.0, fps=20, bitrate="10000k", quality=1, **kwargs):
1✔
900
    """
901
    Generate a movie from a sequence of structures using vtk and ffmpeg.
902

903
    Args:
904
        structures ([Structure]): sequence of structures
905
        output_filename (str): filename for structure output. defaults to
906
            movie.mp4
907
        zoom (float): A zoom to be applied to the visualizer. Defaults to 1.0.
908
        fps (int): Frames per second for the movie. Defaults to 20.
909
        bitrate (str): Video bitate. Defaults to "10000k" (fairly high
910
            quality).
911
        quality (int): A quality scale. Defaults to 1.
912
        kwargs: Any kwargs supported by StructureVis to modify the images
913
            generated.
914
    """
915
    vis = StructureVis(**kwargs)
×
916
    vis.show_help = False
×
917
    vis.redraw()
×
918
    vis.zoom(zoom)
×
919
    sigfig = int(math.floor(math.log10(len(structures))) + 1)
×
920
    filename = "image{0:0" + str(sigfig) + "d}.png"
×
921
    for i, s in enumerate(structures):
×
922
        vis.set_structure(s)
×
923
        vis.write_image(filename.format(i), 3)
×
924
    filename = "image%0" + str(sigfig) + "d.png"
×
925
    args = [
×
926
        "ffmpeg",
927
        "-y",
928
        "-i",
929
        filename,
930
        "-q:v",
931
        str(quality),
932
        "-r",
933
        str(fps),
934
        "-b:v",
935
        str(bitrate),
936
        output_filename,
937
    ]
938
    with subprocess.Popen(args) as p:
×
939
        p.communicate()
×
940

941

942
class MultiStructuresVis(StructureVis):
1✔
943
    """
944
    Visualization for multiple structures.
945
    """
946

947
    DEFAULT_ANIMATED_MOVIE_OPTIONS = {
1✔
948
        "time_between_frames": 0.1,
949
        "looping_type": "restart",
950
        "number_of_loops": 1,
951
        "time_between_loops": 1.0,
952
    }
953

954
    def __init__(
1✔
955
        self,
956
        element_color_mapping=None,
957
        show_unit_cell=True,
958
        show_bonds=False,
959
        show_polyhedron=False,
960
        poly_radii_tol_factor=0.5,
961
        excluded_bonding_elements=None,
962
        animated_movie_options=DEFAULT_ANIMATED_MOVIE_OPTIONS,
963
    ):
964
        """
965
        Args:
966
            element_color_mapping: Optional color mapping for the elements,
967
                as a dict of {symbol: rgb tuple}. For example, {"Fe": (255,
968
                123,0), ....} If None is specified, a default based on
969
                Jmol's color scheme is used.
970
            show_unit_cell: Set to False to not show the unit cell
971
                boundaries. Defaults to True.
972
            show_bonds: Set to True to show bonds. Defaults to True.
973
            show_polyhedron: Set to True to show polyhedrons. Defaults to
974
                False.
975
            poly_radii_tol_factor: The polyhedron and bonding code uses the
976
                ionic radii of the elements or species to determine if two
977
                atoms are bonded. This specifies a tolerance scaling factor
978
                such that atoms which are (1 + poly_radii_tol_factor) * sum
979
                of ionic radii apart are still considered as bonded.
980
            excluded_bonding_elements: List of atom types to exclude from
981
                bonding determination. Defaults to an empty list. Useful
982
                when trying to visualize a certain atom type in the
983
                framework (e.g., Li in a Li-ion battery cathode material).
984
            animated_movie_options (): Used for moving.
985
        """
986
        super().__init__(
×
987
            element_color_mapping=element_color_mapping,
988
            show_unit_cell=show_unit_cell,
989
            show_bonds=show_bonds,
990
            show_polyhedron=show_polyhedron,
991
            poly_radii_tol_factor=poly_radii_tol_factor,
992
            excluded_bonding_elements=excluded_bonding_elements,
993
        )
994
        self.warningtxt_actor = vtk.vtkActor2D()
×
995
        self.infotxt_actor = vtk.vtkActor2D()
×
996
        self.structures = None
×
997
        style = MultiStructuresInteractorStyle(self)
×
998
        self.iren.SetInteractorStyle(style)
×
999
        self.istruct = 0
×
1000
        self.current_structure = None
×
1001
        self.set_animated_movie_options(animated_movie_options=animated_movie_options)
×
1002

1003
    def set_structures(self, structures: Sequence[Structure], tags=None):
1✔
1004
        """
1005
        Add list of structures to the visualizer.
1006

1007
        Args:
1008
            structures (list[Structures]): structures to be visualized.
1009
            tags (): List of tags.
1010
        """
1011
        self.structures = structures
×
1012
        self.istruct = 0
×
1013
        self.current_structure = self.structures[self.istruct]
×
1014
        self.tags = tags if tags is not None else []
×
1015
        self.all_radii = []
×
1016
        self.all_vis_radii = []
×
1017
        for struct in self.structures:
×
1018
            struct_radii = []
×
1019
            struct_vis_radii = []
×
1020
            for site in struct:
×
1021
                radius = 0
×
1022
                for specie, occu in site.species.items():
×
1023
                    radius += occu * (
×
1024
                        specie.ionic_radius
1025
                        if isinstance(specie, Species) and specie.ionic_radius
1026
                        else specie.average_ionic_radius
1027
                    )
1028
                    vis_radius = 0.2 + 0.002 * radius
×
1029
                struct_radii.append(radius)
×
1030
                struct_vis_radii.append(vis_radius)
×
1031
            self.all_radii.append(struct_radii)
×
1032
            self.all_vis_radii.append(struct_vis_radii)
×
1033
        self.set_structure(self.current_structure, reset_camera=True, to_unit_cell=False)
×
1034

1035
    def set_structure(self, structure: Structure, reset_camera=True, to_unit_cell=False):
1✔
1036
        """
1037
        Add a structure to the visualizer.
1038

1039
        Args:
1040
            structure: structure to visualize
1041
            reset_camera: Set to True to reset the camera to a default
1042
                determined based on the structure.
1043
            to_unit_cell: Whether or not to fall back sites into the unit cell.
1044
        """
1045
        super().set_structure(structure=structure, reset_camera=reset_camera, to_unit_cell=to_unit_cell)
×
1046
        self.apply_tags()
×
1047

1048
    def apply_tags(self):
1✔
1049
        """
1050
        Apply tags.
1051
        """
1052
        tags = {}
×
1053
        for tag in self.tags:
×
1054
            istruct = tag.get("istruct", "all")
×
1055
            if istruct != "all":
×
1056
                if istruct != self.istruct:
×
1057
                    continue
×
1058
            site_index = tag["site_index"]
×
1059
            color = tag.get("color", [0.5, 0.5, 0.5])
×
1060
            opacity = tag.get("opacity", 0.5)
×
1061
            if site_index == "unit_cell_all":
×
1062
                struct_radii = self.all_vis_radii[self.istruct]
×
1063
                for isite, _site in enumerate(self.current_structure):
×
1064
                    vis_radius = 1.5 * tag.get("radius", struct_radii[isite])
×
1065
                    tags[(isite, (0, 0, 0))] = {
×
1066
                        "radius": vis_radius,
1067
                        "color": color,
1068
                        "opacity": opacity,
1069
                    }
1070
                continue
×
1071
            cell_index = tag["cell_index"]
×
1072
            if "radius" in tag:
×
1073
                vis_radius = tag["radius"]
×
1074
            elif "radius_factor" in tag:
×
1075
                vis_radius = tag["radius_factor"] * self.all_vis_radii[self.istruct][site_index]
×
1076
            else:
1077
                vis_radius = 1.5 * self.all_vis_radii[self.istruct][site_index]
×
1078
            tags[(site_index, cell_index)] = {
×
1079
                "radius": vis_radius,
1080
                "color": color,
1081
                "opacity": opacity,
1082
            }
1083
        for site_and_cell_index, tag_style in tags.items():
×
1084
            isite, cell_index = site_and_cell_index
×
1085
            site = self.current_structure[isite]
×
1086
            if cell_index == (0, 0, 0):
×
1087
                coords = site.coords
×
1088
            else:
1089
                fcoords = site.frac_coords + np.array(cell_index)
×
1090
                site_image = PeriodicSite(
×
1091
                    site.species,
1092
                    fcoords,
1093
                    self.current_structure.lattice,
1094
                    to_unit_cell=False,
1095
                    coords_are_cartesian=False,
1096
                    properties=site.properties,
1097
                )
1098
                self.add_site(site_image)
×
1099
                coords = site_image.coords
×
1100
            vis_radius = tag_style["radius"]
×
1101
            color = tag_style["color"]
×
1102
            opacity = tag_style["opacity"]
×
1103
            self.add_partial_sphere(
×
1104
                coords=coords,
1105
                radius=vis_radius,
1106
                color=color,
1107
                start=0,
1108
                end=360,
1109
                opacity=opacity,
1110
            )
1111

1112
    def set_animated_movie_options(self, animated_movie_options=None):
1✔
1113
        """
1114
        Args:
1115
            animated_movie_options ():
1116
        """
1117
        if animated_movie_options is None:
×
1118
            self.animated_movie_options = self.DEFAULT_ANIMATED_MOVIE_OPTIONS.copy()
×
1119
        else:
1120
            self.animated_movie_options = self.DEFAULT_ANIMATED_MOVIE_OPTIONS.copy()
×
1121
            for key in animated_movie_options:
×
1122
                if key not in self.DEFAULT_ANIMATED_MOVIE_OPTIONS:
×
1123
                    raise ValueError("Wrong option for animated movie")
×
1124
            self.animated_movie_options.update(animated_movie_options)
×
1125

1126
    def display_help(self):
1✔
1127
        """
1128
        Display the help for various keyboard shortcuts.
1129
        """
1130
        helptxt = [
×
1131
            "h : Toggle help",
1132
            "A/a, B/b or C/c : Increase/decrease cell by one a, b or c unit vector",
1133
            "# : Toggle showing of polyhedrons",
1134
            "-: Toggle showing of bonds",
1135
            "r : Reset camera direction",
1136
            "[/]: Decrease or increase poly_radii_tol_factor by 0.05. Value = " + str(self.poly_radii_tol_factor),
1137
            "Up/Down: Rotate view along Up direction by 90 clockwise/anticlockwise",
1138
            "Left/right: Rotate view along camera direction by 90 clockwise/anticlockwise",
1139
            "s: Save view to image.png",
1140
            "o: Orthogonalize structure",
1141
            "n: Move to next structure",
1142
            "p: Move to previous structure",
1143
            "m: Animated movie of the structures",
1144
        ]
1145
        self.helptxt_mapper.SetInput("\n".join(helptxt))
×
1146
        self.helptxt_actor.SetPosition(10, 10)
×
1147
        self.helptxt_actor.VisibilityOn()
×
1148

1149
    def display_warning(self, warning):
1✔
1150
        """
1151
        Args:
1152
            warning (str): Warning
1153
        """
1154
        self.warningtxt_mapper = vtk.vtkTextMapper()
×
1155
        tprops = self.warningtxt_mapper.GetTextProperty()
×
1156
        tprops.SetFontSize(14)
×
1157
        tprops.SetFontFamilyToTimes()
×
1158
        tprops.SetColor(1, 0, 0)
×
1159
        tprops.BoldOn()
×
1160
        tprops.SetJustificationToRight()
×
1161
        self.warningtxt = f"WARNING : {warning}"
×
1162
        self.warningtxt_actor = vtk.vtkActor2D()
×
1163
        self.warningtxt_actor.VisibilityOn()
×
1164
        self.warningtxt_actor.SetMapper(self.warningtxt_mapper)
×
1165
        self.ren.AddActor(self.warningtxt_actor)
×
1166
        self.warningtxt_mapper.SetInput(self.warningtxt)
×
1167
        winsize = self.ren_win.GetSize()
×
1168
        self.warningtxt_actor.SetPosition(winsize[0] - 10, 10)
×
1169
        self.warningtxt_actor.VisibilityOn()
×
1170

1171
    def erase_warning(self):
1✔
1172
        """
1173
        Remove warnings.
1174
        """
1175
        self.warningtxt_actor.VisibilityOff()
×
1176

1177
    def display_info(self, info):
1✔
1178
        """
1179
        Args:
1180
            info (str): Information.
1181
        """
1182
        self.infotxt_mapper = vtk.vtkTextMapper()
×
1183
        tprops = self.infotxt_mapper.GetTextProperty()
×
1184
        tprops.SetFontSize(14)
×
1185
        tprops.SetFontFamilyToTimes()
×
1186
        tprops.SetColor(0, 0, 1)
×
1187
        tprops.BoldOn()
×
1188
        tprops.SetVerticalJustificationToTop()
×
1189
        self.infotxt = f"INFO : {info}"
×
1190
        self.infotxt_actor = vtk.vtkActor2D()
×
1191
        self.infotxt_actor.VisibilityOn()
×
1192
        self.infotxt_actor.SetMapper(self.infotxt_mapper)
×
1193
        self.ren.AddActor(self.infotxt_actor)
×
1194
        self.infotxt_mapper.SetInput(self.infotxt)
×
1195
        winsize = self.ren_win.GetSize()
×
1196
        self.infotxt_actor.SetPosition(10, winsize[1] - 10)
×
1197
        self.infotxt_actor.VisibilityOn()
×
1198

1199
    def erase_info(self):
1✔
1200
        """
1201
        Erase all info.
1202
        """
1203
        self.infotxt_actor.VisibilityOff()
×
1204

1205

1206
class MultiStructuresInteractorStyle(StructureInteractorStyle):
1✔
1207
    """
1208
    Interactor for MultiStructureVis.
1209
    """
1210

1211
    def __init__(self, parent) -> None:
1✔
1212
        """
1213
        Args:
1214
            parent ():
1215
        """
1216
        StructureInteractorStyle.__init__(self, parent=parent)
×
1217

1218
    def keyPressEvent(self, obj, event):
1✔
1219
        """
1220
        Args:
1221
            obj ():
1222
            event ():
1223
        """
1224
        parent = obj.GetCurrentRenderer().parent
×
1225
        sym = parent.iren.GetKeySym()
×
1226

1227
        if sym == "n":
×
1228
            if parent.istruct == len(parent.structures) - 1:
×
1229
                parent.display_warning("LAST STRUCTURE")
×
1230
                parent.ren_win.Render()
×
1231
            else:
1232
                parent.istruct += 1
×
1233
                parent.current_structure = parent.structures[parent.istruct]
×
1234
                parent.set_structure(parent.current_structure, reset_camera=False, to_unit_cell=False)
×
1235
                parent.erase_warning()
×
1236
                parent.ren_win.Render()
×
1237
        elif sym == "p":
×
1238
            if parent.istruct == 0:
×
1239
                parent.display_warning("FIRST STRUCTURE")
×
1240
                parent.ren_win.Render()
×
1241
            else:
1242
                parent.istruct -= 1
×
1243
                parent.current_structure = parent.structures[parent.istruct]
×
1244
                parent.set_structure(parent.current_structure, reset_camera=False, to_unit_cell=False)
×
1245
                parent.erase_warning()
×
1246
                parent.ren_win.Render()
×
1247
        elif sym == "m":
×
1248
            parent.istruct = 0
×
1249
            parent.current_structure = parent.structures[parent.istruct]
×
1250
            parent.set_structure(parent.current_structure, reset_camera=False, to_unit_cell=False)
×
1251
            parent.erase_warning()
×
1252
            parent.ren_win.Render()
×
1253
            nloops = parent.animated_movie_options["number_of_loops"]
×
1254
            tstep = parent.animated_movie_options["time_between_frames"]
×
1255
            tloops = parent.animated_movie_options["time_between_loops"]
×
1256
            if parent.animated_movie_options["looping_type"] == "restart":
×
1257
                loop_istructs = range(len(parent.structures))
×
1258
            elif parent.animated_movie_options["looping_type"] == "palindrome":
×
1259
                loop_istructs = range(len(parent.structures)) + range(len(parent.structures) - 2, -1, -1)
×
1260
            else:
1261
                raise ValueError('"looping_type" should be "restart" or "palindrome"')
×
1262
            for iloop in range(nloops):
×
1263
                for istruct in loop_istructs:
×
1264
                    time.sleep(tstep)
×
1265
                    parent.istruct = istruct
×
1266
                    parent.current_structure = parent.structures[parent.istruct]
×
1267
                    parent.set_structure(parent.current_structure, reset_camera=False, to_unit_cell=False)
×
1268
                    parent.display_info(
×
1269
                        f"Animated movie : structure {istruct + 1}/{len(parent.structures)} "
1270
                        f"(loop {iloop + 1}/{nloops})"
1271
                    )
1272
                    parent.ren_win.Render()
×
1273
                time.sleep(tloops)
×
1274
            parent.erase_info()
×
1275
            parent.display_info("Ended animated movie ...")
×
1276
            parent.ren_win.Render()
×
1277

1278
        StructureInteractorStyle.keyPressEvent(self, obj, event)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc