まえがき
プロペラの渦法のGUIソフトを引き続き作っています。空いた時間にやってるのでのんびりのんびり・・・
現在のところ渦法とLarabeeの設計法、Adkins & Liebeckの設計法(翼素運動量理論Blade Element Momentum theory:BEMとかとも呼ばれたりする)と設計した翼の性能解析を実装しました。基本的には以前に作ったものの移植です。
機能は整ってきたので公開しようとおもうのですが、クラス設計を見直したいのでソースが整ってきたら公開します。
そんな中
製作段階になると、設計できたプロペラの翼型を印刷したいという要求があります。作っている人達は大抵、翼型データベースから持ってきた翼型datファイルをCADに持ってきて印刷したりしてるかと思います。
設計結果からExcelなどで翼型datファイルを拡大、回転させてCADにインポートして印刷してたりするんでしょうか。
業務としてやるなら、CADについてるスクリプトで書くのが普通でしょうか。
そこで、いま作っているプロペラ設計ソフトに翼型印刷機能を付けようと計画してコード書いていました。
しかし、ソフトに組み込むような機能では無いなと思ったので、ここで翼型印刷機能だけ別ソフトにして公開しておきます。
他記事
鳥人間の現役の人が翼型印刷について頑張って書いた記事は以下のようなものがあります。
設計の為の言語としてOctaveを勧めてしまった経緯もあって細かい技術的な工夫をして作っているのがわかります。
C#で作ってみた
Visual Studio使ってC#によって翼型datファイルの読み込みと拡大縮小・回転、印刷機能を作ってみました。
これを元に機能を追加することによって汎用的なソフトになるかと思います。
元ネタとしては自分が昔いたサークルで作られていたAirFoilPrinterというソフトです。ソース読んだことないけど、多分これをブラッシュアップしたものな気がします
車輪の再発明すぎるものではありますが、ネット上に挙がってなさそうでしたのでブログにしてみました。
ソースだけ晒しておきますが、もし欲しい人がいればexe形式で公開します。
ポイントとしては
csvなどのカンマ区切りではなく、スペース区切りになっている翼型datファイルを読み込むために数字の部分だけをx座標y座標のコレクションに追加している点。
AeroFoilクラスにDrawメソッドを作って
画面への描写と印刷面への描写に対応しているところです。
ミリメートル単位にしているので解像度など気にしないで大きさを直接指定できます。
あとはGUIを作る部分なので大した意味はないです。
188行目などのグラフィックの単位をミリメートルにした点がポイントです。
e.Graphics.PageUnit = GraphicsUnit.Millimeter;
あと見てもらいたいのは後半のAeroFoilクラスの翼型読み込みのためのメソッドと描写のメソッドの部分です。
AeroFoilクラスのポイント部分だけ抽出すると以下です。全文は一番下にあります。
public int point_number = 0; //翼型の外形点の数 public List<float> x = new List<float>(); //翼型の元のx座標データ public List<float> y = new List<float>(); //翼型の元のy座標データ public List<float> draw_x = new List<float>(); //描写x座標 public List<float> draw_y = new List<float>(); //描写y座標 public string foilname; //翼型の名前 public float scale_factor = 100; //倍率 public float origin_x = 20, origin_y = 40; //原点 public float degree = 0; //取り付け角 public void ReadDatFile() { x.Clear(); y.Clear(); draw_x.Clear(); draw_y.Clear(); OpenFileDialog ofd1 = new OpenFileDialog(); ofd1.Filter = "翼型datファイル|*.dat"; if (ofd1.ShowDialog() == DialogResult.OK) { StreamReader sr = new StreamReader(ofd1.FileName, System.Text.Encoding.Default); foilname = sr.ReadLine(); string buffer = sr.ReadToEnd(); string[] buffer_line = buffer.Split('\n'); string[] buffer_x = new string[buffer_line.Length]; string[] buffer_y = new string[buffer_line.Length]; for (int i = 0; i < buffer_line.Length; i++) //datファイルの行の数だけ反復 { string[] buffer_xy = buffer_line[i].Split(' '); //スペースで分割 bool xy_flag = true; //x, yのどちらに代入するかのフラグ for (int j = 0; j < buffer_xy.Length; j++) //スペースも含めたxyの配列だけ反復 { if (buffer_xy[j] != "" && xy_flag == false) //buffer_xyからスペースは排除 { buffer_y[i] = buffer_xy[j]; y.Add(float.Parse(buffer_y[i])); //List<> yにyの値を追加 } if (buffer_xy[j] != "" && xy_flag == true) { buffer_x[i] = buffer_xy[j]; x.Add(float.Parse(buffer_x[i])); xy_flag = false; //フラグ反転 } } } point_number = x.Count(); sr.Close(); } } public void Draw(Graphics g) { draw_x = new List<float>(x); //xをbuffer_xにコピー draw_y = new List<float>(y); //xをbuffer_yにコピー //回転 float radian = (float)(-degree * Math.PI / 180.0); for (int i = 0; i < point_number; i++) { draw_x[i] = (float)(x[i] * Math.Cos(radian) - y[i] * Math.Sin(radian)); draw_y[i] = (float)(x[i] * Math.Sin(radian) + y[i] * Math.Cos(radian)); } //描写 Pen dp = new Pen(Color.Black, (float)0.1); //線種:黒 線幅0.1mm PointF[] foilpoints = new PointF[point_number]; for (int i = 0; i < point_number; i++) { foilpoints[i] = new PointF(draw_x[i] * scale_factor + origin_x, - draw_y[i] * scale_factor + origin_y); } if (foilpoints.Length != 0) { g.DrawCurve(dp, foilpoints); //スプライン曲線で描写 //g.DrawLines(dp, foilpoints); //直線で描写 } }
コードの全文は以下です。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Windows.Forms; using System.IO; using System.Drawing.Printing; using System.Printing; namespace AeroFoilPrint_Blog { class Form1 : Form { Button[] button = new Button[2]; Label[] label = new Label[2]; NumericUpDown[] numericUpDown = new NumericUpDown[2]; PrintDocument printDocument; PrintPreviewDialog printPreviewDialog; AeroFoil aeroFoil = new AeroFoil(); [STAThread] public static void Main() { Application.Run(new Form1()); } public Form1() { this.Text = "翼型印刷"; this.Width = 500; this.Height = 300; button[0] = new Button(); button[0].Location = new Point(20, 20); button[0].Text = "ファイル読み込み"; button[0].Width = 100; button[0].Parent = this; button[1] = new Button(); button[1].Location = new Point(20, 50); button[1].Text = "翼型印刷"; button[1].Width = 100; button[1].Parent = this; label[0] = new Label(); label[0].Location = new Point(150, 20); label[0].Text = "サイズ[mm]"; label[0].Width = 70; label[0].Parent = this; label[1] = new Label(); label[1].Location = new Point(150, 50); label[1].Text = "角度[deg]"; label[1].Width = 70; label[1].Parent = this; numericUpDown[0] = new NumericUpDown(); numericUpDown[0].Value = 100; aeroFoil.scale_factor = (float)numericUpDown[0].Value; numericUpDown[0].Minimum = 0; numericUpDown[0].Maximum = 3000; numericUpDown[0].DecimalPlaces = 2; numericUpDown[0].Location = new Point(220, 20); numericUpDown[0].Parent = this; numericUpDown[1] = new NumericUpDown(); numericUpDown[1].Minimum = -180; numericUpDown[1].Maximum = 180; numericUpDown[1].DecimalPlaces = 2; numericUpDown[1].Location = new Point(220, 50); numericUpDown[1].Parent = this; button[0].Click += new EventHandler(Form1_Click); button[1].Click += new EventHandler(Form1_Click); numericUpDown[0].ValueChanged += new EventHandler(Form1_ValueChanged); numericUpDown[1].ValueChanged += new EventHandler(Form1_ValueChanged); this.Paint +=new PaintEventHandler(Form1_Paint); printDocument = new PrintDocument(); printDocument.PrintPage += new PrintPageEventHandler(printDocument_PrintPage); } void Form1_Click(object sender, EventArgs e) { if (sender == button[0]) { aeroFoil.scale_factor = (float)numericUpDown[0].Value; aeroFoil.ReadDatFile(); this.Invalidate(); } if (sender == button[1]) { printPreviewDialog = new PrintPreviewDialog(); printPreviewDialog.Document = printDocument; printPreviewDialog.ShowDialog(); } } void Form1_ValueChanged(object sender, EventArgs e) { if (sender == numericUpDown[0]) { aeroFoil.scale_factor = (float)numericUpDown[0].Value; this.Invalidate(); } if (sender == numericUpDown[1]) { aeroFoil.degree = (float)numericUpDown[1].Value; this.Invalidate(); } } void Form1_Paint(object sender, PaintEventArgs e) { Graphics g = e.Graphics; e.Graphics.PageUnit = GraphicsUnit.Millimeter; aeroFoil.Draw(g); aeroFoil.DrawFoilName(g); aeroFoil.DrawReferenceLine(g); } void printDocument_PrintPage(object sender, PrintPageEventArgs e) { e.Graphics.PageUnit = GraphicsUnit.Millimeter; Graphics g = e.Graphics; aeroFoil.Draw(g); aeroFoil.DrawFoilName(g); aeroFoil.DrawReferenceLine(g); } } public class AeroFoil { public int point_number = 0; //翼型の外形点の数 public List<float> x = new List<float>(); //翼型の元のx座標データ public List<float> y = new List<float>(); //翼型の元のy座標データ public List<float> draw_x = new List<float>(); //描写x座標 public List<float> draw_y = new List<float>(); //描写y座標 public string foilname; //翼型の名前 public float scale_factor = 100; //倍率 public float origin_x = 20, origin_y = 40; //原点 public float degree = 0; //取り付け角 public void ReadDatFile() { x.Clear(); y.Clear(); draw_x.Clear(); draw_y.Clear(); OpenFileDialog ofd1 = new OpenFileDialog(); ofd1.Filter = "翼型datファイル|*.dat"; if (ofd1.ShowDialog() == DialogResult.OK) { StreamReader sr = new StreamReader(ofd1.FileName, System.Text.Encoding.Default); foilname = sr.ReadLine(); string buffer = sr.ReadToEnd(); string[] buffer_line = buffer.Split('\n'); string[] buffer_x = new string[buffer_line.Length]; string[] buffer_y = new string[buffer_line.Length]; for (int i = 0; i < buffer_line.Length; i++) //datファイルの行の数だけ反復 { string[] buffer_xy = buffer_line[i].Split(' '); //スペースで分割 bool xy_flag = true; //x, yのどちらに代入するかのフラグ for (int j = 0; j < buffer_xy.Length; j++) //スペースも含めたxyの配列だけ反復 { if (buffer_xy[j] != "" && xy_flag == false) //buffer_xyからスペースは排除 { buffer_y[i] = buffer_xy[j]; y.Add(float.Parse(buffer_y[i])); //List<> yにyの値を追加 } if (buffer_xy[j] != "" && xy_flag == true) { buffer_x[i] = buffer_xy[j]; x.Add(float.Parse(buffer_x[i])); xy_flag = false; //フラグ反転 } } } point_number = x.Count(); sr.Close(); } } public void Draw(Graphics g) { draw_x = new List<float>(x); //xをbuffer_xにコピー draw_y = new List<float>(y); //xをbuffer_yにコピー //回転 float radian = (float)(-degree * Math.PI / 180.0); for (int i = 0; i < point_number; i++) { draw_x[i] = (float)(x[i] * Math.Cos(radian) - y[i] * Math.Sin(radian)); draw_y[i] = (float)(x[i] * Math.Sin(radian) + y[i] * Math.Cos(radian)); } //描写 Pen dp = new Pen(Color.Black, (float)0.1); //線種:黒 線幅0.1mm PointF[] foilpoints = new PointF[point_number]; for (int i = 0; i < point_number; i++) { foilpoints[i] = new PointF(draw_x[i] * scale_factor + origin_x, - draw_y[i] * scale_factor + origin_y); } if (foilpoints.Length != 0) { g.DrawCurve(dp, foilpoints); //スプライン曲線で描写 //g.DrawLines(dp, foilpoints); //直線で描写 } } //翼型の名前を描写 public void DrawFoilName(Graphics g) { Font font = new Font("MS UI Gothic", 10); g.DrawString(foilname + "\n" + scale_factor.ToString("F2") + "[mm]" + "\n" + degree.ToString("F1") + "[°]" , font, Brushes.Black, new PointF(10, 20)); } //基準線を描写 public void DrawReferenceLine(Graphics g) { g.DrawLine(new Pen(Brushes.Black, (float)0.1) , new PointF(origin_x, origin_y) , new PointF(origin_x + scale_factor, origin_y)); } } }