From 41f0c3d6e3b276c2bbbdb0a843859654f2a92f0c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 11:46:49 +0900 Subject: [PATCH] =?UTF-8?q?feat(b07):=20=ED=9A=A1=EB=8B=A8=20=EC=9B=90?= =?UTF-8?q?=EC=A7=80=EB=B0=98=20=EC=B8=B5=20=EC=9E=A0=EA=B8=88=20=ED=92=82?= =?UTF-8?q?=20=C2=B7=20=EC=B8=A1=EC=A0=90=20=ED=95=9C=20=EB=B2=8C=20groupI?= =?UTF-8?q?d(180-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 장 · 측점 한 장 원지반 층 잠금 지움(종단 그대로) - 장의 측점마다 그림 · 표 · 글 groupId 하나 · cross_placements group_id - 화면 덧그림(설계선 · 구조물)도 같은 groupId - 새 시험 test_180_b07_cross_group Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL --- .../B07_DesignDetail_Engine_Cad.py | 4 +- .../B07_DesignDetail_Engine_Cad_Sheet.py | 10 ++- .../B07_DesignDetail_UI_Cad_Structures.ts | 3 + resources/tester/test_180_b07_cross_group.py | 85 +++++++++++++++++++ 4 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 resources/tester/test_180_b07_cross_group.py diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py index f6813dc6f..41a46d64b 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad.py @@ -19,7 +19,7 @@ from uuid import UUID, uuid5 from config.config_system import DRAWING_SCALE_CROSS -# 레이어 정의 — 지표면선은 상세설계 제어 대상에서 제외하므로 잠금한다(N-1-3). +# 레이어 정의 — 지표면선은 종단에서 잠금(N-1-3) · 횡단은 장에서 옮기므로 풂(180-1). GROUND_LAYER_ID = "b08-ground" GROUND_COLOR = "#f5f7fa" DESIGN_LAYER_ID = "b08-design" @@ -622,7 +622,7 @@ def build_cross_drawing( } ], "layers": [ - _layer(GROUND_LAYER_ID, "원지반", locked=True), + _layer(GROUND_LAYER_ID, "원지반"), _layer(DESIGN_LAYER_ID, "계획선"), _layer(STRUCTURE_LAYER_ID, "구조물"), _layer(ROCK_LAYER_ID, "암 경계선"), diff --git a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Sheet.py b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Sheet.py index 46818bbd6..f9e8297bd 100644 --- a/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Sheet.py +++ b/B07_DesignDetail/B07_DesignDetail_Engine_Cad_Sheet.py @@ -9,6 +9,7 @@ import re from typing import Any +from uuid import uuid5 from B05_Profile.B05_Profile_Engine_Sections import cross_key from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( @@ -22,6 +23,7 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( ROCK_LAYER_ID, STRUCTURE_LAYER_ID, TABLE_LINE_COLOR, + _ENTITY_NS, _clip_polyline, _layer, _section_window, @@ -211,6 +213,12 @@ def build_cross_sheet(sheet: dict[str, Any], sections: list[dict[str, Any]]) -> left, _bottom, right, top = frames[0] if frames and frames[0] else cell if left < cell[0] - 1e-6 or right > cell[2] + 1e-6 or top > cell[3] + 1e-6: overflow.append(cell) + # 측점 한 벌 = groupId 하나 — 한 번 클릭에 그림 · 표 · 글 함께(180-1) + group_id = str(uuid5(_ENTITY_NS, f"{seed_id}:group")) + for entity in placed["entities"]: + entity["groupId"] = group_id + for placement in placed.get("cross_placements") or []: + placement["group_id"] = group_id entities.extend(placed["entities"]) placements.extend(placed.get("cross_placements") or []) @@ -233,7 +241,7 @@ def build_cross_sheet(sheet: dict[str, Any], sections: list[dict[str, Any]]) -> # 칸보다 큰 단면의 칸 [x0, y0, x1, y1] — 라우터가 CAD 가이드 붉은 틀로 보냄(169-2) "overflow_cells": overflow, "layers": [ - _layer(GROUND_LAYER_ID, "원지반", locked=True), + _layer(GROUND_LAYER_ID, "원지반"), _layer(DESIGN_LAYER_ID, "계획선"), _layer(STRUCTURE_LAYER_ID, "구조물"), _layer(ROCK_LAYER_ID, "암 경계선"), diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts b/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts index e5aeaa014..f0e55d6ba 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Cad_Structures.ts @@ -32,6 +32,8 @@ export interface CrossPlacement { x1: number; /** 블록 테두리(종이 mm) [x0, y0, x1, y1] — 그림을 이 안으로 자른다. */ frame?: number[]; + /** 측점 한 벌 묶음(180-1) — 덧그림도 같은 묶음으로 함께 골라짐. */ + group_id?: string; } /** 측점과 도면 배치를 같은 자리로 볼 허용 오차(m). 정본이 누가거리를 0.01m로 끊어 쓴다. */ @@ -78,6 +80,7 @@ export async function appendStructureEntities( continue; // 한 측점의 기하 실패가 도면 전체를 막으면 안 된다. } if (!entities.length) continue; + if (placement.group_id) for (const entity of entities) entity.groupId = placement.group_id; // 이 블록 설계선은 우리가 다시 그렸다 — 서버가 낸 트림 전 설계선은 걷어낸다. replaced.push(frame); fresh.push(...entities); diff --git a/resources/tester/test_180_b07_cross_group.py b/resources/tester/test_180_b07_cross_group.py new file mode 100644 index 000000000..4feffb1a1 --- /dev/null +++ b/resources/tester/test_180_b07_cross_group.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +"""180-1 B07 횡단 장 — 원지반선 고르기 · 측점 한 벌 groupId. + +못박는 것 + - 횡단 장 · 측점 한 장의 원지반 층은 안 잠김(종단은 그대로 잠김). + - 측점마다 groupId 하나(원지반 · 계획선 · 표 · 글 전부) · 측점끼리 다름. + - 칸 격자선 · 도각엔 groupId 없음. + - cross_placements 의 group_id = 그 측점 엔티티 값(화면 덧그림이 같은 묶음). +""" + +from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( + FRAME_LAYER_ID, + GROUND_LAYER_ID, + build_cross_drawing, +) +from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import ( + build_cross_sheet, + plan_cross_sheets, +) +from B07_DesignDetail.B07_DesignDetail_Engine_Template import use_project_templates + + +def _source(chainage: float) -> dict: + return { + "chainage_m": chainage, + "samples": [ + {"valid": True, "offset_m": float(x), "elevation_m": 100.0 + 0.1 * x} + for x in range(-5, 6) + ], + } + + +def _design_line() -> list[dict]: + return [{"offset_m": x, "elevation_m": 100.0} for x in (-3.0, 0.0, 3.0)] + + +def _drawing() -> dict: + use_project_templates(None) + chainages = [0.0, 20.0, 40.0, 60.0] + sheet = plan_cross_sheets(chainages, {"default": [2, 2]})[0] + sections = [ + { + "chainage": c, + "source": _source(c), + "design_line": _design_line(), + "design": {"structure_note": "L형 측구"}, + "quantity_table": {"ground": 1.0, "planned": 0.5}, + "title": f"No.{int(c // 20)}", + } + for c in chainages + ] + return build_cross_sheet(sheet, sections) + + +def _ground_locked(drawing: dict) -> bool: + return next(layer for layer in drawing["layers"] if layer["id"] == GROUND_LAYER_ID)["isLocked"] + + +def test_원지반_층_안_잠김() -> None: + assert _ground_locked(_drawing()) is False + single = build_cross_drawing(_source(0.0), "cross_00000m", _design_line(), None, None, "") + assert _ground_locked(single) is False + + +def test_측점마다_groupId_하나() -> None: + drawing = _drawing() + placements = drawing["cross_placements"] + assert len(placements) == 4 + group_ids = [p["group_id"] for p in placements] + assert len(set(group_ids)) == 4 # 측점끼리 다름 + grouped = [e for e in drawing["entities"] if "groupId" in e] + assert {e["groupId"] for e in grouped} == set(group_ids) + for group_id in group_ids: + members = [e for e in grouped if e["groupId"] == group_id] + layers = {e["layerId"] for e in members} + types = {e["type"] for e in members} + assert GROUND_LAYER_ID in layers and "Table" in types and "Text" in types + # 격자선 · 도각은 묶지 않음 + assert all("groupId" not in e for e in drawing["entities"] if e["layerId"] == FRAME_LAYER_ID) + assert all(e["layerId"] != FRAME_LAYER_ID for e in grouped) + + +def test_groupId_결정적() -> None: + first = [p["group_id"] for p in _drawing()["cross_placements"]] + assert first == [p["group_id"] for p in _drawing()["cross_placements"]]