feat(B06): 세월교 · 박스 흙막이 횡단 그림 — 관보호공 흙막이와 같은 벽 · 자리 · 연동 · 경사(73-4)
- 유입 · 유출 구조 「흙막이」면 배관 흙막이와 같은 세트(관 숨김 · body)를 같은 측점 culvert 자리에 함께 얹음(파이썬 _guard_set ↔ TS guardSet 짝 · 거울 시험) - 자동 자리 = 벽 전면이 관(세월교) · 본체(박스) 끝 · 좌우 x 는 그 자리 기준(본체를 늘리면 벽도 따라감) · 상하 d 는 배관과 같음 - 연동 옆 측점 · 3D 연동 해제 자리는 본체 기준 x 를 절대값으로 넘겨 받음 · 3D 만료 해시에 흙막이 값 포함 - 설계선 트림 = 벽 선 쪽은 흙막이 · 안 선 쪽은 본체 · 벽 선 쪽 본체 성토선은 걷음(두 줄 방지) · 관 연장은 안 실음 - 좌측 폼 → 캐시: 세트 다시 짓기 · 높이 · 형태 · 추가 흙막이 상자 수 = 유출 다단 수 - [연동][경사] 회색 풀림(옆 측점에 벽이 서면 연동 · 구간 있으면 경사) — 배관과 같은 규칙 - 시험: test_73_4_body_guard(벽 전면 = 본체 끝 · 박스 +1 m 따라감 · 연동 옆 측점 벽 · 서버 Node 면적 = 화면) · 거울 시험 흙막이 칸 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -22,10 +22,12 @@ import {
|
||||
import type { RevetMaterial } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import { outlineLeanOf } from "./B06_Section_UI_Cross_Lean";
|
||||
import { buildRevetWallGeometry } from "./B06_Section_UI_Cross_Wall";
|
||||
import { bodyGuardAxes, bodyGuardBase, mergeGuardTrim } from "./B06_Section_UI_Cross_Body_Guard";
|
||||
import { ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type {
|
||||
BasinLayout,
|
||||
BasinAdjust,
|
||||
CulvertDesignTrim,
|
||||
CulvertLayout,
|
||||
EndFace,
|
||||
InletStructureChoice,
|
||||
@@ -152,6 +154,26 @@ export function computeCulvertLayout(
|
||||
};
|
||||
const inletWallSpec = pipeWallSpec(culvert.inlet, adjInlet);
|
||||
const outletWallSpec = pipeWallSpec(culvert.outlet, adjOutlet);
|
||||
// 세월교 · 박스 흙막이(73-4) — 자동 자리 = 본체 끝(x 0점 · d 기본값). 본체 없으면 배관 규칙.
|
||||
const body = culvert.body ? bodyGuardBase(section, groundSamples) : null;
|
||||
const bodyAxesOf = (role: "inlet" | "outlet", info: typeof inletInfo) => {
|
||||
const end = body?.ends[role];
|
||||
const spec = role === "inlet" ? inletWallSpec : outletWallSpec;
|
||||
if (!end) return null;
|
||||
return bodyGuardAxes(end, {
|
||||
autoOffset: slopeZeroAnchor(info.edge, info.outward, spec.height, spec.lean),
|
||||
outward: info.outward,
|
||||
height: spec.height,
|
||||
edgeElevation: info.edge.elevation_m,
|
||||
lean: spec.lean,
|
||||
thickness: REVET_THICKNESS_M,
|
||||
});
|
||||
};
|
||||
const inletBody = bodyAxesOf("inlet", inletInfo);
|
||||
const outletBody = bodyAxesOf("outlet", outletInfo);
|
||||
const onBody = (adjust: WallAdjust, axes: { x: number; d: number } | null): WallAdjust =>
|
||||
axes ? { ...adjust, x: axes.x + adjust.x, d: adjust.d ?? axes.d } : adjust;
|
||||
const minX = culvert.body ? Number.NEGATIVE_INFINITY : undefined;
|
||||
// 조정창 선택지 가용성 — 판정은 Basin이 맡는다.
|
||||
const inletOptions = inletChoiceAvailability({
|
||||
designAt,
|
||||
@@ -214,10 +236,11 @@ export function computeCulvertLayout(
|
||||
edgeElevation: inletInfo.edge.elevation_m,
|
||||
// 독립 기슭막이(관 숨김)는 관 토피·역경사 제약이 없다 — 유입도 노견까지 자유롭게 오르내린다.
|
||||
invertCap: culvert.hidden_pipe ? Number.POSITIVE_INFINITY : invertCap(inletInfo.edge),
|
||||
adjust: adjInlet,
|
||||
adjust: onBody(adjInlet, inletBody),
|
||||
defaultD: PIPE_WALL_DEFAULT_RUN_M,
|
||||
minX,
|
||||
});
|
||||
appliedAdjust.inlet.x = placed.x;
|
||||
appliedAdjust.inlet.x = placed.x - (inletBody?.x ?? 0);
|
||||
appliedAdjust.inlet.d = placed.d;
|
||||
wallVertical.inlet = {
|
||||
height: inletWallSpec.height,
|
||||
@@ -321,7 +344,8 @@ export function computeCulvertLayout(
|
||||
}
|
||||
// 구조 · 형태를 안 고른 배관 벽 = 그리지 않음(54-39 · 조정창 형태가 있으면 그 값)
|
||||
const pickedForm = (spec.role === "inlet" ? adjInlet : adjOutlet).m || spec.revet_form;
|
||||
if (!culvert.hidden_pipe && (spec.structure !== "기슭막이" || !pickedForm)) return null;
|
||||
if ((!culvert.hidden_pipe || culvert.body) && (spec.structure !== "기슭막이" || !pickedForm))
|
||||
return null;
|
||||
// 형상 = 공용 `buildRevetWallGeometry`(추가·사면안정 흙막이·옹벽과 한 벌). 높이는
|
||||
// vertical(계산용)이 들고 온다 — 형태별 높이 한계 없음, 윗면은 노면에서 멈춤.
|
||||
const height = vertical?.height ?? revetTargetHeight(diameter);
|
||||
@@ -401,10 +425,11 @@ export function computeCulvertLayout(
|
||||
edgeElevation: outletInfo.edge.elevation_m,
|
||||
// 독립 기슭막이는 양쪽 벽이 독립 — 유출을 유입 invert로 묶는 역경사 클램프를 뺀다.
|
||||
invertCap: culvert.hidden_pipe ? Number.POSITIVE_INFINITY : inlet.elevation,
|
||||
adjust: adjOutlet,
|
||||
adjust: onBody(adjOutlet, outletBody),
|
||||
defaultD: PIPE_WALL_DEFAULT_RUN_M,
|
||||
minX,
|
||||
});
|
||||
appliedAdjust.outlet.x = placed.x;
|
||||
appliedAdjust.outlet.x = placed.x - (outletBody?.x ?? 0);
|
||||
appliedAdjust.outlet.d = placed.d;
|
||||
wallVertical.outlet = {
|
||||
height: outletWallSpec.height,
|
||||
@@ -459,7 +484,7 @@ export function computeCulvertLayout(
|
||||
const outletThickness = REVET_THICKNESS_M;
|
||||
// 손으로 민 벽은 정수 맞춤이 **절대 옮기지 않는다**(2026-08-22) — 표기만 올림한다.
|
||||
// d = null(기본 자리)만 자동으로 본다(2026-08-23 — d는 0점 기준 절대값).
|
||||
const outletPinned = Math.abs(adjOutlet.x) > 1e-9 || adjOutlet.d != null;
|
||||
const outletPinned = !!outletBody || Math.abs(adjOutlet.x) > 1e-9 || adjOutlet.d != null;
|
||||
if (outletWall && !outletPinned) {
|
||||
const target = Math.ceil(cutLength(pipeCorners) - 1e-6);
|
||||
const rebuild = (): void => {
|
||||
@@ -608,6 +633,17 @@ export function computeCulvertLayout(
|
||||
extendTrimSlope(drawnFillPoints(extrasForArea), outletInfo.outward);
|
||||
extendTrimSlope(drawnFillPoints(basinExtrasForArea), inletInfo.outward);
|
||||
|
||||
const ownTrim: CulvertDesignTrim | null =
|
||||
walls.length || basin
|
||||
? {
|
||||
minOffset: trimMin,
|
||||
maxOffset: trimMax,
|
||||
...(trimMinElevation != null ? { minElevation: trimMinElevation } : {}),
|
||||
...(trimMaxElevation != null ? { maxElevation: trimMaxElevation } : {}),
|
||||
...(trimMinSlope ? { minSlope: trimMinSlope } : {}),
|
||||
...(trimMaxSlope ? { maxSlope: trimMaxSlope } : {}),
|
||||
}
|
||||
: null;
|
||||
const outletWallFinal = walls.find((wall) => wall.role === "outlet") ?? null;
|
||||
const outletSlope = outletWallFinal ? slopeOf(outletWallFinal) : null;
|
||||
|
||||
@@ -645,16 +681,8 @@ export function computeCulvertLayout(
|
||||
extras: extras.appliedAdjusts,
|
||||
basinExtras: basinExtras.appliedAdjusts,
|
||||
},
|
||||
designTrim:
|
||||
walls.length || basin
|
||||
? {
|
||||
minOffset: trimMin,
|
||||
maxOffset: trimMax,
|
||||
...(trimMinElevation != null ? { minElevation: trimMinElevation } : {}),
|
||||
...(trimMaxElevation != null ? { maxElevation: trimMaxElevation } : {}),
|
||||
...(trimMinSlope ? { minSlope: trimMinSlope } : {}),
|
||||
...(trimMaxSlope ? { maxSlope: trimMaxSlope } : {}),
|
||||
}
|
||||
: null,
|
||||
bodyX:
|
||||
inletBody || outletBody ? { inlet: inletBody?.x ?? 0, outlet: outletBody?.x ?? 0 } : null,
|
||||
designTrim: body && ownTrim ? mergeGuardTrim(ownTrim, body.trim, walls) : ownTrim,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user