From 990343edc1564d2b008d555078b78da163aed389 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 12 Oct 2026 21:15:47 +0900 Subject: [PATCH] =?UTF-8?q?feat(b06):=20=EB=A7=B9=EC=95=94=EA=B1=B0=20?= =?UTF-8?q?=ED=98=95=EC=83=81=20=EB=88=84=EB=A6=84=20=3D=20=EA=B7=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=EB=AC=BC=20=EA=B3=A0=EB=A6=84=20=E2=80=94=20?= =?UTF-8?q?=EB=AC=BC=EB=84=98=EC=9D=B4=EC=99=80=20=EA=B0=99=EC=9D=80=20?= =?UTF-8?q?=ED=8C=90=EC=A0=95=EB=A9=B4=20=C2=B7=20=EB=B3=B4=EB=9D=BC=20?= =?UTF-8?q?=EC=86=90=EC=9E=A1=EC=9D=B4=20=EC=95=84=EB=9E=98=20=C2=B7=20?= =?UTF-8?q?=EB=81=8C=EA=B8=B0=EB=8A=94=20=EA=B3=A0=EB=A6=84=20=EC=95=84?= =?UTF-8?q?=EB=8B=98=20(288-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 맹암거 둘레에 고름 표식(data-structure-id) · 287-5 판정면을 같이 탐(손잡이 아래 · 3 px 끌기 = 고름 아님) - 형상 누름 창구 shapePickRoute — 관 지점(물넘이) = 그 카드 시설 고름 · 맹암거 = id 알림 → 구조물 목록이 id 로 고름(알약 · 폼 · 측점선 따라옴 · 노면공 포장 범위 고름과 따로) - 목록 · 알약 고름 → 형상 강조(287-5 highlightShapes) - 시험 test_288 · 287 대조 글 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ --- B06_Section/B06_Section_UI_Cross_Shape_Pick.ts | 11 +++++++++++ B06_Section/B06_Section_UI_Cross_Underdrain.ts | 1 + B06_Section/B06_Section_UI_Cross_View.ts | 7 +++++-- B06_Section/B06_Section_UI_Section_View.ts | 7 ++++++- resources/tester/helper_288_underdrain.cjs | 17 +++++++++++++++++ .../tester/test_287_ford_pavement_followup.py | 4 ++-- resources/tester/test_288_underdrain_cross.py | 12 ++++++++++++ 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/B06_Section/B06_Section_UI_Cross_Shape_Pick.ts b/B06_Section/B06_Section_UI_Cross_Shape_Pick.ts index 17e960758..d4e7694ec 100644 --- a/B06_Section/B06_Section_UI_Cross_Shape_Pick.ts +++ b/B06_Section/B06_Section_UI_Cross_Shape_Pick.ts @@ -11,6 +11,8 @@ * ========================================================================== */ const SVG_NS = "http://www.w3.org/2000/svg"; +/** 관 지점이 아닌 구조물(맹암거) 형상 누름 — 카드 밖(구조물 목록)이 id 로 고름(288-2). */ +export const SHAPE_PICK_EVENT = "b06-structure-shape-pick"; /** 끌기로 볼 움직임(px) — 이보다 크면 고름 아님. */ export const SHAPE_DRAG_PX = 3; @@ -49,6 +51,15 @@ export function attachShapePick( return (active) => shapes.forEach((shape) => shape.classList.toggle("is-active", active)); } +/** 형상 누름 창구(287-5 · 288-2) — 관 지점(`pipe-…` · 물넘이포장) = 그 카드 시설 고름 · 그 밖(맹암거) = id 를 + * 실은 알림을 위로 띄움(구조물 목록이 받음). */ +export function shapePickRoute(from: Element, pickPipe: () => void): (structureId: string) => void { + return (structureId) => { + if (structureId.startsWith("pipe-")) return pickPipe(); + from.dispatchEvent(new CustomEvent(SHAPE_PICK_EVENT, { bubbles: true, detail: structureId })); + }; +} + /** 카드 안 형상 강조 — 고른 구조물 id 와 같은 형상만(목록 · 알약 · 폼 고름이 따라옴). */ export function highlightShapes(root: ParentNode, structureId: string | null): void { for (const shape of root.querySelectorAll("[data-structure-id]")) { diff --git a/B06_Section/B06_Section_UI_Cross_Underdrain.ts b/B06_Section/B06_Section_UI_Cross_Underdrain.ts index e3bba0f0e..162887718 100644 --- a/B06_Section/B06_Section_UI_Cross_Underdrain.ts +++ b/B06_Section/B06_Section_UI_Cross_Underdrain.ts @@ -98,6 +98,7 @@ export function appendUnderdrainOverlay( const body = document.createElementNS(SVG_NS, "polygon"); body.setAttribute("points", points); body.setAttribute("class", "b06-chart__underdrain-body"); + if (spec.structure_id) body.setAttribute("data-structure-id", spec.structure_id); clipSeq += 1; const clipId = `b06-underdrain-clip-${clipSeq}`; const clip = document.createElementNS(SVG_NS, "clipPath"); diff --git a/B06_Section/B06_Section_UI_Cross_View.ts b/B06_Section/B06_Section_UI_Cross_View.ts index d21aac8a6..7f797e240 100644 --- a/B06_Section/B06_Section_UI_Cross_View.ts +++ b/B06_Section/B06_Section_UI_Cross_View.ts @@ -20,7 +20,7 @@ import { appendFordSurfaceDropPlan, } from "./B06_Section_UI_Cross_Ford_Pavement"; import { ownWallsDrawAt } from "./B06_Section_UI_Cross_Culvert_Wire"; -import { attachShapePick } from "./B06_Section_UI_Cross_Shape_Pick"; +import { attachShapePick, shapePickRoute } from "./B06_Section_UI_Cross_Shape_Pick"; import { appendUnderdrainOverlay } from "./B06_Section_UI_Cross_Underdrain"; import { raiseWalls } from "./B06_Section_UI_Cross_Wall"; import { attachChuteBandPick } from "./B06_Section_UI_Cross_Wall_Hatch"; @@ -579,7 +579,10 @@ export function createCrossSectionCard( setChuteActive(highlightRevet === "chute"); } // 물넘이포장 · 맹암거 형상 고름(287-5 · 288-2) — 다른 시설과 같은 창구 · 보라 손잡이 아래 · 누름만 - const setShapeActive = attachShapePick(plotLayer, revetOffset && (() => toggleRevet("pipe"))); + const setShapeActive = attachShapePick( + plotLayer, + revetOffset && shapePickRoute(svg, () => toggleRevet("pipe")), + ); if (setShapeActive) { const baseSetActive = setRevetActive; setRevetActive = (key) => { diff --git a/B06_Section/B06_Section_UI_Section_View.ts b/B06_Section/B06_Section_UI_Section_View.ts index f6efcaad2..11ecf14a2 100644 --- a/B06_Section/B06_Section_UI_Section_View.ts +++ b/B06_Section/B06_Section_UI_Section_View.ts @@ -69,7 +69,7 @@ import { import type { SectionViewController } from "./B06_Section_UI_Section_View_Types"; import { createFillSlopeNotice } from "./B06_Section_UI_Cross_FillSlope_Notice"; import { highlightPavement, pavementAt } from "./B06_Section_UI_Cross_Design_Surface"; -import { highlightShapes } from "./B06_Section_UI_Cross_Shape_Pick"; +import { highlightShapes, SHAPE_PICK_EVENT } from "./B06_Section_UI_Cross_Shape_Pick"; import { createSectionProfile } from "./B06_Section_UI_Section_View_Profile"; import type { DesignPageShared } from "../A00_Common/design/design_shared"; @@ -440,6 +440,11 @@ export function createSectionView( // 렌더 행과 달라져 카드 내부가 뒤죽박죽된다(N-2-6). 열 수 · 폭을 JS가 명시해 일치시킨다. grid.style.gridTemplateColumns = `repeat(${columnCount}, ${cachedCardWidth}px)`; grid.dataset.columns = String(columnCount); // 4열부터 면적표 「계」만(173-3 · CSS) + // 맹암거 형상 누름 = 그 구조물 id 고름(288-2 · 노면공 포장 범위 표식과 따로 · 같은 id 고름 창구) + grid.addEventListener(SHAPE_PICK_EVENT, (event) => { + const id = (event as CustomEvent).detail; + if (id) pavementSelectListener?.(id); + }); cachedRowHeight.clear(); if (detail.cross_sections.length) { // 1차: 각 카드의 자연 높이(250px 바닥 적용) 측정 → 같은 행(columnCount 단위) 최댓값을 행 높이로. diff --git a/resources/tester/helper_288_underdrain.cjs b/resources/tester/helper_288_underdrain.cjs index 5611079cc..04282f3c8 100644 --- a/resources/tester/helper_288_underdrain.cjs +++ b/resources/tester/helper_288_underdrain.cjs @@ -73,6 +73,21 @@ const group = draw.appendUnderdrainOverlay( ); const body = group?.children.find((k) => k.attrs.class === "b06-chart__underdrain-body"); +// 288-2 형상 누름 창구 — 물넘이(pipe-…) = 시설 고름 · 맹암거 = id 알림(목록이 고름) +globalThis.CustomEvent = class { + constructor(type, init) { + this.type = type; + this.detail = init?.detail; + this.bubbles = init?.bubbles; + } +}; +const shapePick = load(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Shape_Pick.ts")); +const routed = []; +const from = { dispatchEvent: (event) => routed.push([event.type, event.detail, event.bubbles]) }; +const route = shapePick.shapePickRoute(from, () => routed.push(["pipe"])); +route("pipe-230.00"); +route("ud-1"); + process.stdout.write( JSON.stringify({ specs, @@ -82,6 +97,8 @@ process.stdout.write( noHeight: draw.underdrainOutline(design, null), drew: !!group, bodyId: body?.attrs["data-structure-id"] ?? null, + routed, + eventName: shapePick.SHAPE_PICK_EVENT, stones: group?.children.filter((k) => k.tag === "g").flatMap((g) => g.children).length ?? 0, }), ); diff --git a/resources/tester/test_287_ford_pavement_followup.py b/resources/tester/test_287_ford_pavement_followup.py index 67a4a1f7f..7d227d155 100644 --- a/resources/tester/test_287_ford_pavement_followup.py +++ b/resources/tester/test_287_ford_pavement_followup.py @@ -121,8 +121,8 @@ def test_형상_강조와_표식(ts): def test_보라_손잡이는_계획고만_형상_누름은_고름만(): view = _read("B06_Section", "B06_Section_UI_Cross_View.ts") # 형상 누름 = 시설 고름 창구만(설계 · 계획고를 안 건드림) · 보라 선 고름보다 먼저 꽂음(손잡이가 위) - assert 'attachShapePick(plotLayer, revetOffset && (() => toggleRevet("pipe")))' in view - assert view.index("attachShapePick(plotLayer") < view.index("attachDesignLinePick({") + assert 'shapePickRoute(svg, () => toggleRevet("pipe"))' in view + assert view.index("attachShapePick(") < view.index("attachDesignLinePick({") pick = _read("B06_Section", "B06_Section_UI_Cross_Shape_Pick.ts") assert "onDesignChange" not in pick and "design_elevation" not in pick assert '"b06-chart__culvert-revet-hit"' in pick # 구조물 판정면 앞(아래)에 꽂을 기준만 diff --git a/resources/tester/test_288_underdrain_cross.py b/resources/tester/test_288_underdrain_cross.py index b78011f18..915a09620 100644 --- a/resources/tester/test_288_underdrain_cross.py +++ b/resources/tester/test_288_underdrain_cross.py @@ -5,6 +5,8 @@ 서버 `tilt_ranges` · `tilt_args` · TS 짝 `tilted`) · 제원 얹기 서버 `attach_underdrains` ↔ TS `attachUnderdrainSpecs` 같은 값 · 형상 = 노면을 암거 높이만큼 아래로 옵셋 → 성토 쪽은 성토 사면과 만남 · 절토 쪽은 절토 사면을 늘여 만남 · 그 안 돌 무늬 · 높이 비면 안 그림. + - 288-2 맹암거 형상 누름 = 그 구조물 id 고름(구조물 목록 창구 · 알약 · 폼 · 측점선 따라옴) · 물넘이와 같은 + 판정면(보라 손잡이 아래 · 끌기 = 고름 아님 · 287-5) · 노면공 포장 범위 고름과 따로. 진짜 TS 는 `helper_288_underdrain.cjs` 가 Node 로 돌림. 화면은 사용자 확인. """ @@ -141,3 +143,13 @@ def test_횡단도_B07_이_맹암거를_그림(): assert "appendUnderdrainOverlay(svg, section.underdrain, design, x, y);" in b07 refresh = (ROOT / "B06_Section" / "B06_Section_Cross_Refresh.ts").read_text(encoding="utf-8") assert "tilted: Boolean(section.ford_pavement || section.underdrain)," in refresh + + +def test_맹암거_형상_누름은_그_구조물_고름(ts): + assert ts["bodyId"] == "ud-1" # 둘레 = 형상 고름 표식 + assert ts["routed"] == [["pipe"], [ts["eventName"], "ud-1", True]] + view = (ROOT / "B06_Section" / "B06_Section_UI_Section_View.ts").read_text(encoding="utf-8") + assert "grid.addEventListener(SHAPE_PICK_EVENT, (event) => {" in view + assert "if (id) pavementSelectListener?.(id);" in view # = structuresPanel.selectStructure(id) + cross = (ROOT / "B06_Section" / "B06_Section_UI_Cross_View.ts").read_text(encoding="utf-8") + assert 'revetOffset && shapePickRoute(svg, () => toggleRevet("pipe")),' in cross