fix(B06): 세월교 관 끝 꼭짓점 = 그 높이 흙막이 전면 · 3D 도 같은 자리 함수(89-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
This commit is contained in:
@@ -49,6 +49,7 @@ import {
|
||||
type WallPath,
|
||||
} from "./B05_Profile_UI_Corridor_Structures_Path";
|
||||
import { computeRevetmentLayout } from "../B06_Section/B06_Section_UI_Cross_Revetment";
|
||||
import { withoutGuardedFill } from "../B06_Section/B06_Section_UI_Cross_Body_Guard";
|
||||
|
||||
/** 스윕 프레임 하나 — 노선 위 한 지점의 중심 XY·좌향 단위벡터와 종단 표고 오프셋. */
|
||||
export interface StructureFrame {
|
||||
@@ -274,7 +275,8 @@ export function buildCorridorStructures(
|
||||
const solids: CorridorStructure[] = [];
|
||||
for (const section of crossSections) {
|
||||
const layout = culvertLayoutOf(section);
|
||||
const fordLayout = fordLayoutOf(section);
|
||||
// 흙막이가 선 쪽은 횡단과 같은 자리 함수로 — 측벽 걷고 관 · 바닥판이 흙막이까지(89-1)
|
||||
const fordLayout = withoutGuardedFill(fordLayoutOf(section), layout);
|
||||
// 독립 기슭막이(옛 D군 경로) — 측점에 배관/숨김 기슭막이 세트가 붙으면 그쪽(위
|
||||
// culvertLayoutOf)이 그린다. 아직 이관 안 된 정본만 이 옛 경로로 스윕한다
|
||||
// (2026-08-28 이관 이중그리기 방지 — 횡단도 가드와 같은 규칙).
|
||||
@@ -449,12 +451,11 @@ export function buildCorridorStructures(
|
||||
if (fordLayout) {
|
||||
// 구체는 월류 폭만큼 도로 방향으로 이어지고 기준 측점 전후로 절반씩 걸친다.
|
||||
const half = Math.max(fordLayout.ford.span_m, 0.5) / 2;
|
||||
const invertPoints = fordLayout.pipe.outer;
|
||||
const fordBore = pipeBore(
|
||||
fordLayout.ford.diameter_m,
|
||||
{ offset: invertPoints[3].offset, elevation: invertPoints[3].elevation },
|
||||
{ offset: invertPoints[2].offset, elevation: invertPoints[2].elevation },
|
||||
);
|
||||
// 관 끝 = 윗 꼭짓점 자리(흙막이 사면에서 안 튀어나옴 · 89-1) · 높이 = invert
|
||||
const [topLeft, topRight, invertRight, invertLeft] = fordLayout.pipe.outer;
|
||||
const pipeStart = { offset: topLeft.offset, elevation: invertLeft.elevation };
|
||||
const pipeEnd = { offset: topRight.offset, elevation: invertRight.elevation };
|
||||
const fordBore = pipeBore(fordLayout.ford.diameter_m, pipeStart, pipeEnd);
|
||||
// 관은 련수만큼 폭 안에 등간격 — 구멍도 관마다 하나씩 낸다(2026-08-25 사용자 ⑥).
|
||||
const count = Math.max(fordLayout.ford.pipe_count, 1);
|
||||
const pipeChainages = Array.from(
|
||||
@@ -518,7 +519,6 @@ export function buildCorridorStructures(
|
||||
),
|
||||
);
|
||||
// 관은 련수만큼 폭 안에 등간격으로 놓는다(단면엔 1개만 보이지만 실물은 여러 련).
|
||||
const invert = fordLayout.pipe.outer;
|
||||
for (const at of pipeChainages) {
|
||||
const frame = frameAt(at);
|
||||
solids.push({
|
||||
@@ -526,8 +526,8 @@ export function buildCorridorStructures(
|
||||
kind: "pipe",
|
||||
key: "pipe",
|
||||
pipe: {
|
||||
start: [invert[3].offset, invert[3].elevation],
|
||||
end: [invert[2].offset, invert[2].elevation],
|
||||
start: [pipeStart.offset, pipeStart.elevation],
|
||||
end: [pipeEnd.offset, pipeEnd.elevation],
|
||||
diameterM: fordLayout.ford.diameter_m,
|
||||
wallThicknessM: pipeWallThicknessM(
|
||||
fordLayout.ford.pipe_kind,
|
||||
|
||||
@@ -210,8 +210,12 @@ export function withoutGuardedFill<T extends BoxLayout | FordLayout>(
|
||||
if (!layout || !culvert?.culvert.body) return layout;
|
||||
const guarded = (outward: number): boolean => culvert.walls.some((w) => w.outward === outward);
|
||||
const mid = layout.label.at.offset;
|
||||
const reach = (points: OffsetPoint[], outward: number, offset: number): OffsetPoint[] =>
|
||||
points.map((p) => ((p.offset - mid) * outward > 0 ? { ...p, offset } : p));
|
||||
const reach = (
|
||||
points: OffsetPoint[],
|
||||
outward: number,
|
||||
offset: (p: OffsetPoint) => number,
|
||||
): OffsetPoint[] =>
|
||||
points.map((p) => ((p.offset - mid) * outward > 0 ? { ...p, offset: offset(p) } : p));
|
||||
if ("box" in layout && "topSlab" in layout) {
|
||||
const sides = layout.sides.map((side) =>
|
||||
guarded(side.role === "left" ? 1 : -1)
|
||||
@@ -226,7 +230,7 @@ export function withoutGuardedFill<T extends BoxLayout | FordLayout>(
|
||||
const to = wall?.bottomBack.offset ?? side.offset;
|
||||
if ((to - side.offset) * outward < 1e-6) continue;
|
||||
[topSlab, bottomSlab, barrel] = [topSlab, bottomSlab, barrel].map((p) =>
|
||||
reach(p, outward, to),
|
||||
reach(p, outward, () => to),
|
||||
);
|
||||
}
|
||||
const run =
|
||||
@@ -237,19 +241,19 @@ export function withoutGuardedFill<T extends BoxLayout | FordLayout>(
|
||||
const sides = ford.sides.map((side) =>
|
||||
guarded(side.outward) ? { ...side, parts: [], fillSegments: [], cutLine: null } : side,
|
||||
);
|
||||
// 관 · 바닥판이 흙막이까지 따라감 — 관 끝 = 흙막이 전면(관 invert 높이) · 바닥판 끝 = 배면(80)
|
||||
// 관 · 바닥판이 흙막이까지 따라감 — 관 끝 꼭짓점 = 그 높이의 흙막이 전면(사면 이용 · 89-1) ·
|
||||
// 아래 꼭짓점(invert)은 종전 자리라 1 m 맞춤 그대로 · 바닥판 끝 = 배면(80)
|
||||
let { pipe, slabBridge } = ford;
|
||||
for (const side of ford.sides) {
|
||||
const wall = culvert.walls.find((w) => w.role === side.role && w.outward === side.outward);
|
||||
if (!wall) continue;
|
||||
const front =
|
||||
wall.points[2].offset +
|
||||
wall.outward * wall.lean * (wall.topJoint.elevation - side.slabTopElevation);
|
||||
const faceAt = (p: OffsetPoint): number =>
|
||||
wall.points[2].offset + wall.outward * wall.lean * (wall.topJoint.elevation - p.elevation);
|
||||
pipe = {
|
||||
outer: reach(pipe.outer, side.outward, front),
|
||||
inner: reach(pipe.inner, side.outward, front),
|
||||
outer: reach(pipe.outer, side.outward, faceAt),
|
||||
inner: reach(pipe.inner, side.outward, faceAt),
|
||||
};
|
||||
slabBridge = reach(slabBridge, side.outward, wall.bottomBack.offset);
|
||||
slabBridge = reach(slabBridge, side.outward, () => wall.bottomBack.offset);
|
||||
}
|
||||
if (pipe === ford.pipe) return { ...ford, sides } as T;
|
||||
const [right, left] = pipe.outer.slice(2);
|
||||
|
||||
@@ -65,6 +65,8 @@ function pipeFit(edit) {
|
||||
const { ford, culvert } = computeStoredLayouts(section, list);
|
||||
const [right, left] = ford.pipe.outer.slice(2);
|
||||
const ends = { 1: left.offset, [-1]: right.offset };
|
||||
const [topLeft, topRight] = ford.pipe.outer;
|
||||
const tops = { 1: topLeft, [-1]: topRight };
|
||||
const slab = ford.slabBridge.map((p) => p.offset);
|
||||
const slabEnd = { 1: Math.max(...slab), [-1]: Math.min(...slab) };
|
||||
return {
|
||||
@@ -80,6 +82,13 @@ function pipeFit(edit) {
|
||||
role: side.role,
|
||||
guard: true,
|
||||
pipeGap: r3(ends[side.outward] - front),
|
||||
// 관 윗모서리 ↔ 그 높이의 흙막이 전면(사면 이용 · 89-1)
|
||||
topGap: r3(
|
||||
tops[side.outward].offset -
|
||||
(wall.points[2].offset +
|
||||
wall.outward * wall.lean * (wall.topJoint.elevation - tops[side.outward].elevation)),
|
||||
),
|
||||
lean: wall.lean,
|
||||
slabGap: r3(slabEnd[side.outward] - wall.bottomBack.offset),
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -47,6 +47,17 @@ def test_관은_흙막이_전면까지_바닥판은_배면까지(fit: dict) -> N
|
||||
assert side["slabGap"] == pytest.approx(0, abs=1e-3)
|
||||
|
||||
|
||||
def test_관_윗모서리도_흙막이_전면_사면에(fit: dict) -> None:
|
||||
"""89-1 — 흙막이 사면 이용: 윗모서리가 그 높이 전면 자리라 벽 밖으로 안 튀어나감."""
|
||||
leaned = 0
|
||||
for case in (fit[k] for k in ("both", "inletPlus1", "outletPlus2", "inletOnly")):
|
||||
for side in case["sides"]:
|
||||
if side["guard"]:
|
||||
assert side["topGap"] == pytest.approx(0, abs=1e-3)
|
||||
leaned += side["lean"] > 0
|
||||
assert leaned
|
||||
|
||||
|
||||
def test_흙막이를_옮기면_관이_그만큼_따라감(fit: dict) -> None:
|
||||
base = fit["both"]["lengthM"]
|
||||
assert fit["inletPlus1"]["lengthM"] == pytest.approx(base + 1, abs=1e-3)
|
||||
|
||||
Reference in New Issue
Block a user