오픈소스로 DWG 도면 도구 만들기 — AttoGrid 개발기
데이터센터 전기 도면은 대부분 AutoCAD의 .dwg 파일로 옵니다. 문제는 이 포맷이 비공개라는 것, 그리고 도면 안의 텍스트가 중국어라는 것이었습니다. “AutoCAD 없이, 오픈소스만으로 DWG를 읽고 → 중국어를 한국어로 번역하고 → 전압 같은 전기 구성까지 검증할 수 있을까?”라는 질문에서 시작한 프로젝트가 AttoGrid입니다.
몇 달간 실제 도면(AutoCAD 2007, 액침냉각·소방·냉난방 전기 도면, 4.8 MB)으로 검증하며 v1.0.0까지 온 과정을 정리해 봅니다.
DWG 읽기: 두 가지 경로를 실파일로 비교하다
오픈소스 DWG 라이브러리는 사실상 LibreDWG 하나입니다. 읽기 경로는 두 가지가 있는데, 실제 도면으로 비교해 보니 결과가 갈렸습니다.
| 경로 | 결과 |
|---|---|
dwg2dxf로 DXF 변환 후 파싱 |
복잡한 실도면에서 BLOCK_HEADER 에러로 DXF가 잘림 |
dwgread -O JSON으로 덤프 후 직접 파싱 |
115,547개 객체 전부 보존 |
교훈: 중간 포맷 변환을 거치지 말고, 원본에서 가장 가까운 덤프를 직접 파싱하라. DXF 변환은 도구의 지원 범위를 벗어나는 객체를 만나면 조용히 잘라먹지만, JSON 덤프는 일단 다 뱉어놓고 파싱을 내 쪽에서 통제할 수 있습니다.
전압값은 폰트 코드 안에 숨어 있었다
도면에서 220V 같은 전압 텍스트를 뽑아 검증하려고 보니, MTEXT 엔티티의 값이 이렇게 생겼습니다.
{\Fxx|c0;220V\F..;电源线}
전압값과 중국어 라벨이 폰트 전환 코드(\F...;) 사이에 끼어 있는 겁니다. 그대로 정규식을 돌리면 아무것도 안 잡힙니다. MTEXT 포맷 코드를 걷어내는 clean_mtext() 정제 단계를 파이프라인 맨 앞에 두고 나서야 텍스트 추출·번역·전압 검증이 전부 제대로 동작했습니다. CAD 데이터를 다룬다면 “텍스트가 곧 텍스트가 아니다”라는 걸 기억해 둘 만합니다.
번역: 엔진은 갈아끼울 수 있게
중→한 번역은 상황에 따라 요구가 달랐습니다. 인터넷이 되는 환경에선 DeepL이 품질이 좋고, 폐쇄망에선 오프라인 엔진이 필요하고, 로컬 GPU가 있으면 LLM(Ollama/vLLM)을 쓰고 싶고. 그래서 번역기를 인터페이스로 추상화하고 엔진을 선택하게 했습니다.
- DeepL — 온라인, 고품질
- argos-translate — 오프라인·무료 (zh→en→ko 경유)
- Ollama / vLLM — 로컬 LLM, OpenAI 호환 API
여기에 电源线 → 전원선 같은 전문용어 사전을 앞단에 두어 도메인 용어는 엔진과 무관하게 일관되게 번역되도록 했습니다. 번역 결과는 도면 SVG 위에 오버레이로 얹어서, 원본과 번역본을 버튼 하나로 오가며 볼 수 있습니다.
도면 렌더링: 래스터가 아니라 벡터로
처음엔 PNG로 렌더해서 이미지 팬/줌을 했는데, 도면은 확대해서 보는 물건이라 금방 픽셀이 깨졌습니다. 최종적으로는 SVG의 viewBox를 직접 조작하는 팬/줌으로 바꿨습니다. 벡터라 아무리 확대해도 선명하고, non-scaling-stroke로 선 굵기도 줌 레벨과 무관하게 유지됩니다. 여기에 얹은 기능들:
- 비표준 전압 위치를 도면 위에 마커로 표시 (전압 검증 결과와 연동)
- 프레임 자동 감지 / 공간 클러스터링 / NxM 격자 기준 구획 분할 후 이미지컷 추출
- 닫힌 윤곽을 three.js로 압출해서 보는 간단한 2D→3D 뷰
안 되는 건 안 된다고 선을 긋기
DWG 쓰기(저장)는 오픈소스로는 불안정해서 일찍 포기했습니다. 대신 편집 결과를 DXF/JSON으로 저장하는 것으로 범위를 정했습니다. 기능 표에 ⛔를 명시적으로 남겨두는 것 — 안 되는 걸 되는 척하지 않는 것도 도구의 신뢰성이라고 생각합니다.
마치며
데스크톱 앱(pywebview)과 웹 서버 모드(Flask + nginx), CLI까지 한 코드베이스에서 돌아가는 v1.0.0을 릴리스했습니다. 비공개 바이너리 포맷, 폰트 코드에 묻힌 텍스트, 폐쇄망 번역 같은 제약이 겹친 프로젝트였지만, “실제 파일로 일찍, 자주 검증한다”는 원칙 덕분에 잘못된 경로(DXF 변환)를 초반에 버릴 수 있었던 게 가장 컸습니다.