/* ============================================================================= * B07_wf4_DesignDetail_UI_CadViewer.ts * WebCAD PoC 뷰어 — cad-simple-viewer(MIT) 부트스트랩 + 기하 JSON 렌더링 * * 아키텍처 (PLAN.md WebCAD 합의): * - 브라우저는 DXF/DWG를 파싱하지 않는다. GPL 컨버터는 vite 별칭 스텁으로 차단. * - 서버(ezdxf)가 보낸 기하 JSON을 data-model(MIT) API로 도면 DB에 직접 구성. * - AcApDocManager는 싱글톤이므로 뷰어 컨테이너를 모듈 수준에 유지하고 * 페이지 재진입 시 새 레이아웃에 재부착한다. * ========================================================================== */ import { AcApDocManager } from "@mlightcad/cad-simple-viewer"; import { acdbHostApplicationServices, AcCmColor, AcDbLayerTableRecord, AcDbLine, AcDbPolyline, AcDbText, AcGePoint2d, AcGePoint3d, } from "@mlightcad/data-model"; import type { CadDrawingJson, CadEntityJson } from "./B07_wf4_DesignDetail_Api_Fetch"; /** 뷰어 캔버스를 담는 영속 컨테이너 (SPA 라우팅 간 유지) */ let viewerHolder: HTMLDivElement | null = null; /** 도면 JSON이 이미 DB에 적재되었는지 여부 (PoC: 1회 적재) */ let drawingLoaded = false; function ensureViewerHolder(): HTMLDivElement { if (!viewerHolder) { viewerHolder = document.createElement("div"); viewerHolder.className = "b07-cad-canvas"; } return viewerHolder; } function ensureDocManager(holder: HTMLDivElement): AcApDocManager { try { return AcApDocManager.instance; } catch { // 최초 1회 생성 — 워커 미배포 환경이므로 MTEXT는 메인 스레드 렌더링 사용. AcApDocManager.createInstance({ container: holder, autoResize: true, useMainThreadDraw: true, }); return AcApDocManager.instance; } } function toEntity(json: CadEntityJson): AcDbLine | AcDbPolyline | AcDbText | null { if (json.type === "LWPOLYLINE" && json.points && json.points.length >= 2) { const polyline = new AcDbPolyline(); json.points.forEach(([x, y], index) => { polyline.addVertexAt(index, new AcGePoint2d(x, y)); }); polyline.closed = json.closed ?? false; polyline.layer = json.layer; return polyline; } if (json.type === "LINE" && json.start && json.end) { const line = new AcDbLine( new AcGePoint3d(json.start[0], json.start[1], 0), new AcGePoint3d(json.end[0], json.end[1], 0), ); line.layer = json.layer; return line; } if (json.type === "TEXT" && json.text && json.insert) { const text = new AcDbText(); text.textString = json.text; text.position = new AcGePoint3d(json.insert[0], json.insert[1], 0); text.height = json.height ?? 2.5; text.rotation = ((json.rotation ?? 0) * Math.PI) / 180; text.layer = json.layer; return text; } return null; } function loadDrawingIntoDatabase(manager: AcApDocManager, drawing: CadDrawingJson): number { const db = manager.curDocument.database; db.createDefaultData(); for (const layerJson of drawing.layers) { const color = new AcCmColor(); color.colorIndex = layerJson.color_aci; db.tables.layerTable.add(new AcDbLayerTableRecord({ name: layerJson.name, color })); } const modelSpace = db.tables.blockTable.modelSpace; let appended = 0; for (const entityJson of drawing.entities) { const entity = toEntity(entityJson); if (entity) { modelSpace.appendEntity(entity); appended += 1; } } return appended; } /** * PoC CAD 뷰어를 host에 부착하고 서버 기하 JSON을 렌더링한다. * @returns 적재된 엔티티 수 (재진입 시 0 — 이미 적재됨) */ export function mountPocCadViewer(host: HTMLElement, drawing: CadDrawingJson): number { const holder = ensureViewerHolder(); host.append(holder); const manager = ensureDocManager(holder); let appended = 0; if (!drawingLoaded) { appended = loadDrawingIntoDatabase(manager, drawing); drawingLoaded = true; // 파일 오픈 흐름이 아니므로 layoutSwitched 이벤트를 직접 발화해야 // 뷰가 레이아웃 뷰 생성·가시화·초기 줌을 수행한다 (미발화 시 빈 화면). const db = manager.curDocument.database; const modelSpaceBtrId = db.tables.blockTable.modelSpace.objectId; manager.setActiveLayout(); acdbHostApplicationServices().layoutManager.setCurrentLayoutBtrId(modelSpaceBtrId, db); manager.curView.zoomToFitDrawing(); } return appended; }