feat(B06): 곡선부 노폭 자동 확폭 — 평면 R 기준, 곡선 바깥쪽 편측

2026-09-06 사용자 확정. 별표2 Ⅰ.2.나.(4) 확폭표(R 10~45m → 2.25~0.25m)를 측점별
평면 곡선반경에 물려 차도 폭을 넓힌다.

- 확폭 방향은 **곡선 바깥쪽 편측** — 노선 폴리라인의 외적 부호로 회전 방향을 보고
  바깥쪽을 정한다(좌회전이면 우측). 측점 기록에 `curve_outer_side` 로 실린다.
- 차도 반폭을 좌·우로 나눠 들어 한쪽만 넓어지게 함. 확폭이 0이면 예전과 같은 대칭
  단면이다. 노견·측구·사면은 그 바깥으로 그대로 밀린다.
- 확폭을 더한 유효너비는 법정 상한 5m 에서 자른다(규격 3.0m 면 최대 2.0m 까지).
- 계산 짝을 함께 고침 — 파이썬 `compute_cross_design` 과 브라우저 `computeCrossDesign`,
  표는 양쪽에 두되 짝임을 주석으로 못 박음. 확폭 입력은 측점 기록에서 뽑는 헬퍼
  하나로 9개 호출부(횡단·확정·B07 도면)에 같은 값이 가게 함.
- 횡단도에 노폭 라벨 — 확폭이 걸리면 「노폭 4.5m (규격 3.0 + 확폭 1.5)」로 적는다.
- 확인: 표 경계·편측 적용·5m 상한·회전 방향 판정 5건(pytest) + 브라우저 표 15건 일치.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-06 12:07:56 +09:00
co-authored by Claude Opus 5
parent c7099bd1d3
commit 283a248f89
13 changed files with 232 additions and 33 deletions
+9 -3
View File
@@ -9,7 +9,7 @@ from B05_Profile.B05_Profile_Engine_Sections import cross_filename
from B05_Profile.B05_Profile_Structures_Repository import load_structures
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
from B06_Section.B06_Section_Engine_Culvert import load_culvert_sets
from B06_Section.B06_Section_Engine_Design import compute_cross_design
from B06_Section.B06_Section_Engine_Design import compute_cross_design, curve_widening_args
from common_util.common_util_route_profile import design_elevation_from_longitudinal
from config.config_system import STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M
@@ -178,6 +178,7 @@ def enforce_pavement_ranges(
two_stage_slope=bool(design.get("two_stage_slope", True)),
ditch_enabled=design.get("ditch_enabled"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
**curve_widening_args(section),
)
except (ValueError, KeyError):
continue
@@ -228,6 +229,7 @@ def enforce_ford_surface_drops(
two_stage_slope=bool(design.get("two_stage_slope", True)),
ditch_enabled=design.get("ditch_enabled"),
surface_drop_m=wanted,
**curve_widening_args(section),
)
except (ValueError, KeyError):
continue
@@ -251,7 +253,8 @@ def default_section_modes(longitudinal: dict[str, Any]) -> dict[float, str]:
def read_cross_design_inputs(
project_root: Path, longitudinal_file_path: str, chainage_m: float
) -> tuple[list[dict], float | None, bool]:
) -> tuple[list[dict], float | None, bool, dict[str, Any]]:
"""(지반 샘플, 계획고, 포장 제안, 측점 기록) — 측점 기록은 곡선부 확폭 입력을 담고 있다."""
root = project_root.resolve()
longitudinal_path = (root / longitudinal_file_path).resolve()
if root not in longitudinal_path.parents:
@@ -269,7 +272,7 @@ def read_cross_design_inputs(
samples = cross.get("samples") if isinstance(cross, dict) else None
if not isinstance(samples, list):
raise ValueError("횡단 상세 파일 형식이 올바르지 않습니다.")
return samples, design_elevation, suggested
return samples, design_elevation, suggested, cross
def attach_default_designs(
@@ -298,6 +301,7 @@ def attach_default_designs(
standard=standard,
rock_boundary_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
surface_drop_m=ford_drop_at(chainage, ford_drops),
**curve_widening_args(section),
)
design.update(status="provisional", pavement_suggested=suggested)
section["design"] = design
@@ -349,6 +353,7 @@ def recompute_designs_for_alignment(
two_stage_slope=bool(stored.get("two_stage_slope", True)),
ditch_enabled=stored.get("ditch_enabled"),
surface_drop_m=ford_drop_at(chainage, ford_drops),
**curve_widening_args(section),
)
except (ValueError, KeyError):
continue
@@ -408,6 +413,7 @@ def compute_default_designs(
standard=standard,
rock_boundary_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
surface_drop_m=ford_drop_at(chainage_m, ford_drops),
**curve_widening_args(cross),
)
design["status"] = "provisional"
design["pavement_suggested"] = suggested