diff --git a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_CadViewer.ts b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_CadViewer.ts index 212b8149..8da75bbc 100644 --- a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_CadViewer.ts +++ b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_CadViewer.ts @@ -11,6 +11,7 @@ import { AcApDocManager } from "@mlightcad/cad-simple-viewer"; import { + acdbHostApplicationServices, AcCmColor, AcDbLayerTableRecord, AcDbLine, @@ -113,7 +114,14 @@ export function mountPocCadViewer(host: HTMLElement, drawing: CadDrawingJson): n 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(); } - manager.curView.zoomToFitDrawing(); return appended; } diff --git a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts index a2c104ef..687c4d47 100644 --- a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts +++ b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Page.ts @@ -1,10 +1,12 @@ /* ============================================================================= * B07_wf4_DesignDetail_UI_Page.ts - * 로그인 후 07: 4차 워크플로우 (상세 설계) — WebCAD PoC + * 로그인 후 07: 4차 워크플로우 (상세 설계) — WebCAD 전체 앱 임베드 * - * 레이아웃 (PLAN.md WebCAD 사용자 지시): - * - 사이드 패널: 빈 상태 유지 (B06→B07 전달 데이터 스펙 미확정) - * - 상세 페이지 영역: CAD 뷰어 (서버 기하 JSON 렌더링, DXF 파싱 없음) + * 방향 전환 (PLAN.md 2026-07-19): 코어 조립이 아니라 완성된 오픈소스 CAD 앱 + * (external_program/cad-viewer-example, GPL 워커 제외 빌드)을 /cadviewer 정적 + * 경로로 서빙하고 iframe으로 임베드한다. 데이터 연동은 후속 단계. + * + * 레이아웃 (사용자 지시): 사이드 패널 빈 상태 유지 + 상세 영역 CAD 화면. * 제약 준수 (frontend.md §2 3단 레이아웃): createWorkflowLayout 재사용. * ========================================================================== */ @@ -19,8 +21,9 @@ import { WORKFLOW_STEP_ROUTES, type WorkflowState, } from "../A00_Common/b_workflow_nav"; -import { fetchPocSampleDrawing } from "./B07_wf4_DesignDetail_Api_Fetch"; -import { mountPocCadViewer } from "./B07_wf4_DesignDetail_UI_CadViewer"; + +/** WebCAD 전체 앱 정적 경로 (main.py /cadviewer 마운트, dev는 vite proxy 위임) */ +const CAD_VIEWER_APP_URL = "/cadviewer/index.html"; /** locale 헬퍼 */ function L(key: keyof typeof ui_locales): string { @@ -57,10 +60,11 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { const cadHost = document.createElement("div"); cadHost.className = "b07-cad-host"; - const status = document.createElement("div"); - status.className = "b07-cad-status"; - status.textContent = L("B07_Cad_Loading"); - cadHost.append(status); + const frame = document.createElement("iframe"); + frame.className = "b07-cad-frame"; + frame.src = CAD_VIEWER_APP_URL; + frame.title = L("B07_Design_Title"); + cadHost.append(frame); const layout = createWorkflowLayout({ title: L("B07_Design_Title"), @@ -77,14 +81,4 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise { }); layout.root.classList.add("b07-design-layout"); root.replaceChildren(layout.root); - - try { - const drawing = await fetchPocSampleDrawing(); - mountPocCadViewer(cadHost, drawing); - status.remove(); - } catch (error) { - status.textContent = `${L("B07_Cad_Load_Failed")} ${ - error instanceof Error ? error.message : "" - }`.trim(); - } } diff --git a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Style.css b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Style.css index f4b810af..9b60f03d 100644 --- a/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Style.css +++ b/B07_wf4_DesignDetail/B07_wf4_DesignDetail_UI_Style.css @@ -61,6 +61,15 @@ display: block; } +/* WebCAD 전체 앱 임베드 (iframe) */ +.b07-cad-frame { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: 0; +} + /* 로딩·오류 상태 메시지 */ .b07-cad-status { position: absolute; diff --git a/external_program/cad-viewer b/external_program/cad-viewer new file mode 160000 index 00000000..7b9a8084 --- /dev/null +++ b/external_program/cad-viewer @@ -0,0 +1 @@ +Subproject commit 7b9a8084d7b048b7000aa178ae78109d84078cb0 diff --git a/external_program/cad-viewer-example b/external_program/cad-viewer-example new file mode 160000 index 00000000..482a061c --- /dev/null +++ b/external_program/cad-viewer-example @@ -0,0 +1 @@ +Subproject commit 482a061c4462dde0fd7b1d10c9242932e09b4e56 diff --git a/main.py b/main.py index 0a90c515..ec23e102 100644 --- a/main.py +++ b/main.py @@ -223,6 +223,15 @@ app.mount( ) logger.info(f"✓ 정적 파일 서빙 경로 등록: {STATIC_URL} → {STATIC_DIR}") +# WebCAD 전체 앱 (external_program/cad-viewer-example 빌드본, GPL 워커 제외) — B07 iframe용 +CADVIEWER_DIST_DIR = str(Path(__file__).parent / "external_program" / "cad-viewer-example" / "dist") +app.mount( + "/cadviewer", + StaticFiles(directory=CADVIEWER_DIST_DIR, html=True, check_dir=False), + name="cadviewer", +) +logger.info(f"✓ WebCAD 정적 서빙 경로 등록: /cadviewer → {CADVIEWER_DIST_DIR}") + # ───────────────────────────────────────────────────────────────────────── # 기본 엔드포인트 # ───────────────────────────────────────────────────────────────────────── diff --git a/scratch/test_vworld_download.py b/scratch/test_vworld_download.py new file mode 100644 index 00000000..33c78b47 --- /dev/null +++ b/scratch/test_vworld_download.py @@ -0,0 +1,56 @@ +import sys +import traceback +from pathlib import Path +import numpy as np + +# Add project root to sys.path +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(PROJECT_ROOT)) + +from B04_wf1_Surface.B04_wf1_Surface_Engine_VWorld import download_vworld_satellite_map +from B04_wf1_Surface.B04_wf1_Surface_Engine_GisVector import download_all_gis_vectors + +PRJ_PATH = PROJECT_ROOT / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B03_FileInput/input/prj/result.prj" +NPZ_PATH = PROJECT_ROOT / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B04_wf1_Surface/processed/ground_points_csf.npz" +OUTPUT_DIR = PROJECT_ROOT / "storage/1/3/acb9170b-9ac8-49b3-82a0-51cfa32bb42d/B04_wf1_Surface/processed" + +def main(): + print("Loading NPZ to get bounds...") + with np.load(NPZ_PATH) as data: + bounds = data["bounds"] + print("Raw bounds:", bounds) + + bounds_dict = { + "x": [float(bounds[0, 0]), float(bounds[0, 1])], + "y": [float(bounds[1, 0]), float(bounds[1, 1])], + "z": [float(bounds[2, 0]), float(bounds[2, 1])], + } + + print("\n--- Test 1: VWorld Satellite Map Download ---") + try: + res = download_vworld_satellite_map( + prj_path=PRJ_PATH, + bounds=bounds_dict, + output_dir=OUTPUT_DIR, + layer_name="Satellite", + ext="jpeg" + ) + print("Download Result:", res) + except Exception as e: + print("VWorld Download Failed:") + traceback.print_exc() + + print("\n--- Test 2: GIS Vector Download ---") + try: + download_all_gis_vectors( + prj_path=PRJ_PATH, + bounds_meter=bounds_dict, + output_dir=OUTPUT_DIR + ) + print("GIS Vector Download completed.") + except Exception as e: + print("GIS Download Failed:") + traceback.print_exc() + +if __name__ == "__main__": + main() diff --git a/ui_template/ui_template_theme.css b/ui_template/ui_template_theme.css index 20729992..0b1c33ec 100644 --- a/ui_template/ui_template_theme.css +++ b/ui_template/ui_template_theme.css @@ -170,6 +170,12 @@ --card-padding: 24px; --element-gap: 8px; + /* Scrollbar */ + --color-scrollbar-track: color-mix(in srgb, var(--color-text) 9%, transparent); + --color-scrollbar-thumb: color-mix(in srgb, var(--color-text) 38%, transparent); + --color-scrollbar-thumb-hover: color-mix(in srgb, var(--color-text) 56%, transparent); + --color-scrollbar-thumb-active: color-mix(in srgb, var(--color-text) 72%, transparent); + /* 워크플로우 3단 레이아웃 (frontend.md §2) */ --wf-header-height: 56px; /* 상단: 타이틀 + 진행 단계 */ --wf-left-panel-width: 320px; /* 좌측: 입력 폼 (너비 고정) */ @@ -271,6 +277,44 @@ box-sizing: border-box; } +/* Keep the platform scrollbar footprint while giving every scroll container a + * consistent translucent treatment. Width/height are intentionally untouched. */ +* { + scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); + scrollbar-width: auto; +} + +@supports selector(::-webkit-scrollbar) { + * { + scrollbar-color: auto; + } + + *::-webkit-scrollbar-track { + background-color: var(--color-scrollbar-track); + } + + *::-webkit-scrollbar-thumb { + border: 3px solid transparent; + border-radius: var(--radius-pills); + background-color: var(--color-scrollbar-thumb); + background-clip: padding-box; + } + + *::-webkit-scrollbar-thumb:hover { + background-color: var(--color-scrollbar-thumb-hover); + } + + *::-webkit-scrollbar-thumb:active { + background-color: var(--color-scrollbar-thumb-active); + } +} + +@media (forced-colors: active) { + * { + scrollbar-color: auto; + } +} + html, body { margin: 0; diff --git a/vite.config.ts b/vite.config.ts index 9d390bf8..7c4194c1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,6 +41,11 @@ export default { target: "http://localhost:8000", changeOrigin: true, }, + // WebCAD 전체 앱 (B07 iframe) — FastAPI 정적 마운트로 위임 + "/cadviewer": { + target: "http://localhost:8000", + changeOrigin: true, + }, }, }, build: {