AutoLISP 강좌 [3]


1. 입문(I) - AutoLISP의 기본 개념
2. 입문(II) - 사용자 정의 함수 제작
3. 입문(III) - 그 밖의 기본 기능들
4. 응용(I) - Entity와 Selection-Set
5. 응용(II) - Symbol table과 Device 제어
6. 응용(III) - Dialogue box 제어
7. 활용예제


3. 그 밖의 기본 기능들

3.1 화일 입출력 함수

이번에는 화일 입출력 함수에 관해 설명하면서 점들에 관한 자료가 담겨있는 화일을 읽어서 AutoCAD 상에서 그 점들을 잇는 3차원 POLYLINE을 그려주는 프로그램을 작성해 보겠다. 먼저, AutoLISP에서 사용되는 기본적인 화일 입출력 함수들을 살펴보겠다.

1) 기본적인 화일 입출력 함수와 그 사용 방식

(setq <화일 포인터> (open "화일 이름" "r")) <= 화일을 읽을때
(setq <화일 포인터> (open "화일 이름" "w")) <= 화일에 쓸때
(setq <화일 포인터> (open "화일 이름" "a")) <= 기존 화일에 더해서 쓸때
. . . . . .
(setq <문자의 ASCII 코드>
(read-char <화일 포인터>)) <= 화일로부터 문자를 읽을때
(setq <문자열> (read-line <화일 포인터>)) <= 화일로부터 문자열을 읽을때
(write-char <문자의 ASCII 코드> <화일 포인터>) <= 화일에 문자를 쓸때
(write-line <문자열> <화일 포인터>) <= 화일에 문자열을 쓸때
. . . . . .
(close <화일 포인터>) <= 화일 사용을 종료할때

AutoLISP에서는 화일 포인터를 통해 화일을 제어하며 읽기(read), 쓰기(write), 그리고 더하기(append)가 가능하다. 기본적으로는 ASCII 형태의 화일을 처리하는데 적합하며 BINARY 화일의 처리가 불가능한 것은 아니다. OPEN한 화일은 CLOSE하기만 하면 처리가 종료된다.

2) 화일에 쓰기

먼저, 자료 화일을 하나 만들기 위해 다음과 같이 해보자.

Command: (setq fd (open "test.dat" "w")) <= 화일에 쓰기위해 연다.
<File: #?????> <= 화일 포인터의 번호를 알려준다. (큰 의미 없음)
Command: (princ (getpoint) fd) <= PRINC 함수도 화일 출력이 가능
<= 임의의 점을 선택한다.
(1.0 1.0 0.0) <= 임의의 점이 화일에 출력된다.
Command: (princ "\n" fd) <= 다음줄로 이동한다.
"\n"
Command: (princ (getpoint) fd) <= 위의 과정을 반복
. . . . . . <= 필요한 만큼 수행
Command: (close fd) <= 화일 사용을 종료
nil <= 정상적으로 종료되었음

3) 화일로 부터 자료를 읽기

앞서 화일에 기록된 내용을 먼저 살펴보자.

Command: TYPE
File to list: TEST.DAT
(1.0 1.0 0.0) <= 리스트 형태로 기록
. . . . . .

이와 같이 PRINC 함수로 좌표값을 출력하면 리스트 형태로 기록된다는 것을 기억해 두자. 그럼, 이제 이 자료를 읽어오는 방법에 관해 설명하겠다. 우선은 이 점들을 읽어서 POINT를 그려주는 것으로 작성해 보겠다. 먼저, 작업 과정을 시험해 보자.

Command: (setq fd (open "TEST.DAT" "r")) <= 화일을 읽기위해 연다.
<File: #?????>
Command: (setq rl (read-line fd)) <= 한줄을 읽어 온다.
"(1.0 1.0 0.0)" <= 문자열 상태로 읽어 온다.
Command: (setq rl (read rl)) <= 리스트 형태로 바꾼다.
(1.0 1.0 0.0)
Command: POINT
Point: !rl <= 읽어들인 좌표값으로 POINT를 그린다.
Command: (close fd)
nil

위의 과정에 오류가 없다면 에디터를 사용하여 아래와 같이 프로그래을 완성하기 바란다. 이때 화일의 끝을 확인하는 방법은 READ-LINE 함수가 화일의 끝을 만나면 nil을 반환하므로 간단히 화일의 끝을 확인할 수 있다. 즉, 아래에서 보면 변수 RL의 값이 nil이면 WHILE 문을 벗어나게 되어 화일을 닫고 작업을 종료하게 된다.

; 화일에서 좌표값을 읽어서 POINT를 그린다.

(defun draw-point (name / fd rl)
  (setq fd (open name "r"))
  (while (setq rl (read-line fd))
    (command "POINT" (read rl))
  )
  (close fd)
)

(defun C:DPOINT ( / name)
  (setq name (getstring "\nFile name: "))
  (draw-point name)
  (princ)
)

입력이 끝났으면 DPOINT.LSP라는 이름으로 저장을 하고 로드하여 실행해보기 바란다.

Command: (load "DPOINT")
"C:DPOINT"
Command: DPOINT
File name: TEST.DAT

4) 또 다른 읽기 형식

FORTRAN 같은 프로그래밍 언어를 사용해서 출력한 자료는 의례적으로 출력값들이 어떤 형식을 갖게 된다. 예를 들면 소수점이하는 몇째자리까지, 빈칸은 몇칸, 숫자전체가 차지하는 칸의 수는 얼마, 이런 식으로 형식(format)이 정해지게 된다. 이런 자료들을 읽어오는 것은 AutoLISP으로는 처리하기가 앞서본 리스트 형태와는 달리 조금 까다롭고 데이타의 형식이 바뀌면 프로그램을 수정해야 한다는 불편함이 있다. 우선, 데이타의 형태가 다음과 같다고 하자.

|<-10 칸->|<-10 칸->|<-10 칸->|

  1.000000  1.000000  0.000000

이번에는 앞서의 프로그램 중 DRAW-POINT 함수만을 위의 형식의 데이타 화일을 읽을 수 있도록 수정해 보겠다. 그럼, 수정에 앞서 다음과 같이 작업 과정을 시험해 보기 바란다. TEST2.DAT는 다음과 같이 작성한다. 위의 데이타 형식에 주의하면서 입력하기 바란다.

  5.000000  0.000000  0.000000
  6.763360  2.572950  0.500000
  9.755280  3.454920  1.000000
  7.853170  5.927050  1.500000
  7.938930  9.045080  2.000000
  5.000000  8.000000  2.500000
  2.061070  9.045080  3.000000
  2.146830  5.927050  3.500000
  0.244717  3.454920  4.000000
  3.236640  2.572950  4.500000
  5.000000  0.000000  5.000000

Command: (setq fd (open "TEST2.DAT" "r"))
<File: #?????>
Command: (setq rl (read-line fd))
"  5.000000  0.000000  0.000000"
Command: (close fd) <= 일단 화일을 닫음
nil

여기까지의 작업과정은 똑같지만 이번에는 읽어온 데이타의 형태가 앞의 경우와 다르다. 이제 이것을 어떻게 처리해야 할까? 우선, 단순히 문자열로 된 것을 실수 값으로 바꾸는 함수(ATOF) 는 이미 잘 알고 있으리라 생각된다. 그렇다면 읽어온 문자열을 실수 값을 나타내는 각각의 문자열로 분리하기만 하면 되겠다. 그런 작업을 할때 유용하게 사용되는 함수가 SUBSTR이다. 다음을 보자.

Command: (substr rl 1 10) <= 문자열 RL의 1번째 문자부터 10개
"  5.000000"
Command: (setq x (atof (substr rl 1 10))) <= 분리된 문자열은 실수값으로 변환
5.0

같은 방법으로 y, z를 구해보기 바란다. 그런 다음 아래와 같이 수행해 보자.

Command: POINT
Point: (list x y z)

정상적으로 POINT가 그려졌다면 이제 수정한 함수를 완성해 보자.

(defun draw-point (name / fd rl x y z)
  (setq fd (open name "r"))
  (while (setq rl (read-line fd))
    (setq x (atof (substr rl 1 10)) <= SETQ 함수 부분이 수정 부분
          y (atof (substr rl 11 20))
          z (atof (substr rl 21 30))
    )
    (command "POINT" (list x y z))
  )
  (close fd)
)

5) 데이타 화일을 이용하여 3D POLYLINE 그리기

처음 입출력함수를 설명하기 위해 제작하기로 했던 예제를 완성해 보겠다. 우선, 데이타의 형태는 바로 앞에서 다루었던 다른 프로그래밍 언어에서 주로 사용되는 출력형식으로 가정하고 마찬가지로 DRAW-POINT 함수를 수정해 보겠다.

(defun draw-point (name / fd rl x y z)
  (setq fd (open name "r"))
  (command "3DPOLY") <= 3DPOLY 명령을 시작
  (while (setq rl (read-line fd))
    (setq x (atof (substr rl 1 10))
          y (atof (substr rl 11 20))
          z (atof (substr rl 21 30))
    )
    (command (list x y z)) <= 3차원 좌표값을 입력
  )
  (command "") <= 3DPOLY 명령을 종료
  (close fd)
)

이렇게 하여 완성된 프로그램이 리스트 1에 있다. 이 프로그램을 TEST2.DAT를 이용하여 수행한 결과는 그림 1과 같다.


그림 1. TEST2.DAT를 이용하여 그린 결과

3.2 시스템 변수 활용

AutoCAD에는 약 200개 이상의 시스템 변수들이 있는데 모두 프로그래밍을 하는데 매우 유용한 것들이다. 하나하나를 사용해보는 예제 프로그램을 작성해 보면서 공부하면 많은 도움이 되리라 생각한다. 여기서는 시스템 변수 SNAPBASE를 이용한 예제 프로그램을 작성해 보겠다.

1) AutoLISP에서 시스템 변수 다루는 방법

AutoLISP에서 시스템 변수의 값을 알고자 할때는 GETVAR 함수를 사용하며 시스템 변수의 값을 변경하고자 할때는 SETVAR 함수를 사용한다. 다음을 보면서 자주 쓰이는 시스템 변수 몇가지를 익히자.

AutoCAD 상에서 그림을 그리기 위해 임의의 점을 선택할때마다 작은 십자선이 그려진다면 다음 변수의 값을 확인하고 그 값이 1이면 0으로 변경하자. 그런 후에 다시 다른 명령을 사용해 보자.

Command: (getvar "BLIPMODE")
1
Command: (setvar "BLIPMODE" 0)
0

AutoLISP 프로그램에서 그림을 그릴때 COMMAND 함수를 통해 AutoCAD의 명령어를 사용하게 되는데 그 수행과정이 출력이 되어서 보기가 좋지 않을 때는 다음 변수의 값을 확인하고 0으로 변경하자. 그런 후에 다시 그 프로그램을 사용해 보자.

Command: (getvar "CMDECHO")
1
Command: (setvar "CMDECHO" 0)
0

이번 것은 좀 까다로운 것인데 AutoLISP 프로그램을 사용하여 그림을 그릴때 계산이 정확함에도 불구하고 가끔 엉뚱한 곳에 그림을 그리는 경우가 있다. 그런 경우는 보통 OSNAP 모드가 설정되어 있는 경우인데 그런 경우를 방지하기 위해서는 프로그램을 시작하기 전에 시스템 변수 OSMODE의 현재값을 저장해두고 0으로 변경한 후 프로그램이 종료될때 다시 원래의 값으로 돌려주는 과정을 거치는 것이 좋다. 다음의 간단한 예를 보자.

(defun C:MAIN ( / old ...)
  (setq old (getvar "OSMODE")) <= 현재값을 저장
  (setvar "OSMODE" 0) <= 0으로 변경
  . . . . . . <= 임의의 작업을 수행
  (setvar "OSMODE" old) <= 저장값으로 재설정
)

2) HATCH와 SNAPBASE

HATCH 명령어는 익히 잘 아는 명령어일테고 SNAPBASE는 SNAP이나 GRID의 기준점을 의미하는 것이다. 그럼, 이 둘은 어떤 식으로 관계되어 있는가를 알아보자. 먼저, 다음과 같이 BOX를 하나 그린후 가로 1, 세로 0.5의 간격으로 HATCH를 수행해 보자.

Command: LINE
From point: 1.8,1.8
To point: 5.8,1.8
To point: 5.8,5.8
To point: 1.8,5.8
To point: C
Command: HATCH
Pattern (? or name/U,style): U
Angle for crosshatch lines <0>:
Spacing between lines <1.0000>: 0.5
Double hatch area? <N>
Select objects: C
First corner: 1,1
Other corner: 6,6
4 found
Select objects:
Command: HATCH
Pattern (? or name/U,style) <U>:
Angle for crosshatch lines <0>: 90
Spacing between lines <0.5000>: 1
Double hatch area? <N>
Select objects: P
4 found
Select objects:


그림 2. SNAPBASE를 변경하기 전

수행한 결과는 그림 2와 같을 것이며 HATCH가 된 것이 BOX를 가로, 세로로 정확하게 등분하고 있는 상태가 아니다. 그럼, BOX를 가로, 세로로 정확하게 등분하기 위해서는 어떤 식으로 HATCH를 수행하여야 할까? 먼저, 앞서와 같이 되는 이유에 대해 간단히 설명하겠다. HATCH는 항상 어떤 지정된 기준점으로부터 시작된다. 여러개의 물체를 그려놓고 시험해 보면 금방 알 수 있는데 HATCH를 시킨 물체의 형상과는 상관없이 임의의 점에서의 HATCH의 형태는 항상 같게되는 것이다. 즉, 물체에 따라 필요한 기준점을 잡아주어야만 원하는 형태의 HATCH를 얻을 수 있다. 이 기준점이 바로 SNAPBASE라는 시스템 변수이며 이 변수의 값을 바꾸는 것이 HATCH의 기준을 변경시키는 결과가 된다. 그럼, 위의 과정을 SNAPBASE를 다음과 같이 변경한 후 다시 수행해 보자.

Command: SNAPBASE
New value for SNAPBASE
<0.0000,0.0000>: 1.8,1.8

위의 HATCH 과정을 다시 한번 수행해 보면 그림 3과 같은 결과를 얻을 수 있다.


그림 3. SNAPBASE를 변경한 후

3) BOX의 가로, 세로 등분선 그리기

이제, 위에서 살펴 본 내용을 토대로 예제 프로그램을 작성해 보자. 이 예제는 앞서와 같이 BOX를 그린 후 가로, 세로로 지정한 회수만큼 등분하는 선을 그려주는 프로그램이다. 만약 이 프로그램을 SNAPBASE의 기능을 모르는 상태에서 HATCH 명령을 사용하지 않고 만들게 될 경우에는 선을 따로 따로 여러번 그려주거나 한번 그린후에 ARRAY 명령을 사용해야만 한다. 이와 같이 시스템 변수를 잘 활용하면 프로그램을 훨씬 쉽게 그리고 빠르게 제작할 수 있다. 그럼, 프로그램의 각 작업 단계를 살펴보자.

  1. BOX를 그리기 위해 BOX의 두 모서리의 좌표값을 입력받는다. (GETPOINT와 GETCORNER 함수 사용)
  2. 가로, 세로의 등분회수를 입력받는다. (GETINT 함수 사용)
  3. 입력된 두 모서리의 좌표값을 사용하여 BOX를 그린다. (COMMAND 함수와 PLINE 명령 사용)
  4. 두점 사이의 가로, 세로 방향 길이를 지정한 회수로 나눈다.
  5. 시스템 변수 SNAPBASE의 현재값을 저장해 둔다.
  6. SNAPBASE를 BOX의 왼쪽 아래 좌표값으로 설정한다. (이때 주의할 점은 SNAPBASE는 2D 좌표값만 가능)
  7. 세로선을 HATCH 명령을 사용하여 그린다.
  8. 가로선을 HATCH 명령을 사용하여 그린다.
  9. SNAPBASE의 값을 저장해둔 값으로 변경한다.
위의 작업 단계를 코딩하여 완성한 프로그램이 리스트 2에 있다.

3.3 사용자 정의 ERROR 처리 함수

AutoLISP에서는 에러가 발생했을때 처리될 함수를 사용자가 정의할 수 있다. 이 함수의 이름은 *ERROR*이며 인수로는 에러에 관한 문자열이 주어진다. 따라서, 이 문자열에 따라 처리 내용을 결정할 수도 있다. 다음의 간단한 예를 보자.

(defun *ERROR* (msg)
  (princ "\nError: ")
  (princ msg)
  (princ)
)

위의 에러 처리 함수는 단순히 에러에 관한 문자열만을 출력하고 종료한다. 이제, 여기에 몇가지 에러에 대한 처리를 추가하자.

(defun *ERROR* (msg)
  (if (/= msg "Function cancelled")
    (if (= msg "quit / exit abort")
      (princ)
      (princ (strcat "\nError: " msg))
    )
    (princ)
  )
  (princ)
)

가끔, 에러가 발생한 위치를 알 수 없거나 할 경우에는 *ERROR* 함수를 nil로 설정한다. 그렇게 하면 에러가 발생한 문장을 포함하고 있는 최대 100개까지의 문장을 추적하여 출력해 준다. 이 내용을 바탕으로 에러를 하나하나 고쳐 나갈 수 있다. 따라서, 프로그램이 완성되기 전까지는 에러 처리 함수의 가동을 잠시 멈춰두는 것이 보다 편리하다. 아래에서 발생하기 쉬운 에러에 대한 원인과 그 처리방법을 간단히 설명하겠다.

1) AutoCAD rejected function

GETVAR나 SETVAR 함수 사용시에 주로 발생하며 시스템 변수명이 틀렸거나 맞지않은 자료형의 값으로 설정한 경우 또는 값을 변경할 수 없는 변수의 값을 변경하려한 경우가 주 원인이다. 다음의 예를 보자.

Command: (setvar "CMDECOH" 0) <= CMDECHO가 맞다.
error: AutoCAD rejected function

2) bad argument type

함수에 맞지 않은 인수가 전달된 것이 원인이다.

Command: (car 1) <= 인수는 리스트라야한다.
error: bad argument type

3) extra right paren

오른쪽 괄호의 개수가 더 많은 경우에 발생한다.

Command: (princ)) <= 오른쪽 괄호가 하나 더 많다.
error: extra right paren

4) Function cancelled

입력을 요구할때 사용자가 CTRL+C를 입력한 경우에 발생한다.

Command: (getpoint)
*Cancel* <= CTRL+C를 입력한다.
error: Function cancelled

5) incorrect number of arguments to a function

사용자 정의 함수에 전달되는 인수의 수가 맞지 않은 경우에 발생한다.

Command: (draw-grid) <= 인수없이 실행
error: incorrect number of arguments to a function

6) malformed list

화일로 부터 프로그램을 로딩하는 경우에 발생하며 리스트의 괄호가 부족한 것이 원인이 된다. 이 에러를 수정하기 위해 프로그램 코드를 다시 보는 것은 매우 힘든 일이므로 평소에 들여쓰기와 자기 나름내로의 정형화된 방식으로 코딩하여 에러 발생 소지를 제거하는 것이 가장 바람직하겠다.

7) null function

nil로 정의된 함수를 사용하려한 경우에 발생한다.

Command: (cad 1) <= CAD는 존재하지 않는 함수이다.
error: null function

8) quit / exit abort

QUIT나 EXIT 함수를 실행하면 발생한다. 이 두 함수는 프로그램의 디버깅(debugging)시에 필요한 위치에서 프로그램을 종료시키기 위해 주로 사용된다.

Command: (quit)
error: quit / exit abort



관련 자료
disk [1] DPOINT.LSP : 화일로부터 좌표값을 읽어서 3D POLYLINE을 그려주는 프로그램
disk [2] DGRID.LSP : BOX를 가로, 세로로 지정한 회수만큼 등분하는 선을 그려주는 프로그램

Last updated 2002-09-06 by choi@moon-sun.com
This page has been accessed : Counter times.
Home