refactor(472-2 2-B 세션 끌림): 서버 Node 묶음(design-calc · sc · co)이 화면 · 세션 코드를 안 끔

- 그림 신원표 = 새 common_util_pick_tag(tagShape · pipeTagId) — 관 id 는 화면이 열 때(usePipeIds) 세션 장부를 꽂음 · 안 꽂으면 옛 자리 이름(장부가 프로젝트 모를 때와 같은 값)
- 구조물 그림 다섯(Box · Culvert · Ford · Ford_Pavement · Revetment)이 design_pipe_ids · design_pick_match 대신 이것을 씀
- Culvert_Wire 한계 토스트 = 알림 창구(setCulvertNotice) — 화면 Cross_View 가 토스트를 꽂음 · Wire 가 ui_template · Section_Common 안 끔
- 472-3 알려진 끌림 다섯 걷음(b_page_state · pick_match · pipe_ids · selection · Section_Common) · 남은 줄 = 헬퍼 정규식 헛잡음
- 헬퍼 286_1 흉내 → 진짜 pick_tag · 폴더 지침 끌림 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 09:09:47 +09:00
co-authored by Claude Opus 5.5
parent 68214889d4
commit a90f8595b8
14 changed files with 68 additions and 52 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ B05 · B06 한 페이지 뼈대 — 주소 둘(`#/b05-profile` · `#/b06-section
서버 Node 묶음:
- 입구 `logic/design_calc_node.ts`(`build:design-calc` · 계산 등록부 `logic/design_calc_registry.ts` 의 `node:` 줄 · 시험 `test_472_3_calc_registry.py`) · 끌려감 = `design_pick_match` · `design_pipe_ids` · `design_selection`(corridor · server-calc · b07-extent) · `design_store` · `design_structure_{box,ford,ford_pavement,guard,pipe,road,spec}`(b07-extent · b07-note) · 상위 `A00_Common/b_page_state`
- 입구 `logic/design_calc_node.ts`(`build:design-calc` · 계산 등록부 `logic/design_calc_registry.ts` 의 `node:` 줄 · 시험 `test_472_3_calc_registry.py`) · 끌려감 = `design_pick_match` · `design_pipe_ids` · `design_selection`(b07-extent) · `design_store` · `design_structure_{box,ford,ford_pavement,guard,pipe,road,spec}`(b07-extent · b07-note) · 상위 `A00_Common/b_page_state`
- 끌려가는 파일 모듈 맨 위에 DOM · window · sessionStorage 금지 · 고치면 해당 build 다시 지어 시험
남의 페이지가 부르는 파일(고치면 부르는 쪽 시험도):
+1 -7
View File
@@ -6,7 +6,7 @@
* ① 고름 · 그림 둘 다 구조물 id 가 있으면 id 가 같아야(관 = 세션 id `design_pipe_ids` · 옮겨도 그대로)
* ② 아니면 같은 자리(`PIPE_MATCH_M` — 같은 자리 거리는 이 한 값)
* ③ 고른 부분(부재키)이 있으면 그 부분만 · 부분을 안 골랐으면 그 구조물 그림 모두
* DOM 은 `data-structure-id` · `data-part`(`tagShape`) — 부분 그림(관 · 벽 · 날개벽 · 물넘이 콘크리트)은
* DOM 은 `data-structure-id` · `data-part`(`common_util_pick_tag.tagShape`) — 부분 그림(관 · 벽 · 날개벽 · 물넘이 콘크리트)은
* 조정창 손잡이가 같은 고름으로 칠함(`paintPicked` 는 부분 없는 형상만 · 307 「측점 길은 부분 안 켬」).
* ========================================================================== */
@@ -32,12 +32,6 @@ export function pickLights(selection: PickSelection, tag: PickTag): boolean {
return !selection.partKey || tag.part === selection.partKey;
}
/** 그린 형상에 신원표를 닮(부분 없으면 통째 형상). */
export function tagShape(el: Element, structureId: string | null, part?: string | null): void {
if (structureId) el.setAttribute("data-structure-id", structureId);
if (part) el.setAttribute("data-part", part);
}
/** 카드 안 통째 형상 칠하기 — 고른 구조물 id 와 같은 형상만. 부분 그림(`data-part`) · 관 계열(`pipe-`) ·
* 노면공 포장층은 제 손잡이(조정창 · 포장층 고름)가 칠함 — 측점 길(알약 · 목록 · 측점선)은 부분을 안 켬(307). */
export function paintPicked(root: ParentNode, selection: PickSelection): void {
+3
View File
@@ -10,6 +10,7 @@
* 프로젝트가 없으면(시험 · 도움 파일) 옛 자리 이름.
* ========================================================================== */
import { setPipeTagSource } from "../../common_util/common_util_pick_tag";
import { readState, writeState } from "../b_page_state";
import { isNear, nearestWithin, readSelection, selectDesign } from "./design_selection";
@@ -26,6 +27,8 @@ let active: string | null = null;
/** 지금 프로젝트 — 종단 · 횡단 보기가 열 때. */
export function usePipeIds(projectId: string | null): void {
active = projectId;
// 그림 신원표(common_util_pick_tag)가 이 장부를 씀 — 프로젝트 없으면 옛 자리 이름(pipeIdAt 와 같음)
setPipeTagSource(projectId ? (chainageM) => pipeIdAt(chainageM) : null);
}
const bookOf = (projectId: string): Book =>
+1 -1
View File
@@ -19,7 +19,7 @@
서버 Node 묶음:
- 입구 `B05_Profile_Corridor_Node.ts`(package.json `build:corridor`) · 끌어감 = `UI_Corridor_*`(Build · Envelope · Frames · Station · Structures) · `UI_IrregularStations` · B06 `UI_Cross_*` 구조물 · A00 `b_page_state` · `design_pick_match` · `design_pipe_ids` · `design_selection`
- 입구 `B05_Profile_Corridor_Node.ts`(package.json `build:corridor`) · 끌어감 = `UI_Corridor_*`(Build · Envelope · Frames · Station · Structures) · B06 `UI_Cross_*` 구조물(화면 · 세션 안 끔 · `test_472_3_calc_registry.py`)
- `build:b07-extent` · `build:b07-note` 가 `Api_Structures` · `Api_Pipes_Draft` · `UI_Structures_Balloon` · `UI_Structures_Define` 를 끌어감
- 묶음이 끄는 TS 를 고치면 그 build 다시 지어 시험 · 그 파일 모듈 맨 위에 DOM · window · sessionStorage 금지
+2 -3
View File
@@ -9,8 +9,7 @@
* 콘크리트 색은 세월교 바닥판 클래스를 그대로 쓴다.
* ========================================================================== */
import { pipeIdAt } from "../A00_Common/design/design_pipe_ids";
import { tagShape } from "../A00_Common/design/design_pick_match";
import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag";
import type { BoxLayout, BoxSideRole } from "./B06_Section_UI_Cross_Box_Geom";
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
import { appendChuteBand } from "./B06_Section_UI_Cross_Wall_Hatch";
@@ -221,7 +220,7 @@ export function appendBoxOverlay(
event.stopPropagation();
onSelectSide(role);
});
tagShape(hit, box.chainage_m != null ? pipeIdAt(box.chainage_m) : null, role); // 신원표(323-5)
tagShape(hit, box.chainage_m != null ? pipeTagId(box.chainage_m) : null, role); // 신원표(323-5)
hits.set(role, hit);
layer.append(hit);
}
+2 -3
View File
@@ -10,8 +10,7 @@
* 집수정) → ③ 배관 → ④ 유출구측 성토부선(보호공 삭제 — 2026-08-22)
* ========================================================================== */
import { pipeIdAt } from "../A00_Common/design/design_pipe_ids";
import { tagShape } from "../A00_Common/design/design_pick_match";
import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag";
import {
REVET_EMBED_DEPTH_M,
FILL_SLOPE_RATIO_MIN,
@@ -91,7 +90,7 @@ export function appendCulvertOverlay(
): RevetHighlightSetter {
const { culvert, pipe, pipeCorners } = layout;
// 신원표(323-5) — 관 세션 id · 부분 = 조정창 부재키
const ownerId = culvert.chainage_m != null ? pipeIdAt(culvert.chainage_m) : null;
const ownerId = culvert.chainage_m != null ? pipeTagId(culvert.chainage_m) : null;
const diameter = culvert.diameter_m;
const roleLabel = (role: "inlet" | "outlet" | "extra") =>
role === "inlet" ? "유입" : role === "outlet" ? "유출" : "추가(성토부)";
@@ -22,6 +22,7 @@ import {
import { EXTRA_SPAN_DEFAULT } from "./logic/B06_Section_UI_Cross_Culvert_Const";
import type { StructureSpan } from "./logic/B06_Section_UI_Cross_Culvert_Const";
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
import type { LocaleKey } from "@ui/ui_template_locale";
/**
* 기슭막이 4축 조작 제어(2026-08-22 확정) — 벽을 눌러 고르고 조정창에서
* 좌우(x)·상하(d, 성토선 대각)·높이(h)·재질(m)을 만진다. 고른 벽은 여기 담아 두어
@@ -113,8 +114,12 @@ export interface ExtraWallControl {
syncCount: (chainageM: number, built: number, side?: ExtraSide) => void;
}
import { showToast } from "@ui/ui_template_elements";
import { L } from "./B06_Section_UI_Section_Common";
/** 한계에 잘린 조작 알림 — 화면(`Cross_View`)이 토스트를 꽂음 · 서버 Node 묶음은 안 꽂음(조작 중인 벽이
* 없어 부를 일도 없음). 묶음이 화면 코드(토스트 · 글)를 안 끌게 갈라냄(472-2). */
let notice: (key: LocaleKey) => void = () => {};
export function setCulvertNotice(fn: (key: LocaleKey) => void): void {
notice = fn;
}
/**
* 배수관 세트 기하 계산 + 제어 상태 동기화.
@@ -455,7 +460,7 @@ export function computeCardCulvert(
const applied = activeRevet === "inlet" ? layout.revetShift.inlet : layout.revetShift.outlet;
const asked = activeRevet === "inlet" ? requested?.inlet : requested?.outlet;
if (asked?.d != null && applied.d != null && Math.abs(asked.d - applied.d) > 0.05) {
showToast(L("B06_Cross_Revet_Limit_Up"), "info");
notice("B06_Cross_Revet_Limit_Up");
}
}
// 종단경사 반영은 **3D 전용** 스위치다(2026-08-24 사용자 확인). 횡단도는 언제나
@@ -488,7 +493,7 @@ export function computeCardCulvert(
Math.abs(requested.lateralM - applied.lateralM) > 0.05 ||
Math.abs(requested.slopeM - applied.slopeM) > 0.05;
if (moved) {
if (activeRevet === "inlet") showToast(L("B06_Cross_Basin_Limit_Pipe"), "info");
if (activeRevet === "inlet") notice("B06_Cross_Basin_Limit_Pipe");
inletStructure.updateAdjust(section.chainage_m, applied);
}
}
@@ -518,13 +523,10 @@ export function computeCardCulvert(
// 축별로 요청이 잘렸으면 왜 안 됐는지 토스트로 알린다(조작 중인 벽만).
if (activeRevet === role) {
if (held("x") && Math.abs(requested.x - applied.x) > 0.05) {
showToast(
L(
requested.x < applied.x
? "B06_Cross_Revet_Limit_Inward"
: "B06_Cross_Revet_Limit_Outward",
),
"info",
notice(
requested.x < applied.x
? "B06_Cross_Revet_Limit_Inward"
: "B06_Cross_Revet_Limit_Outward",
);
}
if (
@@ -533,9 +535,8 @@ export function computeCardCulvert(
applied.d != null &&
Math.abs(requested.d - applied.d) > 0.05
) {
showToast(
L(requested.d < applied.d ? "B06_Cross_Revet_Limit_Up" : "B06_Cross_Revet_Limit_Down"),
"info",
notice(
requested.d < applied.d ? "B06_Cross_Revet_Limit_Up" : "B06_Cross_Revet_Limit_Down",
);
}
}
+2 -3
View File
@@ -9,8 +9,7 @@
* ④ 계류측 성토부선·절토선. 관 위 성토 채움은 그리지 않는다(2026-08-25 사용자).
* ========================================================================== */
import { pipeIdAt } from "../A00_Common/design/design_pipe_ids";
import { tagShape } from "../A00_Common/design/design_pick_match";
import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag";
import type { FordLayout, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
import {
appendPipeInfoText,
@@ -56,7 +55,7 @@ export function appendFordOverlay(
onSelectWall?: (key: FordPick) => void,
): FordHighlightSetter {
const { ford } = layout;
const ownerId = ford.chainage_m != null ? pipeIdAt(ford.chainage_m) : null; // 신원표(323-5)
const ownerId = ford.chainage_m != null ? pipeTagId(ford.chainage_m) : null; // 신원표(323-5)
const toXY = (point: OffsetPoint): [number, number] => [
x(point.offset),
toDisplayY(point.elevation),
@@ -14,8 +14,7 @@
import type { CrossDesign } from "./B06_Section_Api_Fetch";
import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types";
import { appendPavementOverlay } from "./B06_Section_UI_Cross_Design_Surface";
import { pipeIdAt } from "../A00_Common/design/design_pipe_ids";
import { tagShape } from "../A00_Common/design/design_pick_match";
import { pipeTagId, tagShape } from "../common_util/common_util_pick_tag";
import { appendChuteBand, chuteRun } from "./B06_Section_UI_Cross_Wall_Hatch";
/** 백엔드 `section.ford_pavement` 제원(치수 결정은 서버 몫 — 여기서는 좌표만 만든다). */
@@ -119,7 +118,7 @@ export function appendFordPavementOverlay(
.forEach((el) => {
el.setAttribute("class", "b06-chart__pavement b06-chart__ford-pavement");
// 형상 고름 표식(287-5) — 관 지점 알약 id 와 같은 글
if (typeof spec.chainage_m === "number") tagShape(el, pipeIdAt(spec.chainage_m), "concrete"); // 관 세션 id · 부분(323-5)
if (typeof spec.chainage_m === "number") tagShape(el, pipeTagId(spec.chainage_m), "concrete"); // 관 세션 id · 부분(323-5)
});
// 월류 높이선 — 콘크리트 윗면(설계 노면) 위로 월류 높이만큼 · 같은 각도(그림_183) · 성토 쪽 = 성토선 연장(315-2)
const depth = spec.depth_m ?? 0;
@@ -16,7 +16,7 @@
* 3D 코리도는 `tiers[].polygon`·`span`을 그대로 스윕한다.
* ========================================================================== */
import { tagShape } from "../A00_Common/design/design_pick_match";
import { tagShape } from "../common_util/common_util_pick_tag";
import type { CrossSection } from "./B06_Section_Api_Fetch";
import {
FILL_SLOPE_RATIO_MIN,
+5 -1
View File
@@ -46,7 +46,8 @@ import {
} from "./B06_Section_UI_Cross_View_Frame";
import { appendFordOverlay, computeFordLayout } from "./B06_Section_UI_Cross_Ford";
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
import { computeCardCulvert } from "./B06_Section_UI_Cross_Culvert_Wire";
import { computeCardCulvert, setCulvertNotice } from "./B06_Section_UI_Cross_Culvert_Wire";
import { showToast } from "@ui/ui_template_elements";
import type { CulvertLink } from "./B06_Section_UI_Cross_Culvert_Wire";
import type {
ExtraWallControl,
@@ -87,6 +88,9 @@ import {
effectiveCardHalfWidth,
} from "./B06_Section_UI_Cross_View_Metrics";
// 관 세트 조작이 한계에 잘린 까닭 = 토스트(조작 중인 벽만 · Wire 가 부름).
setCulvertNotice((key) => showToast(L(key), "info"));
/**
* 횡단 카드 요소. 선택 표시와 면적 강조를 **카드를 다시 만들지 않고** 갈아 끼우는 핸들을 단다
* — 카드를 새로 만들면 사용자가 맞춰 둔 휠 줌·팬(viewBox)이 초기화되기 때문이다.
+26
View File
@@ -0,0 +1,26 @@
/* =============================================================================
* common_util/common_util_pick_tag.ts
* 그린 형상의 신원표(323-5) — 구조물 그림(카드 · 서버 Node 묶음)이 쓰는 몫만.
*
* 관 id 는 세션 장부(`design_pipe_ids`)가 정함 — 화면이 열 때(`usePipeIds`) `setPipeTagSource` 로 꽂음.
* 안 꽂혔으면(서버 Node · 시험) 옛 자리 이름 `pipe-{누가거리}` — 장부가 프로젝트를 모를 때와 같은 값.
* 묶음이 세션(`b_page_state`)을 안 끌게 갈라냄(472-2).
* ========================================================================== */
let pipeSource: ((chainageM: number) => string) | null = null;
/** 관 id 원천 — 화면이 세션 장부를 꽂음(null = 옛 자리 이름). */
export function setPipeTagSource(source: ((chainageM: number) => string) | null): void {
pipeSource = source;
}
/** 그 자리 관의 신원표 id. */
export function pipeTagId(chainageM: number): string {
return pipeSource ? pipeSource(chainageM) : `pipe-${chainageM.toFixed(2)}`;
}
/** 그린 형상에 신원표를 닮(부분 없으면 통째 형상). */
export function tagShape(el: Element, structureId: string | null, part?: string | null): void {
if (structureId) el.setAttribute("data-structure-id", structureId);
if (part) el.setAttribute("data-part", part);
}
@@ -31,14 +31,10 @@ const ford = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Ford_Pa
{ "./logic/B06_Section_UI_Cross_Culvert_Const": {} },
),
"./B06_Section_UI_Cross_Design_Surface": { appendPavementOverlay() {} },
// 관 세션 id · 신원표(323-5) — 프로젝트 없음 = 옛 자리 이름
"../A00_Common/design/design_pipe_ids": { pipeIdAt: (at) => `pipe-${at.toFixed(2)}` },
"../A00_Common/design/design_pick_match": {
tagShape: (el, id, part) => {
if (id) el.setAttribute("data-structure-id", id);
if (part) el.setAttribute("data-part", part);
},
},
// 관 신원표(323-5 · 472-2 에서 common_util 로) — 진짜 · 장부 안 꽂음 = 옛 자리 이름
"../common_util/common_util_pick_tag": loadTs(
path.join(ROOT, "common_util", "common_util_pick_tag.ts"),
),
});
// 286-3 도수로 · 돌붙임 길이 — 유출(좌 · 우가 높음) 성토사면 1:1.5(5 m 내려감 · 사면 길이 √(7.5²+5²) ≈ 9.01)
const sets = loadTs(path.join(ROOT, "common_util", "common_util_culvert_sets.ts"));
+3 -7
View File
@@ -47,15 +47,11 @@ pytestmark = pytest.mark.skipif(
reason="프론트엔드 의존성 미설치",
)
# 화면 · 세션 끌림(목록 1.3) — 472-2 2-B 가 정리(PIPE_DISPLAY_NAME → common_util · Api_Fetch 세션 ·
# 구조물 그림이 고른 것 읽기 · 사진첩).
# 화면 · 세션 끌림(목록 1.3) — 472-2 2-B 가 걷음. 남은 줄 = 헬퍼 정규식 헛잡음
# (Corridor_Structures_Hash 머리 주석 속 「import」부터 잡아 아래 `import type` 을 값으로 셈) —
# 헬퍼를 고치면 빈 목록.
SESSION_PULLS = (
"A00_Common/b_page_state.ts",
"A00_Common/design/design_pick_match.ts",
"A00_Common/design/design_pipe_ids.ts",
"A00_Common/design/design_selection.ts",
"B06_Section/B06_Section_Api_Fetch.ts",
"B06_Section/B06_Section_UI_Section_Common.ts",
"config/config_frontend.ts",
"ui_template/ui_template_elements.ts",
"ui_template/ui_template_elements_base.ts",