사용자 지적(2026-08-30): "결국 노선 위에 위치해야 한다".
관 자리를 정한 선(계획노선 CSV)과 화면에 그려지는 선(B05 최적 경로)은 같은 자리를
지나면서 연장이 다르다 — 실측 350.11m vs 354.83m, 전 구간 이격은 평균 0.03m·최대
1.12m뿐인데 길이가 4.7m 차이난다. pipe_points.json이 누가거리만 저장해서 읽는 쪽이
쥔 선에 따라 같은 값이 3.3~4.3m 미끄러졌고, 노선 지문도 늘 어긋나 B05는 저장된 관을
매번 통째로 버렸다("노선 지문이 달라 저장된 관 지점 4건을 쓰지 않습니다").
- PipePoint에 x·y를 두고 저장 시 채운다(save_pipe_points에 노선을 넘긴다).
- 지문이 달라도 좌표가 있으면 읽는 쪽 노선에 투영해 이월한다(project_pipe_points).
구간(start_m·end_m)은 기준점이 옮겨간 만큼 같이 민다 — 시설 치수는 불변.
- 좌표 없는 구 저장분만 종전대로 버린다.
- B05는 같은 로더(load_pipe_points_file)를 쓴다 — 두 화면의 판정 기준을 하나로.
검증: 프로젝트 5d18ebe3 실데이터 — 좌표 4/4 저장, B05 계획선으로 읽어 4건 전부 이월,
관에서 계획선까지 거리 최대 0.016m(종전 4건 전량 폐기). 누가거리는 +3.45~+4.29m
이동(연장 차이 그대로). pytest 11 passed(투영 이월·구 저장분 폐기 테스트 추가).
/surface/confirmed가 지형 가장자리를 structured.npz 또는
ground_points_{filter}.npz에서만 읽었다. 둘 다 LAS 산출물이라 LAS 없이
설계한 프로젝트에는 없고, bounds가 null로 나가 B05가
"지표면 범위 정보를 찾을 수 없습니다"로 막혀 3D 지형을 못 띄웠다.
두 파일이 없으면 확정 모델 격자(npz)의 bounds를 그대로 쓴다. 도엽 서피스
npz는 이미 같은 형식의 bounds(3×2)를 담고 있다.
검증: 프로젝트 5d18ebe3(LAS 0개) B05 재진입 — /surface/confirmed 200,
preview·contour 요청이 뒤따라 나가고 안내문이 사라짐. 지형·등고선·코리도·
구조물 라벨까지 화면에 뜬 것 확인(152_b05.png).
사용자 지시(2026-08-30): IDW·거리비례·TIN 곡면 제거, 5m 도엽등고선 기준 TIN 추가.
TIN(도엽선)은 격자에 구운 라인 셀이 아니라 벡터 등고선 정점의 원좌표를 그대로
Delaunay로 잇는다. 격자 밖은 100m 여유만 물고 자르며, 길이 필터는 두지 않는다
(짧은 봉우리 폐합 링을 버리면 마루가 통째로 평평해진다).
거리비례는 버튼에서만 빼고 함수는 남긴다 — 라플라스·TPS·ANUDEM·다중해상도가
초기추정으로 계속 쓴다. 기본 방식은 distance가 빠져 multires로 옮겼다.
검증: 실데이터 767×840·1m 6종 93.5s. 서피스에서 다시 뽑은 5m 선의 평면 이탈이
TIN(도엽선) 중앙 0.12m·2m초과 8.1%로 6종 중 최소(직전 최선 다중해상도
0.34m/18.3%). pytest 8 passed(격자와 어긋난 링 위 표고 재현 테스트 추가).
DTM 스무딩(184296f5)이 표고 정본(npz)에서 이미 가우시안+B-spline을 걸므로
프리뷰에서 다시 NURBS를 적합하면 곡면 적합이 이중으로 걸린다. c2060f3b 이전의
격자 메시로 되돌리고, 쓰지 않게 된 NURBS import를 지운다.
검증: 프리뷰 glb 정점 표고 = npz 격자 표고 (|차이| 최대 0.0000m,
distance 원본·스무딩 각 161,280정점).
LAS DTM 스무딩과 같은 두 단계를 계수 그대로 옮겨 방식마다
{stem}_smooth.npz·_smooth_preview.glb를 만든다(계수 변경 금지 — 사용자 지시):
정규화 가우시안(SURFACE_SMOOTHING_DTM_SIGMA_M) → C2 바이큐빅 B-spline 재평가
(kx=ky=3, s=SURFACE_SMOOTHING_DTM_SPLINE_SMOOTH). smooth_dtm()은 라이다
발자국(TerrainContext)을 받아 그대로 못 쓴다.
- 결측이 하나라도 있으면 스플라인 결과가 통째로 NaN이 된다(TIN·TIN 곡면은
볼록껍질 밖이 결측). 최근접 표고로 메워 적합하고 원래 마스크로 되돌린다.
- 재평가 격자는 원본보다 성기게 잡지 않는다 — 이 npz는 스무딩 확정 시 종·횡단이
샘플링하는 표고 정본이라 화면 정점 상한으로 해상도를 깎으면 안 된다.
메시 정점 수는 _preview_mesh가 알아서 줄인다.
- 화면: 방식 버튼 줄에 스무딩 드롭다운을 놓고 기본값을 켜 둔다.
실측(c1bb453f, 8종): 격자 767x840 1m 유지, 거칠기 감소(거리비례 0.341→0.217,
TIN 0.067→0.051), 표고 변화 3~33mm. build_surface_sampler(smooth=True) 정상.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
격자 삼각형을 그대로 이으면 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) <noreply@anthropic.com>
- ANUDEM형: 등고선의 국소 최대 곡률점(코너)에서 능선·계곡 구조선을 뽑아
(굽은 안쪽이 높으면 계곡, 낮으면 능선) 이웃 표고끼리 이어 앵커로 심고,
박판 스플라인으로 풀고, 형태학적 재구성으로 가짜 웅덩이를 메운다.
Topo to Raster가 밟는 세 단계를 그대로 옮긴 것. 메운 웅덩이는 평탄해지지
않게 안쪽으로 cm 단위 경사를 남긴다(Garbrecht·Martz 평탄면 해소).
- 다중해상도: 1/8→1/4→1/2→1 격자로 내려오며 앞 단계 해를 시작값으로 쓴다.
- 박판 솔버를 solve_min_curvature()로 분리해 두 방식이 함께 쓴다.
- 화면 버튼 순서에 두 방식을 넣었다(버튼이 곧 하이드·쇼).
실측(c1bb453f, 8종 83s) 평탄 셀: TPS 0.003 / 다중해상도 0.003 /
거리비례 0.014 / TIN 곡면 0.026 / ANUDEM형 0.046 / 라플라스 0.235 /
TIN 선형 10.36 / IDW 69.13 %. LiDAR 대비 |dz|는 2.59~3.00m로 차이가 작다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
문헌(ANUDEM Hutchinson 1988/89, DEM 보간 비교연구)은 지형에 따라 우열이
갈려 단일 최적해가 없다고 본다. 방식을 하나로 고르지 않고 전부 만들어 두고
사용자가 눈으로 비교해 정하도록 했다(2026-08-30 사용자 지시).
신규 B04_PreProcess_Engine_SheetMethods.py — 거리비례 / TPS(박판, 감쇠
최소곡률 LSMR) / 라플라스 / TIN 선형 / TIN 곡면(Clough-Tocher) / IDW.
방식마다 dtm_sheet_{key}.npz + 프리뷰를 만들어 surface_models에
source_filter=sheet_{key}로 등록하므로 기존 뷰어·등고선·프리뷰 경로가 그대로
돈다. 폐합 링 안쪽 처리는 방식과 무관하게 똑같이 적용한다.
화면: 도엽등고 3D 서피스 컨테이너에 방식 전환 버튼과 '라이다 겹쳐 보기'
토글(반투명 파랑)을 달았다. 라이다는 같은 좌표계라 같은 자리에 겹친다.
실측(c1bb453f, 6종 65s): 평탄 셀 TPS 0.003% / 거리비례 0.014% /
TIN 곡면 0.026% / 라플라스 0.235% / TIN 선형 10.4% / IDW 69.1%.
LAS 대비 노선 |dz| 평균은 2.59~3.00m로 방식 간 차이가 작다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 지적(1m 보간선이 등간격 아님)의 원인 세 가지를 잡았다.
- 등고 라인 래스터가 all_touched=True라 2px 두께였다. 그 폭만큼 정확히 5m
배수 표고인 평탄 띠가 생겨(격자의 7.6%) 사이 1m 선 간격이 찌그러졌다.
서피스 전용 얇은 래스터화(all_touched=False)로 바꾸고, 봉우리 폐합 링이
사라지지 않도록 길이 필터도 뺐다.
- 계곡 앵커를 1m로 양자화해 제약에 섞은 탓에 제약 표고가 29단→121단이 되어
계곡 주변만 1m 간격이 됐다. 앵커를 뺐다 — V자 등고선이 계곡 하강을 이미
담고 있어 거리 보간만으로 충분하다.
- 등고선 고정 완화를 수렴시키면 harmonic 해가 되어 마루가 눌린다. z=r은
biharmonic이라서다 — ANUDEM/Topo to Raster가 라플라스가 아니라 thin plate
spline을 쓰는 이유(Hutchinson 1988/89, 조사 결과). 다듬기를 omega=1·5회로
줄였다. 합성 원뿔 검증: 거리보간 오차 0.121m·간격 CV 2.18 → 5회 0.118m·1.43,
20회 이상 CV 50↑ 악화. 원뿔 회귀 테스트를 남겼다.
실측(c1bb453f): 평탄 셀 0.00%, 5m 구간 표고 분포 8.4~10.3%(균등),
생성 6.3s, 노선 |Δz| 2.64m.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
메시(TIN)로 먼저 만들고 거기서 등고선을 뽑던 순서가 계단의 원인이었다.
사용자 지시대로 2D를 먼저 완결한다:
- Delaunay TIN 제거. 표고별 거리장에서 각 셀의 가장 가까운 두 '서로 다른'
등고 라인을 찾아 z=(L1·d2+L2·d1)/(d1+d2)로 보간한다. 같은 표고 정점
3개짜리 평탄 삼각형이 없으므로 계단이 원리적으로 생기지 않는다.
- 폐합 등고선 안쪽은 거리보간이 분화구를 만들므로, 안에 다른 제약이 없는
영역을 마루/웅덩이로 보고 바깥 사면 경사로 ±(간격-0.5m)까지 연장한다.
- 계곡 구조선 앵커는 1m 양자화해 같은 격자에 제약으로 굽는다.
- 등고 라벨 40개 상한(긴 것부터) — 1m 등고선이 잘게 쪼개져 라벨 수백 개가
매 프레임 재계산되며 화면이 멈췄다(사용자 보고). 61 FPS 회복.
실측(c1bb453f): 평탄 셀 2.48%→0.02%, 최대 평탄 덩어리 2406→912㎡,
경사 2% 미만 셀 17145→14444, 생성 36.5s→8.0s, 노선 |Δz| 2.76m.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
앞서는 등거리 '점'을 5셀 간격으로 흩뿌려 점 사이가 벌어진 곳에 평탄
삼각형이 남았고 한 단계(+2.5m)만 넣어 그 안에서 계단이 남았다.
사용자 지시(2D에서 사이 등고선을 먼저 만들고 3D화)대로 등거리선을
연속 셀 선으로 뽑아 새 레벨로 등록하고, 그 선들 사이를 다시 이분한다
(SHEET_SURFACE_MIDLINE_ROUNDS=2 → 5m 주곡선이 2.5m·1.25m 가상 등고로).
정점 간격도 5셀에서 2m로 좁혔다. 거리장은 라운드 간 재사용한다.
실측(c1bb453f): 평탄 셀 3.96%→2.48%(최초 9.35%), 경사 2% 미만 셀
27163→17145, 노선 |Δz| 2.56m 유지. 생성 16.4s→36.5s — 라운드 수는
config로 조절 가능.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5m 등고선만 보고 보간하던 것을 구조선 기준으로 보강(2026-08-30 사용자 확정):
- 계곡: 도엽_하천중심선을 기준선으로, 등고선 교차점을 Z 앵커 삼아 선 길이
비례로 보간한 정점열을 TIN에 추가 — 계곡 바닥이 등고선 사이에서도
연속으로 내려간다 (실데이터 605정점, 480~599m)
- 능선: 마루 캡을 고정 +2.5m에서 마지막 등고선 바깥 사면의 국소 경사
연장으로 교체(상한 +간격−0.5m, 경사 불명 시 돔 폴백) — 마루가 주변
사면 기울기를 따라 이어진다
실측: 평탄 셀 5.14%→3.96%(최초 9.35%), 노선 |Δz| 불변(2.57m), 생성 16.4s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
다음 등고선이 없는 봉우리·능선 마루는 TIN이 마지막 등고 표고로 평탄하게
채워 'ㅜ'가 갑자기 'ㅡ'로 변하는 단이 생긴다(2026-08-30 사용자 지적).
등표고 평탄 컴포넌트 중 바깥 유효 이웃이 전부 낮은 것(=마루)만 골라,
경계 거리 비례로 중심 골격이 +간격/2(주곡선 5m 기준 +2.5m)가 되게 올린다.
실제 마루는 마지막 등고와 +간격 사이가 보장되므로 오차 상한이 데이터
불확실성 이내다. 골짜기 바닥은 배수 방향을 모르므로 불변.
값별 사전 필터와 컴포넌트 bbox 창 연산으로 전체 생성 11.8s 유지.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
등고선 정점만의 Delaunay TIN은 같은 표고 정점 3개짜리 평탄 삼각형이
굴곡부·능선에서 계단(terrace)을 만든다(2026-08-30 사용자 지적).
인접 표고 등고선 쌍의 등거리 중간선을 EDT로 찾아 평균 표고 정점으로
추가하되, 멀리 있는 다른 표고 쌍에 우연히 등거리인 지점을 거르기 위해
'그 지점의 최근접 등고선이 바로 그 쌍'일 때만 인정한다.
실측(c1bb453f): 평탄 셀 9.35%→5.31%, LAS 대비 노선 |Δz| 평균
2.65→2.57m·최대 13.11→13.07m. 최고 등고선 안쪽 봉우리·같은 표고 사이
골짜기 바닥은 원천 데이터 한계로 평탄 유지.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dtm_sheet.npz에 bounds가 없어 등고선 API가 LAS structured.npz bounds로
폴백했고, 메시(glb)는 sheet 격자 중심으로 만들어져 등고선이 X -7.1m,
Y +3.4m, Z +3.3m 떠 보였다. LAS 없는 프로젝트면 등고선 생성 자체가 실패한다.
glb와 같은 bounds(3x2)를 npz에 저장해 두 산출물이 같은 원점을 공유한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- B03: 'LAS 없이 설계' 토글 — LAS 필수카드 비활성화, las_free 플래그로
업로드·완료 검증 면제, 계획노선 CSV를 WF1 분석 입력으로 사용
- B04: 신규 Engine_SheetSurface — 도엽_등고선.geojson을 노선 bbox+300m
직사각형으로 절취, 배수유역 엔진의 등고선 정점구름·Delaunay TIN 보간을
재사용해 dtm_sheet.npz(1m 격자, LAS DTM과 동일 형식) + 프리뷰 glb 생성.
build_surface_sampler('sheet','dtm')로 종·횡단·배수 하류 계산 무수정 동작
- WF1: las_free면 run_sheet_surface_analysis로 분기, sheet/dtm 자동 확정.
VWorld·도엽 확보 블록을 download_geodata()로 추출해 두 경로가 공유
- LAS가 있어도 도엽 서피스를 함께 생성·등록(참고용), B04에
'도엽등고 3D 서피스' 별도 컨테이너로 표시
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
납품 도면 2장을 B07 도면 목록에 새로 붙였다. 계산은 하지 않는다 —
유토곡선은 B06 확정 시 저장한 산출물(longitudinal_sections.data.mass_haul)을,
유역도는 B04 세부유역 GeoJSON과 도엽 등고선·세류선을 읽어 좌표만 종이 mm로 옮긴다.
- 도면 종류 확장: kind에 mass_haul·watershed 추가(Schema·Api_Fetch·openwebcad
App.types), 12분류 라벨에 연결, 목록에 단장 도면 2건 상시 노출
- 엔진 신규: _Engine_Cad_MassHaul.py(축·곡선·평형선·띠 현·balloon·측점 테이블),
_Engine_Cad_Basin.py(등고선·세류선 배경·노선·유역·구역별 정보표·방위표)
- 척도 상수: 유토곡선 H 1/2,000 · 세로 1mm=50㎥, 유역도 1/6,000 (A1 고정)
- 유토곡선 저장 payload에 띠·잔여의 기하 필드 추가 — 파이썬에 곡선 보간·토량 배분
로직을 복제하지 않기 위해 값을 낳는 쪽(TS 엔진)에서 함께 남긴다
- 유역도 배경은 여러 도엽을 합쳐 받은 뒤 도곽 크기로 절취(clip_line_to_box)
- 배수규격은 소요 관경이 아니라 규격관(recommended_diameter_mm)으로 표기
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
파일을 올리면 지표면 확정 직후 "노선 설계로 이동하세요"를 적고 그 뒤에 초기 설계
체인이 돈다. 그 사이 B05에 들어가 만지면 반쯤 계산된 값을 편집하게 되고, 그 편집이
섞인 채 초기값 스냅샷이 찍힌다. 계산이 끝날 때까지 준비 화면(B11)에서 기다리게 한다.
- initial_snapshot: 마커(initial_design.lock) 생성·해제·조회와 스냅샷 무효화,
편집 정본 제거를 한곳에 둔다. 마커는 스냅샷 4트리 밖(프로젝트 루트)이라 복원에
딸려 들어가지 않는다.
- 체인 두 곳(자동·재설계): 시작에 마커, finally에서 해제. 재설계 체인은 끝에 스냅샷을
무효화한다 — 사용자 입력을 유지한 채 재계산하므로 그 결과를 찍으면 가짜 초기값이
된다. 다음 [초기화]가 폴백을 타며 새 초기값을 만들고 그때 촬영한다.
- /surface/status: 마커가 있으면 in_progress + initial_design으로 답한다. stage 1이
COMPLETE면 진행률 파일을 무시하는 기존 분기를 건드리지 않으려고 별도 확인이다.
- 진입 게이트(routeAfterPreloadCheck)와 B11이 그 상태를 보고 기다린다. 상태 조회가
실패하면 막지 않고, B11 대기는 20분 상한을 둔다.
- [초기화] 폴백: 체인 전에 structures.json과 drainage/edits를 지운다. 남기면 구조물
측점이 사용자 편집분에서 다시 파생돼 초기값이 오염된다(2026-08-29 실측).
테스트 4건 추가. 249 passed·8 failed(기슭막이 이관 때 생긴 기존 실패, 변경 전 동일).
2026-08-23 사용자 지시 3건:
- 절성토 완전 분리: 전환 구간을 상대 측점의 폭 0 축퇴점으로 모핑하던 방식
제거. 비탈을 측구와 같은 도로부 앵커 상대 좌표로 보간하고, 행마다 두 측점
지반선을 섞은 행 지반선과 직접 교차시켜 종결(trimSlopeToGround). 지반이
반대편인 행은 조각 드롭 - 절토가 지반으로 내려가거나 서피스끼리 침범하던
문제 해소. 클리핑 외곽도 행 실점유(비탈-측구-노견)로 일치.
700줄 제한: 측점 분류·트림 유틸을 Corridor_Station.ts로 분리(Build 494줄).
- 그랩 팬: 가운데 드래그를 커서 아래 지형점이 1:1로 따라오는 팬으로 직접
구현(B04_PreProcess_UI_Camera). OrbitControls 기본 PAN은 카메라-target
거리 비례라 커서 피벗 회전·줌 후 극단적으로 느려졌다. B04 뷰어도 공용.
- 측점 그룹 갱신: Profile_Panel이 라이브 alignment.samples 노출, Page의
측점 바 계획고·코리도 designSamples가 그걸 사용, 종단 편집 디바운스에서
renderStationLines 동반 호출. BUILD_VERSION 4-5로 저장본 만료.
검증: tsc 통과 · pytest 175 passed(신규 separation 8건) · 사용자 화면 확인.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 3-1 우클릭 메뉴 일원화: 종단그래프·배수유역도 빈 자리 메뉴 = 사이드 「구조물
배치」와 같은 구조물군(A~G·기타) → 종류 2단 아코디언(공용 컨텍스트 메뉴에
하위 메뉴 지원 추가). 선택 = 즉시 추가가 아니라 폼 자동 지정(addAt, A군은
임시 배치까지). 구 [배관 추가]·평면 나열·기성막이/대피로 항목 흡수.
- 3-2 유역 리스트 문구: 세월교 검토→세월교 제안, BOX암거 후보→BOX암거 제안,
추천 Ø{d} 배관→Ø{d} 배관 제안.
- 3-3 구조물 알약 벌룬: 아래(테이블에 가림) 대신 위로 — 1행 좌상, 2행 우상,
솔로 위 중앙.
- 3-4 사이드: 스크롤 영역 상단 여유 + 페이지 설정 샘플 간격 2열(종단 먼저).
- 3-5 배수유역도 위성사진 기본 꺼짐.
- 3-6 B04 초기화 버튼 톤 다운 — 붉은 테두리·글자만, 채움 없음.
- 3-7 그래프 빈 공간 클릭 = 완전 해제 — 알약 해제 신호가 아예 없던 것 수정 +
외부 해제 시 구조물군·종류까지 리셋(선택 없을 땐 유지).
검증: tsc·vite + Playwright 헤드 실화면 V1~V8 전부 PASS (PLAN.md 3-8).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 표시 필터 버튼 묶음을 헤더에서 지도 좌측 상단(8px 여백)으로 옮겼다. 버튼 줄과
상태 문구를 한 오버레이가 세로로 쌓아, 문구가 버튼 아래에서 시작한다
- 버튼 줄은 한 줄 유지. 오른쪽 진행단계 오버레이에 끝이 가려도 창을 넓히거나 그
오버레이를 접으면 드러난다(사용자 확정) — 두 줄로 접는 쪽이 지도를 더 가린다
- 오버레이 빈 자리는 pointer-events를 흘려 지도 팬·유역 고르기를 막지 않고,
버튼 위 pointerdown만 지도로 내려가지 않게 끊는다
- 유역 번호 배지를 원에서 라운드 사각으로. 지도 위 다른 숫자가 전부 동그라미라
크기로만 갈렸다. 크기(지름 22px)는 그대로. B04 지도 배지도 같은 함수라 함께 바뀐다
지시 2 — 배수유역도가 유효직경 다음에 추천을 보여 주고, 초기 전처리 계산이
그 값으로 관 지점을 설정한다:
- recommend_structure(): 유효직경 임계로 구조물·관경을 고른다. D≤1500 배관(규격
스냅 800·1000·1200·1500), 1500<D≤2000 BOX암거 후보, D>2000 세월교 검토.
판정 근거는 유량뿐 — 계곡 횡단경사·하천 차수는 지형 계산이 필요해 뺐고 화면이
"현장 확인"으로 안내한다(2026-08-17 사용자 확정)
- WatershedBasin에 recommended_facility·recommended_diameter_mm·required_area_m2
추가, 응답 payload와 DetailBasin 타입까지 배선
- _apply_recommendations(): 자동 배치 관의 **빈칸에만** 추천을 채운다. 사용자가
놓은 관(source=user)·이미 바꾼 시설·이미 있는 옵션 키는 건드리지 않는다.
carry_facility_attributes 뒤에 얹어 승계값이 덮이지 않게 했다
- 유역 목록 표기: `Ø1234mm → 추천 Ø1500 배관` / `→ BOX암거 후보`
- 추천 관경 목록은 레지스트리 pipe_diameter_mm 선택지와 같아야 한다 — 폼에서 못
고르는 값을 추천하지 않도록 테스트로 묶었다
지시 1 — 물넘이포장·세월교는 지정할 옵션이 거의 없으니 계산값이라도 보여 준다:
- 레지스트리에 ford_width_m(월류 폭) 신설. 기본값 없음 — 지식DB에 폭 근거가 없어
프로그램이 지어내지 않는다
- fordSection(): 설계유량과 폭으로 필요 수심을 되짚는다. 광폭 근사로 출발해 실제
동수반경(R=A/P)으로 수렴시킨다 — 근사값을 그대로 쓰면 통수능이 늘 설계유량에
못 미쳐 판정이 무의미해진다. n=0.017·S=10%는 실무 수리계산서 역산값
(울진1 물넘이·관 시트가 둘 다 S=0.10에서 검산 일치)
- 폼에 월류 폭 한 칸 + 읽기전용 요약(설계유량·필요 수심·필요 단면·유속). 설계유량은
유역 payload에서 폼까지 배선(onPipesChanged → Page → setFacility)
⚠ 실무 시트의 수심 0.27m는 Qd 역산값이 아니라 가정 수심이다(B=20·h=0.27의 통수능
41.2㎥/s ≫ 그 시트 Qd 2.54㎥/s). 실무는 단면을 가정해 통수능을 검증하고, 프로그램은
필요 최소 수심을 낸다 — 화면 문구도 "필요 수심"으로 적었다.
검증: pytest tmp/tests 124 passed·7 skipped(신규 17건), npm run typecheck 무오류,
npm run build 성공. 물넘이 산식은 프론트 전용이라 수치 손검증으로 대신했다 —
Qd=2.54·B=20에서 필요 수심 0.0503m·유속 2.526m/s, 통수능이 2.5400㎥/s로 Qd와 일치.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN 2026-08-17 「B05 구조물 컨테이너 병합」 프론트엔드.
- B05_Profile_Util_Station.ts 신설: 측점번호+잔여거리("3+18.0") ↔ 누가거리
변환·서식·해석. 누가거리 직접 입력도 허용, 부동소수 올림 보정.
- 구조물 패널: 위치 입력을 측점 표기 텍스트로 재편. 점형 = 기준 측점 1칸,
구간형 = 기준점(비우면 시작)+시작+종료 3칸, 시작≤기준≤종료 검증과 잘못된
입력 붉힘. 목록 표기도 측점식. 구간형 마크 드래그는 기준점 기준으로 시작·
종료가 따라온다. 상세(detail) 옵션은 폼에서 숨긴다 — B05는 유무·종류·위치
단계(2026-08-17 사용자 확정).
- 우클릭 메뉴 필터 완화: managed_by와 b05 phase 필수만 제외 — 상세 필수였던
타입(옹벽·돌쌓기 등)도 우클릭 한 번으로 추가된다.
- B05_Profile_UI_Drainage_Facility.ts 신설(패널 700줄 제한 대응 분리): 관 마커
선택 시 시설 종류(배관/BOX암거/물넘이/세월교)·구간·세월교 관 종류/크기/수량
폼. FacilityStore가 시설 확장 정보를 보관하고 세부유역 재계산·저장 요청에
되붙인다(관 이동 후에도 최근접 승계 — 백엔드 carry와 같은 기준). 응답이
정본이라 apply()에서 전량 재구성.
- B04_PreProcess_Api_Fetch.ts: PipeFacility·DetailPipeInput 타입, 요청·응답에
시설 필드 반영.
npm run typecheck·npm run build 통과. 통합 서클마크 표시와 구 비정규 측점
이관·폐기(3단계)는 배관 측점선 체계 이전과 묶어 다음 작업 — PLAN.md 체크리스트
에 미완 사유 기록.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
csf-nurbs 등고선 캐시가 Maximum allowed size exceeded로 실패하던 문제.
평활계수 s가 제어점당 잔차제곱 0.01(RMS 0.1m)로 너무 빡빡해 FITPACK이
제어점 수(122x113)보다 많은 knot(126x117)을 밀어넣고 계수가 발산했다.
실측 표고: csf 1e105, grid_min_z 1e10. grid_min_z는 footprint 안쪽만
보고 z 범위를 잡아 조용히 통과하고 있었다.
- ModelContext에 fit_nurbs_spline/evaluate_nurbs_spline 신설. 모델 빌더와
등고선 엔진이 같은 곡면을 각각 만들면서 s 식이 두 곳에 중복돼 있었다.
- NURBS_RESIDUAL_PER_CONTROL_POINT = 1.0 (RMS 1m). knot이 17~35로 떨어지고
세 지면 필터 모두 데이터 표고 범위 안에 머문다. NURBS는 평활 곡면
표현이라 이 정도 완화가 맞다.
- 평가 결과가 제어 표고 범위(±50% 여유)를 벗어나면 잘라내고 경고를 남긴다.
방치하면 float32 캐스팅에서 inf가 되어 프리뷰 색상(nan)과 등고선까지 번진다.
- extract_contours_from_grid: 레벨 수 상한 검사를 np.arange 앞으로 옮겼다.
뒤에 있어서 상한이 무용지물이었다 — 배열이 만들어지기 전에 터진다.
확인: 3필터 모두 곡면이 제어 표고 범위 내(grid_min_z 503.6~573.1,
csf 504.7~552.3, pmf 504.6~551.8), 등고선 0.55~1.64초에 정상 산출.
표고 범위 1e12 격자도 예외 없이 처리. tmp/tests 59건 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 결정(2026-08-08): 새 자료를 올리면 재계산해 덮어쓰고, 화면은 불러올 자료가 없으면
대시보드로 보낸다. 그러려면 자료가 갈리는 순간 옛 계산 결과가 남아 있으면 안 된다.
- common_util_project_reset 신설: 단계별 산출물 폴더(B04~B09)와 산출물 DB 레코드
(surface_models·routes·route_points·route_statistics·longitudinal_sections·
cross_sections·structures·quantity_items·outputs·processed_point_cloud)를 함께 지운다.
**B03_FileInput(업로드 원본)은 지우지 않는다** — 같은 파일인지 가리는 중복 검사가 쓴다.
- 직접 업로드 완료·보관함 연결 양쪽에서 정리를 호출하고, 진행 단계도 1단계 이후를
NOT_STARTED로 되돌린다(reset_stages_after_input_change).
- is_analysis_running(): 전처리가 도는 중이면 업로드 세션 생성과 보관함 연결을 409로
막는다. 화면 버튼 잠금은 새로고침·다른 탭으로 우회되므로 서버에도 문을 단다.
- fail_stage 아래 있던 리비전(번호표) 안은 폐기 — 사용자가 "산출물이 없으면 대시보드"
방식으로 정리했다.
곁들여: os.replace 공유 위반 재시도(replace_with_retry)
진행률 파일은 서버가 쓰는 동안 화면이 계속 읽어 WinError 5가 났고, 전처리
structured.npz 교체에서는 같은 이유로 분석이 통째로 죽었다(결함 3의 사망 원인).
짧게 여러 번 다시 시도하도록 바꿨다.
검증(실서버 f45243b3, 계획노선 CSV 재업로드로 자료 교체):
전처리 결과 112개 -> 재분석분만, 노선 2->0, 횡단 20->0,
DB 지표면 15->0 / 노선 1->0 / 횡단 19->0, 업로드 원본 6개는 그대로.
진행 단계가 초기로 돌아가고 재분석이 자동 시작됨.
교체 재시도는 읽는 쪽이 파일을 잡고 있는 상황을 만들어 성공 확인.
ruff format·check 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
앞선 두 커밋(c3f643b, 7d50ded)으로도 증상이 남아 헤드리스 크롬으로 실제 앱에 로그인해
B05 3D를 끌어 보며 측정했다. 원인은 회전 계산이 아니라 **탑뷰 카메라 자리**였다.
카메라를 정확히 수직으로 세우면(기존 top = [0, distance, 0.001], 극각 1e-6도) lookAt이
화면의 가로·세로 방향을 정하지 못한다. 그 자리에서 벗어나는 순간 화면 축이 180° 돌아간다.
실측(2px 드래그 6걸음):
before 극각 0.00° -> 0.86°, 1걸음째 화면축 반전, 이후 같은 방향 드래그 먹통
after 극각 2.00° -> 1.15°, 반전 0회, 반대 방향 20걸음도 반전 0회(1.15° -> 130°)
- TOP_VIEW_TILT(2°) 신설 — 화면상 위에서 내려다보는 그림과 구분되지 않으면서 여유각
(POLAR_EPSILON, 약 1.15°)보다 바깥이라 첫 드래그부터 회전이 이어진다.
- B05 노선 뷰어, B04 지형·포인트클라우드 뷰어의 탑뷰 세 곳에 모두 적용.
typecheck·prettier 통과, 정적 번들 재빌드.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
기본 탑뷰는 시선이 up과 거의 나란하다(극각 1.9e-6 rad). 이때 회전축으로 쓰던
시선×up이 0에 수렴해 축 방향이 잡음으로 정해졌고, 살짝만 끌어도 엉뚱한 축으로 돌아
화면이 뒤집혔다. 앞 커밋의 극점 클램프도 각도를 여유각(0.02 rad) 경계까지 끌어올려,
작은 드래그가 여유각만큼 튀게 만들었다.
- 시선×up이 시선 길이 대비 무시할 만큼 짧으면 카메라 자신의 가로축으로 대체한다.
두 축은 모든 각도에서 방향이 같아(내적 1.000000) 전환 순간에도 조작감이 안 바뀐다.
- 클램프 경계를 현재 극각과 여유각 중 극점에서 먼 쪽으로 잡는다. 이미 여유각 안쪽인
탑뷰에서는 경계로 끌어올리지 않고, 극점에서 멀어지는 쪽만 열어 준다.
검증(탑뷰 재현): 0.005 요청 -> 0.005 적용, 0.05 -> 0.05, 극점 쪽 -0.005 -> 0(정지).
typecheck·prettier 통과, 정적 번들 재빌드.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
세로로 많이 끌면 화면이 멈췄다가 한 번에 움직이는 증상. 원인 두 가지였다.
1) 극점을 넘길 회전을 통째로 버렸다. 끄는 동안 화면이 굳어 있다가 각도가 다시 범위
안으로 들어오는 순간 밀린 만큼 한꺼번에 움직였다. 이제 극점 직전까지만 잘라서
적용한다 — right축이 up과 직교하므로 돌린 각이 곧 극각 변화량이고, 부호만 실제로
재서 클램프한다. 극점에 닿으면 더 안 가고 멈출 뿐, 반대로는 자유롭게 빠져나온다.
2) 드래그가 캔버스 밖(브라우저 위·아래 끝)으로 나가면 pointerleave로 회전이 끊겼다.
다시 누를 때 커서 아래로 회전축을 새로 집어 시점이 튀었다. 포인터를 캡처해 밖으로
나가도 회전을 이어 가고, 놓을 때만 끝낸다.
B04 지형·포인트클라우드 뷰어와 B05 노선 뷰어가 같은 모듈을 쓰므로 양쪽 모두 적용된다.
typecheck·prettier 통과, 정적 번들 재빌드.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>