Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -2,13 +2,16 @@
|
||||
* design_structure_ford_pavement.ts
|
||||
* 횡단배수 › 물넘이 포장 명세 — 사용자 구조물 표 03(`docs/raw/구조물정의_표/03_횡단배수_물넘이포장.md`) 그대로.
|
||||
*
|
||||
* 컨테이너 둘: 경사각도(필수 · 각도 손 입력 · 도 + 월류 폭 · 높이 · 65-5) · 도수로/돌붙임(선택 · 구조 = 도수로 · 돌붙임).
|
||||
* 저장 = pipe_points.json `options` — 새 키 `ford_slope_deg` · 선택 컨테이너 `ford_chute_{n}_structure` ·
|
||||
* 컨테이너 둘: 경사각도(필수 · 각도 손 입력 · 도 + 월류 폭 · 높이 · 65-5) · 도수로/돌붙임(선택 · 다른 구조물과
|
||||
* 같은 칸 `CHUTE_ITEMS` + 맨 아래 길이 · 286-3).
|
||||
* 저장 = pipe_points.json `options` — 새 키 `ford_slope_deg` · 선택 컨테이너 `ford_chute_{n}_*` ·
|
||||
* 월류 폭 · 높이 = 세월교와 같은 키(`ford_width_m` · `ford_height_m`) · 같은 계산(`design_structure_ford_calc`).
|
||||
* 경사각도는 옛 「바닥 경사(%)」(`ford_slope_pct` · B06 파임 기울기)와 뜻이 같은지 또렷하지 않아 잇지 않음.
|
||||
* import 없음 — 시험이 이 파일만 옮겨 돌림.
|
||||
* 길이(`ford_chute_{n}_length_m`) = 횡단 · 3D 띠를 사면 따라 그릴 길이 — 그림 전용(수량 · 내역 · 일위대가에 안 씀) ·
|
||||
* 비면 원지반까지.
|
||||
* ========================================================================== */
|
||||
|
||||
import { CHUTE_ITEMS } from "./design_structure_guard";
|
||||
import type { StructureSpec } from "./design_structure_spec";
|
||||
|
||||
export const FORD_PAVEMENT_SPEC: StructureSpec = {
|
||||
@@ -32,20 +35,8 @@ export const FORD_PAVEMENT_SPEC: StructureSpec = {
|
||||
prefix: "ford_chute",
|
||||
link: "gfordpavechute",
|
||||
max: 1,
|
||||
// 구조(기본 도수로) · 두께(cm · 기본 45 · 173-5) — 배관 · 박스 · 세월교 `CHUTE_ITEMS` 와 같은 칸(90-2)
|
||||
items: [
|
||||
{
|
||||
id: "structure",
|
||||
label: "구조",
|
||||
kind: "select",
|
||||
choices: [
|
||||
{ value: "도수로", label: "도수로" },
|
||||
{ value: "돌붙임", label: "돌붙임" },
|
||||
],
|
||||
default: "도수로",
|
||||
},
|
||||
{ id: "thickness", label: "두께 (cm)", kind: "number", step: 1, default: "45" },
|
||||
],
|
||||
// 배관 · 박스 · 세월교와 같은 칸(구조 · 형태 · 두께 · 면적 · 90-2) + 맨 아래 길이(그림 전용 · 286-3)
|
||||
items: [...CHUTE_ITEMS, { id: "length_m", label: "길이 (m)", kind: "number", step: 0.1 }],
|
||||
},
|
||||
],
|
||||
// 표 03 에 없는 옛 칸 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
|
||||
@@ -29,7 +29,6 @@ from B05_Profile.B05_Profile_Structures_Schema import (
|
||||
structure_type_map,
|
||||
)
|
||||
from common_util.common_util_drainage_pipes import (
|
||||
PIPE_FACILITY_FORD_PAVEMENT,
|
||||
PIPE_FACILITY_PIPE,
|
||||
PIPE_FACILITY_REVET,
|
||||
PipePoint,
|
||||
@@ -208,9 +207,7 @@ def _load_pipe_points(project_root: Path, polyline: list[list[float]]) -> list[P
|
||||
|
||||
|
||||
def pipe_station_label(pipe: PipePoint, types: dict[str, Any]) -> str | None:
|
||||
"""관 지점 측점 라벨 — 배관 = 「관종 D관경」 · 그 밖 = 종류 이름 · 물넘이포장 = 없음."""
|
||||
if pipe.facility == PIPE_FACILITY_FORD_PAVEMENT:
|
||||
return None
|
||||
"""관 지점 측점 라벨 — 배관 = 「관종 D관경」 · 그 밖(물넘이포장 포함 · 286-1) = 종류 이름."""
|
||||
if pipe.facility == PIPE_FACILITY_PIPE:
|
||||
values = pipe.options or {}
|
||||
kind = str(values.get("pipe_kind") or PIPE_DEFAULT_TYPE)
|
||||
@@ -267,8 +264,7 @@ def resolve_extra_stations(
|
||||
(2026-08-28 사용자 보고). 정본 파일은 초기화에도 살아남으므로 여기서 매번 다시 만든다
|
||||
— B05·B06은 같은 응답 하나를 보는 한 페이지라 공급자도 하나여야 한다.
|
||||
|
||||
물넘이포장은 비정규 측점으로 올리지 않는다 — 종단 그래프 하단 구조물 표시로만 다룬다
|
||||
(2026-08-28 사용자 확정).
|
||||
물넘이포장도 기준 측점 하나를 심는다 — 그 측점 횡단도에 파인 노면을 그린다(286-1).
|
||||
|
||||
라벨 형식은 프론트 structureLabel()과 같아야 한다 — 재진입 이관이 라벨 문자열로 종류를
|
||||
되짚기 때문이다(B05_Profile_Structures_Migration).
|
||||
|
||||
@@ -175,6 +175,7 @@ async def sync_uphill_overrides_into_designs(
|
||||
surface_drop_m=ford_drop_at(float(chainage), ford_drops),
|
||||
**curve_widening_args(cross_record),
|
||||
**stored_shoulder_extend(design), # 노견 확장(139-6)
|
||||
ford_paved=bool(design.get("ford_paved")), # 물넘이 자리 측구 없음(286-2)
|
||||
)
|
||||
next_design["status"] = design.get("status", "provisional")
|
||||
next_design["pavement_suggested"] = design.get("pavement_suggested", pavement_suggested)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import type { ChuteSpec, CrossSection } from "../B06_Section/B06_Section_Api_Fetch";
|
||||
import type { OffsetPoint } from "../B06_Section/B06_Section_UI_Cross_Culvert_Types";
|
||||
import { chuteBandOutline } from "../B06_Section/B06_Section_UI_Cross_Wall_Hatch";
|
||||
import { outletFillLine } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
|
||||
import { fordChuteLine } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
|
||||
|
||||
/** 성토 조각 하나 — B06 오버레이가 띠를 그리는 선(절토 조각은 뺀다). */
|
||||
interface FillSegment {
|
||||
@@ -51,6 +51,6 @@ export function fordPavementChute(
|
||||
);
|
||||
if (owner !== section) return null;
|
||||
}
|
||||
const bands = chuteBands([{ points: outletFillLine(section.design) }], pave.chute);
|
||||
const bands = chuteBands([{ points: fordChuteLine(section.design, pave.chute) }], pave.chute);
|
||||
return bands.length ? { bands, halfM: Math.max(pave.span_m, 0.5) / 2 } : null;
|
||||
}
|
||||
|
||||
@@ -112,12 +112,6 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 물넘이포장은 비정규 측점이 아니다 — 횡단도로 표현할 것이 없어 종단 알약으로만
|
||||
* 다룬다(2026-08-28 사용자 확정). 관 정본 대조(setPipeChainages)는 전체 목록을 써야
|
||||
* 물넘이가 지워지지 않으므로, **표시·서버 전송 쪽에서만** 뺀다. */
|
||||
const withoutFordPavement = (stations: IrregularStation[]): IrregularStation[] =>
|
||||
stations.filter((station) => station.structure !== FACILITY_NAMES.ford_pavement);
|
||||
|
||||
/** [초기선 복원] 등에서 그래프의 배관 투영만 지운다 — 관 정본은 건드리지 않는다. */
|
||||
function clearProjectedStations(): void {
|
||||
pipeStations = [];
|
||||
@@ -144,7 +138,7 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
});
|
||||
irregularStations = stations;
|
||||
deps.renderStationLines();
|
||||
deps.profilePanel().setIrregularStations(withoutFordPavement(stations));
|
||||
deps.profilePanel().setIrregularStations(stations);
|
||||
// 좌측 구조물 폼에서 "배관" 항목을 고쳤거나 지웠으면 배수유역도까지 따라가야 한다.
|
||||
// 관 목록이 그대로면 배수유역도가 아무 일도 하지 않으므로 되먹임 고리는 여기서 끊긴다.
|
||||
deps
|
||||
@@ -190,18 +184,13 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
|
||||
/** 관 정본 + 측점 심는 수동 구조물을 합쳐 그래프·3D 측점 목록을 맞춘다 — 같은 m 면 측점 하나(관이
|
||||
* 먼저 · 서버 측점 만들기와 같음). 관 기준 측점은 다 둠(관 정본 대조 `setPipeChainages` 가 관을
|
||||
* 지우지 않게) · 물넘이포장은 측점을 안 심어 견주지 않음. */
|
||||
* 지우지 않게) · 물넘이포장도 측점을 심음(286-1). */
|
||||
function syncCrossDrainStations(): void {
|
||||
const planting = (station: IrregularStation) =>
|
||||
station.structure !== FACILITY_NAMES.ford_pavement;
|
||||
const kept: IrregularStation[] = [];
|
||||
for (const station of [...pipeStations, ...crossDrainStationsOf(ownStructures)]) {
|
||||
const taken =
|
||||
station.origin !== "pipe" &&
|
||||
kept.some(
|
||||
(other) =>
|
||||
planting(other) && Math.abs(other.chainage_m - station.chainage_m) < SAME_STATION_M,
|
||||
);
|
||||
kept.some((other) => Math.abs(other.chainage_m - station.chainage_m) < SAME_STATION_M);
|
||||
if (!taken) kept.push(station);
|
||||
}
|
||||
applyIrregularStations(kept);
|
||||
@@ -410,7 +399,7 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) {
|
||||
});
|
||||
},
|
||||
/** 그래프·3D가 쓰는 현재 비정규 측점 목록(물넘이포장 제외 — 알약 레인 몫). */
|
||||
irregularStations: () => withoutFordPavement(irregularStations),
|
||||
irregularStations: () => irregularStations,
|
||||
/** 구조물 종류의 군(A~G · 모르면 undefined) — 측점 고름이 덮어 열 구조물을 가름(216-1). */
|
||||
typeGroup: (typeId: string) => structureTypeMap.get(typeId)?.group,
|
||||
/** 관 지점 목록이 바뀜 — 측점 투영과 알약 레인을 한 번에 맞춘다. */
|
||||
|
||||
@@ -179,7 +179,7 @@ export function structureIdKeeping(
|
||||
return keeps ? selectedId : structureIdAtStation(stationId, stations, structures);
|
||||
}
|
||||
|
||||
/** 제 측점을 심는 알약인가 — 272 측점 심기 한 벌(A군 · C · D군 구간형 · 관은 물넘이포장 뺌).
|
||||
/** 제 측점을 심는 알약인가 — 272 측점 심기 한 벌(A군 · C · D군 구간형 · 관 = 물넘이포장까지 · 286-1).
|
||||
* 안 심는 구조물 알약은 같은 자리 · 근처 측점선을 짝으로 켜지 않음(요구 178) · 종류를 모르면 옛 길. */
|
||||
export function plantsStation(
|
||||
structure: StructureInstance,
|
||||
@@ -194,7 +194,7 @@ export function plantsStation(
|
||||
return !type || structureStationMarks(structure, type).length > 0;
|
||||
}
|
||||
|
||||
/** 알약(구조물) id → 측점선 id. 제 측점을 안 심는 구조물(B군 · 물넘이포장 등)이면 null. */
|
||||
/** 알약(구조물) id → 측점선 id. 제 측점을 안 심는 구조물(B군 · 표지 등)이면 null. */
|
||||
export function stationIdAtStructure(
|
||||
structureId: string | null,
|
||||
stations: IrregularStation[],
|
||||
|
||||
@@ -228,6 +228,8 @@ export interface CulvertSideSpec {
|
||||
export interface ChuteSpec {
|
||||
structure: string;
|
||||
thickness_m: number;
|
||||
/** 사면 따라 그릴 길이(m · 물넘이 포장 286-3) — 그림 전용 · 없으면 원지반까지. */
|
||||
length_m?: number;
|
||||
}
|
||||
|
||||
/** 배수관 측점의 세트(배관·기슭막이·보호공) 제원 — 횡단 카드 오버레이 입력. */
|
||||
|
||||
@@ -359,6 +359,8 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
choice?.ditch_type ??
|
||||
(typeof design.ditch_type === "string" ? design.ditch_type : "standard"),
|
||||
paved: pavedBySpecs(section),
|
||||
// 물넘이 자리 = 측구 없음(286-2 · 서버 `ford_pavement_ranges` 짝 = 얹힌 제원 범위)
|
||||
fordPaved: Boolean(section.ford_pavement),
|
||||
standard,
|
||||
berm: bermAt(section.chainage_m, design),
|
||||
rockBoundaryOffsetM,
|
||||
|
||||
@@ -251,7 +251,8 @@ def _side_spec(
|
||||
def _chute(values: dict[str, Any], head: str = "chute_add") -> dict[str, Any] | None:
|
||||
"""도수로 · 돌붙임(90-2) — 첫 상자(`{head}_{n}_*` · 물넘이 포장 = `ford_chute` · 136-2) 구조 ·
|
||||
두께(cm → m · 빈 값 30 cm) · 구조를 안 고르면 None. 횡단이 유출 성토사면을 따라 띠로 그림.
|
||||
TS 짝 `chute`."""
|
||||
길이(`{head}_{n}_length_m` · 286-3) = 사면 따라 그릴 길이 — 그림 전용(수량 · 내역에 안 씀) ·
|
||||
비면 원지반까지. TS 짝 `chute`."""
|
||||
pattern = re.compile(rf"^{head}_(\d+)_structure$")
|
||||
picked = sorted(
|
||||
(int(hit.group(1)), str(value).strip())
|
||||
@@ -263,7 +264,11 @@ def _chute(values: dict[str, Any], head: str = "chute_add") -> dict[str, Any] |
|
||||
n, structure = picked[0]
|
||||
cm = _number(values.get(f"{head}_{n}_thickness"), None)
|
||||
cm = cm if cm and cm > 0 else CHUTE_THICKNESS_CM
|
||||
return {"structure": structure, "thickness_m": round(cm / 100.0, 3)}
|
||||
found = {"structure": structure, "thickness_m": round(cm / 100.0, 3)}
|
||||
length = _number(values.get(f"{head}_{n}_length_m"), None)
|
||||
if length and length > 0:
|
||||
found["length_m"] = round(length, 3)
|
||||
return found
|
||||
|
||||
|
||||
def _with_chute(
|
||||
|
||||
@@ -194,6 +194,7 @@ def compute_cross_design(
|
||||
spoil_fill: dict[str, Any] | None = None,
|
||||
shoulder_extend_left_m: float = 0.0,
|
||||
shoulder_extend_right_m: float = 0.0,
|
||||
ford_paved: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""측점 하나의 표준횡단 설계선과 절·성토 단면적을 계산한다.
|
||||
|
||||
@@ -210,6 +211,8 @@ def compute_cross_design(
|
||||
입력이며, None·45m 이상이면 확폭이 없다.
|
||||
curve_outer_side: 곡선 **바깥쪽**("left"/"right"). 확폭은 그쪽으로만 붙는다
|
||||
(2026-09-06 사용자 확정). 값이 없으면 확폭을 넣지 않는다.
|
||||
ford_paved: 물넘이포장 범위 안 측점(286-2) — 측구 없음 · 결과에 `ford_paved` 로 되실음.
|
||||
TS 짝 `fordPaved`.
|
||||
surface_drop_m: 노면을 통째로 내리는 양(m) — 세월교 월류 높이. 구체 위 노면은 월류
|
||||
높이만큼 낮게 앉으므로 계획고를 그만큼 내려 잡는다. 단면 전체가 평행 이동하므로
|
||||
횡단경사·측구·사면 규칙은 그대로고 절·성토 면적만 따라 바뀐다(2026-08-30 사용자).
|
||||
@@ -299,6 +302,7 @@ def compute_cross_design(
|
||||
berm=berm,
|
||||
shoulder_extend_left_m=shoulder_extend_left_m,
|
||||
shoulder_extend_right_m=shoulder_extend_right_m,
|
||||
no_ditch=bool(ford_paved),
|
||||
)
|
||||
|
||||
# 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만). 꼭짓점을 넣어야
|
||||
@@ -529,6 +533,8 @@ def compute_cross_design(
|
||||
# 뒤에 쓸 자리가 있다. **되살릴 때는 저장분에서 읽지 말고 계산해서 쓸 것.**
|
||||
"cut_slope_segments": geometry.cut_slope_segments(),
|
||||
}
|
||||
if ford_paved:
|
||||
result["ford_paved"] = True
|
||||
if drop > 0:
|
||||
# 내려 앉힌 양 — 프론트가 "월류가 없었다면" 노면을 점선으로 되그리는 데 쓴다.
|
||||
result["surface_drop_m"] = round(drop, 4)
|
||||
|
||||
@@ -62,6 +62,7 @@ class _SectionGeometry:
|
||||
berm: BermSpec | None = None,
|
||||
shoulder_extend_left_m: float = 0.0,
|
||||
shoulder_extend_right_m: float = 0.0,
|
||||
no_ditch: bool = False,
|
||||
) -> None:
|
||||
half_road = group["road_width_m"] / 2.0
|
||||
# 곡선부 확폭은 **한쪽으로만** 붙는다(2026-09-06 사용자 확정: 곡선 바깥쪽).
|
||||
@@ -144,6 +145,9 @@ class _SectionGeometry:
|
||||
self.has_ditch = auto_ditch if (choice is None or both_fill) else choice
|
||||
#: 그 측점에 **사용자가 정한 선택**(없으면 자동). 결과와 갈라 내보낸다.
|
||||
self.ditch_choice = None if both_fill else choice
|
||||
# 물넘이포장 자리(286-2) — 파인 노면이라 측구가 안 섬 · 선택은 그대로(시설을 걷으면 되살아남).
|
||||
if no_ditch:
|
||||
self.has_ditch = False
|
||||
|
||||
# 설계선 쉬프트 = 쪽마다 사면 이동(≥ 0 · 139-6 · 151-2) — 단면유형 · 측구 판정은 **옮기기 전**
|
||||
# 노견에서(위) · 노견(폭 고정) · 측구 · 사면이 같이 바깥으로 · 늘어난 폭은 차도.
|
||||
|
||||
@@ -158,6 +158,7 @@ def extend_unclosed_sections(
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(design), # 노견 확장(139-6)
|
||||
ford_paved=bool(design.get("ford_paved")), # 물넘이 자리 측구 없음(286-2)
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -217,6 +217,7 @@ def enforce_spoil_fills(
|
||||
},
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(design), # 노견 확장(139-6)
|
||||
ford_paved=bool(design.get("ford_paved")), # 물넘이 자리 측구 없음(286-2)
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
return None
|
||||
|
||||
@@ -46,6 +46,7 @@ from B06_Section.B06_Section_Router_Design import (
|
||||
from B06_Section.B06_Section_Router_Design import (
|
||||
USER_TOUCHED_KEYS,
|
||||
ford_drop_at,
|
||||
ford_pavement_ranges,
|
||||
ford_surface_drops,
|
||||
paved_at,
|
||||
pavement_ranges,
|
||||
@@ -600,6 +601,7 @@ async def compute_cross_section_design(
|
||||
berm=stored_berm(stored_design or {}),
|
||||
**curve_widening_args(cross_record),
|
||||
**stored_shoulder_extend(stored_design or {}), # 노견 확장(139-6)
|
||||
ford_paved=paved_at(request.chainage_m, ford_pavement_ranges(project_root)),
|
||||
)
|
||||
# B06 지정 시점은 잠정치. B07 도면 확정 시 동일 엔진으로 재계산해 confirmed로 승격한다.
|
||||
design["status"] = "provisional"
|
||||
|
||||
@@ -77,12 +77,20 @@ def pavement_ranges(project_root: Path) -> list[tuple[float, float]]:
|
||||
ranges.append((min(float(start), float(end)), max(float(start), float(end))))
|
||||
except Exception: # noqa: BLE001 — 정본을 못 읽어도 설계 계산은 이어 간다
|
||||
logger.exception("B06 포장 구간을 읽지 못했습니다 (사용자 지정 없음으로 본다)")
|
||||
for chainage, spec in load_culvert_sets(project_root).items():
|
||||
if spec.get("type") != "ford_pavement":
|
||||
continue
|
||||
half = float(spec.get("span_m") or 0.0) / 2.0
|
||||
ranges.append((chainage - half, chainage + half))
|
||||
return ranges
|
||||
return ranges + ford_pavement_ranges(project_root)
|
||||
|
||||
|
||||
def ford_pavement_ranges(project_root: Path | None) -> list[tuple[float, float]]:
|
||||
"""물넘이포장 범위(기준 ± 월류 폭/2) — 포장 필수 · 측구 없음(286-2). 판정은 `paved_at`."""
|
||||
if project_root is None:
|
||||
return []
|
||||
sets = load_culvert_sets(project_root).items()
|
||||
halves = [
|
||||
(at, float(s.get("span_m") or 0.0) / 2.0)
|
||||
for at, s in sets
|
||||
if s.get("type") == "ford_pavement"
|
||||
]
|
||||
return [(at - half, at + half) for at, half in halves]
|
||||
|
||||
|
||||
def ford_surface_drops(project_root: Path | None) -> list[tuple[float, float]]:
|
||||
@@ -207,6 +215,7 @@ def enforce_pavement_ranges(
|
||||
사용자 조작값(반폭·구조물 조정)은 그대로 승계한다.
|
||||
"""
|
||||
ranges = pavement_ranges(project_root)
|
||||
ford_ranges = ford_pavement_ranges(project_root)
|
||||
ford_drops = ford_surface_drops(project_root)
|
||||
changed = 0
|
||||
for section in cross_sections:
|
||||
@@ -215,7 +224,8 @@ def enforce_pavement_ranges(
|
||||
continue
|
||||
chainage = float(section.get("chainage_m", 0.0))
|
||||
wanted = paved_at(chainage, ranges)
|
||||
if bool(design.get("paved")) == wanted:
|
||||
ford_paved = paved_at(chainage, ford_ranges) # 물넘이 자리 = 측구 없음(286-2)
|
||||
if bool(design.get("paved")) == wanted and bool(design.get("ford_paved")) == ford_paved:
|
||||
continue
|
||||
try:
|
||||
recomputed = compute_cross_design(
|
||||
@@ -238,6 +248,7 @@ def enforce_pavement_ranges(
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(design), # 노견 확장(139-6)
|
||||
ford_paved=ford_paved,
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
@@ -266,6 +277,7 @@ def enforce_design_lines(
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import berm_span_at, load_berm_spans
|
||||
|
||||
ford_drops = ford_surface_drops(project_root)
|
||||
ford_ranges = ford_pavement_ranges(project_root)
|
||||
spans = load_berm_spans(project_root)
|
||||
changed = 0
|
||||
for section in cross_sections:
|
||||
@@ -296,6 +308,7 @@ def enforce_design_lines(
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(design),
|
||||
ford_paved=paved_at(chainage, ford_ranges),
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
@@ -351,6 +364,7 @@ def enforce_ford_surface_drops(
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(design), # 노견 확장(139-6)
|
||||
ford_paved=bool(design.get("ford_paved")),
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
@@ -423,6 +437,7 @@ def attach_default_designs(
|
||||
modes = default_section_modes(longitudinal)
|
||||
pavement = pavement_suggestions(longitudinal)
|
||||
paved_ranges = pavement_ranges(project_root) if project_root else []
|
||||
ford_ranges = ford_pavement_ranges(project_root)
|
||||
ford_drops = ford_surface_drops(project_root)
|
||||
for section in cross_sections:
|
||||
if section.get("design"):
|
||||
@@ -441,6 +456,7 @@ def attach_default_designs(
|
||||
rock_boundary_offset_m=STANDARD_ROCK_BOUNDARY_DEFAULT_OFFSET_M,
|
||||
surface_drop_m=ford_drop_at(chainage, ford_drops),
|
||||
**curve_widening_args(section),
|
||||
ford_paved=paved_at(chainage, ford_ranges),
|
||||
)
|
||||
design.update(status="provisional", pavement_suggested=suggested)
|
||||
section["design"] = design
|
||||
@@ -501,6 +517,7 @@ def recompute_designs_for_alignment(
|
||||
modes = default_section_modes(longitudinal)
|
||||
pavement = pavement_suggestions(longitudinal)
|
||||
paved_ranges = pavement_ranges(project_root) if project_root else []
|
||||
ford_ranges = ford_pavement_ranges(project_root)
|
||||
ford_drops = ford_surface_drops(project_root)
|
||||
stored_by_chainage = {
|
||||
round(float(record["chainage_m"]), 3): (record.get("design") or {})
|
||||
@@ -559,6 +576,7 @@ def recompute_designs_for_alignment(
|
||||
berm=session_berms.get(key) or stored_berm(stored),
|
||||
**curve_widening_args(section),
|
||||
**stored_shoulder_extend(stored), # 노견 확장(139-6)
|
||||
ford_paved=paved_at(chainage, ford_ranges),
|
||||
)
|
||||
except (ValueError, KeyError):
|
||||
continue
|
||||
@@ -603,6 +621,7 @@ def compute_default_designs(
|
||||
default_modes = default_section_modes(longitudinal)
|
||||
pavement = pavement_suggestions(longitudinal)
|
||||
paved_ranges = pavement_ranges(root)
|
||||
ford_ranges = ford_pavement_ranges(root)
|
||||
ford_drops = ford_surface_drops(root)
|
||||
results: list[tuple[float, dict[str, Any]]] = []
|
||||
for chainage_m in chainages:
|
||||
@@ -642,6 +661,7 @@ def compute_default_designs(
|
||||
surface_drop_m=ford_drop_at(chainage_m, ford_drops),
|
||||
berm=stored_berm(user),
|
||||
**curve_widening_args(cross),
|
||||
ford_paved=paved_at(chainage_m, ford_ranges),
|
||||
)
|
||||
for key in ("berm", "cut_slope_ratio_user"):
|
||||
if user.get(key) is not None:
|
||||
|
||||
@@ -96,7 +96,6 @@ function decorate(
|
||||
const byId = new Map(types.map((type) => [type.type_id, type]));
|
||||
const pipeLabel = (pipe: Pipes[number]): string | null => {
|
||||
const facility = pipe.facility ?? "pipe";
|
||||
if (facility === "ford_pavement") return null;
|
||||
if (facility !== "pipe") return byId.get(facility)?.name ?? facility;
|
||||
const values = { ...registry.pipe, ...pipe.options };
|
||||
return `${values.pipe_kind} D${Math.trunc(Number(values.pipe_diameter_mm))}`;
|
||||
|
||||
@@ -147,8 +147,8 @@ async function withDraftPipeKinds(detail: SectionDetailResponse, projectId: stri
|
||||
const spec = section[key] as Record<string, unknown> | undefined;
|
||||
if (spec && stale.some((pipe) => near(spec, section, pipe.chainage_m))) delete section[key];
|
||||
}
|
||||
// 카드 제목 구조물 이름 — 서버 `resolve_extra_stations` 라벨 규칙(배관 = 관종 D관경 · 물넘이 = 없음)
|
||||
// 라벨 자리 = 이름 있는 측점 · 관 자리 그 측점(물넘이로 지웠다 되돌린 때)
|
||||
// 카드 제목 구조물 이름 — 서버 `resolve_extra_stations` 라벨 규칙(배관 = 관종 D관경 · 그 밖 = 종류 이름)
|
||||
// 라벨 자리 = 이름 있는 측점 · 관 자리 그 측점
|
||||
const pipe = stale.find((entry) => {
|
||||
const gap = Math.abs(entry.chainage_m - Number(section.chainage_m));
|
||||
return section.structure ? gap < PIPE_MATCH_M : gap < 0.01;
|
||||
@@ -156,8 +156,7 @@ async function withDraftPipeKinds(detail: SectionDetailResponse, projectId: stri
|
||||
if (!pipe) continue;
|
||||
const facility = pipe.facility ?? "pipe";
|
||||
const values = { ...registry.pipe, ...pipe.options };
|
||||
if (facility === "ford_pavement") delete section.structure;
|
||||
else if (facility === "pipe")
|
||||
if (facility === "pipe")
|
||||
section.structure = `${values.pipe_kind} D${Math.trunc(Number(values.pipe_diameter_mm))}`;
|
||||
else section.structure = types.find((type) => type.type_id === facility)?.name ?? facility;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
* · 기존 계획고는 **점선**, 물넘이 바닥은 **실선**.
|
||||
* · 포장 필수 — 일반 포장층과 구분되게 진한 회색 + 빗금으로 채운다.
|
||||
*
|
||||
* 유입측 판정은 `section_mode`(상단측 절토 = 등고가 높은 쪽 = 상류)를 따른다.
|
||||
* 유입측 = 표고가 높은 쪽 — 설계선 양 끝(비탈이 원지반을 만나는 자리) 표고로 가름 · 노면은 경사각도
|
||||
* (`ford_slope_deg` → `slope_pct`)만큼 표고 낮은 쪽(유출)으로 기움(286-2). 양 끝이 같으면 `section_mode`
|
||||
* (상단측 절토 = 상류).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CrossDesign } from "./B06_Section_Api_Fetch";
|
||||
@@ -32,8 +34,8 @@ export interface FordPavementSpec {
|
||||
slope_pct: number | null;
|
||||
/** 정본에 안 적혀 기본값으로 그린 칸 「이름 값」 — 카드 머리에 「기본값(미확정)」. */
|
||||
defaulted?: string[];
|
||||
/** 도수로 · 돌붙임(`ford_chute_{n}_*` · 136-2) — 유출측 성토사면 띠. */
|
||||
chute?: { structure: string; thickness_m: number };
|
||||
/** 도수로 · 돌붙임(`ford_chute_{n}_*` · 136-2) — 유출측 성토사면 띠 · 길이 = 사면 따라(286-3 · 비면 원지반까지). */
|
||||
chute?: { structure: string; thickness_m: number; length_m?: number };
|
||||
}
|
||||
|
||||
interface Edge {
|
||||
@@ -41,11 +43,17 @@ interface Edge {
|
||||
elevation_m: number;
|
||||
}
|
||||
|
||||
/** 유입(상류)이 좌측(+offset)인가 — 설계선 양 끝 표고가 높은 쪽(286-2) · 같으면 단면유형. */
|
||||
export function inflowLeft(design: CrossDesign): boolean {
|
||||
const line = [...(design.design_line ?? [])].sort((a, b) => a.offset_m - b.offset_m);
|
||||
const gap = line.length >= 2 ? line[line.length - 1].elevation_m - line[0].elevation_m : 0;
|
||||
return Math.abs(gap) > 1e-3 ? gap > 0 : design.section_mode !== "right_cut";
|
||||
}
|
||||
|
||||
/** 유입측(상류) 부호 — 그 방향으로 갈수록 바닥이 높아진다. */
|
||||
function inflowSign(design: CrossDesign, offsetM: number): number {
|
||||
const inflowLeft = design.section_mode !== "right_cut";
|
||||
const towardLeft = offsetM > 0;
|
||||
return towardLeft === inflowLeft ? 1 : -1;
|
||||
return towardLeft === inflowLeft(design) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +190,8 @@ export function appendFordPavementOverlay(
|
||||
"b06-chart__ford-deck",
|
||||
),
|
||||
);
|
||||
if (spec.chute) appendChuteBand(svg, [{ points: outletFillLine(design) }], spec.chute, x, y);
|
||||
if (spec.chute)
|
||||
appendChuteBand(svg, [{ points: fordChuteLine(design, spec.chute) }], spec.chute, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -193,7 +202,7 @@ export function appendFordPavementOverlay(
|
||||
export function outletFillLine(design: CrossDesign): OffsetPoint[] {
|
||||
const edges = design.road_edges;
|
||||
if (!edges || !design.design_line?.length) return [];
|
||||
const outward = design.section_mode !== "right_cut" ? -1 : 1; // +offset = 좌측 · 유입 반대
|
||||
const outward = inflowLeft(design) ? -1 : 1; // +offset = 좌측 · 유입 반대
|
||||
const edge =
|
||||
outward > 0
|
||||
? Math.max(edges.left.offset_m, edges.right.offset_m)
|
||||
@@ -210,6 +219,29 @@ export function outletFillLine(design: CrossDesign): OffsetPoint[] {
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 물넘이 도수로 · 돌붙임 선(286-3) — 유출 성토사면 선을 노견에서 사면 따라 `length_m` 까지 자름 ·
|
||||
* 비거나 사면보다 길면 원지반까지. 횡단 띠 · 3D 띠가 같이 씀(그림 전용).
|
||||
*/
|
||||
export function fordChuteLine(design: CrossDesign, chute?: { length_m?: number }): OffsetPoint[] {
|
||||
const line = outletFillLine(design);
|
||||
let left = chute?.length_m ?? 0;
|
||||
if (!(left > 0)) return line;
|
||||
const out = line.slice(0, 1);
|
||||
for (let i = 1; i < line.length && left > 1e-9; i += 1) {
|
||||
const [a, b] = [line[i - 1], line[i]];
|
||||
const step = Math.hypot(b.offset - a.offset, b.elevation - a.elevation);
|
||||
if (step < 1e-9) continue;
|
||||
const t = Math.min(left / step, 1);
|
||||
out.push({
|
||||
offset: a.offset + (b.offset - a.offset) * t,
|
||||
elevation: a.elevation + (b.elevation - a.elevation) * t,
|
||||
});
|
||||
left -= step;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 물넘이 자리 노면 · 측구 설계선을 걷는다(66-2 · 66-3) — 노면은 파인 바닥(실선)과 옛 계획고(점선)가
|
||||
* 대신하고, 측구는 서지 않음. `from` = 설계선 그리기 앞의 자식 수(그 뒤에 붙은 선만 본다).
|
||||
|
||||
@@ -94,21 +94,19 @@ function pipeStations(
|
||||
types: ReadonlyArray<StructureType>,
|
||||
intervalM: number,
|
||||
): IrregularStation[] {
|
||||
return marks
|
||||
.filter((mark) => isPipeMark(mark) && mark.type_id !== "ford_pavement")
|
||||
.map((mark) => {
|
||||
const chainage = structureAnchorM(mark);
|
||||
const station = Math.floor(chainage / intervalM);
|
||||
return {
|
||||
id: String(mark.structure_id),
|
||||
station,
|
||||
remainder: chainage - station * intervalM,
|
||||
chainage_m: chainage,
|
||||
structure: types.find((type) => type.type_id === mark.type_id)?.name ?? "배수관",
|
||||
structureType: "배관",
|
||||
origin: "pipe",
|
||||
} as IrregularStation;
|
||||
});
|
||||
return marks.filter(isPipeMark).map((mark) => {
|
||||
const chainage = structureAnchorM(mark);
|
||||
const station = Math.floor(chainage / intervalM);
|
||||
return {
|
||||
id: String(mark.structure_id),
|
||||
station,
|
||||
remainder: chainage - station * intervalM,
|
||||
chainage_m: chainage,
|
||||
structure: types.find((type) => type.type_id === mark.type_id)?.name ?? "배수관",
|
||||
structureType: "배관",
|
||||
origin: "pipe",
|
||||
} as IrregularStation;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,6 +98,8 @@ export interface CrossDesignOptions {
|
||||
ditchChoice?: boolean | null;
|
||||
/** 세월교 월류 높이만큼 노면을 통째로 내린다(m). */
|
||||
surfaceDropM?: number;
|
||||
/** 물넘이포장 범위 안 측점 — 측구 없음(286-2). 짝: 파이썬 `compute_cross_design(ford_paved=…)`. */
|
||||
fordPaved?: boolean;
|
||||
/** 이 측점의 평면 곡선반경(m) — 곡선부 확폭을 정하는 입력. 직선이면 null. */
|
||||
planRadiusM?: number | null;
|
||||
/** 곡선 **바깥쪽**("left"/"right") — 확폭이 붙는 쪽(2026-09-06 사용자 확정). */
|
||||
@@ -187,6 +189,8 @@ 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;
|
||||
pavement_thickness_m?: number;
|
||||
rock_boundary_offset_m?: number;
|
||||
}
|
||||
@@ -367,6 +371,7 @@ export function computeCrossDesign(
|
||||
berm: options.berm ?? null,
|
||||
shoulderExtendLeftM: options.shoulderExtendLeftM ?? 0,
|
||||
shoulderExtendRightM: options.shoulderExtendRightM ?? 0,
|
||||
noDitch: Boolean(options.fordPaved),
|
||||
});
|
||||
|
||||
// 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만).
|
||||
@@ -581,6 +586,7 @@ export function computeCrossDesign(
|
||||
design_line: designLine,
|
||||
cut_slope_segments: geometry.cutSlopeSegments(),
|
||||
};
|
||||
if (options.fordPaved) result.ford_paved = true;
|
||||
if (drop > 0) result.surface_drop_m = round4(drop);
|
||||
if (options.berm) {
|
||||
// 소단 제원을 설계에 되싣는다 — 짝 파이썬과 같은 까닭이고, **옹벽 의무 판정
|
||||
|
||||
@@ -145,6 +145,8 @@ export class SectionGeometry {
|
||||
/** 설계선 쉬프트 = 쪽마다 노견 확장(m · ≥ 0 · 139-6). */
|
||||
shoulderExtendLeftM?: number;
|
||||
shoulderExtendRightM?: number;
|
||||
/** 물넘이포장 자리 — 측구 없음(286-2 · 파이썬 짝 `no_ditch`). */
|
||||
noDitch?: boolean;
|
||||
}) {
|
||||
const { group } = params;
|
||||
const halfRoad = group.road_width_m / 2;
|
||||
@@ -213,6 +215,8 @@ export class SectionGeometry {
|
||||
this.hasDitch = choice === null || bothFill ? autoDitch : choice;
|
||||
/** 그 측점에 **사용자가 정한 선택**(없으면 자동). 결과와 갈라 내보낸다. */
|
||||
this.ditchChoice = bothFill ? null : choice;
|
||||
// 물넘이포장 자리(286-2) — 파인 노면이라 측구가 안 섬 · 선택은 그대로(파이썬 짝)
|
||||
if (params.noDitch) this.hasDitch = false;
|
||||
|
||||
// 설계선 쉬프트 = 쪽마다 사면 이동(≥ 0 · 139-6 · 151-2) — 단면유형 · 측구 판정은 **옮기기 전**
|
||||
// 노견에서(위) · 노견(폭 고정) · 측구 · 사면이 같이 바깥으로 · 늘어난 폭은 차도. ⚠ 파이썬 짝 `_SectionGeometry`.
|
||||
|
||||
@@ -199,11 +199,12 @@ function sideSpec(
|
||||
}
|
||||
|
||||
/** 도수로 · 돌붙임(90-2) — 첫 상자(`{head}_{n}_*` · 물넘이 포장 = `ford_chute` · 136-2) 구조 · 두께
|
||||
* (cm → m · 빈 값 45 cm) · 구조를 안 고르면 null. 횡단이 유출 성토사면을 따라 띠로 그림. 파이썬 짝 `_chute`. */
|
||||
* (cm → m · 빈 값 45 cm) · 구조를 안 고르면 null. 횡단이 유출 성토사면을 따라 띠로 그림 · 길이
|
||||
* (`{head}_{n}_length_m` · 286-3) = 사면 따라 그릴 길이(그림 전용 · 비면 원지반까지). 파이썬 짝 `_chute`. */
|
||||
export function chute(
|
||||
values: Record<string, unknown>,
|
||||
head = "chute_add",
|
||||
): { structure: string; thickness_m: number } | null {
|
||||
): { structure: string; thickness_m: number; length_m?: number } | null {
|
||||
const pattern = new RegExp(`^${head}_(\\d+)_structure$`);
|
||||
const picked = Object.entries(values)
|
||||
.flatMap(([key, value]) => {
|
||||
@@ -215,7 +216,12 @@ export function chute(
|
||||
if (!picked.length) return null;
|
||||
const [n, structure] = picked[0];
|
||||
const cm = num(values[`${head}_${n}_thickness`], null);
|
||||
return { structure, thickness_m: round((cm && cm > 0 ? cm : CHUTE_THICKNESS_CM) / 100, 3) };
|
||||
const found = {
|
||||
structure,
|
||||
thickness_m: round((cm && cm > 0 ? cm : CHUTE_THICKNESS_CM) / 100, 3),
|
||||
};
|
||||
const length = num(values[`${head}_${n}_length_m`], null);
|
||||
return length && length > 0 ? { ...found, length_m: round(length, 3) } : found;
|
||||
}
|
||||
|
||||
/** 도수로 · 돌붙임이 있으면 세트에 `chute` 로 실음(90-2). */
|
||||
|
||||
@@ -111,10 +111,9 @@ function marksOf(
|
||||
);
|
||||
}
|
||||
|
||||
/** 관 시설 하나가 심는 측점 — 물넘이포장 없음 · 독립 기슭막이 = 시작 · 기준 · 끝 · 그 밖 = 기준. */
|
||||
/** 관 시설 하나가 심는 측점 — 독립 기슭막이 = 시작 · 기준 · 끝 · 그 밖(물넘이포장 포함 · 286-1) = 기준. */
|
||||
export function pipeStationMarks(pipe: PlantingPipe): StationMark[] {
|
||||
const facility = pipe.facility ?? "pipe";
|
||||
if (facility === "ford_pavement") return [];
|
||||
return marksOf(pipe.start_m, pipe.chainage_m, pipe.end_m, facility === "revetment");
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +63,12 @@ const STRUCTURES = [
|
||||
S("pipe-80.00", "pipe", 80),
|
||||
S("pipe-120.00", "ford_pavement", 120),
|
||||
];
|
||||
// 종단도 구조물 측점(관 투영 + 심은 구조물) — 물넘이포장은 측점 없음
|
||||
// 종단도 구조물 측점(관 투영 + 심은 구조물) — 물넘이포장도 측점 심음(286-1)
|
||||
const STATIONS = [
|
||||
{ id: "drain", chainage_m: 40, origin: "structure" },
|
||||
{ id: "pipe-80.00", chainage_m: 80, origin: "pipe" },
|
||||
{ id: "wall", chainage_m: 100, origin: "structure" },
|
||||
{ id: "pipe-120.00", chainage_m: 120, origin: "pipe" },
|
||||
];
|
||||
const byId = (id) => STRUCTURES.find((s) => s.structure_id === id);
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/* 286 물넘이포장 종횡단 헬퍼 — 진짜 TS 를 Node 로 돌려 표준출력 JSON 한 벌로.
|
||||
* 입력 = argv[2] JSON 파일 { pipes, structures, types }.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
|
||||
function loadTs(file, stubs = {}) {
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
const localRequire = (request) => {
|
||||
if (stubs[request]) return stubs[request];
|
||||
throw new Error(`stub 없음: ${request}`);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const station = loadTs(path.join(ROOT, "common_util", "common_util_structure_station.ts"));
|
||||
|
||||
const ford = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts"), {
|
||||
"./B06_Section_UI_Cross_Wall_Hatch": { appendChuteBand() {} },
|
||||
});
|
||||
// 286-2 노면 기울기 — 설계선 양 끝 표고가 높은 쪽 = 유입 · 표고 낮은 쪽(유출)으로 기움
|
||||
const SPEC = { span_m: 6, depth_m: 0.3, slope_pct: 5 };
|
||||
const designOf = (left, right, mode) => ({
|
||||
design_elevation_m: 100,
|
||||
cross_slope_pct: 2,
|
||||
section_mode: mode,
|
||||
design_line: [
|
||||
{ offset_m: -10, elevation_m: right },
|
||||
{ offset_m: 10, elevation_m: left },
|
||||
],
|
||||
});
|
||||
const deck = (design) => ({
|
||||
left: ford.fordDeckElevationAt(design, SPEC, 3),
|
||||
right: ford.fordDeckElevationAt(design, SPEC, -3),
|
||||
});
|
||||
|
||||
// 286-3 도수로 · 돌붙임 길이 — 유출(좌 · 우가 높음) 성토사면 1:1.5(5 m 내려감 · 사면 길이 √(7.5²+5²) ≈ 9.01)
|
||||
const sets = loadTs(path.join(ROOT, "common_util", "common_util_culvert_sets.ts"));
|
||||
const slopeDesign = {
|
||||
design_elevation_m: 100,
|
||||
cross_slope_pct: 2,
|
||||
section_mode: "left_cut",
|
||||
road_edges: {
|
||||
left: { offset_m: 2.5, elevation_m: 100 },
|
||||
right: { offset_m: -2.5, elevation_m: 100 },
|
||||
},
|
||||
design_line: [
|
||||
{ offset_m: 10, elevation_m: 95 },
|
||||
{ offset_m: 2.5, elevation_m: 100 },
|
||||
{ offset_m: -2.5, elevation_m: 100 },
|
||||
{ offset_m: -6, elevation_m: 104 },
|
||||
],
|
||||
};
|
||||
const chuteOf = (extra) =>
|
||||
sets.chute(
|
||||
{ ford_chute_1_structure: "돌붙임", ford_chute_1_thickness: 30, ...extra },
|
||||
"ford_chute",
|
||||
);
|
||||
|
||||
const out = {
|
||||
chuteNoLength: chuteOf({}),
|
||||
chuteLength: chuteOf({ ford_chute_1_length_m: 3 }),
|
||||
lineNoLength: ford.fordChuteLine(slopeDesign, chuteOf({})),
|
||||
lineLength: ford.fordChuteLine(slopeDesign, chuteOf({ ford_chute_1_length_m: 3 })),
|
||||
lineTooLong: ford.fordChuteLine(slopeDesign, chuteOf({ ford_chute_1_length_m: 50 })),
|
||||
fordMarks: station.pipeStationMarks({ chainage_m: 230, facility: "ford_pavement" }),
|
||||
planted: station.plantedChainages(input.pipes, input.structures, input.types),
|
||||
deckLeftHigh: deck(designOf(105, 95, "right_cut")),
|
||||
deckRightHigh: deck(designOf(95, 105, "left_cut")),
|
||||
deckFlat: deck(designOf(100, 100, "right_cut")),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -1,7 +1,7 @@
|
||||
"""272-1 측점 한 벌 — 화면(TS)과 서버(파이썬)가 같은 측점 목록을 냄(요구 167 · 측점 겹침 근본).
|
||||
|
||||
- 심는 자리: TS `plantedChainages` ↔ 서버 `resolve_extra_stations`(관 · 독립 기슭막이 시작 ·
|
||||
기준 · 끝 · A군 · C군 구간형 시작 · 기준 · 끝 · 물넘이포장 · 다른 군 · 점형 C/D 는 안 심음).
|
||||
기준 · 끝 · A군 · C군 구간형 시작 · 기준 · 끝 · 물넘이포장 기준(286-1) · 다른 군 · 점형 C/D 는 안 심음).
|
||||
- 같은 m = 측점 하나 · 구조물이 위 · 떠나면 일반 측점: TS `oneStationList` ↔ 서버
|
||||
`_merge_irregular_into_longitudinal`.
|
||||
진짜 TS 는 `helper_272_1_station_list.cjs` 가 Node 로 돌림.
|
||||
@@ -66,7 +66,7 @@ STRUCTURES = [
|
||||
},
|
||||
{"type_id": "evac_road", "placement": "point", "chainage_m": 720.0},
|
||||
]
|
||||
EXPECTED = [95, 120, 160, 300, 395, 400, 410, 500, 510, 530, 530, 545, 560]
|
||||
EXPECTED = [95, 120, 160, 230, 300, 395, 400, 410, 500, 510, 530, 530, 545, 560]
|
||||
|
||||
# 서버 종단 측점(맞추기 전) — 격자 0~100 · 옛 관 측점 95.38 · 구조물이 떠난 격자 자리 40(`grid`) ·
|
||||
# 옛 판 「regular + 글」 60 · 새 목록 = 격자 위 100(구조물이 위) · 새 자리 35
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"""284 종단도 알약 고름 · 풂(요구 178 · 179-1 · 조사 AM).
|
||||
|
||||
못박는 것
|
||||
- 측점 있음 = 제 측점을 심는 알약(272 한 벌 — A군 · C · D 구간형 · 관은 물넘이포장 뺌) · 종류 모르면 옛 길.
|
||||
- 측점 없는 알약(B군 · 표지 · 바닥막이 · 물넘이포장)은 같은 자리 · 근처 측점선을 짝으로 켜지 않음.
|
||||
- 측점 있음 = 제 측점을 심는 알약(272 한 벌 — A군 · C · D 구간형 · 관 = 물넘이포장까지 · 286-1) · 종류 모르면 옛 길.
|
||||
- 측점 없는 알약(B군 · 표지 · 바닥막이)은 같은 자리 · 근처 측점선을 짝으로 켜지 않음.
|
||||
- 관 측점선을 다시 누르면 측점 없는 표지가 아니라 그 관 알약.
|
||||
- B06 알약 다시 누름 = 풀기 한 길(Esc 값) · 측점 없는 관 = 카드 안 고름 · 스크롤만 · 고름 값에 알약 id.
|
||||
- B05 물넘이포장 알약 = 고름 값에 알약 id(같은 m 일반 측점선 안 켬).
|
||||
@@ -48,7 +48,7 @@ def test_측점_심기_규칙(out):
|
||||
"sill": False, # D군 점
|
||||
"unknown": True, # 종류 모름 = 옛 길
|
||||
"pipe-80.00": True, # 관
|
||||
"pipe-120.00": False, # 물넘이포장
|
||||
"pipe-120.00": True, # 물넘이포장(286-1 측점 심음)
|
||||
}
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ def test_측점_없는_알약은_측점선_짝_없음(out):
|
||||
line = out["line"]
|
||||
assert line["drain"] == "irregular:drain" and line["wall"] == "irregular:wall"
|
||||
assert line["pipe-80.00"] == "irregular:pipe-80.00"
|
||||
# 표지는 관 측점(80)과 같은 m 여도 그 선을 안 켬 · 물넘이포장 · B군 · D군 점도 없음
|
||||
assert line["sign"] is None and line["pipe-120.00"] is None
|
||||
# 표지는 관 측점(80)과 같은 m 여도 그 선을 안 켬 · B군 · D군 점도 없음 · 물넘이포장 = 제 측점선(286-1)
|
||||
assert line["sign"] is None and line["pipe-120.00"] == "irregular:pipe-120.00"
|
||||
assert line["ditch"] is None and line["sill"] is None
|
||||
assert out["lineNoTypes"] == "irregular:pipe-80.00" # 종류 없이 부르면 옛 길
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""286 물넘이포장 종횡단 설계(요구 180-2).
|
||||
|
||||
못박는 것
|
||||
- 286-1 물넘이포장도 기준 측점 하나 — TS `pipeStationMarks` · `plantedChainages` ↔ 서버
|
||||
`resolve_extra_stations` 같은 값 · 라벨 「물넘이포장」 · B05 · B06 거름 걷음.
|
||||
- 286-2 물넘이 자리 = 측구 없음(서버 정본 · 화면 짝 · 거울 = `test_b06_cross_design_mirror`) ·
|
||||
노면 = 경사각도로 표고 낮은 쪽(설계선 양 끝 표고 · 같으면 단면유형).
|
||||
- 286-3 도수로/돌붙임 = 다른 구조물과 같은 칸 + 맨 아래 「길이 (m)」(`ford_chute_{n}_length_m`) ·
|
||||
비면 원지반까지 · 있으면 사면 따라 그 길이 · 서버 · TS 같은 값 · 집계 · B08 수량에 안 실림.
|
||||
|
||||
진짜 TS 는 `helper_286_1_ford_pavement.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections import resolve_extra_stations # noqa: E402
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map # noqa: E402
|
||||
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
|
||||
from B06_Section.B06_Section_Router_Design import ford_pavement_ranges, paved_at # noqa: E402
|
||||
from B06_Section.B06_Section_Engine_Culvert import _chute # noqa: E402
|
||||
from B08_Quantity.B08_Quantity_Router_Summary import _chutes # noqa: E402
|
||||
from common_util.common_util_drainage_pipes import parse_pipe_points # noqa: E402
|
||||
from M02_MasterTemplete.M02_Table_Fill_Spec import container_rows # noqa: E402
|
||||
|
||||
PIPES = [
|
||||
{"chainage_m": 95.0, "source": "user"},
|
||||
{"chainage_m": 230.0, "source": "user", "facility": "ford_pavement"},
|
||||
{"chainage_m": 300.0, "source": "user", "facility": "ford_bridge"},
|
||||
]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
return (ROOT.joinpath(*parts)).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts(tmp_path_factory) -> dict:
|
||||
types = [
|
||||
{
|
||||
"type_id": item.type_id,
|
||||
"group": item.group,
|
||||
"placement": item.placement,
|
||||
"managed_by": item.managed_by,
|
||||
}
|
||||
for item in structure_type_map().values()
|
||||
]
|
||||
source = tmp_path_factory.mktemp("s286") / "in.json"
|
||||
source.write_text(
|
||||
json.dumps({"pipes": PIPES, "structures": [], "types": types}), encoding="utf-8"
|
||||
)
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_286_1_ford_pavement.cjs"), str(source)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_물넘이포장_측점_TS_파이썬_같은_값(ts, tmp_path):
|
||||
assert ts["fordMarks"] == [{"chainage_m": 230, "role": "anchor"}]
|
||||
server = resolve_extra_stations(tmp_path, parse_pipe_points(PIPES))
|
||||
assert [mark for mark, _label in server] == ts["planted"] == [95, 230, 300]
|
||||
assert dict(server)[230.0] == "물넘이포장"
|
||||
|
||||
|
||||
def test_B05_B06_물넘이_거름_걷음():
|
||||
page = _read("B05_Profile", "B05_Profile_UI_Page_Structures.ts")
|
||||
assert "withoutFordPavement" not in page
|
||||
assert "FACILITY_NAMES.ford_pavement" not in page
|
||||
view = _read("B06_Section", "B06_Section_UI_Section_View_Profile.ts")
|
||||
assert '"ford_pavement"' not in view
|
||||
drafts = _read("B06_Section", "B06_Section_Section_Drafts.ts")
|
||||
assert 'if (facility === "ford_pavement") return null;' not in drafts
|
||||
|
||||
|
||||
def _ground(slope: float) -> list[dict]:
|
||||
return [
|
||||
{"offset_m": o / 2, "elevation_m": round(100.0 + slope * o / 2, 4), "valid": True}
|
||||
for o in range(-24, 25)
|
||||
]
|
||||
|
||||
|
||||
def test_물넘이_자리_측구_없음():
|
||||
kwargs = {"ground_type": "soil", "section_mode": "left_cut", "ditch_side": "left"}
|
||||
plain = compute_cross_design(_ground(0.35), 100.4, **kwargs)
|
||||
ford = compute_cross_design(_ground(0.35), 100.4, ford_paved=True, **kwargs)
|
||||
assert plain["ditch_enabled"] is True and "ford_paved" not in plain
|
||||
assert ford["ditch_enabled"] is False and ford["ford_paved"] is True
|
||||
assert ford["ditch_area_m2"] == 0
|
||||
# 사용자가 측구를 켜도 물넘이 자리는 안 섬
|
||||
forced = compute_cross_design(
|
||||
_ground(0.35), 100.4, ford_paved=True, ditch_choice=True, **kwargs
|
||||
)
|
||||
assert forced["ditch_enabled"] is False
|
||||
|
||||
|
||||
def test_물넘이_범위_서버_판정(tmp_path):
|
||||
edits = tmp_path / "B04_PreProcess" / "drainage" / "edits"
|
||||
edits.mkdir(parents=True)
|
||||
(edits / "pipe_points.json").write_text(
|
||||
json.dumps({"points": [{**PIPES[1], "options": {"ford_width_m": 8}}]}), encoding="utf-8"
|
||||
)
|
||||
ranges = ford_pavement_ranges(tmp_path)
|
||||
assert ranges == [(226.0, 234.0)]
|
||||
assert paved_at(230.0, ranges) and paved_at(226.0, ranges) and not paved_at(240.0, ranges)
|
||||
|
||||
|
||||
def test_노면은_표고_낮은_쪽으로(ts):
|
||||
# 좌 높음(입력 단면유형이 반대여도 지형) · 우 높음 · 같으면 단면유형(right_cut = 우 상류)
|
||||
assert ts["deckLeftHigh"]["left"] > ts["deckLeftHigh"]["right"]
|
||||
assert ts["deckRightHigh"]["right"] > ts["deckRightHigh"]["left"]
|
||||
assert ts["deckFlat"]["right"] > ts["deckFlat"]["left"]
|
||||
# 경사 5 % · 노선 중심 100 − 깊이 0.3 · 3 m 에서 ±0.15
|
||||
assert ts["deckLeftHigh"]["left"] == pytest.approx(99.85)
|
||||
assert ts["deckLeftHigh"]["right"] == pytest.approx(99.55)
|
||||
|
||||
|
||||
def test_저장_뒤_서버가_물넘이_측구를_맞춤():
|
||||
router = _read("B06_Section", "B06_Section_Router_Design.py")
|
||||
assert 'bool(design.get("ford_paved")) == ford_paved' in router
|
||||
refresh = _read("B06_Section", "B06_Section_Cross_Refresh.ts")
|
||||
assert "fordPaved: Boolean(section.ford_pavement)," in refresh
|
||||
|
||||
|
||||
CHUTE = {"ford_chute_1_structure": "돌붙임", "ford_chute_1_thickness": 30}
|
||||
|
||||
|
||||
def test_도수로_길이_서버_TS_같은_값(ts):
|
||||
assert (
|
||||
_chute(CHUTE, "ford_chute")
|
||||
== ts["chuteNoLength"]
|
||||
== {
|
||||
"structure": "돌붙임",
|
||||
"thickness_m": 0.3,
|
||||
}
|
||||
)
|
||||
long = {**CHUTE, "ford_chute_1_length_m": 3}
|
||||
assert _chute(long, "ford_chute") == ts["chuteLength"]
|
||||
assert ts["chuteLength"]["length_m"] == 3
|
||||
|
||||
|
||||
def test_도수로_길이_없음_원지반까지_있음_그_길이(ts):
|
||||
# 유출 = 좌(우가 표고 높음) · 노견(2.5, 100) → 원지반(10, 95)
|
||||
assert ts["lineNoLength"] == [
|
||||
{"offset": 2.5, "elevation": 100},
|
||||
{"offset": 10, "elevation": 95},
|
||||
]
|
||||
start, end = ts["lineLength"]
|
||||
assert start == {"offset": 2.5, "elevation": 100}
|
||||
assert (end["offset"] - 2.5) ** 2 + (100 - end["elevation"]) ** 2 == pytest.approx(9.0)
|
||||
assert end["offset"] == pytest.approx(2.5 + 3 * 7.5 / 9.0139, abs=1e-3)
|
||||
assert ts["lineTooLong"] == ts["lineNoLength"] # 사면보다 길면 원지반에서 멈춤
|
||||
|
||||
|
||||
def test_컨테이너_같은_칸_맨_아래_길이():
|
||||
spec = _read("A00_Common", "design", "design_structure_ford_pavement.ts")
|
||||
assert (
|
||||
'items: [...CHUTE_ITEMS, { id: "length_m", label: "길이 (m)", kind: "number", step: 0.1 }],'
|
||||
in spec
|
||||
)
|
||||
|
||||
|
||||
def test_길이는_집계_수량에_안_실림():
|
||||
options = {**CHUTE, "ford_chute_1_bond": "찰붙임", "ford_chute_1_area": 12}
|
||||
item = {"type_id": "ford_pavement", "options": options, "복사본": "pipe@50.0"}
|
||||
plain = container_rows(item, {"gfordpavechute1": "SS1"})
|
||||
long = container_rows(
|
||||
{**item, "options": {**options, "ford_chute_1_length_m": 7}}, {"gfordpavechute1": "SS1"}
|
||||
)
|
||||
# 집계 열이 읽는 칸(length_m · 쪽별 revet_length_m)은 그대로 — 길이는 안 읽히는 이름으로만
|
||||
for key in ("length_m", "inlet_revet_length_m", "outlet_revet_length_m", "area_m"):
|
||||
assert plain[0]["options"].get(key) == long[0]["options"].get(key)
|
||||
|
||||
class Sheet:
|
||||
def __init__(self) -> None:
|
||||
self.rows: list[tuple] = []
|
||||
|
||||
def add(self, at, key, sort, group, label, value, combos) -> None:
|
||||
self.rows.append((key, label, value))
|
||||
|
||||
sheet = Sheet()
|
||||
_chutes(sheet, 50.0, {**options, "ford_chute_1_length_m": 7}, {"gfordpavechute1": "SS1"}, {})
|
||||
assert sheet.rows == [("chute|찰붙임", "㎡", 12.0)] # 돌붙임 수량 = 면적 · 길이 안 탐
|
||||
@@ -25,7 +25,9 @@ const load = (file) => {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, () => ({}));
|
||||
// 같은 폴더 명세 import(`CHUTE_ITEMS` · 286-3)만 따라 옮김
|
||||
const near = (m) => (m.startsWith("./") ? load(path.join(path.dirname(file), `${m}.ts`)) : {});
|
||||
new Function("exports", "module", "require", js)(box.exports, box, near);
|
||||
return box.exports;
|
||||
};
|
||||
const s = load(path.join(ROOT, "A00_Common/design/design_structure_spec.ts"));
|
||||
@@ -75,8 +77,9 @@ def test_저장_값은_도_와_인스턴스_키(out):
|
||||
"width": "6",
|
||||
"height": "0.3",
|
||||
} # 월류 폭 · 높이 = 옛 키 그대로 읽음(65-5)
|
||||
# 두께(cm · 90-2) — 빈 칸은 빈 값(새 상자만 기본 30)
|
||||
assert out["state"] == {"slope": [slope], "chute": [{"structure": "돌붙임", "thickness": ""}]}
|
||||
# 다른 구조물과 같은 칸(구조 · 형태 · 두께 · 면적) + 길이(286-3) — 빈 칸은 빈 값(새 상자만 기본값)
|
||||
chute = {"structure": "돌붙임", "bond": "", "thickness": "", "area": "", "length_m": ""}
|
||||
assert out["state"] == {"slope": [slope], "chute": [chute]}
|
||||
assert out["written"] == {
|
||||
"ford_slope_deg": 15,
|
||||
"ford_width_m": 6,
|
||||
@@ -158,14 +161,15 @@ class Node {
|
||||
}
|
||||
globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) };
|
||||
const pave = load(path.join(ROOT, "B06_Section/B06_Section_UI_Cross_Ford_Pavement.ts"));
|
||||
// +offset = 좌측 · 좌 성토 1:1.5 · 우 절토 오름 — 유입 = 좌(left_cut 아님) → 유출 = 우
|
||||
const design = (mode) => ({
|
||||
// +offset = 좌측 · 좌 성토 1:1.5 · 우 절토 오름 — 유입 = 표고 높은 쪽(설계선 끝 · 286-2) = 우 → 유출 = 좌
|
||||
// `rightEnd` = 우 끝 표고 — 좌 끝(97)과 같으면 단면유형으로 가름(left_cut = 유입 좌 → 유출 우)
|
||||
const design = (mode, rightEnd = 99) => ({
|
||||
section_mode: mode, cross_slope_pct: 2, design_elevation_m: 100, pavement_thickness_m: 0.2,
|
||||
road_edges: { left: { offset_m: 2.5, elevation_m: 100 }, right: { offset_m: -2.5, elevation_m: 100 } },
|
||||
design_line: [
|
||||
{ offset_m: 7, elevation_m: 97 }, { offset_m: 2.5, elevation_m: 100 },
|
||||
{ offset_m: -2.5, elevation_m: 100 }, { offset_m: -5.5, elevation_m: 98 },
|
||||
{ offset_m: -6.5, elevation_m: 98 }, { offset_m: -8, elevation_m: 99 },
|
||||
{ offset_m: -6.5, elevation_m: 98 }, { offset_m: -8, elevation_m: rightEnd },
|
||||
],
|
||||
});
|
||||
const spec = { span_m: 9, depth_m: 0.3, slope_pct: 2, chute: { structure: "돌붙임", thickness_m: 0.3 } };
|
||||
@@ -176,7 +180,7 @@ const run = (mode, withChute = true) => {
|
||||
return band ? band.attrs.points : null;
|
||||
};
|
||||
process.stdout.write(JSON.stringify({
|
||||
outLeft: pave.outletFillLine(design("left_cut")), outRight: pave.outletFillLine(design("right_cut")),
|
||||
outLeft: pave.outletFillLine(design("left_cut", 97)), outRight: pave.outletFillLine(design("left_cut")),
|
||||
band: run("left_cut"), none: run("left_cut", false),
|
||||
}));
|
||||
"""
|
||||
@@ -188,13 +192,14 @@ def test_유출측_성토사면_띠_136_2():
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
got = json.loads(proc.stdout)
|
||||
# 유입 = 좌 → 유출 = 우(− 바깥) · 노견에서 내려가는 동안만(평지 · 오름에서 멈춤)
|
||||
# 양 끝 같음 → 단면유형 left_cut = 유입 좌 → 유출 = 우(− 바깥) · 노견에서 내려가는 동안만
|
||||
assert got["outLeft"] == [
|
||||
{"offset": -2.5, "elevation": 100},
|
||||
{"offset": -5.5, "elevation": 98},
|
||||
{"offset": -6.5, "elevation": 98},
|
||||
{"offset": -8, "elevation": 97},
|
||||
]
|
||||
# right_cut = 유입 우 → 유출 좌 성토 1:1.5
|
||||
# 우 끝이 높음 = 유입 우(입력 left_cut 이어도 지형 · 286-2) → 유출 좌 성토 1:1.5
|
||||
assert got["outRight"] == [{"offset": 2.5, "elevation": 100}, {"offset": 7, "elevation": 97}]
|
||||
assert got["band"] and got["band"].startswith("-2.5,100 -5.5,98")
|
||||
assert got["band"] and got["band"].startswith("2.5,100 7,97")
|
||||
assert got["none"] is None
|
||||
|
||||
@@ -87,11 +87,12 @@ def test_pipe_without_options_falls_back_to_config_defaults(tmp_path):
|
||||
assert extras == ((30.0, "파형강관 D800"),)
|
||||
|
||||
|
||||
def test_ford_pavement_is_not_an_irregular_station(tmp_path):
|
||||
"""물넘이포장은 비정규 측점이 아니다 — 종단 그래프 구조물 표시 몫(2026-08-28 사용자 확정)."""
|
||||
def test_ford_pavement_plants_its_station(tmp_path):
|
||||
"""물넘이포장도 기준 측점 하나를 심는다 — 그 측점 횡단도에 파인 노면(286-1)."""
|
||||
points = [{"chainage_m": 40.0, "facility": "ford_pavement"}, {"chainage_m": 60.0}]
|
||||
extras = resolve_extra_stations(tmp_path, parse_pipe_points(points))
|
||||
assert [chainage for chainage, _label in extras] == [60.0]
|
||||
assert [chainage for chainage, _label in extras] == [40.0, 60.0]
|
||||
assert extras[0][1] == "물넘이포장"
|
||||
|
||||
|
||||
# ── 구조물 정본 A군 합류 ───────────────────────────────────────────────────
|
||||
|
||||
@@ -49,7 +49,7 @@ def test_relabel_follows_saved_kind(tmp_path):
|
||||
85.05: "BOX암거",
|
||||
120.0: "옹벽", # 관 지점 라벨이 아닌 측점은 그대로
|
||||
173.09: "흄관 D1200",
|
||||
258.12: None, # 물넘이포장 = 라벨 없음(측점 생성 규칙과 같음)
|
||||
258.12: "물넘이포장", # 물넘이포장도 종류 이름(286-1 · 측점 생성 규칙과 같음)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -218,6 +218,19 @@ CASES = [
|
||||
"shoulder_extend_left_m": 0.4,
|
||||
},
|
||||
},
|
||||
{
|
||||
# 286-2 — 물넘이포장 자리 = 측구 없음(좌절토라 자동이면 측구가 섬)
|
||||
"name": "물넘이포장 자리 측구 없음",
|
||||
"samples": _ground(0.35),
|
||||
"design_elevation_m": 100.4,
|
||||
"options": {
|
||||
"ground_type": "soil",
|
||||
"section_mode": "left_cut",
|
||||
"ditch_side": "left",
|
||||
"paved": True,
|
||||
"ford_paved": True,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
_TS_RUNNER = """
|
||||
@@ -241,6 +254,7 @@ const camel = {
|
||||
curve_widening_m: "curveWideningM",
|
||||
shoulder_extend_left_m: "shoulderExtendLeftM",
|
||||
shoulder_extend_right_m: "shoulderExtendRightM",
|
||||
ford_paved: "fordPaved",
|
||||
};
|
||||
// 사토장 제원만 칸 이름이 다르다 — 파이썬은 snake, TS 는 camel 이다.
|
||||
const spoilCamel = { side: "side", width_m: "widthM", slope_ratio_n: "slopeRatioN" };
|
||||
|
||||
Reference in New Issue
Block a user