배치면 초기설정 단계에서 PDF 도면을 업로드하면 AI(Gemini)가 외벽선을 추출해 캔버스에 그려주는 기능의 구현 현황
요청하신 “PDF 도면 업로드 → AI 분석 → 외벽선 자동 그리기” 기능은 새로 만들 필요가 없습니다. 이미 완전히 구현되어 동작 중입니다.
커밋 849f7a1d(2026-05-20)에서 Google Gemini 기반으로 전체 구현 → f8641079에서 일시 비활성화 → 현재 HEAD 0187b234에서 다시 활성화되어 있습니다.
따라서 이번 작업의 실제 성격은 “신규 개발”이 아니라 “기존 구현의 검증 · 정합성 보완 · 품질 개선”입니다. 아래에서 구현 위치, 데이터 흐름, 그리고 보강이 필요한 갭/리스크를 정리합니다.
gemini-3.1-pro-preview · @google/generative-ai ^0.24.1POST /api/gemini/floor-plan · 분석 후 파일 즉시 폐기QLine(outerLine) + QPolygon(WALL) 생성pdf.import.* / ...size.pdf.* 키usePdfImport 훅 + 배치면 초기설정 통합 + env/의존성/로케일. 총 12개 파일, +2137 라인.[클라이언트 · PlacementShapeSetting.jsx] 배치면 초기설정 모달 열기 │ ▼ "도면 파일 업로드" 라디오 선택 (INPUT_MODE.PDF) 파일 선택 handlePdfFileChange() # MIME=application/pdf, ≤20MB 검증 │ ▼ [저장] 버튼 → handleSaveBtn() → analyzePdfAndApply() FormData(file, unitHint=mm, pageMode, facadePages?, floorPages?) │ ▼ POST [서버 · /api/gemini/floor-plan/route.js] ① MIME / 20MB 재검증 ② GoogleAIFileManager.uploadFile() ──→ Gemini File API 업로드 ③ waitForFileActive() # PROCESSING→ACTIVE, 60s 타임아웃 ④ model.generateContent() # responseSchema 강제, temperature 0.1 ⑤ validateOuterline() # 정점 3~500개, 면적≠0 검증 ⑥ finally: deleteFile() # 성공/실패 무관 Gemini 파일 폐기 │ ▼ { outerline:[{x,y}…], unit:'mm', scale, confidence, notes } [클라이언트 · usePdfImport.js] applyOuterline() ⑦ toCanvasUnits() mm ÷ 10 → canvas unit ⑧ 중복점 필터 인접거리 < 0.5 제거 ⑨ fitAndCenter() 캔버스 60% / 최대 1500 내로 스케일 + 중앙정렬 ⑩ ensureCounterClockwise() CCW 정규화 ⑪ [기존 외벽선 존재 시] useSwal confirm "덮어쓰시겠습니까?" ⑫ clearExistingCanvasState() → setOuterLinePoints() (Recoil) ⑬ drawOuterLines() QLine(name='outerLine') 생성 ⑭ addPolygonByLines() QPolygon(name=WALL) 생성 ⑮ canvas.renderAll() │ ▼ roofSizeSet='1'(복시도)로 저장 → setSelectedMenu('outline') 외벽선 메뉴로 자동 전환 (수동 입력 흐름과 동일 지점에 합류)
POST 단일 핸들러)gemini-3.1-pro-preview (L9), 20MB (L7), 최대 500 정점 (L8).responseMimeType: 'application/json' + responseSchema(FLOOR_PLAN_SCHEMA, L11-31) + temperature: 0.1 (L156-160).validateOuterline() (L64-93) — 배열/정점수/숫자형/면적≠0(|area|<1 거부, L89). 빈 배열은 “평면도 아님”으로 정상 처리.finally에서 deleteFile()로 업로드 PDF 폐기 (L202-208) — 계획서 요구사항 “분석 후 폐기” 충족.process.env.GEMINI_API_KEY 직접 사용 (서버 전용, 클라이언트 미노출).MM_PER_CANVAS_UNIT = 10 (L11), MAX_DIM = 1500 (L12), MIN_CANVAS_UNIT_LENGTH = 0.5 (L13).toCanvasUnits(mm/10) → 중복점 필터 → fitAndCenter(화면 60% 맞춤·중앙정렬, L32-58) → ensureCounterClockwise(L27-30).drawOuterLines(L89-102)가 점 배열을 순회하며 QLine(stroke #000, width 3, name:'outerLine', idx) 생성 → canvas.add.addPolygonByLines(lines, { name: POLYGON_TYPE.WALL })로 WALL 폴리곤 생성(L148).clearExistingCanvasState로 관련 객체 정리.applyOuterline(mmPoints, { unit }), hasExistingOuterLine() 노출.INPUT_MODE = { SIZE_ROOF:'1', SIZE_ACTUAL:'2', PDF:'pdf' } (L34-38). 라디오 배열에 ra-pdf 항목 추가(L107-111).roofSizeSet은 ‘1’(복시도)로 저장(L113-121) → 저장 후 외벽선 메뉴로 자동 합류(L427-429).handlePdfFileChange(L128-142) — MIME/20MB 클라이언트 가드.analyzePdfAndApply(L144-198) — fetch → 에러/빈응답 분기 → applyOuterline 호출.package.json L23 — @google/generative-ai: ^0.24.1.GEMINI_API_KEY, GEMINI_MODEL — .env.development / .env.local.dev / .env.localhost / .env.production 4개 파일 모두 존재. (값은 본 리포트에 미기재)ko.json(L19-45) / ja.json 각 30개 메시지 — pdf.import.*(동작/에러) + modal.placement.initial.setting.size.pdf.*(UI 라벨/안내).구현 과정에서 초기 계획(docs/…구현 계획.md)과 달라진 설계 결정들입니다. 대부분 더 나은 통합 방향으로 수렴했습니다.
| 항목 | 계획서 | 실제 구현 (HEAD) | 평가 |
|---|---|---|---|
| UI 진입점 | floor-plan 상단 툴바 + 신규 PDF 아이콘 | 배치면 초기설정 모달의 치수 입력방법 라디오 옵션 | 변경 기존 흐름과 자연스럽게 통합 |
| 업로드 모달 | 별도 PdfImportModal.jsx (상태머신) | 없음 — PlacementShapeSetting에 통합 | 변경 모달 중첩 회피 |
| 메뉴 등록 | menuAtom에 pdf-import 항목 | 미등록 (툴바 방식 폐기로 불필요) | 정합 |
| 페이지 지정 | (없음) | 전체/지정 + 입면도·평면도 페이지 입력 | 추가 분석 속도·정확도 개선 |
| config 매핑 | config.*.js에 GEMINI 추가 | 미적용 — route가 process.env 직접 사용 | 상이 아래 리스크 R3 참조 |
| 자기교차 검증 | turf 로 self-intersection 검출 | 미구현 — 정점수·면적만 검증, 나머지는 프롬프트+CCW 의존 | 미구현 리스크 R2 |
| 거대 폴리곤 | Douglas–Peucker 단순화 or 거부 | 500 정점 초과 시 거부만 (단순화 없음) | 미구현 |
| 파일 폐기 | finally에서 삭제 보장 | 동일 구현 | 정합 |
기능은 동작하지만, “외벽선을 도면 그대로 정확히” 재현하려는 목표 관점에서 다음 항목들의 검증/보완을 권장합니다.
fitAndCenter()가 외벽선을 캔버스 60%(≈840 canvas unit, 약 8.4m)에 맞춰 추가 스케일링합니다. 도면이 그보다 크면 scale < 1이 곱해져 실제 치수 ↔ 캔버스 길이 정합이 깨집니다. 수동 입력 흐름은 실측/10을 그대로 좌표로 쓰므로, PDF 경로만 길이가 달라질 위험이 있습니다.
권장: 일반 주택(외곽 8.4m 초과 흔함) 기준으로 실측 비율을 보존하는 변환으로 재검토. 화면 맞춤이 필요하면 좌표 자체가 아니라 canvas viewport zoom/pan으로 처리하거나, 그리드 축척과 동일한 mm/unit 비율을 강제할 것.
서버 validateOuterline()은 정점수·면적만 본다. 자기교차는 Gemini 프롬프트 지침과 CCW 정규화에만 의존한다. 꼬인 폴리곤이 들어오면 QPolygon의 sortedPointLessEightPoint 정렬과 충돌해 형상이 깨질 수 있다.
권장: 계획서대로 turf(또는 경량 segment-intersection)로 서버/클라이언트에서 자기교차 거부. SizeSetting의 0-area 가드 정신과 일관되게 처리.
로케일 …pdf.file.info는 “외벽선과 지붕형상이 함께 생성”된다고 안내하지만, usePdfImport는 외벽선(QLine) + WALL 폴리곤까지만 만든다. 수동 흐름의 ccwCheck → RoofShapeSetting 자동 오픈 → drawRoofPolygon 단계가 PDF 경로에선 호출되지 않아 지붕면은 수동 작성이 필요하다.
권장: ①요청 목표(외벽선 그리기)만 보면 충족이므로 문구를 동작에 맞게 정정하거나, ②PDF 적용 후 기존 useRoofShapeSetting 자산을 재사용해 지붕 자동화를 확장. (요청 범위 명확화 필요)
CLAUDE.md는 “feature code에서 env 직접 읽지 말고 src/config에서 import”를 규정한다. route는 process.env를 직접 읽는다. 다만 서버 시크릿은 클라이언트 config로 노출하면 안 되므로 서버 라우트에서의 직접 사용은 정당화 가능한 회색지대다.
권장: 현행 유지 가능. 일관성을 원하면 서버 전용 config 헬퍼로 묶되 클라이언트 번들 유입이 없도록 주의.
프로젝트에 테스트 러너가 없고(CLAUDE.md), Gemini 출력은 비결정적이다. 회귀 검증은 수동 의존. 분석 소요(최대 60s 타임아웃)에 비해 진행률 UX가 약하다.
권장: 좌표 변환부(toCanvasUnits/fitAndCenter/ensureCounterClockwise)는 순수 함수이므로 단위 테스트로 분리 검증 가능. 분석 중 스피너/단계 표시 추가 검토.
| 경로 | 구분 | 역할 / 핵심 라인 |
|---|---|---|
| src/app/api/gemini/floor-plan/route.js | 신규 | Gemini 분석 서버 라우트 · POST(L111) · validate(L64) · finally 폐기(L202) |
| src/hooks/pdf-import/usePdfImport.js | 신규 | 좌표 변환·캔버스 반영 · applyOuterline(L104) · drawOuterLines(L89) |
| src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx | 수정 | UI 통합 · INPUT_MODE(L34) · analyzePdfAndApply(L144) · UI(L486-591) |
| package.json | 수정 | @google/generative-ai ^0.24.1 (L23) |
| .env.{development,local.dev,localhost,production} | 수정 | GEMINI_API_KEY · GEMINI_MODEL |
| src/locales/ko.json · ja.json | 수정 | pdf.import.* / …size.pdf.* (각 30개) |
| docs/PDF Gemini 평면도 분석 기능 구현 계획.md / .html | 문서 | 초기 설계 문서 (2026-05-18) |
“새 기능 개발”이 아니라 기존 구현의 검증·보완으로 방향을 잡는 것을 권합니다. 우선순위 순:
fitAndCenter 스케일 전략부터 재설계.yarn lint)에 맞춰 마무리.실제 코드 보완에 착수하기 전, 위 R1·R3는 제품 의사결정이 필요하므로 먼저 합의하는 것을 권합니다. 합의되면 brainstorming → 구현 → 검증 순으로 진행하겠습니다.