Compare commits
11
Commits
f5dbb6e3dc
...
bfa2b3a97f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa2b3a97f | ||
|
|
8fcc4c9fbe | ||
|
|
a791f7f988 | ||
|
|
98023a717e | ||
|
|
403722e687 | ||
|
|
189474a507 | ||
|
|
31e1debcf6 | ||
|
|
0081353dd0 | ||
|
|
95dd1f0253 | ||
|
|
08a1bef47b | ||
|
|
22ccd1085f |
@@ -235,9 +235,6 @@ export const STATE_REGISTRY = {
|
||||
"design-stations": { bucket: "result", scope: "project" },
|
||||
"design-profile": { bucket: "result", scope: "project" },
|
||||
"design-footprint": { bucket: "result", scope: "project" },
|
||||
/** 소단 사본 — 구조물(소단)에서 셈한 구간 목록(`design_facility_lane` 만 씀 · 고침은 `structures` 칸).
|
||||
* 다시 셈에 실어 보냄 · 안 실으면 계획선을 고치는 순간 계단이 사라짐(3-9 · 477-4). */
|
||||
berm: { bucket: "result", scope: "route" },
|
||||
} as const satisfies Record<string, StateEntry>;
|
||||
|
||||
export type StateName = keyof typeof STATE_REGISTRY;
|
||||
|
||||
@@ -142,18 +142,12 @@ export interface CalcRequest {
|
||||
pick: (results: Record<string, unknown>) => unknown;
|
||||
}
|
||||
|
||||
/** F7 구조물 면적 · C4 소단 구간 — 등록부 `node:` 입력 그대로. */
|
||||
/** F7 구조물 면적 — 등록부 `node:` 입력 그대로. */
|
||||
export const areasRequest = (rows: unknown[]): CalcRequest => ({
|
||||
run: ["structure.layouts"],
|
||||
inputs: { detail: { cross_sections: rows } },
|
||||
pick: (results) => (results["structure.layouts"] as { areas: unknown }).areas,
|
||||
});
|
||||
export const bermRequest = (structures: unknown[]): CalcRequest => ({
|
||||
run: ["cross.berm_spans"],
|
||||
inputs: { berm: { structures, chainages: [] } },
|
||||
pick: (results) => (results["cross.berm_spans"] as { spans: unknown }).spans,
|
||||
});
|
||||
|
||||
export const trialRequest = (id: string, args: unknown[]): CalcRequest => ({
|
||||
run: [id],
|
||||
inputs: { args: { [id]: args } },
|
||||
|
||||
@@ -8,20 +8,10 @@
|
||||
* 입력 = 관 알림(배수유역도 · B06 관 목록) · 구조물(보기 고침 · 저장분) · 종류 표 · 측점 간격 · 추가 측점(가게)
|
||||
* 관 id 장부 = 관 고정 id 가 쥔 세션 id 를 그 자리로(411-B) · 헛 id 정리(411-E) — 어느 보기에서 고쳐도
|
||||
* 계획고 편집 옮김(사라진 측점 → 새 측점 · 213-2) = 사용자 고침만 · 노선 따라감(`follow`)은 안 옮김(계산 묶음 몫 · 349-0d)
|
||||
* 소단 사본(세션 `berm`) = 구조물 소단 구간 — 쓰는 자리 여기 하나(B05 만 열어도 · 같으면 안 씀 · 쓰면 cross 한 번 · 467)
|
||||
* ========================================================================== */
|
||||
|
||||
import { useDesignStore } from "./design_store";
|
||||
import { pageRouteId } from "./design_page_route";
|
||||
import { readState } from "../b_page_state";
|
||||
import {
|
||||
bermSpansFromStructures,
|
||||
writeBermSpans,
|
||||
type BermSpan,
|
||||
} from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { pipeIdAt, placePipeIds, prunePipeIds, withPipeIds } from "./design_pipe_ids";
|
||||
import { calcOnServerNow } from "./design_calc_sites";
|
||||
import { bermRequest, calcChecked, calcOnServerOnly } from "./design_calc_server";
|
||||
import { plannedIrregular } from "./design_structure_status";
|
||||
import { planStations } from "../../common_util/common_util_structure_station";
|
||||
import { followPipeReports } from "../../B05_Profile/logic/B05_Profile_Pipeline_Follow";
|
||||
@@ -99,7 +89,7 @@ export function createFacilityLane(
|
||||
let types: StructureType[] = [];
|
||||
let stored: StructureInstance[] | null = null;
|
||||
let structures: StructureInstance[] = [];
|
||||
let structuresKnown = false; // 구조물을 받기 전 = 소단 사본 안 씀(빈 목록으로 저장분 소단을 지우지 않게)
|
||||
let structuresKnown = false; // 구조물을 받기 전 = 측점 계획 모름(빈 목록으로 저장 시설 측점을 지우지 않게)
|
||||
/** 마지막 관 알림 — 노선 따라감 · 관 초안이 바뀌면 이것을 초안 측점으로 다시 맞춤. */
|
||||
let reported: LanePipe[] | null = null;
|
||||
let pipes: LanePipe[] = [];
|
||||
@@ -152,36 +142,6 @@ export function createFacilityLane(
|
||||
|
||||
const marks = (): StructureInstance[] => [...structures, ...pipesToStructureMarks(pipes)];
|
||||
|
||||
/** 소단 사본 = 지금 구조물의 소단 구간(467 · 사본 없는 동안 다시 셈 = 저장분 `design.berm`) · 같으면 안 씀 ·
|
||||
* 쓰면 저장 노선 cross 한 번 더(같은 값 측점은 다시 셈 기억이 건너뜀 · 묶음은 늦게 불러옴 · 순환 없음). */
|
||||
function syncBerm(): void {
|
||||
const routeId = pageRouteId(projectId);
|
||||
if (!structuresKnown || routeId === null) return;
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 값만(511-A) · 아니면 브라우저 셈
|
||||
if (calcOnServerNow("quantity")) {
|
||||
void calcOnServerOnly<BermSpan[]>("quantity", "C4.copy", bermRequest(structures)).then(
|
||||
(next) => next && putBerm(routeId, next),
|
||||
);
|
||||
return;
|
||||
}
|
||||
putBerm(
|
||||
routeId,
|
||||
calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [structures]),
|
||||
);
|
||||
}
|
||||
|
||||
function putBerm(routeId: number, next: BermSpan[]): void {
|
||||
const copy = readState<BermSpan[]>("berm", projectId, routeId);
|
||||
if (Array.isArray(copy) && JSON.stringify(copy) === JSON.stringify(next)) return;
|
||||
writeBermSpans(projectId, routeId, next);
|
||||
void Promise.all([import("./design_pipeline_cross"), import("./design_pipeline_page")])
|
||||
.then(([cross, page]) => {
|
||||
cross.forceCross();
|
||||
page.pipelineCross(projectId);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
/** 측점 계획 입력이 다 옴 — 모름(관 알림 전 · 구조물 받기 전)을 빈 목록으로 셈하면 종단도가 저장 시설 측점
|
||||
* (관 등)을 뺀 선으로 횡단을 다시 셈 · 유토 배지가 잠깐 틀림(473 · 요구 272). */
|
||||
const known = (): boolean => types.length > 0 && reported !== null && structuresKnown;
|
||||
@@ -234,14 +194,12 @@ export function createFacilityLane(
|
||||
}
|
||||
if (next.pipes) reported = [...next.pipes];
|
||||
refresh(carryEdits);
|
||||
if (next.structures) run(syncBerm);
|
||||
},
|
||||
follow() {
|
||||
const drafted = store.get("structures");
|
||||
structures = drafted ?? stored ?? [];
|
||||
structuresKnown ||= drafted !== null || stored !== null;
|
||||
refresh(false);
|
||||
run(syncBerm);
|
||||
},
|
||||
setIntervalM(next) {
|
||||
if (next > 0 && next !== intervalM) {
|
||||
|
||||
@@ -338,14 +338,13 @@ function createPagePipeline(
|
||||
groundPvi: groundPviAt, // 계산 자리 「서버」 = 서버 Node(511-B B-3)
|
||||
}),
|
||||
profile: profileAt,
|
||||
// 단면 유형 = 새 지형 상단측 · 소단 = 지금 구조물(365-5) · 저장 노선 = 다시 셈 창구 한 벌(365-4) · 엔진은
|
||||
// 창구 출력을 앞 결과로 안 이어 씀(초안 = 새로고침)
|
||||
// 단면 유형 = 상단측 · 소단 = 얹힌 제원(577-2) · 저장 노선 = 다시 셈 창구(365-4) · 엔진은 창구 출력 안 이어 씀
|
||||
cross: (ctx) => {
|
||||
const stations = ctx.out.ground?.stations ?? [];
|
||||
const status = statusOf(ctx.inputs.pipes, ctx.inputs.structures);
|
||||
const engine = () =>
|
||||
crossStage.stage({
|
||||
...contextCross(ctx, status, options.bermAt?.(ctx.inputs.structures) ?? null),
|
||||
...contextCross(ctx, status),
|
||||
prev: ctx.prev && savedOut.has(ctx.prev) ? null : ctx.prev,
|
||||
});
|
||||
// 계산 자리 cross = server — 엔진 길도 서버 Node 단계(같은 TS · 511-B B-4) · 못 받음 = 브라우저
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
AlignmentBase,
|
||||
AlignmentEdits,
|
||||
} from "../../B05_Profile/logic/B05_Profile_UI_Profile_Alignment";
|
||||
import type { BermAt } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
import type { SectionDetailResponse } from "../../B06_Section/B06_Section_Api_Fetch";
|
||||
import type { DesignStoreValues } from "./design_store";
|
||||
|
||||
@@ -61,8 +60,6 @@ export interface PagePipelineOptions {
|
||||
stationKeys?: (routeId: number, detail: SectionDetailResponse) => string[];
|
||||
/** 고리가 `RECONCILE_ROUNDS` 바퀴 안에 안 맞음 — 「계산 불안정」 한 줄(365-2). */
|
||||
onUnstable?: () => void;
|
||||
/** 소단 구간(지금 구조물 목록 → 누가거리마다 제원) — 초안 cross 가 따라간 구조물로 소단을 셈(365-5). */
|
||||
bermAt?: (structures: unknown) => BermAt;
|
||||
/** 저장 노선 cross = 다시 셈 창구 한 벌(365-4 · 측점이 상세와 다르면 null = 엔진) · 주면 종단도는 따로 안 셈. */
|
||||
savedCross?: (
|
||||
d: SectionDetailResponse,
|
||||
|
||||
@@ -59,8 +59,7 @@ import {
|
||||
refreshCrossDesigns,
|
||||
type CrossRefreshInput,
|
||||
} from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
import { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
import { areasRequest, bermRequest, calcChecked } from "./design_calc_server";
|
||||
import { areasRequest, calcChecked } from "./design_calc_server";
|
||||
import { registeredKeys, stationIntervalOf } from "../../B06_Section/B06_Section_Station_Keys";
|
||||
import { detailCross } from "../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
import type { CrossOutput, PipelineStation } from "./logic/design_pipeline_types";
|
||||
@@ -255,15 +254,6 @@ function startPagePipeline(shared: DesignPageShared, profile: DesignProfile): vo
|
||||
each((hooks) => hooks.onFacilitiesFollowed?.(far));
|
||||
},
|
||||
onUnstable: () => showToast(L("Design_Pipeline_Unstable"), "warning"),
|
||||
// 초안 소단 = 지금 구조물(따라간 자리)의 소단 구간(365-5 · 저장 노선은 다시 셈 창구가 세션 사본으로)
|
||||
bermAt: (structures) => {
|
||||
const list = Array.isArray(structures) ? structures : [];
|
||||
// 계산 자리 quantity = server 면 잠잠 뒤 서버 대조만(초안 미리보기 · 511-A)
|
||||
const spans = calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, [list], {
|
||||
request: bermRequest(list),
|
||||
});
|
||||
return (chainageM) => bermSpecAt(spans, chainageM);
|
||||
},
|
||||
// 저장 노선 횡단 = 다시 셈 창구 한 벌(365-4) — 카드 · 유토곡선 · [저장] 패치가 보는 상세 설계를 늘 고치고 그
|
||||
// 값을 묶음 출력으로(발자국 · 평면 횡단 판) · 측점이 상세와 다르면(갈아끼움 전) 묶음 엔진(439)
|
||||
savedCross: (detail, routeId, stations) =>
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
* ========================================================================== */
|
||||
|
||||
import { readFileSync, statSync } from "node:fs";
|
||||
import { bermSpansFromStructures, bermSpecAt } from "@util/common_util_berm_spans";
|
||||
import type { FollowingStructure } from "@util/common_util_route_follow";
|
||||
import { useCatalogDefinition, type CatalogDefinition } from "@util/common_util_structure_catalog";
|
||||
import type { PlanPipe, PlanStructure, PlanType } from "@util/common_util_structure_station";
|
||||
@@ -189,9 +188,7 @@ async function runStages(
|
||||
ctx.inputs.structures ?? savedStructures,
|
||||
types,
|
||||
);
|
||||
const list = Array.isArray(ctx.inputs.structures) ? ctx.inputs.structures : [];
|
||||
const spans = bermSpansFromStructures(list);
|
||||
return crossStage.stage(contextCross(ctx, status, (at) => bermSpecAt(spans, at)));
|
||||
return crossStage.stage(contextCross(ctx, status));
|
||||
},
|
||||
};
|
||||
if (stages.until === "follow") return followOnly(table, inputs);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from "../../../common_util/common_util_route_follow";
|
||||
import {
|
||||
crossWithContext,
|
||||
type BermAt,
|
||||
type CrossInput,
|
||||
type ShapeStatus,
|
||||
} from "../../../B05_Profile/logic/B05_Profile_Pipeline_Detail";
|
||||
@@ -45,11 +44,10 @@ export function placedFollowInputs(
|
||||
};
|
||||
}
|
||||
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림) · 단면 유형 기본값(상단측) · 소단(지금 구조물) 얹음(365-5 · 457). */
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림 · 소단 = 얹힌 제원 · 577-2) · 단면 유형 기본값(상단측) 얹음(365-5 · 457). */
|
||||
export const contextCross = (
|
||||
ctx: StageContext<"cross">,
|
||||
status: ShapeStatus | null,
|
||||
bermAt: BermAt | null,
|
||||
): StageContext<"cross"> => ({
|
||||
...ctx,
|
||||
inputs: {
|
||||
@@ -58,7 +56,6 @@ export const contextCross = (
|
||||
ctx.inputs.cross as CrossInput | null,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
status,
|
||||
bermAt,
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
* `resources/tester/test_346_2_pipeline_cross_page.py`.
|
||||
* crossWithUphill cross 단계 입력에 단면 유형 기본값(상단측)을 얹음 — 서버 노선 저장이 단면 유형 ·
|
||||
* 측구 방향을 새 지형으로 정하는 것과 같은 규칙(넘기는 칸 = 부르는 쪽 입력).
|
||||
* crossWithShapes 얹힌 시설 맥락(포장 · 기울임 · 하강) = 지금 현황을 새 측점에 다시 얹은 그림에서(457).
|
||||
* crossWithShapes 얹힌 시설 맥락(포장 · 기울임 · 하강 · 소단) = 지금 현황을 새 측점에 다시 얹은 그림에서(457 ·
|
||||
* 소단 = 얹힌 `berm` 제원 + 세션 쪽 깊이 · 577-2).
|
||||
* crossAtSeat 자리 칸(암 경계 · 2단 경사 · 손 상단측 · `seats`)은 측점이 그 자리 그대로일 때만 · 새 자리 =
|
||||
* 기본값(요구 275-3 · 판정 `sameSeat` · 서버 노선 저장 · Node 넘김과 같은 규칙).
|
||||
* draftSectionDetail ground · profile · cross → 상세(측점 · 지반 · 횡단 설계 · 그림 자료 · 카드 이름 = 지금
|
||||
@@ -113,38 +114,6 @@ export function crossWithUphill(
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
/** 소단 한 측점 제원(세션 `berm` 꼴 · 구조물 목록에서 폄). */
|
||||
export type BermAt = (
|
||||
chainageM: number,
|
||||
) => { width_m: number; interval_m: number; slope_deg: number; depth_m?: number } | null;
|
||||
|
||||
/** 측점마다 소단 = 지금 구조물(초안 따라간 자리)의 소단 구간 · 쪽 깊이는 세션 고침(`bermDepthLeftM` ·
|
||||
* `bermDepthRightM` · 측점 열쇠) — 노선 초안 동안 소단 고침도 묶음으로(365-5). 구간 밖 측점은 소단 없음. */
|
||||
export function crossWithBerm(
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
bermAt: BermAt | null,
|
||||
): CrossInput | null {
|
||||
if (!input || !bermAt) return input;
|
||||
const byKey: CrossInput["byKey"] = { ...input.byKey };
|
||||
for (const station of stations) {
|
||||
const { bermDepthLeftM, bermDepthRightM, ...rest } = input.byKey[station.key] ?? {};
|
||||
const at = bermAt(station.chainageM);
|
||||
byKey[station.key] = {
|
||||
...rest,
|
||||
berm: at && {
|
||||
widthM: at.width_m,
|
||||
intervalM: at.interval_m,
|
||||
slopeDeg: at.slope_deg,
|
||||
depthM: at.depth_m,
|
||||
depthLeftM: (bermDepthLeftM as number | undefined) ?? null,
|
||||
depthRightM: (bermDepthRightM as number | undefined) ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
const kindOf = (station: PipelineStation): SectionStation["kind"] =>
|
||||
station.key === "end:BP"
|
||||
? "bp"
|
||||
@@ -269,7 +238,8 @@ function shapeContext(section: CrossSection): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
/** 측점마다 시설 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강) = 지금 현황을 새 측점에 다시 얹은 그림에서(457). */
|
||||
/** 측점마다 시설 맥락(포장 · 물넘이 기울임 · 세월교 노면 하강 · 소단) = 지금 현황을 새 측점에 다시 얹은 그림에서
|
||||
* (457) · 소단 쪽 깊이 = 세션 고침(`bermDepthLeftM` · `bermDepthRightM` · 측점 열쇠 · 365-5) · 구간 밖 = 소단 없음. */
|
||||
export function crossWithShapes(
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
@@ -285,23 +255,31 @@ export function crossWithShapes(
|
||||
);
|
||||
const byKey: CrossInput["byKey"] = { ...input.byKey };
|
||||
stations.forEach((station, i) => {
|
||||
byKey[station.key] = { ...byKey[station.key], ...shapeContext(sections[i]) };
|
||||
const { bermDepthLeftM, bermDepthRightM, ...rest } = byKey[station.key] ?? {};
|
||||
const at = sections[i].berm;
|
||||
byKey[station.key] = {
|
||||
...rest,
|
||||
...shapeContext(sections[i]),
|
||||
berm: at && {
|
||||
widthM: at.width_m,
|
||||
intervalM: at.interval_m,
|
||||
slopeDeg: at.slope_deg,
|
||||
depthM: at.depth_m,
|
||||
depthLeftM: (bermDepthLeftM as number | undefined) ?? null,
|
||||
depthRightM: (bermDepthRightM as number | undefined) ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
return { ...input, byKey };
|
||||
}
|
||||
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림) · 단면 유형 기본값(상단측) · 소단(지금 구조물) 얹음 — 페이지 묶음 cross. */
|
||||
/** cross 단계 입력 = 시설 맥락(새 자리 그림 · 소단 포함) · 단면 유형 기본값(상단측) 얹음 — 페이지 묶음 cross. */
|
||||
export const crossWithContext = (
|
||||
input: CrossInput | null,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
status: ShapeStatus | null,
|
||||
bermAt: BermAt | null,
|
||||
): CrossInput | null =>
|
||||
crossWithBerm(
|
||||
crossWithUphill(crossWithShapes(crossAtSeat(input, stations), stations, status), stations),
|
||||
stations,
|
||||
bermAt,
|
||||
);
|
||||
crossWithUphill(crossWithShapes(crossAtSeat(input, stations), stations, status), stations);
|
||||
|
||||
/** 묶음 출력 → 상세. 저장 상세의 종단 머리(이름)는 그대로 · 측점 · 지반 · 횡단 · 계획선 표본은 새 노선
|
||||
* (십자선 대체 높이도 초안 종단 · 413) · 그림 자료 · 카드 이름 = `status`(지금 현황)를 새 측점에 다시 얹음(457). */
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* 평면 발자국 — 측점 횡단 설계 결과를 평면 선으로(도로 끝 좌 · 우 · 비탈 끝 깎기 · 쌓기 · 눈금) (346-2)
|
||||
*
|
||||
* ⚠⚠ 짝: `B07_DesignDetail/B07_DesignDetail_Engine_Cad_PlanSlope.py`
|
||||
* (`route_frame` · `route_normal` · `side_is_cut` · `slope_end` · `slope_pieces` 와 그 아래
|
||||
* 단조 3차 보간 · 촘촘히 · 접힘 버림 · 353-1 · 모서리 둥글림 · 364-3) — 한쪽만 고치면
|
||||
* (`route_frame` · `route_normal` · `side_is_cut` · `slope_end` · `final_slope_end` ·
|
||||
* `slope_pieces` 와 그 아래 단조 3차 보간 · 촘촘히 · 접힘 버림 · 353-1 · 모서리 둥글림 · 364-3) — 한쪽만 고치면
|
||||
* 종단 · 횡단 편집 중 평면과 B07 계획평면도가 갈린다.
|
||||
* `footprintRecord` = `B07_DesignDetail_Router_Support_Plan.slope_records` 의 측점 한 줄.
|
||||
* 거울 시험: `resources/tester/test_346_2_plan_footprint_mirror.py`
|
||||
@@ -35,6 +35,9 @@ export interface FootprintStation {
|
||||
right?: FootprintPoint | null;
|
||||
} | null;
|
||||
design_line?: FootprintPoint[] | null;
|
||||
/** 구조물 처리 뒤 비탈 끝(B06 `STRUCTURE_ROW_KEYS` · 벽 측점만 · 578-2). */
|
||||
slope_end_left_m?: number | null;
|
||||
slope_end_right_m?: number | null;
|
||||
} | null;
|
||||
samples: FootprintPoint[];
|
||||
}
|
||||
@@ -156,6 +159,20 @@ export function slopeEnd(
|
||||
return end;
|
||||
}
|
||||
|
||||
/** 구조물 처리 뒤 비탈 끝 — 벽 측점은 B06 이 얹은 `slope_end_<쪽>_m`(578-2) · 없으면 설계선 끝.
|
||||
* 짝: `final_slope_end`. */
|
||||
export function finalSlopeEnd(
|
||||
design: NonNullable<FootprintStation["design"]>,
|
||||
samples: FootprintPoint[],
|
||||
edge: number,
|
||||
side: Side,
|
||||
): number {
|
||||
const stored = design[`slope_end_${side}_m`];
|
||||
return typeof stored === "number"
|
||||
? stored
|
||||
: slopeEnd(design.design_line ?? [], samples, edge, side);
|
||||
}
|
||||
|
||||
/** 측점 한 자리 → `slope_pieces` 줄 · 도로 끝이 없으면 null. 짝: `slope_records` 한 줄. */
|
||||
export function footprintRecord(station: FootprintStation): FootprintRecord | null {
|
||||
const edges = station.design?.road_edges;
|
||||
@@ -163,14 +180,14 @@ export function footprintRecord(station: FootprintStation): FootprintRecord | nu
|
||||
const right = Number(edges?.right?.offset_m);
|
||||
if (edges?.left?.offset_m == null || edges?.right?.offset_m == null) return null;
|
||||
if (!Number.isFinite(left) || !Number.isFinite(right)) return null;
|
||||
const line = station.design?.design_line ?? [];
|
||||
const design = station.design ?? {};
|
||||
const samples = station.samples ?? [];
|
||||
const record: FootprintRecord = {
|
||||
chainage_m: station.chainage_m,
|
||||
left_edge_m: left,
|
||||
left_end_m: slopeEnd(line, samples, left, "left"),
|
||||
left_end_m: finalSlopeEnd(design, samples, left, "left"),
|
||||
right_edge_m: right,
|
||||
right_end_m: slopeEnd(line, samples, right, "right"),
|
||||
right_end_m: finalSlopeEnd(design, samples, right, "right"),
|
||||
};
|
||||
for (const side of SIDES) {
|
||||
const elevation = edges[side]?.elevation_m;
|
||||
|
||||
@@ -62,6 +62,7 @@ import type { FordPavementSpec } from "./logic/B06_Section_Cross_Ford_Pavement";
|
||||
import type { RevetmentSpec } from "./logic/B06_Section_Cross_Revetment_Geom";
|
||||
import type { PavementEntry } from "@util/common_util_structure_pavement";
|
||||
import type { UnderdrainSpec } from "@util/common_util_structure_underdrain";
|
||||
import type { BermSessionSpec } from "@util/common_util_berm_spans";
|
||||
export type { FordPavementSpec, RevetmentSpec };
|
||||
|
||||
export interface SectionContextResponse {
|
||||
@@ -343,6 +344,8 @@ export interface CrossSection extends SectionStation {
|
||||
pavement?: PavementEntry[];
|
||||
/** 맹암거 — 기준 측점(288-1). 높이 · 경사 · 측구 없음 · 노면 기울임 · 돌 무늬 형상. */
|
||||
underdrain?: UnderdrainSpec;
|
||||
/** 소단 — 덮는 구간 제원(577-2 · null = 얹었는데 없음 · 칸 없음 = 아직 안 얹음 → 저장분 `design.berm`). */
|
||||
berm?: BermSessionSpec | null;
|
||||
/** 빈칸 경고(323-4 · 읽기 칸 · 저장 안 함) — 「종류 칸」(형상 필수 칸이 빈 관 · 구조물 · 측점 계획 자리). */
|
||||
blank?: string[];
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@
|
||||
import type { StandardCrossSectionSpec } from "@util/common_util_cross_design";
|
||||
import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { readCutSlopeSession, readRockBoundarySession } from "./B06_Section_UI_Page_Persist";
|
||||
import type { BermSpan } from "./B06_Section_UI_Page_Persist";
|
||||
import { readState } from "../A00_Common/b_page_state";
|
||||
import { USER_TOUCHED_KEYS } from "./logic/B06_Section_Design_Keys";
|
||||
import { savedDesigns } from "./B06_Section_Section_Canon";
|
||||
import {
|
||||
@@ -228,17 +226,14 @@ function gatherRefresh(input: CrossRefreshInput): RefreshInputs | null {
|
||||
const standard = effectiveStandardCross(projectId) as StandardCrossSectionSpec | null;
|
||||
if (!standard) return null; // 컨텍스트를 아직 못 받음 — 받은 뒤 다시 셈
|
||||
const { routeId } = input;
|
||||
// 소단 사본 없음(시설 줄이 구조물을 받기 전) = 저장분 `design.berm`(파이썬 `load_berm_spans` 못 읽음 →
|
||||
// 저장분 · `stored_berm` 짝 · 467)
|
||||
const bermCopy = readState<BermSpan[]>("berm", projectId, routeId);
|
||||
return {
|
||||
alignment,
|
||||
standard,
|
||||
rockOffsets: [...readRockOffsets(projectId, routeId)],
|
||||
rockDefault: readRockBoundaryDefault(projectId),
|
||||
// 측점별 암 절토 경사 · 소단 · 카드 선택 · 노견 확장 = 세션에만 있는 값 — **계산 전에** 실음(2026-09-07 · 3-9)
|
||||
// 측점별 암 절토 경사 · 소단 깊이 · 카드 선택 · 노견 확장 = 세션에만 있는 값 — **계산 전에** 실음(2026-09-07 ·
|
||||
// 3-9) · 소단 제원 = 측점에 얹힌 `section.berm`(577-2)
|
||||
cutSlopes: [...readCutSlopeRatios(projectId, routeId)],
|
||||
bermSpans: Array.isArray(bermCopy) ? bermCopy : null,
|
||||
bermDepths: [...readBermDepths(projectId, routeId)],
|
||||
choices: [...crossDesignChoices(projectId, routeId)],
|
||||
shoulderExtends: [...readShoulderExtends(projectId, routeId)],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""형상 얹기 한 벌(323-4) — 측점(횡단 dict) + 현황(관 · 구조물) + 구조물 표 → 그 측점 그림 자료.
|
||||
|
||||
구조물 열쇠(`SHAPE_KEYS` · 빈칸 경고 `blank`)를 걷고 차례대로 다시 얹음 — 관 세트 → 벽 → 포장 → 맹암거 →
|
||||
빈칸 경고. 고르기 = 표 형상(322-1). 서버 상세 읽기(`_read_section_detail`) · 재생성 응답이 이것 하나를 씀.
|
||||
소단 → 빈칸 경고. 고르기 = 표 형상(322-1). 서버 상세 읽기(`_read_section_detail`) · 재생성 응답이 이것 하나를 씀.
|
||||
셈 = 늘 떠 있는 Node `facility.attach`(화면과 같은 TS · 511-B B-4) ·
|
||||
파이썬 몫(`_attach_python`)은 Node 를 못 부를 때만.
|
||||
TS 짝 = `common_util/common_util_structure_attach.ts` `attachShapes`(화면 = 초안 ?? 저장분 현황) ·
|
||||
@@ -18,7 +18,12 @@ from typing import Any
|
||||
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from B06_Section.B06_Section_Engine_Culvert_Sets import attach_culvert_specs, culvert_sets_from
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import attach_wall_specs, wall_specs_from
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import (
|
||||
attach_berm_specs,
|
||||
attach_wall_specs,
|
||||
berm_spans_from,
|
||||
wall_specs_from,
|
||||
)
|
||||
from B06_Section.B06_Section_Engine_Tilt import attach_underdrain_specs, underdrains_from
|
||||
from common_util.common_util_structure_catalog import catalog_row, field_name, type_ids_with
|
||||
from common_util.common_util_structure_pavement import (
|
||||
@@ -35,7 +40,16 @@ from common_util.common_util_structure_station import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: 측점 그림 자료 열쇠 — TS `common_util_structure_shape_keys.SHAPE_KEYS` 짝.
|
||||
SHAPE_KEYS = ("culvert", "ford", "box", "ford_pavement", "revetment", "pavement", "underdrain")
|
||||
SHAPE_KEYS = (
|
||||
"culvert",
|
||||
"ford",
|
||||
"box",
|
||||
"ford_pavement",
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
)
|
||||
|
||||
|
||||
def _blank_labels(type_id: str, options: dict | None, types: dict[str, Any]) -> list[str]:
|
||||
@@ -128,6 +142,7 @@ def _attach_python(
|
||||
pavement_specs_from([s.model_dump() for s in structures], type_ids_with(shape="pavement")),
|
||||
)
|
||||
attach_underdrain_specs(sections, underdrains_from(structures))
|
||||
attach_berm_specs(sections, berm_spans_from(structures))
|
||||
_attach_blanks(sections, pipes, structures)
|
||||
return misses
|
||||
|
||||
|
||||
@@ -110,11 +110,10 @@ def wall_specs_from(structures: list[Any]) -> list[dict[str, Any]]:
|
||||
return found
|
||||
|
||||
|
||||
def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, float]]] | None:
|
||||
"""구조물 정본의 소단 구간 `[(시작, 끝, 제원)]` — [저장] 서버 다시 셈의 소단 원천(194-1).
|
||||
def berm_spans_from(structures: list[Any]) -> list[tuple[float, float, dict[str, float]]]:
|
||||
"""구조물 목록 → 소단 구간 `[(시작, 끝, 제원)]`(형상 얹기 한 벌 577-2 · [저장] 194-1).
|
||||
|
||||
짝: TS `bermSpansFromStructures`(같은 규칙 — 폭 0 = 없음 · 깊이 빈 값 = 기본 1.0 · 0 미만 = 0).
|
||||
못 읽으면 None(부르는 쪽이 저장분 `design.berm` 으로 물러섬).
|
||||
"""
|
||||
from common_util.common_util_cross_berm import (
|
||||
BERM_DEFAULT_DEPTH_M,
|
||||
@@ -130,35 +129,41 @@ def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, fl
|
||||
return fallback
|
||||
return value if math.isfinite(value) and value >= 0 else fallback
|
||||
|
||||
spans: list[tuple[float, float, dict[str, float]]] = []
|
||||
for item in structures:
|
||||
if (catalog_row(item.type_id) or {}).get("shape") != "berm": # 표 형상(323-4)
|
||||
continue
|
||||
anchor = item.chainage_m if item.chainage_m is not None else item.start_m
|
||||
start = item.start_m if item.start_m is not None else anchor
|
||||
end = item.end_m if item.end_m is not None else anchor
|
||||
if start is None or end is None:
|
||||
continue
|
||||
options = item.options or {}
|
||||
width = number(options, "width_m", BERM_DEFAULT_WIDTH_M)
|
||||
if width <= 0:
|
||||
continue
|
||||
raw = options.get("depth_m")
|
||||
try:
|
||||
depth = BERM_DEFAULT_DEPTH_M if raw in (None, "") else max(float(raw), 0.0)
|
||||
except (TypeError, ValueError):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
if not math.isfinite(depth):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
spec = {
|
||||
"width_m": width,
|
||||
"interval_m": number(options, "interval_m", BERM_DEFAULT_INTERVAL_M),
|
||||
"slope_deg": number(options, "slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
||||
"depth_m": depth,
|
||||
}
|
||||
spans.append((float(min(start, end)), float(max(start, end)), spec))
|
||||
return spans
|
||||
|
||||
|
||||
def load_berm_spans(project_root: Path) -> list[tuple[float, float, dict[str, float]]] | None:
|
||||
"""구조물 정본의 소단 구간 — [저장] 서버 다시 셈의 소단 원천(194-1) · 못 읽으면 None(부르는 쪽이
|
||||
저장분 `design.berm` 으로 물러섬)."""
|
||||
try:
|
||||
spans: list[tuple[float, float, dict[str, float]]] = []
|
||||
for item in load_structures(str(project_root))[1]:
|
||||
if (catalog_row(item.type_id) or {}).get("shape") != "berm": # 표 형상(323-4)
|
||||
continue
|
||||
anchor = item.chainage_m if item.chainage_m is not None else item.start_m
|
||||
start = item.start_m if item.start_m is not None else anchor
|
||||
end = item.end_m if item.end_m is not None else anchor
|
||||
if start is None or end is None:
|
||||
continue
|
||||
options = item.options or {}
|
||||
width = number(options, "width_m", BERM_DEFAULT_WIDTH_M)
|
||||
if width <= 0:
|
||||
continue
|
||||
raw = options.get("depth_m")
|
||||
try:
|
||||
depth = BERM_DEFAULT_DEPTH_M if raw in (None, "") else max(float(raw), 0.0)
|
||||
except (TypeError, ValueError):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
if not math.isfinite(depth):
|
||||
depth = BERM_DEFAULT_DEPTH_M
|
||||
spec = {
|
||||
"width_m": width,
|
||||
"interval_m": number(options, "interval_m", BERM_DEFAULT_INTERVAL_M),
|
||||
"slope_deg": number(options, "slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
||||
"depth_m": depth,
|
||||
}
|
||||
spans.append((float(min(start, end)), float(max(start, end)), spec))
|
||||
return spans
|
||||
return berm_spans_from(load_structures(str(project_root))[1])
|
||||
except Exception: # noqa: BLE001 — 정본을 못 읽으면 저장분으로 물러섬
|
||||
logger.exception("B06 소단 구간을 읽지 못했습니다 (저장분 소단을 씀)")
|
||||
return None
|
||||
@@ -174,6 +179,24 @@ def berm_span_at(
|
||||
return None
|
||||
|
||||
|
||||
def attach_berm_specs(
|
||||
cross_sections: list[dict[str, Any]], spans: list[tuple[float, float, dict[str, float]]]
|
||||
) -> int:
|
||||
"""측점마다 소단 제원을 얹음(TS `attachBermSpecs` 짝 · 577-2) — 덮는 소단 없음 = None
|
||||
(얹었음 표시). 얹은 측점 수."""
|
||||
attached = 0
|
||||
for section in cross_sections:
|
||||
chainage = section.get("chainage_m")
|
||||
if isinstance(chainage, bool) or not isinstance(chainage, (int, float)):
|
||||
continue
|
||||
if not math.isfinite(chainage):
|
||||
continue
|
||||
spec = berm_span_at(spans, float(chainage))
|
||||
section["berm"] = dict(spec) if spec else None
|
||||
attached += 1 if spec else 0
|
||||
return attached
|
||||
|
||||
|
||||
def attach_wall_specs(cross_sections: list[dict[str, Any]], walls: list[dict[str, Any]]) -> int:
|
||||
"""구간 안 측점에 벽 제원을 얹는다(TS `attachWallSpecs` 짝). 얹은 개수를 돌려준다."""
|
||||
if not walls:
|
||||
|
||||
@@ -73,6 +73,8 @@ _AREA_KEYS = (
|
||||
"box_ground_slope_pct",
|
||||
"box_length_m",
|
||||
"ford_pipe_length_m",
|
||||
"slope_end_left_m",
|
||||
"slope_end_right_m",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import type { DesignViewSave } from "../A00_Common/design/design_shared";
|
||||
import { buildCrossPatches, type CrossPatchSources } from "./logic/B06_Section_UI_Page_Patches";
|
||||
import { clearState, readByKey, writeByKey, writeState } from "../A00_Common/b_page_state";
|
||||
import { clearState, readByKey, writeByKey } from "../A00_Common/b_page_state";
|
||||
import { kindChangedChainages } from "./B06_Section_Api_Culvert_Options";
|
||||
import {
|
||||
crossDesignChoices,
|
||||
@@ -30,15 +30,8 @@ import type { StandardCrossSection } from "./B06_Section_Api_Fetch";
|
||||
import type { RockBoundaryControl } from "./B06_Section_UI_Section_View";
|
||||
import type { CutSlopeControl } from "./B06_Section_UI_Cross_CutSlope";
|
||||
import { balloonOffsetsPayload } from "@util/common_util_mass_haul_balance_view";
|
||||
// 소단 구간(C4) — common_util 로 옮김 · 부르는 곳 그대로(다시 내보냄).
|
||||
import type { BermSpan } from "@util/common_util_berm_spans";
|
||||
export {
|
||||
bermSpansFromStructures,
|
||||
bermSpecAt,
|
||||
isBermType,
|
||||
type BermSessionSpec,
|
||||
type BermSpan,
|
||||
} from "@util/common_util_berm_spans";
|
||||
// 소단 종류 판정 — common_util 로 옮김 · 부르는 곳 그대로(다시 내보냄).
|
||||
export { isBermType } from "@util/common_util_berm_spans";
|
||||
import { readKeyed, writeKeyed, type KeyedState } from "./B06_Section_Station_Keys";
|
||||
|
||||
/**
|
||||
@@ -124,11 +117,6 @@ function putCell(
|
||||
}
|
||||
}
|
||||
|
||||
/** 소단 사본(결과 통 · 구조물에서 셈 · 477-4) — 쓰는 곳은 페이지 시설 줄 하나. */
|
||||
export function writeBermSpans(projectId: string, routeId: number, spans: BermSpan[]): void {
|
||||
writeState("berm", spans, projectId, routeId);
|
||||
}
|
||||
|
||||
/** 암 경계선 오프셋 저장소 — 값(세션 칸)과 조정창 제어기를 함께 낸다. */
|
||||
export interface RockBoundaryStore {
|
||||
/** 지금 세션 칸 — 측점키(누가거리 2자리) → 오프셋(m). `buildCrossPatches` 재료(새 Map). */
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* 순수 — 화면 · fetch · 세션 없음(세션 읽기는 부르는 쪽).
|
||||
* ========================================================================== */
|
||||
|
||||
import { bermSpecAt, type BermSessionSpec, type BermSpan } from "@util/common_util_berm_spans";
|
||||
import type { BermSessionSpec } from "@util/common_util_berm_spans";
|
||||
import {
|
||||
BERM_DEFAULT_DEPTH_M,
|
||||
BERM_DEFAULT_INTERVAL_M,
|
||||
@@ -42,8 +42,6 @@ export interface RefreshInputs {
|
||||
rockDefault: number | null;
|
||||
/** 측점별 암 절토 경사(0 = 표준값). */
|
||||
cutSlopes: Array<[number, number]>;
|
||||
/** 소단 사본(세션 `berm` · 없으면 null = 저장분 `design.berm`). */
|
||||
bermSpans: BermSpan[] | null;
|
||||
bermDepths: Array<[number, SideValues]>;
|
||||
/** 카드 선택(지반 · 측구 · 2단 경사 …). */
|
||||
choices: Array<[number, CrossDesignChoice]>;
|
||||
@@ -141,13 +139,12 @@ export function refreshDesigns(
|
||||
// 측점별 암 절토 경사도 세션에만 있는 값이다 — **계산 전에** 실어야 설계선이 새 경사로
|
||||
// 그려진다. 계산 뒤에 값만 베껴 붙이면 그림은 옛 경사, 숫자만 새것이 된다(2026-09-07).
|
||||
const cutSlopeRatios = new Map(inputs.cutSlopes);
|
||||
// 소단도 같은 성격 — 세션에만 있는 값이라 여기서 실어 주지 않으면 계획선을 고치는
|
||||
// 순간 계단이 사라진다(계획서 3-9). 사본 없음(시설 줄이 구조물을 받기 전) = 저장분 `design.berm`
|
||||
// (파이썬 `load_berm_spans` 못 읽음 → 저장분 · `stored_berm` 짝 · 467).
|
||||
const bermSpans = inputs.bermSpans;
|
||||
// 소단도 같은 성격 — 계산 전에 실어야 계획선을 고치는 순간 계단이 안 사라진다(계획서 3-9). 제원 = 측점에
|
||||
// 얹힌 소단(형상 얹기 한 벌 · 577-2) · 아직 안 얹음(칸 없음) = 저장분 `design.berm`(`stored_berm` 짝 · 467)
|
||||
const bermDepthAt = bermDepthReader(new Map(inputs.bermDepths));
|
||||
const bermAt = (chainageM: number, design: Record<string, unknown>): BermSpec | null => {
|
||||
const spec = bermSpans ? bermSpecAt(bermSpans, chainageM) : storedBermSpec(design.berm);
|
||||
const bermAt = (section: CrossSection, design: Record<string, unknown>): BermSpec | null => {
|
||||
const chainageM = section.chainage_m;
|
||||
const spec = section.berm === undefined ? storedBermSpec(design.berm) : section.berm;
|
||||
return spec
|
||||
? {
|
||||
widthM: spec.width_m,
|
||||
@@ -226,7 +223,7 @@ export function refreshDesigns(
|
||||
tilted: Boolean(section.ford_pavement || section.underdrain),
|
||||
tiltPct: section.ford_pavement?.slope_pct ?? null, // 맹암거 = 표준 경사(371-3)
|
||||
standard,
|
||||
berm: bermAt(section.chainage_m, design),
|
||||
berm: bermAt(section, design),
|
||||
rockBoundaryOffsetM,
|
||||
cutSlopeRatio: cutSlopeAt(section.chainage_m, design),
|
||||
twoStageSlope: choice?.two_stage_slope ?? Boolean(design.two_stage_slope),
|
||||
|
||||
@@ -33,6 +33,7 @@ import type {
|
||||
import { computeFordLayout, fordAdjustOf } from "./B06_Section_Cross_Ford_Geom";
|
||||
import type { FordLayout } from "./B06_Section_Cross_Ford_Geom";
|
||||
import { computeRevetmentLayout } from "./B06_Section_Cross_Revetment_Geom";
|
||||
import type { RevetmentLayout } from "./B06_Section_Cross_Revetment_Geom";
|
||||
|
||||
/** 같은 측점으로 볼 누가거리 오차(m) — 정본 반올림 자릿수보다 크게 잡는다. */
|
||||
const CHAINAGE_TOLERANCE_M = 0.02;
|
||||
@@ -163,6 +164,9 @@ export const STRUCTURE_ROW_KEYS = [
|
||||
"box_length_m",
|
||||
// B08 자재 집계표 세월교 관 한 련 길이(131) — 그린 관 길이 1 m 올림 · 같은 길로 실음.
|
||||
"ford_pipe_length_m",
|
||||
// 평면 비탈 끝(578-2) — 벽이 선 쪽 횡단 최종 모양이 땅과 만나는 오프셋 · 평면 발자국 · B07 계획평면도가 읽음.
|
||||
"slope_end_left_m",
|
||||
"slope_end_right_m",
|
||||
] as const;
|
||||
|
||||
/** 박스 두 끝(구체 끝) 원지반 경사(%) — 유입 − 유출 ÷ 수평거리. 본체는 늘 수평이라 원지반으로(110). */
|
||||
@@ -191,6 +195,28 @@ function fordPipeLengthM(ford: FordLayout): number | null {
|
||||
return length > 0 ? Math.ceil(length - 1e-6) : null;
|
||||
}
|
||||
|
||||
/** 벽(옹벽 · 흙막이 · 돌쌓기)이 선 쪽 — 구조물 처리 뒤 횡단이 땅과 만나는 오프셋(578-2).
|
||||
* 윤곽 = 트림 사면(노견 → 벽 이음) → 단마다 벽 앞(성토 위 이음 → 전면 발끝) · 사이 성토부선(3D 실루엣 차례).
|
||||
* 위에서 내려와 처음 땅에 닿는 자리 · 끝까지 안 닿으면(뜬 벽) 마지막 발끝 · 노견부터 땅 밑이면 null. */
|
||||
function wallSlopeEnd(section: CrossSection, own: RevetmentLayout): number | null {
|
||||
const groundAt = groundInterpolator(section.samples);
|
||||
const trim = own.designTrim;
|
||||
const chain = [...((own.side === "left" ? trim.maxSlope : trim.minSlope)?.points ?? [])];
|
||||
const segments = own.fillSegments.filter((segment) => segment.kind !== "cut");
|
||||
own.walls.forEach((wall, index) => {
|
||||
chain.push(wall.fillTop, wall.bottomFront, ...(segments[index]?.points ?? []));
|
||||
});
|
||||
if (!groundAt || chain.length < 2) return null;
|
||||
let [prev, above] = [chain[0], chain[0].elevation - groundAt(chain[0].offset)];
|
||||
if (above <= 0) return null;
|
||||
for (const point of chain.slice(1)) {
|
||||
const now = point.elevation - groundAt(point.offset);
|
||||
if (now <= 0) return prev.offset + ((point.offset - prev.offset) * above) / (above - now);
|
||||
[prev, above] = [point, now];
|
||||
}
|
||||
return prev.offset;
|
||||
}
|
||||
|
||||
/** 폐회로 면적 입력(설계선·지반선·트림) — 트림이나 설계선이 모자라면 null. */
|
||||
function areaInputOf(section: CrossSection, layouts: StoredLayouts) {
|
||||
const trim = trimOfLayouts(layouts);
|
||||
@@ -254,6 +280,11 @@ function areaRowOf(
|
||||
const fordLength =
|
||||
layouts.ford && isOwner(section.ford?.chainage_m) ? fordPipeLengthM(layouts.ford) : null;
|
||||
if (fordLength !== null) ownRow.ford_pipe_length_m = fordLength;
|
||||
// 벽 트림이 그려지는 측점만(관 · 세월교 · BOX 트림이 앞서면 그쪽 모양 · 578-2 범위 밖)
|
||||
const own = layouts.own;
|
||||
const wallEnd =
|
||||
own && trimOfLayouts(layouts) === own.designTrim ? wallSlopeEnd(section, own) : null;
|
||||
if (own && wallEnd !== null) ownRow[`slope_end_${own.side}_m`] = Number(wallEnd.toFixed(4));
|
||||
const pipeRow = Object.keys(ownRow).length > 1 ? ownRow : null;
|
||||
const areaInput = areaInputOf(section, layouts);
|
||||
if (!areaInput) return pipeRow;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
`_Engine_Cad_PlanKinds.py` 가 700줄을 넘어 떼어냄(141-1) — 파일 입출력 없음 · 좌표 = 사업지 m.
|
||||
|
||||
⚠⚠ 짝: `B05_Profile/logic/B05_Profile_PlanFootprint.ts`(route_frame · route_normal · side_is_cut ·
|
||||
slope_end · slope_pieces 와 그 아래 보간 · 촘촘히 · 접힘 버림 · 모서리 둥글림) — 종단 · 횡단
|
||||
편집 중 평면 발자국 · 한쪽만 고치면 화면과 계획평면도가 갈림.
|
||||
slope_end · final_slope_end · slope_pieces 와 그 아래 보간 · 촘촘히 · 접힘 버림 · 모서리 둥글림)
|
||||
— 종단 · 횡단 편집 중 평면 발자국 · 한쪽만 고치면 화면과 계획평면도가 갈림.
|
||||
거울 시험: `resources/tester/test_346_2_plan_footprint_mirror.py`
|
||||
"""
|
||||
|
||||
@@ -86,6 +86,19 @@ def slope_end(
|
||||
return end
|
||||
|
||||
|
||||
def final_slope_end(
|
||||
design: dict[str, Any], samples: list[dict[str, Any]], edge: float, side: str
|
||||
) -> float:
|
||||
"""구조물 처리 뒤 비탈 끝 — 벽 측점은 B06 이 얹은 `slope_end_<쪽>_m`(578-2) · 없으면 설계선 끝.
|
||||
|
||||
짝: `finalSlopeEnd`(TS) — 값은 B06 `STRUCTURE_ROW_KEYS` 가 서버 Node 로 셈해 설계 칸에 얹음.
|
||||
"""
|
||||
stored = design.get(f"slope_end_{side}_m")
|
||||
if isinstance(stored, (int, float)) and not isinstance(stored, bool):
|
||||
return float(stored)
|
||||
return slope_end(design.get("design_line") or [], samples, edge, side)
|
||||
|
||||
|
||||
def _split_runs(
|
||||
points: list[list[float]], flags: list[bool]
|
||||
) -> tuple[list[list[list[float]]], list[list[list[float]]]]:
|
||||
|
||||
@@ -42,10 +42,9 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import (
|
||||
curve_pieces,
|
||||
route_frame,
|
||||
side_is_cut,
|
||||
slope_end,
|
||||
slope_pieces,
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope import tick_step_m
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope import final_slope_end, tick_step_m
|
||||
from B07_DesignDetail.B07_DesignDetail_Router_Support_Basin import (
|
||||
_chunk_route,
|
||||
_hillshade_for_box,
|
||||
@@ -375,13 +374,13 @@ def slope_records(
|
||||
right_edge = float(edges["right"]["offset_m"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
continue
|
||||
line, samples = (design or {}).get("design_line") or [], source.get("samples") or []
|
||||
samples = source.get("samples") or []
|
||||
record: dict[str, Any] = {
|
||||
"chainage_m": float(chainage),
|
||||
"left_edge_m": left_edge,
|
||||
"left_end_m": slope_end(line, samples, left_edge, "left"),
|
||||
"left_end_m": final_slope_end(design, samples, left_edge, "left"),
|
||||
"right_edge_m": right_edge,
|
||||
"right_end_m": slope_end(line, samples, right_edge, "right"),
|
||||
"right_end_m": final_slope_end(design, samples, right_edge, "right"),
|
||||
}
|
||||
for side in ("left", "right"):
|
||||
elevation = edges[side].get("elevation_m")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* =============================================================================
|
||||
* common_util_berm_spans.ts
|
||||
* 소단 구간 — 구조물 목록(표 형상 berm)에서 구간 · 측점마다 제원(PLAN 472-4 C4 · 화면 파일
|
||||
* `B06_Section_UI_Page_Persist.ts` 에서 옮김 · 그쪽이 다시 내보냄).
|
||||
* 브라우저(시설 줄 · 다시 셈)와 서버 Node 입구(`design_calc_node` · 등록부 `cross.berm_spans`)가 같은 셈.
|
||||
* 파이썬 짝 = `B06_Section_Engine_Structures_Wall.py` `load_berm_spans` · `berm_span_at`
|
||||
* (Node 를 못 부를 때만 · 거울 시험 `test_272_berm_source.py`).
|
||||
* 소단 구간 — 구조물 목록(표 형상 berm)에서 구간 · 측점마다 제원(PLAN 472-4 C4) · 측점에 얹기(577-2 ·
|
||||
* 벽처럼 형상 얹기 한 벌 `attachShapes` 가 `section.berm` 을 세움 · 계단은 계산 안쪽 그대로).
|
||||
* 브라우저(형상 얹기 · 다시 셈)와 서버 Node 입구(`design_calc_node` · 등록부 `cross.berm_spans` · `facility.attach`)가 같은 셈.
|
||||
* 파이썬 짝 = `B06_Section_Engine_Structures_Wall.py` `berm_spans_from` · `load_berm_spans` · `berm_span_at` ·
|
||||
* `attach_berm_specs`(Node 를 못 부를 때만 · 거울 시험 `test_272_berm_source.py` · `test_323_4_attach_mirror.py`).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "./common_util_cross_berm";
|
||||
import { catalogRow } from "./common_util_structure_catalog";
|
||||
|
||||
/** 측점 하나의 소단 제원 — 서버 payload 와 같은 이름을 쓴다(그대로 실어 보낸다). */
|
||||
/** 측점 하나의 소단 제원(`section.berm`) — 서버 payload 와 같은 이름을 쓴다(그대로 실어 보낸다). */
|
||||
export interface BermSessionSpec {
|
||||
width_m: number;
|
||||
interval_m: number;
|
||||
@@ -42,10 +42,6 @@ export const isBermType = (typeId: string): boolean => catalogRow(typeId)?.shape
|
||||
/**
|
||||
* 구조물 목록에서 **소단 구간**을 뽑는다 — 사용자는 「구조물 배치」에서 놓는다
|
||||
* (2026-09-07 사용자 확정: 별도 폼이 아니라 다른 옹벽·기슭막이와 같은 자리).
|
||||
*
|
||||
* 세션 열쇠 `berm` 은 그 결과를 담는 **사본**이다. 재계산(브라우저·서버)이 측점마다 값을
|
||||
* 읽어야 하는데 구조물 목록은 비동기로 오므로, 목록이 바뀔 때마다 여기서 펴 두고 계산은
|
||||
* 그 사본만 본다.
|
||||
*/
|
||||
export function bermSpansFromStructures(
|
||||
structures: ReadonlyArray<{
|
||||
@@ -105,3 +101,19 @@ export function bermSpecAt(spans: BermSpan[], chainageM: number): BermSessionSpe
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/** 측점마다 소단 제원을 얹음(파이썬 `attach_berm_specs` 짝 · 제자리) — 덮는 소단 없음 = null(얹었음 표시 ·
|
||||
* 칸 없음 = 아직 안 얹음 → 다시 셈이 저장분 `design.berm` 으로 물러섬). 얹은 측점 수. */
|
||||
export function attachBermSpecs(
|
||||
sections: Array<Record<string, unknown>>,
|
||||
spans: BermSpan[],
|
||||
): number {
|
||||
let attached = 0;
|
||||
for (const section of sections) {
|
||||
const chainage = section.chainage_m;
|
||||
if (typeof chainage !== "number" || !Number.isFinite(chainage)) continue;
|
||||
section.berm = bermSpecAt(spans, chainage);
|
||||
if (section.berm) attached += 1;
|
||||
}
|
||||
return attached;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* 형상 얹기 한 벌(323-4) — 측점(횡단) + 현황(관 · 구조물) + 구조물 표 → 그 측점 그림 자료.
|
||||
*
|
||||
* 구조물 열쇠(`SHAPE_KEYS` · 빈칸 경고 `blank`)를 걷고 서버와 같은 차례로 다시 얹음 — 관 세트 → 벽 →
|
||||
* 포장 → 맹암거 → 빈칸 경고. 고르기 = 표 형상(322-1). 화면은 초안 ?? 저장분 현황으로 이것 하나를 씀
|
||||
* 포장 → 맹암거 → 소단 → 빈칸 경고. 고르기 = 표 형상(322-1). 화면은 초안 ?? 저장분 현황으로 이것 하나를 씀
|
||||
* (`B06_Section_Section_Drafts.withDraftShapes`) — 저장 전 그림 = 저장 뒤 그림.
|
||||
* 파이썬 짝 = `B06_Section/B06_Section_Engine_Attach.py` `attach_shapes`(서버 상세 읽기 · 재생성 응답) ·
|
||||
* 거울 시험 = `resources/tester/test_323_4_attach_mirror.py`.
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
underdrainSpecsFrom,
|
||||
type UnderdrainInput,
|
||||
} from "./common_util_structure_underdrain";
|
||||
import { attachBermSpecs, bermSpansFromStructures } from "./common_util_berm_spans";
|
||||
import { catalogRow, fieldName } from "./common_util_structure_catalog";
|
||||
import {
|
||||
blankKeys,
|
||||
@@ -163,6 +164,10 @@ export function attachShapes(
|
||||
),
|
||||
);
|
||||
attachUnderdrainSpecs(sections, underdrainSpecsFrom(structures as UnderdrainInput[]));
|
||||
attachBermSpecs(
|
||||
sections,
|
||||
bermSpansFromStructures(structures as Parameters<typeof bermSpansFromStructures>[0]),
|
||||
);
|
||||
attachBlanks(sections, pipes, structures, types);
|
||||
return misses;
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ export const SHAPE_KEYS = [
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
] as const;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# PLAN 575 이관 (2026-10-29 · 요구 432 · 434 · 435)
|
||||
|
||||
## 575. 노선 손 고침 [저장] 뒤 종단이 지반고로 (요구 432 · 브레인 조사)
|
||||
|
||||
- 정함(브레인 · 분석 432-A): 498-2 탓 아님 · 옛 규칙 — 손 [저장] = 옛 선 1 m 안 측점만 옛 높이 · 나머지 「지반 첫 계획선」(계획고 = 지반고) · 균형 종단은 기준 꼭짓점(base_pvi)에 있음 · 검증_랩탑메인 22:04 교점 19 → 16 = 57 중 39 측점 바뀜 → 최대 76.67 % · 미달 43 · 덧 흠 = 손 [저장]이 「종단 반영」을 끔으로 덮음(`B05_Profile_Router_Replan.py:586-592`) · 근거 `docs/raw/요구_1025/_분석_432-A_종단_지반고.md`.
|
||||
- [x] 575-1 ⬜ 사용자 결정 — 바뀐 구간 종단: A 그 구간만 균형 종단 다시 풂(권함) · B 옛 높이를 길이 비례로 옮김 · C 지금대로 + 알림 · 고칠 자리 `B05_Profile_Route_Save.py:277` · `B05_Profile_Engine_Grade_Carry.py:120-142` · 짝 TS `B05_Profile_Grade_Carry.ts:163-180` · `B05_Profile_Pipeline_Grade.ts:426-461`.
|
||||
- 검증완료 — 사용자 B(요구 434 · 435 「진행」).
|
||||
- 사용자 확정(요구 434 · 435): B = 노선 손 고침 [저장] 때 바뀐 구간 종단 = 옛 종단(기준 꼭짓점) 높이를 길이 비례로 옮김(다시 풀지 않음 · 빠름) · 넘친 기울기 · 미달은 지금 표시로 · 고칠지는 설계자 · 덧 흠(손 [저장]이 「종단 반영」 끔으로 덮음) 같이 고침.
|
||||
- [x] 575-2 B 고침 — 파이썬 `B05_Profile_Route_Save.py:277` · `B05_Profile_Engine_Grade_Carry.py:120-142` ↔ TS `B05_Profile_Grade_Carry.ts:163-180` · `B05_Profile_Pipeline_Grade.ts:426-461`(짝 · 거울 시험) · 덧 `B05_Profile_Router_Replan.py:586-592` — sub3 Opus high · 확인: 교점 지움 · 큰 끌기 [저장] 뒤 계획고 ≠ 지반고 · 끌 때 화면 = 저장 뒤 · 안 바뀐 구간 0 차 · 「종단 반영」 그대로.
|
||||
- 검증완료 — 검증_sub3 교점 지움 + 41.8 m 끌기 [저장]: 바뀐 17 측점 계획고=지반고 0 곳(옛 종단 비례) · 같은 자리 꼭짓점 0 mm(측점 계획고는 연장 바뀌어 측점 격자 밀린 곡선 차 ≤ 110 mm · 옛 규칙과 같음) · 최대 기울기 11.72→12.73 % · 미달 19→12 · 끌 때 화면=저장 뒤=새로고침 0.0 mm(74 측점) · 종단 반영 켬 그대로 · 그대로 [저장] 0 mm · 추천 [적용] 정상(켬 기록) · 노선 저장 체인 13.5 s(계획고 옮김 0.4 s · 전 9~12 s · 0.4~1.1 s) · 비례 기준 = 같은 자리 측점 + 시점 0 · 종점(새 ↔ 옛 연장) · 거울 · 고친 대상 시험 통과 · typecheck 0 · 밝음 · 어두움 · ORCA · 되돌림 같음.
|
||||
@@ -0,0 +1,11 @@
|
||||
# PLAN 577 이관 (2026-10-29 · 요구 434 · 436 · 437)
|
||||
|
||||
## 577. 사면안정 구조물군 한 벌 — 소단 사본 걷음 (요구 434-4 · 435 · 브레인 조사)
|
||||
|
||||
- 정함(브레인 · 분석 434): 놓기 · structures.json · M02 표 제어는 구조물군 모두 같은 길 · 횡단 계산만 다름(벽 = 계산 뒤 사면 잘라 덧그림 · 소단 = 계산 앞 설계선 계단 · 성질이라 유지) · 계산 앞 구조물(포장 · 물넘이 · 세월교 하강 · 맹암거)은 사본 없이 감 · 소단만 저장 노선 다시 셈 길에서 세션 사본을 거침(09-07 B06 측점 입력 시절 흔적) · 근거 `docs/raw/요구_1025/_분석_434_사면안정_구조물군.md`.
|
||||
- 정함(브레인 · 분석 434 2차 · 요구 436 사면안정 그룹끼리): 벽(옹벽 · 흙막이 · 돌쌓기) = 측점마다 얹음 → 계산은 표준 사면 → 벽 상단에서 사면 자르고 면적 칸만 덮음(설계선 = 표준) · 소단 = 측점 얹기 없음 · 계산 안에서 성토 사면 계단(늘림 · 비탈 끝 다시 찾음) · 소단을 벽처럼 계산 뒤에 넣는 길은 안 됨(늘림이라 비탈 끝 · 면적 · 3D 따로 고쳐야) · 권함 = 가(소단도 옹벽처럼 측점에 제원 얹고 계단은 계산 안 · 값 같음 · 사본 사라짐 · 소단 측점 3D 한 번 다시 셈) · 덤 = 평면 발자국 · B07 계획평면도가 벽에 잘린 모양 모름(코드 읽기).
|
||||
- [x] 577-1 ⬜ 사용자 결정 — 안 A(권함) 저장 노선 길에도 구조물 목록을 넘겨 사본 없앰(`design_pipeline_page.ts` · `design_shared.ts` · `B06_Section_Cross_Refresh.ts` 고침 · `design_facility_lane.ts` · `B06_Section_UI_Page_Persist.ts` · `b_page_state.ts` 사본 지움 · 값 같음) / 안 B 소단도 측점에 얹는 한 벌(범위 큼 · 소단 측점 3D 다시 셈).
|
||||
- 검증완료 — 사용자 가(요구 437).
|
||||
- 사용자 확정(요구 437): 가 = 소단도 옹벽처럼 측점에 제원을 얹는 한 벌 · 3D 까지 이어짐 · 꼴 = 소단 자리에서 사면을 자르고 수평선(소단 폭) + 그 끝에서 새 성토선 · 개발 순서상 늦게 들어와 빠졌던 것.
|
||||
- [x] 577-2 가 고침 — 소단 측점 얹기(TS · 파이썬 짝) · 읽는 쪽(저장 노선 다시 셈 · 노선 초안 · 서버 묶음) · 사본 지움(`design_facility_lane.ts` · `B06_Section_UI_Page_Persist.ts` · `b_page_state.ts`) · 횡단 · 토공 · 평면 비탈 끝 · B07 · 3D · 수량까지 — sub1 Opus high · 확인: 설계 값 전후 같음 · 3D 소단 계단 보임 · 사본 0.
|
||||
- 검증완료 — 검증_sub1 5+0 소단 폭 1.5: 성토 28.13→46.37 ㎡ · 평면 비탈 끝 16.85→20 m · 3D 바깥선 21.4 m · 횡단 카드 계단(밝음 · 어두움 · ORCA) · ↶ 28.13 ↷ 46.37 · [저장] 뒤 새로고침 같음 · B07 성토 사면 25.38 m(소단 1.5) · 옛 코드 대 새 코드 화면 값 차 0(65 측점 면적 · 설계선) · 세션 berm 칸 0 · 시험 거울(272 · 323-4) 통과 · typecheck 0 · 묶음 다섯 다시 지음 · storage · DB 되돌림
|
||||
@@ -0,0 +1,180 @@
|
||||
# 요구 434-4 · 435 — 사면안정 구조물군 로직 조사
|
||||
|
||||
조사만 · 코드 · git · storage · 마스터 안 고침 · 서버 · 브라우저 안 씀.
|
||||
|
||||
## 0. 한 줄 결론
|
||||
|
||||
- 소단도 놓기 · 저장 · M02 표 제어는 다른 사면안정 구조물과 같은 길임.
|
||||
- 다른 것은 「횡단 계산에 들어가는 문」 하나 — 벽(옹벽 · 흙막이 · 돌쌓기)은 **계산 뒤 덧그림 · 자름**, 소단은 **계산 앞 입력**(설계선 자체가 계단)임. 이것은 성질이라 못 합침.
|
||||
- 그런데 계산 앞 입력인 다른 시설(포장 · 물넘이 기울임 · 세월교 하강 · 맹암거)은 사본 없이 구조물 목록이나 측점에 얹은 형상에서 바로 읽음. 소단만 **저장 노선 다시 셈 한 길**에서 세션 사본 `berm` 을 거침.
|
||||
- 사본은 성질 탓이 아니라 이력 탓 — 2026-09-07 처음엔 B06 카드의 측점 입력(세션 `berm` = 「측점키 → 폭 · 간격 · 기울기」 · 암 경계 `rockb` 와 같은 자리)이었고, 같은 날 「구조물 배치」로 옮기며 세션 칸을 구조물에서 편 사본으로 남김(`docs/raw/plans/2026-09-07_plan_completed_items_2.md:866-884`).
|
||||
|
||||
## 1. 마스터에 있는 사면안정 구조물군
|
||||
|
||||
| 종류 | type_id | 군 | 목록 | 표 형상 | 횡단도 | 3D | 정의 · 부품 줄 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 옹벽 | retaining_wall | C | 켜짐 | wall(형태 콘크리트 · 물빼기) | span3 | wall | `resources/master_template/구조물정의.json:919` · `common_util/common_util_structure_parts.json:287` |
|
||||
| 흙막이 | soil_guard | C | 켜짐 | wall(빈 형태 = 통나무 · 목재틀) | span3 | wall | `구조물정의.json:1468` · `parts.json:318` |
|
||||
| 소단 | berm | C | 켜짐 | **berm** | span3 | surface(설계선만) | `구조물정의.json:1905` · `parts.json:338` |
|
||||
| 돌쌓기(찰) | masonry_wet | C | 숨김 | wall | span3 | wall | `구조물정의.json:1028` · `parts.json:298` |
|
||||
| 돌쌓기(메) | masonry_dry | C | 숨김 | wall | span3 | wall | `구조물정의.json:1247` · `parts.json:308` |
|
||||
| 큰돌쌓기 | boulder_masonry | C | 숨김 | wall | span3 | wall | `구조물정의.json:1720` · `parts.json:328` |
|
||||
| (곁) 기슭막이 | revetment | D | 숨김 · 관 지점 정본 | hidden_revet | span3 | wall | `parts.json:366` |
|
||||
| (곁) 비탈면 녹화 | revegetation | F | B06 만 | none | none | none | `구조물정의.json:2898` · `parts.json:479` |
|
||||
|
||||
- 낙석 · 블록 독립 종류 없음 — 블록은 옹벽 「형식」 선택지 「식생옹벽블럭」(detail 단계)뿐.
|
||||
- 표 09(54-26) 칸 — 흙막이 형태 · 길이 · 높이 · 전 · 후 / 옹벽 형태 · 길이 · 높이 · 전 · 후 / 소단 **폭만**(`구조물정의.json:3379` 「표시」가 깊이 · 간격 · 기울기 · 높이 · 길이 · 전 · 후 숨김 → 깊이 1.0 · 간격 3.0 · 0° 기본값으로 돎).
|
||||
- 「표 형상」 칸이 길을 가름(`common_util/common_util_structure_catalog.py` `resolve_catalog` · TS 짝) — wall 이면 벽 길 · berm 이면 소단 길 · 종류 키로 안 가름. M02 제어는 이미 같은 문.
|
||||
|
||||
## 2. 길 표 — 벽 대 소단
|
||||
|
||||
| 단계 | 벽(옹벽 · 흙막이 · 돌쌓기 · 큰돌) | 소단 |
|
||||
|---|---|---|
|
||||
| 놓기 | B05 「구조물 배치」 같은 폼 · 옹벽 · 흙막이 「추가 단」(`A00_Common/design/logic/design_structure_slope_tier.ts:26`) | 같은 폼 · 칸은 폭만 |
|
||||
| 저장(정본) | `structures.json`(`B05_Profile/B05_Profile_Structures_Repository.py:50` 읽기 · `:110` 쓰기) | 같음 |
|
||||
| 측점에 얹기 | 형상 얹기 한 벌 `attachShapes`(`common_util/common_util_structure_attach.ts:130-168`) → `wallSpecsFrom` · `attachWallSpecs`(`common_util/common_util_structure_walls.ts:105-168`) → `section.revetment` · 파이썬 짝 `B06_Section/B06_Section_Engine_Structures_Wall.py:72-110 · 179-197` · 서버 GET `B06_Section/B06_Section_Engine_Attach.py:169-199` | **안 얹음** — 구조물 목록 → `bermSpansFromStructures`(`common_util/common_util_berm_spans.ts:50-90`) → 구간 → 측점마다 `bermSpecAt`(:93) · 파이썬 짝 `Engine_Structures_Wall.py:113-176` |
|
||||
| 횡단 계산 | 표준 설계선은 벽을 모르고 셈 → **계산 뒤** 벽 기하(`computeRevetmentLayout`)가 성토 사면을 벽에서 자름(트림) → 폐회로 면적으로 면적 칸 덮어씀(`B06_Section/logic/B06_Section_Structure_Layouts.ts:98-145 · 287 · 379` · `common_util/common_util_cross_structure_areas.ts`) · 저장 설계선 = 표준선 그대로 | **계산 앞** 입력 `berm` → 설계선 자체가 계단(`common_util/common_util_cross_design_geometry.ts:356` · `common_util_cross_berm.ts` · 파이썬 짝 `.py`) → 지반과 만나는 점이 밀림 · 면적 = 기본 적분 · 결과 `design.berm` 저장 |
|
||||
| 횡단 조절(사용자) | 카드 끌기 → `design.revet_adjust`(횡단 patch · `B06_Section/logic/B06_Section_Design_Keys.ts:18`) | 카드 ▲▼ → 세션 `bermdepth` → `design.berm_depth_left/right_m`(횡단 patch · 같은 파일 :40-41) |
|
||||
| 횡단 그림 · B07 | 덧그림 `B06_Section/B06_Section_UI_Cross_Revetment.ts` · B07 `B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts:493-500`(트림 설계선) · `:513`(벽) · 전개도 `B07_DesignDetail_Engine_Cad_Development.py:150` | 설계선 그대로(같은 `:493`) · B07 사면 가름 `B07_DesignDetail_Engine_SlopeGeometry.py:178 · 240`(design.berm) |
|
||||
| 3D | 벽 스윕 `B05_Profile/logic/B05_Profile_Corridor_Structures.ts:300-306`(section.revetment) | surface = 설계선만 → 계단이 면에 그대로 |
|
||||
| 수량(B08) | 토공 = 트림 폐회로 면적 · 구조물 집계 줄 없음(`B08_Quantity/B08_Quantity_Router_Summary.py:79-81` 배수 종류만) | 토공 = 계단 설계선 면적 · 집계 줄 없음 |
|
||||
|
||||
## 3. 계산 앞 입력 — 소단 대 다른 시설
|
||||
|
||||
| 길 | 소단 원천 | 포장 · 물넘이 기울임 · 세월교 하강 · 맹암거 원천 |
|
||||
|---|---|---|
|
||||
| 노선 초안(브라우저 묶음 cross 엔진) | `ctx.inputs.structures` → `bermAt`(`A00_Common/design/design_pipeline_page.ts:348` · `design_shared.ts:258-266`) → `crossWithBerm`(`B05_Profile/logic/B05_Profile_Pipeline_Detail.ts:123-146`) | 같은 `ctx.inputs.structures` → `crossWithShapes`(같은 파일 :267-286 · 형상 얹기) |
|
||||
| **저장 노선 다시 셈**(브라우저 · cross=server 면 Node `cross.refresh`) | **세션 사본 `berm`**(`B06_Section/B06_Section_Cross_Refresh.ts:231-242`) ← 시설 줄 `syncBerm` · `putBerm`(`A00_Common/design/design_facility_lane.ts:155-182`) · 사본이 바뀌면 cross 를 한 번 더 부탁(forceCross) | 다시 셈 바로 앞에 형상 다시 얹음(`Cross_Refresh.ts:138` → `withDraftShapes` → `attachShapes`) → 측점 형상 읽음(`B06_Section/logic/B06_Section_Cross_Refresh_Calc.ts:224-243`) |
|
||||
| 서버 Node 묶음 cross | `ctx.inputs.structures`(`A00_Common/design/logic/design_calc_node_stages.ts:193`) | 같은 status |
|
||||
| [저장] · [확정] | 구조물 정본(TS `B06_Section/logic/B06_Section_Cross_Save.ts:222-256` · 파이썬 `B06_Section_Router_Design.py:224-228` → `B06_Section_Engine_Design_Node.py:139` Node `cross.berm_spans`) | 구조물 · 관 정본(`facilityRanges` 같은 파일 :222 · 파이썬 tilt_ranges · ford drops) |
|
||||
| B05 확정 · 넓힘 | 저장 결과 `design.berm`(`B05_Profile_Router_Confirm.py:184` · `Engine_SampleExtend.py:162`) | 저장 결과 `design.paved` 등 |
|
||||
|
||||
- 사본은 저장 노선 다시 셈 한 길에만 있음. 까닭 = 묶음이 저장 노선 길 `savedCross(detail, routeId, stations)`(`design_pipeline_page.ts:357`)에 구조물 목록을 안 넘김 — 엔진 길(:348)은 넘김.
|
||||
|
||||
## 4. 소단만 다른 까닭
|
||||
|
||||
- **비탈 모양을 계산 전에 바꿔야 함 — 맞음.** 소단은 성토 사면 노면 끝 아래 깊이에 한 단 · 계단 뒤 사면이 다시 지반까지 걸어 내려가 만나는 점이 밀림(3-9 기록 — 늘어난 면적이 「폭 × 높이」가 아니라 밀린 거리로 정해짐). 계산 뒤 자르는 벽 방식으로는 못 냄.
|
||||
- **벽은 비탈 모양을 안 바꿈.** 표준 사면으로 셈한 뒤 벽 자리에서 자르고 성토부선으로 대신 그림 · 면적만 폐회로 값(2026-09-06 확정 「구조물 자체 면적을 빼는 것이 아님」).
|
||||
- **같은 일을 하는데 길이 다른 것 — 사본 하나.** 같은 「계산 앞 입력」인 포장 · 물넘이 · 세월교 하강 · 맹암거는 사본 없이 감. 소단 사본은 옛 B06 측점 입력의 흔적이고, 그 뒤 465 · 467(B05 만 연 탭) · 477-4(결과 통)로 사본을 덧고쳐 옴.
|
||||
- 사본 때문에 따로 있는 것 — 시설 줄 `syncBerm` · `putBerm` · 사본 비교 · forceCross 한 바퀴(`design_facility_lane.ts:175-181`) · 계산 자리 서버 갈래 `C4.copy` · 지킴 시험(`test_477_undo_now_berm` · `test_272_berm_source`).
|
||||
|
||||
## 5. 한 벌로 합치는 안
|
||||
|
||||
### 안 A — 사본 없앰 (가장 작음 · 권함)
|
||||
|
||||
- 저장 노선 길에도 엔진 길처럼 구조물 목록을 넘김 — `design_pipeline_page.ts:357` 에서 `ctx.inputs.structures` 를 `savedCross` 에 더 넘김 → `design_shared.ts:270` savedCross 에서 노선 초안 `bermAt`(:262)이 이미 쓰는 `calcChecked("quantity", "cross.berm_spans", bermSpansFromStructures, …)` 로 구간 셈 → `CrossRefreshInput`(`Cross_Refresh.ts:75`)에 `bermSpans` 칸 → `gatherRefresh`(:233-241)가 세션 대신 그 값.
|
||||
- 지움 — 세션 칸 `berm`(`A00_Common/b_page_state.ts` 결과 통 줄) · `writeBermSpans`(`B06_Section/B06_Section_UI_Page_Persist.ts:128`) · `syncBerm` · `putBerm` · `run(syncBerm)` 두 곳(`design_facility_lane.ts:155-182 · 237 · 244`).
|
||||
- 고칠 파일 — `A00_Common/design/design_pipeline_page.ts` · `design_pipeline_page_types.ts:67` · `design_shared.ts` · `B06_Section/B06_Section_Cross_Refresh.ts` · `A00_Common/design/design_facility_lane.ts` · `B06_Section/B06_Section_UI_Page_Persist.ts` · `A00_Common/b_page_state.ts`. 시험 — `test_477_undo_now_berm.py` · `test_272_berm_source.py` · `test_511_a_calc_browser.py:62`(`C4.copy` 줄) · `helper_271` · `helper_473`(빈 stub).
|
||||
- 파이썬 · TS 짝 — 그대로(파이썬 안 고침). 서버 Node 묶음 — `cross.refresh` 가 받는 `bermSpans` 꼴이 같고 `Cross_Refresh_Calc.ts` 안 고침 → 다시 짓기 없음.
|
||||
- 값 영향 — 없음(같은 구조물 → 같은 구간). 구조물 고친 뒤 cross 한 바퀴 줄어듦 · 옛 사본으로 한 번 셌다 다시 세는 깜빡임 없어짐(코드 읽기 · 실측 안 함).
|
||||
- 위험 — 낮음. 저장분 옛 꼴 없음(structures.json · design.berm 그대로 · 탭에 남은 옛 세션 `berm` 은 안 읽힘). 정본 자리 그대로 — 소단 제원 = 구조물 배치(structures.json) · 쪽 깊이 = 횡단 patch · design.berm = 계산 결과. 구조물을 아직 모를 때는 지금처럼 저장분 design.berm 으로 물러섬 유지.
|
||||
|
||||
### 안 B — 소단도 측점에 얹음(형상 얹기 한 벌에 들임)
|
||||
|
||||
- `attachShapes`(TS) · `_attach_python`(파이썬 짝)에 소단 얹기를 더해 측점 칸(예 `berm_span`)을 세움 → 다시 셈은 포장처럼 측점 칸을 읽음(`Cross_Refresh_Calc.ts:150`) · 노선 초안 `shapeContext`(`B05_Profile_Pipeline_Detail.ts:256`)도 같은 칸 → `crossWithBerm` · `bermAt` 옵션 · `bermSpans` 입력 지움 + 안 A 의 사본 지움.
|
||||
- 고칠 파일 — `common_util_structure_shape_keys.ts` · `common_util_structure_attach.ts` · `common_util_berm_spans.ts` · `B06_Section_Engine_Attach.py`(SHAPE_KEYS · `_attach_python`) · `B06_Section_Engine_Structures_Wall.py`(얹기 짝) · `Cross_Refresh_Calc.ts` · `B05_Profile_Pipeline_Detail.ts` · `design_shared.ts` · `design_pipeline_page(_types).ts` · `design_calc_node_stages.ts` + 안 A 지움.
|
||||
- 짝 — TS ↔ 파이썬 얹기 거울(`test_323_4_attach_mirror`) 늘림. 서버 Node 묶음(server-calc · corridor) 다시 지어야 반영.
|
||||
- 값 영향 — 설계 값 없음. `SHAPE_KEYS` 가 3D 지문(`B05_Profile/logic/B05_Profile_UI_Corridor_Structures_Hash.ts:41`)에 들어가 소단 측점 3D 저장본이 한 번 다시 셈 · 서버 GET 상세에 새 칸.
|
||||
- 위험 — 중간. 정본 못 읽을 때 물러섬 · B07 · E03 이 같은 얹기를 씀(`E03_StabSection/E03_StabSection_Walls.py:50`).
|
||||
- 이득 — 사면안정 구조물(벽 · 소단) 모두 「표 형상 → 측점 얹기 → 그림 · 계산」 한 길.
|
||||
|
||||
### 안 C — 「계산 앞 시설 맥락」 한 함수
|
||||
|
||||
- 같은 판정이 셋으로 나뉨 — 노선 초안 `shapeContext`(`B05_Profile_Pipeline_Detail.ts:256-264`) · 다시 셈 안쪽(`Cross_Refresh_Calc.ts:224-243`) · [저장] `facilityAt`(`B06_Section/logic/B06_Section_Cross_Enforce.ts:71`) + 소단 `bermSpecAt`. 안 B 위에서 포장 · 기울임 · 하강 · 소단을 한 함수로 모으고 [저장] 파이썬 짝(`B06_Section_Router_Design.py` 포장 · 기울임 · 소단)도 그 꼴로.
|
||||
- 범위 큼(파이썬 [저장] 길 · 거울 시험 셋) · 지금 값 어긋남 없음 → 지금은 안 권함.
|
||||
|
||||
### 권함
|
||||
|
||||
- **안 A.** 물음(「왜 소단만 사본」)의 원인을 바로 없앰 · 값 그대로 · 파이썬 · 서버 묶음 안 건드림.
|
||||
- 소단이 「계산 앞 입력」인 것은 성질이라 남음 — 벽과 같은 문(계산 뒤 자름)으로 합치는 것은 기하상 불가.
|
||||
- 안 B 는 측점 얹기까지 한 길로 하고 싶을 때 다음 단계.
|
||||
|
||||
## 6. 사용자 물음
|
||||
|
||||
- 안 A(사본만 없앰 · 값 같음)로 갈지 · 안 B(소단도 다른 구조물처럼 측점에 얹음 · 소단 측점 3D 한 번 다시 셈)까지 갈지.
|
||||
|
||||
## 7. 확인 못 함
|
||||
|
||||
- 화면 실측 — 서버 · 브라우저 안 씀(지시대로) · 안 A 의 「cross 한 바퀴 줄어듦」은 코드 읽기로만.
|
||||
- 낙석 · 블록 — 마스터에 독립 종류 없음 · 사용자 표 09 원본 문서와 대조 안 함.
|
||||
- 3D surface 가 소단 계단을 그대로 면으로 그리는지 — 부품 이름 「설계선만(몸체 없음)」 · 코리도 빌드가 design_line 을 읽는 것까지만 봄.
|
||||
- B08 사면안정 구조물 집계 — 지금 집계표가 배수 종류만이라 벽 · 소단 수량 길 없음(재작업 범위 · 이 조사 밖).
|
||||
- 안 A 에서 계산 자리 quantity = server 일 때 저장 노선 소단 값 — 지금 `C4.copy` 는 서버 값만 · 노선 초안 `bermAt` 은 브라우저 값 + 뒤 대조. 안 A 는 노선 초안 꼴로 맞추는 것으로 봄(코드상 둘 다 가능).
|
||||
|
||||
---
|
||||
|
||||
## 2차 — 사면안정 그룹끼리 비교 (요구 436)
|
||||
|
||||
1차의 포장 · 물넘이 · 세월교 · 맹암거 비교는 걷음. 같은 사면안정 그룹(옹벽 · 흙막이 · 돌쌓기 찰 · 메 · 큰돌쌓기 = 표 형상 wall)과 소단(표 형상 berm)만 견줌.
|
||||
|
||||
### 2-1. 벽(옹벽 · 흙막이 · 돌쌓기)이 횡단에 들어가는 길
|
||||
|
||||
| 단계 | 하는 일 · 자리 |
|
||||
|---|---|
|
||||
| ① 구조물 목록 받기 | 서버 상세 읽기 `B06_Section/B06_Section_Router.py:254 · 462` → `attach_saved_shapes`(`B06_Section_Engine_Attach.py:186-199` · structures.json + 노선 관) · 브라우저 초안 `withDraftShapes`(`B06_Section/B06_Section_Section_Drafts.ts:130-158` · 초안 ?? 저장분) · 다시 셈 바로 앞 한 번 더(`B06_Section_Cross_Refresh.ts:138`) · 노선 초안 `attachStatus`(`B05_Profile/logic/B05_Profile_Pipeline_Detail.ts:234`) |
|
||||
| ② 측점에 얹기 | 형상 얹기 한 벌 `attachShapes`(`common_util/common_util_structure_attach.ts:130-168` · 서버는 Node `facility.attach` 로 같은 TS · 못 부르면 파이썬 `_attach_python` `Engine_Attach.py:111-132`) → `wallSpecsFrom` · `attachWallSpecs`(`common_util/common_util_structure_walls.ts:105-168`) → **측점 겉 칸 `section.revetment`**(높이 · 형태 · 설치 측 · 기초 · 추가 단 · 전면 기울기) · 정본에 안 씀(읽을 때마다 걷고 다시 얹음) |
|
||||
| ③ 횡단 계산 | `computeCrossDesign` 은 벽을 모름 → 설계선 · 면적 = 표준 사면 그대로 |
|
||||
| ④ 계산 뒤 사면 자르기 | `computeRevetmentLayout`(`B06_Section/logic/B06_Section_Cross_Revetment_Geom.ts:160-334`) — 벽 자리(노견 · 조절 x · d) · 벽 도형 · 다단 → `designTrim`(:296-312) = **벽 상단에서 설계선을 끊음** · 노견→벽 상단 성토선 대신 · 벽 바깥은 원지반(면적 0). 묶음 = `computeStoredLayouts`(`Structure_Layouts.ts:95-131`) · 트림 고름 `trimOfLayouts`(:136-143 · 관 ?? 세월교 ?? BOX ?? 벽 — **한 측점에 트림 하나**) |
|
||||
| ⑤ 측점 설계 칸에 쓰는 것 | 면적 칸만 덮어씀 — `cut_area_m2 · fill_area_m2 · cut_soil_area_m2 · cut_rock_area_m2`(`STRUCTURE_ROW_KEYS` `Structure_Layouts.ts:153` · `areaRowOf` :211 → 폐회로 `computeStructureAreas` `common_util/common_util_cross_structure_areas.ts:127-170` → `applyStructureAreaRows` :379) · 사용자 조절 `design.revet_adjust.own`(횡단 patch) · `design_line` 은 표준선 그대로 · 벽 제원은 design 에 안 씀 |
|
||||
| ⑥ 면적 셈 부르는 자리 | 다시 셈 `Cross_Refresh.ts:155-163` · 상세 가게 `B06_Section_Section_Store.ts:132` · 노선 초안 `design_shared.ts:286-292` · 서버 Node `design_calc_node.ts:137` · `design_calc_node_cross.ts:95` — 파이썬 짝 없음(서버도 이 TS 실행) |
|
||||
| ⑦ 토공 면적(B08) | `B08_Quantity/B08_Quantity_Engine_Earthwork.py:125` 가 design 면적 칸(덮어쓴 값)을 읽음 |
|
||||
| ⑧ 횡단 그림 · B07 | B06 카드 `B06_Section/B06_Section_UI_Cross_Revetment.ts` · B07 덧그림 `B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts:493-500`(트림 설계선) · `:513`(벽) · CAD 장은 화면 덧그림으로 계획선 · 구조물 층을 갈아 끼움(`B07_DesignDetail_Engine_Cad_Sheet.py:248-249`) · 전개도는 structures.json 직접(`B07_DesignDetail_Engine_Cad_Development.py:146-160`) |
|
||||
| ⑨ 3D | `B05_Profile/logic/B05_Profile_Corridor_Structures.ts:300-306` 벽 스윕(`section.revetment` → `computeRevetmentLayout`) · 비탈 면은 같은 `designTrim` 을 따름(`B05_Profile_Corridor_Envelope.ts:126 · 164`) |
|
||||
| ⑩ 수량 | B08 구조물 집계 줄 없음(`B08_Quantity_Router_Summary.py:79-81` 배수만) · 벽 몸 ∩ 성토 겹침(`wallBodyInFillM2`)은 관 세트 벽만(`Structure_Layouts.ts:327-356`) |
|
||||
| ⑪ 안정성(E03) | `E03_StabSection/E03_StabSection_Walls.py:37-50`(같은 `attach_saved_shapes`) · `E03_StabSection_Walls_Node.ts`(같은 `drawnProfile`) |
|
||||
| 짝 | 제원 TS `common_util_structure_walls.ts` ↔ 파이썬 `B06_Section_Engine_Structures_Wall.py:72-110 · 179-197`(거울 `test_b06_structure_walls_mirror.py`) · 얹기 TS ↔ 파이썬(`test_323_4_attach_mirror.py`) · 자르기 기하 · 폐회로 면적 = TS 한 벌 |
|
||||
|
||||
- 곁 발견(안 고침) — 평면 발자국 비탈 끝(`B05_Profile/logic/B05_Profile_PlanFootprint.ts:133-166` · 짝 B07 계획평면도 `B07_DesignDetail_Engine_Cad_PlanSlope.py`)은 `design_line` 만 읽고 벽 트림을 모름 → 벽이 선 자리도 표준 비탈 끝으로 그림(코드 읽기 · 화면 확인 못 함).
|
||||
|
||||
### 2-2. 소단이 지금 들어가는 길(같은 단계로)
|
||||
|
||||
| 단계 | 소단 |
|
||||
|---|---|
|
||||
| ① 목록 | 같은 structures.json · 받는 자리는 셋으로 갈림 — 노선 초안 `design_pipeline_page.ts:348` · 저장 노선 다시 셈 = 세션 사본(`Cross_Refresh.ts:231-242` ← `design_facility_lane.ts:155-182`) · [저장] `Cross_Save.ts:222-256` · `Router_Design.py:224` |
|
||||
| ② 측점에 얹기 | **없음** — 측점 칸 없이 구간 목록(`common_util_berm_spans.ts:50-90`) → 측점마다 `bermSpecAt` |
|
||||
| ③ 횡단 계산 | **계산 안쪽** — 성토 사면 꼭짓점에 계단(`common_util_cross_design_geometry.ts:346-361` `fillPoints` · 설계선 꼭짓점 :539-548) → 계단 뒤 사면이 지반과 새로 만나는 점(비탈 끝)을 찾음 |
|
||||
| ④ 계산 뒤 | 없음 |
|
||||
| ⑤ 설계 칸 | `design_line`(계단 든 선) · 면적(기본 적분) · `design.berm`(제원 메아리) · `berm_depth_left/right_m`(▲▼ 조절 · 횡단 patch) |
|
||||
| ⑦–⑪ | 모두 `design_line` · 면적 칸을 그대로 읽어 따로 할 일 없음 — 평면 비탈 끝 · B07 사면길이(`SlopeGeometry.py:178 · 240`) · 3D 면 · E03 · 미교차 넓힘이 다 계단을 앎 |
|
||||
|
||||
### 2-3. 소단을 벽 길로 옮길 수 있나
|
||||
|
||||
- **(ㄱ) 측점 칸 꼴만 같게(벽처럼 `attachShapes` 로 측점에 제원을 얹고 계산은 그 칸에서 읽음)** — 가능 · 값 0 차. 계단은 지금처럼 계산 안쪽.
|
||||
- **(ㄴ) 계산 뒤 계단 덧 처리(벽과 완전 같은 문)** — 기하로는 되나 같은 값이 나오는 자리가 적음.
|
||||
- 성질 차이 — 벽 트림은 **자름**(표준 사면을 벽 상단에서 끊고 벽면으로 지반에 닫음 · 새 교점 찾기 없음). 소단은 **늘림**(계단 뒤 사면이 더 바깥에서 지반과 만남 · 비탈 끝을 다시 찾아야 함 = 지금 계산 안쪽이 하는 일을 뒤에서 한 번 더).
|
||||
- 밀리는 거리 — 폭 ÷ (1 − 성토 경사 n × 지반 기울기). 예 성토 1:1.2 · 지반 1:2(0.5) → 폭 0.5 m 가 비탈 끝을 1.25 m 밖으로 · 지반이 가파를수록 커짐.
|
||||
- 토공 면적 — 뒤 처리가 같은 함수(`fillProfilePoints` · 지반 교점)를 다시 부르면 표본 안쪽 측점은 0 차. 밀린 비탈 끝이 지반 표본 밖이면 지금은 `slope_unclosed` → DTM 넓힘(+5 m · `extendUnclosed`)이 받아 주는데, 뒤 처리는 그 판정이 끝난 뒤라 못 받음 → 면적이 표본 끝에서 잘려 **작아짐**.
|
||||
- 비탈 끝(daylight) 자리 — `design_line` 이 표준선으로 남음 → 평면 발자국 · B07 계획평면도 · 용지 경계가 소단 없는 자리(밀린 거리만큼 안쪽)로 그려짐.
|
||||
- 횡단 그림 — 트림 선을 그리면 B06 · B07 덧그림은 같게 보임. 그러나 `trimOfLayouts` 가 한 측점에 트림 하나만 고름 → 벽 + 소단이 한 측점에 같이 서면 하나를 잃음 · 트림 합치기 새로 필요. 지금은 계단이 설계선 안에 있어 벽 트림이 그 선을 그대로 자름(겹쳐도 됨).
|
||||
- 사면 길이 · 사면보호공(B07 횡단 수량표) · 3D 면 · E03 모형 · 성토 지반 기울기 칸 — 다 `design_line` 을 읽음 → 각 자리가 소단 트림을 따로 배워야 지금과 같음(한 벌이 아니라 여러 벌).
|
||||
- 수량(B08 토공) — 면적 칸 덮어쓰기라 표본 안쪽은 0 차 · 표본 밖은 위처럼 작아짐.
|
||||
- 결론 — 입구(측점 칸)는 벽과 같게 할 수 있음 · 계단을 넣는 시점(계산 안쪽)은 비탈 끝을 바꾸는 성질이라 그대로 두는 것이 맞음.
|
||||
|
||||
### 2-4. 안 다시 정리
|
||||
|
||||
**가 — 사면안정 그룹 한 벌(소단도 옹벽처럼 측점에 얹음 · 계단은 계산 안쪽 그대로)**
|
||||
- 범위 — 얹기: `common_util/common_util_structure_walls.ts`(또는 `common_util_berm_spans.ts`)에 소단 얹기 · 파이썬 짝 `B06_Section_Engine_Structures_Wall.py` · `common_util_structure_attach.ts:130-168` 한 줄 · `common_util_structure_shape_keys.ts` · `B06_Section_Engine_Attach.py:38 · 111-132`. 읽기: `B06_Section/logic/B06_Section_Cross_Refresh_Calc.ts:147-160`(측점 칸) · `B05_Profile_Pipeline_Detail.ts:256-264`(노선 초안도 측점 칸) · `crossWithBerm`(:123-146) · `design_shared.ts:258-266` bermAt · `design_pipeline_page.ts:348` · `design_pipeline_page_types.ts:65` · `design_calc_node_stages.ts:193`. 사본 지움: `design_facility_lane.ts:155-182 · 237 · 244` · `B06_Section_UI_Page_Persist.ts:128` · `b_page_state.ts`. [저장] 길(`Cross_Save.ts:222-256` · `Router_Design.py:224`)은 구조물 직접 그대로 둠.
|
||||
- 시험 — `test_323_4_attach_mirror` · `test_b06_structure_walls_mirror` 늘림 · `test_272_berm_source` · `test_477_undo_now_berm` · `test_511_a_calc_browser.py:62` · helper 271 · 473.
|
||||
- 값 영향 — 설계 값 0 차(같은 구조물 · 같은 셈). 측점 칸이 3D 지문(`B05_Profile_UI_Corridor_Structures_Hash.ts:41`)에 들어가 소단 측점 3D 저장본이 한 번 다시 셈 · 서버 상세에 새 칸.
|
||||
- 위험 — 중간. 파이썬 얹기 짝 · 서버 Node 묶음(server-calc · corridor) 다시 짓기 · E03 이 같은 얹기를 씀. 저장분 옛 꼴 없음(structures.json · design.berm 그대로). 정본 자리 그대로(제원 = 구조물 배치 · 쪽 깊이 = 횡단 patch).
|
||||
|
||||
**나 — 1차 안 A(사본만 없앰)**
|
||||
- 범위 — 파일 7(`design_pipeline_page.ts` · `_types.ts` · `design_shared.ts` · `Cross_Refresh.ts` · `design_facility_lane.ts` · `Persist.ts` · `b_page_state.ts`) · 파이썬 · Node 묶음 없음.
|
||||
- 값 영향 0 차 · 위험 낮음. 다만 소단 입구는 여전히 「구간 목록 → 측점마다 읽기」라 벽(「측점에 얹기」)과 다름. 가 의 일부(가 를 하면 나 도 됨).
|
||||
|
||||
**다 — 그 밖**
|
||||
- 다-1 계산 뒤 계단 덧 처리(벽과 완전 같은 문) — 2-3 (ㄴ) 까닭으로 권하지 않음(면적 · 비탈 끝 · 겹침 · 여러 자리 따로 고침).
|
||||
- 다-2 그대로 둠 — 값 문제 없음 · 사본 · 두 번 셈은 남음.
|
||||
|
||||
**권함 — 가.** 사용자 뜻(「사면안정 그룹끼리 같게」)에 맞는 것은 입구를 같게 하는 것 · 계단 넣는 시점은 성질이라 남김 · 값 0 차. 한 번에 크면 나 → 가 차례로 해도 끝은 같음.
|
||||
|
||||
### 2-5. 사용자 물음
|
||||
|
||||
- 소단을 옹벽처럼 「측점에 얹는 한 벌」(가)로 갈지 · 사본만 없앨지(나).
|
||||
- 벽과 소단을 한 구간에 같이 놓을 일이 있는지 — 지금 코드로는 벽 상단 바깥 사면을 벽이 잘라 그 아래 소단은 사라짐(화면 확인 못 함).
|
||||
|
||||
### 2-6. 확인 못 함
|
||||
|
||||
- 화면 · 서버 실측 없음 — 밀리는 거리 · 면적 차는 식과 코드 읽기로만 · 실제 측점 수치 안 냄.
|
||||
- 평면 발자국이 벽 트림을 모르는 것(곁 발견) — 코드 읽기만.
|
||||
- 벽 + 소단 한 측점 겹침 모양 — 코드 읽기만.
|
||||
@@ -579,3 +579,21 @@ CAD 시험에서 원래부터 실패하던 11건 정리 <<< 진행하고 나와
|
||||
|
||||
2번 진행
|
||||
4번 전체적으로 사면안정 구조물군의 로직을 보고 어떻게 반영하는게 좋을지 조사.
|
||||
|
||||
## 요구 436 (10-29 · 사용자)
|
||||
|
||||
577은 횡단배수 구조물말고 옹벽같은 구조물인 ㅇ사면안정 그룹과 비교해보라고
|
||||
검증 랩탑메인은 초기화해도 무관
|
||||
|
||||
## 요구 437 (10-29 · 사용자 · 577 답)
|
||||
|
||||
577은 '가'로 진행되어야함. 3d까지 연걀이되어야하고 개발순서 상 나중에 반영되다보니 누락되었던것임. 따지고 보면 옹벽과 크게 다를것고 없는데 소단들어가는 부분을 자르고 수평선과 거기에 따른 신규 성토선이 들어오면 되는것임
|
||||
|
||||
## 요구 438 (10-29 · 사용자)
|
||||
|
||||
1.위에 흠에 대한 개선 방향성이 문제임. 이전에는 서버와 브라우져의 계산값이 저장시 다를것을 대비하여 저장시 브라우져의 사용자 입력값을 서버로 젘달하게 했던걸로 기억함. 동일한게 반영할건지
|
||||
2. 평면 비탈끝은 구조물이 있는경우 어떻게할건지 설몉
|
||||
|
||||
## 요구 439 (10-29 · 사용자 · 438 답)
|
||||
|
||||
진행
|
||||
|
||||
@@ -35,8 +35,6 @@ const STUBS = {
|
||||
}),
|
||||
"ui_template_elements.ts": anyModule(),
|
||||
"B04_PreProcess_Api_Fetch.ts": anyModule(),
|
||||
// 소단 사본(467) — 끌어오는 줄이 import.meta 를 써 이 로더로 못 읽음 · 소단 몫은 test_272_berm_source(vite 묶음)
|
||||
"B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }),
|
||||
};
|
||||
|
||||
const resolve = (base) =>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 467 헬퍼 — 소단 원천 한 벌: 구조물 → 소단 구간(TS) · 저장분 소단 제원(TS) · 시설 줄이 세션 사본을 씀 ·
|
||||
* 다시 셈 = 사본 없으면 저장분 `design.berm`.
|
||||
/* 467 · 577-2 헬퍼 — 소단 원천 한 벌: 구조물 → 소단 구간(TS) · 측점 얹기(TS) · 저장분 소단 제원(TS) ·
|
||||
* 다시 셈 = 얹힌 `section.berm` · 칸 없으면 저장분 `design.berm`.
|
||||
* node helper_272_berm_source.mjs <묶음.mjs> <job.json>
|
||||
* job = { standard, rockDefault, structureCases, storedCases, ground } · 짝 = `test_272_berm_source.py`.
|
||||
* job = { standard, rockDefault, structureCases, storedCases, ground, chainages } · 짝 = `test_272_berm_source.py`.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -45,34 +45,12 @@ const out = {};
|
||||
out.spans = job.structureCases.map((list) => m.bermSpansFromStructures(list));
|
||||
out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
|
||||
/* 2. 시설 줄이 사본을 씀 — 구조물 받기 전 안 씀 · 같으면 안 씀 · 바뀌면 한 번 */
|
||||
{
|
||||
m.setPageRouteId(P, R);
|
||||
const key = m.stateKey("berm", P, R);
|
||||
const count = () => writes.filter((k) => k === key).length;
|
||||
const copy = () => m.readState("berm", P, R);
|
||||
const lane = m.createFacilityLane(P, () => null);
|
||||
const steps = {};
|
||||
lane.setTypes([]);
|
||||
lane.follow(); // 저장분 · 가게 구조물 없음 = 아직 모름
|
||||
steps.beforeKnown = { copy: copy(), writes: count() };
|
||||
const saved = job.structureCases[0];
|
||||
lane.setStored(saved);
|
||||
lane.set({ structures: saved }, false);
|
||||
steps.loaded = { copy: copy(), writes: count() };
|
||||
lane.set({ structures: saved.map((item) => ({ ...item })) });
|
||||
lane.set({ pipes: [] });
|
||||
steps.same = { writes: count() };
|
||||
const moved = saved.map((item) =>
|
||||
item.type_id === "berm" ? { ...item, start_m: item.start_m + 5, end_m: item.end_m + 5 } : item,
|
||||
);
|
||||
lane.set({ structures: moved });
|
||||
steps.moved = { copy: copy(), writes: count() };
|
||||
lane.set({ structures: [] });
|
||||
steps.removed = { copy: copy(), writes: count() };
|
||||
lane.dispose();
|
||||
out.lane = steps;
|
||||
}
|
||||
/* 2. 측점 얹기 — 구조물마다 측점 목록에 얹은 칸(덮는 소단 없음 = null · 누가거리 없음 = 칸 없음) */
|
||||
out.attach = job.structureCases.map((list) => {
|
||||
const sections = job.chainages.map((c) => ({ chainage_m: c }));
|
||||
m.attachBermSpecs(sections, m.bermSpansFromStructures(list));
|
||||
return sections;
|
||||
});
|
||||
|
||||
/* 2-2. 시설 줄 칠함 — 종류 표 받기 전 측점(빈 목록)을 종단도에 안 씀 · 받은 뒤 한 번 */
|
||||
{
|
||||
@@ -132,10 +110,9 @@ out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
],
|
||||
balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 },
|
||||
};
|
||||
const run = async (copy) => {
|
||||
if (copy === undefined) sessionStorage.removeItem(m.stateKey("berm", P, R));
|
||||
else m.writeState("berm", copy, P, R);
|
||||
const run = async (berm) => {
|
||||
const detail = detailOf();
|
||||
if (berm !== undefined) detail.cross_sections[0].berm = berm;
|
||||
await m.refreshCrossDesigns({
|
||||
projectId: P,
|
||||
routeId: R,
|
||||
@@ -147,9 +124,9 @@ out.stored = job.storedCases.map((spec) => m.storedBermSpec(spec));
|
||||
return { fill: design.fill_area_m2 ?? null, cut: design.cut_area_m2 ?? null };
|
||||
};
|
||||
out.refresh = {
|
||||
noCopy: await run(undefined),
|
||||
copySame: await run(m.bermSpansFromStructures(job.structureCases[0])),
|
||||
copyEmpty: await run([]),
|
||||
unattached: await run(undefined),
|
||||
attachedSame: await run(job.storedBerm),
|
||||
attachedNone: await run(null),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* 467 시험 묶음 진입점 — 소단 원천 한 벌(시설 줄 사본 · 다시 셈 저장분 폴백 · 진짜 모듈 · vite ssr). */
|
||||
/* 467 · 577-2 시험 묶음 진입점 — 소단 원천 한 벌(측점 얹기 · 다시 셈 저장분 폴백 · 진짜 모듈 · vite ssr). */
|
||||
export { createFacilityLane } from "../../A00_Common/design/design_facility_lane";
|
||||
export { setPageRouteId } from "../../A00_Common/design/design_page_route";
|
||||
export { refreshCrossDesigns, storedBermSpec } from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||
export { bermSpansFromStructures } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
export { attachBermSpecs, bermSpansFromStructures } from "../../common_util/common_util_berm_spans";
|
||||
export { rememberRockBoundaryDefault } from "../../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
export { readState, stateKey, writeState } from "../../A00_Common/b_page_state";
|
||||
|
||||
@@ -38,13 +38,6 @@ const R = 6;
|
||||
const AT = 40;
|
||||
m.writeState("std-cross", job.standard, P);
|
||||
m.rememberRockBoundaryDefault(P, job.rockDefault);
|
||||
// 소단 사본 = 측점을 덮는 구간(소단 깊이가 먹게)
|
||||
m.writeState(
|
||||
"berm",
|
||||
[{ start_m: 0, end_m: 100, width_m: 1, interval_m: 3, slope_deg: 0, depth_m: 1 }],
|
||||
P,
|
||||
R,
|
||||
);
|
||||
const canonDetail = () => ({
|
||||
longitudinal: { length_m: 100, stations: [], profile_alignment: { policy: {} } },
|
||||
cross_sections: [
|
||||
@@ -59,6 +52,8 @@ const canonDetail = () => ({
|
||||
center_y: 0,
|
||||
frame: { left_xy: [0, 1] },
|
||||
uphill_side: "left",
|
||||
// 측점에 얹힌 소단(577-2 · 소단 깊이가 먹게)
|
||||
berm: { width_m: 1, interval_m: 3, slope_deg: 0, depth_m: 1 },
|
||||
samples: job.ground,
|
||||
design: {
|
||||
design_elevation_m: 100,
|
||||
|
||||
@@ -80,8 +80,24 @@ STUBS["B05_Profile_Api_Replan"] = {
|
||||
station_snap: false,
|
||||
}),
|
||||
};
|
||||
// ⑪ 소단 프로젝트 = 저장 구조물에 소단 한 구간(577-2 · 측점에 얹힌 제원으로 셈)
|
||||
const BERM_SPAN = { from: 30, to: 70 };
|
||||
STUBS["B05_Profile_Api_Structures"] = {
|
||||
fetchStructures: async () => ({ structures: [] }),
|
||||
fetchStructures: async (projectId) => ({
|
||||
structures:
|
||||
projectId === "proj-365c"
|
||||
? [
|
||||
{
|
||||
structure_id: "berm-1",
|
||||
type_id: "berm",
|
||||
placement: "interval",
|
||||
start_m: BERM_SPAN.from,
|
||||
end_m: BERM_SPAN.to,
|
||||
options: { width_m: 1.5, interval_m: 5, slope_deg: 60, depth_m: 1 },
|
||||
},
|
||||
]
|
||||
: [],
|
||||
}),
|
||||
fetchStructureTypes: async () => job.types,
|
||||
};
|
||||
STUBS["B05_Profile_Api_TerrainGrid"] = {
|
||||
@@ -421,17 +437,11 @@ async function run() {
|
||||
pageMod.disposePagePipeline(Q);
|
||||
page.clearDrafts(Q);
|
||||
|
||||
// ⑪ 초안 소단(365-5) — 소단 = 지금 구조물 구간(bermAt) · 쪽 깊이 = 세션 고침(측점 열쇠) → 묶음 cross
|
||||
// ⑪ 초안 소단(365-5) — 소단 = 지금 구조물 구간(측점에 얹힌 제원 · 577-2) · 쪽 깊이 = 세션 고침(측점 열쇠) → 묶음 cross
|
||||
const B = "proj-365c";
|
||||
window.localStorage.setItem("frd_current_project_id", B);
|
||||
const span = { from: 30, to: 70 };
|
||||
const bBridge = pageMod.routeDraftBridge(B, link(), {
|
||||
stationIntervalM: job.interval,
|
||||
bermAt: () => (c) =>
|
||||
c >= span.from && c <= span.to
|
||||
? { width_m: 1.5, interval_m: 5, slope_deg: 60, depth_m: 1 }
|
||||
: null,
|
||||
});
|
||||
const span = BERM_SPAN;
|
||||
const bBridge = pageMod.routeDraftBridge(B, link(), { stationIntervalM: job.interval });
|
||||
pageMod.pipelineEarthwork(B, { conversion: job.conversion });
|
||||
const bPipe = pageMod.pagePipeline(B);
|
||||
await settle(runs().length + 1);
|
||||
|
||||
@@ -69,10 +69,6 @@ const savedStations = m.buildStations(savedLine(), savedCurves(), pipes, structu
|
||||
});
|
||||
const crossSource = m.pipelineCrossSource(P, detail, savedStations, R);
|
||||
const edits = m.readAlignment(detail.longitudinal).edits;
|
||||
const bermAt = (list) => {
|
||||
const spans = m.bermSpansFromStructures(Array.isArray(list) ? list : []);
|
||||
return (chainageM) => m.bermSpecAt(spans, chainageM);
|
||||
};
|
||||
const crossStage = m.createCrossStage({ grid: () => ({ grid }) });
|
||||
const pipe = m.createDesignPipeline({
|
||||
stages: {
|
||||
@@ -119,7 +115,6 @@ const pipe = m.createDesignPipeline({
|
||||
ctx.inputs.cross,
|
||||
ctx.out.ground?.stations ?? [],
|
||||
m.shapeStatus(ctx.inputs.pipes, ctx.inputs.structures, types),
|
||||
bermAt(ctx.inputs.structures),
|
||||
),
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -30,7 +30,6 @@ export {
|
||||
applyStructureAreaRows,
|
||||
structureAreaRows,
|
||||
} from "../../B06_Section/logic/B06_Section_Structure_Layouts";
|
||||
export { bermSpansFromStructures, bermSpecAt } from "../../B06_Section/B06_Section_UI_Page_Persist";
|
||||
export { parseDtmGrid } from "../../common_util/common_util_surface_sampler";
|
||||
export { computeMassHaul } from "../../common_util/common_util_mass_haul";
|
||||
export { writeState, writeStateRaw } from "../../A00_Common/b_page_state";
|
||||
|
||||
@@ -49,7 +49,6 @@ refreshDesigns(
|
||||
rockOffsets: [],
|
||||
rockDefault: job.rockDefault,
|
||||
cutSlopes: [],
|
||||
bermSpans: null,
|
||||
bermDepths: [],
|
||||
choices: [],
|
||||
shoulderExtends: [],
|
||||
|
||||
@@ -183,14 +183,9 @@ await sync.handleDesignChange(20, { two_stage_slope: true }).catch(() => undefin
|
||||
out.choice = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
||||
await sync.handleDesignChange(20, {}).catch(() => undefined);
|
||||
out.choiceAfterEmpty = Object.fromEntries(m.crossDesignChoices("pD", 7));
|
||||
// 소단 사본(시설 줄이 열 때 구조물에서 씀) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘
|
||||
// 결과 통 칸(계산 묶음 결과) — 떠남 확인에 안 잡힘 · 진짜 고침은 잡힘(소단 사본 칸은 걷음 · 577-2)
|
||||
sessionStorage.clear();
|
||||
m.writeState(
|
||||
"berm",
|
||||
[{ start_m: 0, end_m: 40, width_m: 1, interval_m: 5, slope_deg: 45, depth_m: 1 }],
|
||||
"pB",
|
||||
7,
|
||||
);
|
||||
m.writeState("design-stations", [{ chainage_m: 0 }], "pB");
|
||||
out.dirtyBermOnly = m.designDirty("pB");
|
||||
m.writeCrossDesignChoice("pB", 7, 20, { ditch_choice: false });
|
||||
out.dirtyWithEdit = m.designDirty("pB");
|
||||
|
||||
@@ -35,8 +35,6 @@ const STUBS = {
|
||||
}),
|
||||
"ui_template_elements.ts": anyModule(),
|
||||
"B04_PreProcess_Api_Fetch.ts": anyModule(),
|
||||
// 소단 사본(467) — 끌어오는 줄이 import.meta 를 써 이 로더로 못 읽음 · 소단 몫은 test_272_berm_source(vite 묶음)
|
||||
"B06_Section_UI_Page_Persist.ts": anyModule({ bermSpansFromStructures: () => [] }),
|
||||
};
|
||||
|
||||
const resolve = (base) =>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""467 (요구 272 · 분석 4장 (B2)) — 소단 원천 한 벌.
|
||||
"""467 · 577-2 (요구 272 · 434 · 437) — 소단 원천 한 벌 = 측점에 얹은 제원(옹벽처럼).
|
||||
|
||||
못박는 것
|
||||
- 소단 사본(세션 `berm`) 쓰는 자리 = 페이지 시설 줄 하나 — 구조물 받기 전 안 씀 ·
|
||||
같으면 안 씀 · 바뀌면 한 번 · 지우면 빈 목록(B05 만 열어도 씀 · 횡단 보기는 안 씀).
|
||||
- 다시 셈 = 사본 없으면 저장분 `design.berm`(파이썬 `load_berm_spans` 규칙) —
|
||||
B05 만 연 값 = B06 연 뒤 값 = [저장] 서버 값(`enforce_design_lines`) · 빈 사본 = 소단 없음.
|
||||
- 측점 얹기 — 구조물 → 측점마다 `section.berm`(덮는 소단 없음 = null · 겹치면 먼저 놓은 것) ·
|
||||
TS `attachBermSpecs` ↔ 파이썬 `attach_berm_specs`.
|
||||
- 다시 셈 = 얹힌 제원 · 칸 없음(아직 안 얹음) = 저장분 `design.berm` · null = 소단 없음 —
|
||||
얹힌 값 = 저장분 값 = [저장] 서버 값(`enforce_design_lines`).
|
||||
- 거울 — TS `bermSpansFromStructures` ↔ 파이썬 `load_berm_spans` ·
|
||||
TS `storedBermSpec` ↔ 파이썬 `berm_from`.
|
||||
- 세션 사본(`berm` 칸 · 시설 줄 `syncBerm`) 없음.
|
||||
|
||||
Node 헬퍼 `helper_272_berm_source.mjs` 가 vite ssr 로 묶은 진짜 모듈을 부름.
|
||||
"""
|
||||
@@ -25,7 +26,10 @@ ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import load_berm_spans # noqa: E402
|
||||
from B06_Section.B06_Section_Engine_Structures_Wall import ( # noqa: E402
|
||||
attach_berm_specs,
|
||||
load_berm_spans,
|
||||
)
|
||||
from B06_Section.B06_Section_Router_Design import berm_from, enforce_design_lines # noqa: E402
|
||||
from config.config_system import ( # noqa: E402
|
||||
STANDARD_CROSS_SECTION,
|
||||
@@ -69,6 +73,7 @@ GROUND = [
|
||||
for o in range(-40, 41)
|
||||
]
|
||||
STORED_BERM = {"width_m": 1.0, "interval_m": 3.0, "slope_deg": 0.0, "depth_m": 1.0}
|
||||
CHAINAGES = [0.0, 10.0, 25.0, 30.0, 35.0, 55.0, 65.0, 100.0, 100.5, None]
|
||||
LONG = {
|
||||
"design_profiles": [
|
||||
{
|
||||
@@ -126,6 +131,7 @@ def out(tmp_path_factory):
|
||||
"storedCases": STORED_CASES,
|
||||
"ground": GROUND,
|
||||
"storedBerm": STORED_BERM,
|
||||
"chainages": CHAINAGES,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
@@ -163,25 +169,27 @@ def test_거울_저장분_소단_제원(out):
|
||||
assert ts == pytest.approx(want), spec
|
||||
|
||||
|
||||
def test_시설_줄이_사본을_씀(out):
|
||||
lane = out["lane"]
|
||||
assert lane["beforeKnown"] == {"copy": None, "writes": 0} # 구조물 받기 전 = 안 씀
|
||||
assert lane["loaded"]["writes"] == 1
|
||||
assert lane["loaded"]["copy"] == out["spans"][0] # 저장 구조물 소단 구간
|
||||
assert lane["same"]["writes"] == 1 # 같은 구간 · 관만 알림 = 안 씀
|
||||
assert lane["moved"]["writes"] == 2
|
||||
assert lane["moved"]["copy"][0]["start_m"] == 5.0 and lane["moved"]["copy"][0]["end_m"] == 105.0
|
||||
assert lane["removed"] == {
|
||||
"copy": [],
|
||||
"writes": 3,
|
||||
} # 소단 지움 = 빈 사본(저장분 소단으로 안 물러섬)
|
||||
def test_거울_측점_얹기(out, tmp_path):
|
||||
for index, (structures, ts) in enumerate(zip(STRUCTURE_CASES, out["attach"], strict=True)):
|
||||
spans = load_berm_spans(_project(tmp_path / f"a{index}", structures))
|
||||
py = [{"chainage_m": c} for c in CHAINAGES]
|
||||
attach_berm_specs(py, spans)
|
||||
assert [("berm" in r) for r in ts] == [("berm" in r) for r in py], index
|
||||
for got, exp in zip(ts, py, strict=True):
|
||||
if exp.get("berm") is None:
|
||||
assert got.get("berm") is None, (index, got, exp)
|
||||
else:
|
||||
assert got["berm"] == pytest.approx(exp["berm"]), (index, got, exp)
|
||||
first = out["attach"][0]
|
||||
assert first[1]["berm"] == pytest.approx(STORED_BERM) and first[8]["berm"] is None
|
||||
assert "berm" not in first[-1] # 누가거리 없음 = 칸 없음
|
||||
|
||||
|
||||
def test_다시_셈_B05만_B06_저장_같은_값(out, tmp_path):
|
||||
r = out["refresh"]
|
||||
no_copy, same, empty = r["noCopy"], r["copySame"], r["copyEmpty"]
|
||||
assert no_copy["fill"] == same["fill"] and no_copy["cut"] == same["cut"] # B05 만 = B06 연 뒤
|
||||
assert empty["fill"] != no_copy["fill"] # 빈 사본 = 소단 없음(저장분으로 안 물러섬)
|
||||
no_copy, same, empty = r["unattached"], r["attachedSame"], r["attachedNone"]
|
||||
assert no_copy["fill"] == same["fill"] and no_copy["cut"] == same["cut"] # 안 얹음 = 저장분
|
||||
assert empty["fill"] != no_copy["fill"] # 얹었는데 없음 = 소단 없음(저장분으로 안 물러섬)
|
||||
# [저장] = 서버가 구조물 정본으로 다시 셈
|
||||
root = _project(tmp_path / "save", SAVED)
|
||||
design = {"ground_type": "soil", "section_mode": "both_fill", "ditch_type": "standard"}
|
||||
@@ -193,11 +201,27 @@ def test_다시_셈_B05만_B06_저장_같은_값(out, tmp_path):
|
||||
assert abs(bare[0]["design"]["fill_area_m2"] - empty["fill"]) < 1e-3
|
||||
|
||||
|
||||
def test_쓰는_자리_하나():
|
||||
sync = (ROOT / "B06_Section" / "B06_Section_UI_Page_Design_Sync.ts").read_text(encoding="utf-8")
|
||||
assert "writeBermSpans" not in sync and "useStructureIds(projectId, structures)" in sync
|
||||
lane = (ROOT / "A00_Common" / "design" / "design_facility_lane.ts").read_text(encoding="utf-8")
|
||||
assert "writeBermSpans(projectId, routeId, next)" in lane
|
||||
def test_사본_없음():
|
||||
for rel in (
|
||||
"A00_Common/design/design_facility_lane.ts",
|
||||
"A00_Common/b_page_state.ts",
|
||||
"B06_Section/B06_Section_UI_Page_Persist.ts",
|
||||
"B06_Section/B06_Section_Cross_Refresh.ts",
|
||||
"A00_Common/design/design_shared.ts",
|
||||
):
|
||||
text = (ROOT / rel).read_text(encoding="utf-8")
|
||||
for word in (
|
||||
"writeBermSpans",
|
||||
"syncBerm",
|
||||
"putBerm",
|
||||
'readState<BermSpan[]>("berm"',
|
||||
"bermAt:",
|
||||
):
|
||||
assert word not in text, (rel, word)
|
||||
shape = (ROOT / "common_util" / "common_util_structure_shape_keys.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
assert '"berm"' in shape
|
||||
|
||||
|
||||
def test_시설_줄_종류_표_전_측점_안_칠함(out):
|
||||
|
||||
@@ -188,10 +188,14 @@ def test_every_shape_lands(ts: dict) -> None:
|
||||
"revetment",
|
||||
"pavement",
|
||||
"underdrain",
|
||||
"berm",
|
||||
} <= keys
|
||||
assert any(section.get("berm") for section in ts["cases"]["all"]) # 소단 = 측점에 얹음(577-2)
|
||||
old = ts["cases"]["all"][1] # 옛 물넘이 자리(5 m) — 걷힘
|
||||
assert "ford_pavement" not in old
|
||||
assert all(len(section) == 1 for section in ts["cases"]["empty"])
|
||||
# 구조물 없음 = 소단 「얹었는데 없음」(null)만
|
||||
assert all(set(section) == {"chainage_m", "berm"} for section in ts["cases"]["empty"])
|
||||
assert all(section["berm"] is None for section in ts["cases"]["empty"])
|
||||
|
||||
|
||||
def test_blank_chips(ts: dict) -> None:
|
||||
|
||||
@@ -19,9 +19,9 @@ if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope import ( # noqa: E402
|
||||
final_slope_end,
|
||||
route_frame,
|
||||
side_is_cut,
|
||||
slope_end,
|
||||
slope_pieces,
|
||||
)
|
||||
|
||||
@@ -93,6 +93,9 @@ def _stations() -> list[dict]:
|
||||
"samples": samples,
|
||||
}
|
||||
)
|
||||
# 벽 측점(578-2) — B06 이 얹은 구조물 처리 뒤 비탈 끝이 설계선 끝보다 앞섬
|
||||
stations[5]["design"]["slope_end_left_m"] = 4.2
|
||||
stations[6]["design"]["slope_end_right_m"] = -3.1
|
||||
stations.append({**stations[3], "chainage_m": 70.0, "design": {"road_edges": None}})
|
||||
stations.append({**stations[4], "chainage_m": 90.0, "samples": []}) # 지반 없음
|
||||
return stations
|
||||
@@ -107,13 +110,13 @@ def _py_record(station: dict) -> dict | None:
|
||||
right_edge = float(edges["right"]["offset_m"])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
line, samples = design.get("design_line") or [], station["samples"] or []
|
||||
samples = station["samples"] or []
|
||||
record = {
|
||||
"chainage_m": float(station["chainage_m"]),
|
||||
"left_edge_m": left_edge,
|
||||
"left_end_m": slope_end(line, samples, left_edge, "left"),
|
||||
"left_end_m": final_slope_end(design, samples, left_edge, "left"),
|
||||
"right_edge_m": right_edge,
|
||||
"right_end_m": slope_end(line, samples, right_edge, "right"),
|
||||
"right_end_m": final_slope_end(design, samples, right_edge, "right"),
|
||||
}
|
||||
for side in ("left", "right"):
|
||||
elevation = edges[side].get("elevation_m")
|
||||
|
||||
@@ -104,6 +104,6 @@ def test_세션_고름은_바꾼_칸끼리_얹음(out):
|
||||
assert out["choiceAfterEmpty"] == out["choice"]
|
||||
|
||||
|
||||
def test_소단_사본은_떠남_확인에_안_잡힘(out):
|
||||
def test_결과_통은_떠남_확인에_안_잡힘(out):
|
||||
assert out["dirtyBermOnly"] is False
|
||||
assert out["dirtyWithEdit"] is True
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
못박는 것
|
||||
- 477-3 조작 놓은 직후(찍기 기다림 400 ms 안) ↶ 켜짐 · 단추 맞춤 알림 · 바뀐 것 없는 누름은 안 켜짐.
|
||||
- 기다림 중 ↶ = 먼저 찍고 되돌림(한 번에 조작 전) · ↷ = 다시 · 서버 다시 셈 뒤 쓰기 묶음은 그대로 한 걸음.
|
||||
- 477-4 `berm` = 결과 통(사진 · 떠남 물음 밖) · 떠남 제외 목록에 사본 줄 없음.
|
||||
- 477-4 결과 통 칸은 사진 밖 · 소단 사본 칸 `berm` 은 걷음(577-2 · 측점에 얹힌 제원) ·
|
||||
떠남 목록에 줄 없음.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_477_undo_now_berm.py -q
|
||||
"""
|
||||
@@ -41,7 +42,7 @@ const { createProfileHistory, onPageHistory } = loadTs(process.argv[4]);
|
||||
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const PIPES = "aislo:draft:pipes:p1";
|
||||
const CROSS = "aislo:draft:crossdesign:p1:7";
|
||||
const BERM = "aislo:result:berm:p1:7";
|
||||
const BERM = "aislo:result:design-stations:p1";
|
||||
const out = {};
|
||||
let changes = 0;
|
||||
onPageHistory({ change: () => (changes += 1) });
|
||||
@@ -81,7 +82,7 @@ sessionStorage.setItem(PIPES, "b");
|
||||
lift();
|
||||
await wait(200);
|
||||
sessionStorage.setItem(CROSS, "y");
|
||||
sessionStorage.setItem(BERM, '[{"s":1}]'); // 사본 = 결과 통 — 사진 밖
|
||||
sessionStorage.setItem(BERM, '[{"s":1}]'); // 결과 통 칸 — 사진 밖
|
||||
await wait(300);
|
||||
h2.undo();
|
||||
out.grouped = [sessionStorage.getItem(PIPES), sessionStorage.getItem(CROSS)];
|
||||
@@ -128,15 +129,15 @@ def test_기다림_중_되돌리기_먼저_찍고_한_걸음(result) -> None:
|
||||
|
||||
def test_서버_다시_셈_묶음_그대로(result) -> None:
|
||||
assert result["grouped"] == ["a", "x"] and result["groupedLeft"] is False
|
||||
assert result["bermKept"] == '[{"s":1}]' # 결과 통 사본은 되돌리기가 안 건드림
|
||||
assert result["bermKept"] == '[{"s":1}]' # 결과 통 칸은 되돌리기가 안 건드림
|
||||
|
||||
|
||||
def test_바뀐_것_없는_누름은_안_켜짐(result) -> None:
|
||||
assert result["idleCanUndo"] is False
|
||||
|
||||
|
||||
def test_소단_사본은_결과_통() -> None:
|
||||
def test_소단_사본_칸_없음() -> None:
|
||||
state = (ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8")
|
||||
assert re.search(r'^\s*berm: \{ bucket: "result", scope: "route" \}', state, re.M)
|
||||
assert not re.search(r"^\s*berm: \{", state, re.M) # 577-2 — 측점에 얹힌 제원(사본 걷음)
|
||||
leave = (ROOT / "A00_Common" / "design" / "design_leave.ts").read_text(encoding="utf-8")
|
||||
assert '"berm"' not in leave
|
||||
|
||||
@@ -57,9 +57,9 @@ def test_자리마다_판_열쇠_저장_길_안_부름():
|
||||
assert 'calcOnServerNow("quantity")' in refresh and '"F7.saved"' in refresh
|
||||
shared = _text("A00_Common/design/design_shared.ts")
|
||||
assert 'calcChecked("quantity", "structure.layouts"' in shared
|
||||
assert 'calcChecked("quantity", "cross.berm_spans"' in shared
|
||||
lane = _text("A00_Common/design/design_facility_lane.ts")
|
||||
assert '"C4.copy", bermRequest(structures)' in lane
|
||||
# 소단 = 측점에 얹힌 제원(577-2) — 사본 · 초안 구간 셈 부름 없음
|
||||
assert '"cross.berm_spans"' not in shared
|
||||
assert '"C4.copy"' not in _text("A00_Common/design/design_facility_lane.ts")
|
||||
mass = _text("B05_Profile/B05_Profile_UI_Profile_MassHaul.ts")
|
||||
assert 'calcFromServer<MassHaulSeries[]>("quantity", "masshaul.series"' in mass
|
||||
assert "openCalcSites(projectId)" in _text("A00_Common/design/design_page.ts")
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
"""578-2 평면 비탈 끝 = 횡단 최종 모양(구조물 처리 뒤)이 땅과 만나는 점.
|
||||
|
||||
- 벽(옹벽) 측점 — `structure.layouts`(서버 Node 묶음 · B06 `STRUCTURE_ROW_KEYS`)가
|
||||
`slope_end_<쪽>_m` 을 얹음 · 벽 앞(성토 위 이음 → 전면 발끝)이 땅과 만나는 자리
|
||||
· 설계선 끝보다 안쪽
|
||||
- 구조물 없는 측점 · 벽 반대쪽 — 키 없음(평면 선 전후 0 차)
|
||||
- 파이썬 짝 `final_slope_end` — 키가 있으면 그 값 · 없으면 설계선 끝
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_578_2_plan_slope_end.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope import ( # noqa: E402
|
||||
final_slope_end,
|
||||
slope_end,
|
||||
)
|
||||
from common_util.common_util_calc_runner import BUNDLE, run_calc # noqa: E402
|
||||
|
||||
|
||||
def _ground(offset: float) -> float:
|
||||
"""좌(+)로 내려가는 지반 — 좌 = 쌓기 · 우 = 깎기."""
|
||||
return 100.0 - 0.5 * offset
|
||||
|
||||
|
||||
def _section(chainage: float, wall: bool) -> dict:
|
||||
offsets = [-20.0 + 0.5 * i for i in range(81)]
|
||||
line = []
|
||||
for o in offsets:
|
||||
if -2.5 <= o <= 2.5:
|
||||
z = 100.0
|
||||
elif o > 2.5: # 쌓기 1:1.5 → 10 m 에서 땅
|
||||
z = max(100.0 - (o - 2.5) / 1.5, _ground(o))
|
||||
else: # 깎기 1:1 → -5 m 에서 땅
|
||||
z = min(100.0 + (-2.5 - o), _ground(o))
|
||||
line.append({"offset_m": o, "elevation_m": z})
|
||||
section = {
|
||||
"chainage_m": chainage,
|
||||
"center_x": 0.0,
|
||||
"center_y": chainage,
|
||||
"samples": [{"offset_m": o, "elevation_m": _ground(o), "valid": True} for o in offsets],
|
||||
"design": {
|
||||
"design_elevation_m": 100.0,
|
||||
"section_mode": "right_cut",
|
||||
"road_edges": {
|
||||
"left": {"offset_m": 2.5, "elevation_m": 100.0},
|
||||
"right": {"offset_m": -2.5, "elevation_m": 100.0},
|
||||
},
|
||||
"design_line": line,
|
||||
"cut_area_m2": 1.0,
|
||||
"fill_area_m2": 1.0,
|
||||
},
|
||||
}
|
||||
if wall:
|
||||
section["revetment"] = {
|
||||
"structure_id": "w1",
|
||||
"type_id": "retaining_wall",
|
||||
"name": "옹벽",
|
||||
"start_m": 0.0,
|
||||
"end_m": 20.0,
|
||||
"anchor_m": 10.0,
|
||||
"form": "콘크리트",
|
||||
"height_m": 3.0,
|
||||
"side": "좌",
|
||||
}
|
||||
return section
|
||||
|
||||
|
||||
@pytest.mark.skipif(not BUNDLE.is_file(), reason="design_calc 묶음 없음(npm run build:design-calc)")
|
||||
def test_벽_측점만_비탈_끝을_얹음() -> None:
|
||||
sections = [_section(0.0, False), _section(10.0, True), _section(20.0, False)]
|
||||
results = run_calc(["structure.layouts"], {"detail": {"cross_sections": sections}})
|
||||
assert results is not None, "Node 묶음 실패"
|
||||
rows = {row["chainage_m"]: row for row in results["structure.layouts"]["areas"]}
|
||||
plain = slope_end(sections[1]["design"]["design_line"], sections[1]["samples"], 2.5, "left")
|
||||
assert plain == pytest.approx(10.0)
|
||||
end = rows[10.0]["slope_end_left_m"]
|
||||
assert 2.5 < end < plain, f"벽이 자른 끝({end})이 설계선 끝({plain}) 안쪽이어야"
|
||||
assert "slope_end_right_m" not in rows[10.0], "벽 반대쪽은 그대로"
|
||||
for chainage in (0.0, 20.0):
|
||||
assert not any(key.startswith("slope_end_") for key in rows.get(chainage, {}))
|
||||
|
||||
|
||||
def test_파이썬_짝은_얹은_값을_먼저() -> None:
|
||||
section = _section(10.0, False)
|
||||
design, samples = section["design"], section["samples"]
|
||||
assert final_slope_end(design, samples, 2.5, "left") == pytest.approx(10.0)
|
||||
assert final_slope_end({**design, "slope_end_left_m": 4.25}, samples, 2.5, "left") == 4.25
|
||||
assert final_slope_end({**design, "slope_end_left_m": None}, samples, 2.5, "left") == (
|
||||
pytest.approx(10.0)
|
||||
)
|
||||
Reference in New Issue
Block a user