feat(B06): 세월교 바닥판 표고 하나(수평) · 측벽마다 좌우만 · 내림 양쪽 한 값 아래로만 · 끌기 수직 · 옛 ford_adjust 평균 옮김 · 박스 표고 아래로만(139-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
@@ -26,10 +26,7 @@ import { computeCulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culver
|
||||
import { restrictToSide, tierSpanOf } from "../B06_Section/B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { pipeWallThicknessM } from "../B06_Section/B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { CulvertLayout } from "../B06_Section/B06_Section_UI_Cross_Culvert";
|
||||
import {
|
||||
computeFordLayout,
|
||||
DEFAULT_FORD_WALL_ADJUST,
|
||||
} from "../B06_Section/B06_Section_UI_Cross_Ford";
|
||||
import { computeFordLayout, fordAdjustOf } from "../B06_Section/B06_Section_UI_Cross_Ford";
|
||||
import type { FordLayout } from "../B06_Section/B06_Section_UI_Cross_Ford";
|
||||
import {
|
||||
boreBandAt,
|
||||
@@ -203,10 +200,7 @@ export function fordLayoutOf(section: CrossSection): FordLayout | null {
|
||||
if (!section.ford || !section.design) return null;
|
||||
const stored = section.design.ford_adjust;
|
||||
try {
|
||||
return computeFordLayout(section, section.samples, {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.inlet ?? {}) },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.outlet ?? {}) },
|
||||
});
|
||||
return computeFordLayout(section, section.samples, fordAdjustOf(stored));
|
||||
} catch {
|
||||
return null; // 한 측점의 기하 실패가 3D 전체를 막으면 안 된다.
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ export function structureHashParts(section: CrossSection): Array<string | number
|
||||
const value = adjust?.[role];
|
||||
return value ? `${value.heightM ?? ""},${value.lateralM},${value.slopeM}` : "";
|
||||
};
|
||||
extras.push(`dr:${adjust?.dropM ?? ""}`);
|
||||
extras.push(
|
||||
"fd",
|
||||
ford.diameter_m,
|
||||
|
||||
@@ -386,6 +386,8 @@ export interface StoredFordWallAdjust {
|
||||
export interface StoredFordAdjust {
|
||||
inlet: StoredFordWallAdjust;
|
||||
outlet: StoredFordWallAdjust;
|
||||
/** 바닥판 내림(m · 수직 · 양쪽 한 값 · 139-3) — 없으면 옛 저장(읽을 때 옮김). */
|
||||
dropM?: number | null;
|
||||
}
|
||||
|
||||
/** BOX암거 한쪽 끝의 저장형 조작값(2026-08-25). */
|
||||
|
||||
@@ -117,6 +117,8 @@ class FordAdjustPatch(BaseModel):
|
||||
|
||||
inlet: FordWallAdjustPatch = Field(default_factory=FordWallAdjustPatch)
|
||||
outlet: FordWallAdjustPatch = Field(default_factory=FordWallAdjustPatch)
|
||||
# 바닥판 내림(수직 · 양쪽 한 값 · 아래로만 · 139-3) — None = 옛 저장(화면이 읽을 때 옮김)
|
||||
dropM: float | None = Field(default=None, ge=0, le=20)
|
||||
|
||||
|
||||
class BoxSideAdjustPatch(BaseModel):
|
||||
|
||||
@@ -29,7 +29,7 @@ import type {
|
||||
InletStructureControl,
|
||||
RevetOffsetControl,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import { computeFordLayout, DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import { computeFordLayout, fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import type { FordLayout } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import { computeRevetmentLayout } from "./B06_Section_UI_Cross_Revetment";
|
||||
|
||||
@@ -119,10 +119,7 @@ export function computeStoredLayouts(section: CrossSection, sections: readonly C
|
||||
culvert,
|
||||
);
|
||||
const ford = withoutGuardedFill(
|
||||
computeFordLayout(section, section.samples, {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(design.ford_adjust?.inlet ?? {}) },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(design.ford_adjust?.outlet ?? {}) },
|
||||
}),
|
||||
computeFordLayout(section, section.samples, fordAdjustOf(design.ford_adjust)),
|
||||
culvert,
|
||||
);
|
||||
// 독립 기슭막이(옛 D군 경로) — 배관 세트가 붙었거나 옆에서 이어져 오면 그쪽이 그린다.
|
||||
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
WallAdjust,
|
||||
WallLayout,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import { computeFordLayout, DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import { computeFordLayout, fordAdjustOf } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import type { FordLayout } from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall";
|
||||
|
||||
@@ -77,10 +77,7 @@ export function bodyGuardBase(
|
||||
}
|
||||
if (body === "ford" && section.ford) {
|
||||
const stored = defaults ? null : adjust?.ford_adjust;
|
||||
const layout = computeFordLayout(section, samples, {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.inlet ?? {}) },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.outlet ?? {}) },
|
||||
});
|
||||
const layout = computeFordLayout(section, samples, fordAdjustOf(stored));
|
||||
if (!layout) return null;
|
||||
// 세월교 끝 = 관 끝(측벽 바깥면) · 윗면 = 측벽 상단(상판 높이) · 바닥 = 바닥판 밑
|
||||
const endOf = (role: Role): BodyEnd | null => {
|
||||
|
||||
@@ -133,8 +133,9 @@ export function computeBoxLayout(
|
||||
const inletOnLeft = (section.uphill_side ?? "left") === "left";
|
||||
const baseTop = Math.min(roadTopAt(leftEdge), roadTopAt(rightEdge), roadTopAt(0)) - box.cover_m;
|
||||
const bodyHeight = box.top_thickness_m + box.inner_height_m + box.slab_thickness_m;
|
||||
// 본체는 늘 수평(79-7) — 좌 · 우 표고 조정이 달라도(옛 저장) 평균 하나로 섬.
|
||||
const riseM = (adjust.left.riseM + adjust.right.riseM) / 2;
|
||||
// 본체는 늘 수평(79-7) — 좌 · 우 표고 조정이 달라도(옛 저장) 평균 하나로 섬 · 아래로만(노면 −
|
||||
// 복토 위 금지 · 옛 위 값은 자름 · 139-3).
|
||||
const riseM = Math.min((adjust.left.riseM + adjust.right.riseM) / 2, 0);
|
||||
const sampleOffsets = groundSamples.map((sample) => sample.offset_m ?? 0);
|
||||
// 끝 절토 = 본선 절토 사면과 같은 복합경사(91-4)
|
||||
const cutSpec = structureCutSpec(section.design);
|
||||
|
||||
@@ -127,7 +127,7 @@ export function buildBoxPanel(deps: BoxPanelDeps, opts?: { dock?: boolean }): Bo
|
||||
slopeOutward: null,
|
||||
clamp: (left, down) => [
|
||||
clampAxis(from.lengthM, left * lengthSign, 0, Infinity) * lengthSign,
|
||||
-clampAxis(from.riseM, -down, -RISE_LIMIT_M, RISE_LIMIT_M),
|
||||
-clampAxis(from.riseM, -down, -RISE_LIMIT_M, 0), // 아래로만(139-3)
|
||||
],
|
||||
apply: (left, down) => deps.move(role, left, -down),
|
||||
});
|
||||
|
||||
@@ -17,6 +17,8 @@ export {
|
||||
computeFordLayout,
|
||||
DEFAULT_FORD_ADJUST,
|
||||
DEFAULT_FORD_WALL_ADJUST,
|
||||
fordAdjustOf,
|
||||
fordDropOf,
|
||||
} from "./B06_Section_UI_Cross_Ford_Geom";
|
||||
export type {
|
||||
FordAdjust,
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
* 백엔드 `section.ford` 제원을 좌표로 옮긴다(치수 결정 금지).
|
||||
*
|
||||
* 구성(2026-08-25 사용자 확정):
|
||||
* · 양측 **측벽** = ㄴ형 집수정 부재를 재사용한다(`buildBasin`) — 좌우·상하(대각)
|
||||
* 이동·계류측 성토부선이 집수정과 같은 규칙으로 따라온다. 단 **벽은 수직**이다
|
||||
* · 양측 **측벽** = ㄴ형 집수정 부재를 재사용한다(`buildBasin`) — 좌우 이동·계류측
|
||||
* 성토부선이 집수정과 같은 규칙으로 따라온다. 단 **벽은 수직**이다
|
||||
* (`leanRatio: 0` — 2026-08-30 사용자 확정, 교본 그림 3-18). 집수정의 1:0.3
|
||||
* 기움은 세월교 근거가 아니라 부재를 빌려 오며 딸려 온 값이었다.
|
||||
* · **바닥판** = 두 측벽 사이를 잇는 판 + 각 측 바깥으로 **날개벽 투영**(길이×cos각)
|
||||
* 만큼 뻗은 판. 집수정 내공 폭 자리에 그 연장량을 넣어 같은 기하로 만든다.
|
||||
* · **관**은 바닥판 상면(측벽 바깥면)에 안히고, 관 위 ~ 노면 아래는 **성토(노체)** 다.
|
||||
* · **수평**(139-3 · 박스 「구체 수평」 준용) — 바닥판 표고 하나 = 낮은 쪽 노견 노면 −
|
||||
* (관경 + 토피) − 내림(양쪽 한 값 · 아래로만) · 측벽마다 좌우만(도로 쪽 금지) ·
|
||||
* 측벽 높이 = 그 쪽 노견 노면 − 바닥판(결과).
|
||||
* · 구체는 월류 폭만큼 도로 방향으로 이어져 기준 측점 전후로 절반씩 걸친다
|
||||
* (붙는 측점 판정은 백엔드 `attach_culvert_sets`).
|
||||
* 성토측이라도 기슭막이 세트(다단·재질)와는 연계하지 않는다.
|
||||
@@ -29,20 +32,32 @@ import { bermCut, buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
import { designInterpolator, groundInterpolator } from "./B06_Section_UI_Cross_Culvert_Solve";
|
||||
import { structureCutSpec } from "./B06_Section_UI_Cross_Structure_Cut";
|
||||
|
||||
/** 측벽 한 매의 조작값 — 집수정 9키와 같은 축(높이·좌우·상하 대각). */
|
||||
/** 측벽 한 매의 조작값 — 좌우만(139-3). `heightM` · `slopeM` 은 옛 저장(측벽마다 높이 · 1:1.2
|
||||
* 대각) 읽기 전용 — `dropM` 이 없을 때 바닥판 표고로 옮김(`fordDropOf`). */
|
||||
export interface FordWallAdjust {
|
||||
/** 벽 높이(m, 노견 상단 ~ 바닥판 상면). null = 기본 = 관경 + 최소 토피(원지반 안 봄 · 107-2). */
|
||||
/** 옛 벽 높이(m) — 새 값은 결과라 늘 null. */
|
||||
heightM: number | null;
|
||||
/** 좌우(m, + = 노견 바깥). 안쪽으로는 못 간다. */
|
||||
lateralM: number;
|
||||
/** 상하(m, 수평 성분, + = 사면 아래) — 집수정과 같이 1:1.2 대각으로 내려간다. */
|
||||
/** 옛 상하(m, 수평 성분 · 1:1.2 대각) — 새 값은 늘 0. */
|
||||
slopeM: number;
|
||||
}
|
||||
|
||||
/** 측점별 세월교 조정값 — 유입·유출 측벽을 따로 잡는다. */
|
||||
/** 측점별 세월교 조정값 — 측벽마다 좌우 · 바닥판 내림 하나. */
|
||||
export interface FordAdjust {
|
||||
inlet: FordWallAdjust;
|
||||
outlet: FordWallAdjust;
|
||||
/** 바닥판 내림(m · 수직 · ≥ 0 · 양쪽 한 값 · 139-3). null/없음 = 옛 저장 → `fordDropOf`. */
|
||||
dropM?: number | null;
|
||||
}
|
||||
|
||||
/** 저장값 → 조정값(빈 칸 기본) — 화면 · 3D · 서버가 같은 꼴로 읽음. */
|
||||
export function fordAdjustOf(stored?: Partial<FordAdjust> | null): FordAdjust {
|
||||
return {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.inlet ?? {}) },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.outlet ?? {}) },
|
||||
dropM: stored?.dropM ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export const DEFAULT_FORD_WALL_ADJUST: FordWallAdjust = {
|
||||
@@ -54,6 +69,7 @@ export const DEFAULT_FORD_WALL_ADJUST: FordWallAdjust = {
|
||||
export const DEFAULT_FORD_ADJUST: FordAdjust = {
|
||||
inlet: DEFAULT_FORD_WALL_ADJUST,
|
||||
outlet: DEFAULT_FORD_WALL_ADJUST,
|
||||
dropM: 0,
|
||||
};
|
||||
|
||||
export type FordWallRole = "inlet" | "outlet";
|
||||
@@ -110,6 +126,27 @@ function minWallHeight(ford: FordSet): number {
|
||||
return ford.diameter_m + ford.min_cover_m;
|
||||
}
|
||||
|
||||
/**
|
||||
* 바닥판 내림(m · ≥ 0) — 새 값(`dropM`) 그대로 · 옛 저장(측벽마다 높이 · 1:1.2 대각)은 측벽마다
|
||||
* 바닥판 표고의 평균으로 옮김(박스 79-7 꼴) · 기본 자리 위는 0 에서 자름. 노견이 없으면 0.
|
||||
*/
|
||||
export function fordDropOf(section: CrossSection, adjust: FordAdjust): number {
|
||||
if (adjust.dropM != null) return Math.max(adjust.dropM, 0);
|
||||
const ford = section.ford;
|
||||
const edges = section.design?.road_edges;
|
||||
if (!ford || !edges) return 0;
|
||||
const roadTopAt = roadTopFactory(section, edges.left, edges.right);
|
||||
const minH = minWallHeight(ford);
|
||||
const inletOnLeft = (section.uphill_side ?? "left") === "left";
|
||||
const oldSlab = (role: FordWallRole): number => {
|
||||
const edge = (role === "inlet") === inletOnLeft ? edges.left : edges.right;
|
||||
const wall = adjust[role];
|
||||
return roadTopAt(edge.offset_m) - Math.max(wall.heightM ?? minH, minH) - wall.slopeM / 1.2;
|
||||
};
|
||||
const base = Math.min(roadTopAt(edges.left.offset_m), roadTopAt(edges.right.offset_m)) - minH;
|
||||
return Math.max(base - (oldSlab("inlet") + oldSlab("outlet")) / 2, 0);
|
||||
}
|
||||
|
||||
/** 성토 채움 윗면을 만들 노면 표고 — 설계선이 있으면 그 곡선, 없으면 노견 직선. */
|
||||
function roadTopFactory(
|
||||
section: CrossSection,
|
||||
@@ -150,6 +187,12 @@ export function computeFordLayout(
|
||||
const uphill = section.uphill_side ?? "left";
|
||||
const inletOnLeft = uphill === "left";
|
||||
const roadTopAt = roadTopFactory(section, edges.left, edges.right);
|
||||
// 바닥판 표고 하나(139-3) — 낮은 쪽 노견 노면 − (관경 + 토피) − 내림
|
||||
const dropM = fordDropOf(section, adjust);
|
||||
const slabTop =
|
||||
Math.min(roadTopAt(edges.left.offset_m), roadTopAt(edges.right.offset_m)) -
|
||||
minWallHeight(ford) -
|
||||
dropM;
|
||||
|
||||
const buildSide = (role: FordWallRole): FordSideLayout | null => {
|
||||
const onLeft = role === "inlet" ? inletOnLeft : !inletOnLeft;
|
||||
@@ -159,12 +202,9 @@ export function computeFordLayout(
|
||||
const wing = role === "inlet" ? ford.wing_in : ford.wing_out;
|
||||
const wanted = role === "inlet" ? adjust.inlet : adjust.outlet;
|
||||
const lateralM = Math.max(wanted.lateralM, 0);
|
||||
const heightM = Math.max(wanted.heightM ?? minWallHeight(ford), minWallHeight(ford));
|
||||
// 벽 상단이 노견에 붙도록 바닥판 상면을 역산한다. 상하 이동은 buildBasin이 얹는다.
|
||||
const anchor: OffsetPoint = {
|
||||
offset: edge.offset_m,
|
||||
elevation: roadTopAt(edge.offset_m) - heightM,
|
||||
};
|
||||
// 측벽 높이 = 그 쪽 노견 노면 − 바닥판(결과) — 벽 상단이 노견 표고 · 좌우는 노견 수평 확장
|
||||
const heightM = roadTopAt(edge.offset_m) - slabTop;
|
||||
const anchor: OffsetPoint = { offset: edge.offset_m, elevation: slabTop };
|
||||
const built = buildBasin({
|
||||
anchor,
|
||||
outward,
|
||||
@@ -180,7 +220,7 @@ export function computeFordLayout(
|
||||
innerWidthM: Math.max(wing.slab_extend_m, 0),
|
||||
innerHeightM: heightM,
|
||||
lateralM,
|
||||
slopeM: wanted.slopeM,
|
||||
slopeM: 0,
|
||||
},
|
||||
memberThicknessM: ford.wall_thickness_m,
|
||||
floorThicknessM: ford.slab_thickness_m,
|
||||
@@ -200,11 +240,11 @@ export function computeFordLayout(
|
||||
adjusts: [],
|
||||
equalize: false,
|
||||
});
|
||||
// 접근선 — 좌우 이동분은 노견 표고 그대로 수평, 상하 이동분만 대각(1:1.2).
|
||||
// 접근선 — 좌우 이동분은 노견 표고 그대로 수평(상하는 벽이 길어짐 · 139-3).
|
||||
const approach = basinApproachPoints(
|
||||
{ offset_m: edge.offset_m, elevation_m: roadTopAt(edge.offset_m) },
|
||||
outward,
|
||||
{ innerWidthM: 0, innerHeightM: 0, lateralM, slopeM: wanted.slopeM },
|
||||
{ innerWidthM: 0, innerHeightM: 0, lateralM, slopeM: 0 },
|
||||
{ offset: built.trimOffset, elevation: built.trimElevation },
|
||||
);
|
||||
return {
|
||||
@@ -241,11 +281,7 @@ export function computeFordLayout(
|
||||
limitOffset,
|
||||
).points
|
||||
: built.basin.cutLine,
|
||||
adjust: {
|
||||
heightM: wanted.heightM === null ? null : heightM,
|
||||
lateralM,
|
||||
slopeM: wanted.slopeM,
|
||||
},
|
||||
adjust: { heightM: null, lateralM, slopeM: 0 },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -268,7 +304,7 @@ export function computeFordLayout(
|
||||
Math.min(...sides.map((side) => side.parts[1].points[1].offset)),
|
||||
);
|
||||
|
||||
// 관 — 각 측 바닥판 상면(벽 바깥면)에 안힌다. 양측 표고가 다르면 그만큼 기운다.
|
||||
// 관 — 각 측 바닥판 상면(벽 바깥면)에 안힌다 · 바닥판 표고 하나라 수평(139-3).
|
||||
const pipeWall = pipeWallThicknessM(ford.pipe_kind, ford.diameter_m);
|
||||
const leftInvert = left.pipeEnd;
|
||||
const rightInvert = right.pipeEnd;
|
||||
@@ -315,6 +351,6 @@ export function computeFordLayout(
|
||||
minSlope: right.approach ? { points: right.approach } : undefined,
|
||||
maxSlope: left.approach ? { points: left.approach } : undefined,
|
||||
},
|
||||
adjust: { inlet: inlet.adjust, outlet: outlet.adjust },
|
||||
adjust: { inlet: inlet.adjust, outlet: outlet.adjust, dropM },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
* `_Cross_Panel_Base.ts`)을 쓴다 — 머리줄(제목·닫기), 2행 항목 구조,
|
||||
* **십자 조작 세트는 창 맨 아래**, 키보드 방향키 제어 포함(2026-08-25 일원화).
|
||||
*
|
||||
* 조작: 측벽 좌우(노견 확장) · 측벽 상하(1:1.2 대각) — 십자 · 그림 끌기. 측벽 높이 줄 없음(107-3) ·
|
||||
* 조작: 측벽 좌우(노견 확장 · 측벽마다) · 바닥판 상하(수직 · 양쪽 한 값 · 아래로만 · 139-3) — 십자 ·
|
||||
* 그림 끌기. 측벽 높이 = 결과(읽기 줄만) ·
|
||||
* 관경 · 수량 · 날개벽 제원은 폼 칸 하나(계획서 24-4a).
|
||||
* 바닥판 연장은 날개벽 길이×cos(각도)가 정하므로 읽기 전용으로 보여 준다.
|
||||
* ========================================================================== */
|
||||
@@ -32,11 +33,13 @@ export interface FordPanelDeps {
|
||||
heightFor: (role: FordWallRole) => number;
|
||||
/** 좌우: 화면 좌(+)/우(−)로 미는 양(m). 부호 환산은 카드가 한다. */
|
||||
nudge: (role: FordWallRole, screenDeltaM: number) => void;
|
||||
/** 상하: 1:1.2 사면을 타는 대각 이동(수평 성분 m, + = 사면 아래). */
|
||||
/** 상하: 바닥판 수직 내림(m, + = 아래 · 양쪽 한 값 · 139-3). */
|
||||
nudgeSlope: (role: FordWallRole, deltaM: number) => void;
|
||||
/** 지금 바닥판 내림(m · ≥ 0). */
|
||||
dropFor: () => number;
|
||||
/** 사면 바깥이 화면 왼쪽이면 1 · 오른쪽이면 −1(끌기 축 나누기 · 108-2). */
|
||||
outwardOf: (role: FordWallRole) => number;
|
||||
/** 그림 끌기 — 좌우(화면 좌 +) · 상하(사면) 를 한 번에 반영. */
|
||||
/** 그림 끌기 — 좌우(화면 좌 +) · 상하(수직 내림) 를 한 번에 반영. */
|
||||
move: (role: FordWallRole, screenDeltaM: number, slopeDeltaM: number) => void;
|
||||
/** 자동 자리로 되돌린다(세 축 모두). */
|
||||
reset: (role: FordWallRole) => void;
|
||||
@@ -88,7 +91,7 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
dpadButton(
|
||||
"▲",
|
||||
"up",
|
||||
"사면 위로(1:1.2 대각)",
|
||||
"바닥판 위로(양쪽 같이 · 기본 자리까지)",
|
||||
act((role) => deps.nudgeSlope(role, -STEP_M)),
|
||||
),
|
||||
dpadButton(
|
||||
@@ -107,7 +110,7 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
dpadButton(
|
||||
"▼",
|
||||
"down",
|
||||
"사면 아래로(1:1.2 대각)",
|
||||
"바닥판 아래로(양쪽 같이)",
|
||||
act((role) => deps.nudgeSlope(role, STEP_M)),
|
||||
),
|
||||
);
|
||||
@@ -119,8 +122,8 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
const arrows = opts?.dock
|
||||
? { attach: (): void => undefined, detach: (): void => undefined }
|
||||
: bindArrowKeys(root, () => [moveRow.controls]);
|
||||
// 그림 위 끌기 손잡이 — 십자와 같은 걸음 · 좌우 + 1:1.2 사면 두 축 함께(108-2).
|
||||
// 한계 = 좌우 0 ~ 10 m(노견 안쪽 금지) · 상하 ±10 m.
|
||||
// 그림 위 끌기 손잡이 — 십자와 같은 걸음 · 좌우 + 수직 두 축 함께(108-2 · 139-3).
|
||||
// 한계 = 좌우 0 ~ 10 m(노견 안쪽 금지) · 내림 0 ~ 10 m(기본 자리 위 금지).
|
||||
const drag = opts?.dock
|
||||
? { attach: (): void => undefined, detach: (): void => undefined }
|
||||
: bindDragHandle(root, {
|
||||
@@ -131,10 +134,10 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
const outward = deps.outwardOf(role);
|
||||
return snapAxisDrag(leftM, downM, {
|
||||
stepM: STEP_M,
|
||||
slopeOutward: outward,
|
||||
slopeOutward: null,
|
||||
clamp: (left, down) => [
|
||||
clampAxis(from.lateralM, left * outward, 0, MOVE_LIMIT_M) * outward,
|
||||
clampAxis(from.slopeM, down, -MOVE_LIMIT_M, MOVE_LIMIT_M),
|
||||
clampAxis(deps.dropFor(), down, 0, MOVE_LIMIT_M),
|
||||
],
|
||||
apply: (left, down) => deps.move(role, left, down),
|
||||
});
|
||||
@@ -147,9 +150,8 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
const role = current === "inlet" ? "유입" : "유출";
|
||||
title.textContent = `세월교 ${role} 측벽`;
|
||||
const adjust: FordWallAdjust = deps.adjustFor(current);
|
||||
heightLine.textContent =
|
||||
`높이 ${deps.heightFor(current).toFixed(2)}m` + (adjust.heightM === null ? " (자동)" : "");
|
||||
moveLine.textContent = `좌우 ${adjust.lateralM.toFixed(1)}m · 상하 ${adjust.slopeM.toFixed(1)}m`;
|
||||
heightLine.textContent = `높이 ${deps.heightFor(current).toFixed(2)}m`;
|
||||
moveLine.textContent = `좌우 ${adjust.lateralM.toFixed(1)}m · 내림 ${deps.dropFor().toFixed(1)}m`;
|
||||
slabLine.textContent = `바닥판 ${deps.slabLengthM().toFixed(2)}m`;
|
||||
}
|
||||
|
||||
@@ -177,7 +179,11 @@ export function buildFordPanel(deps: FordPanelDeps, opts?: { dock?: boolean }):
|
||||
/** 측점별 세월교 조작값 제어 — 페이지(세션·정본)가 구현한다. */
|
||||
export interface FordControl {
|
||||
adjustFor: (chainageM: number) => FordAdjust;
|
||||
update: (chainageM: number, role: FordWallRole, patch: Partial<FordWallAdjust>) => void;
|
||||
update: (
|
||||
chainageM: number,
|
||||
role: FordWallRole,
|
||||
patch: { lateralM?: number; dropM?: number },
|
||||
) => void;
|
||||
reset: (chainageM: number, role: FordWallRole) => void;
|
||||
/** 관경(mm)·수량(련)·관종·월류 폭 저장 — B05 정본(`pipe_points`)으로 간다.
|
||||
* 좌측 「구조물 배치」 폼만 보낸다(조정창에 칸 없음 · 계획서 24-4a). */
|
||||
|
||||
@@ -35,16 +35,16 @@ export function fordPanelDeps(context: FordPanelContext): FordPanelDeps {
|
||||
ford.update(chainage, role, {
|
||||
lateralM: ford.adjustFor(chainage)[role].lateralM + screenDeltaM * outwardOf(section, role),
|
||||
}),
|
||||
// 상하 = 바닥판 내림(양쪽 한 값 · 아래로만 · 139-3)
|
||||
nudgeSlope: (role, deltaM) =>
|
||||
ford.update(chainage, role, {
|
||||
slopeM: ford.adjustFor(chainage)[role].slopeM + deltaM,
|
||||
}),
|
||||
ford.update(chainage, role, { dropM: (ford.adjustFor(chainage).dropM ?? 0) + deltaM }),
|
||||
dropFor: () => ford.adjustFor(chainage).dropM ?? 0,
|
||||
outwardOf: (role) => outwardOf(section, role),
|
||||
move: (role, screenDeltaM, slopeDeltaM) => {
|
||||
const from = ford.adjustFor(chainage)[role];
|
||||
move: (role, screenDeltaM, dropDeltaM) => {
|
||||
const from = ford.adjustFor(chainage);
|
||||
ford.update(chainage, role, {
|
||||
lateralM: from.lateralM + screenDeltaM * outwardOf(section, role),
|
||||
slopeM: from.slopeM + slopeDeltaM,
|
||||
lateralM: from[role].lateralM + screenDeltaM * outwardOf(section, role),
|
||||
dropM: (from.dropM ?? 0) + dropDeltaM,
|
||||
});
|
||||
},
|
||||
reset: (role) => ford.reset(chainage, role),
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
wingSlabExtendM,
|
||||
} from "@util/common_util_culvert_sets";
|
||||
import type { CrossDesign, CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import { DEFAULT_FORD_WALL_ADJUST } from "./B06_Section_UI_Cross_Ford";
|
||||
import { DEFAULT_FORD_WALL_ADJUST, fordAdjustOf, fordDropOf } from "./B06_Section_UI_Cross_Ford";
|
||||
import type { FordAdjust, FordPick, FordWallAdjust } from "./B06_Section_UI_Cross_Ford";
|
||||
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||
import { DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box";
|
||||
@@ -141,10 +141,7 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
FordAdjust
|
||||
>;
|
||||
Object.entries(parsed).forEach(([chainage, value]) =>
|
||||
fordAdjustments.set(chainage, {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...value.inlet },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...value.outlet },
|
||||
}),
|
||||
fordAdjustments.set(chainage, fordAdjustOf(value)),
|
||||
);
|
||||
} catch {
|
||||
/* 손상된 세션 값은 기본값으로 대체. */
|
||||
@@ -157,14 +154,18 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
window.sessionStorage.setItem(key, JSON.stringify(Object.fromEntries(fordAdjustments)));
|
||||
}
|
||||
|
||||
// 옛 저장(측벽마다 높이 · 대각)은 읽을 때 바닥판 내림 하나로 옮김(139-3 · `fordDropOf`)
|
||||
const fordAdjustAt = (chainageM: number): FordAdjust => {
|
||||
const stored = sectionAt(chainageM)?.design?.ford_adjust;
|
||||
return (
|
||||
fordAdjustments.get(chainageM.toFixed(2)) ?? {
|
||||
inlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.inlet ?? {}) },
|
||||
outlet: { ...DEFAULT_FORD_WALL_ADJUST, ...(stored?.outlet ?? {}) },
|
||||
}
|
||||
);
|
||||
const section = sectionAt(chainageM);
|
||||
const adjust =
|
||||
fordAdjustments.get(chainageM.toFixed(2)) ?? fordAdjustOf(section?.design?.ford_adjust);
|
||||
if (adjust.dropM != null || !section) return adjust;
|
||||
const side = (wall: FordWallAdjust): FordWallAdjust => ({ ...wall, heightM: null, slopeM: 0 });
|
||||
return {
|
||||
inlet: side(adjust.inlet),
|
||||
outlet: side(adjust.outlet),
|
||||
dropM: fordDropOf(section, adjust),
|
||||
};
|
||||
};
|
||||
|
||||
const writeFord = (chainageM: number, next: FordAdjust): void => {
|
||||
@@ -176,22 +177,20 @@ export function createFordControls(deps: FordControlDeps): FordControls {
|
||||
|
||||
const fordControl: FordControl = {
|
||||
adjustFor: fordAdjustAt,
|
||||
// 측벽마다 좌우(도로 쪽 금지) · 바닥판 내림은 양쪽 한 값 · 아래로만(139-3)
|
||||
update: (chainageM, role, patch) => {
|
||||
const current = fordAdjustAt(chainageM);
|
||||
const wall: FordWallAdjust = { ...current[role], ...patch };
|
||||
const lateralM = patch.lateralM ?? current[role].lateralM;
|
||||
writeFord(chainageM, {
|
||||
...current,
|
||||
[role]: {
|
||||
// 높이는 0.1m 눈금, 하한(관경+토피)은 기하가 다시 잡는다.
|
||||
heightM: wall.heightM === null ? null : Math.max(round1(wall.heightM), 0),
|
||||
lateralM: Math.max(0, clampMove(wall.lateralM)),
|
||||
slopeM: clampMove(wall.slopeM),
|
||||
},
|
||||
[role]: { heightM: null, lateralM: Math.max(0, clampMove(lateralM)), slopeM: 0 },
|
||||
dropM: Math.max(0, round1(clampMove(patch.dropM ?? current.dropM ?? 0))),
|
||||
});
|
||||
},
|
||||
// 되돌림 = 그 측벽 좌우 + 바닥판 내림(양쪽 한 값이라 함께 0)
|
||||
reset: (chainageM, role) => {
|
||||
const current = fordAdjustAt(chainageM);
|
||||
writeFord(chainageM, { ...current, [role]: { ...DEFAULT_FORD_WALL_ADJUST } });
|
||||
writeFord(chainageM, { ...current, [role]: { ...DEFAULT_FORD_WALL_ADJUST }, dropM: 0 });
|
||||
},
|
||||
setPipe: (chainageM, patch) => {
|
||||
const spec = sectionAt(chainageM)?.ford;
|
||||
@@ -380,8 +379,8 @@ export function createBoxControls(deps: FordControlDeps): {
|
||||
update: (chainageM, role, patch) => {
|
||||
const current = adjustAt(chainageM);
|
||||
const side: BoxSideAdjust = { ...current[role], ...patch };
|
||||
// 본체는 늘 수평(79-7) — 표고는 좌 · 우가 한 값.
|
||||
const riseM = clampMove(side.riseM);
|
||||
// 본체는 늘 수평(79-7) — 표고는 좌 · 우가 한 값 · 아래로만(노면 − 복토 위 금지 · 139-3).
|
||||
const riseM = Math.min(clampMove(side.riseM), 0);
|
||||
const other = role === "left" ? "right" : "left";
|
||||
const next: BoxAdjust = { ...current, [other]: { ...current[other], riseM } };
|
||||
// 길이는 바깥으로만 — 안쪽으로 줄이면 성토선 물매가 깨진다.
|
||||
|
||||
@@ -252,14 +252,15 @@ out.box.left = boxCase("left", { lengthM: 0, riseM: 0 }, 0.47, 0.33);
|
||||
out.box.right = boxCase("right", { lengthM: 1, riseM: 0 }, -0.47, -0.21);
|
||||
out.box.shorten = boxCase("left", { lengthM: 0.2, riseM: 9.9 }, -0.9, -0.5);
|
||||
|
||||
// ── 세월교 창 — 좌우 + 1:1.2 사면 · 0.1 걸음 ─────────────────────────────────────
|
||||
function fordCase(outward, adjust, leftM, downM) {
|
||||
// ── 세월교 창 — 좌우 + 수직 내림(양쪽 한 값 · 아래로만 · 139-3) · 0.1 걸음 ─────────────
|
||||
function fordCase(outward, adjust, leftM, downM, dropM = 0) {
|
||||
const moves = [];
|
||||
const panel = buildFordPanel({
|
||||
adjustFor: () => adjust,
|
||||
heightFor: () => 1.5,
|
||||
nudge: () => {},
|
||||
nudgeSlope: () => {},
|
||||
dropFor: () => dropM,
|
||||
outwardOf: () => outward,
|
||||
move: (r, screen, slope) => moves.push({ role: r, screen: r3(screen), slope: r3(slope) }),
|
||||
reset: () => {},
|
||||
@@ -278,6 +279,7 @@ function fordCase(outward, adjust, leftM, downM) {
|
||||
out.ford = {
|
||||
diag: fordCase(1, { heightM: null, lateralM: 0, slopeM: 0 }, 0.53, 0.27),
|
||||
inward: fordCase(-1, { heightM: null, lateralM: 0.2, slopeM: 0 }, 0.9, 0),
|
||||
up: fordCase(1, { heightM: null, lateralM: 0, slopeM: 0 }, 0, -0.5, 0.2),
|
||||
};
|
||||
|
||||
// ── BOX 판정면 — 좌측 끝 날개벽(에이프런 4 → 6) · 우측 끝 날개벽 없음 ───────────
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/* 139-3 세월교 수평 헬퍼 — `_Cross_Ford_Geom`(computeFordLayout · fordDropOf)를 그 자리에서 트랜스파일해
|
||||
* 73-4 픽스처 세월교(173.087)로 돌림. 짝 = `test_139_3_ford_level.py`.
|
||||
*/
|
||||
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 noop = new Proxy({}, { get: () => () => "" });
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (request.startsWith("@ui/") || request.startsWith("@config/")) return noop;
|
||||
if (request.startsWith("@util/"))
|
||||
return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`));
|
||||
return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const { computeFordLayout, fordAdjustOf } = loadTs(
|
||||
path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Geom.ts"),
|
||||
);
|
||||
const sections = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "fixtures", "b06_73_4_body_guard_sections.json"), "utf8"),
|
||||
).cross_sections;
|
||||
const ford = sections.find((s) => s.ford);
|
||||
const r3 = (v) => Math.round(v * 1000) / 1000;
|
||||
const run = (stored) => {
|
||||
const layout = computeFordLayout(ford, ford.samples, fordAdjustOf(stored));
|
||||
return {
|
||||
slab: layout.sides.map((s) => r3(s.slabTopElevation)),
|
||||
pipeEnds: layout.sides.map((s) => r3(s.pipeEnd.elevation)),
|
||||
tops: layout.sides.map((s) => r3(s.top.elevation)),
|
||||
heights: layout.sides.map((s) => r3(s.heightM)),
|
||||
wallX: layout.sides.map((s) => r3(s.top.offset)),
|
||||
dropM: r3(layout.adjust.dropM),
|
||||
};
|
||||
};
|
||||
const wall = (o) => ({ heightM: null, lateralM: 0, slopeM: 0, ...o });
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
base: run(null),
|
||||
drop: run({ inlet: wall(), outlet: wall(), dropM: 0.4 }),
|
||||
up: run({ inlet: wall(), outlet: wall(), dropM: -1 }),
|
||||
lateral: run({ inlet: wall({ lateralM: 1 }), outlet: wall({ lateralM: -2 }), dropM: 0 }),
|
||||
// 옛 저장 — 유입 높이 + 0.6 · 유출 대각 1.2(= 수직 1.0) → 평균 내림 0.8 언저리
|
||||
old: run({ inlet: wall({ heightM: 2.1 }), outlet: wall({ slopeM: 1.2 }) }),
|
||||
}),
|
||||
);
|
||||
@@ -188,6 +188,7 @@ const fordDeps = {
|
||||
heightFor: () => 1.5,
|
||||
nudge: () => {},
|
||||
nudgeSlope: () => {},
|
||||
dropFor: () => 0,
|
||||
reset: () => {},
|
||||
slabLengthM: () => 7.6,
|
||||
close: () => {},
|
||||
|
||||
@@ -60,7 +60,11 @@ def test_세월교_원지반을_바꿔도_바닥_그대로(out: dict, variant: s
|
||||
for name, shape in runs.items():
|
||||
assert shape == runs["base"], name
|
||||
base = runs["base"]
|
||||
assert all(side["depth"] == pytest.approx(base["pipeCover"]) for side in base["sides"])
|
||||
# 바닥판 수평(139-3) — 표고 하나 · 낮은 노견 쪽 깊이 = 관경 + 토피 · 높은 쪽은 그보다 깊음
|
||||
sides = base["sides"]
|
||||
assert len({side["slabTop"] for side in sides}) == 1
|
||||
assert min(side["depth"] for side in sides) == pytest.approx(base["pipeCover"])
|
||||
assert all(side["depth"] >= base["pipeCover"] - 1e-6 for side in sides)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("variant", ["guards", "bare"])
|
||||
|
||||
@@ -82,19 +82,26 @@ def test_걸음_반도_안_끌면_반영_없음(out) -> None:
|
||||
def test_BOX_끌기는_길이_표고_함께(out) -> None:
|
||||
assert out["box"]["left"]["move"] == {"role": "left", "screen": 0.5, "rise": -0.3}
|
||||
assert out["box"]["left"]["snap"]["leftM"] == 0.5
|
||||
assert out["box"]["right"]["move"] == {"role": "right", "screen": -0.5, "rise": 0.2}
|
||||
# 위로는 기본 자리(노면 − 복토)까지만(139-3) — 0 에서 0.2 올리기 = 그대로
|
||||
assert out["box"]["right"]["move"] == {"role": "right", "screen": -0.5, "rise": 0}
|
||||
|
||||
|
||||
def test_BOX_한계_길이_0_표고_10(out) -> None:
|
||||
"""안으로 줄이면 길이 0 에서 · 표고는 +10 m 에서 멈춤."""
|
||||
assert out["box"]["shorten"]["move"] == {"role": "left", "screen": -0.2, "rise": 0.1}
|
||||
def test_BOX_한계_길이_0_표고_0(out) -> None:
|
||||
"""안으로 줄이면 길이 0 에서 · 표고는 0(기본 자리) 위로 못 감 — 옛 +9.9 는 0 으로 내려옴(139-3)."""
|
||||
assert out["box"]["shorten"]["move"] == {"role": "left", "screen": -0.2, "rise": -9.9}
|
||||
|
||||
|
||||
def test_세월교_끌기는_좌우_사면_함께(out) -> None:
|
||||
"""사면 d 0.3 · 좌우 0.2(0.1 걸음) — 미리 보기 = 좌 0.2 + 0.3 · 아래 0.3/1.2."""
|
||||
def test_세월교_끌기는_좌우_수직_함께(out) -> None:
|
||||
"""좌우 0.5 · 바닥판 내림 0.3(수직 · 0.1 걸음 · 139-3) — 두 축이 따로."""
|
||||
case = out["ford"]["diag"]
|
||||
assert case["move"] == {"role": "inlet", "screen": 0.2, "slope": 0.3}
|
||||
assert case["snap"] == {"leftM": 0.5, "downM": 0.25, "text": "◀ 0.2m · ▼ 0.3m"}
|
||||
assert case["move"] == {"role": "inlet", "screen": 0.5, "slope": 0.3}
|
||||
assert case["snap"] == {"leftM": 0.5, "downM": 0.3, "text": "◀ 0.5m · ▼ 0.3m"}
|
||||
|
||||
|
||||
def test_세월교_기본_자리_위로는_못_감(out) -> None:
|
||||
"""내림 0.2 에서 0.5 올려도 0(기본 자리)까지만(139-3)."""
|
||||
case = out["ford"]["up"]
|
||||
assert case["move"] == {"role": "inlet", "screen": 0, "slope": -0.2}
|
||||
|
||||
|
||||
def test_세월교_노견_안쪽은_0_에서_멈춤(out) -> None:
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""139-3 세월교 수평 — 바닥판 표고 하나 · 좌우만(도로 쪽 금지) · 내림 = 양쪽 한 값 · 아래로만 · 옛 값 옮김.
|
||||
|
||||
화면 TS(`_Cross_Ford_Geom`)를 그 자리에서 돌린 값(`helper_139_3_ford_level.cjs` · 73-4 픽스처 세월교 173.087).
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HELPER = Path(__file__).parent / "helper_139_3_ford_level.cjs"
|
||||
NODE_MODULES = ROOT / "config" / "node_modules"
|
||||
|
||||
pytestmark = pytest.mark.skipif(not NODE_MODULES.is_dir(), reason="프론트엔드 의존성 미설치")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out() -> dict:
|
||||
done = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HELPER)], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8"
|
||||
)
|
||||
assert done.returncode == 0, done.stderr
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
def test_바닥판_하나_관_수평_벽높이는_결과(out: dict) -> None:
|
||||
base = out["base"]
|
||||
assert base["slab"][0] == base["slab"][1] == base["pipeEnds"][0] == base["pipeEnds"][1]
|
||||
# 낮은 노견 쪽 = 관경 + 토피(1.5) · 높은 쪽은 노견 차만큼 더 김 · 벽 윗면 = 그 쪽 노견
|
||||
assert min(base["heights"]) == pytest.approx(1.5)
|
||||
assert base["heights"][1] - base["heights"][0] == pytest.approx(
|
||||
base["tops"][1] - base["tops"][0], abs=1e-3
|
||||
)
|
||||
|
||||
|
||||
def test_내림은_양쪽_한_값_아래로만(out: dict) -> None:
|
||||
assert out["drop"]["slab"] == [pytest.approx(out["base"]["slab"][0] - 0.4)] * 2
|
||||
assert out["drop"]["tops"] == out["base"]["tops"] # 벽 윗면은 노견에 그대로
|
||||
assert out["up"] == out["base"] # 기본 자리 위 금지 — 음수 내림 = 0
|
||||
|
||||
|
||||
def test_좌우만_도로_쪽_금지(out: dict) -> None:
|
||||
lat, base = out["lateral"], out["base"]
|
||||
assert lat["slab"] == base["slab"]
|
||||
assert abs(lat["wallX"][0] - base["wallX"][0]) == pytest.approx(1.0) # 바깥 1 m
|
||||
assert lat["wallX"][1] == base["wallX"][1] # 안쪽 −2 = 0 에서 멈춤
|
||||
|
||||
|
||||
def test_옛_값은_평균_내림으로_옮김(out: dict) -> None:
|
||||
"""유입 높이 2.1(기본 1.5 + 0.6) · 유출 대각 1.2(수직 1.0) + 노견 차 0.12 → (0.6 + 0.88) / 2 = 0.74."""
|
||||
old = out["old"]
|
||||
assert old["dropM"] == pytest.approx(0.74, abs=1e-3)
|
||||
assert old["slab"][0] == old["slab"][1]
|
||||
@@ -105,11 +105,12 @@ def test_유입_수평공간_시작은_본체_바닥(shape: dict) -> None:
|
||||
def test_관_보호공_기본_자리는_제일_위(shape: dict) -> None:
|
||||
pipe = shape["pipe"]
|
||||
assert pipe is not None
|
||||
assert pipe["d"]["inlet"] == pytest.approx(0, abs=1e-6)
|
||||
# 벽 윗면 = 관 윗면(139-1) · 관 위 토피 0.5 → 유입 벽 윗면 = 노견 − 0.5 = 사면 d 0.6(1:1.2)
|
||||
assert pipe["d"]["inlet"] == pytest.approx(0.6, abs=1e-6)
|
||||
# 유출은 관 m 단위 맞춤이 사면 아래로 민 만큼만 내려감(종전 기본 = 경사길이 4 m · d 3.07)
|
||||
assert pipe["d"]["outlet"] < 1.0
|
||||
inlet = next(w for w in pipe["walls"] if w["role"] == "inlet")
|
||||
assert inlet["topBelowEdge"] == pytest.approx(0, abs=0.05)
|
||||
assert inlet["topBelowEdge"] == pytest.approx(0.5, abs=0.05)
|
||||
assert float(pipe["lengthM"]).is_integer()
|
||||
|
||||
|
||||
|
||||
@@ -103,8 +103,12 @@ def test_흙막이_수평공간_뒤_절토_복합경사(fit: dict) -> None:
|
||||
|
||||
|
||||
def test_박스_끝_세월교_측벽_끝_절토_복합경사(fit: dict) -> None:
|
||||
# 세월교 바닥판 수평(139-3) — 높은 노견 쪽 끝은 암까지 안 닿는 짧은 토사 절토일 수 있음
|
||||
for cuts in (fit["boxEnd"], fit["fordEnd"]):
|
||||
assert cuts and all(_knee(cut) and cut["endGap"] < 0.01 for cut in cuts)
|
||||
assert cuts and all(cut["endGap"] < 0.01 for cut in cuts)
|
||||
assert any(_knee(cut) for cut in cuts)
|
||||
rock = [c for c in cuts if any(leg["ratio"] == 0.4 for leg in c["legs"])]
|
||||
assert all(_knee(cut) for cut in rock)
|
||||
|
||||
|
||||
def test_절토_쪽_배관_유출_벽_뒤_절토사면_살아남(fit: dict) -> None:
|
||||
|
||||
Reference in New Issue
Block a user