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.
- vestacrystparser-0.1.0/LICENSE +21 -0
- vestacrystparser-0.1.0/PKG-INFO +51 -0
- vestacrystparser-0.1.0/README.md +19 -0
- vestacrystparser-0.1.0/pyproject.toml +49 -0
- vestacrystparser-0.1.0/setup.cfg +4 -0
- vestacrystparser-0.1.0/vestacrystparser/__init__.py +1 -0
- vestacrystparser-0.1.0/vestacrystparser/convert.py +53 -0
- vestacrystparser-0.1.0/vestacrystparser/export.py +143 -0
- vestacrystparser-0.1.0/vestacrystparser/parser.py +2156 -0
- vestacrystparser-0.1.0/vestacrystparser/resources/__init__.py +0 -0
- vestacrystparser-0.1.0/vestacrystparser/resources/default.vesta +177 -0
- vestacrystparser-0.1.0/vestacrystparser/resources/elements.csv +97 -0
- vestacrystparser-0.1.0/vestacrystparser/resources/sbond.csv +914 -0
- vestacrystparser-0.1.0/vestacrystparser.egg-info/PKG-INFO +51 -0
- vestacrystparser-0.1.0/vestacrystparser.egg-info/SOURCES.txt +16 -0
- vestacrystparser-0.1.0/vestacrystparser.egg-info/dependency_links.txt +1 -0
- vestacrystparser-0.1.0/vestacrystparser.egg-info/requires.txt +11 -0
- vestacrystparser-0.1.0/vestacrystparser.egg-info/top_level.txt +1 -0
|
@@ -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 @@
|
|
|
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.")
|