"""
単位格子を描画するスクリプト。
概要:
結晶学における単位格子を3Dで可視化する機能を提供します。
詳細説明:
任意の格子定数と角度から単位格子の形状を計算し、Matplotlibを使用して描画します。
基本ベクトル(a, b, c)の描画には、パースペクティブを考慮したカスタム3D矢印クラスを使用します。
関連リンク: :doc:`draw_unit_cell_usage`
"""
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
[ドキュメント]
class Arrow3D(FancyArrowPatch):
"""
Matplotlibの3Dプロットでカスタムの3D矢印を描画するためのクラス。
詳細説明: `FancyArrowPatch` を継承し、3D空間内の座標を2Dスクリーン座標に変換して描画することで、
常に適切なパースペクティブで矢印が表示されるようにします。
"""
def __init__(self, xs, ys, zs, *args, **kwargs):
"""
Arrow3Dクラスのコンストラクタ。
:param xs: list[float]またはtuple[float]: 矢印のX座標のリストまたはタプル。
:param ys: list[float]またはtuple[float]: 矢印のY座標のリストまたはタプル。
:param zs: list[float]またはtuple[float]: 矢印のZ座標のリストまたはタプル。
: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: matplotlib.backend_bases.RendererBase: Matplotlibのレンダラーオブジェクト。
:returns: None
"""
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プロジェクションを実行し、矢印の深度を計算します。
詳細説明: 3D座標を2Dスクリーン座標に変換し、Z軸方向の最小値(深度)を返すことで、
3Dビューにおけるオブジェクトの描画順序を適切に管理します。
:param renderer: matplotlib.backend_bases.RendererBase: Matplotlibのレンダラーオブジェクト(オプション)。
:returns: numpy.float: 矢印のZ軸方向の最小値(深度)。
"""
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空間にベクトルと対応するラベルを描画します。
詳細説明: `Arrow3D` クラスを利用して、原点から指定されたベクトルまでの矢印を描画し、
ベクトルの先端にラベルを配置します。
:param ax: matplotlib.axes.Axes: 3DプロットのAxesオブジェクト。
:param vec: numpy.ndarray: 描画するベクトルの3D座標([x, y, z])。
:param color: str: 矢印とラベルの色。
:param label: str: ベクトルに付けるラベル。
:param fontsize: int: ラベルのフォントサイズ。
:returns: None
"""
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):
"""
格子定数と角度から基本格子ベクトルを計算します。
詳細説明: 結晶学で用いられる格子定数 (a, b, c) と軸間角 (alpha, beta, gamma) を用いて、
直交座標系における3つの基本格子ベクトル (va, vb, vc) を計算します。
:param a: float: 格子定数aの長さ。
:param b: float: 格子定数bの長さ。
:param c: float: 格子定数cの長さ。
:param alpha: float: b軸とc軸の間の角度(度数)。
:param beta: float: a軸とc軸の間の角度(度数)。
:param gamma: float: a軸とb軸の間の角度(度数)。
:returns: tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: 3つの基本格子ベクトル (va, vb, vc)。各ベクトルは `[x, y, z]` の形式の `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(max(0, c**2 - cx**2 - cy**2)) # 負の平方根を避けるための安全策
vc = np.array([cx, cy, cz])
return va, vb, vc
[ドキュメント]
def set_equal_aspect(ax, points):
"""
3Dプロットのアスペクト比を均等に設定し、描画範囲を調整します。
詳細説明: 描画される点の最大・最小座標に基づいて、X, Y, Z軸の表示範囲を同じにします。
:param ax: matplotlib.axes.Axes: 3DプロットのAxesオブジェクト。
:param points: numpy.ndarray: 描画される全ての点のNumpy配列。形状は `(N, 3)`。
:returns: None
"""
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_plot(a, b, c, alpha, beta, gamma, elev=5.90, azim=-67.55, fontsize=24, color='blue'):
"""
指定された格子定数と角度を持つ単位格子を3Dで描画します。
詳細説明: `lattice_vectors` 関数を使用して基本格子ベクトルを計算し、それらから単位格子の8つの頂点を生成します。
その後、Matplotlibを用いて単位格子の辺、頂点、および基本格子ベクトルを3D空間に描画します。
"""
va, vb, vc = lattice_vectors(a, b, c, alpha, beta, gamma)
origin = np.array([0, 0, 0])
points = [
origin,
va,
vb,
vc,
va + vb,
va + vc,
vb + vc,
va + vb + vc
]
points = np.array(points)
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):
curr_elev = ax.elev
curr_azim = ax.azim
print(f"elev: {curr_elev:.2f}, azim: {curr_azim:.2f}")
fig.canvas.mpl_connect('draw_event', on_draw)
# 単位格子の辺(細い黒線)
for i,j in edges:
ax.plot(*zip(points[i], points[j]), color='black', linewidth=0.5)
# 頂点(黒い小さな●)
ax.scatter(points[:,0], points[:,1], points[:,2], color='black', s=20)
# 基本ベクトル(太い矢印)とラベル
for vec, label in zip([va, vb, vc], ['a', 'b', 'c']):
draw_vector(ax, vec, color, label, fontsize)
# グラフの見た目を整える
ax.set_axis_off()
ax.grid(False)
ax.xaxis.pane.set_visible(False)
ax.yaxis.pane.set_visible(False)
ax.zaxis.pane.set_visible(False)
ax.set_facecolor((1,1,1,0)) # 背景透明
set_equal_aspect(ax, points)
plt.tight_layout()
plt.savefig("unit_cell.png", dpi=300, bbox_inches='tight', transparent=True)
plt.show()
[ドキュメント]
def main():
"""
メインの実行ルーチン。特定の格子定数で単位格子を描画します。
"""
# 始点の初期値やフォントサイズなどの設定
params = {
'a': 5.0, 'b': 5.5, 'c': 4.5,
'alpha': 80, 'beta': 70, 'gamma': 100,
'elev': 5.90, 'azim': -67.55,
'fontsize': 24, 'color': 'blue'
}
print(f"Drawing unit cell with a={params['a']}, b={params['b']}, c={params['c']}")
print(f"Angles: alpha={params['alpha']}, beta={params['beta']}, gamma={params['gamma']}")
draw_unit_cell_plot(**params)
if __name__ == "__main__":
main()