From c2060f3b6f8d18d65db9ecf1ef148b8d5d5c30f5 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 30 Aug 2026 16:40:31 +0900 Subject: [PATCH] =?UTF-8?q?feat(B04):=20=EB=8F=84=EC=97=BD=20=EC=84=9C?= =?UTF-8?q?=ED=94=BC=EC=8A=A4=20=ED=94=84=EB=A6=AC=EB=B7=B0=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EB=A5=BC=20NURBS=20=EA=B3=A1=EB=A9=B4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A7=8C=EB=93=A0=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 격자 삼각형을 그대로 이으면 1m 셀 경계가 톱니로 보인다. LAS NURBS 모델이 쓰는 fit_nurbs_spline·evaluate_nurbs_spline을 그대로 걸어 곡면을 평가한 뒤 메시를 만든다. 제어 간격은 같은 config(SURFACE_NURBS_PATCH_SIZE_M ÷ 축당 제어점 수, 최소 1셀). 무효 셀은 최근접 표고로 메워 적합하고 원래 유효 마스크로 다시 도려낸다. 적합이 실패하면 격자 메시로 되돌린다. 표고 정본(npz)은 격자 그대로다 — 종·횡단·배수는 격자를 샘플링하므로 계산값에 영향이 없다. 실측에서도 8종 지표가 모두 동일했다. 생성 83s → 108s. Co-Authored-By: Claude Opus 5 (1M context) --- .../B04_PreProcess_Engine_SheetSurface.py | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/B04_PreProcess/B04_PreProcess_Engine_SheetSurface.py b/B04_PreProcess/B04_PreProcess_Engine_SheetSurface.py index d8fda608..caae8c1c 100644 --- a/B04_PreProcess/B04_PreProcess_Engine_SheetSurface.py +++ b/B04_PreProcess/B04_PreProcess_Engine_SheetSurface.py @@ -27,6 +27,8 @@ from pyproj import Transformer from B04_PreProcess.B04_PreProcess_Engine_ModelContext import ( atomic_npz, clip_and_compact_mesh, + evaluate_nurbs_spline, + fit_nurbs_spline, grid_faces, grid_vertices, write_glb, @@ -41,6 +43,9 @@ from config.config_system import ( SHEET_SURFACE_MARGIN_M, SHEET_SURFACE_METHODS, SURFACE_MAX_PREVIEW_VERTICES, + SURFACE_NURBS_CONTROL_POINTS_PER_AXIS, + SURFACE_NURBS_DEGREE, + SURFACE_NURBS_PATCH_SIZE_M, ) logger = logging.getLogger(__name__) @@ -98,13 +103,45 @@ def _load_features_metric( def _preview_mesh( x: np.ndarray, y: np.ndarray, z: np.ndarray, valid: np.ndarray ) -> tuple[np.ndarray, np.ndarray]: - """프리뷰용 정점·면 — 정점 수가 상한을 넘으면 격자를 성기게 딴다.""" + """프리뷰용 정점·면 — 격자를 그대로 잇지 않고 **NURBS(B-spline) 곡면**으로 만든다. + + 격자 삼각형을 그대로 쓰면 1m 셀 경계가 계단처럼 보인다. LAS 파이프라인이 쓰는 + 것과 같은 곡면 적합(`fit_nurbs_spline`·`evaluate_nurbs_spline`)을 걸어 매끈한 + 면을 얻는다(2026-08-30 사용자 지시). **표고 정본(npz)은 격자 그대로**이고 여기서 + 바꾸는 것은 화면에 보이는 메시뿐이다 — 종·횡단은 격자를 샘플링한다. + + 적합에 쓰는 제어 간격은 LAS NURBS와 같은 config 값(패치 크기 ÷ 축당 제어점 수)이며, + 제어 격자가 격자보다 성기지 않도록 최소 1셀로 묶는다. + """ stride = 1 while (len(x) // stride + 1) * (len(y) // stride + 1) > SURFACE_MAX_PREVIEW_VERTICES: stride += 1 px, py = x[::stride], y[::stride] - pz, pv = z[::stride, ::stride], valid[::stride, ::stride] - vertices = grid_vertices(px, py, pz.astype(np.float64)) + pv = valid[::stride, ::stride] + + cell_m = float(x[1] - x[0]) if len(x) > 1 else SHEET_SURFACE_GRID_M + control_m = max( + SURFACE_NURBS_PATCH_SIZE_M / max(SURFACE_NURBS_CONTROL_POINTS_PER_AXIS - 1, 1), cell_m + ) + control_step = max(1, int(round(control_m / max(cell_m, 1e-6)))) + # 곡면 적합은 결측을 못 받는다 — 무효 셀은 가장 가까운 유효 표고로 메우고, + # 메시를 자를 때 원래 유효 마스크로 다시 도려낸다. + filled = z.astype(np.float64) + if not valid.all(): + from scipy.ndimage import distance_transform_edt + + _, (rows, cols) = distance_transform_edt(~valid, return_indices=True) + filled = filled[rows, cols] + cx, cy = x[::control_step], y[::control_step] + cz = filled[::control_step, ::control_step] + try: + spline, z_range = fit_nurbs_spline(cx, cy, cz, SURFACE_NURBS_DEGREE) + pz = evaluate_nurbs_spline(spline, py, px, z_range, "도엽 서피스") + except Exception as exc: # noqa: BLE001 — 적합 실패 시 격자 메시로 되돌린다 + logger.warning("도엽 서피스: NURBS 곡면 적합 실패(%s) — 격자 메시를 씁니다.", exc) + pz = filled[::stride, ::stride] + + vertices = grid_vertices(px, py, np.asarray(pz, dtype=np.float64)) faces = grid_faces(len(py), len(px)) return clip_and_compact_mesh(vertices, faces, pv.reshape(-1))