📏

フリーエッジを取得する(C#)

2024/06/17に公開

フリーエッジとは?

  • シェル(2次元の面)の端で、他のシェルのエッジ(1次元の辺)と共有していないもの
  • この形状の
  • この部分

フリーエッジが取得できると何がうれしいのか

  • 例えば、剛性解析で固定点に依存しない全体剛性を見たいときに、部品形状の外周を拘束条件にする必要があります。この時、外周のセットノードをHyperMesh等のプリソフトで選択していくのは、面倒だしミスすることもあります。
  • フリーエッジの中に外周の節点(ノード)は含まれているので、そこからいらない節点を省いていくほうが簡単です。

今回やること

  • 1:メッシュモデルファイル(今回は、keywordの標準表記)を読み込み、
    節点、シェルの内容を記録する。
  • 2:指定したPIDのすべてのシェルから構成するエッジを作成する。
  • 3:エッジの内一つも重複していないエッジ(フリーエッジ)を取り出す

プログラムの説明

  • まず節点、エッジ、シェルとデータを保存するクラスを作成します。
    class NODE//節点クラス
    {
        public Vector3 Crd { get; set; }
        public NODE(Vector3 _Crd)
        {
            this.Crd = _Crd;
        }

    }
    class EAGE//エッジクラス、重複が判定できるようにEquals等をオーバーライド
    {
        public Vector3 Center { get; set; }
        public List<int> Nid { get; set; }
        public EAGE(Vector3 _Center,List<int> _Nid)
        {
            this.Center = _Center;
            this.Nid = _Nid;
        }
        public override bool Equals(object other)
        {
            if (other == null || this.GetType() != other.GetType())
            {
                return false;
            }
            var _other = other as EAGE;
            return this.Center.X - _other.Center.X < 0.001 &&
                   this.Center.Y - _other.Center.Y < 0.001 &&
                   this.Center.Z - _other.Center.Z < 0.001;
        }
        public override int GetHashCode()
        {
            return this.Center.X.GetHashCode() ^
                   this.Center.Y.GetHashCode() ^
                   this.Center.Z.GetHashCode();
        }
        public static bool operator == (EAGE lhs, EAGE rhs)
        {
            if (lhs is null)
            {
                if (rhs is null)
                {
                    return true;
                }

                // Only the left side is null.
                return false;
            }
            // Equals handles case of null on right side.
            return lhs.Equals(rhs);
        }

        public static bool operator !=(EAGE lhs, EAGE rhs) => !(lhs == rhs);
    }

    class SHELL//シェルクラス
    {
        public int Pid { get; set; }
        public List<int> Nid { get; set; }
        public SHELL(int _Pid, List<int> _Nid)
        {
            this.Pid = _Pid;
            this.Nid = _Nid;
        }
        
    }
    class CAE_DATA//データの保存用
    {
        public Dictionary<int, NODE>  Node { get; set; }
        public Dictionary<int, SHELL> Shell { get; set; }
        public CAE_DATA()
        {
            this.Node  = new Dictionary<int, NODE>();
            this.Shell = new Dictionary<int, SHELL>();
        }

    }
  • メッシュファイルを読み込んで保存する。
   class FILE_CAE
    {
        public CAE_DATA from_Key_File(string FileName)
        {
            var DATA = new CAE_DATA();
            //ファイルの内容で特定のキーワード(今回は"$")が書かれていない行を
            //一行ずつリストにする
            var Base = Read_File_Make_All_Line_without_keyword(FileName, "$");

            var NodeLine = Sepalete_Line(Base, "*NODE");//節点の行だけにする
            var ShellLine = Sepalete_Line(Base, "*ELEMENT_SHELL");//シェルだけの行にする

            DATA.Node = NodeList_make(NodeLine);//節点を保存する
            DATA.Shell = ShellList_make(ShellLine);//シェルを保存する
            
            return DATA;
        }
        
        private List<string> Read_File_Make_All_Line_without_keyword(string FileName, string Without_Keyword)
        {
            return File.ReadAllLines(FileName).Where(i=>!i.Contains(Without_Keyword)).ToList();
        }
        private List<string> Sepalete_Line(List<string>BaseLine,string Sepalate_keyword)
        {
            var temp = new List<string>();
            var JUG = false;
            foreach (var i in BaseLine)
            {
                if (i.Contains(Sepalate_keyword)){ JUG = true; }
                if (JUG)
                {
                    temp.Add(i);
                    if (i.Contains("*") && !i.Contains(Sepalate_keyword)) { break; }
                }
            }
            temp.RemoveAt(0);
            temp.RemoveAt(temp.Count-1);
            return temp;
        }
        private List<List<string>> Sepalete_Cloum(List<string> ORG, int Sepalate_num)
        {
            //文字列を文字数(Sepalate_num)で分割する。
            //SubstringAtCountは拡張メソッドです。下に内容を記載します。
            return ORG.Select(i => i.SubstringAtCount(Sepalate_num)).ToList();
        }
        private Dictionary<int,NODE> NodeList_make(List<string> ORG)
        {
            var temp = new Dictionary<int, NODE>();
            foreach (var i in Sepalete_Cloum(ORG,8))
            {
                var ID = int.Parse(i[0]);
                var X  = float.Parse(i[1] + i[2]);
                var Y  = float.Parse(i[3] + i[4]);
                var Z  = float.Parse(i[5] + i[6]);
                var Crd = new Vector3(X, Y, Z);
                temp.Add(ID,new NODE(Crd));
            }
            return temp;
        }
        private Dictionary<int, SHELL> ShellList_make(List<string> ORG)
        {
            var temp = new Dictionary<int, SHELL>();
            foreach (var i in Sepalete_Cloum(ORG, 8))
            {
                var ID  = int.Parse(i[0]);
                var PID = int.Parse(i[1]);
                i.RemoveRange(0, 2);
                
                var NID=i.Select(j => int.Parse(j)).ToList();
                temp.Add(ID, new SHELL(PID,NID));
            }
            return temp;
        }
        
    }
    //拡張メソッド
    public static class String_Expension
    {
        public static List<string> SubstringAtCount(this string self, int count)
        {
            //  拡張メソッド 文字列を文字数で分割する 例: String.SubstringAtCount(5) 戻り値:String型配列
            var result = new List<string>();
            var length = (int)Math.Ceiling((double)self.Length / count);

            for (int i = 0; i < length; i++)
            {
                int start = count * i;
                if (self.Length <= start)
                {
                    break;
                }
                if (self.Length < start + count)
                {
                    result.Add(self.Substring(start));
                }
                else
                {
                    result.Add(self.Substring(start, count));
                }
            }

            return result;
        }
    }
  • 保存したデータからエッジを作成し、そこからフリーエッジを取得する
    class FreeEage
    {
        public List<EAGE> FreeEage_Make(CAE_DATA DATA,int TargetPID)
        {
            //TargetPIDのシェルからエッジを作成する
            var EageList = EageList_make(DATA,TargetPID);
            //処理速度を上げるため、重複したエッジを検索する範囲を分割する
            var sepalete_List = Sepalete_Area(EageList,1000);
            
            return Get_Free_Eage(sepalete_List);//フリーエッジを取得する
        }
        private List< EAGE> EageList_make(CAE_DATA DATA,int TargetPID)
        {
            return DATA.Shell.Where(i=>i.Value.Pid==TargetPID).SelectMany(i => Eage_Nid_Make(DATA, i.Value)).ToList();
        }
        private List<EAGE> Eage_Nid_Make(CAE_DATA DATA,SHELL E)
        {
            var temp = new List<EAGE>();
            //keyword形式では、三角形シェルの節点IDの4番目は、3番目と同じ値になっているため
            //下の方法でシェルが三角か四角かが判別できる
            var num = E.Nid[2]==E.Nid[3] ? 3 : 4;
            var E_NID = E.Nid.GetRange(0, num);
            E_NID.Add(E.Nid[0]);
            for (var i=0;i<E_NID.Count()-1;i++)
            {
                var NID=new List<int> { E_NID[i], E_NID[i+1] };
                var Crd1 = DATA.Node[NID[0]].Crd;
                var Crd2 = DATA.Node[NID[1]].Crd;
                var Center = Vector3.Divide(Vector3.Add(Crd1, Crd2), 2);
                temp.Add(new EAGE(Center, NID));
            }
            return temp;
        }
        private List<EAGE> Get_Free_Eage(List<List<EAGE>> temp)
        {
            var Free_Eage = new List<EAGE>();
            foreach (var i in temp)
            {
                var distinct = i.Distinct();
                foreach (var q in distinct)
                {
                    var temp_List = i.Where(j => j==q).ToList();
                    if (temp_List.Count() == 1)
                    {
                        Free_Eage.Add(temp_List[0]);
                    }
                }
            }

            return Free_Eage;
        }
        private List<List<EAGE>> Sepalete_Area(List<EAGE> temp,int num)
        {
            var Max = temp.Max(i => i.Center.Y)+1;
            var Min = temp.Min(i => i.Center.Y)-1;
            var dis = (Max - Min) / num;
            var temp2 = new List<List<EAGE>>();

            for (var i = 0; i <= num; i++)
            {
                temp2.Add(temp.Where(j => j.Center.Y >= Min + i * dis &&
                j.Center.Y < Min + (i + 1) * dis).ToList());
            }
            return temp2;
        }
    }

使い方

static void Main(string[] args)
        {
            var Infile = @"C:/Users/test.key";
            var DATA=new FILE_CAE().from_Key_File(Infile);
            var TargetPID=1;
            //List<EAGE>でフリーエッジが取得できます
            var FreeEage=new FreeEage().FreeEage_Make(DATA,TargetPID);
        }

今後

  • フリーエッジが書けたので次は、フリーの面(表面シェル)を書いていこうと思います

Discussion