Merge remote-tracking branch 'origin/dev' into sub_laptop_7
This commit is contained in:
@@ -34,8 +34,15 @@ import {
|
||||
readCutSlopeSession,
|
||||
readRockBoundarySession,
|
||||
} from "./B06_Section_UI_Page_Persist";
|
||||
import type { BermSessionSpec } from "./B06_Section_UI_Page_Persist";
|
||||
import type { BermSpec } from "@util/common_util_cross_berm";
|
||||
import type { BermSessionSpec, BermSpan } from "./B06_Section_UI_Page_Persist";
|
||||
import {
|
||||
BERM_DEFAULT_DEPTH_M,
|
||||
BERM_DEFAULT_INTERVAL_M,
|
||||
BERM_DEFAULT_SLOPE_DEG,
|
||||
BERM_DEFAULT_WIDTH_M,
|
||||
type BermSpec,
|
||||
} from "@util/common_util_cross_berm";
|
||||
import { readState } from "../A00_Common/b_page_state";
|
||||
import {
|
||||
crossDesignChoices,
|
||||
readBermDepths,
|
||||
@@ -260,6 +267,24 @@ function bermPayload(
|
||||
type Side = "left" | "right";
|
||||
const SIDES: Side[] = ["left", "right"];
|
||||
|
||||
/** 저장분 소단 제원(`design.berm`) — 짝: 파이썬 `berm_from`(빈 칸 = 기본값 · 깊이 0 미만 = 0 · 못 읽으면 없음). */
|
||||
export function storedBermSpec(berm: unknown): BermSessionSpec | null {
|
||||
if (!berm || typeof berm !== "object" || Array.isArray(berm)) return null;
|
||||
const spec = berm as Record<string, unknown>;
|
||||
const value = (key: string, fallback: number): number => {
|
||||
const raw = spec[key];
|
||||
if (raw === undefined) return fallback;
|
||||
return raw === null || raw === "" || typeof raw === "object" ? Number.NaN : Number(raw);
|
||||
};
|
||||
const out = {
|
||||
width_m: value("width_m", BERM_DEFAULT_WIDTH_M),
|
||||
interval_m: value("interval_m", BERM_DEFAULT_INTERVAL_M),
|
||||
slope_deg: value("slope_deg", BERM_DEFAULT_SLOPE_DEG),
|
||||
depth_m: Math.max(value("depth_m", BERM_DEFAULT_DEPTH_M), 0),
|
||||
};
|
||||
return Object.values(out).every(Number.isFinite) ? out : null;
|
||||
}
|
||||
|
||||
/** 소단 쪽마다 옮긴 깊이(194-1) — 세션 → 저장분 순 · 없으면 null(구간 깊이를 씀). */
|
||||
function bermDepthReader(projectId: string, routeId: number) {
|
||||
const session = readBermDepths(projectId, routeId);
|
||||
@@ -333,11 +358,13 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
// 그려진다. 계산 뒤에 값만 베껴 붙이면 그림은 옛 경사, 숫자만 새것이 된다(2026-09-07).
|
||||
const cutSlopeRatios = readCutSlopeRatios(projectId, input.routeId);
|
||||
// 소단도 같은 성격 — 세션에만 있는 값이라 여기서 실어 주지 않으면 계획선을 고치는
|
||||
// 순간 계단이 사라진다(계획서 3-9).
|
||||
const bermSpans = readBermSpans(projectId, input.routeId);
|
||||
// 순간 계단이 사라진다(계획서 3-9). 사본 없음(시설 줄이 구조물을 받기 전) = 저장분 `design.berm`
|
||||
// (파이썬 `load_berm_spans` 못 읽음 → 저장분 · `stored_berm` 짝 · 467).
|
||||
const bermCopy = readState<BermSpan[]>("berm", projectId, input.routeId);
|
||||
const bermSpans = Array.isArray(bermCopy) ? bermCopy : null;
|
||||
const bermDepthAt = bermDepthReader(projectId, input.routeId);
|
||||
const bermAt = (chainageM: number, design: Record<string, unknown>): BermSpec | null => {
|
||||
const spec = bermSpecAt(bermSpans, chainageM);
|
||||
const spec = bermSpans ? bermSpecAt(bermSpans, chainageM) : storedBermSpec(design.berm);
|
||||
return spec
|
||||
? {
|
||||
widthM: spec.width_m,
|
||||
|
||||
@@ -41,6 +41,7 @@ import { staleKeyAgainstScreen, USER_TOUCHED_KEYS } from "./B06_Section_Cross_Re
|
||||
import { kindChangedChainages, STRUCTURE_ADJUST_KEYS } from "./B06_Section_Api_Culvert_Options";
|
||||
import type { PipelineStation } from "../A00_Common/design/logic/design_pipeline_types";
|
||||
import type { DesignStoreValues } from "../A00_Common/design/design_store";
|
||||
import { useDesignStore } from "../A00_Common/design/design_store";
|
||||
import {
|
||||
carryStationKeys,
|
||||
KEYED_STATES,
|
||||
@@ -237,7 +238,13 @@ export async function swapSectionStations(
|
||||
const interval = stationIntervalOf(current);
|
||||
const oldKeys = registeredKeys(projectId, routeId, current.cross_sections, interval);
|
||||
if (sameStationList(current.cross_sections, oldKeys, stations)) return null;
|
||||
const swap = swapCrossSections(current, stations, oldKeys, interval);
|
||||
const swap = swapCrossSections(
|
||||
current,
|
||||
stations,
|
||||
oldKeys,
|
||||
interval,
|
||||
useDesignStore(projectId, null).get("routeDraft") !== null,
|
||||
); // 초안 없으면 정본 표본(467)
|
||||
// 설계는 사본 — 면적 얹기 · 다시 셈이 앞 상세(초안 전 상세 등)의 설계를 제자리로 안 고침(349-0o)
|
||||
for (const section of swap.detail.cross_sections)
|
||||
if (section.design) section.design = { ...section.design };
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* - `handleDesignChange` : 버튼 선택 즉시 반영 + 세션 초안 기록 + 재계산
|
||||
* - `recomputeIfRock` : 암 경계·암 절토 경사 변경 후 암 측점만 재계산
|
||||
* - `reconcileStaleDesigns`: 횡단 다시 셈을 계산 묶음에 부탁(cross 단계 한 곳 · 387-4)
|
||||
* - `syncBermSpans` : 구조물 목록의 소단을 세션 사본으로 펴기
|
||||
* - `syncBermSpans` : 구조물 측점 열쇠(소단 사본은 페이지 시설 줄이 씀 · 467)
|
||||
* - `applyPanelToAll` : 표준 횡단면 설정 [전체 반영]
|
||||
* 고친 뒤 계산 묶음 cross 입력도 다시(`pipelineCross` · 노선 초안 동안 카드 = 묶음 새 노선 상세 · 365-2).
|
||||
* ========================================================================== */
|
||||
@@ -15,11 +15,6 @@ import {
|
||||
writeCrossDesignChoice,
|
||||
writeShoulderExtend,
|
||||
} from "./B06_Section_Cross_Design_Session";
|
||||
import {
|
||||
bermSpansFromStructures,
|
||||
readBermSpans,
|
||||
writeBermSpans,
|
||||
} from "./B06_Section_UI_Page_Persist";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { L } from "./B06_Section_UI_Page_Common";
|
||||
import type { SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
@@ -137,20 +132,10 @@ export function createDesignSync(ctx: DesignSyncContext): DesignSyncController {
|
||||
await reconcilePagePipeline(projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조물 목록의 **소단**(C군 사면안정)을 세션 사본으로 편다 — 재계산이 측점마다 읽는 값이다.
|
||||
*
|
||||
* 사용자는 「구조물 배치」에서 놓고(2026-09-07 확정), 계산은 그 사본만 본다. 달라졌을 때만
|
||||
* 다시 계산한다 — 목록은 화면을 열 때도 오므로 매번 돌리면 진입이 느려진다.
|
||||
*/
|
||||
/** 구조물 측점 열쇠 = 구조물 id(344-3) — 소단 사본(세션 `berm`)은 페이지 시설 줄 한 곳이 씀(B05 만 열어도 · 467). */
|
||||
function syncBermSpans(structures: ReadonlyArray<StructureInstance>): void {
|
||||
const currentRouteId = ctx.routeId();
|
||||
if (!projectId || currentRouteId === null) return;
|
||||
useStructureIds(projectId, structures); // 구조물 측점 열쇠 = 구조물 id(344-3)
|
||||
const next = bermSpansFromStructures(structures);
|
||||
if (JSON.stringify(next) === JSON.stringify(readBermSpans(projectId, currentRouteId))) return;
|
||||
writeBermSpans(projectId, currentRouteId, next);
|
||||
void reconcileStaleDesigns({ force: true });
|
||||
if (!projectId || ctx.routeId() === null) return;
|
||||
useStructureIds(projectId, structures);
|
||||
}
|
||||
|
||||
/** 패널 [전체 반영](N-2-1 · 346-2): 표준값으로 전 측점을 **한 번** 다시 셈 — 측점마다 돌면
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface SessionMap<V> {
|
||||
values: Map<string, V>;
|
||||
/** 세션값 읽기 — 맵을 비우고 다시 채운다. */
|
||||
load: () => void;
|
||||
/** 맵을 통째로 세션에 쓴다. */
|
||||
/** 맵을 통째로 세션에 쓴다 — 빈 맵이면 칸을 지운다. */
|
||||
persist: () => void;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,8 @@ export function createSessionMap<V>(
|
||||
const key = sessionKey();
|
||||
if (!key) return;
|
||||
try {
|
||||
writeByKey(key, JSON.stringify(Object.fromEntries(values)));
|
||||
// 빈 맵 = 칸 지움(272) — 「{}」 를 남기면 사진첩이 걸음으로 봄
|
||||
writeByKey(key, values.size ? JSON.stringify(Object.fromEntries(values)) : null);
|
||||
} catch {
|
||||
/* 세션 저장 실패는 무시(용량 초과 등) — 값은 메모리에 유지된다. */
|
||||
}
|
||||
@@ -144,10 +145,12 @@ export function createLinkFlagSession(sessionKey: () => string | null): LinkFlag
|
||||
if (!key) return;
|
||||
writeByKey(
|
||||
key,
|
||||
JSON.stringify({
|
||||
detached: Object.fromEntries(detached),
|
||||
grade: Object.fromEntries(followGrade),
|
||||
}),
|
||||
detached.size || followGrade.size
|
||||
? JSON.stringify({
|
||||
detached: Object.fromEntries(detached),
|
||||
grade: Object.fromEntries(followGrade),
|
||||
})
|
||||
: null, // 둘 다 비면 칸 지움(272)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* 받음 — 설계 · 구조물 제원 · 이름 그대로 · 자리(누가거리 · 좌표 · 방위 · 확폭)만 새것(433-4).
|
||||
* · 지반 표본 · 상단측 · 중심고 = 자리 · 축이 같은 앞 횡단이면 앞 값(서버 샘플링 정본 · 덧 측점만 넣어도 나머지
|
||||
* 측점 면적이 브라우저 지형으로 안 바뀜 · 433-11) · 새 측점 · 옮긴 측점만 지반 표본(채워 왔으면).
|
||||
* · 노선 초안 없는 차례(관 · 구조물 · 추가 측점만) = 같은 자리(누가거리 · 좌표 0.01 m · 축 1e-3) 앞 횡단 — 없으면
|
||||
* 그 자리 옛 횡단(격자에 구조물을 놓고 뺌) — 의 기하 · 표본 그대로(브라우저 측점 틀 ↔ 서버 틀 mm 차가 정본 표본을
|
||||
* 안 덮음 · 467 · 요구 272) · 설계는 지금 규칙.
|
||||
* · 새 측점 = 지반 표본 + 기본 설계(계획고 0 → 낡음 → 브라우저 다시 셈이 곧 채움).
|
||||
* · 상단측 = 새 지반 표본으로 다시 셈한 측점은 다시(단면 유형 기본값 재료 · 서버 횡단 파일 `uphill_side` 짝 · 428).
|
||||
* · 0.5 m 넘게 옮긴 관 · 구조물 측점 = 기본 설계 + 사용자 칸(만진 칸 · 지반 등 입력 `ROW_CARRY_KEYS`) — 서버가
|
||||
@@ -146,27 +149,31 @@ export interface StationSwap {
|
||||
moved: boolean;
|
||||
}
|
||||
|
||||
/** 앞 횡단과 자리(누가거리 · 좌표 0.01 m) · 축(왼쪽 단위 벡터)이 같은가 — 같으면 앞 지반 표본을 그대로 씀. */
|
||||
function samePlace(prior: CrossSection, station: PipelineStation): boolean {
|
||||
/** 앞 횡단과 자리(누가거리 · 좌표 0.01 m) · 축(왼쪽 단위 벡터 · 허용 `axisTol`)이 같은가 — 같으면 앞 지반 표본을 그대로 씀. */
|
||||
function samePlace(prior: CrossSection, station: PipelineStation, axisTol = 1e-4): boolean {
|
||||
const [lx, ly] = prior.frame?.left_xy ?? [Number.NaN, Number.NaN];
|
||||
return (
|
||||
Math.abs(prior.chainage_m - station.chainageM) < 0.01 &&
|
||||
Math.hypot(prior.center_x - station.x, prior.center_y - station.y) <= 0.01 &&
|
||||
Math.abs(lx - station.left[0]) < 1e-4 &&
|
||||
Math.abs(ly - station.left[1]) < 1e-4
|
||||
Math.abs(lx - station.left[0]) < axisTol &&
|
||||
Math.abs(ly - station.left[1]) < axisTol
|
||||
);
|
||||
}
|
||||
|
||||
/** 노선 초안 없는 차례의 축 허용(측점 축 0.06° · 20 m 끝 2 cm) — 브라우저 틀 ↔ 서버 틀 차(R 12 곡선 3.3e-4)를 넘김. */
|
||||
const CANON_AXIS_TOL = 1e-3;
|
||||
|
||||
/**
|
||||
* 새 측점 목록으로 상세를 새로 짬. `oldKeys` = 지금 상세 측점마다 열쇠(같은 차례 · `stationKeysOf`) ·
|
||||
* 열쇠가 안 맞으면 옮긴 곡선 열쇠(`curveMoves` · 서버 `carry_cross_inputs` 같은 규칙) · 그다음 같은 자리(0.01 m) ·
|
||||
* 같은 종류의 앞 횡단을 씀.
|
||||
* 같은 종류의 앞 횡단을 씀. `routeDraft` = 노선 초안 차례(가게 `routeDraft` · 아니면 정본 기하 · 표본 유지 · 467).
|
||||
*/
|
||||
export function swapCrossSections(
|
||||
detail: SectionDetailResponse,
|
||||
stations: ReadonlyArray<PipelineStation>,
|
||||
oldKeys: ReadonlyArray<string>,
|
||||
intervalM: number | null,
|
||||
routeDraft = false,
|
||||
): StationSwap {
|
||||
const byKey = new Map(detail.cross_sections.map((section, i) => [oldKeys[i], section]));
|
||||
const used = new Set<CrossSection>();
|
||||
@@ -209,8 +216,18 @@ export function swapCrossSections(
|
||||
moved = true;
|
||||
if (prior && text(prior.chainage_m) !== text(station.chainageM))
|
||||
moves.set(text(prior.chainage_m), text(station.chainageM));
|
||||
// 노선 초안 없는 차례 · 같은 자리 = 앞 횡단(없으면 그 자리 옛 횡단 · 격자에 구조물을 놓고 뺌) 기하 · 표본 그대로
|
||||
// (467 · 되돌리면 정본과 같음)
|
||||
const atPlace = (section: CrossSection | undefined): section is CrossSection =>
|
||||
!!section?.samples?.length && samePlace(section, station, CANON_AXIS_TOL);
|
||||
const canon = routeDraft
|
||||
? undefined
|
||||
: atPlace(prior)
|
||||
? prior
|
||||
: detail.cross_sections.find(atPlace);
|
||||
// 자리 같은 앞 횡단 = 서버 표본 그대로(433-11) · 아니면 새 지반 표본(채워 왔으면)
|
||||
const fresh = !!station.ground && !(prior?.samples?.length && samePlace(prior, station));
|
||||
const fresh =
|
||||
!canon && !!station.ground && !(prior?.samples?.length && samePlace(prior, station));
|
||||
const kind = kindOf(station);
|
||||
const onGrid =
|
||||
kind === "irregular" &&
|
||||
@@ -230,20 +247,38 @@ export function swapCrossSections(
|
||||
label: formatStation(station.chainageM),
|
||||
kind,
|
||||
...(onGrid ? { grid: true } : {}),
|
||||
center_z: fresh
|
||||
? (station.ground?.centerZ ?? prior?.center_z ?? null)
|
||||
: (prior?.center_z ?? null),
|
||||
azimuth_deg: station.azimuthDeg,
|
||||
plan_radius_m: station.planRadiusM,
|
||||
curve_outer_side: station.curveOuterSide,
|
||||
curve_widening_m: station.curveWideningM,
|
||||
center_x: station.x,
|
||||
center_y: station.y,
|
||||
frame: { left_xy: [station.left[0], station.left[1]] as [number, number] },
|
||||
center_z: canon
|
||||
? canon.center_z
|
||||
: fresh
|
||||
? (station.ground?.centerZ ?? prior?.center_z ?? null)
|
||||
: (prior?.center_z ?? null),
|
||||
...(canon
|
||||
? {
|
||||
azimuth_deg: canon.azimuth_deg,
|
||||
plan_radius_m: canon.plan_radius_m,
|
||||
curve_outer_side: canon.curve_outer_side,
|
||||
curve_widening_m: canon.curve_widening_m,
|
||||
center_x: canon.center_x,
|
||||
center_y: canon.center_y,
|
||||
frame: canon.frame,
|
||||
}
|
||||
: {
|
||||
azimuth_deg: station.azimuthDeg,
|
||||
plan_radius_m: station.planRadiusM,
|
||||
curve_outer_side: station.curveOuterSide,
|
||||
curve_widening_m: station.curveWideningM,
|
||||
center_x: station.x,
|
||||
center_y: station.y,
|
||||
frame: { left_xy: [station.left[0], station.left[1]] as [number, number] },
|
||||
}),
|
||||
};
|
||||
longStations.push({ ...place, ...(prior?.structure ? { structure: prior.structure } : {}) });
|
||||
const samples = fresh ? groundSamples(station) : (prior?.samples ?? []);
|
||||
const uphill = fresh ? uphillSide(station) : (prior?.uphill_side ?? null);
|
||||
const samples = canon ? canon.samples : fresh ? groundSamples(station) : (prior?.samples ?? []);
|
||||
const uphill = canon
|
||||
? (canon.uphill_side ?? null)
|
||||
: fresh
|
||||
? uphillSide(station)
|
||||
: (prior?.uphill_side ?? null);
|
||||
if (!prior) return { ...place, uphill_side: uphill, samples, design: freshDesign(uphill) };
|
||||
const reseed =
|
||||
(station.kind === "pipe" || station.kind === "structure") &&
|
||||
|
||||
Reference in New Issue
Block a user