Merge remote-tracking branch 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -88,28 +88,31 @@ export function chuteAreaM2(widthM: number, lengthM: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* 물넘이 포장 도수로/돌붙임 길이 ↔ 면적(287-1) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가 도로 방향으로
|
||||
* 걸치는 월류 폭 · 비면 물넘이 기본 폭) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
|
||||
* 물넘이 포장 · 세월교 도수로/돌붙임 길이 ↔ 면적(287-1 · 294-4) — 길이를 넣으면 면적 = 폭 × 길이(폭 = 띠가
|
||||
* 도로 방향으로 걸치는 월류 폭 · 비면 물넘이 기본 폭 · 배관 · 박스는 폭 정의 없어 자동 안 함) · 길이를 비우면 면적 = 칸 기본값 · 월류 폭을 바꾸면 길이가 있을 때만
|
||||
* 다시 셈. 길이는 그림(사면 따라 그 길이) · 면적은 수량(B08 돌붙임)이 씀.
|
||||
*/
|
||||
export function syncChuteArea(
|
||||
form: Pick<SpecForm, "state" | "setValue">,
|
||||
c: SpecContainer,
|
||||
item: SpecItem,
|
||||
/** 도수로 상자 · 월류 폭 상자 — 물넘이 `chute` · `slope` · 세월교 `chute_add` · `ford`(294-4). */
|
||||
chuteBox = "chute",
|
||||
widthBox = "slope",
|
||||
): void {
|
||||
const byLength = c.id === "chute" && item.id === "length_m";
|
||||
if (!byLength && !(c.id === "slope" && item.id === "width")) return;
|
||||
const chute = form.state().chute?.[0];
|
||||
const byLength = c.id === chuteBox && item.id === "length_m";
|
||||
if (!byLength && !(c.id === widthBox && item.id === "width")) return;
|
||||
const chute = form.state()[chuteBox]?.[0];
|
||||
if (!chute) return;
|
||||
const length = Number(chute.length_m ?? "");
|
||||
if (!(chute.length_m ?? "").trim() || !(length > 0)) {
|
||||
if (byLength)
|
||||
form.setValue("chute", "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
|
||||
form.setValue(chuteBox, "area", CHUTE_ITEMS.find((i) => i.id === "area")?.default ?? "");
|
||||
return;
|
||||
}
|
||||
const width = Number(form.state().slope?.[0]?.width ?? "");
|
||||
const width = Number(form.state()[widthBox]?.[0]?.width ?? "");
|
||||
form.setValue(
|
||||
"chute",
|
||||
chuteBox,
|
||||
"area",
|
||||
String(chuteAreaM2(width > 0 ? width : FORD_PAVEMENT_DEFAULT_WIDTH_M, length)),
|
||||
);
|
||||
|
||||
@@ -35,8 +35,8 @@ export const FORD_PAVEMENT_SPEC: StructureSpec = {
|
||||
prefix: "ford_chute",
|
||||
link: "gfordpavechute",
|
||||
max: 1,
|
||||
// 배관 · 박스 · 세월교와 같은 칸(구조 · 형태 · 두께 · 면적 · 90-2) + 맨 아래 길이(그림 전용 · 286-3)
|
||||
items: [...CHUTE_ITEMS, { id: "length_m", label: "길이 (m)", kind: "number", step: 0.1 }],
|
||||
// 배관 · 박스 · 세월교와 같은 칸(구조 · 형태 · 두께 · 면적 · 맨 아래 길이 — 그림 전용 · 286-3 · 294-4)
|
||||
items: CHUTE_ITEMS,
|
||||
},
|
||||
],
|
||||
// 표 03 에 없는 옛 칸 — 화면에서 빼고 값은 남김(지난 정의 값)
|
||||
|
||||
@@ -144,6 +144,8 @@ export const CHUTE_ITEMS: SpecItem[] = [
|
||||
},
|
||||
{ id: "thickness", label: "두께 (cm)", kind: "number", step: 1, default: "45" },
|
||||
{ id: "area", label: "면적 (㎡)", kind: "number", step: 0.1, default: "30" },
|
||||
// 길이(294-4 · `{접두}_{n}_length_m`) — 띠를 사면 따라 그릴 길이 · 그림 전용(수량 · 내역 · 일위대가에 안 씀)
|
||||
{ id: "length_m", label: "길이 (m)", kind: "number", step: 0.1 },
|
||||
];
|
||||
|
||||
/** 도수로/돌붙임(선택 · 하나만 · 54-48) — 옛 둘 이상은 보이되 [추가]만 숨김. */
|
||||
|
||||
@@ -487,6 +487,8 @@ export function buildCorridorStructures(
|
||||
chuteBands(
|
||||
fordLayout.sides.filter((side) => side.role === "outlet").flatMap((s) => s.fillSegments),
|
||||
section.ford?.chute,
|
||||
undefined,
|
||||
fordLayout.ground,
|
||||
).forEach((band) => pushSwept("chute", band, half, half, null, "chute"));
|
||||
// 날개벽 4매 — BOX암거와 같은 규칙(2026-08-25 사용자). 뿌리는 **벽 외측면**
|
||||
// (성토 경사측면과 마주치는 면) 모서리다(2026-08-25 ⑤ — 종전 바닥판 바깥 끝은
|
||||
@@ -595,7 +597,12 @@ export function buildCorridorStructures(
|
||||
);
|
||||
}
|
||||
// 도수로 · 돌붙임 띠 — 유출 성토 조각(단 사이 포함) · 묻힌 끝 3도 절토 조각(152-3) · 유출 벽 길이(135-3).
|
||||
chuteBands(layout.outletFill.segments, section.culvert?.chute, isChuteSegment).forEach((band) =>
|
||||
chuteBands(
|
||||
layout.outletFill.segments,
|
||||
section.culvert?.chute,
|
||||
isChuteSegment,
|
||||
layout.ground,
|
||||
).forEach((band) =>
|
||||
pushSwept("chute", band, outletSpan.beforeM, outletSpan.afterM, null, "chute"),
|
||||
);
|
||||
// 다단은 **단별 구간값**을 따른다(2026-08-29 사용자 — 단마다 연장이 다르다).
|
||||
|
||||
@@ -182,6 +182,8 @@ export function boxSolids(
|
||||
const chutes: CorridorStructure[] = chuteBands(
|
||||
layout.sides.filter((side) => side.wingRole === "outlet").flatMap((side) => side.fillSegments),
|
||||
box.chute,
|
||||
undefined,
|
||||
layout.ground,
|
||||
).map((band) => ({
|
||||
chainage_m: chainage,
|
||||
kind: "chute",
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
|
||||
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 { fordChuteLine } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
|
||||
import {
|
||||
chuteBandOutline,
|
||||
chuteRun,
|
||||
groundLine,
|
||||
} from "../B06_Section/B06_Section_UI_Cross_Wall_Hatch";
|
||||
import { outletFillLine } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
|
||||
|
||||
/** 성토 조각 하나 — B06 오버레이가 띠를 그리는 선(절토 조각은 뺀다). */
|
||||
interface FillSegment {
|
||||
@@ -25,10 +29,11 @@ export function chuteBands(
|
||||
segments: ReadonlyArray<FillSegment>,
|
||||
chute: ChuteSpec | null | undefined,
|
||||
keep: (segment: FillSegment) => boolean = (segment) => segment.kind !== "cut",
|
||||
/** 원지반 — 길이가 사면보다 길면 이어 그림(294-4 · 횡단 띠와 같은 `chuteRun`). */
|
||||
ground?: readonly OffsetPoint[],
|
||||
): OffsetPoint[][] {
|
||||
if (!chute) return [];
|
||||
return segments.flatMap((segment) => {
|
||||
if (!keep(segment)) return [];
|
||||
return chuteRun(segments.filter(keep), chute.length_m, ground).flatMap((segment) => {
|
||||
const band = chuteBandOutline(segment.points, chute.thickness_m);
|
||||
return band ? [band.outline] : [];
|
||||
});
|
||||
@@ -51,6 +56,12 @@ export function fordPavementChute(
|
||||
);
|
||||
if (owner !== section) return null;
|
||||
}
|
||||
const bands = chuteBands([{ points: fordChuteLine(section.design, pave.chute) }], pave.chute);
|
||||
const ground = groundLine(section.samples ?? []);
|
||||
const bands = chuteBands(
|
||||
[{ points: outletFillLine(section.design, ground) }],
|
||||
pave.chute,
|
||||
undefined,
|
||||
ground,
|
||||
);
|
||||
return bands.length ? { bands, halfM: Math.max(pave.span_m, 0.5) / 2 } : null;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ import type {
|
||||
|
||||
/** 도수로 · 돌붙임 띠(135-3) — 고른 때만 붙인다(안 고른 측점은 해시 그대로 · 저장본 안 만료). */
|
||||
function chuteParts(chute: ChuteSpec | null | undefined): Array<string | number> {
|
||||
return chute ? ["ch", chute.structure, chute.thickness_m] : [];
|
||||
if (!chute) return [];
|
||||
// 길이(294-4)는 넣은 때만 — 옛 저장본 해시 그대로
|
||||
const base = ["ch", chute.structure, chute.thickness_m];
|
||||
return chute.length_m ? [...base, chute.length_m] : base;
|
||||
}
|
||||
|
||||
/** 해시 입력용 요약 — 구조물에 영향을 주는 값만 짧게 이어 붙인다(Corridor.ts가 쓴다). */
|
||||
|
||||
@@ -384,7 +384,10 @@ export function createFacilityOptionsForm(
|
||||
);
|
||||
// 유출 흙막이 단 수 = 흙막이 상자 수(관보호공과 같은 방향 · 「+ 흙막이」 없음 · 90-1)
|
||||
const fordForm: SpecForm = createSpecForm(specLinks, {
|
||||
onChange: fordFlowBox.onChange,
|
||||
onChange: (c, item) => (
|
||||
syncChuteArea(fordForm, c, item, "chute_add", "ford"),
|
||||
fordFlowBox.onChange(c, item)
|
||||
),
|
||||
fields: (c, values) => [...guardFields(c, values), ...fordFlowBox.fields(c)],
|
||||
count: guardSoilCount(
|
||||
() => chainage,
|
||||
|
||||
@@ -166,6 +166,7 @@ export function appendBoxOverlay(
|
||||
box.chute,
|
||||
x,
|
||||
toDisplayY,
|
||||
layout.ground,
|
||||
);
|
||||
// ⑥ 끝 바깥 절 · 성토선 — 세월교 측벽과 같은 체계(79-7). 흙막이 쪽은 기하가 걷어 둠.
|
||||
for (const side of layout.sides) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* 부재 두께는 세월교 승계(측벽 0.2·상판 0.3·저판 0.3)이며 백엔드가 실어 보낸다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import type { BoxSet, CrossSection, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import type {
|
||||
BasinLayout,
|
||||
@@ -74,6 +75,8 @@ export interface BoxSideLayout {
|
||||
|
||||
export interface BoxLayout {
|
||||
box: BoxSet;
|
||||
/** 원지반 — 도수로 · 돌붙임 길이가 사면보다 길면 이어 그림(294-4). */
|
||||
ground?: OffsetPoint[];
|
||||
sides: BoxSideLayout[];
|
||||
/** 상판 폴리곤(좌·우 표고가 다르면 기울어진다). */
|
||||
topSlab: OffsetPoint[];
|
||||
@@ -222,6 +225,7 @@ export function computeBoxLayout(
|
||||
const apronLengthOf = (side: BoxSideLayout): number =>
|
||||
side.apron.length ? Math.abs(side.apron[1].offset - side.apron[0].offset) : 0;
|
||||
return {
|
||||
ground: groundLine(groundSamples),
|
||||
box,
|
||||
sides: [left, right],
|
||||
topSlab: quad(0, barrelTopDrop),
|
||||
|
||||
@@ -125,6 +125,7 @@ export function appendCulvertOverlay(
|
||||
culvert.chute,
|
||||
x,
|
||||
toDisplayY,
|
||||
layout.ground,
|
||||
);
|
||||
for (const segment of layout.outletFill.segments) {
|
||||
// 묻힌 유출 끝 = 3도 수평 절토선(보라 · 면적에도 듦 · 139-4) — 3도 선이 원지반을
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* (designTrim). 유입 = 상단측. 집수정은 `_Basin.ts`, 성토부·다단은 `_Extra.ts`.
|
||||
* ========================================================================== */
|
||||
|
||||
import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import type { CrossSection, CulvertSideSpec, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import {
|
||||
revetWallSpec,
|
||||
@@ -575,6 +576,7 @@ export function computeCulvertLayout(
|
||||
const outletSlope = outletWallFinal ? slopeOf(outletWallFinal) : null;
|
||||
|
||||
return {
|
||||
ground: groundLine(groundSamples),
|
||||
inletOptions,
|
||||
culvert,
|
||||
pipe: { inlet: pipeStart, outlet, lengthM, slopePct },
|
||||
|
||||
@@ -201,6 +201,8 @@ export interface OutletFillSegment {
|
||||
|
||||
export interface CulvertLayout {
|
||||
culvert: CulvertSet;
|
||||
/** 원지반 — 도수로 · 돌붙임 길이가 사면보다 길면 이어 그림(294-4). */
|
||||
ground?: OffsetPoint[];
|
||||
pipe: { inlet: OffsetPoint; outlet: OffsetPoint; lengthM: number; slopePct: number };
|
||||
/** 관 축·법선 단위벡터(그리기가 두께 방향으로 밀 때 쓴다). */
|
||||
pipeAxis: { axis: OffsetPoint; normal: OffsetPoint };
|
||||
|
||||
@@ -144,6 +144,7 @@ export function appendFordOverlay(
|
||||
ford.chute,
|
||||
x,
|
||||
toDisplayY,
|
||||
layout.ground,
|
||||
);
|
||||
// ④ 계류측 마감선 — 구조물이 원지반 위면 성토부선, 안이면 절토선(집수정과 같은 체계).
|
||||
for (const side of layout.sides) {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
* 성토측이라도 기슭막이 세트(다단·재질)와는 연계하지 않는다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { groundLine } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import type { CrossSection, FordSet, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import { GUARD_CUT_BERM_M, pipeWallThicknessM } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type {
|
||||
@@ -108,6 +109,8 @@ export interface FordSideLayout {
|
||||
|
||||
export interface FordLayout {
|
||||
ford: FordSet;
|
||||
/** 원지반 — 도수로 · 돌붙임 길이가 사면보다 길면 이어 그림(294-4). */
|
||||
ground?: OffsetPoint[];
|
||||
sides: FordSideLayout[];
|
||||
/** 두 측벽 사이를 잇는 바닥판. */
|
||||
slabBridge: OffsetPoint[];
|
||||
@@ -329,6 +332,7 @@ export function computeFordLayout(
|
||||
|
||||
const countText = ford.pipe_count > 1 ? ` ${ford.pipe_count}련` : "";
|
||||
return {
|
||||
ground: groundLine(groundSamples),
|
||||
ford,
|
||||
sides,
|
||||
slabBridge,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import type { CrossDesign } from "./B06_Section_Api_Fetch";
|
||||
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import { appendPavementOverlay } from "./B06_Section_UI_Cross_Design_Surface";
|
||||
import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import { appendChuteBand, chuteRun } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
|
||||
/** 백엔드 `section.ford_pavement` 제원(치수 결정은 서버 몫 — 여기서는 좌표만 만든다). */
|
||||
export interface FordPavementSpec {
|
||||
@@ -102,6 +102,8 @@ export function appendFordPavementOverlay(
|
||||
design: CrossDesign,
|
||||
x: (offset: number) => number,
|
||||
y: (elevation: number) => number,
|
||||
/** 원지반 — 도수로 띠가 원지반에 닿으면 끝 · 길이가 사면보다 길면 이어 그림(294-4). */
|
||||
ground?: readonly OffsetPoint[],
|
||||
): boolean {
|
||||
const edges = fordEdges(design);
|
||||
if (!spec || !edges) return false;
|
||||
@@ -130,7 +132,7 @@ export function appendFordPavementOverlay(
|
||||
),
|
||||
);
|
||||
if (spec.chute)
|
||||
appendChuteBand(svg, [{ points: fordChuteLine(design, spec.chute) }], spec.chute, x, y);
|
||||
appendChuteBand(svg, [{ points: fordChuteLine(design, spec.chute, ground) }], spec.chute, x, y);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -143,18 +145,38 @@ export function fordEdges(design: CrossDesign): CrossDesign["road_edges"] | unde
|
||||
return reach.side === "left" ? { ...edges, left: end } : { ...edges, right: end };
|
||||
}
|
||||
|
||||
/** 원지반 꺾은선의 그 오프셋 표고(밖은 끝값). */
|
||||
function groundZ(ground: readonly OffsetPoint[], offset: number): number | null {
|
||||
if (!ground.length) return null;
|
||||
if (offset <= ground[0].offset) return ground[0].elevation;
|
||||
for (let i = 1; i < ground.length; i += 1) {
|
||||
const [a, b] = [ground[i - 1], ground[i]];
|
||||
if (offset > b.offset) continue;
|
||||
const span = b.offset - a.offset;
|
||||
return span > 1e-9
|
||||
? a.elevation + ((b.elevation - a.elevation) * (offset - a.offset)) / span
|
||||
: b.elevation;
|
||||
}
|
||||
return ground[ground.length - 1].elevation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 유출측(`inflowLeft` 반대) 노견 바깥 성토사면 선(136-2) — 설계선을 노견에서 바깥으로 따라
|
||||
* 내려가는 동안만(오르면 절토라 멈춤). 성토가 없으면 빈 선(띠 안 그림).
|
||||
* 유출측(낮은 쪽) 성토사면 선(136-2 · 294-4) — 낮은 쪽 끝단(늘인 노면 끝 · 아니면 유출 쪽 노견 끝)에서
|
||||
* 설계선을 바깥으로 따라 내려가는 동안만(오르면 절토라 멈춤) · 원지반(`ground`)에 닿으면 끝.
|
||||
* 성토가 없으면(끝단이 이미 원지반 · 양측 절토 물넘이) 점 하나 = 띠 없음.
|
||||
*/
|
||||
export function outletFillLine(design: CrossDesign): OffsetPoint[] {
|
||||
const edges = design.road_edges;
|
||||
export function outletFillLine(
|
||||
design: CrossDesign,
|
||||
ground?: readonly OffsetPoint[],
|
||||
): OffsetPoint[] {
|
||||
const edges = fordEdges(design);
|
||||
if (!edges || !design.design_line?.length) return [];
|
||||
const outward = inflowLeft(design) ? -1 : 1; // +offset = 좌측 · 유입 반대
|
||||
const edge =
|
||||
outward > 0
|
||||
? Math.max(edges.left.offset_m, edges.right.offset_m)
|
||||
: Math.min(edges.left.offset_m, edges.right.offset_m);
|
||||
const reach = design.tilt_reach;
|
||||
const outLeft = reach ? reach.side === "left" : !inflowLeft(design); // +offset = 좌측
|
||||
const outward = outLeft ? 1 : -1;
|
||||
const edge = outLeft
|
||||
? Math.max(edges.left.offset_m, edges.right.offset_m)
|
||||
: Math.min(edges.left.offset_m, edges.right.offset_m);
|
||||
const points = design.design_line
|
||||
.filter((p) => (p.offset_m - edge) * outward >= -1e-6)
|
||||
.sort((a, b) => (a.offset_m - b.offset_m) * outward);
|
||||
@@ -163,31 +185,25 @@ export function outletFillLine(design: CrossDesign): OffsetPoint[] {
|
||||
const last = line[line.length - 1];
|
||||
if (last && p.elevation_m > last.elevation + 1e-6) break;
|
||||
line.push({ offset: p.offset_m, elevation: p.elevation_m });
|
||||
const z = ground ? groundZ(ground, p.offset_m) : null;
|
||||
if (z !== null && Math.abs(z - p.elevation_m) < 1e-3) break; // 원지반에 닿음 = 끝
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* 물넘이 도수로 · 돌붙임 선(286-3) — 유출 성토사면 선을 노견에서 사면 따라 `length_m` 까지 자름 ·
|
||||
* 비거나 사면보다 길면 원지반까지. 횡단 띠 · 3D 띠가 같이 씀(그림 전용).
|
||||
* 물넘이 도수로 · 돌붙임 선(286-3 · 294-4) — 낮은 쪽 끝단에서 성토사면 따라 · 길이 비움 = 원지반 닿으면 끝 ·
|
||||
* 길이 넣음 = 그 길이(사면보다 길면 원지반 따라 이어) · 양측 절토(늘인 노면 끝이 원지반) = 길이 없으면 안 그림.
|
||||
* 횡단 띠 · 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;
|
||||
export function fordChuteLine(
|
||||
design: CrossDesign,
|
||||
chute?: { length_m?: number },
|
||||
ground?: readonly OffsetPoint[],
|
||||
): OffsetPoint[] {
|
||||
return (
|
||||
chuteRun([{ points: outletFillLine(design, ground) }], chute?.length_m, ground)[0]?.points ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ import { ownWallsDrawAt } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { attachShapePick, shapePickRoute } from "./B06_Section_UI_Cross_Shape_Pick";
|
||||
import { appendUnderdrainOverlay } from "./B06_Section_UI_Cross_Underdrain";
|
||||
import { raiseWalls } from "./B06_Section_UI_Cross_Wall";
|
||||
import { attachChuteBandPick } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import { attachChuteBandPick, groundLine } from "./B06_Section_UI_Cross_Wall_Hatch";
|
||||
import { appendSpoilFillOverlay } from "./B06_Section_UI_Cross_SpoilFill";
|
||||
import { appendRevetmentOverlay, computeRevetmentLayout } from "./B06_Section_UI_Cross_Revetment";
|
||||
import {
|
||||
@@ -454,6 +454,7 @@ export function createCrossSectionCard(
|
||||
section.design,
|
||||
x,
|
||||
toDisplayY,
|
||||
groundLine(section.samples ?? []),
|
||||
);
|
||||
// 월류 폭으로 걸친 옆 카드 = 흙막이 연동 꼴 · 주인 측점 글(294-6)
|
||||
markLinkedFordPavement(plotLayer, fordFrom, section, stationInterval);
|
||||
|
||||
@@ -349,6 +349,77 @@ export function chuteBandOutline(
|
||||
return { top, normals, outline: [...top, ...bottom.reverse()] };
|
||||
}
|
||||
|
||||
/** 꺾은선 앞에서부터 `lengthM` 까지(쓴 길이와 함께). */
|
||||
function trimPolyline(
|
||||
points: readonly OffsetPoint[],
|
||||
lengthM: number,
|
||||
): { points: OffsetPoint[]; used: number } {
|
||||
const out = points.slice(0, 1);
|
||||
let used = 0;
|
||||
for (let i = 1; i < points.length && lengthM - used > 1e-9; i += 1) {
|
||||
const [a, b] = [points[i - 1], points[i]];
|
||||
const step = Math.hypot(b.offset - a.offset, b.elevation - a.elevation);
|
||||
if (step < 1e-9) continue;
|
||||
const t = Math.min((lengthM - used) / step, 1);
|
||||
out.push({
|
||||
offset: a.offset + (b.offset - a.offset) * t,
|
||||
elevation: a.elevation + (b.elevation - a.elevation) * t,
|
||||
});
|
||||
used += step * t;
|
||||
}
|
||||
return { points: out, used };
|
||||
}
|
||||
|
||||
/** 원지반 샘플 → 꺾은선(유효 점만 · 오프셋 차례). */
|
||||
export function groundLine(
|
||||
samples: ReadonlyArray<{
|
||||
offset_m?: number | null;
|
||||
elevation_m?: number | null;
|
||||
valid?: boolean;
|
||||
}>,
|
||||
): OffsetPoint[] {
|
||||
return samples
|
||||
.filter((s) => s.valid !== false && s.offset_m != null && s.elevation_m != null)
|
||||
.map((s) => ({ offset: s.offset_m as number, elevation: s.elevation_m as number }))
|
||||
.sort((a, b) => a.offset - b.offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 도수로 · 돌붙임 선 길이(294-4) — 선들을 차례로 사면 따라 `lengthM` 까지 자름 · 사면보다 길면 마지막 끝에서
|
||||
* 원지반(`ground`)을 바깥으로 따라 이어 그 길이까지 · 길이 없으면 그대로(원지반 닿으면 끝). 점 하나뿐인 선
|
||||
* (사면 없음 · 끝이 이미 원지반)은 길이가 있으면 원지반 따라 · 없으면 안 그림. 그림 전용(수량 · 내역 무관).
|
||||
*/
|
||||
export function chuteRun<L extends { points: OffsetPoint[] }>(
|
||||
lines: readonly L[],
|
||||
lengthM?: number | null,
|
||||
ground?: readonly OffsetPoint[],
|
||||
): L[] {
|
||||
const drawn = (list: L[]) => list.filter((line) => line.points.length > 1);
|
||||
if (!(lengthM && lengthM > 0) || !lines.length) return drawn([...lines]);
|
||||
let left = lengthM;
|
||||
const out: L[] = [];
|
||||
for (const line of lines) {
|
||||
if (left <= 1e-9) break;
|
||||
const cut = trimPolyline(line.points, left);
|
||||
out.push({ ...line, points: cut.points });
|
||||
left -= cut.used;
|
||||
}
|
||||
const last = out[out.length - 1];
|
||||
if (left > 1e-9 && ground?.length && last) {
|
||||
const first = lines[0].points[0];
|
||||
const end = last.points[last.points.length - 1];
|
||||
const outward = Math.sign(end.offset - first.offset) || Math.sign(end.offset) || 1;
|
||||
const along = [
|
||||
end,
|
||||
...ground
|
||||
.filter((p) => (p.offset - end.offset) * outward > 1e-6)
|
||||
.sort((a, b) => (a.offset - b.offset) * outward),
|
||||
];
|
||||
out.push({ ...last, points: trimPolyline(along, left).points });
|
||||
}
|
||||
return drawn(out);
|
||||
}
|
||||
|
||||
/** 띠가 서는 유출 조각(152-3) — 성토 조각 + 내림 · 수평 절토 조각(3도 절토선 · 끝 ≤ 시작).
|
||||
* 3도 선이 못 만나 복합경사로 오르는 절토 조각은 뺌. B06 관 · 흙막이 띠와 3D 띠가 같은 거름. */
|
||||
export function isChuteSegment(segment: { kind?: string; points: OffsetPoint[] }): boolean {
|
||||
@@ -365,13 +436,16 @@ export function isChuteSegment(segment: { kind?: string; points: OffsetPoint[] }
|
||||
*/
|
||||
export function appendChuteBand(
|
||||
layer: SVGElement,
|
||||
lines: ReadonlyArray<{ points: OffsetPoint[]; kind?: string }>,
|
||||
chute: { structure: string; thickness_m: number },
|
||||
allLines: ReadonlyArray<{ points: OffsetPoint[]; kind?: string }>,
|
||||
chute: { structure: string; thickness_m: number; length_m?: number },
|
||||
x: (offset: number) => number,
|
||||
toDisplayY: (elevation: number) => number,
|
||||
/** 원지반 — 길이가 사면보다 길면 이어 그림(294-4). */
|
||||
ground?: readonly OffsetPoint[],
|
||||
): void {
|
||||
const thickness = chute.thickness_m;
|
||||
if (!(thickness > 0)) return;
|
||||
const lines = chuteRun(allLines, chute.length_m, ground);
|
||||
const pixelsPerMeter = Math.abs(x(1) - x(0)) || 1;
|
||||
const px = (p: OffsetPoint): [number, number] => [x(p.offset), toDisplayY(p.elevation)];
|
||||
for (const line of lines) {
|
||||
@@ -383,7 +457,8 @@ export function appendChuteBand(
|
||||
band.setAttribute("class", "b06-chart__culvert-pitching");
|
||||
const title = document.createElementNS(SVG_NS, "title");
|
||||
const along = line.kind === "cut" ? "3도 절토선" : "성토사면";
|
||||
title.textContent = `유출부 ${chute.structure} — 두께 ${thickness.toFixed(2)}m · ${along} 따라 원지반까지`;
|
||||
const reach = chute.length_m ? `길이 ${chute.length_m.toFixed(2)}m` : "원지반까지";
|
||||
title.textContent = `유출부 ${chute.structure} — 두께 ${thickness.toFixed(2)}m · ${along} 따라 ${reach}`;
|
||||
band.append(title);
|
||||
clipSeq += 1;
|
||||
const clipId = `b06-chute-clip-${clipSeq}`;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { appendCulvertOverlay } from "../B06_Section/B06_Section_UI_Cross_Culver
|
||||
import { appendFordOverlay } from "../B06_Section/B06_Section_UI_Cross_Ford";
|
||||
import { appendFordPavementOverlay } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
|
||||
import { appendUnderdrainOverlay } from "../B06_Section/B06_Section_UI_Cross_Underdrain";
|
||||
import { groundLine } from "../B06_Section/B06_Section_UI_Cross_Wall_Hatch";
|
||||
import {
|
||||
appendCrossDesignOverlay,
|
||||
appendPavementOverlay,
|
||||
@@ -486,7 +487,8 @@ function drawDesign(
|
||||
y: (elevation: number) => number,
|
||||
): void {
|
||||
const { design, culvert, box, ford, own } = layouts;
|
||||
const paved = appendFordPavementOverlay(svg, section.ford_pavement, design, x, y);
|
||||
const ground = groundLine(section.samples ?? []); // 물넘이 도수로 띠 끝 · 길이 이음(294-4)
|
||||
const paved = appendFordPavementOverlay(svg, section.ford_pavement, design, x, y, ground);
|
||||
if (!paved) appendPavementOverlay(svg, design, x, y, section.pavement);
|
||||
appendUnderdrainOverlay(svg, section.underdrain, design, x, y); // 맹암거(288-1)
|
||||
appendCrossDesignOverlay(
|
||||
|
||||
@@ -24,7 +24,10 @@ 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() {} },
|
||||
"./B06_Section_UI_Cross_Wall_Hatch": loadTs(
|
||||
path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Wall_Hatch.ts"),
|
||||
{ "./B06_Section_UI_Cross_Culvert_Const": {} },
|
||||
),
|
||||
"./B06_Section_UI_Cross_Design_Surface": { appendPavementOverlay() {} },
|
||||
});
|
||||
// 286-3 도수로 · 돌붙임 길이 — 유출(좌 · 우가 높음) 성토사면 1:1.5(5 m 내려감 · 사면 길이 √(7.5²+5²) ≈ 9.01)
|
||||
|
||||
@@ -55,8 +55,40 @@ pave.appendFordPavementOverlay(
|
||||
);
|
||||
const paveLayer = svg.children.find((k) => (k.attrs.class ?? "").includes("ford-pavement"));
|
||||
|
||||
// 294-4 도수로 · 돌붙임 끝단 · 길이
|
||||
const hatch = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Wall_Hatch.ts"));
|
||||
const P = (offset, elevation) => ({ offset, elevation });
|
||||
const slope = [{ points: [P(0, 0), P(3, -4)], kind: "fill" }]; // 사면 5 m
|
||||
const flat = [P(3, -4), P(5, -4), P(8, -4)]; // 사면 끝 바깥 원지반
|
||||
const pts = (lines) =>
|
||||
lines.map((l) => l.points.map((p) => [+p.offset.toFixed(4), +p.elevation.toFixed(4)]));
|
||||
const dipGround = hatch.groundLine(input.dipSamples);
|
||||
const fillGround = hatch.groundLine(input.fillSamples);
|
||||
const calc = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_calc.ts"));
|
||||
const formOf = (state) => ({ state: () => state, setValue: (c, i, v) => (state[c][0][i] = v) });
|
||||
const fordState = { ford: [{ width: "8" }], chute_add: [{ length_m: "2.5", area: "30" }] };
|
||||
calc.syncChuteArea(formOf(fordState), { id: "chute_add" }, { id: "length_m" }, "chute_add", "ford");
|
||||
const guard = load(path.join(ROOT, "A00_Common", "design", "design_structure_guard.ts"));
|
||||
const fordPave = load(path.join(ROOT, "A00_Common", "design", "design_structure_ford_pavement.ts"));
|
||||
|
||||
const out = {
|
||||
edges: pave.fordEdges(design),
|
||||
pavePoints: paveLayer?.attrs.points ?? null,
|
||||
runNone: pts(hatch.chuteRun(slope)),
|
||||
runShort: pts(hatch.chuteRun(slope, 2)),
|
||||
runLong: pts(hatch.chuteRun(slope, 7, flat)),
|
||||
runPointOnly: pts(hatch.chuteRun([{ points: [P(3, -4)] }])),
|
||||
runPointLength: pts(hatch.chuteRun([{ points: [P(3, -4)] }], 2, flat)),
|
||||
dipLine: pave.outletFillLine(design, dipGround),
|
||||
dipChute: pave.fordChuteLine(design, {}, dipGround),
|
||||
dipChuteLength: pave.fordChuteLine(design, { length_m: 1.5 }, dipGround),
|
||||
fillLine: pave.outletFillLine(input.fillDesign, fillGround),
|
||||
fillLineOld: pave.outletFillLine(input.fillDesign),
|
||||
fillChuteLength: pave.fordChuteLine(input.fillDesign, { length_m: 1 }, fillGround),
|
||||
fordArea: fordState.chute_add[0].area,
|
||||
chuteItems: guard.CHUTE_ITEMS.map((i) => i.id),
|
||||
fordPaveChuteSame:
|
||||
fordPave.FORD_PAVEMENT_SPEC.containers.find((c) => c.id === "chute").items ===
|
||||
guard.CHUTE_ITEMS,
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -55,9 +55,8 @@ def test_거름_한_벌_배선() -> None:
|
||||
culvert = _src("B06_Section/B06_Section_UI_Cross_Culvert.ts")
|
||||
assert "layout.outletFill.segments.filter(isChuteSegment)," in culvert
|
||||
corridor = _src("B05_Profile/B05_Profile_UI_Corridor_Structures.ts")
|
||||
assert (
|
||||
"chuteBands(layout.outletFill.segments, section.culvert?.chute, isChuteSegment)" in corridor
|
||||
)
|
||||
flat = " ".join(corridor.split())
|
||||
assert "chuteBands( layout.outletFill.segments, section.culvert?.chute, isChuteSegment," in flat
|
||||
hatch = _src("B06_Section/B06_Section_UI_Cross_Wall_Hatch.ts")
|
||||
assert '"3도 절토선"' in hatch
|
||||
# 박스 · 세월교 본체 끝은 성토만(3도 규칙 안 넓힘)
|
||||
|
||||
@@ -174,8 +174,7 @@ def test_도수로_길이_없음_원지반까지_있음_그_길이(ts):
|
||||
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
|
||||
"items: CHUTE_ITEMS," in spec # 공용 칸 맨 아래 길이(294-4 · CHUTE_ITEMS 로 옮김)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
처음 만나는 자리까지(그 쪽 절토 사면 없음 · `tilt_reach`) · 성토 쪽 그대로 · 반폭 안에서 못 만나면 옛 길 ·
|
||||
서버 정본 ↔ TS 짝 같은 값 = 횡단 설계 거울(`test_b06_cross_design_mirror` 「물넘이포장 낮은 쪽 노면
|
||||
연장」) · 그림 = 콘크리트(포장층)가 늘인 끝까지.
|
||||
- 294-4 도수로/돌붙임 띠 = 낮은 쪽 끝단(늘인 끝 · 아니면 유출 쪽 노견 끝)에서 성토 사면 따라 · 길이 비움 =
|
||||
원지반 닿으면 끝 · 양측 절토 물넘이 = 띠 없음 · 길이 = 그 길이(사면보다 길면 원지반 따라) · 다른 구조물
|
||||
(배관 · 박스 · 세월교) 같은 규칙(`chuteRun` · 원지반 `layout.ground`) + 같은 「길이 (m)」 칸 · 면적 = 세월교만
|
||||
월류 폭 × 길이(배관 · 박스 = 폭 정의 없어 자동 안 함) · 길이는 집계 · B08 수량에 안 실림.
|
||||
|
||||
진짜 TS 는 `helper_294_3_ford_extend_chute.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
@@ -60,7 +64,15 @@ def dip() -> dict:
|
||||
@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")
|
||||
fill_samples = _line(lambda o: 100.0 + 0.35 * o)
|
||||
fill = compute_cross_design(fill_samples, 100.4, **KW)
|
||||
payload = {
|
||||
"design": dip,
|
||||
"dipSamples": _dip_ground(),
|
||||
"fillSamples": fill_samples,
|
||||
"fillDesign": fill,
|
||||
}
|
||||
source.write_text(json.dumps(payload), encoding="utf-8")
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_294_3_ford_extend_chute.cjs"), str(source)],
|
||||
capture_output=True,
|
||||
@@ -110,3 +122,80 @@ def test_콘크리트는_늘인_끝까지(dip, ts):
|
||||
}
|
||||
first = ts["pavePoints"].split()[1] # 포장층 윗면 우 끝
|
||||
assert float(first.split(",")[0]) == pytest.approx(-4.3333, abs=1e-4)
|
||||
|
||||
|
||||
def test_띠_길이_맞춤_공용(ts):
|
||||
assert ts["runNone"] == [[[0, 0], [3, -4]]] # 길이 없음 = 원지반 닿으면 끝(그대로)
|
||||
assert ts["runShort"] == [[[0, 0], [1.2, -1.6]]] # 길이 2 = 사면 따라 2 m
|
||||
assert ts["runLong"] == [[[0, 0], [3, -4]], [[3, -4], [5, -4]]] # 사면 5 + 원지반 2
|
||||
assert ts["runPointOnly"] == [] # 끝단이 이미 원지반 · 길이 없음 = 안 그림
|
||||
assert ts["runPointLength"] == [[[3, -4], [5, -4]]]
|
||||
|
||||
|
||||
def test_양측_절토_물넘이_띠_없음(ts):
|
||||
assert len(ts["dipLine"]) == 1 # 늘인 끝이 원지반
|
||||
assert ts["dipLine"][0]["offset"] == pytest.approx(-4.3333, abs=1e-4)
|
||||
assert ts["dipChute"] == []
|
||||
tail = ts["dipChuteLength"] # 길이를 넣으면 늘인 끝에서 원지반 따라 그 길이
|
||||
assert tail[0]["offset"] == pytest.approx(-4.3333, abs=1e-4) and len(tail) >= 2
|
||||
|
||||
|
||||
def test_성토_쪽_띠는_원지반_닿으면_끝(ts):
|
||||
line = ts["fillLine"]
|
||||
end = line[-1]
|
||||
assert end["elevation"] == pytest.approx(100.0 + 0.35 * end["offset"], abs=1e-3) # 원지반 위
|
||||
assert len(ts["fillLineOld"]) > len(line) # 원지반을 모르면 내려가는 원지반까지 따라갔음
|
||||
chute = ts["fillChuteLength"]
|
||||
start, stop = chute[0], chute[-1]
|
||||
assert (stop["offset"] - start["offset"]) ** 2 + (
|
||||
stop["elevation"] - start["elevation"]
|
||||
) ** 2 == pytest.approx(1.0, abs=1e-6)
|
||||
|
||||
|
||||
def test_다른_구조물_같은_규칙과_길이_칸(ts):
|
||||
assert ts["chuteItems"][-1] == "length_m"
|
||||
assert ts["fordPaveChuteSame"] is True
|
||||
assert ts["fordArea"] == "20" # 세월교 월류 폭 8 × 길이 2.5
|
||||
hatch = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Wall_Hatch.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "const lines = chuteRun(allLines, chute.length_m, ground);" in hatch
|
||||
for name in ("Culvert", "Box", "Ford"):
|
||||
overlay = (ROOT / "B06_Section" / f"B06_Section_UI_Cross_{name}.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert "layout.ground," in overlay
|
||||
form = (ROOT / "B05_Profile" / "B05_Profile_UI_Drainage_Facility.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert form.count("syncChuteArea(") == 2 # 물넘이 · 세월교만(배관 · 박스 = 폭 정의 없음)
|
||||
|
||||
|
||||
def test_길이는_집계_수량에_안_실림():
|
||||
from B06_Section.B06_Section_Engine_Culvert import _chute
|
||||
from B08_Quantity.B08_Quantity_Router_Summary import _chutes
|
||||
from M02_MasterTemplete.M02_Table_Fill_Spec import container_rows
|
||||
|
||||
options = {
|
||||
"chute_add_1_structure": "돌붙임",
|
||||
"chute_add_1_bond": "찰붙임",
|
||||
"chute_add_1_area": 12,
|
||||
}
|
||||
longer = {**options, "chute_add_1_length_m": 7}
|
||||
assert _chute(longer)["length_m"] == 7 and "length_m" not in _chute(options)
|
||||
|
||||
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, longer, {"gchuteadd1": "SS1"}, {})
|
||||
assert sheet.rows == [("chute|찰붙임", "㎡", 12.0)]
|
||||
item = {"type_id": "pipe", "options": options, "복사본": "pipe@50.0", "흙막이단": 0}
|
||||
plain = container_rows(item, {"gchuteadd1": "SS1"})
|
||||
long = container_rows({**item, "options": longer}, {"gchuteadd1": "SS1"})
|
||||
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)
|
||||
|
||||
@@ -113,4 +113,7 @@ def test_정의_파일에_세월교_이름_표가_올라_있음():
|
||||
os.path.join(ROOT, "resources", "master_template", "구조물정의.json"), encoding="utf-8"
|
||||
) as f:
|
||||
doc = json.load(f)
|
||||
assert doc["종류"]["ford_bridge"]["이름"] == names
|
||||
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
|
||||
assert doc["종류"]["ford_bridge"]["이름"] == {
|
||||
k: v for k, v in names.items() if k != "chute_add.length_m"
|
||||
}
|
||||
|
||||
@@ -102,7 +102,8 @@ def test_정의_파일에_이름_표_그룹_선택지가_올라_있음():
|
||||
os.path.join(ROOT, "resources", "master_template", "구조물정의.json"), encoding="utf-8"
|
||||
) as f:
|
||||
doc = json.load(f)
|
||||
assert set(names) == set(doc["종류"]["box_culvert"]["이름"])
|
||||
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
|
||||
assert set(names) - {"chute_add.length_m"} == set(doc["종류"]["box_culvert"]["이름"])
|
||||
assert [g["id"] for g in doc["그룹"]["box_culvert"]] == ["gboxbody", "gboxin", "gboxout"]
|
||||
box = next(t for t in doc["types"] if t["type_id"] == "box_culvert")
|
||||
wing = {o["key"]: o for o in box["options"]}
|
||||
|
||||
@@ -107,5 +107,6 @@ def test_정의_파일에_이름_표가_올라_있음():
|
||||
) as f:
|
||||
doc = json.load(f)
|
||||
on_file = doc["종류"]["pipe"]["이름"]
|
||||
assert set(names) == set(on_file)
|
||||
# 그림 전용 길이 칸(294-4)은 마스터 정의 이름 표에 안 올림(마스터 손대지 않음) — 대조 밖
|
||||
assert set(names) - {"chute_add.length_m"} == set(on_file)
|
||||
assert on_file["inlet.structure=기슭막이"] == "흙막이"
|
||||
|
||||
@@ -73,6 +73,7 @@ def test_유입_유출은_구조와_흙막이_칸만(out):
|
||||
["bond", None, "메붙임"],
|
||||
["thickness", None, "45"],
|
||||
["area", None, "30"],
|
||||
["length_m", None, None], # 그림 전용 길이(294-4)
|
||||
]
|
||||
keys = out[f"{spec}Keys"]
|
||||
assert keys["prefixes"] == ["soil_add_", "chute_add_"]
|
||||
|
||||
Reference in New Issue
Block a user