Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_7' into sub_laptop_6
This commit is contained in:
@@ -501,6 +501,8 @@ export interface CrossDesign {
|
||||
/** 물넘이포장 · 맹암거 자리(286-2 · 287-4) — 측구 없음 · 노면 = 지대 낮은 쪽 한 경사(`tilt_pct`). */
|
||||
tilted?: boolean;
|
||||
tilt_pct?: number;
|
||||
/** 물넘이 낮은 쪽 늘인 노면 끝(294-3) — 그 쪽 절토 사면 대신 노면 · 콘크리트가 원지반까지. */
|
||||
tilt_reach?: { side: "left" | "right"; offset_m: number; elevation_m: number };
|
||||
cut_area_m2: number;
|
||||
/**
|
||||
* 절토 내역(합 = `cut_area_m2`). 지표면~암반 경계선이 토사, 그 아래가 암이다.
|
||||
|
||||
@@ -314,6 +314,7 @@ def compute_cross_design(
|
||||
shoulder_extend_left_m=shoulder_extend_left_m,
|
||||
shoulder_extend_right_m=shoulder_extend_right_m,
|
||||
tilted=bool(tilted),
|
||||
ground_span=(valid[0][0], valid[-1][0]),
|
||||
)
|
||||
|
||||
# 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만). 꼭짓점을 넣어야
|
||||
@@ -546,6 +547,13 @@ def compute_cross_design(
|
||||
}
|
||||
if tilted:
|
||||
result["tilted"] = True
|
||||
for side, reach in geometry.reach.items(): # 낮은 쪽 늘인 노면 끝(294-3)
|
||||
signed = reach if side == "left" else -reach
|
||||
result["tilt_reach"] = {
|
||||
"side": side,
|
||||
"offset_m": round(signed, 4),
|
||||
"elevation_m": round(geometry.road_z(signed), 4),
|
||||
}
|
||||
if tilt_pct is not None:
|
||||
result["tilt_pct"] = round(float(tilt_pct), 4)
|
||||
if drop > 0:
|
||||
|
||||
@@ -63,6 +63,7 @@ class _SectionGeometry:
|
||||
shoulder_extend_left_m: float = 0.0,
|
||||
shoulder_extend_right_m: float = 0.0,
|
||||
tilted: bool = False,
|
||||
ground_span: tuple[float, float] | None = None,
|
||||
) -> None:
|
||||
half_road = group["road_width_m"] / 2.0
|
||||
# 곡선부 확폭은 **한쪽으로만** 붙는다(2026-09-06 사용자 확정: 곡선 바깥쪽).
|
||||
@@ -195,6 +196,36 @@ class _SectionGeometry:
|
||||
(edge_offset + outward * top, edge_z),
|
||||
]
|
||||
|
||||
# 물넘이 낮은 쪽 연장(294-3) — 노면이 내려가는 쪽 노면 끝 원지반이 노면보다 높으면(절토)
|
||||
# 노면을 같은 기울기로 늘여 원지반과 처음 만나는 자리까지(그 쪽 절토 사면 없음) · 반폭
|
||||
# (`ground_span`) 안에서 못 만나면 옛 길(절토 사면). ⚠ TS 짝 `SectionGeometry.reach`.
|
||||
self.reach: dict[str, float] = {}
|
||||
if tilted and ground_at is not None and abs(self.slope_per_offset) > 1e-12:
|
||||
self._extend_low_side(ground_at, ground_span)
|
||||
|
||||
def _extend_low_side(
|
||||
self, ground_at: Callable[[float], float], span: tuple[float, float] | None
|
||||
) -> None:
|
||||
side = "left" if self.slope_per_offset < 0 else "right"
|
||||
sign = 1.0 if side == "left" else -1.0
|
||||
extent = self.left_extent if side == "left" else self.right_extent
|
||||
limit = (span[1] if side == "left" else -span[0]) if span else extent + 50.0
|
||||
|
||||
def gap(dist: float) -> float: # > 0 = 원지반이 늘인 노면 위(절토)
|
||||
return ground_at(sign * dist) - self.road_z(sign * dist)
|
||||
|
||||
if gap(extent) <= 1e-3:
|
||||
return
|
||||
step = 0.05
|
||||
dist = extent
|
||||
while dist < limit - 1e-9:
|
||||
nxt = min(dist + step, limit)
|
||||
g0, g1 = gap(dist), gap(nxt)
|
||||
if g1 <= 0:
|
||||
self.reach[side] = dist + (nxt - dist) * g0 / (g0 - g1)
|
||||
return
|
||||
dist = nxt
|
||||
|
||||
def road_z(self, offset_m: float) -> float:
|
||||
"""노면(노견 포함) 설계고 — 중심 계획고에서 횡단경사로 기운 단일 평면."""
|
||||
return self.z_center + self.slope_per_offset * offset_m
|
||||
@@ -430,6 +461,9 @@ class _SectionGeometry:
|
||||
return points[-1][1]
|
||||
role = self.left_role if side == "left" else self.right_role
|
||||
dist = abs(offset_m)
|
||||
reach = self.reach.get(side)
|
||||
if reach is not None: # 늘인 노면 → 그 바깥은 원지반(294-3)
|
||||
return self.road_z(offset_m) if dist <= reach + 1e-9 else ground_m
|
||||
if role == "cut":
|
||||
# 지반과 1회 교차하면 그 이후 절토는 의미 없음 → 지반 추종(N-2-4).
|
||||
cross = self.cut_cross_dist(side)
|
||||
@@ -468,6 +502,9 @@ class _SectionGeometry:
|
||||
if cross is not None and offset > cross + 1e-9:
|
||||
break
|
||||
points.append(offset if side == "left" else -offset)
|
||||
# 늘인 노면 끝(294-3)
|
||||
for side, reach in self.reach.items():
|
||||
points.append(reach if side == "left" else -reach)
|
||||
# 절·성토 사면과 지반의 **첫** 교차점을 꼭짓점에 넣어 면적 절단을 정확히 한다(N-2-4).
|
||||
for side in ("left", "right"):
|
||||
role = self.left_role if side == "left" else self.right_role
|
||||
|
||||
@@ -103,7 +103,7 @@ export function appendFordPavementOverlay(
|
||||
x: (offset: number) => number,
|
||||
y: (elevation: number) => number,
|
||||
): boolean {
|
||||
const edges = design.road_edges;
|
||||
const edges = fordEdges(design);
|
||||
if (!spec || !edges) return false;
|
||||
const from = svg.children.length;
|
||||
// 노면공 항목은 안 넘김 — 물넘이 자체 포장(노면공 포장 고름과 안 섞임)
|
||||
@@ -134,6 +134,15 @@ export function appendFordPavementOverlay(
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 콘크리트 · 노면 양 끝 — 낮은 쪽을 늘였으면(294-3 `tilt_reach`) 그 쪽은 원지반과 만난 자리까지. */
|
||||
export function fordEdges(design: CrossDesign): CrossDesign["road_edges"] | undefined {
|
||||
const edges = design.road_edges;
|
||||
const reach = design.tilt_reach;
|
||||
if (!edges || !reach) return edges;
|
||||
const end = { offset_m: reach.offset_m, elevation_m: reach.elevation_m };
|
||||
return reach.side === "left" ? { ...edges, left: end } : { ...edges, right: end };
|
||||
}
|
||||
|
||||
/**
|
||||
* 유출측(`inflowLeft` 반대) 노견 바깥 성토사면 선(136-2) — 설계선을 노견에서 바깥으로 따라
|
||||
* 내려가는 동안만(오르면 절토라 멈춤). 성토가 없으면 빈 선(띠 안 그림).
|
||||
|
||||
@@ -194,6 +194,8 @@ export interface CrossDesignResult {
|
||||
/** 물넘이포장 · 맹암거 자리(286-2 · 287-4) — 측구 없음 · 지대 낮은 쪽 한 경사. */
|
||||
tilted?: boolean;
|
||||
tilt_pct?: number;
|
||||
/** 물넘이 낮은 쪽 늘인 노면 끝(294-3) — 그 쪽 절토 사면 대신 노면이 원지반까지. */
|
||||
tilt_reach?: { side: "left" | "right"; offset_m: number; elevation_m: number };
|
||||
pavement_thickness_m?: number;
|
||||
rock_boundary_offset_m?: number;
|
||||
}
|
||||
@@ -377,6 +379,7 @@ export function computeCrossDesign(
|
||||
shoulderExtendLeftM: options.shoulderExtendLeftM ?? 0,
|
||||
shoulderExtendRightM: options.shoulderExtendRightM ?? 0,
|
||||
tilted: Boolean(options.tilted),
|
||||
groundSpan: [valid[0][0], valid[valid.length - 1][0]],
|
||||
});
|
||||
|
||||
// 적분 오프셋 = 지반 샘플 ∪ 설계 꼭짓점(샘플 범위 안쪽만).
|
||||
@@ -592,6 +595,17 @@ export function computeCrossDesign(
|
||||
cut_slope_segments: geometry.cutSlopeSegments(),
|
||||
};
|
||||
if (options.tilted) result.tilted = true;
|
||||
// 낮은 쪽 늘인 노면 끝(294-3 · 파이썬 짝)
|
||||
for (const side of ["left", "right"] as const) {
|
||||
const reach = geometry.reach[side];
|
||||
if (!options.tilted || reach === undefined) continue;
|
||||
const signed = side === "left" ? reach : -reach;
|
||||
result.tilt_reach = {
|
||||
side,
|
||||
offset_m: round4(signed),
|
||||
elevation_m: round4(geometry.roadZ(signed)),
|
||||
};
|
||||
}
|
||||
if (options.tilted && options.tiltPct != null) result.tilt_pct = round4(options.tiltPct);
|
||||
if (drop > 0) result.surface_drop_m = round4(drop);
|
||||
if (options.berm) {
|
||||
|
||||
@@ -147,6 +147,8 @@ export class SectionGeometry {
|
||||
shoulderExtendRightM?: number;
|
||||
/** 물넘이포장 · 맹암거 자리 — 측구 없음 · 노면 = 지대 낮은 쪽 한 경사(286-2 · 287-4 · 파이썬 짝 `tilted`). */
|
||||
tilted?: boolean;
|
||||
/** 원지반 샘플 범위(오프셋 최소 · 최대) — 낮은 쪽 연장 한계(294-3 · 파이썬 짝 `ground_span`). */
|
||||
groundSpan?: [number, number] | null;
|
||||
}) {
|
||||
const { group } = params;
|
||||
const halfRoad = group.road_width_m / 2;
|
||||
@@ -261,6 +263,36 @@ export class SectionGeometry {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 물넘이 낮은 쪽 연장(294-3) — 노면이 내려가는 쪽 노면 끝 원지반이 노면보다 높으면(절토) 노면을
|
||||
// 같은 기울기로 늘여 원지반과 처음 만나는 자리까지(그 쪽 절토 사면 없음) · 반폭 안에서 못 만나면
|
||||
// 옛 길(절토 사면). ⚠ 파이썬 짝 `_extend_low_side`.
|
||||
if (params.tilted && params.groundAt && Math.abs(this.slopePerOffset) > 1e-12)
|
||||
this.extendLowSide(params.groundAt, params.groundSpan ?? null);
|
||||
}
|
||||
|
||||
/** 낮은 쪽 늘인 노면 끝(오프셋 절대값) — 쪽마다(294-3). */
|
||||
reach: { left?: number; right?: number } = {};
|
||||
|
||||
private extendLowSide(groundAt: (offsetM: number) => number, span: [number, number] | null) {
|
||||
const side = this.slopePerOffset < 0 ? "left" : "right";
|
||||
const sign = side === "left" ? 1 : -1;
|
||||
const extent = side === "left" ? this.leftExtent : this.rightExtent;
|
||||
const limit = span ? (side === "left" ? span[1] : -span[0]) : extent + 50;
|
||||
const gap = (dist: number) => groundAt(sign * dist) - this.roadZ(sign * dist); // > 0 = 절토
|
||||
if (gap(extent) <= 1e-3) return;
|
||||
const step = 0.05;
|
||||
let dist = extent;
|
||||
while (dist < limit - 1e-9) {
|
||||
const next = Math.min(dist + step, limit);
|
||||
const g0 = gap(dist);
|
||||
const g1 = gap(next);
|
||||
if (g1 <= 0) {
|
||||
this.reach[side] = dist + ((next - dist) * g0) / (g0 - g1);
|
||||
return;
|
||||
}
|
||||
dist = next;
|
||||
}
|
||||
}
|
||||
|
||||
/** 노면(노견 포함) 설계고 — 중심 계획고에서 횡단경사로 기운 단일 평면. */
|
||||
@@ -422,6 +454,8 @@ export class SectionGeometry {
|
||||
}
|
||||
const role = side === "left" ? this.leftRole : this.rightRole;
|
||||
const dist = Math.abs(offsetM);
|
||||
const reach = this.reach[side];
|
||||
if (reach !== undefined) return dist <= reach + 1e-9 ? this.roadZ(offsetM) : groundM; // 294-3
|
||||
if (role === "cut") {
|
||||
const cross = this.cutCrossDist(side);
|
||||
if (cross !== null && dist >= cross) return groundM;
|
||||
@@ -512,6 +546,9 @@ export class SectionGeometry {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 늘인 노면 끝(294-3)
|
||||
if (this.reach.left !== undefined) points.push(this.reach.left);
|
||||
if (this.reach.right !== undefined) points.push(-this.reach.right);
|
||||
for (const side of ["left", "right"]) {
|
||||
const role = side === "left" ? this.leftRole : this.rightRole;
|
||||
const cross = role === "cut" ? this.cutCrossDist(side) : this.fillCrossDist(side);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/* 294 물넘이 낮은 쪽 연장 · 도수로 끝단 · 띠 ↔ 사면 고름 헬퍼 — 진짜 TS 를 Node 로 돌려 JSON 한 벌로. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
const load = (file) => {
|
||||
if (cache.has(file)) return cache.get(file);
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(file, box.exports);
|
||||
const near = (m) => {
|
||||
if (m.startsWith("@config/")) return load(path.join(ROOT, "config", `${m.slice(8)}.ts`));
|
||||
if (m.startsWith("@util/")) return load(path.join(ROOT, "common_util", `${m.slice(6)}.ts`));
|
||||
if (m.startsWith(".")) return load(path.join(path.dirname(file), `${m}.ts`));
|
||||
return new Proxy({}, { get: () => () => {} });
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, near);
|
||||
return box.exports;
|
||||
};
|
||||
|
||||
class Node {
|
||||
constructor(tag) {
|
||||
this.tag = tag;
|
||||
this.attrs = {};
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.style = {};
|
||||
}
|
||||
setAttribute(k, v) {
|
||||
this.attrs[k] = String(v);
|
||||
}
|
||||
getAttribute(k) {
|
||||
return this.attrs[k] ?? null;
|
||||
}
|
||||
append(...kids) {
|
||||
this.children.push(...kids);
|
||||
}
|
||||
}
|
||||
globalThis.document = { createElementNS: (_ns, tag) => new Node(tag) };
|
||||
|
||||
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const pave = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts"));
|
||||
const design = input.design; // 서버가 낸 양측 절토 · 낮은 쪽 연장 설계
|
||||
const svg = new Node("g");
|
||||
pave.appendFordPavementOverlay(
|
||||
svg,
|
||||
{ span_m: 8, depth_m: 0.3, slope_pct: 5 },
|
||||
design,
|
||||
(o) => o,
|
||||
(e) => e,
|
||||
);
|
||||
const paveLayer = svg.children.find((k) => (k.attrs.class ?? "").includes("ford-pavement"));
|
||||
|
||||
const out = {
|
||||
edges: pave.fordEdges(design),
|
||||
pavePoints: paveLayer?.attrs.points ?? null,
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -0,0 +1,112 @@
|
||||
"""294 물넘이 낮은 쪽 연장 · 도수로/돌붙임 끝단 · 띠 ↔ 성토 사면 고름(요구 188-2 · 188-3 · 189 · 그림_188).
|
||||
|
||||
못박는 것
|
||||
- 294-3 물넘이 측점 낮은 쪽 노면 끝 원지반이 노면보다 높으면(절토) 노면을 같은 기울기로 늘여 원지반과
|
||||
처음 만나는 자리까지(그 쪽 절토 사면 없음 · `tilt_reach`) · 성토 쪽 그대로 · 반폭 안에서 못 만나면 옛 길 ·
|
||||
서버 정본 ↔ TS 짝 같은 값 = 횡단 설계 거울(`test_b06_cross_design_mirror` 「물넘이포장 낮은 쪽 노면
|
||||
연장」) · 그림 = 콘크리트(포장층)가 늘인 끝까지.
|
||||
|
||||
진짜 TS 는 `helper_294_3_ford_extend_chute.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))
|
||||
if str(HERE) not in sys.path:
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402
|
||||
from config.config_system import STANDARD_CROSS_SECTION # noqa: E402
|
||||
from test_b06_cross_design_mirror import CASES, _dip_ground # noqa: E402
|
||||
|
||||
KW = {
|
||||
"ground_type": "soil",
|
||||
"section_mode": "left_cut",
|
||||
"ditch_side": "left",
|
||||
"paved": True,
|
||||
"tilted": True,
|
||||
"tilt_pct": 5.0,
|
||||
"standard": STANDARD_CROSS_SECTION,
|
||||
}
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
def _line(samples_fn) -> list[dict]:
|
||||
return [
|
||||
{"offset_m": o / 2, "elevation_m": round(samples_fn(o / 2), 4), "valid": True}
|
||||
for o in range(-24, 25)
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def dip() -> dict:
|
||||
return compute_cross_design(_dip_ground(), 100.4, **KW)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts(dip, tmp_path_factory) -> dict:
|
||||
source = tmp_path_factory.mktemp("s294") / "in.json"
|
||||
source.write_text(json.dumps({"design": dip}), encoding="utf-8")
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_294_3_ford_extend_chute.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_양측_절토_낮은_쪽_늘인_끝은_원지반_교점(dip):
|
||||
reach = dip["tilt_reach"]
|
||||
# 원지반 101.05 + 0.2·o(−4.5 까지) · 노면 100.4 + 0.05·o → −4.3333 에서 만남
|
||||
assert reach["side"] == "right"
|
||||
assert reach["offset_m"] == pytest.approx(-4.3333, abs=1e-4)
|
||||
assert reach["elevation_m"] == pytest.approx(100.4 - 0.05 * 4.3333, abs=1e-4)
|
||||
edge = dip["road_edges"]["right"]["offset_m"]
|
||||
for p in dip["design_line"]: # 노견 끝 ~ 늘인 끝 = 노면 기울기 그대로(절토 사면 없음)
|
||||
if reach["offset_m"] - 1e-6 <= p["offset_m"] <= edge:
|
||||
assert p["elevation_m"] == pytest.approx(100.4 + 0.05 * p["offset_m"], abs=1e-4)
|
||||
assert dip["ditch_enabled"] is False
|
||||
|
||||
|
||||
def test_성토_쪽은_그대로():
|
||||
got = compute_cross_design(_line(lambda o: 100.0 + 0.35 * o), 100.4, **KW)
|
||||
assert "tilt_reach" not in got # 낮은 쪽(우) 노면 끝 원지반이 노면 아래 = 성토 사면
|
||||
|
||||
|
||||
def test_반폭_안에서_못_만나면_옛_길():
|
||||
got = compute_cross_design(_line(lambda o: 101.0 + 0.02 * o), 100.4, **KW)
|
||||
assert "tilt_reach" not in got # 늘인 노면이 끝까지 원지반 아래 = 절토 사면 그대로
|
||||
right = [
|
||||
p for p in got["design_line"] if p["offset_m"] < got["road_edges"]["right"]["offset_m"]
|
||||
]
|
||||
assert any(p["elevation_m"] > got["road_edges"]["right"]["elevation_m"] for p in right)
|
||||
|
||||
|
||||
def test_서버_TS_같은_값은_거울_시험():
|
||||
assert any(case["name"] == "물넘이포장 낮은 쪽 노면 연장" for case in CASES)
|
||||
|
||||
|
||||
def test_콘크리트는_늘인_끝까지(dip, ts):
|
||||
assert ts["edges"]["right"] == {
|
||||
"offset_m": dip["tilt_reach"]["offset_m"],
|
||||
"elevation_m": dip["tilt_reach"]["elevation_m"],
|
||||
}
|
||||
first = ts["pavePoints"].split()[1] # 포장층 윗면 우 끝
|
||||
assert float(first.split(",")[0]) == pytest.approx(-4.3333, abs=1e-4)
|
||||
@@ -51,6 +51,16 @@ def _ground(slope: float, *, gap: tuple[float, float] | None = None) -> list[dic
|
||||
return samples
|
||||
|
||||
|
||||
def _dip_ground() -> list[dict]:
|
||||
"""양측 절토 · 우측 노면 끝 너머(−4.5 m 밖)에서 원지반이 급히 내려가는 지반(294-3)."""
|
||||
samples = []
|
||||
for index in range(49):
|
||||
offset = -12.0 + index * 0.5
|
||||
z = 101.05 + 0.2 * offset if offset >= -4.5 else 100.15 + 1.0 * (offset + 4.5)
|
||||
samples.append({"offset_m": offset, "elevation_m": round(z, 4), "valid": True})
|
||||
return samples
|
||||
|
||||
|
||||
# 갈래를 골고루 태운다 — 지반유형 셋 · 단면유형 · 측구 형식 · 포장 · 2단 경사 · 세월교
|
||||
# 노면 하강 · 곡선부 확폭(바깥쪽 좌/우) · 지반선 결측 구간.
|
||||
CASES = [
|
||||
@@ -245,6 +255,20 @@ CASES = [
|
||||
"tilt_pct": 5.2408,
|
||||
},
|
||||
},
|
||||
{
|
||||
# 294-3 — 양측 절토 · 낮은 쪽(우) 노면을 늘여 원지반이 내려와 만나는 자리까지
|
||||
"name": "물넘이포장 낮은 쪽 노면 연장",
|
||||
"samples": _dip_ground(),
|
||||
"design_elevation_m": 100.4,
|
||||
"options": {
|
||||
"ground_type": "soil",
|
||||
"section_mode": "left_cut",
|
||||
"ditch_side": "left",
|
||||
"paved": True,
|
||||
"tilted": True,
|
||||
"tilt_pct": 5.0,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
_TS_RUNNER = """
|
||||
|
||||
Reference in New Issue
Block a user