From bb5f07eb0c4caa28294a6b714cc8630c78474bdb Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 17 Oct 2026 14:53:45 +0900 Subject: [PATCH] =?UTF-8?q?feat(377-2=20B07=20=EC=A0=84=EA=B0=9C=EB=8F=84)?= =?UTF-8?q?:=20=EC=A2=8C=EC=B8=A1=20=E3=80=8C=EC=A0=84=EA=B0=9C=EB=8F=84?= =?UTF-8?q?=E3=80=8D=20=EC=B9=B4=EB=93=9C=20=C2=B7=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=C2=B7=20=EB=8B=A8=EA=B1=B4=20=C2=B7=20=ED=99=95=EC=A0=95?= =?UTF-8?q?=EB=B3=B8=20kind=20=E2=80=94=20=EB=8A=98=20=EC=B9=B4=EB=93=9C?= =?UTF-8?q?=20=C2=B7=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=20=EC=A2=85=EB=A5=98?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=ED=95=98=EC=9C=84=20=EB=AC=B6=EC=9D=8C(?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EC=A4=84=20+=20=ED=98=95=ED=83=9C=20?= =?UTF-8?q?=EB=8B=A8=EC=B6=94)=20=C2=B7=20=EB=AA=A9=EB=A1=9D=20=EB=A7=A8?= =?UTF-8?q?=20=EB=92=A4=20=C2=B7=20=EC=A0=84=EA=B0=9C=EB=8F=84=20id=20?= =?UTF-8?q?=EB=A9=B4=20=EC=B8=A1=EC=A0=90=EB=B3=84=20=EC=84=A4=EA=B3=84=20?= =?UTF-8?q?=EC=8B=A4=EC=96=B4=20=EA=B7=B8=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Router_Support _drawing_list 맨 뒤 development_list · _read_drawing 분기 · 확정본 kind development - Router 단건 = _designs_by_chainage · Schema · Api_Fetch kind development - UI_Development.ts 새 카드(groupPlanSheets 재사용) · test_311 접힘 대조에 전개도 카드(기본 펼침) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf --- .../B07_DesignDetail_Api_Fetch.ts | 2 + ...B07_DesignDetail_Engine_Cad_Development.py | 8 +-- B07_DesignDetail/B07_DesignDetail_Router.py | 4 ++ .../B07_DesignDetail_Router_Support.py | 13 +++++ .../B07_DesignDetail_UI_Development.ts | 50 +++++++++++++++++++ .../B07_DesignDetail_UI_Panels.ts | 8 +++ resources/tester/test_241_development.py | 4 +- resources/tester/test_311_panel_folded.py | 2 + 8 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 B07_DesignDetail/B07_DesignDetail_UI_Development.ts diff --git a/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts b/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts index 2efd5bcc4..6258b53d6 100644 --- a/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts +++ b/B07_DesignDetail/B07_DesignDetail_Api_Fetch.ts @@ -16,6 +16,7 @@ export interface DesignDrawingItem { | "landuse" | "plan_lidar" | "cross_standard" + | "development" | "blank"; label: string; chainage_m: number | null; @@ -112,6 +113,7 @@ export interface DesignDrawingResponse { | "landuse" | "plan_lidar" | "cross_standard" + | "development" | "blank"; label: string; drawing: CadDrawing; diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Development.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Development.py index 7d5b481cc..e1c4381c9 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Development.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Development.py @@ -252,7 +252,7 @@ def development_sheets(rows: list[dict[str, Any]]) -> list[dict[str, Any]]: { "id": kind_id if index == 1 else f"{kind_id}_{index}", "kind_id": kind_id, - "title": title, + "title": title if index == 1 else f"{title} ({index})", "group": group, "form": form, "rows": chunk, @@ -290,15 +290,15 @@ def _read_json(path: Path) -> dict[str, Any]: def development_list( project_root: Path, manifest_drawings: dict[str, Any] ) -> list[DesignDrawingItem]: - """좌측 「전개도」 카드 단추 — 장마다 한 줄(plan_kind = 묶음 · 형태 · 하위 묶음).""" + """좌측 「전개도」 카드 단추 — 장마다 한 줄 · 하위 묶음(plan_kind) = 구조물 종류.""" return [ DesignDrawingItem( id=sheet["id"], kind="development", label=sheet["title"], confirmed=bool(manifest_drawings.get(sheet["id"], {}).get("confirmed")), - plan_kind=sheet["kind_id"], - plan_kind_name=sheet["title"], + plan_kind=f"development_{sheet['group']}", + plan_kind_name=GROUPS[sheet["group"]], ) for sheet in development_sheets(development_rows(project_root)) ] diff --git a/B07_DesignDetail/B07_DesignDetail_Router.py b/B07_DesignDetail/B07_DesignDetail_Router.py index 7ffc5ceb4..84436bea2 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router.py +++ b/B07_DesignDetail/B07_DesignDetail_Router.py @@ -27,6 +27,7 @@ from B06_Section.B06_Section_Repository import ( get_project_standard_cross_section, merge_cross_section_design_by_round, ) +from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Development import DEVELOPMENT_ID from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import ( CROSS_SHEET_ID, ) @@ -341,6 +342,9 @@ async def get_design_drawing( if row else None ) + elif DEVELOPMENT_ID.fullmatch(drawing_id): + # 전개도 — 추가 흙막이 단 수 · 포장 빈 폭을 측점별 설계에서 읽음(377) + source_design = await _designs_by_chainage(route_id) elif PLAN_KIND_ID.fullmatch(drawing_id): # 계획평면도 — 정보 조각을 서버가 계산 · 파일로 두고 그 종류가 고른 것만 얹는다. context, reason = await load_drainage_context(project_id) diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index b6cb11762..397e8f814 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -34,6 +34,11 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad_CulvertNote import ( from B07_DesignDetail.B07_DesignDetail_Engine_Cad_CulvertNote import ( station_no_label as station_no_label_upper, ) +from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Development import ( + DEVELOPMENT_ID, + development_list, + read_development, +) from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Long import ( build_longitudinal_drawing, longitudinal_chunks, @@ -243,6 +248,8 @@ def _drawing_list(project_root: Path, longitudinal_path: Path) -> list[DesignDra ) ) # 표준도(구조물도)는 2026-09-13 B08 「구조물도」 탭으로 이관 — 사용자 확정 ③, PLAN 3장 ④. + # 전개도(요구 241 · 377) — 맨 뒤 · (구조물 종류 · 형태)마다 장 + drawings += development_list(project_root, manifest_drawings) _store_drawing_numbers(project_root, drawings) return drawings @@ -480,6 +487,8 @@ def _read_drawing( kind = drawing_id # id와 kind가 같은 단장 도면 elif PLAN_KIND_ID.fullmatch(drawing_id): kind = "plan" + elif DEVELOPMENT_ID.fullmatch(drawing_id): + kind = "development" else: kind = "longitudinal" if _LONG_ID.fullmatch(drawing_id) else "cross" label = str(manifest_entry.get("label") or drawing_id) @@ -531,6 +540,10 @@ def _read_drawing( None, ) + if DEVELOPMENT_ID.fullmatch(drawing_id): + # stored_design = 노선 전체 측점별 설계(추가 흙막이 단 수 · 포장 빈 폭) + return read_development(project_root, longitudinal_path, drawing_id, stored_design) + if PLAN_KIND_ID.fullmatch(drawing_id): # stored_design = plan_kind_source()가 계산 · 저장한 조각 중 그 종류가 고른 것. if not isinstance(stored_design, dict): diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Development.ts b/B07_DesignDetail/B07_DesignDetail_UI_Development.ts new file mode 100644 index 000000000..a91ad770d --- /dev/null +++ b/B07_DesignDetail/B07_DesignDetail_UI_Development.ts @@ -0,0 +1,50 @@ +/* ============================================================================= + * B07_DesignDetail_UI_Development.ts + * 좌측 「전개도」 카드(요구 241 · PLAN 377) — 늘 카드 · 구조물 종류마다 하위 묶음(이름 줄 + 형태 단추). + * + * 묶음 = `groupPlanSheets`(plan_kind = 구조물 종류 · plan_kind_name = 이름) 재사용 · 모양 = 평면도 묶음 class. + * 단추 글 = 장 이름에서 종류 이름 · 「전개도」를 뺀 형태(전체 이름은 툴팁). + * ========================================================================== */ + +import { createPanelCard } from "@ui/ui_template_collapsible"; +import type { DesignDrawingItem } from "./B07_DesignDetail_Api_Fetch"; +import { groupPlanSheets } from "./B07_DesignDetail_UI_PlanKinds"; + +/** 「관보호공 돌쌓기(찰) 전개도 (2)」 → 「돌쌓기(찰) (2)」 · 포장 「콘크리트포장 전개도」 → 「콘크리트포장」. */ +function shortLabel(label: string, kindName: string): string { + const rest = label.replace(" 전개도", ""); + return rest.startsWith(`${kindName} `) ? rest.slice(kindName.length + 1) : rest; +} + +export function buildDevelopmentSection( + items: DesignDrawingItem[], + makeButton: (drawing: DesignDrawingItem, label: string) => HTMLButtonElement, + collapsed = false, +): HTMLElement { + const section = createPanelCard(`전개도 ${items.length}`, collapsed); + section.classList.add("b07-drawing-group"); + section.dataset.kind = "development"; + if (items.length === 0) { + const empty = document.createElement("span"); + empty.className = "b07-plan-kind__name"; + empty.textContent = "종단 길이를 가진 구조물이 없습니다."; + section.append(empty); + return section; + } + for (const { kindId, name, sheets } of groupPlanSheets(items)) { + const block = document.createElement("div"); + block.className = "b07-plan-kind"; + block.dataset.planKind = kindId; + const title = document.createElement("span"); + title.className = "b07-plan-kind__name"; + title.textContent = name; + block.append(title); + for (const { drawing } of sheets) { + const button = makeButton(drawing, shortLabel(drawing.label, name)); + button.title = drawing.label; + block.append(button); + } + section.append(block); + } + return section; +} diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts b/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts index a9eb48d9f..4be3b3c98 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts @@ -10,6 +10,7 @@ import { attachCollapsible, createPanelCard } from "@ui/ui_template_collapsible" import { createButton, createInputField, showToast } from "@ui/ui_template_elements"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; import type { CrossDesignInfo, DesignDrawingItem } from "./B07_DesignDetail_Api_Fetch"; +import { buildDevelopmentSection } from "./B07_DesignDetail_UI_Development"; import { buildPlanSection } from "./B07_DesignDetail_UI_PlanKinds"; import { openPlanManager, type PlanManageControls } from "./B07_DesignDetail_UI_PlanManage"; @@ -37,6 +38,8 @@ export const DRAWING_GROUPS: readonly { // 표준 횡단면도는 횡단도 묶음 맨 위 한 줄(93-5). { label: "횡단도", kind: "cross" }, { label: "토적도(유토곡선)", kind: "mass_haul" }, + // 전개도(요구 241 · 377) — 늘 카드 · 구조물 종류마다 하위 묶음 + { label: "전개도", kind: "development" }, ]; /** 311 기본 접힘 묶음 — 그 밖 묶음(토적도 등)은 기본 펼침. */ @@ -120,6 +123,11 @@ export function buildDrawingSidePanel( ); continue; } + if (group.kind === "development") { + const sheets = drawings.filter((item) => item.kind === "development"); + panel.append(buildDevelopmentSection(sheets, drawingButton, isFolded("development"))); + continue; + } const items = group.kind ? drawings.filter((item) => item.kind === group.kind) : group.idPrefix diff --git a/resources/tester/test_241_development.py b/resources/tester/test_241_development.py index 5c145cf9e..19a568b25 100644 --- a/resources/tester/test_241_development.py +++ b/resources/tester/test_241_development.py @@ -207,7 +207,8 @@ def test_설계_없으면_단수_안_거름_목록(root): assert sheet["missing"] == 1 and len(sheet["rows"]) == 2 items = dev.development_list(root, {}) assert {i.kind for i in items} == {"development"} - assert items[0].plan_kind == "development_pipe0" + assert (items[0].plan_kind, items[0].plan_kind_name) == ("development_pipe", "관보호공") + assert items[0].label == "관보호공 돌쌓기(찰) 전개도" def test_넘치면_2장(): @@ -215,6 +216,7 @@ def test_넘치면_2장(): sheets = dev.development_sheets(rows) assert [s["id"] for s in sheets] == ["development_soil3", "development_soil3_2"] assert [len(s["rows"]) for s in sheets] == [30, 5] + assert sheets[1]["title"] == "흙막이 돌망태 전개도 (2)" def test_높이_빈칸_박스와_미입력_글_무늬_DXF(root): diff --git a/resources/tester/test_311_panel_folded.py b/resources/tester/test_311_panel_folded.py index 7b4aaf40a..87a079efd 100644 --- a/resources/tester/test_311_panel_folded.py +++ b/resources/tester/test_311_panel_folded.py @@ -64,6 +64,7 @@ def test_B07_첫_그림_접힘(out): "longitudinal": True, "cross": True, "mass_haul": False, + "development": False, # 377 전개도 카드 — 기본 펼침 } @@ -73,6 +74,7 @@ def test_B07_다시_세워도_접힘_이어_받음(out): "longitudinal": True, "cross": False, "mass_haul": True, + "development": False, }