From 5e8df73763e8cfab81f9be168b648fc712bcb60f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 16 Oct 2026 10:59:09 +0900 Subject: [PATCH] =?UTF-8?q?feat(=EC=84=A4=EA=B3=84=EB=AC=B6=EC=9D=8C):=203?= =?UTF-8?q?44-4=20=EA=B3=84=EC=82=B0=20=EB=AC=B6=EC=9D=8C=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=20=E2=80=94=20=EB=8B=A8=EA=B3=84=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=20=C2=B7=20=EC=B6=9C=EB=A0=A5=20=C2=B7=20=EC=B8=A1=EC=A0=90=20?= =?UTF-8?q?=C2=B7=20=EB=85=B8=EC=84=A0=20=EC=B4=88=EC=95=88=20=EB=AA=A8?= =?UTF-8?q?=EC=96=91=20=C2=B7=20route-draft=20=EC=BA=90=EC=8B=9C=20?= =?UTF-8?q?=EC=B9=B8=20=C2=B7=20=EA=B2=B0=EA=B3=BC=20=EC=B9=B8=20=EC=85=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - design_pipeline_types.ts 새 — 단계 차례 · 주인 · deps · 키별 모양(측점 · 지반 · 노선 초안 · 발자국) - b_page_state 등록표 — route-draft(draft · project) · design-stations · design-profile · design-footprint(result) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ --- A00_Common/b_page_state.ts | 7 + A00_Common/design/design_pipeline_types.ts | 251 +++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 A00_Common/design/design_pipeline_types.ts diff --git a/A00_Common/b_page_state.ts b/A00_Common/b_page_state.ts index 2f14d7bab..18b32061b 100644 --- a/A00_Common/b_page_state.ts +++ b/A00_Common/b_page_state.ts @@ -208,6 +208,9 @@ export const STATE_REGISTRY = { scope: "route", legacy: (_p, r) => `b05-profile-alignment-draft:${r}`, }, + /** 노선 초안(344-4) — 노드 · 곡선 · 반경 · 스냅 · 지형 · rev(`RouteDraft` · `design_pipeline_types`). + * 노선 번호는 [저장]마다 새로 나므로 project 범위 · 가게 키 `routeDraft`. */ + "route-draft": { bucket: "draft", scope: "project" }, /* ── ④ 계산 결과 ─────────────────────────────────────────────────────── */ /** 노선·종단 최신 응답. 페이지를 오갈 때 이 값으로 먼저 그린다. */ @@ -235,6 +238,10 @@ export const STATE_REGISTRY = { /** B05 계획노선 편집 창 — 마지막에 적용한 초기노선 갈래(로직 · 비중 · 노드 · 비교값). * 노선을 다시 계산해 결과를 버릴 때도 이 값은 새 노선의 출발점이라 되살린다(`_Apply`). */ "routeedit-initial": { bucket: "result", scope: "project" }, + /** 계산 묶음 결과(344-4) — 측점 · 종단 · 평면 발자국 · 가게가 메모리 우선 · 멈춘 뒤 한 번 씀. */ + "design-stations": { bucket: "result", scope: "project" }, + "design-profile": { bucket: "result", scope: "project" }, + "design-footprint": { bucket: "result", scope: "project" }, } as const satisfies Record; export type StateName = keyof typeof STATE_REGISTRY; diff --git a/A00_Common/design/design_pipeline_types.ts b/A00_Common/design/design_pipeline_types.ts new file mode 100644 index 000000000..dcc3c9fa8 --- /dev/null +++ b/A00_Common/design/design_pipeline_types.ts @@ -0,0 +1,251 @@ +/* ============================================================================= + * A00_Common/design/design_pipeline_types.ts + * 실시간 계산 묶음 **계약**(PLAN 344-4) — 키별 모양 · 단계 입력 · 출력. 다른 창(sub1 · sub2 · + * sub3 · sub6 · sub8)은 이 모양대로 단계 함수를 만들어 `createDesignPipeline({ stages })` 에 넣음. + * 계산 묶음 본체 = `design_pipeline.ts`. + * + * 흐름 — 한 방향: + * 입력 route(노선 초안) · alignment(계획고) · cross(횡단 입력) · pipes(관) · structures(구조물) + * ↓ update(출처, 값, 범위) — 조작마다 차례 번호(generation) +1 · 옛 계산 · 옛 요청 버림 + * 브라우저 즉시(차례대로 · 단계 사이마다 번호 확인 · 단계마다 결과 바로 내보냄) + * route → follow → stations → ground → planline → profile → cross → footprint → masshaul + * 뒤(기다리지 않음 · 새 조작이 오면 AbortSignal 로 끊김 · 옛 번호 결과 버림) + * haul(흙 배분 · 서버 Node) · drainage(배수유역 「낡음」 표시) · corridor(3D 「낡음」 표시) + * + * 규칙: + * · 단계는 자기 출력만 돌려줌 — 입력 · 다른 단계 출력을 고치지 않음(ctx 의 값은 읽기만). + * · 예외 하나 = follow — 노선이 바뀐 차례에만 · pipes · structures · alignment 를 한 번 고쳐 + * 돌려줌(관 · 구조물 좌표 따라감 · 계획고 옮김). 묶음이 입력에 넣고 다음 단계로 감. + * · 단계 함수가 없으면(아직 안 붙음) 건너뜀 — 출력은 앞 값 그대로(처음엔 null). + * · 기대는 값(deps)의 판이 그대로면 다시 안 셈(같은 입력 다시 안 셈). + * · range = 마지막으로 이 단계가 끝난 뒤 쌓인 바뀐 범위 전부 — 범위만 셈 · 범위 밖은 ctx.prev 재사용. + * + * 단계 · 주인 · 입력(deps) → 출력: + * | 단계 | 주인 | deps | 출력 | + * | route | sub8 | input.route | RouteLine | + * | follow | sub1 | route | FollowPatch(입력 고침) | + * | stations | sub1·sub2| route · input.pipes · input.structures | PipelineStation[](지반 null) | + * | ground | sub8 | stations | GroundOutput | + * | planline | sub1 | ground | AlignmentBase(첫 계획선) | + * | profile | sub1 | planline · input.alignment | ProfileAlignment | + * | cross | sub2 | profile · ground · input.cross · pipes · structures | CrossOutput | + * | footprint | sub6 | cross · ground | PlanFootprint | + * | masshaul | sub2 | cross | unknown(sub2 모양) | + * | haul | sub2 | masshaul | unknown(서버 응답) | + * | drainage | sub3 | route · input.pipes | 「낡음」 표시(출력 안 씀) | + * | corridor | sub5 | profile · cross | 「낡음」 표시(출력 안 씀) | + * + * 가게 키(`design_store.ts`) — routeDraft(입력 · 세션 즉시) · stations(ground 단계 출력) · + * profile · footprint(메모리 우선 · 멈춘 뒤 한 번 세션) · generation(메모리만). + * ========================================================================== */ + +import type { + AlignmentBase, + AlignmentEdits, + ProfileAlignment, +} from "../../B05_Profile/B05_Profile_UI_Profile_Alignment"; + +/** 평면 좌표(m) — 도면 좌표계 x · y. */ +export type Vertex = [number, number]; + +/* ── 입력 ───────────────────────────────────────────────────────────────── */ + +/** 노선 초안 — 캐시 칸 `route-draft`(draft · project 범위) · [저장] 때 서버로 · 노선 편집기가 씀. */ +export interface RouteDraft { + /** 놓을 때마다 +1 — 같은 초안인지 보는 판 번호. */ + rev: number; + /** 노드(시점 · 교각점 · 종점) — 사용자가 끄는 꺾임점. 원호는 route 단계가 만듦. */ + nodes: Vertex[]; + /** 꺾임점마다 곡선 둠 · 못박은 반경(m · null = 기본) · 붙든 값 · 붙든 곡선 길이(m). 길이 = nodes. */ + curveOn: boolean[]; + curveRadius: Array; + curveLock: Array<"radius" | "arc" | null>; + curveArc: Array; + /** [측점 스냅](요구 224-2) — 켜면 호 안 측점 하나를 곡선 중점으로. */ + stationSnap: boolean; + /** 분석 지형(`las` · `sheet` · null = 확정 지표면) — ground 단계가 고를 격자. */ + terrain: string | null; +} + +/** 묶음이 받는 입력 다섯. 없으면 null(= 저장분 그대로). cross · pipes · structures 모양은 주인 몫. */ +export interface PipelineInputs { + route: RouteDraft | null; + alignment: AlignmentEdits | null; + /** 횡단 입력(crossdesign · cutslope · berm · std-cross 등 초안 묶음) — 모양 = sub2. */ + cross: unknown; + /** 관 목록(`DetailPipeInput[]`) — 좌표가 정본 · 누가거리는 stations 가 다시 매김. */ + pipes: unknown; + /** 구조물 목록(`StructureInstance[]`) — 위와 같음. */ + structures: unknown; +} + +export type InputKey = keyof PipelineInputs; + +/** 조작 출처 — 노선 · 계획고 · 횡단 · 관 · 구조물. */ +export type ChangeSource = "route" | "grade" | "cross" | "pipe" | "structure"; + +/** 출처 → 입력 칸. */ +export const SOURCE_INPUT: Record = { + route: "route", + grade: "alignment", + cross: "cross", + pipe: "pipes", + structure: "structures", +}; + +/** + * 바뀐 범위(누가거리 m) — 부르는 쪽이 정함: + * 노선 = 처음 바뀐 접선점부터 끝(toM = Infinity) · 계획고 = 이웃 꼭짓점 사이 · 횡단 = 그 측점(fromM = toM). + * 여러 조작이 쌓이면 합침(가장 앞 ~ 가장 뒤 · keys 는 모음). null = 전 구간. + */ +export interface ChangeRange { + sources: ChangeSource[]; + fromM: number; + toM: number; + /** 측점 키로 정한 범위(횡단 조작) — 비면 거리로만. */ + keys: string[]; +} + +/** update 가 받는 범위 한 조각 — sources 는 출처로 채움. */ +export interface RangePart { + fromM: number; + toM: number; + keys?: string[]; +} + +/* ── 출력 ───────────────────────────────────────────────────────────────── */ + +/** route 단계 — 원호가 섞인 그린 노선(`_RouteEdit_Curve` 짝) · 누가거리. */ +export interface RouteLine { + line: Vertex[]; + /** line 정점마다 누가거리(m). */ + chainage: number[]; + lengthM: number; +} + +/** follow 단계 — 노선이 바뀐 차례에 한 번만 입력을 고침. 안 고치는 칸은 비움. */ +export interface FollowPatch { + pipes?: unknown; + structures?: unknown; + alignment?: AlignmentEdits; +} + +/** 측점 종류 — 격자(간격) · 구조물 · 관 · 끝(시점 · 종점). */ +export type StationKind = "grid" | "structure" | "pipe" | "end"; + +/** 횡단 지반 표본 한 점 — 중심에서 거리(m · 좌 +) · 높이(null = 빈칸). */ +export interface GroundSample { + offsetM: number; + z: number | null; +} + +export interface StationGround { + centerZ: number | null; + /** 중심 → 바깥 차례 · offsetM 은 둘 다 양수. */ + left: GroundSample[]; + right: GroundSample[]; +} + +/** 측점 한 자리 — stations 가 만들고 ground 가 지반을 채움. 가게 키 `stations`. */ +export interface PipelineStation { + /** 측점 키 — 규칙 = 344-3(sub2 · 0.01 m · 격자 = 격자 번호 · 구조물 · 관 = id). */ + key: string; + chainageM: number; + x: number; + y: number; + /** 진행 방향(도 · 북 0 시계) · 좌측 단위 벡터(`_RouteEdit_Frame` 짝). */ + azimuthDeg: number; + left: Vertex; + kind: StationKind; + /** 구조물 · 관 id — 격자 · 끝은 null. */ + id: string | null; + /** ground 단계 전엔 null. */ + ground: StationGround | null; + /** 평면 곡선반경(m · 직선 null) · 확폭이 붙는 바깥쪽 · 확폭량(m). */ + planRadiusM: number | null; + curveOuterSide: "left" | "right" | null; + curveWideningM: number | null; +} + +/** ground 단계 — 지반 채운 측점 · 종단 지반선(1 m). */ +export interface GroundOutput { + stations: PipelineStation[]; + longitudinal: { chainageM: number[]; z: Array }; +} + +/** cross 단계 — 측점 키 → 그 측점 결과(면적은 유토곡선 재료 · detail 모양 = sub2). */ +export interface CrossStationResult { + key: string; + chainageM: number; + cutAreaM2: number; + fillAreaM2: number; + detail: unknown; +} + +export interface CrossOutput { + byKey: Record; +} + +/** footprint 단계 — 평면 발자국(도로 끝 · 비탈 끝) · 시점 → 종점 차례 · 가게 키 `footprint`. */ +export interface PlanFootprint { + roadLeft: Vertex[]; + roadRight: Vertex[]; + slopeLeft: Vertex[]; + slopeRight: Vertex[]; +} + +/** 단계 이름 → 출력. drainage · corridor 는 표시만 하므로 출력이 없음(null). */ +export interface PipelineOutputs { + route: RouteLine; + follow: FollowPatch; + stations: PipelineStation[]; + ground: GroundOutput; + planline: AlignmentBase; + profile: ProfileAlignment; + cross: CrossOutput; + footprint: PlanFootprint; + masshaul: unknown; + haul: unknown; + drainage: null; + corridor: null; +} + +export type StageName = keyof PipelineOutputs; + +/* ── 단계 함수 ──────────────────────────────────────────────────────────── */ + +export interface StageContext { + /** 이 계산의 차례 번호 — 다르면 이미 버려진 계산. */ + generation: number; + /** 마지막으로 이 단계가 끝난 뒤 쌓인 바뀐 범위(null = 전 구간 · 처음). */ + range: ChangeRange | null; + /** 새 조작이 오면 끊김 — fetch 에 그대로 넘김. */ + signal: AbortSignal; + /** 입력(읽기만). */ + inputs: Readonly; + /** 앞 단계 출력(이번 계산 것 · 없으면 null · 읽기만). */ + out: Readonly<{ [S in StageName]: PipelineOutputs[S] | null }>; + /** 이 단계 앞 출력 — 범위 밖을 재사용. */ + prev: PipelineOutputs[K] | null; +} + +export type StageFn = ( + ctx: StageContext, +) => PipelineOutputs[K] | Promise; + +export type PipelineStages = { [K in StageName]?: StageFn }; + +/* ── 시간 기록(window.__aisloPipeline · 개발용) ─────────────────────────── */ + +export interface PipelineRunRecord { + generation: number; + sources: ChangeSource[]; + /** 단계 → ms(뒤 단계는 끝난 때 채움 · 건너뜀은 안 적음). */ + stages: Partial>; + /** 건너뛴 단계(같은 입력 · 함수 없음). */ + skipped: StageName[]; + /** 브라우저 단계 합 ms. */ + totalMs: number; + /** done = 브라우저 단계 끝 · dropped = 새 조작에 밀려 버림 · error = 단계가 던짐. */ + outcome: "running" | "done" | "dropped" | "error"; +}