기본 콘텐츠로 건너뛰기

[윈도우즈 이벤트 로그] System.Security.SecurityException

프로그램에서 발생하는 에러를 이벤트 로그를 남기려고 아래와 같이 이벤트 로그에 접근하니 windowEventLog = Log; windowEventLog.Source = SourceName; windowEventLog.Log = LogName; if (!EventLog.SourceExists(SourceName)) EventLog.CreateEventSource(SourceName, "Application"); System.Security.SecurityException 오류가 발생했습니다. 위 5 번째 줄에서 원본 여부를 존재 여부를 확인할때 오류가 발생합니다. 검색해보니 EventLog.SourceExists 함수에서 EventLog\Security 에 접근하게 되는데 관리자 권한 이 없을 경우에 오류가 발생한다고 합니다. 이 경우에는 레지스트리에 SourceName 으로 키를 생성해주면 오류가 발생하지 않는다고 합니다. Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Application\SourceName] 위 내용을 .reg 파일로 생성해서 실행시켜주면 됩니다.
최근 글

IronPython 활용

예전부터 응용 프로그램에 Python을 결합하여 즐겨 사용했는데, 어떤 사용자의 요구사항 때문이었습니다. 그 사용자가 사용하는 프로그램에 계산식이 있는데 계산식이 프로젝트의 특성때문에 조금씩 수정해야 한다는 것이었습니다. 프로젝트마다 계산식을 수정해서 프로그램을 배포하기가 어려워 계산식 자체를 Python 코드로 외부 파일로 빼내는 방식으로 처리했습니다. 어느날 차를 몰고 가는데 전화가 와서 이번 프로젝트에서는 factor 3을 3.5로 바꿔야 하는데 어떻게 하나요? 라고 문의가 왔습니다. Python 파일을 열어 숫자 3을 3.5로 바꾸면 된다고 답변을 해줬습니다. 이렇게 사용자는 프로젝트마다 Python에서 계산식을 수정하여 사용하면 되기 때문에 프로그램을 수정할 필요가 없어집니다. 이전 글 에서도 이야기했듯이 IronPython은 C#과 궁합이 아주 좋아 이번 프로젝트에서도 IronPython을 프로그램에 포함시켰습니다. 자동으로 생성된 배관 라인을 수정하거나 새로 생성하는 기능을 위해 IronPython을 활용했습니다. 먼저 앱 실행 경로를 IronPython 검색 경로에 추가해줍니다. 1 2 3 4 5 6 #region IronPython의 검색 경로에 앱 실행 경로를 추가 var paths  =  _IronPython.GetSearchPaths(); var ExecutePath  =   System .IO.Path.GetDirectoryName(Application.ExecutablePath); paths.Add(ExecutePath); _IronPython.SetSearchPaths(paths); #endregion Colored by Color Scripter cs 아래와 같이 C# Class를 정의하고 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public   class  MoveTo {      public  MoveTo(PythonTuple value)     {          int

[EYESHOT] Ver 2022에서 Ver 2023으로 업그레이드

기존 2022버전에서 2023으로 업그레드하기 위해 GIT에서 2023 브랜치를 새로 만들었습니다. devDept 사이트 에서 최신 2023 버전을 다운받습니다. 오늘 들어가 보니 2023.3.630 이 최신 버전입니다. 설치한 후 Activate 툴의 Convert 탭에서 기존 프로젝트를 Eyeshot 2023프로젝트로 변환할 수 있습니다. 아래 Got It! 버튼을 눌러 기존 프로젝트 폴더를 선택하면 선택한 프로젝트의 Eyeshot 버전이 표시됩니다. 2023으로 업그레이드한 화면을 캡쳐했네요.. Upgrade 버튼을 누르면 자동으로 코드를 변경합니다. 이때 레퍼런스까지 자동으로 변경됩니다. 레퍼런스가 자동으로 추가된 모습 프로젝트를 열어 컴파일해서 자동으로 변경하지 못한 부분에서 발생한 오류나 프로그램 동작 시 이상한 부분들을 수정해줍니다. 2023 변경 사항 에서 2023 버전에서의 변경 사항을 확인할 수 있습니다. 실제 프로젝트를 변경해보니 Designer쪽에 넣어놓은 컨트롤들은 거의 변경되지 않았습니다. Workspace 및 ProgressBar의 namespace가 devDept.Eyeshot에서 devDept.Eyeshot.Control로 변경되어서 Designer쪽에서 변경해주고나면 코드 쪽에서는 거의 대부분 자동으로 변환됩니다.

[EYESHOT] LinearDim 생성하기

Eyeshot에서 LinearDim을 생성하는 방법을 찾아봐도 자세한 설명이 나오지 않아 조금의 시행 착오를 거쳤습니다. Eyeshot 도움말을 찾아보면 LinearDim의 생성자는 아래와 같습니다. 1 2 3 4 5 6 7 public  LinearDim(     Plane dimPlane,    Point3D extLine1,    Point3D extLine2,    Point3D dimLinePos,     double  textHeight ) cs Parameters dimPlane     The dimension plane extLine1     First extension line point extLine2     Second extension line point dimLinePos     Dimension line position. Affects text position when the text cannot fit between extension lines. textHeight     Text height dimPlane은 Dimension이 놓여지는 평면입니다 음... 백문이불여일견이라 아무래도 말이나 글로 설명하는 것보다 그림으로 보여주는 것이 이해하기가 더 쉬울 것 같습니다 하지만 Eyeshot에서는 그림을 제공하지 않아 제가 그려봤습니다. 여기서 dimPlane을 변경하면 같은 위치의 extLine1, extLine2라도 표시되는 Dimension이 바뀌게 됩니다. dimPlane 변경 extLine1과 extLine2를 dimPlane의 X축으로 Projection한 거리가 Dimension의 값이 되고, dimPlane의 Y축 방향으로 extLine1, extLine2에서 지시선이 생깁니다. Dimension Text는 dimPlane의 법선 방향으로 dimLinePos의 위치에 표시됩니다. 만일 dimPlane의 법선 방향이 반대 방향으로 되면 Dimension Text

Shipping Box - #2

이전 글 에서 미처 고려하지 못했던 사항이 있어 이 글을 작성하게 되었습니다. 길이가 가장 긴 직선 구간을 선택해 그와 붙어 있는 직선 구간을 선택해 두 벡터를 외적하여 폭 벡터를 구한다 위 부분은 두 직선 구간이 수직이라는 가정으로 작성되었습니다. 어제 동료와 이야기하다 45도로 연결된 구간을 어떻게 처리해야 하는지 고민이 된다는 이야기를 들었을 때 이전 글에서 위 부분이 문제가 있다는 것을 알았습니다. 수학 시간에 우리가 배웠던 모든 축 벡터를 서로 수직입니다. 한번 기억을 더듬어 보시기 바랍니다. 따라서 우리가 구해야 하는 축 벡터도 서로 수직이어야 합니다. 또 다른 말로 서로 독립적이어야 합니다. 그런데 두 직선 구간이 45로 연결되어 있다면 두 구간을 독립적이지 않게 됩니다.(한 축의 좌표가 다른 축 좌표 값을 포함하게 됩니다.) A, B 벡터가 있다고 가정하고 두 벡터가 서로 수직이지 않다고 하면, A, B 벡터 외적으로 A, B에 수직인 C를 구할 수 있습니다. $$\vec{C} = \vec{A}\times\vec{B}$$ 그리고 A, C 벡터 외적을 통하여 B 벡터를 구할 수 있습니다. 이때 B 벡터는 A, C 벡터에 수직이 됩니다. 이로써 세 벡터 A, B, C는 수직이며 서로 독립적입니다. $$\vec{B}=\vec{A}\times\vec{C}$$ 이로써 이전 글의 문제점을 보완할 수 있습니다. 하지만 복잡해 보입니다. 단순함이 복잡함보다 아름답습니다. 이전 글에서의 목적을 간단히 되짚어 보면 점들을 포함하는 OrientedBoundingBox를 구하는 것입니다. 위 점들에서 가장 멀리 떨어진 두 점을 선택하고 두 점을 잇는 선(A)을 구합니다. 이 선에서 가장 멀리 떨어진 점을 하나 선택해 선에 수직인 다른 선(B)을 구합니다. A, B 두 선의 외적을 이용하여 두 선에 수직인 벡터 C를 구할 수 있습니다. 점들을 A, B, C 축으로 변환하여 점들을 포

GITHUB 블로그를 Google 검색 엔진에 노출하기

요즘 GITHUB에 블로그를 생성하여 블로그 작성에 의욕을 불태우고 있는데, 아무래도 혼자만 만족하는 것보다 여러 사람들과 공유하는 것이 좋을 것 같아서 Google 검색 엔진에 노출하는 방법을 찾아 보았습니다. 저는 Chripy JekyII 테마를 이용하여 블로그를 만들었는데, Google 검색 엔진에 노출하는 방법은 찾아보면 많이 나와있어 여기서는 언급하지 않겠습니다. [Github Blog] Github blog를 Google 검색 엔진에 노출시키기 다만 제가 따라하면서 겪었던 오류에 대해서 이야기 해보겠습니다. 저는 아래와 같이 수작업으로 sitemap.xml을 생성하여 root폴더에 두었습니다. --- layout: null --- {% for post in site.posts %} {{ site.url }}{{ post.url }} {% if post.lastmod == null %} {{ post.date | date_to_xmlschema }} {% else %} {{ post.lastmod | date_to_xmlschema }} {% endif %} {% if post.sitemap.changefreq == null %} weekly {% else %} {{ post.sitemap.changefreq }} {% endif %} {% if post.sitemap.priority == null %} 0.5 {% else %} {{ post.sitemap.priority }} {% endif %} {% endfor %} 로컬에서 테스트하기 위해 http://localhost:4000/sitemap.xml을 호출하니 아래와 같은 오류가 발생했습니다.

[C#] Class를 Json으로 저장하기

JsonConverter.Serialize를 사용하면 Class를 Json으로 변환할 수 있습니다. 하지만 Json으로 변환할때 Class를 명시해줘야 하기 때문에 아래와 같은 경우는 처리하기가 어렵습니다. 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 class  A {      public   virtual   string  Type { get; set; }  =   "A" ;      public   string  Parent { get; set; }  =   "Parent" ; }   class  B : A {      public   override   string  Type { get; set; }  =   "B" ;      public   string  Child { get; set; }  =   "Child" ; }   List < A >  list  =   new   List (){ new  A(),  new  B()}   foreach (var tmp  in  list) {      if (tmp  is  A a)     {         JsonConverter.Serialize < A > (a);     }      else   if (tmp  is  B b)     {         JsonConverter.Serialize < B > (b);     } } Colored by Color Scripter cs 위와 같은 코드는 상속받는 클래스가 늘어나면 해당 코드를 변경해야 하기 때문에 관리가 힘듭니다. 아래와 같이 바꾸면 되겠지만 이때 상속 받은 B 클래스가 Json으로 제대로 변환되지 않습니다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class  A {      public   virtual

Ellipse의 Start, End Point 구하기

포맷마다 프리미티브를 정의하는 것이 달라 데이터 교환을 위해서 프리미티브 변환을 해줘야 합니다. Ellipse는 아래와 같이 정의할 수 있습니다.($\alpha$=시작 각도, $\beta$=회전 각도) 위 그림에서 Ellipse상의 점 $A$는 아래 식으로 구할 수 있습니다. $x^\prime = a*cos(\theta)\\y^\prime = b*sin(\theta)$ 위 식은 2D일때의 식이고 그럼 3D일때는 어떻게 식을 구해야 할까요? $a, b$ 는 각각  $\vec{U}$축, $\vec{V}$축상의 거리이기 때문에 위 식은 아래 식으로 변환할 수 있습니다. $x^\prime = a*\vec{U}*cos(\theta)\\y^\prime = b*\vec{V}*sin(\theta)$ 위 식이 Ellipse의 일반식이 됩니다. $\vec{U}, \vec{V}$에 축의 벡터를 입력하면 원하는 좌표를 구할 수 있습니다. 예를 들어 $\vec{U}$에 $\vec{X}([1, 0, 0])$을 $\vec{V}$에 $\vec{Y}([0,1,0])$을 대입하면 아래와 같습니다. $$x^\prime = a*[1_x,0_y,0_z]*cos(\theta)\\y^\prime = b*[0_x,1_y,0_z]*sin(\theta)$$ $\vec{X}$축의 $y,z$ 요소는 0이고, $\vec{Y}$축의 $x,z$ 요소는 0이기 때문에 이것을 풀어보면 첫번째 식과 같게 됩니다. $\vec{U}$, $\vec{V}$축이 임의의 3D 축이라고 가정해보면 $$x^\prime = a*[u_x,u_y,u_z]*cos(\theta)\\y^\prime = b*[v_x,v_y,v_z]*sin(\theta)$$ 월드 좌표 $(x,y,z)$는 아래 식으로 구할 수 있습니다. $$x = a*u_x*cos(\theta)+b*v_x*sin(\theta)\\y = a*u_y*cos(\theta)+b*v_y*sin(\theta)\\z = a*u_z*cos(\theta)+b*v_z*sin(\theta)$$

평면 상의 점 구하기

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