vestacrystparser 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Bernard Field, Musa Hussein
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: vestacrystparser
3
+ Version: 0.1.0
4
+ Summary: Parser for VESTA crystal structure visualisation files.
5
+ Author-email: Bernard Field <bfield@lbl.gov>, Musa Hussein <mhussein@lbl.gov>
6
+ Maintainer-email: Bernard Field <bfield@lbl.gov>
7
+ License-Expression: MIT
8
+ Project-URL: Repository, https://github.com/Griffin-Group/vestacrystparser
9
+ Project-URL: Documentation, https://griffin-group.github.io/vestacrystparser/
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: File Formats
16
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
17
+ Classifier: Topic :: Scientific/Engineering :: Physics
18
+ Classifier: Topic :: Scientific/Engineering :: Visualization
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: pymatgen
23
+ Requires-Dist: pymatgen; extra == "pymatgen"
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: flake8; extra == "dev"
27
+ Requires-Dist: autopep8; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+ Requires-Dist: sphinx; extra == "dev"
30
+ Requires-Dist: myst-parser; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ # vestacrystparser
34
+
35
+ **vestacrystparser** is an unofficial general-purpose Python parser for
36
+ [VESTA](https://jp-minerals.org/vesta/) crystal visualisation files.
37
+ It provides a programmatic API for creating and modifying VESTA files.
38
+ It is intended to enable batch-creating consistent visualisation states,
39
+ removing the tedium of manually adjusting settings via the GUI.
40
+
41
+ This project is functional, but feature-incomplete.
42
+ High-level API for modifying specific parts of the file in specific ways are
43
+ implemented as needed or requested.
44
+ Pull requests adding more methods are welcome.
45
+ However, the low-level API can cover all cases, albeit requiring more care.
46
+
47
+ See the [documentation](https://griffin-group.github.io/vestacrystparser/)
48
+ for instructions for installation and usage.
49
+
50
+ **vestacrystparser** is made available under the MIT License (see `LICENSE`
51
+ file).
@@ -0,0 +1,19 @@
1
+ # vestacrystparser
2
+
3
+ **vestacrystparser** is an unofficial general-purpose Python parser for
4
+ [VESTA](https://jp-minerals.org/vesta/) crystal visualisation files.
5
+ It provides a programmatic API for creating and modifying VESTA files.
6
+ It is intended to enable batch-creating consistent visualisation states,
7
+ removing the tedium of manually adjusting settings via the GUI.
8
+
9
+ This project is functional, but feature-incomplete.
10
+ High-level API for modifying specific parts of the file in specific ways are
11
+ implemented as needed or requested.
12
+ Pull requests adding more methods are welcome.
13
+ However, the low-level API can cover all cases, albeit requiring more care.
14
+
15
+ See the [documentation](https://griffin-group.github.io/vestacrystparser/)
16
+ for instructions for installation and usage.
17
+
18
+ **vestacrystparser** is made available under the MIT License (see `LICENSE`
19
+ file).
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 80.0", "setuptools_scm[simple] >= 8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vestacrystparser"
7
+ #version = "0.0.1a1"
8
+ dynamic = ["version"]
9
+ dependencies = [
10
+ ]
11
+ requires-python = ">= 3.9"
12
+ authors = [
13
+ {name = "Bernard Field", email = "bfield@lbl.gov"},
14
+ {name = "Musa Hussein", email = "mhussein@lbl.gov"}
15
+ ]
16
+ maintainers = [
17
+ {name = "Bernard Field", email = "bfield@lbl.gov"}
18
+ ]
19
+ description = "Parser for VESTA crystal structure visualisation files."
20
+ readme = "README.md"
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Science/Research",
24
+ "Natural Language :: English",
25
+ "Programming Language :: Python",
26
+ "Programming Language :: Python :: 3",
27
+ "Topic :: File Formats",
28
+ "Topic :: Scientific/Engineering :: Chemistry",
29
+ "Topic :: Scientific/Engineering :: Physics",
30
+ "Topic :: Scientific/Engineering :: Visualization",
31
+ ]
32
+ license = "MIT"
33
+ license-files = ["LICENSE"]
34
+
35
+ [project.optional-dependencies]
36
+ pymatgen = ["pymatgen"]
37
+ dev = [
38
+ "pytest",
39
+ "flake8",
40
+ "autopep8",
41
+ "pytest-cov",
42
+ "sphinx",
43
+ "myst-parser",
44
+ ]
45
+
46
+ [project.urls]
47
+ Repository = "https://github.com/Griffin-Group/vestacrystparser"
48
+ Documentation = "https://griffin-group.github.io/vestacrystparser/"
49
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ from vestacrystparser.parser import VestaFile
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright 2025 Bernard Field
3
+ """Create VESTA files from structural data files (POSCAR, etc.).
4
+ """
5
+
6
+ from pymatgen.core import Structure
7
+ from pymatgen.io.vasp.inputs import Poscar
8
+
9
+ from vestacrystparser.parser import VestaFile
10
+
11
+
12
+ def vesta_from_structure(stru: Structure) -> VestaFile:
13
+ """Return a VestaFile from pymatgen.core.Structure"""
14
+ # TODO Convert numpy floats to regular floats.
15
+ # Initialise an empty Vesta file
16
+ vfile = VestaFile()
17
+ # Set the lattice parameters.
18
+ vfile.set_cell(*stru.lattice.abc, *stru.lattice.angles)
19
+ # Add the sites
20
+ counts = {}
21
+ for site in stru:
22
+ element = site.specie.symbol
23
+ # When loading POSCAR, site labels in VESTA are numbered.
24
+ if element in counts:
25
+ counts[element] += 1
26
+ else:
27
+ counts[element] = 1
28
+ vfile.add_site(element, element+str(counts[element]),
29
+ *site.frac_coords,
30
+ add_bonds=True)
31
+ # Sort SBOND
32
+ vfile.sort_bonds()
33
+ # Done
34
+ return vfile
35
+
36
+
37
+ def vesta_from_poscar(fname: str) -> VestaFile:
38
+ """Return a VestaFile from a POSCAR file at fname"""
39
+ # Load the POSCAR
40
+ pos = Poscar.from_file(fname)
41
+ # Create a VestaFile from the structure
42
+ vfile = vesta_from_structure(pos.structure)
43
+ # Set the title
44
+ vfile.title = pos.comment
45
+ return vfile
46
+
47
+ # Thoughts...
48
+ # CIF will be tricky, because it contains symmetry and precision
49
+ # information and is variable in the data it contains, so I can't simply
50
+ # convert to Structure then use that.
51
+ # pymatgen.io.cif supports reading CIF files with all data.
52
+ # If pymatgen proves unreliable, could also attempt PyCifRW https://pypi.org/project/PyCifRW/
53
+ # In any case, CIF is hard, and I don't have much experience with CIF's.
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright 2025 Bernard Field
3
+ """Open VESTA and export images.
4
+
5
+ :func:`export_image_from_file` is the core function,
6
+ which directly opens a VESTA file then exports an image file
7
+ using VESTA's (rather sparse) command line interface.
8
+
9
+ This requires a working VESTA installation on your system.
10
+ """
11
+
12
+ import platform
13
+ import os
14
+ import subprocess
15
+ import time
16
+
17
+
18
+ class NoVestaError(OSError):
19
+ """VESTA does not exist or cannot be found."""
20
+ pass
21
+
22
+
23
+ def export_image_from_file(input: str, output: str, scale: int = 1,
24
+ close: bool = True, block: bool = True,
25
+ timeout: float = None):
26
+ """Opens a file in VESTA and saves it as an image.
27
+
28
+ You can open VESTA before running this command to set the size of the
29
+ window, which determines the size of the image.
30
+
31
+ Runs ASYNCHRONOUSLY. May be subject to race conditions.
32
+ Modifying the files while running this command will
33
+ cause unexpected behaviour. I do not know how to retrieve an exit call,
34
+ besides checking if `output` has been written (which is what `block` does).
35
+
36
+ Note that, even with the `close` argument, VESTA will remain open after
37
+ this call. (`close` just closes the tab within VESTA.) You are
38
+ responsible for closing it yourself when you're done.
39
+
40
+ `block` is strongly recommended if you are doing more than a couple
41
+ at once, because I've found VESTA will max-out on processes if it
42
+ tries to open too many files simultaneously.
43
+
44
+ VESTA has a command line interface:
45
+ https://jp-minerals.org/vesta/en/doc/VESTAch17.html
46
+
47
+ Koichi Momma gives directions on how to use VESTA's command line interface:
48
+ https://groups.google.com/g/vesta-discuss/c/xePcwJ3Mdgw/m/GyC8_UZbAwAJ
49
+
50
+ Args:
51
+ input: Path to file readable by VESTA.
52
+ output: Path to write image to. Should include a recognisable image
53
+ file extension.
54
+ scale: Amount to scale raster image by.
55
+ close: Whether to close the VESTA tab afterward.
56
+ block: Whether to block the main process until the file is written.
57
+ If the export process fails on VESTA's end, then this will hang!
58
+ However, it is recommended to reduce race conditions or system
59
+ overload.
60
+ timeout: Number of seconds to block for until we raise TimeoutError.
61
+ If None, will count indefinitely.
62
+ Note, though, that VESTA will still be running if we hit the
63
+ Timeout. We just hand the focus back to Python.
64
+
65
+ Raises:
66
+ NoVestaError: If fails to run VESTA.
67
+ OSError: If run on an unsupport OS.
68
+ (Supports Windows, Linux, MacOS/Darwin.)
69
+ FileNotFoundError: If `input` doesn't exist or `output`'s directory
70
+ doesn't exist.
71
+ TimeoutError: block=True and we timeout.
72
+ """
73
+ # First, identify which platform we are on.
74
+ opsys = platform.system()
75
+ # This determines the form of the VESTA command
76
+ if opsys == "Darwin": # a.k.a. MacOS
77
+ vesta_cmd = ["open", "-n", "-a", "VESTA.app", "--args"]
78
+ elif opsys == "Windows" or opsys == "Linux":
79
+ vesta_cmd = ["VESTA"]
80
+ else:
81
+ # If we're here, then python platform has dones something wrong.
82
+ raise OSError(f"Unrecognised operating system {opsys}.")
83
+ # Next, identify the absolute path to the input,
84
+ # as my tests on MacOS have found that we must specify the absolute path
85
+ # or else it will do the path from /Applications/VESTA
86
+ abs_input = os.path.abspath(input)
87
+ # Validate that the file exists.
88
+ if not os.path.isfile(abs_input):
89
+ raise FileNotFoundError(f"Cannot find file {abs_input}.")
90
+ # Sanitise scale
91
+ if not isinstance(scale, int):
92
+ scale = int(scale)
93
+ # Validate that output directory exists.
94
+ output_dir = os.path.dirname(output)
95
+ if output_dir and not os.path.isdir(output_dir):
96
+ # output_dir may be an empty string if output has no path.
97
+ # That is acceptable, but isdir would say False.
98
+ # So only check if the directory doesn't exist if non-empty.
99
+ raise FileNotFoundError(f"Cannot find directory {output_dir}.")
100
+ # Prepare current state for blocking.
101
+ if block:
102
+ # If the file exists, we'll check when it gets overwritten.
103
+ if os.path.exists(output):
104
+ old_time = os.path.getmtime(output)
105
+ else:
106
+ # Otherwise, we'll check when it gets written.
107
+ old_time = None
108
+ # Form the command to execute.
109
+ cmd = vesta_cmd + ["-open", abs_input,
110
+ "-export_img", f"scale={scale}", str(output)]
111
+ if close:
112
+ cmd += ["-close"]
113
+ # Run
114
+ try:
115
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
116
+ except subprocess.CalledProcessError as e:
117
+ # MacOS gave the following stderr:
118
+ # Unable to find application named 'VESTA.app'\n
119
+ # But there might be other reasons this fails, or platform
120
+ # dependencies.
121
+ raise NoVestaError("Failed to run VESTA.")
122
+ except FileNotFoundError:
123
+ # I find that Mac and Linux will give FileNotFoundError if the
124
+ # specified command does not exist.
125
+ raise NoVestaError("Failed to run VESTA.")
126
+ if block:
127
+ elapsed = 0
128
+ increment = 0.02
129
+ if old_time is None:
130
+ # Check if the file has been created.
131
+ while not os.path.exists(output) and \
132
+ (timeout is None or elapsed < timeout):
133
+ time.sleep(increment)
134
+ elapsed += increment
135
+ else:
136
+ # Check if the file has been overwritten.
137
+ while os.path.getmtime(output) == old_time and \
138
+ (timeout is None or elapsed < timeout):
139
+ time.sleep(increment)
140
+ elapsed += increment
141
+ if timeout is not None and elapsed >= timeout:
142
+ raise TimeoutError(
143
+ f"export_image_from_file timed out after {elapsed} seconds.")