기본 콘텐츠로 건너뛰기

2023의 게시물 표시

평면 상의 점 구하기

S3D는 민감한것 같습니다. 조그마한 오차도 허용하지 않고 형상을 만들지 못하겠다고 튕겨버립니다. 그림에서와 같이 사선 방향으로 Extrude하는 형상을 만들때 단면을 이루는 점들이 하나의 평면상에 있지 않으면 형상을 만들지 못합니다.(’범위를 벗어났다’나 뭐 그런 오류가 발생합니다.) 단면을 이루는 점들을 평면 상의 점들로 변환해줘야 S3D에서 오류 없이 형상을 만들 수 있습니다. 먼저 평면의 방정식은 아래와 같습니다. $$ a*x + b*y + c*z + d = 0 $$ 여기서 $<a,b,c>$는 평면의 법선 벡터입니다. 일반적으로 평면의 법선 벡터는 알고 있기 때문에 $d$ 값은 아래 식으로 구할 수 있습니다.(x,y,z에 단면을 이루는 점 하나를 대입) $$ d = -(a*x + b*y + c*z) $$ 이제 평면의 방정식을 구했기 때문에 나머지 다른 점($x_1,y_1,z_1$)들을 평면 상의 점들로 변환해주면 됩니다. $$ \begin{aligned} h &= a*x_1+b*y_1+c*z_1+d\\ pt^\prime &= pt - <a,b,c>*h \end{aligned} $$ 이렇게 평면 상의 점 $pt^\prime$ 을 구할 수 있습니다.

PCF 파일 뷰어 제작

PCF 파일은 배관 스풀의 정보를 가지는 텍스트 파일입니다. 아주 단순한 구조를 가지고 있기 때문에 내용을 파악하기 쉽습니다. 파이프의 경우 양 끝점과 BORE 사이즈를 가지고 있기 때문에 쉽게 3D을 생성할 수 있습니다. ELBOW의 경우에는 3D 형상을 그리기위해 몇가지 작업이 필요합니다. 일단 ELBOW는 양 끝점($P_1,P_2$)과 CENTER POINT(교차점),BORE 사이즈 그리고 각도를 가지고 있습니다. ELBOW를 그리는 방법은 먼저 $P_1$에 $\overrightarrow{CENTER - P_1}$을 법선 벡터로 가지는 지름이 BORE 사이즈인 원을 생성합니다. 그리고 생성한 원을 Origin 기준으로 $P_1,CENTER,P_2$로 이루어지는 평면의 법선을 축으로 하여 Angle만큼 회전하면 됩니다. $P_1,CENTER,P_2$로 이루어지는 평면의 법선 벡터는 아래와 같이 구할 수 있습니다. $\overrightarrow{Normal} = \overrightarrow{CENTER - P_1}\times \overrightarrow{P_2 - P_1}$ Origin 좌표를 구하기 위해 먼저 CENTER와 Origin사이의 거리(L)를 구합니다. $L=\frac{l}{sin(Angle/2)},(l = \left|\overrightarrow{CENTER - P_1}\right|)$ 거리를 구했으니 CENTER에서 Origin으로 향하는 방향 벡터만 구하면 Origin의 좌표를 구할 수 있습니다. 방향 벡터는 아래와 같이 구할 수 있습니다. $\overrightarrow{\text{방향 벡터}}=\overrightarrow{M-CENTER}, (M=\frac{P_1+P_2}{2})$ (삼각형 합동 규칙을 통하여 위 내용이 사실임을 확인할 수 있습니다.) 이제 방향 벡터까지 구했으니 Origin의 좌표를 구할 수 있습니다. Origin은 CENTER에서 방향 벡터 방향으로 L만큼 떨어진

사용자 설정값 유지

윈폼 프로그램에서 필요한 정보를 사용자 설정(Properties.Settings.Default)에 저장하여 프로그램 종료 후에도 사용할 수 있도록 합니다. 손쉽게 정보들을 사용자 설정에 저장하여 사용할 수 있는데, 프로그램이 업데이트(버전 정보 변경)되면 이러한 정보들이 초기화 된다는 문제점이 있습니다. 예를 들어 S/W 라이선스 키를 받아 사용자 환경 설정에 저장하여 사용하고 있다가 프로그램이 업데이트되어 재 설치하면 라이선스 키가 초기화가 되어 라이선스 키를 다시 받아야 합니다. 이러한 원인은 사용자 설정 파일이 어셈블리\버전별로 관리되고 있기 때문입니다. 버전 이름의 폴더 안의 user.config 파일에 사용자 설정 데이타가 저장됩니다. 따라서 새로운 프로그램을 설치하면 버전이 변경되기 때문에 기존 데이터를 잃어 버리게 됩니다. 이를 방지하기 위해서 프로그램에서 사용자 설정 데이터에 접근하기 전에 데이터 유효성을 검사하여 데이터가 초기화되었다면 업그레이드를 통해 기존 데이터를 가져와야 합니다. 1 2 3 4 5 6 #region 프로그램 업데이트 후 기존 설정값을 유지하도록 함 if  (Properties.Settings.Default.x_auth_token.Equals( string .Empty)) {     Properties.Settings.Default.Upgrade(); } #endregion Colored by Color Scripter cs 위는 라이선스 키가 비어 있다면 기존 라이선스 키로 복원하는 코드입니다. 프로그램 업그레이드 후에 사용자 설정 데이터가 초기화된다면 위 코드를 적용하여 문제를 해결할 수 있습니다. 참조 링크 : https://www.arclab.com/en/kb/csharp/save-and-restore-position-size-windows-forms-application.html

명령행으로 웹서비스 배포하기

웹서비스를 단위 테스트하기 위하여 명령행으로 웹서비스를 배포하도록 작성했습니다. 일반적인 단위 테스트트는 프로젝트는 참조하여 테스트를 진행하면 되지만 웹서비스의 경우에는 먼저 서비스가 웹서버에 배포되어야 하는데 수작업으로 웹서버에 배포를 해야한다면 단위 테스트를 자동화할수 없습니다. 그래서 어플리케이션을 빌드할때 웹서비스를 컴파일하고 -> 명령행으로 자동으로 웹서버에 배포한 뒤 -> 단위 테스를 진행하도록 하였습니다. MSBuild.exe를 이용하여 웹서비스를 컴파일 한뒤 배포할 수 있습니다. WebApi 프로젝트를 Release로 컴파일 한뒤 CustomProfile을 이용하여 웹서버에 배포합니다. 1 %MSBUILD% WebApi.csproj  / t:WebPublish  / p:DeployBuild = true ;Configuration = Release  / p:PublishProfile = CustomProfile  / p:VisualStudioVersion = 15. 0   cs 컴파일에 앞서 배포할 Profile을 하나 만들어 줍니다.(참조 : https://docs.microsoft.com/ko-kr/aspnet/web-forms/overview/deployment/visual-studio-web-deployment/deploying-to-iis" ) 프로젝트 파일을 컴파일하는 경우에는 VS 버전을 명시해야 한다고 합니다. (/p:VisualStudioVersion=15.0 => VS 2017) 이렇게 웹배포가 끝난뒤에 xUnit을 이용하여 Web Api에 대한 단위 테스트를 진행할 수 있습니다.

[ARS] Clash Check

Piping Auto Routing을 개발하다 보면 파이프가 장치 혹은 구조물을 뚫고 지나가는 현상이 발생하게 됩니다. Routing을 돌리고 나서 무수히 많은 모델에서 이러한 간섭을 일일이 찾는 것은 힘들고도 시간 낭비입니다. 그래서 간섭이 일어난 부분을 찾는 Clash Check 기능이 필요합니다. Clash Check 기능은 모델의 OrientedBoundingBox를 구해 OrientedBoundingBox끼리 충돌 검사를 하여 결과를 리포트합니다. 리포트를 더블 클릭하면 충돌이 발생한 두 OrientedBoundingBox를 줌하여 화면에 표시합니다. 어떻게 말해야 할지 모르겠지만 일반적인 경우(두 OrientedBoundingBox의 크기가 비슷할 경우)는 충돌이 발생한 지점을 찾기가 쉽지만, 한 쪽이 다른 쪽에 비해 월등히 크다면 충돌이 일어나 지점을 정확히 찾기가 어렵게 됩니다. 좀 더 정확한 충돌 지점을 찾기 위해서는 충돌이 일어난 OrientedBoundingBox를 세분화하여 작은 OrientedBoundingBox들을 생성합니다. [하나의 OrientedBoundingBox를 여러 개의 작은 OrientedBoundingBox로 나눔] Small OrientedBoundingBox의 좌표는 아래 식으로 계산할 수 있습니다. $$(x,y,z) = \overrightarrow{X}*i + \overrightarrow{Y}*j + \overrightarrow{Z}*k,\ (i,j,k는 각각 \overrightarrow{X},\overrightarrow{Y},\overrightarrow{Z}상의 값)$$ 그리고 생성한 OrientedBoundingBox들 끼리 충돌 검사를 하여 충돌이 일어난 정확한 지점을 찾습니다.

DataBinding

뷰에 데이터를 연결하여 화면에 데이터를 표시할때 DataBinding을 많이 사용합니다. 일반적인 예가 GridView 에 데이터를 연결하는 것입니다. 1 this.radGridViewProject.DataSource = this.Projects; cs GridView가 아닌 일반 컨트롤, 예를들어 TextBox나 ComboBox에도 DataBinding을 사용할 수 있습니다. 1 this .radTextBoxTcpServerPort.DataBindings.Add( "Text" , Properties.Settings.Default,  "TcpServerPort" ); cs 첫번째 인자 Text는 TextBox의 속성 이름이고 두번째는 DataSource 그리고 마지막은 DataSource의 멤버 이름을 설정하면 됩니다. 즉 위 코드는 TextBox의 “Text” 속성에 Properties.Setting.Default의 “TcpServerPort”를 연결시킵니다. 이렇게 UI와 데이터를 연결시켜 놓으면 UI 변경 시 연결된 데이터도 함께 변경됩니다. 1 2 3 4 5 6 7 8 9 this .radTextBoxTcpServerPort.DataBindings.Add( "Text" , Properties.Settings.Default,  "TcpServerPort" ); /// DO SOMETHING(예: UI를 통한 TcpServerPort 수정) Properties.Settings.Default.Save(); DataBinding을 사용하지 않으면 UI를 통해 수정한 내용을 TcpServerPort에 적용 후에 저장해야 합니다.   this .radTextBoxTcpServerPort.DataBindings.Add( "Text" , Properties.Settings.Default,  "TcpServerPort" ); /// D

Host 어플리케이션과 AutoCAD 플러그인의 상호 연동

AutoCAD 플러그인을 개발하면서 항상 고민이 되는 부분이 어떻게 Host 어플리케이션과 연동하는냐하는 문제였습니다. 초기에는 윈도위 API인 SendMessage를 이용하여 서로 데이타를 주고 받도록 디자인을 했었는데 뭔가 밀접하게 연결이 되어 있지 않다는 느낌이 강했습니다. 그래서 Tcp/IP 통신을 이용하여 상호 연동하는 방식으로 변경하기로 했습니다. AutoCAD에 플러그인이 로딩됨과 동시에 Tcp/IP로 Host 어플리케이션이 접속하기를 기다립니다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public   void  DoAcceptTcpClient() {     _TcpListener.Start( 1 );        // Start to listen for connections from a client.      while  ( true )     {          // Accept the connection.           using  (TcpClient client  =  _TcpListener.AcceptTcpClient())         {              // Get a stream object for reading and writing              using  (NetworkStream stream  =  client.GetStream())             {                 DataSet dsParam  =   new  DataSet();                 #region get data size                  byte [] buffer  =   new   byte [ sizeof ( long )];            

White Space 검색

아래와 같이 주어진 도면에 Label을 도면 상의 다른 객체들과 겹쳐지지 않게 배치하고자 할때 White Space 검색이 필요합니다. 먼저 도면의 모든 객체들을 포함하는 Bounding Box를 구합니다. 이 Bounding Box 안에 Label을 배치한다고 가정하겠습니다. 도면 상의 객체의 Bounding Box를 구해 도면의 Bounding Box안에 배치시킵니다. 그리고 배치하려는 Label의 Bounding Box를 구한 다음 Label을 배치할 위치를 찾을 Search Bounding Box를 설정합니다. 일반적으로 Label을 가지는 객체의 Bounding Box의 비율로 Search Bounding Box를 구합니다.(물론 도면 전체 Bounding Box가 될 수도 있습니다.) 라인 넘버의 경우 Search Bounding Box를 파이프 길이 방향으로 길게 생성하면 될것 같습니다. Search Bounding Box안에서 Label을 X,Y로 조금씩 이동(δt)하면서 Score를 구합니다. Score = distance * distance factor + overlap area ∗ overlap area factor distance = 밸브의 원점과 Label 위치와의 거리 distance factor = distance가 score에 미치는 영향 overlap area = label의 bounding box가 도면 상의 객체와 겹치는 영역 overlap area factor = overlap area가 score에 미치는 영향 $\delta t$의 크기가 작을 수록 Label을 배치할 위치의 정밀도가 높아지지만 위치를 찾는데 걸리는 시간을 길어집니다. overlap area는 사각형들이(Label과 여러 객체들이 동시에 겹쳐질수 있음) 겹쳐지는 영역을 구하면 됩니다. Label을 회전할 필요가 있으면 회전한 Bounding Box를 구하여 Search Bounding

SQLite Bulk Insert

SQLite에는 데이터를 대량 삽입하는 특별한 방법이 없습니다. 데이터를 삽입하거나 업데이트할 때 최적의 성능을 얻으려면 다음을 수행해야 합니다. - 트랜잭션을 사용하세요. - 데이터베이스에 연결하는 횟수를 줄이세요.(Command를 하나만 생성하여 재사용) - 동일한 매개 변수를 사용하여 명령을 다시 사용하세요. using (var transaction = connection.BeginTransaction()) { var command = connection.CreateCommand(); command.CommandText = @" INSERT INTO Markup(UID, Color) VALUES ($UID, $Color) "; var paramUID = command.CreateParameter(); paramUID.ParameterName = "$UID"; var paramColor = command.CreateParameter(); paramColor.ParameterName = "$Color"; command.Parameters.Add(paramUID); // Insert a lot of data var random = new Random(); for (var i = 0; i < 1500000; i++) { paramUID.Value = random.Next(); paramColor.Value = "FFFFFF"; command.ExecuteNonQuery(); } transaction.Commit(); } 단일 건마다 Command를 생성하여 쿼리를 실행하는 것보다 엄청난 성능 향상을 볼 수 있습니다.

유용한 Visual Studio 확장 기능

유용한 Visutal Studio 확장 기능을 소개해 드리고자 합니다. 1. SonarLint      1. 정적 분석을 통해 잠재적인 코드 상의 오류 사항 표시 및 개선 코드를 제시해 줍니다.      2. 개선 코드 제시 var blk = ViewModelControl.Blocks.Where(param => param.Name.Equals((node.DataBoundItem as Wbs).UID)).FirstOrDefault(); if (blk != null) { ViewModelControl.Blocks[(node.DataBoundItem as Wbs).UID].Entities.SelectAll(); } var blk = ViewModelControl.Blocks.Where(param => param.Name.Equals((node.DataBoundItem as Wbs).UID)).FirstOrDefault(); if (blk != null) { ViewModelControl.Blocks[(node.DataBoundItem as Wbs).UID].Entities.SelectAll(); } 위 코드에 대해서 아래와 같은 개선 코드를 제시해 줍니다.(Where 대신에 FirstOrDefault를 사용하라고 함) 위 제안대로 바꾸면 같은 동작을 하는 좀더 간단한 코드로 바꿀 수 있습니다. var blk = ViewModelControl.Blocks.FirstOrDefault(param => param.Name.Equals((node.DataBoundItem as Wbs).UID)); if (blk != null) { ViewModelControl.Blocks[(node.DataBoundItem as Wbs).UID].Entities.Se