본문 바로가기
카테고리 없음

(엔지니어를 위한) 파이썬 시작하기[16] 파이썬-캐드 연동 2/2(win32com)

by 돌종 2023. 1. 3.

(엔지니어를 위한) 파이썬 시작하기[16]

 

내용 : 파이썬-CAD 연동-2/2 (win32com)

참조 :

https://pypi.org/project/pywin32/ Python for Window Extensions

https://wikidocs.net/135798 Win32com (pywin32)

https://thgeomacademy.wordpress.com/python-autocad-selectionsets/

https://www.supplychaindataanalytics.com/selectionset-object-in-autocad-with-python/#

 

 

 

0.시작하며

앞의 파이썬-캐드연동 1/2에서는 pyautocad를 위주로 소개했다. pyautocad는 MicroSoft의 COM기술을 쉽게 쓸 수 있도록 정리한 라이브러리다. 따라서 COM기술의 모든 기능이 담겨있지는 않다. 그래서 파이썬으로 COM기술을 직접 사용하면 구현할 수 있는 캐드 기능들이 더 많아진다. 이번 시간에는 win32com(pywin32)를 이용해서 캐드를 통제하는 것을 소개하겠다. 비슷한 기능을 하는 파이썬 패키지로 comtypes가 있는데 win32com을 사용하는 사례가 더 많아 win32com의 사용에 대해 진행하고자 한다. 

 

1. win32com(pywin32)

win32com은 파이썬으로 COM을 지원한다. COM은 마이크로소프트의 기술로 프로그램간의 소통을 지원하는 기술이다. win32com은 anaconda를 설치했다면 컴퓨터에 설치되어있다. jupyter notebook, xlwings 등이 win32com을 사용하기 때문이다. win32com은 AutoCAD뿐 아니라 엑셀이나 워드, 파워포인트 프로그램도 지원한다. win32com을 쓰는 이유는 pyautocad에서 지원하지 않는 캐드 본연의 기능들을 사용하기 위한 것이다.

 

2. pyautocad와 차이.

pyautocad와 win32com의 차이를 알아보자.

 

pyautocad에서는 아래와 같이 객체를 선택한다. 지난 시간에 소개되었다.

acad.get_selection(‘메시지’)

 

반면 win32com(ActiveX)에서는 아래와 같이 객체를 선택한다. object는 SelectionSet이다.

object.Select Mode [, Point1] [, Point2] [, FilterType, FilterData]

 

차이가 있다는 것을 알 수 있다. pyautocad에서는 acad의 method로 get_selection()가 존재하지만 win32com에서는 SelectionSet의 method로 Select가 존재한다. 앞에서 pyautocad가 ActiveX를 좀더 편하게 쓰기 위해서 만들어진 라이브러리라고 소개했었다. ActiveX 본연의 형태는 SelectionSet을 이용하는 것으로 되어있다.

C:\Program Files\Common Files\Autodesk Shared\ko-KR\acadauto.chm 파일을 열어서 AutoCAD Object Model을 확인해보자. Document 아래 SelectionSets와 SelectionSet이 있는 것을 확인할 수 있다.

 


 

SelectionSet을 눌러서 들어가보면 아래와 같이 SelectinSet의 Method, Properties, Event를 볼 수 있다. SelectionSets는 SelectionSet들의 집합이다.

 


앞에서 예시로 제시한 object.Select 메소드를 선택해서 내용을 보자. VBA 문법을 보여준다. python 문법이 아니라는 것을 기억하자. 이 VBA기능을 python에서 사용할 수 있게 해주는 것이 win32com, pyautocad, comtypes 등 COM관련 패키지들이다.

 

Select메소드는 Mode, Point1, Point2, FilterType, FilterData 등을 인자로 주는 것을 알 수 있다. 각 인자들에 대한 설명은 아래쪽에 나와 있다. 이 메소드는 한점 또는 두점을 이용하거나 점 없이 CAD의 객체들을 선택할 수 있다. 이때 필터를 이용해서 선만 선택한다든가 레이어 “0”에 있는 객체들만 선택한다든가 이런 옵션을 사용할 수 있다. 자세한 것은 나중에 알아보도록 하겠다. 


다른 메소드를 알아보자. SelectOnScreen이라는 것이 있다. 이것이 사용자로 하여금 물체를 선택하도록 요구하는 메소드다. pyautocad에서 제공하는 get_selection 메소드는 이 기능을 쉽게 쓸 수 있도록 정의된 것이다. SelectOnScreen의 내용을 보자. 물론 VBA문법이다. 파이썬 문법은 나중에 살펴볼 것이니 걱정하지 말자. COM기술을 사용하려면 ActiveX 문서에 익숙해져야 한다.

 

object.SelectOnScreen [FilterType, FilterData]

 

[ ] 안의 내용은 들어갈 수도 있고 안들어가도 된다. 옵션 사항이라는 것이다. 앞의 Select 메소드와 마찬가지로 필터를 사용할 수 있다는 것을 알 수 있다. pyautocad에서 제공하는 get_selection(‘메시지’) 함수는 필터를 사용할 수 없었다. 필터에 관한 옵션을 넣을 수 없었다. 이것이 pyautocad와 win32com의 차이다. pyautocad는 ActiveX의 모든 기능을 사용할 수 없다. ActiveX기능을 간편하게 사용하려고 만든 라이브러리이기 때문이다. 반면 win32com으로는 ActiveX기능을 모두 사용할 수 있다.

 

 

 

3. win32com 설치확인 및 설치

anaconda를 설치했다면 win32com이 이미 설치 되어있을 것이다. 설치확인을 위해서 anaconda prompt를 실행하고 pip show pywin32를 입력해보자. win32com이 아니라 pywin32임을 주의하자. Required-by 항목에서 jupyter-core,xlwings 등 앞에서 경험해본 패키지들이 win32com(win32com)을 쓰고 있다는 것을 확인할 수 있다.

 

(base) C:\Users\ysj>pip show pywin32
Name: pywin32
Version:
228
Summary: Python
for Window Extensions
Home-page: https://github.com/mhammond/pywin32
Author: Mark Hammond (et al)
Author-email: mhammond@skippinet.com.au
License: PSF
Location: c:\programdata\anaconda3\lib\site-packages
Requires:
Required-by: jupyter-core, jupyter-server, menuinst, xlwings

 

만일 pywin32가 설치되어있지 않다면 아래와 같이 명령을 내려 pywin32를 설치하자.

pip install pywin32

 

 

4. 시작하기-파이썬과 CAD연결

win32com을 이용해서 CAD와 연결하는 방법을 알아보자. 앞에서 살펴본 pyautocad와 비교해보면서 살펴보도록 하겠다.

import win32com.client
acad = win32com.client.Dispatch(
"AutoCAD.Application")
doc = acad.ActiveDocument
acadModel = doc.ModelSpace
doc.Utility.Prompt(
"Hello, Autocad from Python\n")
print(doc.Name)

 

 

from pyautocad import Autocad
acad = Autocad()
acad.prompt(
"Hello, Autocad from Python\n")

print(acad.doc.Name)

 

pyautocad와 비교해보자. pyautocad가 조금 더 편리하게 사용할 수 있도록 되어있다는 것을 알 수 있다. pyautocad에서는 acad=Autocad()로 AutoCAD프로그램에 접근했지만 win32com에서는 acad=win32com.client.Dispatch(“AutoCAD.Application”)으로 접근했다. 또한 pyautocad에서는 캐드의 명령창에 텍스트를 출력하는 prompt가 acad의 메소드이지만 win32com에서는 acad의 하위인 ActiveDocument의 아래 있는 Utility의 하위 메소드로 존재한다.실제로 prompt가 AutoCAD Object Mode에서는 어디에 위치하는 것인지 확인해보자.

C:\Program Files\Common Files\Autodesk Shared\ko-KR\acadauto.chm 를 열고 prompt를 검색해보면 아래와 같은 화면을 볼 수 있다.prompt는 Utility의 메소드임을 알 수 있다.


 

Utility의 위치를 AutoCAD Object Model에서 살펴보면 아래와 같이 Document의 하위에 있는 것을 확인할 수 있다.


pyautocad와 win32com의 차이를 알고 있는 것이 CAD프로그램을 하는데 도움이 된다. pyautocad가 지원하는 기능은 pyautocad를 이용하고 pyautocad가 지원하지 않는 기능들은 win32com을 이용해야 하는 경우가 있을 수 있다. win32com을 이용하려면 AutoCAD Object Model을 잘 이해하고 있는 것이 중요하다.

 

5. 객체 추가

win32com으로 AutoCAD 객체를 추가하는 방법을 알아보자. 객체를 추가하기 위해서는 좌표가 필요하고 pyautocad에서도 Apoint를 이용했듯이 win32com에서도 Apoint를 이용한다. 대신 Apoint함수를 정의하고 이용한다. Apoint함수의 역할은 파이썬에서 입력하는 좌표를 AutoCAD가 인식할 수 있는 형식으로 변환해주는 코드라고 생각하면 되겠다. APoint함수에서 pythoncom을 이용했기 때문에 pythoncom을 추가로 import한 것을 볼 수 있다.

import win32com.client
import pythoncom

acad = win32com.client.Dispatch(
"AutoCAD.Application")
doc = acad.ActiveDocument
acadModel = doc.ModelSpace

def APoint(x, y, z = 0):
   
return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))
   
l1 = acadModel.AddLine(APoint(
0, 0, 0),APoint(1000, 1000, 0))
l2 = acadModel.AddLine(APoint(
1000, 1000, 0),APoint(2000, 0, 0))

c1 = acadModel.AddCircle(APoint(
0, 0, 0),500.0)

 

객체를 추가하는 메소드는 AutoCAD Object Model을 참조하면 되겠다. 위 코드에서는 선과 원을 추가한 예를 보였다.


 

 

-        Text 추가하기(AddText)

사용법 : doc.ModelSpace.AddText(문자열, 삽입점, 텍스트높이)

return : Text 객체

이 메소드는 텍스트를 그리고 텍스트 객체를 돌려준다. 메소드 자체는 pyautocad와 동일하다. 하지만 pyautocad의 경우 acad.model.AddText와 같이 model이 acad의 속성인 것에 비해 wim32com에서는 ModelSpace가 Document의 속성이다. 앞에서 설명한 것처럼 pyautocad가 사용하기 편하게 하려고 한 단계를 줄인 것으로 보면 되겠다. win32com은 ActiveX의 모델을 충실히 따른다는 것을 기억하자.

 

텍스트의 정렬, 색상 등은 생성된 객체의 속성을 바꿔주는 방법으로 변경할 수 있다. 위의 코드에서 생성된 텍스트 t1, 선 l1, 원 c1의 색을 각각 1,2,3으로 수정하는 코드다. 

t1.Color=1
l1.Color=
2
c1.Color=
3

 

 

 

-        Polyline 추가하기

pyautocad에서 polyline을 추가하는 방법과 다르지 않다. pyautocad에서는 좌표를 1차원 리스트로 만들고 단 다음 array.array를 이용해서 캐드가 인식할 수 있는 형식으로 변환했었다. win32com에서는 array 대신 aDouble함수를 정의해서 변환한다. 앞의 코드에 위의 내용을 추가해서 실행하면 아래와 같이 lwpolyline이 추가되는 것을 확인할 수 있다.

def aDouble(xyz):
   
return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (xyz))
points_2d = [
0, 0, 1000, 2000, 2000, 2000]
pl=acadModel.AddLightWeightPolyline(aDouble(points_2d))

 

 

6. 점 입력받기

캐드 자동화에서 사용자로부터 점을 입력받는 기능이 필요하다. 하지만 pyautocad에서는 지원하지 않는 기능이다. ActiveX에는 Utility아래 GetPoint 메소드가 있다. AutoCAD Object Model에서 GetPoint를 찾아보면 아래와 같은 내용을 확인할 수 있다.

 

VBA:
 
RetVal = object.GetPoint([Point [, Prompt]])
 
object
Type: Utility
The object this method applies to.
Point
Access: Input-only; optional
Type: Variant (three-element array of doubles)
The 3D WCS coordinates specifying the relative base point.
Prompt
Access: Input-only; optional
Type: Variant (string)
The text used to prompt the user for input.

 

point와 prompt를 줄 수 있는데 생략이 가능하다. point를 주면 그 점이 base point가 된다.

prompt는 사용자에게 보여지는 메시지다.

 

두점을 입력 받아서 선을 그리는 기능을 만들어보자. Getpoint를 두번 사용하면 된다. 첫번째 호출할 때는 Point 없이 Prompt만 지정하고 두번째 호출할 때는 첫번째 호출에서 리턴받은 점(pnt11)을 Point로 주면 첫번째 점을 base point로 보여준다. 테스트해본 결과 첫번째 점 입력받을 때 Prompt가 나타나지 않았다. 버그 같다. 그래서 앞에 임의로 Prompt 메소드를 사용했다. 이 코드를 실행하면 캐드 명령창에 prompt들이 출력되면서 2점을 입력받을 수 있다.

doc.Utility.Prompt(u"Pick first point: ")
pnt11 = doc.Utility.GetPoint( Prompt =
u"\nPick first point: ")
pnt22 = doc.Utility.GetPoint( Prompt =
u"\nPick second point: ", Point=aDouble(pnt11))

LineObj = acadModel.AddLine(aDouble(pnt11), aDouble(pnt22))

 


주의할 점은 GetPoint로 입력받은 점을 aDouble로 변환해줘야 한다는 것이다. 입력받은 pnt11을 변환하지 않고 직접 사용하면 pywintypes.com_error라는 오류가 발생한다.

 

7. 객체 선택

pyautocad에서는 get_selection()를 이용해서 객체를 선택했다. selection set을 만들 필요 없었다. win32com에서는 selection set을 만든 다음 selection set의 하위 메소드들을 이용해서 객체를 선택할 수 있다.

selection set을 추가하는 방법은 아래와 같다. “SS1”은 selection set 이름으로 임의의 이름으로 줄 수 있다.

slcn = doc.SelectionSets.Add("SS1")

만들어진 selection set 객체인 slcn을 이용해서 다양한 선택 메소드들을 이용할 수 있다. 앞에서 다양한 select방법을 알아보았었다.

 

Select                    object.Select Mode [, Point1] [, Point2] [, FilterType, FilterData]

SelectAtPoint          object.SelectAtPoint Point, FilterType, FilterData

SelectByPolygon       object.SelectByPolygon Mode, PointsList [, FilterType, FilterData]

SelectOnScreen        object.SelectOnScreen [FilterType, FilterData]

 

Select 메소드의 Mode는 아래와 같다. 이전 pyautocad 편에서 설명한 것처럼 AutoCAD의 상수를 사용하려면 pyautocad의 ACAD를 import해야 한다. ACAD를 import하기 싫다면 직접 값을 사용하면 된다. 대신 원하는 mode의 값을 알고 있어야 한다.

      acSelectionSetWindow        0

      acSelectionSetCrossing        1

      acSelectionSetPrevious        3

      acSelectionSetLast             4       

      acSelectionSetAll               5

SelectByPolygon 메소드의 Mode는 다음과 같다. 각 상수의 값을 제시했다.

      acSelectionSetFence                    2

      acSelectionSetWindowPolygon       6       

      acSelectionSetCrossingPolygon      7

AutoCAD Object Model에서 제시된 상수의 값을 알고 싶다면 아나콘다 콘솔에서 python을 실행시키고 from pyautocad import ACAD를 실행한 다음 ACAD.acSelectionSetFence 와 같이 직접 상수명을 출력해보는 것이 가장 확실한 방법이다. 물론 코드에 ACAD를 import한 경우에는 상수명을 직접 사용하면 된다.

아래의 코드를 보자. 캐드화면에 선,원,호,타원을 그리고 selection set을 만든 다음 두 점을 이용해서 윈도우 박스로 객체를 선택해서 객체 이름을 출력해주는 코드다.

 여기서 vtpt와 vtobj 함수 선언을 주목하자. 객체를 선택할 때 필터를 사용하기 위해서 필요한 함수선언이다. 필터 사용법은 ftyp변수와 ftdt변수에 항목과 값을 지정한다. ftyp에 [0,8]이 지정되어있는데 0은 객체형식을 의미하는 것이고 8은 layer를 의미하는 것이다. ftdt변수에 [“Line”,”0’]이 지정되어있는데 앞의 ftyp에 해당하는 값이다. 즉 0(객체형식)은 “Line”, 8(layer)은 “0”인 객체만 선택하겠다는 뜻이다. ftyp과 ftdt를 형변환해야 함을 잊지 말자.

0이 객체형식이고, 8일 레이어라는 의미는 DXF code라고 한다. 구글에서 dxf code라고 검색하면 쉽게 찾아볼 수 있다. 아래의 링크는 Autodesk 문서다. http://docs.autodesk.com/ACAD_E/2012/ENU/filesDXF/WS1a9193826455f5ff18cb41610ec0a2e719-7a62.htm

몇가지 DXF 코드를 살펴보면 아래와 같다.

0 : entity type

1 : Primary text value for an entity

6 : linetype name

7 : text style name

8 : layer name

62 : color number

 

<selectw32com.py>

import win32com.client
import pythoncom

acad = win32com.client.Dispatch(
"AutoCAD.Application")
doc = acad.ActiveDocument
acadModel = doc.ModelSpace

def APoint(x, y, z = 0):
   
return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x, y, z))

LineObj = acadModel.AddLine(APoint(
40,40) , APoint(500, 500))
CircleObj = acadModel.AddCircle(APoint(
300, 200), 100)
ArcObj = acadModel.AddArc(APoint(
600, 200), 50, 0, 1.57)
EllObj = acadModel.AddEllipse(APoint(
700, 200), APoint(100, 0), 0.5)

doc.Regen(
0)

def vtpt(x,y,z=0):
   
return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, (x,y,z))
def vtobj(obj):
   
return win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, obj)

try:
    doc.SelectionSets.Item(
"SS1").Delete()
except:
    print(
"Delete selection failed")
slcn = doc.SelectionSets.Add(
"SS1")

ftyp = [
0,8]
ftdt=[
"Line","0"]

filterType = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_I2, ftyp)
filterData = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_VARIANT, ftdt)
# use filter
#slcn.Select(0, APoint(0,0), APoint(1000, 1000), filterType, filterData)
# no filter
slcn.Select(
0, APoint(0,0), APoint(2000, 2000))

print(slcn.count,
" Selected")
for i in slcn:
    print(i.ObjectName)
    doc.Utility.Prompt(i.ObjectName+
"\n")

 

위의 코드를 실행하면 캐드화면에 네개의 객체가 그려지고 콘솔창과 캐드명령창에 각각 4개의 객체이름이 출력된다. 위 코드는 필터 없이 객체를 선택하는 경우이고 아래와 같이 필터처리를 적용한 줄의 #을 삭제하고 필터를 적용하지 않은 줄에 #을 추가해서 실행하면 선만 선택된다.

# use filter
slcn.Select(
0, APoint(0,0), APoint(1000, 1000), filterType, filterData)
# no filter
#slcn.Select(0, APoint(0,0), APoint(2000, 2000))

(base) D:\dev\python\pyautocad>python selectw32com.py
4  Selected
AcDbEllipse
AcDbArc
AcDbCircle
AcDbLine

 

객체 선택방법 중에 Select, SelectAtPoint, SelectByPolygon 이 세가지 방법은 사용자가 직접 선택하는 것이 아니라 코드를 이용해서 선택하는 방법이다. 사용자가 직접 선택하도록 할 때는 SelectOnScreen을 사용한다. pyautocad의 acad.get_selection 메소드는 ActiveX의 SelectOnScreen메소드를 사용하기 좋게 구현한 것이다.

 

실제로 pyautocad의 코드를 살펴보자. anaconda가 설치된 디렉토리 아래에 pyautocad 디렉토리를 찾아가 보면 (C:\ProgramData\Anaconda3\Lib\site-packages\pyautocad) api.py 파일을 볼 수 있다. 이 파일을 열고 get_selection으로 검색해보면 아래의 코드를 볼 수 있다. 내용을 살펴보면 “SS1”이라는 이름으로 selection set을 만들고 SelectOnScreen 메소드를 실행시키는 것을 확인할 수 있다.

<api.py>

    def get_selection(self, text="Select objects"):
       
""" Asks user to select objects

        :param text: prompt for selection
        """

        self.prompt(text)
       
try:
            self.doc.SelectionSets.Item(
"SS1").Delete()
       
except Exception:
            logger.debug(
'Delete selection failed')

        selection = self.doc.SelectionSets.Add(
'SS1')
        selection.SelectOnScreen()
       
return selection

 

 

8. 맷음말

이번 시간에는 win32com으로 autocad에서 객체를 생성하고 수정하는 것을 해봤다. 대부분 이전 시간에 다루었던 pyautocad에서 지원하는 기능들이었다. 하지만 getpoint는 pyautocad에서 지원하지 않는 기능이다. 앞에서 설명했듯이 pyautocad는 쉽게 ActiveX를 사용할 수 있도록 만든 라이브러리이기 때문에 한계가 있을 수 있다. 그래서 ActiveX 기능을 직접 사용하는 win32com을 알아본 것이다. pyautocad와 win32com을 둘다 import하고 pyautocad가 지원하는 기능은 pyautocad를 이용하고 pyautocad가 지원하지 않는 기능은 win32com을 이용하는 것도 좋은 방법일 수 있고, 전체 코드를 win32com만으로 작성하는 것도 하나의 방법일 수 있다. 중요한 것은 AutoCAD Object Model을 이해하고 있는 것이 중요하다. 그리고 형변환에 신경을 써야한다는 것을 명심하자.

pyautocad와 win32com을 비교하면 아래와 같다.

 

기능 pyautocad win32com
import from pyautocad import Autocad import win32com.client
Autocad접근 acad = Autocad() acad = win32com.client.Dispatch("AutoCAD.Application")
Document,
Modelspace접근
필요없음. doc = acad.ActiveDocument
acadModel = doc.ModelSpace
prompt acad.prompt("Hello, Autocad") doc.Utility.Prompt(“Hello, Autocad")
객체 추가 acad.model.AddLine(p1, p2) acadModel.AddLine(p1,p2)
점입력 없음. doc.Utility.GetPoint([Point [, Prompt]])
객체선택 acad.iter_objects(조건)
acad.get_selection(“메시지”)
slcn = doc.SelectionSets.Add("SS1")
slcn.Select(mode, APoint1, APoint2)
slcn.SelectOnScreen()

 

 

-끝-