fix(구조물표): 322-3 고침 — B06 을 바로 열어도 옛 소수 관 자리 = 정수 m 초안(B05 snapOldStations 와 같은 셈 · 요구 167) · 계획고 편집 · 고름 따라감 · 알림 한 줄 같은 글 · 투영 이월 끝 = 선 길이(관이 전부 0 m 로 가던 흠) · design_view_cross 685줄 안(322-3 넘침)
- B06 구조물 목록 load: 관 초안 없고 저장분에 소수 자리 = `snapPlan` 으로 옮긴 초안(persistPipes) → 카드 · 측점 계획이 B05 를 먼저 연 것과 같음 · 정본은 [저장]에서(B08 사본 이름도 PUT 길) - 서버 관 읽기는 그대로(파일 = 측점) · `project_pipe_points` 끝 = 선 길이 - 시험: test_322_3_flow 견본(85.05 … 982.64) → 85 · 173 · 258 · 352 · 440 · 534 · 620 · 720 · 804 · 900 · 983 · 정수 저장분 = 새 미리 뜬 카드 0 · 소수 저장분 = 정수 자리로 · test_322_3_saved_snap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
@@ -17,12 +17,7 @@ import { leaveForDashboard } from "../b_missing_data_guard";
|
||||
import { readByKey, stateKey, writeByKey } from "../b_page_state";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { mountMissingStationNotice } from "../../B06_Section/B06_Section_UI_Missing_Stations";
|
||||
import type { PlanPipe } from "../../common_util/common_util_structure_station";
|
||||
import {
|
||||
onStaleStructureTypes,
|
||||
type StructureInstance,
|
||||
type StructureType,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import { onStaleStructureTypes } from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import {
|
||||
fetchSectionContext,
|
||||
getSections,
|
||||
@@ -136,13 +131,7 @@ export async function mountCrossView(
|
||||
);
|
||||
|
||||
// 「구조물 배치」 — 종단 보기와 같은 폼 한 벌에 횡단 보기 동작을 건다(계획서 24-3b).
|
||||
let structureMarksSink:
|
||||
| ((
|
||||
structures: StructureInstance[],
|
||||
types: StructureType[],
|
||||
pipes: ReadonlyArray<PlanPipe>,
|
||||
) => void)
|
||||
| null = null;
|
||||
let structureMarksSink: Parameters<typeof createB06StructuresPanel>[0]["onMarks"] | null = null;
|
||||
/** 관 목록 초안을 고치면 한 벌 배수유역도가 그 초안으로 다시 선다(잇단 조작은 한 번). */
|
||||
let pipeReloadTimer = 0;
|
||||
let wallAdjustTimer = 0;
|
||||
@@ -344,8 +333,7 @@ export async function mountCrossView(
|
||||
shared,
|
||||
);
|
||||
// 좌측 목록이 넘겨 준 구조물을 종단 알약 레인으로 보낸다(표시 통일).
|
||||
structureMarksSink = (structures, types, pipes) =>
|
||||
sectionView.setStructureMarks(structures, types, pipes);
|
||||
structureMarksSink = sectionView.setStructureMarks;
|
||||
// 계획선 편집(▲/▼)은 종단도 한 벌이 맡는다(계획서 24-2c) — 같은 초안 가게 · 같은 막기 규칙.
|
||||
|
||||
// 종단 그래프 우클릭 — 종단 보기와 같은 메뉴로 넣고 뺀다(2026-09-12 사용자). 어느 길로
|
||||
|
||||
@@ -125,11 +125,16 @@ export function snapOldStations(ctx: SnapContext): number {
|
||||
}
|
||||
plan.pipes.forEach(({ from, to, attributes }) => ctx.movePipe(from, to, attributes));
|
||||
}
|
||||
if (count || plan.skipped)
|
||||
showToast(
|
||||
(count ? `옛 소수 측점 ${count}건을 정수 m 로 옮겼습니다 — [저장]하면 남습니다.` : "") +
|
||||
(plan.skipped ? ` 맞출 자리에 다른 관이 있는 관 ${plan.skipped}건은 그대로 둡니다.` : ""),
|
||||
plan.skipped ? "info" : "success",
|
||||
);
|
||||
snapNotice(count, plan.skipped);
|
||||
return count;
|
||||
}
|
||||
|
||||
/** 옛 소수 맞춤 알림 한 줄 — B05 종단 보기 · B06 구조물 목록(관)이 같은 글. */
|
||||
export function snapNotice(count: number, skipped: number): void {
|
||||
if (count || skipped)
|
||||
showToast(
|
||||
(count ? `옛 소수 측점 ${count}건을 정수 m 로 옮겼습니다 — [저장]하면 남습니다.` : "") +
|
||||
(skipped ? ` 맞출 자리에 다른 관이 있는 관 ${skipped}건은 그대로 둡니다.` : ""),
|
||||
skipped ? "info" : "success",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { snapStructureStation } from "../common_util/common_util_structure_station";
|
||||
import { plantsStation } from "../B05_Profile/B05_Profile_UI_Profile_Structures";
|
||||
import { snapNotice, snapPlan } from "../B05_Profile/B05_Profile_UI_Structures_Snap";
|
||||
import {
|
||||
adjustDockRoot,
|
||||
refreshAdjustSlots,
|
||||
@@ -427,6 +428,9 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
(basin) => Math.abs(basin.chainage_m - pipe.chainage_m) < PIPE_MATCH_M,
|
||||
)?.design_flow_m3s ?? null,
|
||||
}));
|
||||
// 옛 소수 관 자리 = 정수 m 초안(요구 167 · B05 `snapOldStations` 와 같은 셈) — B06 을 바로 열어도
|
||||
// 카드 · 측점이 B05 를 거친 것과 같음 · 정본은 [저장]에서
|
||||
if (!pendingPipes) snapOldPipes(projectId);
|
||||
section.setPipeFacilities(pipeFacilities);
|
||||
// 종단 알약 레인 — 구조물 정본 + 계곡 통과 시설(관 정본)을 합쳐 B05 와 같은 표기로.
|
||||
pushMarks();
|
||||
@@ -500,6 +504,24 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
deps.onPipesWritten?.();
|
||||
}
|
||||
|
||||
/** 옛 소수 관 자리 → 정수 m(확인 창 없이) — 계획고 편집 · 고름도 새 자리로(`movePipeTo` 와 같은 따라가기). */
|
||||
function snapOldPipes(projectId: string): void {
|
||||
const plan = snapPlan([], pipeFacilities);
|
||||
plan.pipes.forEach(({ from, to, attributes }) => {
|
||||
const hit = pipeFacilities.find((pipe) => pipe.chainage_m === from);
|
||||
if (!hit) return;
|
||||
deps.shared.profile?.resetStationEdit(from, to);
|
||||
followMovedPipe(projectId, from, to);
|
||||
Object.assign(hit, { chainage_m: to, start_m: attributes.start_m, end_m: attributes.end_m });
|
||||
});
|
||||
if (plan.pipes.length) {
|
||||
pipeFacilities.sort((left, right) => left.chainage_m - right.chainage_m);
|
||||
persistPipes();
|
||||
deps.onStructuresChanged?.(); // 새 자리 횡단을 미리 떠 카드로
|
||||
}
|
||||
snapNotice(plan.pipes.length, plan.skipped);
|
||||
}
|
||||
|
||||
/** 관 옮기기 — 폼의 측점 칸과 종단 끌기가 같은 길을 탄다. 재분할은 [저장]·[확정] 뒤. */
|
||||
function movePipeTo(fromChainageM: number, toChainageM: number): void {
|
||||
if (!deps.projectId) {
|
||||
|
||||
@@ -221,7 +221,8 @@ def project_pipe_points(points: list[PipePoint], vertices: list[RouteVertex]) ->
|
||||
if line.length <= 0:
|
||||
return points
|
||||
# 새 노선 위 자리도 정수 m · 일반 측점 1 m 안이면 그 측점(234-1) — 시설 속성은 관과 함께 옮겨 감
|
||||
stations = route_general_stations(vertices[-1].chainage_m)
|
||||
# 끝 = 선 길이(B05 계획선 꼭짓점은 누가거리 0 으로 와서 끝이 0 → 관이 전부 0 m 로 가던 흠)
|
||||
stations = route_general_stations(line.length)
|
||||
for point in points:
|
||||
if point.x is None or point.y is None:
|
||||
continue
|
||||
|
||||
@@ -68,6 +68,7 @@ function load(target) {
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const need = (spec) => {
|
||||
if (spec.startsWith("@ui/")) return { showToast: () => {} };
|
||||
const alias = spec.startsWith("@util/") ? path.join(ROOT, "common_util", spec.slice(6)) : null;
|
||||
const base = alias ?? path.join(path.dirname(full), spec);
|
||||
const hit = [`${base}.ts`, base.replace(/\.js$/, ".ts"), base].find(
|
||||
@@ -189,6 +190,48 @@ async function run(setup, detail) {
|
||||
sel.selectDesign("P", { chainageM: 101, structureId: "w1" });
|
||||
sel.followMovedPipe("P", 101, 120);
|
||||
out.follow = [moved, sel.readSelection("P")];
|
||||
// 옛 소수 관(검증_sub1 견본) — B06 을 바로 열어도 B05 와 같은 정수 m 초안(`snapPlan` · 요구 167)
|
||||
const snap = load(file("B05_Profile", "B05_Profile_UI_Structures_Snap.ts"));
|
||||
const DEC = [
|
||||
85.05, 173.09, 258.12, 352.14, 440.24, 533.98, 619.71, 720.44, 804.18, 899.91, 982.64,
|
||||
];
|
||||
const ford = new Set([173.09, 352.14]);
|
||||
const decPipes = DEC.map((at) => ({
|
||||
chainage_m: at,
|
||||
facility: ford.has(at) ? "ford_bridge" : "pipe",
|
||||
options: { pipe_diameter_mm: "800" },
|
||||
}));
|
||||
const plan = snap.snapPlan([], decPipes);
|
||||
out.snapTo = [plan.pipes.map((move) => move.to), plan.skipped];
|
||||
const snapped = decPipes.map((pipe) => ({
|
||||
...pipe,
|
||||
chainage_m: plan.pipes.find((move) => move.from === pipe.chainage_m)?.to ?? pipe.chainage_m,
|
||||
}));
|
||||
const gridTo = (end) =>
|
||||
Array.from({ length: end / 20 + 1 }, (_v, i) => ({
|
||||
chainage_m: i * 20,
|
||||
kind: "regular",
|
||||
station_id: `g${i * 20}`,
|
||||
}));
|
||||
const titleAt = (at) => (ford.has(at) ? "세월교" : "파형강관 D800");
|
||||
const own = [85, 173, 258, 352, 534, 804, 983];
|
||||
const onGrid = [440, 620, 720, 900];
|
||||
// 서버가 정수 자리로 심은 저장분(격자 자리는 구조물 측점 하나 · grid) — 새 미리 뜬 카드 0
|
||||
const savedInt = [
|
||||
...gridTo(1000).filter((s) => !onGrid.includes(s.chainage_m)),
|
||||
...onGrid.map((at) => irregular(at, "파형강관 D800", { grid: true })),
|
||||
...own.map((at) => irregular(at, at === 173 || at === 352 ? "세월교" : "파형강관 D800")),
|
||||
];
|
||||
out.snapSavedInt = await run(
|
||||
{ savedPipes: decPipes, draftPipes: snapped, draftStructures: [] },
|
||||
{ cross_sections: savedInt },
|
||||
);
|
||||
// 소수 자리로 심긴 저장분(검증_sub1 지금) = B05 를 먼저 연 것과 같음 — 소수 카드는 빼 두고 정수 자리 미리 뜸
|
||||
const savedDec = [...gridTo(1000), ...DEC.map((at) => irregular(at, titleAt(at)))];
|
||||
out.snapSavedDec = await run(
|
||||
{ savedPipes: decPipes, draftPipes: snapped },
|
||||
{ cross_sections: savedDec },
|
||||
);
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
|
||||
@@ -86,3 +86,28 @@ def test_remove_and_reopen(flows: dict) -> None:
|
||||
[101, "옹벽", False, []],
|
||||
[113, "옹벽", False, []],
|
||||
]
|
||||
|
||||
|
||||
# ── 옛 소수 관(검증_sub1 견본) — B06 을 바로 열어도 B05 와 같은 정수 m 초안(요구 167 · `snapPlan`) ──
|
||||
SNAPPED = [85, 173, 258, 352, 440, 534, 620, 720, 804, 900, 983]
|
||||
|
||||
|
||||
def test_old_decimal_pipes_snap_like_b05(flows: dict) -> None:
|
||||
assert flows["snapTo"] == [SNAPPED, 0]
|
||||
|
||||
|
||||
def test_saved_integer_stations_keep_cards(flows: dict) -> None:
|
||||
# 서버가 정수 자리로 심은 저장분 = 계획과 같음 — 저장 카드 그대로 · 새 미리 뜬 카드 0
|
||||
rows = flows["snapSavedInt"]
|
||||
assert [row[0] for row in _marked(rows)] == SNAPPED
|
||||
assert not any(row[2] for row in rows)
|
||||
assert len(rows) == 58
|
||||
|
||||
|
||||
def test_saved_decimal_stations_follow_plan(flows: dict) -> None:
|
||||
# 소수 자리 저장분 = B05 를 먼저 연 것과 같음 — 소수 카드는 빼 두고 정수 자리(격자 자리는 사본)
|
||||
rows = flows["snapSavedDec"]
|
||||
assert [row[0] for row in _marked(rows)] == SNAPPED
|
||||
assert all(row[2] for row in _marked(rows))
|
||||
assert all(float(row[0]).is_integer() for row in rows)
|
||||
assert len(rows) == 58
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"""PLAN 322-3 고침 — 노선이 바뀌어 관을 좌표로 옮길 때(투영 이월) 끝 = 선 길이.
|
||||
|
||||
B05 계획선 꼭짓점은 누가거리 0 으로 와서 끝이 0 으로 잡혀 관이 전부 0 m 로 가던 흠.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_Sections import _load_pipe_points
|
||||
|
||||
LINE = [[0.0, 0.0, 100.0], [300.0, 0.0, 100.0]]
|
||||
|
||||
|
||||
def test_projected_pipes_snap_to_whole_metres_not_zero(tmp_path) -> None:
|
||||
folder = tmp_path / "B04_PreProcess" / "drainage" / "edits"
|
||||
folder.mkdir(parents=True)
|
||||
points = [
|
||||
{"chainage_m": 85.05, "source": "user", "x": 90.05, "y": 0.3},
|
||||
{"chainage_m": 173.09, "source": "user", "x": 178.4, "y": 0.2},
|
||||
]
|
||||
(folder / "pipe_points.json").write_text(
|
||||
json.dumps({"route_signature": "옛노선", "points": points}), encoding="utf-8"
|
||||
)
|
||||
assert [pipe.chainage_m for pipe in _load_pipe_points(tmp_path, LINE)] == [90.0, 178.0]
|
||||
Reference in New Issue
Block a user