0. 导入公共工具¶
In [1]:
Copied!
import os
import sys
from pathlib import Path
os.environ.setdefault("MPLCONFIGDIR", str(Path("_sharpy_runs") / ".mplconfig"))
candidate_dirs = [
Path.cwd(),
Path.cwd() / "docs" / "reference-aircraft",
Path("docs/reference-aircraft"),
]
for candidate in candidate_dirs:
if (candidate / "ttail_hale_utils.py").exists():
sys.path.insert(0, str(candidate.resolve()))
break
else:
raise FileNotFoundError("没有找到 ttail_hale_utils.py。请从仓库根目录或 docs/reference-aircraft 目录运行本 Notebook。")
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML, display
import ttail_hale_utils as hale
hale.configure_plots()
import os
import sys
from pathlib import Path
os.environ.setdefault("MPLCONFIGDIR", str(Path("_sharpy_runs") / ".mplconfig"))
candidate_dirs = [
Path.cwd(),
Path.cwd() / "docs" / "reference-aircraft",
Path("docs/reference-aircraft"),
]
for candidate in candidate_dirs:
if (candidate / "ttail_hale_utils.py").exists():
sys.path.insert(0, str(candidate.resolve()))
break
else:
raise FileNotFoundError("没有找到 ttail_hale_utils.py。请从仓库根目录或 docs/reference-aircraft 目录运行本 Notebook。")
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML, display
import ttail_hale_utils as hale
hale.configure_plots()
1. 准备官方 T-Tail HALE 输入文件¶
prepare_hale_case 会从当前 SHARPy 安装里定位 cases/coupled/simple_HALE/generate_hale.py,复制到 _sharpy_runs/,然后在本地工作目录生成 .fem.h5、.aero.h5 和 .sharpy。这样不会改动 site-packages 里的官方文件。
In [2]:
Copied!
FORCE_REGENERATE = True
case_dir = hale.prepare_hale_case("01_reconstruction", force=FORCE_REGENERATE)
model = hale.read_model(case_dir)
print("case directory:", case_dir)
for suffix in [".fem.h5", ".aero.h5", ".sharpy"]:
path = case_dir / f"{hale.CASE_NAME}{suffix}"
print(path.name, "exists:", path.exists(), "size:", path.stat().st_size)
FORCE_REGENERATE = True
case_dir = hale.prepare_hale_case("01_reconstruction", force=FORCE_REGENERATE)
model = hale.read_model(case_dir)
print("case directory:", case_dir)
for suffix in [".fem.h5", ".aero.h5", ".sharpy"]:
path = case_dir / f"{hale.CASE_NAME}{suffix}"
print(path.name, "exists:", path.exists(), "size:", path.stat().st_size)
case directory: _sharpy_runs/ttail_hale_reference_aircraft/01_reconstruction/simple_HALE simple_HALE.fem.h5 exists: True size: 22552 simple_HALE.aero.h5 exists: True size: 65512 simple_HALE.sharpy exists: True size: 5421
2. 气动面和舵面清单¶
这个模型有 5 个气动面:左右主翼、垂尾、左右平尾。控制面有 2 个:平尾升降舵和垂尾方向舵。如下表所示:
In [3]:
Copied!
display(HTML(hale.html_table(hale.surface_summary(model))))
display(HTML(hale.html_table(hale.control_surface_summary(model))))
display(HTML(hale.html_table(hale.model_checks(model))))
display(HTML(hale.html_table(hale.surface_summary(model))))
display(HTML(hale.html_table(hale.control_surface_summary(model))))
display(HTML(hale.html_table(hale.model_checks(model))))
| surface id | name | m panels | span/y range [m] | height/z range [m] | control |
|---|---|---|---|---|---|
| 0 | right main wing | 4 | 15.7588 | 1.3681 | - |
| 1 | left main wing | 4 | 15.7588 | 1.3681 | - |
| 2 | vertical fin / rudder | 4 | 0.0000 | 2.5000 | rudder on vertical fin |
| 3 | right horizontal tail / elevator | 4 | 2.5000 | 0.0000 | elevator on horizontal tail |
| 4 | left horizontal tail / elevator | 4 | 2.5000 | 0.0000 | elevator on horizontal tail |
| control id | role | initial deflection [deg] | surfaces |
|---|---|---|---|
| 0 | elevator on horizontal tail | -2.0800 | right horizontal tail / elevator left horizontal tail / elevator |
| 1 | rudder on vertical fin | 0.0000 | vertical fin / rudder |
| check | passed |
|---|---|
| aerodynamic surfaces | yes |
| control surfaces | yes |
| finite structural coordinates | yes |
| finite chord values | no |
| right/left main wing span dominates tail | yes |
3. 三维总览¶
这张图把梁单元、气动面和控制面叠在一起。蓝色是主翼,灰色是机身,紫色是垂尾,青色是平尾,橙色/红色是控制面区域。
In [4]:
Copied!
traces = hale.aircraft_traces_from_model(model, show_controls=True)
points, labels = hale.aircraft_label_points(model)
traces.append(hale.label_trace(points, labels))
hale.show_plotly(
traces,
"T-Tail HALE reference aircraft: beams, aero surfaces and controls",
camera={"eye": {"x": 1.6, "y": -1.95, "z": 0.9}},
)
traces = hale.aircraft_traces_from_model(model, show_controls=True)
points, labels = hale.aircraft_label_points(model)
traces.append(hale.label_trace(points, labels))
hale.show_plotly(
traces,
"T-Tail HALE reference aircraft: beams, aero surfaces and controls",
camera={"eye": {"x": 1.6, "y": -1.95, "z": 0.9}},
)
4. 舵面聚焦¶
如果后面要做 trim,最容易出错的是控制面编号和物理舵面含义对不上。这里专门看尾翼区域:control_surface = 0 对应平尾升降舵,control_surface = 1 对应垂尾方向舵。
In [5]:
Copied!
control_traces = hale.aircraft_traces_from_model(model, show_controls=True)
hale.show_plotly(
control_traces,
"Control-surface focus: elevator and rudder regions",
camera={"eye": {"x": 1.35, "y": -1.7, "z": 0.75}},
)
control_traces = hale.aircraft_traces_from_model(model, show_controls=True)
hale.show_plotly(
control_traces,
"Control-surface focus: elevator and rudder regions",
camera={"eye": {"x": 1.35, "y": -1.7, "z": 0.75}},
)
5. 三视图检查¶
In [6]:
Copied!
fig, axes = plt.subplots(1, 3, figsize=(11.2, 3.4))
views = [
(0, 1, "top view: x-y", "x [m]", "y [m]"),
(0, 2, "side view: x-z", "x [m]", "z [m]"),
(1, 2, "front view: y-z", "y [m]", "z [m]"),
]
for ax, (i_axis, j_axis, title, xlabel, ylabel) in zip(axes, views):
for surface_id in range(len(model["aero"]["surface_m"])):
grid = hale.surface_grid_from_model(model, surface_id)["xyz"]
color = hale.COMPONENT_COLORS.get(surface_id, hale.COLORS["grey"])
for strip in grid:
ax.plot(strip[:, i_axis], strip[:, j_axis], color=color, linewidth=0.8, alpha=0.65)
for chord_line in np.swapaxes(grid, 0, 1):
ax.plot(chord_line[:, i_axis], chord_line[:, j_axis], color=color, linewidth=0.6, alpha=0.45)
coords = model["fem"]["coordinates"]
ax.scatter(coords[:, i_axis], coords[:, j_axis], s=6, color=hale.COLORS["dark"], alpha=0.6)
ax.set_title(title, loc="left")
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.axis("equal")
ax.grid(True, color=hale.COLORS["light_grey"], linewidth=0.6)
hale.finish_figure(fig)
fig, axes = plt.subplots(1, 3, figsize=(11.2, 3.4))
views = [
(0, 1, "top view: x-y", "x [m]", "y [m]"),
(0, 2, "side view: x-z", "x [m]", "z [m]"),
(1, 2, "front view: y-z", "y [m]", "z [m]"),
]
for ax, (i_axis, j_axis, title, xlabel, ylabel) in zip(axes, views):
for surface_id in range(len(model["aero"]["surface_m"])):
grid = hale.surface_grid_from_model(model, surface_id)["xyz"]
color = hale.COMPONENT_COLORS.get(surface_id, hale.COLORS["grey"])
for strip in grid:
ax.plot(strip[:, i_axis], strip[:, j_axis], color=color, linewidth=0.8, alpha=0.65)
for chord_line in np.swapaxes(grid, 0, 1):
ax.plot(chord_line[:, i_axis], chord_line[:, j_axis], color=color, linewidth=0.6, alpha=0.45)
coords = model["fem"]["coordinates"]
ax.scatter(coords[:, i_axis], coords[:, j_axis], s=6, color=hale.COLORS["dark"], alpha=0.6)
ax.set_title(title, loc="left")
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.axis("equal")
ax.grid(True, color=hale.COLORS["light_grey"], linewidth=0.6)
hale.finish_figure(fig)