diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts index 74c9119fe..9365f237b 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures.ts @@ -54,7 +54,7 @@ import { computeRevetmentLayout } from "../B06_Section/B06_Section_UI_Cross_Reve import { withoutGuardedFill } from "../B06_Section/B06_Section_UI_Cross_Body_Guard"; import { chuteBands, fordPavementChute } from "./B05_Profile_UI_Corridor_Structures_Chute"; import { isChuteSegment } from "../B06_Section/B06_Section_UI_Cross_Wall_Hatch"; -import { builds, culvertBody, ownsBodies } from "./B05_Profile_UI_Corridor_Structures_Pick"; +import { builds, culvertBody, ownsBody } from "./B05_Profile_UI_Corridor_Structures_Pick"; /** 스윕 프레임 하나 — 노선 위 한 지점의 중심 XY·좌향 단위벡터와 종단 표고 오프셋. */ export interface StructureFrame { @@ -279,12 +279,12 @@ export function buildCorridorStructures( ): CorridorStructure[] { const solids: CorridorStructure[] = []; for (const section of crossSections) { - // 몸체 고르기 = 그림 자료 열쇠의 주인 종류 표 3D 부품(322-1) · 같은 m 카드 둘이면 한 번(323-9) - const own = ownsBodies(section, crossSections); - const layout = own && culvertBody(section) ? culvertLayoutOf(section) : null; + // 몸체 고르기 = 그림 자료 열쇠의 주인 종류 표 3D 부품(322-1) · 같은 시설 카드 둘이면 한 번(323-9) + const own = (key: "culvert" | "ford" | "box"): boolean => ownsBody(section, crossSections, key); + const layout = own("culvert") && culvertBody(section) ? culvertLayoutOf(section) : null; // 흙막이가 선 쪽은 횡단과 같은 자리 함수로 — 측벽 걷고 관 · 바닥판이 흙막이까지(89-1) const fordLayout = - own && builds("ford_bridge", "ford") + own("ford") && builds("ford_bridge", "ford") ? withoutGuardedFill(fordLayoutOf(section), layout) : null; // 독립 기슭막이(옛 D군 경로) — 측점에 배관/숨김 기슭막이 세트가 붙으면 그쪽(위 @@ -299,9 +299,10 @@ export function buildCorridorStructures( builds(section.revetment?.type_id, "wall") ? computeRevetmentLayout(section, section.design?.revet_adjust?.own) : null; - const paveChute = - own && builds("ford_pavement", "chute") ? fordPavementChute(section, crossSections) : null; - const box = own && builds("box_culvert", "box") ? section.box : undefined; + const paveChute = builds("ford_pavement", "chute") + ? fordPavementChute(section, crossSections) + : null; + const box = own("box") && builds("box_culvert", "box") ? section.box : undefined; if (!layout && !fordLayout && !box && !revetLayout && !paveChute) continue; const chainage = section.chainage_m; const stationFrame: StructureFrame = { diff --git a/B05_Profile/B05_Profile_UI_Corridor_Structures_Pick.ts b/B05_Profile/B05_Profile_UI_Corridor_Structures_Pick.ts index 3ac464282..52b6b96cd 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Structures_Pick.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Structures_Pick.ts @@ -12,9 +12,22 @@ import { catalogRow } from "../common_util/common_util_structure_catalog"; export const builds = (typeId: string | null | undefined, body: string): boolean => (catalogRow(typeId ?? "")?.model3d ?? body) === body; -/** 관 몸체를 세울 측점인가 — 같은 m 카드 둘(노선 끝 EP)이면 앞 카드만(관 하나에 한 번 · 323-9). */ -export const ownsBodies = (section: CrossSection, sections: readonly CrossSection[]): boolean => - sections.find((other) => other.chainage_m === section.chainage_m) === section; +/** + * 이 카드가 시설(관 · 세월교 · BOX) 몸체를 세우는가 — 같은 시설(얹을 때 적은 시설 누가거리)을 그리는 카드 중 + * 시설 자리에 가장 가까운 하나만(노선 끝 EP 카드 0.01 m 차 · 323-9 · `fordPavementChute` 와 같은 규칙). + */ +export function ownsBody( + section: CrossSection, + sections: readonly CrossSection[], + key: "culvert" | "ford" | "box", +): boolean { + const at = section[key]?.chainage_m; + if (typeof at !== "number") return true; // 시설 자리 없음 = 옛 자료 그대로 + const gap = (item: CrossSection): number => Math.abs(item.chainage_m - at); + const same = sections.filter((item) => item[key]?.chainage_m === at); + const owner = same.reduce((best, item) => (gap(item) < gap(best) ? item : best), same[0]); + return owner === section; +} /** `culvert` 자리 주인 = 박스 · 세월교 흙막이(body) · 독립 기슭막이(관 숨김) · 배관. */ export function culvertBody(section: CrossSection): boolean { diff --git a/resources/tester/helper_323_9_twin_cards_3d.cjs b/resources/tester/helper_323_9_twin_cards_3d.cjs index 713b9d76f..437064ae8 100644 --- a/resources/tester/helper_323_9_twin_cards_3d.cjs +++ b/resources/tester/helper_323_9_twin_cards_3d.cjs @@ -1,4 +1,4 @@ -/* PLAN 323-9 ④-② — 노선 끝(EP)처럼 같은 m 카드 둘이면 3D 관 · 세월교 · BOX 몸체는 한 번. +/* PLAN 323-9 ④-② — 같은 시설을 그리는 카드 둘(같은 m · 노선 끝 EP 꼴 소수 차 0.01 m)이면 3D 관 · 세월교 · BOX 몸체는 한 번. * 화면 TS(`buildCorridorStructures`)를 그 자리에서 트랜스파일해 Node 로 돌림(로더 = helper_135_3 과 같음). * 입력 = 73-4 픽스처(세월교 173.087 · BOX 352.14) · 결과 = 표준출력 JSON. 짝 = test_323_9_twin_cards_3d.py. */ @@ -74,14 +74,19 @@ const kinds = (sections) => .map((s) => `${s.kind}:${s.key ?? ""}:${s.chainage_m.toFixed(2)}`) .sort(); const pick = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Corridor_Structures_Pick.ts")); -const own = pick.ownsBodies; +const own = pick.ownsBody; +/** 노선 끝 꼴 — 같은 시설을 그리는 카드가 소수 차(EP 1078 · 1078.010813)로 하나 더. */ +const ep = (one) => Object.assign(clone(one), { chainage_m: one.chainage_m + 0.010813 }); const out = {}; for (const c of [173.087, 352.14]) { const one = at(c); const twin = kinds([one, clone(one)]); - pick.ownsBodies = () => true; // 고치기 전(측점마다 몸체) — 기준이 변경을 재는지 + const epTwin = kinds([one, ep(one)]); + const epFirst = kinds([ep(one), one]); // 차례가 바뀌어도 시설 자리에 가까운 카드 하나 + pick.ownsBody = () => true; // 고치기 전(카드마다 몸체) — 기준이 변경을 재는지 const before = kinds([one, clone(one)]); - pick.ownsBodies = own; - out[c] = { one: kinds([one]), twin, before }; + const epBefore = kinds([one, ep(one)]); + pick.ownsBody = own; + out[c] = { one: kinds([one]), twin, before, epTwin, epFirst, epBefore }; } process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_323_9_twin_cards_3d.py b/resources/tester/test_323_9_twin_cards_3d.py index 7c72ea64b..b1950230c 100644 --- a/resources/tester/test_323_9_twin_cards_3d.py +++ b/resources/tester/test_323_9_twin_cards_3d.py @@ -1,6 +1,6 @@ -"""PLAN 323-9 ④-② — 노선 끝(EP)처럼 같은 m 카드 둘이면 3D 관 · 세월교 · BOX 몸체가 둘 서던 흠. +"""PLAN 323-9 ④-② — 같은 시설을 그리는 카드 둘(같은 m · 노선 끝 EP 꼴 0.01 m 차)이면 3D 몸체가 둘 서던 흠. -3D 몸체는 구조물(관) 하나에 한 번 — 같은 m 카드 둘이면 앞 카드만 세움(`ownsBodies`). +3D 몸체는 시설(관 · 세월교 · BOX) 하나에 한 번 — 시설 자리에 가장 가까운 카드만 세움(`ownsBody`). Node 헬퍼 = `helper_323_9_twin_cards_3d.cjs`(73-4 픽스처 · 진짜 `buildCorridorStructures`). """ @@ -38,5 +38,8 @@ def test_twin_cards_build_bodies_once(out: dict, at: str) -> None: got = out[at] assert got["one"] and got["twin"] == got["one"] assert len(got["before"]) == 2 * len(got["one"]) # 고치기 전 = 두 번(시험이 변경을 잼) + # 노선 끝 EP 꼴 — 같은 시설 카드가 소수 차(0.010813 m) · 차례가 바뀌어도 시설 자리에 가까운 카드 하나 + assert got["epTwin"] == got["one"] and got["epFirst"] == got["one"] + assert len(got["epBefore"]) == 2 * len(got["one"]) if at == "173.087": # 세월교 관 두 련 — 관 몸체도 한 번 assert sum(k.startswith("pipe:") for k in got["twin"]) == 2