feat(b05,b06): 물넘이포장 콘크리트 윗면 = 설계 노면 · 포장층 노견까지 · 월류 높이 점선은 노면 위 (287-2 · 287-4)

- 설계 계산 tilted · tilt_pct(파이썬 · TS 짝) — 물넘이 범위 노면 = 경사각도로 지대 낮은 쪽 한 경사 · 측구 없음 · 중심점 = 계획고(콘크리트 윗면) · 노면 끝에서 사면이 이어짐(짧으면 성토)
- 서버 tilt_ranges · tilt_args · stored_tilt — 기본 설계 · [저장] 재계산 · 포장 범위 맞춤 · 저장분 재계산이 같은 값(옛 ford_paved 갈음)
- 횡단 그림 — 포장층 = 노면공 포장 형상(appendPavementOverlay)을 노견까지 · 물넘이 표식 · 월류 높이선 = 노면 + 높이 같은 각도 · 옛 파임 · 노면 걷기 그림 걷음
- 3D 노면 파기 걷음(설계 노면이 곧 윗면) · 코리도 빌드 판 94
- 시험 test_287 · 286_1 · 횡단 설계 거울(기울임 갈래) · 물넘이 명세 시험

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
2026-10-12 20:57:54 +09:00
co-authored by Claude Opus 5.5
parent 0c7f71d103
commit a5a9a7cec0
23 changed files with 273 additions and 288 deletions
+13 -7
View File
@@ -98,8 +98,10 @@ export interface CrossDesignOptions {
ditchChoice?: boolean | null;
/** 세월교 월류 높이만큼 노면을 통째로 내린다(m). */
surfaceDropM?: number;
/** 물넘이포장 범위 안 측점 — 측구 없음(286-2). 짝: 파이썬 `compute_cross_design(ford_paved=…)`. */
fordPaved?: boolean;
/** 물넘이포장 범위 · 맹암거 측점 — 측구 없음 · 노면 = `tiltPct`(비면 표준 횡단경사)로 지대 낮은 쪽 한 경사
* (286-2 · 287-4 · 288-1). 짝: 파이썬 `compute_cross_design(tilted=…, tilt_pct=…)`. */
tilted?: boolean;
tiltPct?: number | null;
/** 이 측점의 평면 곡선반경(m) — 곡선부 확폭을 정하는 입력. 직선이면 null. */
planRadiusM?: number | null;
/** 곡선 **바깥쪽**("left"/"right") — 확폭이 붙는 쪽(2026-09-06 사용자 확정). */
@@ -189,8 +191,9 @@ export interface CrossDesignResult {
/** 이 측점에 놓인 소단 제원 — 옹벽 의무 판정이 사면을 도막으로 끊는 데 쓴다. */
berm?: { width_m: number; interval_m: number; slope_deg: number; depth_m?: number };
surface_drop_m?: number;
/** 물넘이포장 범위 안 측점(286-2) — 측구 없음. */
ford_paved?: boolean;
/** 물넘이포장 · 맹암거 자리(286-2 · 287-4) — 측구 없음 · 지대 낮은 쪽 한 경사. */
tilted?: boolean;
tilt_pct?: number;
pavement_thickness_m?: number;
rock_boundary_offset_m?: number;
}
@@ -316,7 +319,9 @@ export function computeCrossDesign(
}
const pavedGroup = resolveGroup("paved", options.standard);
// 포장 중첩: 횡단경사와 포장층 두께만 포장 그룹을 따른다.
const crossSlopePct = paved ? pavedGroup.cross_slope_pct : group.cross_slope_pct;
const tiltPct = options.tilted && (options.tiltPct ?? 0) > 0 ? options.tiltPct! : null;
// 구조물 자리 경사(287-4 · 288-1 · 파이썬 짝)
const crossSlopePct = tiltPct ?? (paved ? pavedGroup.cross_slope_pct : group.cross_slope_pct);
const resolvedDitchSide = resolveDitchSide(sectionMode, options.ditchSide);
const valid: Array<[number, number]> = samples
@@ -371,7 +376,7 @@ export function computeCrossDesign(
berm: options.berm ?? null,
shoulderExtendLeftM: options.shoulderExtendLeftM ?? 0,
shoulderExtendRightM: options.shoulderExtendRightM ?? 0,
noDitch: Boolean(options.fordPaved),
tilted: Boolean(options.tilted),
});
// 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만).
@@ -586,7 +591,8 @@ export function computeCrossDesign(
design_line: designLine,
cut_slope_segments: geometry.cutSlopeSegments(),
};
if (options.fordPaved) result.ford_paved = true;
if (options.tilted) result.tilted = true;
if (options.tilted && options.tiltPct != null) result.tilt_pct = round4(options.tiltPct);
if (drop > 0) result.surface_drop_m = round4(drop);
if (options.berm) {
// 소단 제원을 설계에 되싣는다 — 짝 파이썬과 같은 까닭이고, **옹벽 의무 판정
@@ -145,8 +145,8 @@ export class SectionGeometry {
/** 설계선 쉬프트 = 쪽마다 노견 확장(m · ≥ 0 · 139-6). */
shoulderExtendLeftM?: number;
shoulderExtendRightM?: number;
/** 물넘이포장 자리 — 측구 없음(286-2 · 파이썬 짝 `no_ditch`). */
noDitch?: boolean;
/** 물넘이포장 · 맹암거 자리 — 측구 없음 · 노면 = 지대 낮은 쪽 한 경사(286-2 · 287-4 · 파이썬 짝 `tilted`). */
tilted?: boolean;
}) {
const { group } = params;
const halfRoad = group.road_width_m / 2;
@@ -171,6 +171,12 @@ export class SectionGeometry {
// 횡단경사: 측구 방향으로 내려가는 단일 사면 (좌=+offset 규약).
const slope = params.crossSlopePct / 100;
this.slopePerOffset = params.ditchSide === "left" ? -slope : slope;
// 물넘이포장 · 맹암거 자리(287-4 · 288-1) — 노면 = 한 경사로 지대 낮은 쪽(노면 끝 원지반이 낮은 쪽)
// 으로 기움 · 측구 없음(아래 · 파이썬 짝)
if (params.tilted && params.groundAt) {
const lowLeft = params.groundAt(this.leftExtent) < params.groundAt(-this.rightExtent);
this.slopePerOffset = lowLeft ? -slope : slope;
}
// 단면유형 자동 판정(D-2) — 노면 끝 지반이 설계면보다 높으면 절토, 낮으면 성토.
const groundAt = params.groundAt;
@@ -215,8 +221,8 @@ export class SectionGeometry {
this.hasDitch = choice === null || bothFill ? autoDitch : choice;
/** 그 측점에 **사용자가 정한 선택**(없으면 자동). 결과와 갈라 내보낸다. */
this.ditchChoice = bothFill ? null : choice;
// 물넘이포장 자리(286-2) — 파인 노면이라 측구가 안 섬 · 선택은 그대로(파이썬 짝)
if (params.noDitch) this.hasDitch = false;
// 물넘이포장 · 맹암거 자리(286-2 · 288-1) — 측구가 안 섬 · 선택은 그대로(파이썬 짝)
if (params.tilted) this.hasDitch = false;
// 설계선 쉬프트 = 쪽마다 사면 이동(≥ 0 · 139-6 · 151-2) — 단면유형 · 측구 판정은 **옮기기 전**
// 노견에서(위) · 노견(폭 고정) · 측구 · 사면이 같이 바깥으로 · 늘어난 폭은 차도. ⚠ 파이썬 짝 `_SectionGeometry`.
+1 -1
View File
@@ -505,7 +505,7 @@ function fordPavementSet(
{
type: "ford_pavement",
span_m: span,
// 노선 중심에서 잰 깊이. 없으면 화면이 파임을 그리지 않는다(수치를 지어내지 않는다).
// 월류 높이 — 노면 위 점선(287-4). 없으면 안 그린다(수치를 지어내지 않는다).
depth_m: depth && depth > 0 ? depth : null,
slope_pct: used !== null ? round(Math.tan((used * Math.PI) / 180) * 100, 4) : legacy,
defaulted: defaulted(values, [