Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -435,7 +435,7 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
paved: pavedBySpecs(section),
|
||||
// 물넘이 자리 = 측구 없음 · 지대 낮은 쪽 한 경사(286-2 · 287-4 · 서버 `tilt_ranges` 짝 = 얹힌 제원)
|
||||
tilted: Boolean(section.ford_pavement || section.underdrain),
|
||||
tiltPct: section.ford_pavement?.slope_pct ?? section.underdrain?.slope_pct ?? null,
|
||||
tiltPct: section.ford_pavement?.slope_pct ?? null, // 맹암거 = 표준 경사(371-3)
|
||||
standard,
|
||||
berm: bermAt(section.chainage_m, design),
|
||||
rockBoundaryOffsetM,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""구조물 자리 기울기 — 물넘이포장 · 맹암거 측점의 노면(287-4 · 288-1).
|
||||
|
||||
그 자리 횡단 설계 = 측구 없음 · 노면 = 한 경사로 지대 낮은 쪽(`compute_cross_design(tilted=…,
|
||||
tilt_pct=…)`). 경사 = 물넘이 경사각도(%) · 맹암거 암거 경사(%) · 비면 표준 횡단경사.
|
||||
tilt_pct=…)`). 경사 = 물넘이 경사각도(%) · 맹암거 = 표준 횡단경사(암거 경사는 형상 하단 · 371-3).
|
||||
· 물넘이포장 = 관 지점 정본(pipe_points) 범위(기준 ± 월류 폭/2).
|
||||
· 맹암거 = 구조물 정본(structures.json) A군 점형 · 기준 측점 한 곳 — 횡단 dict 에
|
||||
`underdrain`(높이 · 경사)을 얹어 횡단도가 돌 무늬 형상을 그림(TS 짝
|
||||
@@ -78,7 +78,8 @@ def tilt_ranges(project_root: Path | None) -> list[TiltRange]:
|
||||
half = float(spec.get("span_m") or 0.0) / 2.0
|
||||
out.append((at - half, at + half, spec.get("slope_pct")))
|
||||
for drain in load_underdrains(project_root):
|
||||
out.append((drain["chainage_m"], drain["chainage_m"], drain["slope_pct"]))
|
||||
# 노면 = 표준 경사 · 암거 경사는 맹암거 하단 경계만(371-3 · TS `facilityRanges` 짝)
|
||||
out.append((drain["chainage_m"], drain["chainage_m"], None))
|
||||
return out
|
||||
|
||||
|
||||
|
||||
@@ -313,12 +313,12 @@ export function appendCulvertOverlay(
|
||||
|
||||
const kindLabel = culvert.pipe_kind ? `${culvert.pipe_kind} ` : "";
|
||||
const wallThickness = pipeWallThicknessM(culvert.pipe_kind, diameter);
|
||||
const pipeInfo = `관매설 ${kindLabel}Φ${Math.round(diameter * 1000)}m/m, L=${pipeLengthText(pipe.lengthM)}`;
|
||||
// 관 기울기 = L 뒤 소수 한 자리 · 역경사는 0.0(371-2)
|
||||
const pipeInfo = `관매설 ${kindLabel}Φ${Math.round(diameter * 1000)}m/m, L=${pipeLengthText(pipe.lengthM)}, S=${Math.max(pipe.slopePct, 0).toFixed(1)}%`;
|
||||
// 관 정보 글 = 노폭 · 노견 라벨 위 줄(225-1 · B06 카드만 — 그 라벨 없는 덧그림엔 안 그림)
|
||||
if (!hidePipe) appendPipeInfoText(layer, pipeInfo, null);
|
||||
const tip =
|
||||
pipeInfo +
|
||||
` · 매설 경사 ${pipe.slopePct.toFixed(1)}%(수평 가능)` +
|
||||
` · 관 두께 ${(wallThickness * 1000).toFixed(1)}㎜(외경선 기준)` +
|
||||
` · 최소 토피 ${culvert.min_cover_m.toFixed(1)}m(별표2 복토 교차 참조)`;
|
||||
// 외경(관 두께 바깥) → 내경 순으로 2겹. 도면처럼 관벽 두께가 보이게 한다(사용자 ③).
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* (2026-08-22 사용자 확정 규칙):
|
||||
* · 기슭막이 사이 성토사면은 **정확히 1:1.2** · 자동 자리 = 사면 1.0 m 뒤 벽 상단(원지반 무관 · 91-3).
|
||||
* · 아랫단 벽 상단은 윗단 벽 **하단(수평 기초선)을 뚫고 올라갈 수 없다**.
|
||||
* · 다음 단 성토선 시작점 = 윗단 벽 **하단 수평선 +0.5m와 전면 경사선(1:n · 표준경사)의
|
||||
* 교차점**(배관 기슭막이의 관 하단 꼭짓점과 같은 자리 — 배관만 없다).
|
||||
* · 다음 단 성토선 시작점 = 윗단 벽 **발끝**(하단 수평선 ∩ 전면 경사선 · 원지반 무관 · 371-1).
|
||||
* 마지막 벽 → 끝 구간만 하단 수평선 +0.5m ∩ 전면(관 하단 꼭짓점과 같은 자리) · 뜬 벽은 발끝.
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
materialFromForm,
|
||||
revetFormOf,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
REVET_FILL_TOP_M,
|
||||
REVET_LEAN_RATIO,
|
||||
REVET_THICKNESS_M,
|
||||
WALL_MIN_PURE_HEIGHT_M,
|
||||
@@ -208,11 +209,25 @@ export function inletGroundConnector(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 벽 중심(하단 중점)에서 이음선 상단점까지의 수평거리 — 높이·기울기에 따라 커진다. */
|
||||
/** 벽 중심(하단 중점)에서 성토 위 이음까지의 수평거리 — 높이·기울기에 따라 커진다(371-1). */
|
||||
function jointRunOf(height: number, lean: number): number {
|
||||
return 0.25 * REVET_THICKNESS_M + (lean / 2) * height;
|
||||
return 0.75 * REVET_THICKNESS_M - REVET_FILL_TOP_M + (lean / 2) * height;
|
||||
}
|
||||
|
||||
/** 벽 아래 성토 시작점(371-1) — 아래 단이 있으면(`tiered`) 늘 발끝(전면 ∩ 하단 수평선 ·
|
||||
* 원지반 무관) · 끝 구간은 발끝이 원지반 위에 뜬 벽만 발끝 · 땅에 선 · 묻힌 벽은 `grounded`
|
||||
* (기준선 ∩ 전면 · 묻힘 판정 그대로). */
|
||||
export const floatFillStart = (
|
||||
wall: WallLayout,
|
||||
grounded: OffsetPoint,
|
||||
groundAt: (offset: number) => number,
|
||||
tiered: boolean,
|
||||
): OffsetPoint =>
|
||||
tiered ||
|
||||
(wall.floatGapM > 1e-6 && groundAt(wall.bottomFront.offset) < wall.bottomFront.elevation - 0.01)
|
||||
? wall.bottomFront
|
||||
: grounded;
|
||||
|
||||
/** 끝 성토부선 — src에서 1:1.2로 내려가며 원지반을 만나면 끝(지반이 높으면 지반 따름). */
|
||||
function trailingFill(
|
||||
src: OffsetPoint,
|
||||
@@ -359,22 +374,28 @@ export function buildOutletExtras(input: OutletExtrasInput): OutletExtrasResult
|
||||
? { offset: src.offset + outward * shelfRun, elevation: src.elevation }
|
||||
: null;
|
||||
const slopeFrom = shelf ?? src;
|
||||
const run = Math.abs(wall.topJoint.offset - slopeFrom.offset);
|
||||
const rise = slopeFrom.elevation - wall.topJoint.elevation;
|
||||
const run = Math.abs(wall.fillTop.offset - slopeFrom.offset);
|
||||
const rise = slopeFrom.elevation - wall.fillTop.elevation;
|
||||
segments.push({
|
||||
points: shelf ? [src, shelf, wall.topJoint] : [src, wall.topJoint],
|
||||
points: shelf ? [src, shelf, wall.fillTop] : [src, wall.fillTop],
|
||||
lengthM: Math.hypot(run, rise),
|
||||
ratio: rise > 1e-9 ? run / rise : FILL_SLOPE_RATIO_MIN,
|
||||
overLimit: Math.hypot(run, rise) >= FILL_SLOPE_MAX_LENGTH_M - 1e-6,
|
||||
});
|
||||
// 다음 단 시작점 = 이 벽 바닥 +0.5m(= 기준선) 수평선과 전면
|
||||
// 경사선(1:0.3)의 교차점(2026-08-22 사용자 — 관 끝점 규칙과 동일 기준).
|
||||
// 다음 단 시작점 = 벽 발끝(전면 경사 ∩ 하단 수평선 · 원지반 무관 · 371-1). 마지막 벽 →
|
||||
// 끝 구간은 바닥 +0.5m(= 기준선) ∩ 전면(2026-08-22 사용자) · 뜬 벽만 발끝.
|
||||
const startElevation = wall.bottomBack.elevation + REVET_EMBED_DEPTH_M;
|
||||
src = {
|
||||
offset:
|
||||
wall.points[2].offset + outward * wall.lean * (wall.topJoint.elevation - startElevation),
|
||||
elevation: startElevation,
|
||||
};
|
||||
src = floatFillStart(
|
||||
wall,
|
||||
{
|
||||
offset:
|
||||
wall.points[2].offset +
|
||||
outward * wall.lean * (wall.topJoint.elevation - startElevation),
|
||||
elevation: startElevation,
|
||||
},
|
||||
groundAt,
|
||||
i < input.adjusts.length - 1,
|
||||
);
|
||||
prevBottom = wall.bottomBack.elevation;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ import {
|
||||
BASIN_INNER_WIDTH_M,
|
||||
inletChoiceAvailability,
|
||||
} from "./logic/B06_Section_UI_Cross_Culvert_Basin";
|
||||
import { inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
import { floatFillStart, inletGroundConnector } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
import { withBackCut } from "./logic/B06_Section_UI_Cross_Structure_Cut";
|
||||
import {
|
||||
buildCulvertTiers,
|
||||
@@ -377,17 +377,17 @@ export function computeCulvertLayout(
|
||||
lean,
|
||||
roadElevation: side.elevation_m,
|
||||
});
|
||||
const topJoint = wall.topJoint.offset;
|
||||
const topElevation = wall.topJoint.elevation;
|
||||
const fillTop = wall.fillTop.offset;
|
||||
const topElevation = wall.fillTop.elevation;
|
||||
const topFront = wall.points[2].offset;
|
||||
const bottomFront = wall.bottomFront;
|
||||
walls.push(wall);
|
||||
// 성토 사면선은 **이음선 상단점**에서 끊고, 끝단 표고도 그 점에 맞춘다(사용자 ②).
|
||||
// 성토 사면선은 **성토 위 이음**에서 끊고, 끝단 표고도 그 점에 맞춘다(사용자 ② · 371-1).
|
||||
if (outward > 0) {
|
||||
trimMax = topJoint;
|
||||
trimMax = fillTop;
|
||||
trimMaxElevation = topElevation;
|
||||
} else {
|
||||
trimMin = topJoint;
|
||||
trimMin = fillTop;
|
||||
trimMinElevation = topElevation;
|
||||
}
|
||||
// 관 끝단 마감면 = 기슭막이의 **계류측 변**(전면)을 그대로 복사(사용자 ③).
|
||||
@@ -507,11 +507,24 @@ export function computeCulvertLayout(
|
||||
// 다단 흙막이(유출측 · 집수정 계류측) — `_Culvert_Tiers` 가 푼다.
|
||||
const { extras, basinExtras, extrasForArea, basinExtrasForArea } = buildCulvertTiers({
|
||||
section,
|
||||
outletStart: outletWall ? pipeCorners.outlet.bottom : null,
|
||||
outletStart: outletWall
|
||||
? floatFillStart(
|
||||
outletWall,
|
||||
pipeCorners.outlet.bottom,
|
||||
groundAt,
|
||||
(revetShift?.extras ?? []).length > 0,
|
||||
)
|
||||
: null,
|
||||
outletBottom: outletWall?.bottomBack.elevation ?? 0,
|
||||
basinStart: culvert.hidden_pipe
|
||||
? inletWall
|
||||
? (guardFloorStart(section, body, inletWall) ?? pipeCorners.inlet.bottom)
|
||||
? (guardFloorStart(section, body, inletWall) ??
|
||||
floatFillStart(
|
||||
inletWall,
|
||||
pipeCorners.inlet.bottom,
|
||||
groundAt,
|
||||
(revetShift?.basinExtras ?? []).length > 0,
|
||||
))
|
||||
: null
|
||||
: basinFillStart,
|
||||
basinBottom: culvert.hidden_pipe ? (inletWall?.bottomBack.elevation ?? 0) : basinFillBottom,
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import {
|
||||
GUARD_CUT_BERM_M,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
REVET_TRAP_TOP_M,
|
||||
REVET_FILL_TOP_M,
|
||||
} from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { OutletExtrasResult } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
import { buildExtrasAt } from "./B06_Section_UI_Cross_Culvert_Extra";
|
||||
@@ -26,7 +26,7 @@ export const slopeZeroAnchor = (
|
||||
outward: number,
|
||||
wallHeight: number,
|
||||
lean: number,
|
||||
): number => edge.offset_m + outward * (fillWallBaseWidth(wallHeight, lean) / 2 - REVET_TRAP_TOP_M);
|
||||
): number => edge.offset_m + outward * (fillWallBaseWidth(wallHeight, lean) / 2 - REVET_FILL_TOP_M);
|
||||
|
||||
/** 유출 관 하단 끝점 목표 = 벽 바닥 기준 0.5m 상단(기준선)과 **전면 경사선의
|
||||
* 교차점**(2026-08-22 사용자 ① — 성토부선 시작 규칙과 같은 자리). */
|
||||
@@ -106,7 +106,7 @@ export function buildCulvertTiers(input: {
|
||||
};
|
||||
const extras = buildExtrasAt(input.outletStart, outletInput);
|
||||
// 독립 기슭막이(관 숨김)는 집수정이 없어 이 채널이 **유입측 벽의 성토부선·다단**이다 —
|
||||
// 유출측(`extras`)과 같은 시작점(관 하단 꼭짓점)·같은 `bextra` 키(2026-08-29 사용자).
|
||||
// 유출측(`extras`)과 같은 시작점(`floatFillStart` · 371-1)·같은 `bextra` 키(2026-08-29 사용자).
|
||||
const basinInput = {
|
||||
cutBerm: cutBerm(culvert?.body ? (culvert.inlet.revet_berm_m ?? GUARD_CUT_BERM_M) : 0),
|
||||
startBottomElevation: input.basinBottom,
|
||||
|
||||
@@ -123,8 +123,10 @@ export interface WallLayout {
|
||||
points: OffsetPoint[];
|
||||
/** 상단 배면 꼭짓점 — 성토 사면선과의 접점(설계선 트림 경계). */
|
||||
topBack: OffsetPoint;
|
||||
/** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 성토 사면선이 여기서 끝난다. */
|
||||
/** 이음선 상단점(평행사변형↔사다리꼴 상단 교차점) — 안 무늬 이음선 자리. */
|
||||
topJoint: OffsetPoint;
|
||||
/** 성토 위 이음 — 윗면 사면 쪽 끝에서 0.1 m 안(371-1). 성토 사면선이 여기서 끝난다. */
|
||||
fillTop: OffsetPoint;
|
||||
/** 하단선 양 끝 — **수평 기초**(2026-08-22 사용자 확정): 발자국 최저 지반(또는 관
|
||||
* invert) − 0.5m 표고의 수평선. 전면 발끝은 1:0.3 경사선을 하단까지 연장한 자리다. */
|
||||
bottomBack: OffsetPoint;
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
PIPE_WALL_DEFAULT_RUN_M,
|
||||
REVET_EMBED_DEPTH_M,
|
||||
REVET_THICKNESS_M,
|
||||
REVET_FILL_TOP_M,
|
||||
WALL_MIN_PURE_HEIGHT_M,
|
||||
} from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
||||
import { outlineLeanOf } from "./logic/B06_Section_UI_Cross_Lean";
|
||||
@@ -208,7 +208,7 @@ export function computeRevetmentLayout(
|
||||
});
|
||||
const roadSlope = roadSlopePerOutward(design, side, outward);
|
||||
const autoOffset =
|
||||
edge.offset_m + outward * (fillWallBaseWidth(height, lean) / 2 - REVET_THICKNESS_M / 2);
|
||||
edge.offset_m + outward * (fillWallBaseWidth(height, lean) / 2 - REVET_FILL_TOP_M);
|
||||
// x 원점 = 성토사면(1:1.2) 경사길이 4m 지점 — 저장된 x 의 절대 자리 그대로(79b).
|
||||
const autoRun = PIPE_WALL_DEFAULT_RUN_M;
|
||||
/**
|
||||
@@ -285,7 +285,7 @@ export function computeRevetmentLayout(
|
||||
const walls: WallLayout[] = [tier1, ...extras.walls];
|
||||
// 물빼기 파이프 단면 = 표 형상칸.weep(322-1 · 옹벽)
|
||||
if (catalogRow(spec.type_id)?.shapeOpts.weep) for (const wall of walls) wall.retaining = true;
|
||||
const jt = tier1.topJoint;
|
||||
const jt = tier1.fillTop;
|
||||
// 노견 → (물매를 탄 노견 연장) → 벽 이음선 상단점. 연장 구간은 노면 물매 그대로 가고,
|
||||
// 꺾임점에서 성토 물매 1:1.2로 벽 상단에 닿는다(2026-08-29 사용자 확정).
|
||||
// D = 노견에서 벽 상단까지 바깥 방향 거리, R0 = 노견과 벽 상단의 낙차
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
* B06_Section_UI_Cross_Underdrain.ts
|
||||
* 맹암거 횡단 형상(288-1 · 요구 183-3).
|
||||
*
|
||||
* · 노면 = 설계 노면 — 서버 · TS 짝이 암거 경사로 지대 낮은 쪽 한 경사로 기울여 냄 · 측구 없음.
|
||||
* · 옵셋선 = 노면을 암거 높이만큼 아래로 같은 각도.
|
||||
* · 노면 = 설계 노면 — 서버 · TS 짝이 표준 경사로 지대 낮은 쪽 한 경사로 기울여 냄 · 측구 없음.
|
||||
* · 옵셋선(하단 경계) = 높은 쪽 노면 끝에서 암거 높이 아래 · 암거 경사로 낮은 쪽으로 내림(371-3) ·
|
||||
* 경사가 비면 노면과 같은 각도.
|
||||
* · 성토 쪽 = 옵셋선을 성토 사면과 만날 때까지 · 절토 쪽 = 절토 사면을 아래로 늘여 옵셋선과 만남.
|
||||
* · 양측절토(낮은 쪽 늘인 노면 `tilt_reach`) = 늘인 노면이 원지반과 만나는 점에서 수직으로 끊음(356-1).
|
||||
* · 노면 · 양쪽 선 · 옵셋선이 둘러싼 안 = 돌 무늬(306-1) — 켜마다 반 칸 엇갈리고 이웃과 겹친 돌 ·
|
||||
@@ -42,16 +43,18 @@ export function drawCrossedStones(group: SVGElement, frame: BandFrame, heightM:
|
||||
|
||||
type Edge = { offset_m: number; elevation_m: number };
|
||||
|
||||
/** 하단 경계(옵셋선) — 표고 = `z0 + grade · (o − o0)`. */
|
||||
type Bottom = { o0: number; z0: number; grade: number };
|
||||
|
||||
/** 한쪽 둘레(노면 끝 → 옵셋선과 만나는 자리) — `outward` = 바깥 부호(+1 좌 · −1 우). */
|
||||
function sidePath(
|
||||
edge: Edge,
|
||||
outward: 1 | -1,
|
||||
line: readonly Edge[],
|
||||
roadGrade: number,
|
||||
heightM: number,
|
||||
bottom: Bottom,
|
||||
): OffsetPoint[] {
|
||||
const start = { offset: edge.offset_m, elevation: edge.elevation_m };
|
||||
const offZ = (o: number) => edge.elevation_m + roadGrade * (o - edge.offset_m) - heightM;
|
||||
const offZ = (o: number) => bottom.z0 + bottom.grade * (o - bottom.o0);
|
||||
const outside = line
|
||||
.filter((p) => (p.offset_m - edge.offset_m) * outward > 1e-6)
|
||||
.sort((a, b) => (a.offset_m - b.offset_m) * outward);
|
||||
@@ -60,7 +63,8 @@ function sidePath(
|
||||
if (first.elevation_m >= edge.elevation_m) {
|
||||
// 절토 — 사면을 안쪽 아래로 늘여 옵셋선과 만남
|
||||
const k = (first.elevation_m - edge.elevation_m) / (first.offset_m - edge.offset_m);
|
||||
const d = Math.abs(k - roadGrade) < 1e-9 ? 0 : -heightM / (k - roadGrade);
|
||||
const g = bottom.grade;
|
||||
const d = Math.abs(k - g) < 1e-9 ? 0 : (offZ(edge.offset_m) - edge.elevation_m) / (k - g);
|
||||
const offset = edge.offset_m + d;
|
||||
return [start, { offset, elevation: offZ(offset) }];
|
||||
}
|
||||
@@ -83,25 +87,38 @@ function sidePath(
|
||||
return path;
|
||||
}
|
||||
|
||||
/** 하단 경계 — 높은 쪽(상류) 노면 끝 − 높이에서 암거 경사로 낮은 쪽으로 내림(371-3) · 경사가 비면 노면과 평행. */
|
||||
function bottomOf(left: Edge, right: Edge, heightM: number, slopePct: number | null): Bottom {
|
||||
const roadGrade = (left.elevation_m - right.elevation_m) / (left.offset_m - right.offset_m);
|
||||
const high = left.elevation_m >= right.elevation_m ? left : right;
|
||||
const low = high === left ? right : left;
|
||||
const grade =
|
||||
slopePct && slopePct > 0
|
||||
? (Math.sign(low.offset_m - high.offset_m) * -slopePct) / 100
|
||||
: roadGrade;
|
||||
return { o0: high.offset_m, z0: high.elevation_m - heightM, grade };
|
||||
}
|
||||
|
||||
/** 맹암거 형상 둘레 — 좌 노면 끝 → 우 노면 끝 → 우 둘레 → 옵셋선 → 좌 둘레(위로). 높이 없으면 null. */
|
||||
export function underdrainOutline(
|
||||
design: CrossDesign,
|
||||
heightM: number | null,
|
||||
slopePct: number | null = null,
|
||||
): OffsetPoint[] | null {
|
||||
const edges = design.road_edges;
|
||||
if (!edges || !(heightM && heightM > 0)) return null;
|
||||
const { left, right } = edges;
|
||||
const span = left.offset_m - right.offset_m;
|
||||
if (!(Math.abs(span) > 1e-9)) return null;
|
||||
const grade = (left.elevation_m - right.elevation_m) / span;
|
||||
const bottom = bottomOf(left, right, heightM, slopePct);
|
||||
// 양측절토(356-1) — 낮은 쪽 늘인 노면이 원지반과 만나는 점(tilt_reach)에서 끊음 · 너머 점 거름 → 꼬리가 수직으로 닫음
|
||||
const reach = design.tilt_reach;
|
||||
const outward = reach?.side === "left" ? 1 : -1;
|
||||
const line = (design.design_line ?? []).filter(
|
||||
(p) => !reach || (p.offset_m - reach.offset_m) * outward <= 1e-6,
|
||||
);
|
||||
const leftPath = sidePath(left, 1, line, grade, heightM);
|
||||
const rightPath = sidePath(right, -1, line, grade, heightM);
|
||||
const leftPath = sidePath(left, 1, line, bottom);
|
||||
const rightPath = sidePath(right, -1, line, bottom);
|
||||
return [
|
||||
{ offset: left.offset_m, elevation: left.elevation_m },
|
||||
...rightPath,
|
||||
@@ -117,7 +134,7 @@ export function appendUnderdrainOverlay(
|
||||
x: (offset: number) => number,
|
||||
y: (elevation: number) => number,
|
||||
): SVGGElement | null {
|
||||
const outline = spec ? underdrainOutline(design, spec.height_m) : null;
|
||||
const outline = spec ? underdrainOutline(design, spec.height_m, spec.slope_pct) : null;
|
||||
if (!spec || !outline) return null;
|
||||
const px = (p: OffsetPoint): [number, number] => [x(p.offset), y(p.elevation)];
|
||||
const points = outline.map((p) => px(p).join(",")).join(" ");
|
||||
@@ -136,15 +153,19 @@ export function appendUnderdrainOverlay(
|
||||
clip.append(clipShape);
|
||||
const stones = document.createElementNS(SVG_NS, "g");
|
||||
stones.setAttribute("clip-path", `url(#${clipId})`);
|
||||
const height = spec.height_m!;
|
||||
const offsets = outline.map((p) => p.offset);
|
||||
const [lo, hi] = [Math.min(...offsets), Math.max(...offsets)];
|
||||
const { left, right } = design.road_edges;
|
||||
const grade = (left.elevation_m - right.elevation_m) / (left.offset_m - right.offset_m);
|
||||
const roadZ = (o: number) => left.elevation_m + grade * (o - left.offset_m);
|
||||
const bottom = bottomOf(left, right, spec.height_m!, spec.slope_pct);
|
||||
const bottomZ = (o: number) => bottom.z0 + bottom.grade * (o - bottom.o0);
|
||||
// 돌 띠 = 노면 ~ 하단 경계(두께가 자리마다 다름 · 켜 수는 두꺼운 쪽)
|
||||
const height = Math.max(roadZ(lo) - bottomZ(lo), roadZ(hi) - bottomZ(hi), spec.height_m!);
|
||||
const at = (u: number, v: number): OffsetPoint => {
|
||||
const offset = lo + (hi - lo) * u;
|
||||
return { offset, elevation: roadZ(offset) - height * (v + 0.5) };
|
||||
const top = roadZ(offset);
|
||||
return { offset, elevation: top + (bottomZ(offset) - top) * (v + 0.5) };
|
||||
};
|
||||
const [ax, ay] = px(at(0, 0));
|
||||
const [bx, by] = px(at(1, 0));
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
REVET_EMBED_DEPTH_M,
|
||||
REVET_LEAN_RATIO,
|
||||
REVET_THICKNESS_M,
|
||||
WALL_FILL_TOP_INSET_M,
|
||||
WALL_MIN_PURE_HEIGHT_M,
|
||||
} from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
||||
import type { RevetMaterial } from "./logic/B06_Section_UI_Cross_Culvert_Const";
|
||||
@@ -104,6 +105,7 @@ export function buildRevetWallGeometry(input: RevetWallGeometryInput): WallLayou
|
||||
outward,
|
||||
topBack,
|
||||
topJoint: { offset: topJoint, elevation: topElevation },
|
||||
fillTop: { offset: topFront - outward * WALL_FILL_TOP_INSET_M, elevation: topElevation },
|
||||
bottomBack,
|
||||
bottomFront,
|
||||
points: [bottomBack, topBack, { offset: topFront, elevation: topElevation }, bottomFront],
|
||||
|
||||
@@ -63,8 +63,9 @@ export function facilityRanges(
|
||||
const depth = num(spec.overflow_depth_m) ?? 0;
|
||||
if (spec.type === "ford" && depth > 0) drops.push([at, depth]);
|
||||
}
|
||||
// 노면 = 표준 경사 · 암거 경사는 맹암거 하단 경계만(371-3 · 파이썬 `tilt_ranges` 짝)
|
||||
for (const drain of underdrainSpecsFrom(structures))
|
||||
tilt.push([drain.chainage_m, drain.chainage_m, drain.slope_pct]);
|
||||
tilt.push([drain.chainage_m, drain.chainage_m, null]);
|
||||
return { paved, tilt, drops };
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ export const REVET_LEAN_RATIO = 0.3;
|
||||
/** 사다리꼴(배면부) 상단 폭(m) — 띠 두께의 절반(2026-08-20 사용자 확정). */
|
||||
export const REVET_TRAP_TOP_M = REVET_THICKNESS_M / 2;
|
||||
|
||||
/** 성토 위 이음 = 벽 윗면 사면 쪽 끝에서 이만큼 안(m · 371-1). */
|
||||
export const WALL_FILL_TOP_INSET_M = 0.1;
|
||||
|
||||
/** 배면에서 성토 위 이음까지 수평거리(m) — 윗면 1.5t − 0.1(371-1). */
|
||||
export const REVET_FILL_TOP_M = REVET_THICKNESS_M * 1.5 - WALL_FILL_TOP_INSET_M;
|
||||
|
||||
/**
|
||||
* 집수정 상부 여유(m) — 관 상단에서 집수정 벽 상단까지의 간격. 관 벽(유입·유출)은
|
||||
* 여유 없이 벽 윗면 = 관 윗면(139-1 · `revetTargetHeight`).
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
FILL_SLOPE_RATIO_MIN,
|
||||
REVET_LEAN_RATIO,
|
||||
REVET_THICKNESS_M,
|
||||
REVET_TRAP_TOP_M,
|
||||
REVET_FILL_TOP_M,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import type {
|
||||
EndFace,
|
||||
@@ -144,10 +144,10 @@ export interface FillSlopeSegment {
|
||||
* 접점이 다시 어긋난다.
|
||||
*/
|
||||
export function fillSlopeOf(
|
||||
wall: { role: "inlet" | "outlet" | "extra"; topJoint: OffsetPoint; outward: number },
|
||||
wall: { role: "inlet" | "outlet" | "extra"; fillTop: OffsetPoint; outward: number },
|
||||
edge: { offset_m: number; elevation_m: number },
|
||||
): FillSlopeSegment {
|
||||
const to = wall.topJoint;
|
||||
const to = wall.fillTop;
|
||||
const rise = edge.elevation_m - to.elevation;
|
||||
const edgePoint: OffsetPoint = { offset: edge.offset_m, elevation: edge.elevation_m };
|
||||
if (!(rise > 1e-6)) {
|
||||
@@ -244,7 +244,7 @@ export function minShoulderWallOffset(
|
||||
const offset = edge.offset_m + outward * ((span * i) / steps);
|
||||
const rise = edge.elevation_m - (baseAt(offset) + height);
|
||||
if (rise < FILL_MIN_RISE_M) continue;
|
||||
const joint = offset - outward * (fillWallBaseWidth(height, lean) / 2 - REVET_TRAP_TOP_M);
|
||||
const joint = offset - outward * (fillWallBaseWidth(height, lean) / 2 - REVET_FILL_TOP_M);
|
||||
if (((joint - edge.offset_m) * outward) / rise < FILL_SLOPE_RATIO_MIN) continue;
|
||||
if ((offset - toeOffset) * outward > 1e-6) {
|
||||
const toeRise = edge.elevation_m - (baseAt(toeOffset) + height);
|
||||
|
||||
Reference in New Issue
Block a user