"""
ブラベー格子の単位格子と格子点を3Dで可視化するスクリプト。
このスクリプトは、指定された格子定数と結晶系に基づいて、
ブラベー格子の単位格子、格子点、格子ベクトル、およびオプションで補助線や基本格子を3Dで描画します。
Matplotlibを使用して3Dプロットを生成し、結果を画像ファイルとして保存し、表示します。
関連リンク: :doc:`draw_bravais_lattice_usage`
"""
import sys
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import Axes3D, proj3d
# 始点の初期値
elev = 5.90
azim = -67.55
draw_lattice_vectors = True
draw_support_lines = True
draw_primitive_cell = False
# ブラべー格子
lattice_vector_color = 'blue'
axis_arrow_color = 'blue'
axis_label_font_size = 24
# 格子点
point_color = 'red'
point_size = 500
#cell = 'SC'
#cell = 'ST'
#cell = 'SO'
#cell = 'SR'
#cell = 'SH'
#cell = 'FC'
#cell = 'FO'
cell = 'BC'
#cell = 'BT'
#cell = 'BO'
#cell = 'CO'
#cell = 'SM'
#cell = 'STri'
#cell = 'CM'
[ドキュメント]
class Arrow3D(FancyArrowPatch):
"""
MatplotlibのFancyArrowPatchを継承し、3D空間に矢印を描画するためのクラス。
このクラスは、3D座標の矢印を2Dのスクリーン座標に投影し、
Matplotlibの3D軸上に適切に描画することを可能にします。
"""
def __init__(self, xs, ys, zs, *args, **kwargs):
"""
Arrow3Dオブジェクトを初期化します。
:param xs: 矢印のX座標 (開始点, 終了点)。
:type xs: list[float]
:param ys: 矢印のY座標 (開始点, 終了点)。
:type ys: list[float]
:param zs: 矢印のZ座標 (開始点, 終了点)。
:type zs: list[float]
:param args: FancyArrowPatchに渡される追加の位置引数。
:param kwargs: FancyArrowPatchに渡される追加のキーワード引数。
"""
super().__init__((0, 0), (0, 0), *args, **kwargs)
self._verts3d = xs, ys, zs
[ドキュメント]
def draw(self, renderer):
"""
矢印をレンダラーに描画します。
3D座標を2Dスクリーン座標に変換し、変換された座標に基づいて矢印を描画します。
:param renderer: 描画に使用されるレンダラーオブジェクト。
:type renderer: matplotlib.backend_bases.RendererBase
"""
xs3d, ys3d, zs3d = self._verts3d
proj = self.axes.get_proj()
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, proj)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
super().draw(renderer)
[ドキュメント]
def do_3d_projection(self, renderer=None):
"""
3D投影を計算し、描画順序のためのZ深度を返します。
矢印の3D座標を2Dスクリーン座標に変換し、最小のZ深度を返して、
Matplotlibの3Dレンダリングにおける描画順序を適切に処理します。
:param renderer: 描画に使用されるレンダラーオブジェクト(オプション)。
:type renderer: matplotlib.backend_bases.RendererBase or None
:returns: 変換されたZ座標の最小値。
:rtype: float
"""
xs3d, ys3d, zs3d = self._verts3d
proj = self.axes.get_proj()
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, proj)
self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
return np.min(zs)
[ドキュメント]
def draw_vector(ax, vec, color, label, fontsize):
"""
指定された3Dベクトルを矢印としてMatplotlibの3D軸に描画します。
矢印は原点(0,0,0)から指定されたベクトルまで描画され、
ベクトルの先端にはラベルが表示されます。
:param ax: 矢印を描画する3D軸オブジェクト。
:type ax: mpl_toolkits.mplot3d.axes3d.Axes3D
:param vec: 矢印の終点となる3Dベクトル。
:type vec: numpy.ndarray
:param color: 矢印とラベルの色。
:type color: str
:param label: 矢印のラベル文字列。
:type label: str
:param fontsize: ラベルのフォントサイズ。
:type fontsize: int
:returns: なし
"""
arrow = Arrow3D([0, vec[0]], [0, vec[1]], [0, vec[2]],
mutation_scale=20, lw=2, arrowstyle="-|>", color=color)
ax.add_artist(arrow)
ax.text(vec[0] * 1.05, vec[1] * 1.05, vec[2] * 1.05, label, fontsize=fontsize, color=color)
[ドキュメント]
def lattice_vectors(a, b, c, alpha, beta, gamma):
"""
単位格子の格子定数から3つの基本格子ベクトルを計算します。
計算は、a軸をx軸に沿わせ、b軸をxy平面に置き、c軸をz軸正方向に向ける形で
座標系を設定して行われます。
:param a: 格子定数aの長さ。
:type a: float
:param b: 格子定数bの長さ。
:type b: float
:param c: 格子定数cの長さ。
:type c: float
:param alpha: 角度α(bとcの間の角度、度数)。
:type alpha: float
:param beta: 角度β(aとcの間の角度、度数)。
:type beta: float
:param gamma: 角度γ(aとbの間の角度、度数)。
:type gamma: float
:returns: 3つの基本格子ベクトル (va, vb, vc)。
:rtype: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]
"""
alpha_r, beta_r, gamma_r = np.radians([alpha, beta, gamma])
va = np.array([a, 0, 0])
vb = np.array([b * np.cos(gamma_r), b * np.sin(gamma_r), 0])
cx = c * np.cos(beta_r)
cy = c * (np.cos(alpha_r) - np.cos(beta_r) * np.cos(gamma_r)) / np.sin(gamma_r)
cz = np.sqrt(c**2 - cx**2 - cy**2)
vc = np.array([cx, cy, cz])
return va, vb, vc
[ドキュメント]
def set_equal_aspect(ax, points):
"""
Matplotlibの3Dプロットの軸のアスペクト比を等しく設定します。
描画される全ての点の範囲に基づいて、x, y, z軸の表示範囲を調整し、
立体が歪まないように真のアスペクト比を維持します。
:param ax: 軸のアスペクト比を設定する3D軸オブジェクト。
:type ax: mpl_toolkits.mplot3d.axes3d.Axes3D
:param points: 描画される全ての点のNumpy配列。
:type points: numpy.ndarray
:returns: なし
"""
xlim = [np.min(points[:, 0]), np.max(points[:, 0])]
ylim = [np.min(points[:, 1]), np.max(points[:, 1])]
zlim = [np.min(points[:, 2]), np.max(points[:, 2])]
max_range = max(
xlim[1] - xlim[0],
ylim[1] - ylim[0],
zlim[1] - zlim[0]
) / 2
mid_x = np.mean(xlim)
mid_y = np.mean(ylim)
mid_z = np.mean(zlim)
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)
ax.set_box_aspect([1, 1, 1])
[ドキュメント]
def draw_unit_cell_with_lattice(
a, b, c, alpha, beta, gamma,
lattice_points=None,
dashed_lines=None,
basis_vectors=None
):
"""
指定された格子定数と格子点を用いて、ブラベー単位格子を3Dで描画します。
この関数は、単位格子の辺、頂点、格子ベクトル、格子点、オプションで補助線、
および基本格子を描画し、結果を画像ファイルとして保存した後、画面に表示します。
ユーザーがプロットを操作すると、現在の視点角度 (elev, azim) がコンソールに出力されます。
:param a: 格子定数aの長さ。
:type a: float
:param b: 格子定数bの長さ。
:type b: float
:param c: 格子定数cの長さ。
:type c: float
:param alpha: 角度α(bとcの間の角度、度数)。
:type alpha: float
:param beta: 角度β(aとcの間の角度、度数)。
:type beta: float
:param gamma: 角度γ(aとbの間の角度、度数)。
:type gamma: float
:param lattice_points: 格子点を表す係数のリスト。各要素は `[u, v, w]` 形式で `u*va + v*vb + w*vc` と計算されます。
Noneの場合、格子点は描画されません。
:type lattice_points: list[list[float]] or None
:param dashed_lines: 補助線(破線)の始点と終点を表す係数のリスト。各要素は `([u1, v1, w1], [u2, v2, w2])` 形式です。
Noneの場合、補助線は描画されません。
:type dashed_lines: list[tuple[tuple[float, float, float], tuple[float, float, float]]] or None
:param basis_vectors: 基本格子ベクトルを表す係数のリスト。各要素は `[u, v, w]` 形式です。
Noneの場合、基本格子は描画されません。
:type basis_vectors: list[list[float]] or None
:returns: なし
"""
va, vb, vc = lattice_vectors(a, b, c, alpha, beta, gamma)
origin = np.array([0, 0, 0])
corners = [
origin,
va,
vb,
vc,
va + vb,
va + vc,
vb + vc,
va + vb + vc
]
corners = np.array(corners)
edges = [
(0, 1), (0, 2), (0, 3),
(1, 4), (1, 5),
(2, 4), (2, 6),
(3, 5), (3, 6),
(4, 7), (5, 7), (6, 7)
]
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.view_init(elev=elev, azim=azim)
def on_draw(event):
elev_now = ax.elev
azim_now = ax.azim
print(f"elev: {elev_now:.2f}, azim: {azim_now:.2f}")
fig.canvas.mpl_connect('draw_event', on_draw)
# 単位格子の辺
for i, j in edges:
ax.plot(*zip(corners[i], corners[j]), color='black', linewidth=0.5)
# 単位格子の頂点
ax.scatter(corners[:, 0], corners[:, 1], corners[:, 2], color='black', s=20)
# 格子ベクトル
if draw_lattice_vectors:
for vec, label in zip([va, vb, vc], ['a', 'b', 'c']):
draw_vector(ax, vec, lattice_vector_color, label, axis_label_font_size)
# 格子点の描画
real_points = []
if lattice_points:
real_points = [p[0] * va + p[1] * vb + p[2] * vc for p in lattice_points]
real_points = np.array(real_points)
ax.scatter(real_points[:, 0], real_points[:, 1], real_points[:, 2],
color=point_color, s=point_size)
# 補助線(鎖線)
if draw_support_lines and dashed_lines:
for p1, p2 in dashed_lines:
pt1 = p1[0] * va + p1[1] * vb + p1[2] * vc
pt2 = p2[0] * va + p2[1] * vb + p2[2] * vc
ax.plot(*zip(pt1, pt2), color='gray', linestyle='dashed', linewidth=0.8)
# 基本格子
if draw_primitive_cell and basis_vectors:
for vec in basis_vectors:
v = vec[0] * va + vec[1] * vb + vec[2] * vc
ax.quiver(0, 0, 0, *v, color='green', linewidth=1.5, arrow_length_ratio=0.2)
if len(basis_vectors) == 3:
bv1 = basis_vectors[0][0] * va + basis_vectors[0][1] * vb + basis_vectors[0][2] * vc
bv2 = basis_vectors[1][0] * va + basis_vectors[1][1] * vb + basis_vectors[1][2] * vc
bv3 = basis_vectors[2][0] * va + basis_vectors[2][1] * vb + basis_vectors[2][2] * vc
base_points = [
origin,
bv1,
bv2,
bv3,
bv1 + bv2,
bv1 + bv3,
bv2 + bv3,
bv1 + bv2 + bv3
]
base_edges = [
(0, 1), (0, 2), (0, 3),
(1, 4), (1, 5),
(2, 4), (2, 6),
(3, 5), (3, 6),
(4, 7), (5, 7), (6, 7)
]
for i, j in base_edges:
ax.plot(*zip(base_points[i], base_points[j]),
color='lightgreen', linewidth=1.2)
for vec, label in zip([bv1, bv2, bv3], ["a'", "b'", "c'"]):
ax.text(*vec, label, fontsize=12, color='green')
# 軸や背景の調整
ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([])
ax.set_xticklabels([]); ax.set_yticklabels([]); ax.set_zticklabels([])
ax.set_xlabel(''); ax.set_ylabel(''); ax.set_zlabel('')
ax.grid(False)
ax.xaxis.pane.set_visible(False)
ax.yaxis.pane.set_visible(False)
ax.zaxis.pane.set_visible(False)
ax.xaxis.line.set_color((0, 0, 0, 0))
ax.yaxis.line.set_color((0, 0, 0, 0))
ax.zaxis.line.set_color((0, 0, 0, 0))
ax.set_facecolor((1, 1, 1, 0))
all_points = np.vstack([corners] + ([real_points] if lattice_points else []))
set_equal_aspect(ax, all_points)
plt.tight_layout()
plt.savefig("bravais_cell_with_basis_vectors.png",
dpi=300, bbox_inches='tight', transparent=True)
plt.show()
[ドキュメント]
def main():
"""
プログラムのエントリポイント。コマンドライン引数を解析し、ブラベー単位格子の描画を実行します。
コマンドライン引数に基づいて、描画する結晶系と表示オプション(格子ベクトル、補助線、基本格子)を
設定し、`draw_unit_cell_with_lattice`関数を呼び出して単位格子を描画します。
引数が指定されない場合は、デフォルト値が使用されます。
使用方法:
python draw_bravais_lattice.py [cell_type] [draw_lattice_vectors] [draw_support_lines] [draw_primitive_cell]
- cell_type (str): 描画する結晶系 ('SC', 'ST', 'SO', 'SR', 'SH', 'FC', 'FO', 'BC', 'BT', 'BO', 'CO', 'SM', 'STri', 'CM'など)。
- draw_lattice_vectors (int): 格子ベクトルを描画するか (1: はい, 0: いいえ)。
- draw_support_lines (int): 補助線を描画するか (1: はい, 0: いいえ)。
- draw_primitive_cell (int): 基本格子を描画するか (1: はい, 0: いいえ)。
:returns: なし
"""
global cell, draw_lattice_vectors, draw_support_lines, draw_primitive_cell
argv = sys.argv
nargs = len(argv)
if nargs > 1:
cell = argv[1]
if nargs > 2:
draw_lattice_vectors = int(argv[2])
if nargs > 3:
draw_support_lines = int(argv[3])
if nargs > 4:
draw_primitive_cell = int(argv[4])
print()
print(f"{cell=}")
print(f"{draw_lattice_vectors=}")
print(f"{draw_support_lines=}")
print(f"{draw_primitive_cell=}")
if cell[0] == 'S': # Simple (単純)
if cell[1] == 'C': # Simple Cubic (単純立方)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
elif cell[1:] == 'T': # Simple Tetragonal (単純正方)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 7.0, 90.0, 90.0, 90.0
elif cell[1] == 'O': # Simple Orthorhombic (単純斜方)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
elif cell[1] == 'H': # Simple Hexagonal (単純六方)
a, b, c, alpha, beta, gamma = 7.0, 7.0, 5.0, 90.0, 90.0, 120.0
elif cell[1] == 'R': # Simple Rhombohedral (単純菱面体)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 70.0, 70.0, 70.0
elif cell[1] == 'M': # Simple Monoclinic (単純単斜)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 110.0, 90.0
elif cell[1:] == 'Tri': # Simple Triclinic (単純三斜)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 80.0, 110.0, 60.0
lattice_points = [
[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
[0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
]
dashed_lines = []
basis_vectors = []
elif cell[0] == 'F': # Face-centered (面心)
if cell[1] == 'C': # Face-centered Cubic (面心立方)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
elif cell[1] == 'O': # Face-centered Orthorhombic (面心斜方)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
lattice_points = [
[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
[0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
[0.0, 0.5, 0.5],
[0.5, 0.0, 0.5],
[0.5, 0.5, 0.0],
[1.0, 0.5, 0.5],
[0.5, 1.0, 0.5],
[0.5, 0.5, 1.0],
]
dashed_lines = [
[(0, 0, 0), (1, 1, 0)],
[(0, 1, 0), (1, 0, 0)],
[(0, 0, 1), (1, 1, 1)],
[(0, 1, 1), (1, 0, 1)],
[(0, 0, 0), (1, 0, 1)],
[(0, 0, 1), (1, 0, 0)],
[(0, 1, 0), (1, 1, 1)],
[(0, 1, 1), (1, 1, 0)],
[(0, 0, 0), (0, 1, 1)],
[(0, 1, 0), (0, 0, 1)],
[(1, 0, 0), (1, 1, 1)],
[(1, 1, 0), (1, 0, 1)],
]
basis_vectors = [
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[ 0.5, 0.5, -0.5]
]
elif cell[0] == 'B': # Body-centered (体心)
if cell[1] == 'C': # Body-centered Cubic (体心立方)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 5.0, 90.0, 90.0, 90.0
elif cell[1] == 'T': # Body-centered Tetragonal (体心正方)
a, b, c, alpha, beta, gamma = 5.0, 5.0, 7.0, 90.0, 90.0, 90.0
elif cell[1] == 'O': # Body-centered Orthorhombic (体心斜方)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
lattice_points = [
[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
[0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
[0.5, 0.5, 0.5]
]
dashed_lines = [
[(0, 0, 0), (1, 1, 1)],
[(1, 0, 0), (0, 1, 1)],
[(0, 1, 0), (1, 0, 1)],
[(0, 0, 1), (1, 1, 0)],
[(0.5, 0.5, 0.5), (0, 0, 0)],
[(0.5, 0.5, 0.5), (1, 1, 1)]
]
basis_vectors = [
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[ 0.5, 0.5, -0.5]
]
elif cell[0] == 'C': # Base-centered (底心)
if cell[1] == 'O': # Base-centered Orthorhombic (底心斜方)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 90.0, 90.0
lattice_points = [
[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
[0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
[0.5, 0.5, 0.0],
[0.5, 0.5, 1.0]
]
dashed_lines = [
[(0, 0, 0), (1, 1, 0)],
[(0, 1, 0), (1, 0, 0)],
[(0, 0, 1), (1, 1, 1)],
[(0, 1, 1), (1, 0, 1)],
]
basis_vectors = [
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[ 0.5, 0.5, -0.5]
]
elif cell[1] == 'M': # Base-centered Monoclinic (底心単斜)
a, b, c, alpha, beta, gamma = 5.0, 7.0, 6.0, 90.0, 110.0, 90.0
lattice_points = [
[0, 0, 0], [1, 0, 0], [0, 1, 0], [0, 0, 1],
[0, 1, 1], [1, 0, 1], [1, 1, 0], [1, 1, 1],
[0.5, 0.0, 0.5],
[0.5, 1.0, 0.5]
]
dashed_lines = [
[(0, 0, 0), (1, 0, 1)],
[(0, 0, 1), (1, 0, 0)],
[(0, 1, 0), (1, 1, 1)],
[(0, 1, 1), (1, 1, 0)],
]
basis_vectors = [
[-0.5, 0.5, 0.5],
[ 0.5, -0.5, 0.5],
[ 0.5, 0.5, -0.5]
]
else:
print(f"\nError: Invalid cell type [{cell}]\n")
return
draw_unit_cell_with_lattice(
a=a, b=b, c=c, alpha=alpha, beta=beta, gamma=gamma,
lattice_points=lattice_points,
dashed_lines=dashed_lines,
basis_vectors=basis_vectors
)
if __name__ == "__main__":
main()