Files
Aislo/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_CadViewer.ts
T
2026-07-19 14:27:50 +09:00

128 lines
4.4 KiB
TypeScript

/* =============================================================================
* 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;
}