fix(B06): 박스 흙막이 기본 높이 = 박스 높이 값 · 저장 x 벽 종전 자리 그대로 · 흙막이 조정창 제목 · 박스 날개벽 툴팁(79b)
- 박스 흙막이 높이 빈 칸 = 박스 높이 값(2 m 박스 → 2 m) · 윗면 = 본체 윗면 · 바닥 = 윗면 − 높이 · 세월교는 그대로 - x 원점(사면 경사길이 4 m 지점) 되돌림 — 손대지 않은 벽(x · d 빈 칸)만 성토사면 제일 위 · 손으로 x 를 넣은 독립 기슭막이 · 옹벽 · 관 보호공은 종전 자리 - 세월교 · 박스 흙막이 조정창 제목 「흙막이(유입 · 유출)」 · 박스 에이프런 툴팁 유입 · 유출 날개벽 이름 바로잡음 - 시험: test_79_guard_shape — 박스 높이 값 · 저장 x 있는 관 보호공 · 독립 기슭막이 = 종전 자리 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -50,7 +50,7 @@ const guardContainer = (
|
||||
{ value: "흙막이", label: "흙막이" },
|
||||
],
|
||||
},
|
||||
...bodyGuardItems(id),
|
||||
...bodyGuardItems(id, "박스 높이(자동)"),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ const guardContainer = (id: "inlet" | "outlet", title: string, link: string): Sp
|
||||
key: `${id}_type`,
|
||||
choices: guard(id === "inlet" ? "in" : "out"),
|
||||
},
|
||||
...bodyGuardItems(id),
|
||||
...bodyGuardItems(id, "본체 끝 높이(자동)"),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -82,11 +82,11 @@ export const revetKeys = (side: "inlet" | "outlet"): Record<string, string> => (
|
||||
after: `${side}_revet_after_m`,
|
||||
});
|
||||
|
||||
/** 세월교 · 박스 유입 · 유출 흙막이 칸(79-7) — 높이 빈 칸 = 본체 끝 높이(횡단도가 셈) ·
|
||||
/** 세월교 · 박스 유입 · 유출 흙막이 칸(79-7) — 높이 빈 칸 = 자동(`heightHint` · 횡단도가 셈) ·
|
||||
* 절토부 앞 수평공간 칸(`{side}_revet_berm_m`)을 더함. */
|
||||
export function bodyGuardItems(side: "inlet" | "outlet"): SpecItem[] {
|
||||
export function bodyGuardItems(side: "inlet" | "outlet", heightHint: string): SpecItem[] {
|
||||
const items = guardItems({ 흙막이: revetKeys(side) }, undefined, true).map((item) =>
|
||||
item.id === "height" ? { ...item, default: undefined, hint: "본체 끝 높이(자동)" } : item,
|
||||
item.id === "height" ? { ...item, default: undefined, hint: heightHint } : item,
|
||||
);
|
||||
const berm: SpecItem = {
|
||||
id: "berm",
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*
|
||||
* 세트는 배관 흙막이와 같은 `section.culvert`(관 숨김 · `body`)로 얹혀 그림 · 연동 · 경사 · 3D ·
|
||||
* 면적 길을 그대로 탐. 다른 점은 자동 자리 하나 — 벽 **배면(수직)이 본체 끝**에 붙어 벽 몸이 본체
|
||||
* 바깥에 섬 · 벽 윗면 = 본체 윗면(세월교 상판 · 박스 상판) · 높이 기본 = 본체 끝 높이(79-7 · 그림
|
||||
* 바깥에 섬 · 벽 윗면 = 본체 윗면(세월교 상판 · 박스 상판) · 높이 기본 = 세월교 본체 끝 높이 ·
|
||||
* 박스 높이 값(2 m 박스 → 2 m · 79b · 바닥 = 윗면 − 높이)(79-7 · 그림
|
||||
* `tmp/79_세월교_기본위치.png`). 좌우 x 는 그 자리 기준 · 상하 d 는 배관과 같은 절대값.
|
||||
* 본체가 이 측점에 없으면(연동 옆 측점) 배관 규칙 그대로 — 소유 측점 값을 절대값으로 넘겨 받음.
|
||||
* ========================================================================== */
|
||||
@@ -25,11 +26,12 @@ import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall";
|
||||
|
||||
type Role = "inlet" | "outlet";
|
||||
|
||||
/** 본체 끝 — 전면 자리(offset) · 윗면(top) · 바닥(bottom) 표고(79-7). */
|
||||
/** 본체 끝 — 전면 자리(offset) · 윗면(top) · 바닥(bottom) 표고 · 흙막이 기본 순수 높이(79-7 · 79b). */
|
||||
export interface BodyEnd {
|
||||
offset: number;
|
||||
top: number;
|
||||
bottom: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** 본체 끝(세월교 관 끝 · 박스 끝) + 본체 설계선 트림 — 본체가 이 측점에 없으면 null. */
|
||||
@@ -56,7 +58,13 @@ export function bodyGuardBase(
|
||||
(s) => (s.role === "left") === ((role === "inlet") === inletOnLeft),
|
||||
);
|
||||
return side
|
||||
? { offset: side.offset, top: side.topElevation, bottom: side.bottomElevation }
|
||||
? {
|
||||
offset: side.offset,
|
||||
top: side.topElevation,
|
||||
bottom: side.bottomElevation,
|
||||
// 박스 높이 값 그대로(사용자 원문 2 m → 2 m · 79b)
|
||||
height: layout.box.inner_height_m,
|
||||
}
|
||||
: null;
|
||||
};
|
||||
return { ends: { inlet: endOf("inlet"), outlet: endOf("outlet") }, trim: layout.designTrim };
|
||||
@@ -75,6 +83,7 @@ export function bodyGuardBase(
|
||||
offset: side.pipeEnd.offset,
|
||||
top: side.top.elevation,
|
||||
bottom: side.slabTopElevation - layout.ford.slab_thickness_m,
|
||||
height: side.top.elevation - (side.slabTopElevation - layout.ford.slab_thickness_m),
|
||||
}
|
||||
: null;
|
||||
};
|
||||
@@ -83,8 +92,8 @@ export function bodyGuardBase(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 흙막이 기본 순수 높이(근입 포함) = 본체 끝 높이(윗면 ~ 바닥 · 79-7). */
|
||||
export const bodyGuardHeight = (end: BodyEnd): number => end.top - end.bottom;
|
||||
/** 흙막이 기본 순수 높이(근입 포함) — 세월교 = 본체 끝 높이 · 박스 = 박스 높이 값(79-7 · 79b). */
|
||||
export const bodyGuardHeight = (end: BodyEnd): number => end.height;
|
||||
|
||||
/**
|
||||
* 벽 배면을 본체 끝에 붙인 배관 벽 4축(x · d) — `placePipeWall` 역산.
|
||||
|
||||
@@ -80,7 +80,8 @@ export function appendBoxOverlay(
|
||||
side.apron,
|
||||
"b06-chart__ford-slab",
|
||||
`${mark}측 날개벽 구간 바닥 — 수평 · 두께 ${box.slab_thickness_m.toFixed(2)}m` +
|
||||
` (날개벽 ${wingText(side.role === "left" ? box.wing_in : box.wing_out)} 투영 연장)`,
|
||||
` (${side.wingRole === "inlet" ? "유입" : "유출"} 날개벽 ` +
|
||||
`${wingText(side.wingRole === "inlet" ? box.wing_in : box.wing_out)} 투영 연장)`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,8 @@ export interface BoxSideLayout {
|
||||
fillSegments: OutletFillSegment[];
|
||||
/** 끝이 원지반에 묻힐 때의 절토선(계획선). */
|
||||
cutLine: BasinLayout["cutLine"];
|
||||
/** 이 끝의 날개벽 칸 — 유입(상단측) · 유출(툴팁 이름 · 79b). */
|
||||
wingRole: "inlet" | "outlet";
|
||||
/** 적용된 조작값(한계에 걸린 뒤). */
|
||||
adjust: BoxSideAdjust;
|
||||
}
|
||||
@@ -146,7 +148,8 @@ export function computeBoxLayout(
|
||||
const offset = edgeOffset + outward * (reach + lengthM);
|
||||
const bottomElevation = topElevation - bodyHeight;
|
||||
// 날개벽 구간 바닥 — 각 끝 표고에서 **수평**(2026-08-25 사용자).
|
||||
const wing = (role === "left") === inletOnLeft ? box.wing_in : box.wing_out;
|
||||
const wingRole = (role === "left") === inletOnLeft ? "inlet" : "outlet";
|
||||
const wing = wingRole === "inlet" ? box.wing_in : box.wing_out;
|
||||
const extend = Math.max(wing.slab_extend_m, 0);
|
||||
const outer = offset + outward * extend;
|
||||
const apron: OffsetPoint[] = extend
|
||||
@@ -195,6 +198,7 @@ export function computeBoxLayout(
|
||||
fillLine,
|
||||
fillSegments,
|
||||
cutLine,
|
||||
wingRole,
|
||||
adjust: { lengthM, riseM },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -184,19 +184,26 @@ export const FILL_SLOPE_RATIO_MAX = 2.0;
|
||||
export const FILL_SLOPE_MAX_LENGTH_M = 5.0;
|
||||
|
||||
/**
|
||||
* 흙막이 · 옹벽 · 관 보호공 기본 자리 = 성토사면을 따라 **가능한 범위 안 제일 위**(79-5) —
|
||||
* 성토선 길이 0 지점(벽 상단 = 노견). 상하(d) 조작의 0점도 그 점이고, d는 1:1.2 성토선을
|
||||
* 타고 내려간 수평 성분(m)이다. 관 보호공은 배치 풀이의 한계(토피 · 역경사)와 관 m 단위
|
||||
* 맞춤(유출 벽을 사면 아래로 밂)이 그 위에서 자리를 정한다.
|
||||
* 좌우(x) 원점 = 노견에서 성토사면 경사길이 4m 지점(2026-08-23 · 독립 기슭막이 · 옹벽의 x 원점).
|
||||
* 상하(d) 조작의 0점은 성토선 길이 0 지점(벽 상단 = 노견)이고, d는 1:1.2 성토선을 타고
|
||||
* 내려간 수평 성분(m)이다. 저장된 x 는 이 원점 기준이라 절대 자리가 그대로다(79b).
|
||||
*/
|
||||
export const PIPE_WALL_DEFAULT_SLOPE_M = 0;
|
||||
/** 기본 자리의 수평 성분(m) — d 기본값. */
|
||||
export const PIPE_WALL_DEFAULT_SLOPE_M = 4.0;
|
||||
/** x 원점(경사길이 4m)의 수평 성분(m). */
|
||||
/** 세월교 · 박스 흙막이 절토부 앞 수평공간 기본값(m · 79-7) — 흙막이 컨테이너 칸이 비면.
|
||||
* 관 유입 접속선의 「1m 공간 확보」(2026-08-23 사용자)와 같은 값. */
|
||||
export const GUARD_CUT_BERM_M = 1.0;
|
||||
export const PIPE_WALL_DEFAULT_RUN_M =
|
||||
(PIPE_WALL_DEFAULT_SLOPE_M * FILL_SLOPE_RATIO_MIN) / Math.hypot(1, FILL_SLOPE_RATIO_MIN);
|
||||
|
||||
/**
|
||||
* 관 보호공 d(상하) 빈 칸의 기본값 — x 도 비었으면 성토사면 **제일 위**(0 · 79-5), 손으로 x 를
|
||||
* 넣어 둔 벽은 종전 자리(경사길이 4m)라 절대 자리가 안 바뀜(79b). 관은 그 위에서 m 단위 맞춤이
|
||||
* 유출 벽을 사면 아래로 민다.
|
||||
*/
|
||||
export const pipeWallDefaultD = (adjust: { x: number }): number =>
|
||||
Math.abs(adjust.x) > 1e-9 ? PIPE_WALL_DEFAULT_RUN_M : 0;
|
||||
|
||||
/**
|
||||
* 성토 전체고(노견 − 원지반) 기준(m) — 이 높이를 넘으면 1:1.2로도 사면이 5m를 넘어
|
||||
* 구조물이 필요해진다(3m × √(1+1.2²) = 4.69m, 3.2m에서 5.0m). 기슭막이를 자연 사면
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import type { CrossSection, CulvertSideSpec, SectionSample } from "./B06_Section_Api_Fetch";
|
||||
import {
|
||||
revetWallSpec,
|
||||
PIPE_WALL_DEFAULT_RUN_M,
|
||||
pipeWallDefaultD,
|
||||
FILL_SLOPE_MAX_LENGTH_M,
|
||||
FILL_SLOPE_RATIO_MAX,
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
@@ -250,7 +250,7 @@ export function computeCulvertLayout(
|
||||
// 독립 기슭막이(관 숨김)는 관 토피·역경사 제약이 없다 — 유입도 노견까지 자유롭게 오르내린다.
|
||||
invertCap: culvert.hidden_pipe ? Number.POSITIVE_INFINITY : invertCap(inletInfo.edge),
|
||||
adjust: onBody(adjInlet, inletBody),
|
||||
defaultD: PIPE_WALL_DEFAULT_RUN_M,
|
||||
defaultD: pipeWallDefaultD(adjInlet),
|
||||
minX,
|
||||
});
|
||||
appliedAdjust.inlet.x = placed.x - (inletBody?.x ?? 0);
|
||||
@@ -439,7 +439,7 @@ export function computeCulvertLayout(
|
||||
// 독립 기슭막이는 양쪽 벽이 독립 — 유출을 유입 invert로 묶는 역경사 클램프를 뺀다.
|
||||
invertCap: culvert.hidden_pipe ? Number.POSITIVE_INFINITY : inlet.elevation,
|
||||
adjust: onBody(adjOutlet, outletBody),
|
||||
defaultD: PIPE_WALL_DEFAULT_RUN_M,
|
||||
defaultD: pipeWallDefaultD(adjOutlet),
|
||||
minX,
|
||||
});
|
||||
appliedAdjust.outlet.x = placed.x - (outletBody?.x ?? 0);
|
||||
|
||||
@@ -381,7 +381,7 @@ export function placePipeWall(input: {
|
||||
/** invert 상한 — 유입: 노면−관경−토피, 유출: 유입 invert(역경사 방지). */
|
||||
invertCap: number;
|
||||
adjust: WallAdjust;
|
||||
/** d 미지정(null) 시의 기본 자리 — 성토선 0 지점(제일 위 · 79-5). */
|
||||
/** d 미지정(null) 시의 기본 자리 — 성토선 0 지점(제일 위 · 79-5) · x 를 넣은 벽은 4m 지점(79b). */
|
||||
defaultD: number;
|
||||
/** x 하한 — 기본 0(안쪽 금지) · 세월교 · 박스 흙막이는 본체 끝이 0점이라 안 막음(73-4). */
|
||||
minX?: number;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { outlineLeanOf } from "./B06_Section_UI_Cross_Lean";
|
||||
|
||||
// d의 0점 = **성토선 길이 0 지점**(벽 상단 = 노견 — 2026-08-23 사용자 확정).
|
||||
// 그 자리(하단선 중점)는 이음선 상단점이 노견 끝에 오는 offset이고, 기본 자리는
|
||||
// 그 점 자체다(PIPE_WALL_DEFAULT_RUN_M = 0 · 79-5).
|
||||
// 그 점 자체다 · 손으로 x 를 넣은 벽만 경사길이 4m 지점(`pipeWallDefaultD` · 79-5 · 79b).
|
||||
export const slopeZeroAnchor = (
|
||||
edge: { offset_m: number },
|
||||
outward: number,
|
||||
|
||||
@@ -202,20 +202,25 @@ export function computeRevetmentLayout(
|
||||
const roadSlope = roadSlopePerOutward(design, side, outward);
|
||||
const autoOffset =
|
||||
edge.offset_m + outward * (fillWallBaseWidth(height, lean) / 2 - REVET_THICKNESS_M / 2);
|
||||
// 자동 자리 = 성토사면(1:1.2) 제일 위(노견 · 79-5) — 배관 유출 벽과 같은 기준값.
|
||||
// x 원점 = 성토사면(1:1.2) 경사길이 4m 지점 — 저장된 x 의 절대 자리 그대로(79b).
|
||||
const autoRun = PIPE_WALL_DEFAULT_RUN_M;
|
||||
/**
|
||||
* 조작 축(2026-08-29 사용자): x = **순수 좌우(m)**, d = **노견에서 잰 순수 수직
|
||||
* 하강(m)**. 성토선 각도는 그대로 두고 **노견 확장 구간**이 길이를 흡수한다
|
||||
* (확장량 t는 아래 폴리라인에서 역산 — t ≥ 0이 각도 유지의 한계다).
|
||||
*/
|
||||
const defaultDrop = autoRun / FILL_SLOPE_RATIO_MIN;
|
||||
const requestedX = Math.max(adjust?.x ?? 0, 0);
|
||||
/** 각도(1:1.2)를 지키려면 그만큼은 밖으로 나가 있어야 하는 최소 수평 자리. */
|
||||
const xFloor = (drop: number): number => Math.max(0, FILL_SLOPE_RATIO_MIN * drop - autoRun);
|
||||
// 손대지 않은 벽(x · d 빈 칸) = 성토사면 제일 위(노견 · 79-5) · 손으로 넣은 벽은 종전 기본(79b).
|
||||
const untouched = adjust?.d == null && !adjust?.x;
|
||||
const defaultDrop = untouched ? 0 : autoRun / FILL_SLOPE_RATIO_MIN;
|
||||
/** 각도(1:1.2)를 지키려면 그만큼은 밖으로 나가 있어야 하는 최소 수평 자리 — 음수 = 원점보다
|
||||
* 사면 위(노견 쪽)라 제일 위 자리는 x = −autoRun 이다. */
|
||||
const xFloor = (drop: number): number => FILL_SLOPE_RATIO_MIN * drop - autoRun;
|
||||
const requestedX = untouched ? Number.NEGATIVE_INFINITY : (adjust?.x ?? 0);
|
||||
// 원지반은 자리를 막지 않는다 — 묻히든 뜨든 입력 그대로(54-45). 노면 위 금지 = d ≥ 0.
|
||||
const appliedDrop = Math.round(Math.max(adjust?.d ?? defaultDrop, 0) * 10) / 10;
|
||||
const appliedX = Math.round(Math.max(requestedX, xFloor(appliedDrop)) * 10) / 10;
|
||||
// 0.1 눈금 반올림(종전과 같음) · 반올림이 하한 아래로 내리면 하한(제일 위 자리가 노견 안쪽으로 안 감).
|
||||
const floor = xFloor(appliedDrop);
|
||||
const appliedX = Math.max(Math.round(Math.max(requestedX, floor) * 10) / 10, floor);
|
||||
|
||||
const anchorOffset = autoOffset + outward * (autoRun + appliedX);
|
||||
const baseElevation = edge.elevation_m - appliedDrop - height;
|
||||
@@ -238,9 +243,8 @@ export function computeRevetmentLayout(
|
||||
// 넣은 좌우 이동이 **각도 하한**에 눌려 통째로 무시됐으면 그 하한을 남긴다(2026-09-07).
|
||||
// 실측 — d 2.6m 자리에서 하한이 1.0m 를 넘어 「1.0m 를 넣어도 0.00m 이동」이 났고,
|
||||
// 지형에 막힌 것이 아니라 `shiftBlockedM` 은 0 이어서 까닭을 알 길이 없었다.
|
||||
const floorNow = xFloor(appliedDrop);
|
||||
if (requestedX > 0.005 && floorNow > requestedX + 0.005) {
|
||||
tier1.shiftFloorM = Math.round(floorNow * 100) / 100;
|
||||
if (requestedX > 0.005 && floor > requestedX + 0.005) {
|
||||
tier1.shiftFloorM = Math.round(floor * 100) / 100;
|
||||
}
|
||||
|
||||
// 다단 — 배관과 같은 함수. 1단 벽 전면 하단 꼭짓점을 성토부선 시작점으로.
|
||||
|
||||
@@ -60,6 +60,8 @@ export interface StructurePanelDeps {
|
||||
/** 독립 기슭막이(관 숨김) 카드인가 — 유입·유출 벽이 같은 성토 로직이라 조정창도
|
||||
* 좌우 같은 행을 준다(집수정 형식 숨김 · 유입측 단 수 행 표시 — 2026-08-29). */
|
||||
hiddenPipe: () => boolean;
|
||||
/** 세월교 · BOX암거 흙막이 세트인가 — 조정창 제목을 「흙막이」로(79b). */
|
||||
bodyGuard?: () => boolean;
|
||||
/** 이동 조작 가능 여부 — 집수정(자리 고정)은 숨긴다. */
|
||||
canNudge: (key: RevetKey) => boolean;
|
||||
/** 좌우·상하 걸음이 0.1m인가(독립·추가 기슭막이). 배관 기준벽은 false = 1m. */
|
||||
@@ -517,16 +519,19 @@ export function buildStructurePanel(
|
||||
key === "inlet" && (structure === "I" || structure === "L" || structure === "U");
|
||||
// 독립 기슭막이(관 숨김)의 기준벽은 관이 없어 유입·유출이라는 이름이 안 맞는다 —
|
||||
// 좌·우 어느 쪽이든 "기슭막이(독립)"으로 적는다(2026-08-29 사용자).
|
||||
const guardKey = key === "inlet" || key === "outlet";
|
||||
title.textContent =
|
||||
isOwn || (deps.hiddenPipe() && (key === "inlet" || key === "outlet"))
|
||||
? L("B06_Cross_Revet_Own")
|
||||
: isExtra
|
||||
? L("B06_Cross_Revet_Extra").replace("{n}", String(extraIndex + 1))
|
||||
: key === "outlet"
|
||||
? L("B06_Cross_Revet_Outlet")
|
||||
: movable
|
||||
? L("B06_Cross_Revet_Inlet")
|
||||
: L("B06_Cross_Struct_InletBasin");
|
||||
deps.bodyGuard?.() && guardKey
|
||||
? L(key === "inlet" ? "B06_Cross_Guard_Inlet" : "B06_Cross_Guard_Outlet")
|
||||
: isOwn || (deps.hiddenPipe() && guardKey)
|
||||
? L("B06_Cross_Revet_Own")
|
||||
: isExtra
|
||||
? L("B06_Cross_Revet_Extra").replace("{n}", String(extraIndex + 1))
|
||||
: key === "outlet"
|
||||
? L("B06_Cross_Revet_Outlet")
|
||||
: movable
|
||||
? L("B06_Cross_Revet_Inlet")
|
||||
: L("B06_Cross_Struct_InletBasin");
|
||||
// 이동 조작은 기슭막이(집수정 제외) · 집수정은 위치 십자.
|
||||
dragKind = isBasin ? "basin" : movable ? "wall" : null;
|
||||
moveRow.row.classList.toggle("is-hidden", !movable);
|
||||
|
||||
@@ -645,6 +645,7 @@ export function createCrossSectionCard(
|
||||
appliedD: () => culvertAppliedD,
|
||||
inletIsBasin: () => culvertInletIsBasin,
|
||||
hiddenPipe: () => culvertHiddenPipe,
|
||||
bodyGuard: () => !!(section.culvert ?? culvertLink?.source.culvert)?.body,
|
||||
inletOptions: () => culvertInletOptions,
|
||||
extraState: () => culvertExtraState,
|
||||
basinExtraState: () => culvertBasinExtraState,
|
||||
|
||||
@@ -71,6 +71,8 @@ export interface StructurePanelContext {
|
||||
inletIsBasin: () => boolean;
|
||||
/** 기준벽이 독립 기슭막이(관 없는 숨김 세트)인가 — 조정창 걸음 분류에 쓴다. */
|
||||
hiddenPipe: () => boolean;
|
||||
/** 세월교 · BOX암거 흙막이 세트인가(링크 카드는 소유 측점 세트 · 79b). */
|
||||
bodyGuard?: () => boolean;
|
||||
inletOptions: () => { revetAllowed: boolean; basinLUAllowed: boolean };
|
||||
extraState: () => { canAdd: boolean; count: number };
|
||||
basinExtraState: () => { canAdd: boolean; count: number };
|
||||
@@ -148,6 +150,7 @@ export function structurePanelDeps(ctx: StructurePanelContext): StructurePanelDe
|
||||
isLinkedCard: () => ctx.isLinked,
|
||||
basinExtraState: () => ctx.basinExtraState(),
|
||||
hiddenPipe: () => ctx.hiddenPipe(),
|
||||
bodyGuard: () => ctx.bodyGuard?.() ?? false,
|
||||
setExtraCount: (count) => {
|
||||
// 줄어들며 사라지는 벽이 선택 중이면 선택을 유출 벽으로 옮긴다(빈 대상 방지).
|
||||
const active = ctx.activeRevet() ?? "";
|
||||
|
||||
@@ -77,6 +77,7 @@ function guardFit(section, list = sections) {
|
||||
bottomGap: r3(bottom - end.bottom),
|
||||
pureHeight: r3(w.height + 0.5),
|
||||
endHeight: r3(end.top - end.bottom),
|
||||
autoHeight: r3(end.height),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -180,6 +181,15 @@ out.pipe = pipeLayout
|
||||
}
|
||||
: null;
|
||||
|
||||
// ⑥-2 손으로 x 를 넣은 관 보호공 = 종전 자리(d 기본 = 경사길이 4m 지점 · x 원점 그대로 · 79b)
|
||||
const pipeX = computeCulvertLayout(
|
||||
pipe,
|
||||
pipe.samples,
|
||||
{ outlet: { x: 1, d: null, h: null, m: null } },
|
||||
"revet",
|
||||
);
|
||||
out.pipeStoredX = pipeX ? { x: pipeX.revetShift.outlet.x, d: r3(pipeX.revetShift.outlet.d) } : null;
|
||||
|
||||
// ⑦ 독립 기슭막이 · 옹벽 기본 자리 = 제일 위(d 0)
|
||||
const revet = clone(at(180));
|
||||
revet.revetment = {
|
||||
@@ -201,6 +211,17 @@ out.revet = own
|
||||
linked: own.linked,
|
||||
}
|
||||
: null;
|
||||
// 저장 x 가 있는 벽 = 종전 자리 그대로(x 원점 = 경사길이 4m 지점 · d 빈 칸 = 종전 기본 · 79b)
|
||||
const autoX = computeRevetmentLayout(revet, null).walls[0].bottomBack.offset;
|
||||
const storedX = computeRevetmentLayout(revet, { x: 1, d: null });
|
||||
const storedXD = computeRevetmentLayout(revet, { x: 1, d: 1 });
|
||||
out.revetStoredX = {
|
||||
x: storedX.appliedAdjust.x,
|
||||
d: storedX.appliedAdjust.d,
|
||||
// 노견 → 4m 지점(3.07) + x 1 = 제일 위 자리에서 바깥 4.07 m
|
||||
shift: r3(Math.abs(storedX.walls[0].bottomBack.offset - autoX)),
|
||||
shiftXD: r3(Math.abs(storedXD.walls[0].bottomBack.offset - autoX)),
|
||||
};
|
||||
const neighbour = clone(revet);
|
||||
neighbour.chainage_m = 170;
|
||||
out.revetLinked = computeRevetmentLayout(neighbour, null)?.linked ?? null;
|
||||
|
||||
@@ -33,9 +33,8 @@ def shape() -> dict:
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("key", ["boxAuto", "fordAuto"])
|
||||
def test_높이_빈칸은_본체_끝_높이_윗면은_본체_윗면(shape: dict, key: str) -> None:
|
||||
walls = shape[key]
|
||||
def test_세월교_높이_빈칸은_본체_끝_높이_윗면은_본체_윗면(shape: dict) -> None:
|
||||
walls = shape["fordAuto"]
|
||||
assert {w["role"] for w in walls} == {"inlet", "outlet"}
|
||||
for wall in walls:
|
||||
assert wall["topGap"] == pytest.approx(0, abs=1e-3)
|
||||
@@ -43,6 +42,17 @@ def test_높이_빈칸은_본체_끝_높이_윗면은_본체_윗면(shape: dict,
|
||||
assert wall["pureHeight"] == pytest.approx(wall["endHeight"], abs=1e-3)
|
||||
|
||||
|
||||
def test_박스_높이_빈칸은_박스_높이_값_윗면은_본체_윗면(shape: dict) -> None:
|
||||
# 2 m 박스 → 2 m(사용자 원문 숫자 · 79b) — 바닥 = 윗면 − 그 높이
|
||||
walls = shape["boxAuto"]
|
||||
assert {w["role"] for w in walls} == {"inlet", "outlet"}
|
||||
for wall in walls:
|
||||
assert wall["topGap"] == pytest.approx(0, abs=1e-3)
|
||||
assert wall["pureHeight"] == pytest.approx(wall["autoHeight"], abs=1e-3)
|
||||
assert wall["autoHeight"] < wall["endHeight"]
|
||||
assert wall["bottomGap"] == pytest.approx(wall["endHeight"] - wall["autoHeight"], abs=1e-3)
|
||||
|
||||
|
||||
def test_높이를_적으면_윗면은_그대로_바닥이_따라감(shape: dict) -> None:
|
||||
for wall in shape["boxSet"]:
|
||||
assert wall["topGap"] == pytest.approx(0, abs=1e-3)
|
||||
@@ -88,6 +98,22 @@ def test_관_보호공_기본_자리는_제일_위(shape: dict) -> None:
|
||||
assert float(pipe["lengthM"]).is_integer()
|
||||
|
||||
|
||||
def test_저장_x_있는_관_보호공은_종전_자리_그대로(shape: dict) -> None:
|
||||
# d 빈 칸 기본 = 경사길이 4 m 지점(3.07) — x 원점 그대로라 절대 자리 안 바뀜(79b)
|
||||
stored = shape["pipeStoredX"]
|
||||
assert stored["x"] == pytest.approx(1.0)
|
||||
assert stored["d"] == pytest.approx(4.0 * 1.2 / (1 + 1.2**2) ** 0.5, abs=0.05)
|
||||
|
||||
|
||||
def test_저장_x_있는_독립_기슭막이_옹벽은_종전_자리_그대로(shape: dict) -> None:
|
||||
stored = shape["revetStoredX"]
|
||||
assert stored["x"] == pytest.approx(1.0)
|
||||
assert stored["d"] == pytest.approx(2.6) # 종전 기본 하강(3.07 / 1.2 → 0.1 눈금)
|
||||
run = 4.0 * 1.2 / (1 + 1.2**2) ** 0.5
|
||||
assert stored["shift"] == pytest.approx(run + 1.0, abs=1e-3)
|
||||
assert stored["shiftXD"] == pytest.approx(run + 1.0, abs=1e-3)
|
||||
|
||||
|
||||
def test_독립_기슭막이_옹벽_기본_자리는_제일_위(shape: dict) -> None:
|
||||
revet = shape["revet"]
|
||||
assert revet["d"] == 0
|
||||
|
||||
@@ -254,6 +254,9 @@ export const ui_locales_b2 = {
|
||||
B06_Cross_Revet_Outlet: ["기슭막이(유출)", "Revetment (outlet)"],
|
||||
/* 배관과 무관한 독립 기슭막이(구조물 정본 D군) — 2026-08-28. */
|
||||
B06_Cross_Revet_Own: ["기슭막이(독립)", "Revetment (standalone)"],
|
||||
/* 세월교 · BOX암거 유입 · 유출 흙막이 조정창(79b). */
|
||||
B06_Cross_Guard_Inlet: ["흙막이(유입)", "Retaining wall (inlet)"],
|
||||
B06_Cross_Guard_Outlet: ["흙막이(유출)", "Retaining wall (outlet)"],
|
||||
B06_Cross_Struct_Label: ["구조물 형식", "Structure type"],
|
||||
// 조정창 제목은 "기슭막이(…)" 한 형식으로 통일한다(2026-08-29 사용자). {n} = 단 번호.
|
||||
B06_Cross_Revet_Extra: ["기슭막이(추가 {n})", "Revetment (extra {n})"],
|
||||
|
||||
Reference in New Issue
Block a user