refactor(B06): 관 길이 m 맞춤 루프 = _Culvert_Fit.ts 로 나눔 · 노견 넓힌 쪽 = 사면 우선 + 유출 벽 높이 역산 갈래(139-6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
@@ -452,6 +452,9 @@ export interface CrossDesign {
|
||||
/** 곡선부 확폭(m) — 붙은 쪽만 값이 있다. */
|
||||
widening_left_m?: number;
|
||||
widening_right_m?: number;
|
||||
/** 설계선 쉬프트 = 쪽마다 노견 확장(m · ≥ 0 · 139-6) — 사용자 값 · 노체 · 측구 · 사면 · 구조물 같이. */
|
||||
shoulder_extend_left_m?: number;
|
||||
shoulder_extend_right_m?: number;
|
||||
cross_slope_pct: number;
|
||||
ditch:
|
||||
| { type: "standard"; top_width_m: number; bottom_width_m: number; depth_m: number }
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* =============================================================================
|
||||
* B06_Section_UI_Cross_Culvert_Fit.ts
|
||||
* 관 길이 **m 단위 맞춤** — 배수관 기하(`_Cross_Culvert_Geom.ts`)에서 700줄 제한으로 분리.
|
||||
*
|
||||
* · 보통(2026-08-21 사용자 ①) — 긴 변 기준 올림, 모자란 만큼 유출 벽 자리를 관 축 방향 바깥으로
|
||||
* 민다(벽 두께 0.45 · 높이 고정 · 벽 상단은 1:1.2 성토선을 탐).
|
||||
* · 노견을 넓힌 쪽(139-6 설계선 쉬프트) — 사면 우선: 벽을 관 축을 따라 옮기되 벽 상단이 그 쪽
|
||||
* 성토선(넓힌 노견에서 같은 물매)에 닿도록 **벽 높이를 0.1 m 눈금으로 역산**. 눈금 탓에 남는
|
||||
* 차는 표기 올림.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
FILL_SLOPE_MAX_LENGTH_M,
|
||||
FILL_SLOPE_RATIO_MAX,
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
REVET_HEIGHT_STEP_M,
|
||||
WALL_MIN_PURE_HEIGHT_M,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
|
||||
export interface OutletFitInput {
|
||||
/** 유출 쪽 노견을 넓혔나(139-6) — 높이 역산 갈래. */
|
||||
shifted: boolean;
|
||||
edge: { offset_m: number; elevation_m: number };
|
||||
outward: number;
|
||||
axis: () => OffsetPoint;
|
||||
thickness: number;
|
||||
anchor: OffsetPoint;
|
||||
/** 계산용 높이(순수 − 근입). */
|
||||
height: number;
|
||||
/** 보통 갈래의 invert 선(높이 고정 · 성토선 평행). */
|
||||
lineInvertAt: (offset: number) => number;
|
||||
slopeAt: (
|
||||
offset: number,
|
||||
thickness: number,
|
||||
height: number,
|
||||
) => { ratio: number; lengthM: number };
|
||||
cutLength: () => number;
|
||||
/** 벽을 새 자리 · 높이로 다시 세우고 관 축 길이 `target` 으로 겨눔 — 벽이 안 서면 false. */
|
||||
rebuild: (anchor: OffsetPoint, height: number, target: number) => boolean;
|
||||
}
|
||||
|
||||
/** 맞춤 뒤 벽 자리 · 높이. */
|
||||
export function fitOutletPipeLength(c: OutletFitInput): { anchor: OffsetPoint; height: number } {
|
||||
const target = Math.ceil(c.cutLength() - 1e-6);
|
||||
let anchor = c.anchor;
|
||||
let height = c.height;
|
||||
const ratio0 = c.slopeAt(anchor.offset, c.thickness, height).ratio;
|
||||
for (let pass = 0; pass < 8; pass += 1) {
|
||||
const gap = target - c.cutLength();
|
||||
if (Math.abs(gap) < 0.005) break;
|
||||
const axis = c.axis();
|
||||
const moved = anchor.offset + axis.offset * gap;
|
||||
// 노견 안쪽으로는 옮기지 않는다 — 길이 맞춤보다 도면 성립이 먼저.
|
||||
if ((moved - c.edge.offset_m) * c.outward < 0) break;
|
||||
if (c.shifted) {
|
||||
// 사면 우선 — invert 는 관 축을 따라 · 벽 상단 = 같은 물매 성토선 → 높이 역산(0.1 m 눈금)
|
||||
if (!Number.isFinite(ratio0)) break;
|
||||
const invert = anchor.elevation + axis.elevation * gap;
|
||||
const run = Math.abs(moved + c.outward * (c.thickness / 2) - c.edge.offset_m);
|
||||
const pure = c.edge.elevation_m - run / ratio0 - invert + REVET_EMBED_DEPTH_M;
|
||||
const stepped = Math.round(pure / REVET_HEIGHT_STEP_M) * REVET_HEIGHT_STEP_M;
|
||||
if (stepped < WALL_MIN_PURE_HEIGHT_M - 1e-9) break;
|
||||
const next = stepped - REVET_EMBED_DEPTH_M;
|
||||
if (Math.abs(next - height) < 1e-9 && Math.abs(moved - anchor.offset) < 0.01) break;
|
||||
anchor = { offset: moved, elevation: invert };
|
||||
height = next;
|
||||
if (!c.rebuild(anchor, height, target)) break;
|
||||
continue;
|
||||
}
|
||||
const movedSlope = c.slopeAt(moved, c.thickness, height);
|
||||
// 사면 역전(벽 상단이 노견 위)으로는 옮기지 않는다 · 성토고 0(성토선 0점)까지는 허용(2026-08-23).
|
||||
if (c.edge.elevation_m - (c.lineInvertAt(moved) + height) < 0) break;
|
||||
// 자동 자리만 옮긴다(수동 벽은 이 루프에 아예 안 들어온다). 폭은 0.45 고정 —
|
||||
// 폭으로 흡수하면 미는 동안 벽 단면이 변한다(2026-08-21 사용자 지적).
|
||||
if (
|
||||
movedSlope.ratio >= FILL_SLOPE_RATIO_MIN - 1e-6 &&
|
||||
movedSlope.ratio <= FILL_SLOPE_RATIO_MAX &&
|
||||
movedSlope.lengthM <= FILL_SLOPE_MAX_LENGTH_M + 1e-6
|
||||
) {
|
||||
anchor = { offset: moved, elevation: c.lineInvertAt(moved) };
|
||||
if (!c.rebuild(anchor, height, target)) break;
|
||||
continue;
|
||||
}
|
||||
// 물매·사면 5m를 벗어나면 멈춘다. 폭으로 흡수하지 않는다(좌·우 단면이 달라진다).
|
||||
break;
|
||||
}
|
||||
return { anchor, height };
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
revetWallSpec,
|
||||
pipeWallDefaultD,
|
||||
FILL_SLOPE_MAX_LENGTH_M,
|
||||
FILL_SLOPE_RATIO_MAX,
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
MIN_PIPE_COVER_M,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
@@ -58,6 +57,7 @@ import {
|
||||
} from "./B06_Section_UI_Cross_Culvert_Basin";
|
||||
import { inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
import { withBackCut } from "./B06_Section_UI_Cross_Structure_Cut";
|
||||
import { fitOutletPipeLength } from "./B06_Section_UI_Cross_Culvert_Fit";
|
||||
import {
|
||||
buildCulvertTiers,
|
||||
drawnFillPoints,
|
||||
@@ -492,76 +492,61 @@ export function computeCulvertLayout(
|
||||
outlet: endCorners("outlet", outlet),
|
||||
};
|
||||
|
||||
// ── 관 길이 m 단위 맞춤(2026-08-21 사용자 ①) — 긴 변 기준 올림, 모자란 만큼 유출
|
||||
// 벽 자리를 관 축 방향 바깥으로 민다(벽 두께 0.45 고정).
|
||||
// 정수 맞춤이 옮길 후보 자리도 같은 성토선을 탄다(2026-08-23 — 0점 기준 통일).
|
||||
const outletSlopeAt = outletSlopeFactory(outletInfo, outletLineInvertAt, outletWallSpec.height);
|
||||
/** 유출 벽 두께 — 항상 0.45 고정(2026-08-21 사용자 확정). 폭으로 길이를 맞추지 않는다. */
|
||||
const outletThickness = REVET_THICKNESS_M;
|
||||
// ── 관 길이 m 단위 맞춤 — `_Culvert_Fit`(보통 = 유출 벽을 관 축 바깥으로 · 노견을 넓힌
|
||||
// 쪽 = 사면 우선 + 벽 높이 역산 · 139-6). 정수 맞춤 후보 자리도 같은 성토선을 탄다(2026-08-23).
|
||||
const outletThickness = REVET_THICKNESS_M; // 늘 0.45 고정(2026-08-21) — 폭으로 길이를 맞추지 않음
|
||||
// 손으로 민 벽은 정수 맞춤이 **절대 옮기지 않는다**(2026-08-22) — 표기만 올림한다.
|
||||
// d = null(기본 자리)만 자동으로 본다(2026-08-23 — d는 0점 기준 절대값).
|
||||
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 => {
|
||||
walls.pop();
|
||||
outletWall = buildWall(
|
||||
culvert.outlet,
|
||||
outletWallAnchor,
|
||||
outletInfo.outward,
|
||||
null,
|
||||
outletThickness,
|
||||
{
|
||||
height: outletWallSpec.height,
|
||||
baseElevation: outletWallAnchor.elevation,
|
||||
floatGapM: Math.max(0, outletWallAnchor.elevation - invertAt(outletWallAnchor.offset)),
|
||||
},
|
||||
outletWallSpec.material,
|
||||
outletWallSpec.form,
|
||||
outletWallSpec.lean,
|
||||
);
|
||||
// 관 하단선은 옮겨진 벽의 **전면 기준선 교차점**을 지나야 한다(사용자 ①).
|
||||
const face = outletWall ? outletPipeEnd(outletWall) : outletWallAnchor;
|
||||
const runW = face.offset - pipeStart.offset;
|
||||
const riseW = face.elevation - pipeStart.elevation;
|
||||
const lenW = Math.hypot(runW, riseW) || 1;
|
||||
outlet.offset = pipeStart.offset + (runW / lenW) * target;
|
||||
outlet.elevation = pipeStart.elevation + (riseW / lenW) * target;
|
||||
deriveAxis();
|
||||
pipeCorners = {
|
||||
inlet: endCorners("inlet", pipeStart),
|
||||
outlet: endCorners("outlet", outlet),
|
||||
};
|
||||
};
|
||||
for (let pass = 0; pass < 8; pass += 1) {
|
||||
const gap = target - cutLength(pipeCorners);
|
||||
if (Math.abs(gap) < 0.005) break;
|
||||
// ① 벽 자리를 관 축 방향으로 gap 만큼 옮긴다(두께 불변 — 사용자 확정 우선순위).
|
||||
const moved = outletWallAnchor.offset + pipeAxis.axis.offset * gap;
|
||||
const movedSlope = outletSlopeAt(moved, outletThickness);
|
||||
// 사면 역전(벽 상단이 노견 위)·노견 안쪽으로는 옮기지 않는다 — 길이 맞춤보다
|
||||
// 도면 성립이 먼저다. 성토고 0(성토선 0점)까지는 허용(2026-08-23 사용자).
|
||||
if (
|
||||
(moved - outletInfo.edge.offset_m) * outletInfo.outward < 0 ||
|
||||
outletInfo.edge.elevation_m - (outletLineInvertAt(moved) + outletWallSpec.height) < 0
|
||||
) {
|
||||
break;
|
||||
}
|
||||
// 자동 자리만 옮긴다(수동 벽은 이 루프에 아예 안 들어온다). 폭은 0.45 고정 —
|
||||
// 폭으로 흡수하면 미는 동안 벽 단면이 변한다(2026-08-21 사용자 지적).
|
||||
if (
|
||||
movedSlope.ratio >= FILL_SLOPE_RATIO_MIN - 1e-6 &&
|
||||
movedSlope.ratio <= FILL_SLOPE_RATIO_MAX &&
|
||||
movedSlope.lengthM <= FILL_SLOPE_MAX_LENGTH_M + 1e-6
|
||||
) {
|
||||
outletWallAnchor = { offset: moved, elevation: outletLineInvertAt(moved) };
|
||||
rebuild();
|
||||
if (!outletWall) break;
|
||||
continue;
|
||||
}
|
||||
// ② 물매·사면 5m를 벗어나면 멈춘다. 폭으로 흡수하지 않는다(좌·우 단면이 달라진다).
|
||||
break;
|
||||
}
|
||||
const extend =
|
||||
section.design?.[`shoulder_extend_${outletInfo.outward > 0 ? "left" : "right"}_m`];
|
||||
fitOutletPipeLength({
|
||||
shifted: (extend ?? 0) > 1e-9,
|
||||
edge: outletInfo.edge,
|
||||
outward: outletInfo.outward,
|
||||
axis: () => pipeAxis.axis,
|
||||
thickness: outletThickness,
|
||||
anchor: outletWallAnchor,
|
||||
height: outletWallSpec.height,
|
||||
lineInvertAt: outletLineInvertAt,
|
||||
slopeAt: (offset, thickness, height) =>
|
||||
outletSlopeFactory(outletInfo, outletLineInvertAt, height)(offset, thickness),
|
||||
cutLength: () => cutLength(pipeCorners),
|
||||
rebuild: (anchor, height, target) => {
|
||||
outletWallAnchor = anchor;
|
||||
walls.pop();
|
||||
outletWall = buildWall(
|
||||
culvert.outlet,
|
||||
anchor,
|
||||
outletInfo.outward,
|
||||
null,
|
||||
outletThickness,
|
||||
{
|
||||
height,
|
||||
baseElevation: anchor.elevation,
|
||||
floatGapM: Math.max(0, anchor.elevation - invertAt(anchor.offset)),
|
||||
},
|
||||
outletWallSpec.material,
|
||||
outletWallSpec.form,
|
||||
outletWallSpec.lean,
|
||||
);
|
||||
if (!outletWall) return false;
|
||||
// 관 하단선은 옮겨진 벽의 **전면 기준선 교차점**을 지나야 한다(사용자 ①).
|
||||
const face = outletPipeEnd(outletWall);
|
||||
const runW = face.offset - pipeStart.offset;
|
||||
const riseW = face.elevation - pipeStart.elevation;
|
||||
const lenW = Math.hypot(runW, riseW) || 1;
|
||||
outlet.offset = pipeStart.offset + (runW / lenW) * target;
|
||||
outlet.elevation = pipeStart.elevation + (riseW / lenW) * target;
|
||||
deriveAxis();
|
||||
pipeCorners = {
|
||||
inlet: endCorners("inlet", pipeStart),
|
||||
outlet: endCorners("outlet", outlet),
|
||||
};
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
if (outletWall) {
|
||||
// 표기 길이 = **실제 그려진 관**의 올림값(목표를 적으면 도면보다 길게 적힌다).
|
||||
|
||||
Reference in New Issue
Block a user