CAD 도면 94장을 Vision으로 읽다 마샬링 버그를 잡다

8월 29, 2026 · 노이반
현장의 기록: AI Engineer / Forward Deployed Engineer — 18편
한 줄 요약

같은 실패 신호(제네릭 타입 오류)가 'API 한계'와 '클라이언트 버그' 양쪽 모두에서 똑같이 보일 수 있다 — 이 둘을 구분하는 유일한 방법은 같은 호출을 다른 클라이언트로 재현해 결과가 바뀌는지 보는 것뿐이다.

조사 파이프라인: Vision 분석에서 작동하는 우회 방법까지
조사 파이프라인: Vision 분석에서 작동하는 우회 방법까지

배경

생산라인 하나의 전체 공정 순서를 담은 CAD 도면 검토 요청이 들어왔다. 파일 하나에 레이아웃 시트 94장, 그 시트들이 공유하는 ModelSpace 안에는 엔티티가 약 23,700개. 요청 자체는 단순했다 — 어느 스테이션에서 무슨 작업이 이루어지는지 정리해서 팀 사내 위키에 올려달라는 것. 제약은 복잡함이 아니라 규모였다. CAD 뷰어에서 94개 탭을 일일이 열어 라벨을 옮겨 적는 건 누구도 원하지 않는 일이었고, 각 레이아웃 시트를 이미지로 렌더링해 Vision 모델에 스테이션별 요약을 맡기는 게 자연스러운 선택이었다.

시작이 된 질문

93개 시트(1개는 표지라 제외)를 Vision 모델로 분석해 스테이션별 작업 요약표를 위키에 올린 뒤, 요청자에게서 자연스러운 후속 질문이 나왔다. "이 정도로 도면을 잘 읽어낼 수 있으면, 반대로 도면을 직접 만들 수도 있지 않을까?" 단발성 문서 요약 작업이 CAD 파일 내부 구조와 COM 자동화까지 파고드는 타당성 조사로 확장된 순간이었다.

첫 번째 시도, 그리고 두 번째 시도가 막힌 지점

기존 레이아웃 하나를 복제해 템플릿으로 재활용하는 게 자연스러운 첫 접근이었다. CAD 애플리케이션의 매크로 스크립팅 언어로 명령줄 스타일 복사 명령을 보내는 방식으로 구현했는데, 안정적으로 동작하지 않았다. 명령줄 프롬프트 상태와 스크립트의 기대가 정확히 맞아떨어져야 했고, 반복 호출 시 응답이 정리되지 않은 채 그대로 멈추는 경우가 간헐적으로 나왔다. 타임아웃을 늘리거나 재시도를 추가하는 식으로 땜질하지 않고 이 접근 자체를 포기했다 — GUI 지향 명령 인터프리터를 프로세스 밖에서 조종하는 설계 자체가 쌓아 올릴 기반이 아니었다.

두 번째 시도는 CAD 애플리케이션의 네이티브 COM API로 전환하는 것이었다. Layouts.Add로 레이아웃을 만들고 CopyObjects로 콘텐츠를 옮기는 방식은 행(hang) 없이 결정적으로 동작했다. 그런데 여기서 소스 파일 자체의 구조가 드러났다: 템플릿이 아닌 93개 레이아웃 중 92개가 공유 ModelSpace를 참조하는 뷰포트 객체 하나만 담고 있었다 — 자기완결적 도면이 아니었다. 레이아웃의 실제 내용을 재생성하려면 그 레이아웃이 ModelSpace의 어느 영역을 보고 있는지부터 알아야 했는데, 바로 이 지점에서 막혔다. 엔티티 중심점·바운딩 박스 같은 기본 좌표 속성을 읽으려 하자 사용 중이던 스크립팅 언어에서 제네릭 타입 불일치 오류가 났다.

조사 — 가정을 검증으로 바꾸다

언뜻 오래되고 문서가 부실한 COM API에서 흔한 벽처럼 보였다 — "이 속성이 이 객체 타입에는 아예 구현이 안 됐을 수도 있다"는 가정이 자연스러웠다. 하지만 그 가정을 그대로 받아들이는 대신 직접 검증했다. 같은 CAD 문서, 같은 실행 중인 애플리케이션을 대상으로, 같은 COM 호출을 네이티브 COM 바인딩을 지원하는 범용 언어로 다시 작성했다. 즉시, 정확하게 동작했다.

' Before — CAD 스크립팅 엔진, 배열 타입 COM 반환값에서 실패
Set ent = doc.ModelSpace.Item(0)
center = ent.Center   ' 제네릭 타입 불일치(err=13)
# After — 범용 언어, 네이티브 COM 바인딩
ent = doc.ModelSpace.Item(0)
center = ent.Center   # (145.3, 101.8, 0.0) — 정상 반환
min_pt, max_pt = ent.GetBoundingBox()

결론: API 한계가 아니라, CAD 애플리케이션의 COM 인터페이스가 반환하는 배열 타입(SAFEARRAY) 값을 스크립팅 언어의 COM 바인딩 레이어가 처리하지 못하는 마샬링 버그였다. 스칼라·문자열 값은 그 언어에서도 내내 문제없었다 — 배열 형태로 돌아오는 값만 마샬링에 실패했다. 언어를 바꾼 건 자동화 대상 도구의 한계를 우회한 게 아니라, 자동화를 수행하는 클라이언트 자체의 한계를 우회한 것이었다.

정직하게 짚어야 할 부분도 있다: 뷰포트의 뷰 중심점·뷰 대상점·뷰 높이 이 세 속성은 정상 동작하는 COM 바인딩에서도 계속 별개 오류로 실패했다. 레이아웃 1개짜리 초기 테스트는 성공한 것처럼 보였지만, 나머지 92개 전체에서 반복하자 실제 활성 레이아웃과 무관하게 모두 같은 값을 반환했다 — API가 뷰를 재계산하지 않고 이전 상태를 그대로 돌려주고 있다는 뜻이었다. 이건 마샬링 문제가 아니라 벤더 도구 자체의 해결되지 않은 한계였고, 조사 기간 안에서는 우회 방법을 더 찾지 못했다.

좌표 접근이 정상화된 뒤, 텍스트 라벨의 X좌표 간격이 넓은 표본에서 균일하다는 걸 이용해 후보 그리드를 만드는 접근을 시도했는데, 조사 후반에 전체 엔티티를 다시 살펴보니 이건 실제 레이아웃 경계와 무관한 훨씬 큰 규모의 참조 격자였다는 게 드러났다. 처음에 깔끔해 보이는 신호도 재검증이 필요했고, 이 부정적 결과도 숨기지 않고 그대로 기록했다.

별개의 작업에서 잡아낸 설계 결함

관련은 있지만 별개인 작업으로, 여러 제품 기종이 공유하는 생산라인의 자동화 개조 사양서를 같은 방식(Vision 판독 + 교차검증)으로 감사했다. 사양서 안에는 하위 시스템이 제품 기종을 런타임에 구분할 수 있도록 기종마다 고유한 검지홀 패턴을 정의한 매트릭스가 있었는데, 이걸 셀 단위로 읽고 행끼리 비교하자 서로 다른 두 기종이 완전히 동일한 검지 신호값에 매핑돼 있었다. 두 문제 행은 인접하지 않았고, 표는 큰 스프레드시트 안의 비슷하게 생긴 여러 매트릭스 중 하나였으며, 개별 셀만 보면 아무 이상이 없었다 — 결함은 오직 행과 행 사이의 비교에서만 드러났다. 페이지 단위로 훑어보는 방식이었다면 십중팔구 놓쳤을 결함이었다.

검증

  • 위키 게시물: 94장 중 93장 처리 완료를 직접 확인, 요약표 게시·검토됨.
  • COM 마샬링 진단: 같은 객체·같은 인터페이스에 대해 스크립팅 엔진(실패, err=13)과 네이티브 COM 바인딩 언어(성공)를 나란히 실행해 비교 — 추측이 아니라 통제된 대조.
  • 뷰 속성 한계: 레이아웃 1개의 우연한 성공에 속지 않으려고 92개 레이아웃 전체에 반복 적용해 진짜 API 공백임을 확인.
  • 그리드 클러스터링 가설: 표본이 아니라 약 23,700개 엔티티 전체로 테스트하고, 부정적 결과를 그대로 보고.
  • 검지홀 매트릭스 결함: 자동 셀 단위 교차검증으로 확인, 수동 스캔이 아니라 구조화된 데이터 검증 방식으로 잡아냄.
얻은 교훈

실패 신호 하나만 보면 "벤더가 구현 안 한 것"과 "호출하는 언어가 이 반환 타입을 못 다루는 것"은 똑같이 생겼다. 이 둘은 완전히 다른 다음 행동을 요구한다 — 하나는 멈추고 영구적 한계를 우회하는 것, 다른 하나는 언어를 바꿔서 계속 가는 것. 구분하는 유일한 방법은 같은 호출을 다른 클라이언트에서 재현해 결과가 바뀌는지 보는 것뿐이었다. Vision 모델을 무조건 믿는 오라클이 아니라 검증 시스템의 한 구성요소로 다루는 태도, 그리고 끝내 풀리지 않은 부분은 풀렸다고 포장하지 않고 그대로 보고하는 태도 — 이 두 가지가 이번 조사에서 실제로 값어치를 만든 부분이었다.

Advertisement

첫 댓글을 남겨보세요