閉じた輪郭を「弦」で正規化して、その断面の性質を数字にする層です。入力は (N, 2) の (x, y) 点列(閉輪郭)、出力は厚み分布・キャンバー線・前縁半径・後縁隙間、そして設計形状との偏差です。
翼型が典型ですが、原理は「細長い閉断面を弦に沿って測る」だけなので、送風ファンの羽根、羽根車、タービン翼、押し出し材、ガスケット、板金の断面にそのまま使えます。
12 op / 4 カテゴリ(numpy のみ。台帳は opsprofile.py、実体は profileops.py):
• synth(2) — profile_synth_naca4 / profile_perturb: 閉形式の翼型と、既知の量の欠陥。この族の真値の供給源。
• frame(3) — profile_chord_frame / profile_normalise / profile_resample: 弦の枠、正規化、等弧長の取り直し。
• measure(5) — profile_sides / profile_thickness / profile_camber / profile_leading_edge_radius / profile_trailing_edge_gap。
• compare(2) — profile_align / profile_deviation: 位置合わせと符号つき偏差。
2-D レジストリには輪郭を扱う op が 97 個ありました(*_xld の生成・選択・平滑化・モーメント・当てはめ、convex_hull、distance_transform …)。しかし:
| やりたいこと | 既存で近いもの | なぜ代わりにならないか |
|---|---|---|
| 弦に沿った厚み分布 | get_region_thickness | 領域の太さで、弦方向の分布ではない |
| キャンバー線 | —— | 無い |
| 前縁半径 | hx_fit_circle_contour | 円は当てられるが「どこが前縁か」を決める層が無い |
| 輪郭同士の符号つき偏差 | hx_dist_ellipse_contour / distance_transform | 前者は楕円限定、後者はラスタ |
つまり「形はいくらでも作れて選べるのに、断面として測る層が空いていた」。
import numpy as np
import profileops
# 1) 設計形状は閉形式から(真値が計算で出る)
ref = profileops.profile_synth_naca4("2412", n=801)
# 2) 既知の欠陥を注入して「測定品」を作る —— 検出力を測るために要る
measured = profileops.profile_perturb(ref, "thicken", amount=0.002)
# 3) 断面としての量
t = profileops.profile_thickness(ref, 401) # (x, 厚み)
cam = profileops.profile_camber(ref, 401) # (x, キャンバー)
r_le = profileops.profile_leading_edge_radius(ref) # 前縁半径
gap = profileops.profile_trailing_edge_gap(ref) # 後縁の開き
assert abs(np.max(t[:, 1]) - 0.12) < 5e-4 # 下 2 桁 = 最大厚み
assert abs(r_le - 1.1019 * 0.12 ** 2) < 0.05 * r_le # NACA の閉形式
assert gap > 1e-3 # 既定係数は後縁が開く
# 4) 設計形状との偏差 —— 注入した 0.002 がそのまま戻るか
dev = profileops.profile_deviation(measured, ref, n=400)
assert abs(dev["mean"] - 0.002) < 0.0002
# 5) 3 度回して 0.7 ずらしても、欠陥は消えない
a = np.radians(3.0)
rot = np.array([[np.cos(a), -np.sin(a)], [np.sin(a), np.cos(a)]])
moved = measured @ rot.T + np.array([0.7, -0.3])
assert abs(profileops.profile_deviation(moved, ref, n=400)["mean"] - 0.002) < 0.0002
# 6) 同じ形どうしなら偏差はほぼ 0(検査の床)
assert profileops.profile_deviation(ref, ref, n=400)["rms"] < 1e-5
Mermaid 図(ソース):
flowchart TD
N["profile_synth_naca4<br/>閉形式の設計形状"]
M["測定した輪郭<br/>(スキャン・撮影・CAD)"]
N --> PB["profile_perturb<br/>既知の欠陥を注入"]
PB --> M
M --> FR["profile_chord_frame<br/>弦・前縁・後縁"]
FR --> NO["profile_normalise<br/>弦長 1・前縁が原点"]
NO --> SI["profile_sides<br/>上面 / 下面"]
SI --> TH["profile_thickness"]
SI --> CA["profile_camber"]
NO --> LE["profile_leading_edge_radius"]
SI --> TE["profile_trailing_edge_gap"]
M --> AL["profile_align<br/>chord / rigid / none"]
N --> AL
AL --> DV["profile_deviation<br/>符号つき法線偏差"]
N --> DV
DV --> OUT["合否・工程管理<br/>ロット別ヒートマップ"]
TH --> OUT
CA --> OUT
LE --> OUT
| 量 | 閉形式 | 実測(NACA 2412、点数 801) |
|---|---|---|
| 最大厚み | 下 2 桁 / 100 = 0.12 | 0.12006(x = 0.300) |
| 最大キャンバー | 1 桁目 / 100 = 0.02 | 0.01882(x = 0.415)★定義差 |
| 前縁半径 | 1.1019 t² = 0.01587 | 0.01594(frac=0.001) |
| 後縁隙間 | 既定係数なら開く | 0.00390 |
公開表とも突き合わせてあります —— UIUC の naca2412.dat の上面 15 点と閉形式の差は最大 1.0e-4 翼弦。
対称翼(NACA 0012 など)のキャンバーは定義上 0 です。ここが 0 にならなければ、どこかで規約が破れています。
実際、最初の実装は 0.001257 を返しました。これは後縁の半隙間そのもの。原因は「弦 = 最も離れた 2 点」という素朴な定義で、後縁が開いていると角の片方を拾って弦が 0.07 度傾くことでした。有翼(2412)だけを見ていたら「キャンバーが 7 % 低い」で片づけていたはずです。
いまは後縁を隙間の中点に取り直してから前縁を決め直すので、対称翼で 0.00001 になります。
もう 1 つ、前縁と後縁の区別も直観が逆でした。「前縁は丸い = 曲率が大きい」と考えて 3 点円の曲率で判定したところ、上下面がほぼ接する後縁のほうが曲率が大きく出て、前縁と後縁が入れ替わりました。正しい弁別は「少し入ったところが太いほう」です。
鼻先は円ですが、少し離れると円ではありません。窓を広げると系統的に過大になります(NACA の閉形式 1.1019 t² と突き合わせた実測、点数 4001):
| frac | NACA 0012 | NACA 2412 | NACA 0021 | NACA 0008 |
|---|---:|---:|---:|---:|
| 閉形式 | 0.01587 | 0.01587 | 0.04859 | 0.00705 |
| 0.001(既定) | 0.01590 | 0.01594 | 0.04780 | 0.00730 |
| 0.005 | 0.01708 | 0.01716 | 0.04801 | 0.00870 |
| 0.01 | 0.01872 | 0.01880 | 0.04897 | 0.01042 |
| 0.03 | 0.02517 | 0.02526 | 0.05412 | 0.01688 |
既定を 0.03 にしていたころは閉形式の 1.6 倍を返していました(docstring には「1 % 以内」と書いてありましたが、それは確かめる前の推測でした)。厚い翼ほど鈍いので窓の影響が小さく、薄い翼ほど敏感です。
一様な厚み増(0.002)は、どの合わせ方でも平均 0.00199 として戻ります。3 度回して並進を掛けても同じ。ここは安心してよい。
問題は、欠陥が合わせの基準そのものに乗っているときです。前縁を 0.003 削った形を比べた実測:
| 合わせ方 | 最悪の偏差 | その位置 | 前縁側の最悪 |
|---|---:|---|---:|
| "chord"(既定) | -0.00283 | x = 1.000(後縁!) | -0.00096 |
| "rigid" | -0.00166 | x = 0.009 | -0.00166 |
| "none" | -0.00309 | x = 0.001 | -0.00309 |
前縁を削ると前縁の位置そのものが動くので、前縁と後縁を合わせる "chord" は損失を反対の端へ移し、前縁での落ち込みを 3 分の 1 に見せます。"rigid"(全点の最小二乗)は場所は正しく出しますが、ずれを全体へ散らして半分に見せます。**同じ座標系にある("none")ときだけ、注入した 0.003 がそのまま出ます**。
> 欠陥が基準に乗る場所(前縁・後縁・取り付け面)を検査するときは、合わせの自由度をその場所から外して取るか、測定側の座標系を先に決めて "none" で比べること。
相似(スケール推定)は提供していません —— 大きさの誤差そのものを吸収するからです。profile_align は常に scale = 1.0 を返します(推定していないことの明示)。
同じ形を自分自身と比べたときの偏差が、検出したい欠陥と同じ桁なら、その検査は何も見つけません。
最初の実装は rms 6.05e-4 でした(狙う欠陥 1e-3 と同じ桁)。原因は「合わせる前に取り方が揃っていなかった」こと —— 生の輪郭と取り直した輪郭では弦の枠(とくに後縁の中点)がわずかに違い、同一の形でも 0.02 度の回転と 7.8e-4 の並進が入っていました。比べる前に同じ取り方で取り直すようにして、床は 1.13e-6 になりました(535 倍)。
その結果、注入した欠陥はここまで測り返せます:
| 注入 | 量 | 測定 |
|---|---:|---|
| 一様な厚み増 | 0.002 | 平均 +0.001993 |
| 同上 | 0.0005 | 平均 +0.000497 |
| 同上 | 0.0001 | 平均 +0.000099 |
| 前縁侵食 | 0.003 | 最小 -0.00309(none) |
| うねり | 0.001 | ±0.00104(平均は打ち消し) |
| ねじれ | 0.5 度 | ±0.0018(平均は打ち消し) |
0.0001 翼弦は、弦 1 m の羽根なら 0.1 mm です。
profile_resample は等弧長です。NACA の生成は余弦分布(鼻先が密)なので、等弧長に直すと前縁の分解能が落ちます:
| 取り直しの点数 | 元形状からの rms |
|---:|---:|
| 150 | 5.4e-4 |
| 300 | 4.1e-4 |
| 600 | 1.7e-4 |
| 1200 | 1.1e-4 |
| 2400 | 2.0e-5 |
「取り直しても形は同じ」ではありません。比べる 2 本は同じ点数で取り直すこと(profile_deviation はそうしています)。
輪郭は既存の pairs((N, 2) の (x, y))です。この repo で pairs は「1 価の関数データ」(spectrum / MTF / ray_fan)と「多角形」(filled_polygon が食う)の両方に使われており、後者と同じ立場を採りました。
混ぜたら嘘になるかを確かめた結果:
• 当初は「1 価の関数を渡すと下面が取れないので拒否される」と考えていました。これは嘘でした —— 点列は閉じれば多角形になり、厚みも面積も出ます(指数関数の曲線が素通りしました)。
• 実際に効く判定は「もともと閉じているか」。端点間の隙間を形の広がりで割って見ます。NACA の開いた後縁は 0.25 %、閉じ忘れた曲線は 100 % 前後。閾値 20 % で分かれます。
型を分けると、97 個ある既存の輪郭 op(gen_region_contour_xld / smooth_contours / convex_hull 系 / filled_polygon)との接続が切れます。そこを切る損のほうが大きい、という判断です。
• 実データ —— UIUC 翼型データベース(1,625 形状、認証不要)。ファイル形式が Selig と Lednicer の 2 系統あり、見分けを誤ると翼型が砂時計状に交差した多角形になります(RAD コーパス fullseye_poc_datasets_corpus_v2 のノート 031 に判別法)。
• 3D へ —— 断面を積めば羽根になります。measure3d の OBB / 体積、SDF、点群位置合わせと繋がります。
• 流れへ —— 同じ翼型の流れ場は piv 族(ノート 062 の SPLEEN 翼列 PIV は実データで CC BY 4.0)。形と性能を同じ土俵に載せられるのがこの 2 族を並べた狙いです。