"""B07 종단도 — 장마다 그림 좌하단 = 작도영역 좌하단 · 고정 축척 · 넘치면 장을 더 나눔 (59-3). 우측 상단 평면 위치도(521-2) — 칸 자리 · 그래프와 안 겹침 · 장마다 강조 구간 · 척도. """ import json import math import pytest from B07_DesignDetail.B07_DesignDetail_Engine_Cad import FRAME_LAYER_ID from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Long import ( build_longitudinal_drawing, longitudinal_chunks, ) from B07_DesignDetail.B07_DesignDetail_Engine_Cad_LongPlan import ( LONG_PLAN_LAYER_ID, plan_box_overlaps, ) from B07_DesignDetail.B07_DesignDetail_Engine_Template import ( drawing_scales_path, entities_bbox, usable_area, use_project_templates, ) from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import ( _read_manifest, _write_manifest, release_old_long_sheets, ) def _longitudinal(count: int, interval: float, slope: float) -> dict: xs = [index * interval for index in range(count)] return { "samples": [{"valid": True, "chainage_m": x, "elevation_m": 100.0 + slope * x} for x in xs], "stations": [{"chainage_m": x} for x in xs], "design_profiles": [ {"samples": [{"chainage_m": x, "elevation_m": 100.5 + slope * x} for x in xs]} ], } def _sheets(longitudinal: dict) -> list[tuple[tuple, tuple]]: result = [] for chunk in longitudinal_chunks(longitudinal): entities = build_longitudinal_drawing(longitudinal, chunk["id"], chunk)["entities"] content = entities_bbox([e for e in entities if e.get("layerId") != FRAME_LAYER_ID]) frame = entities_bbox([e for e in entities if e.get("layerId") == FRAME_LAYER_ID]) result.append((content, frame)) return result @pytest.mark.parametrize( ("count", "interval", "slope"), [(66, 20.0, 0.05), (40, 40.0, 0.0), (40, 20.0, 0.25)], ) def test_every_sheet_fits_and_starts_at_same_corner(count, interval, slope) -> None: longitudinal = _longitudinal(count, interval, slope) sheets = _sheets(longitudinal) usable_w, usable_h = usable_area() corners = set() for content, frame in sheets: assert content[2] - content[0] <= usable_w + 1e-6 assert content[3] - content[1] <= usable_h + 1e-6 corners.add((round(content[0] - frame[0], 3), round(content[1] - frame[1], 3))) assert len(corners) == 1 chunks = longitudinal_chunks(longitudinal) # 이어지는 장은 경계 측점 하나를 나눠 갖고 끝까지 덮는다. assert chunks[0]["start_m"] == 0.0 assert chunks[-1]["end_m"] == (count - 1) * interval for before, after in zip(chunks, chunks[1:]): assert before["end_m"] == after["start_m"] def test_wide_interval_splits_more() -> None: # 40 m 간격 30측점 = 1,160 mm > 작도영역 폭 → 30개보다 적게 담는다 chunks = longitudinal_chunks(_longitudinal(40, 40.0, 0.0)) assert len(chunks) > 2 # ── 우측 상단 평면 위치도(521-2 · 요구 340-2) ───────────────────────────────── def _with_xy(longitudinal: dict) -> dict: for station in longitudinal["stations"]: chainage = station["chainage_m"] station["center_x"] = 200000.0 + chainage station["center_y"] = 500000.0 + 60.0 * math.sin(chainage / 150.0) return longitudinal def _plan_sheets(longitudinal: dict) -> list[dict]: """장마다 그래프 bbox · 칸 엔티티 · 굵은(이 장) 선 꼭짓점 · 글.""" sheets = [] for chunk in longitudinal_chunks(longitudinal): entities = build_longitudinal_drawing(longitudinal, chunk["id"], chunk)["entities"] graph = [e for e in entities if e["layerId"] not in (FRAME_LAYER_ID, LONG_PLAN_LAYER_ID)] plan = [e for e in entities if e["layerId"] == LONG_PLAN_LAYER_ID] bold = [ (child["shapeData"][key]["x"], child["shapeData"][key]["y"]) for entity in plan for child in entity.get("children") or [] if child["lineWidth"] == 3 for key in ("startPoint", "endPoint") ] texts = [e["shapeData"]["label"] for e in plan if e["type"] == "Text"] sheets.append( { "graph": graph, "bbox": entities_bbox(graph), "plan": plan, "bold": bold, "texts": texts, } ) return sheets def test_평면_칸_작도영역_우상단_안_겹침_장마다_강조() -> None: longitudinal = _with_xy(_longitudinal(66, 20.0, 0.05)) sheets = _plan_sheets(longitudinal) usable_w, usable_h = usable_area() lows = [] for number, sheet in enumerate(sheets, start=1): x0, y0 = sheet["bbox"][0], sheet["bbox"][1] box = entities_bbox(sheet["plan"]) # 칸 = 작도 영역 우상단 5 mm 안쪽 150×100 mm assert box == pytest.approx( (x0 + usable_w - 155.0, y0 + usable_h - 105.0, x0 + usable_w - 5.0, y0 + usable_h - 5.0) ) assert not plan_box_overlaps(longitudinal, sheet["graph"], sheet["bbox"]) assert f"{number}/{len(sheets)} 장" in sheet["texts"] assert any(text.startswith("평면도 S=1/") for text in sheet["texts"]) assert sheet["bold"] # 이 장 구간 굵게 lows.append(min(x for x, _y in sheet["bold"]) - box[0]) # 자동 = 모든 장이 같은 축척 · 같은 자리 — 굵은 구간만 장마다 오른쪽으로 옮겨 감 assert lows == sorted(lows) and len(set(lows)) == len(lows) def test_칸을_덮으면_측점_줄여_장_더_나눔() -> None: # 25 m 간격 30 측점 = 725 mm(폭 안) · 오르막 그래프가 우상단 칸을 덮음 plain = longitudinal_chunks(_longitudinal(60, 25.0, 0.1)) with_box = _with_xy(_longitudinal(60, 25.0, 0.1)) assert longitudinal_chunks(with_box)[0]["end_m"] < plain[0]["end_m"] for sheet in _plan_sheets(with_box): assert not plan_box_overlaps(with_box, sheet["graph"], sheet["bbox"]) def test_좌표_없으면_칸_없음() -> None: assert all(not sheet["plan"] for sheet in _plan_sheets(_longitudinal(40, 20.0, 0.0))) def test_척도_정하면_이_장_확대_밖_흐린_파선(tmp_path) -> None: longitudinal = _with_xy(_longitudinal(66, 20.0, 0.05)) use_project_templates(tmp_path) try: path = drawing_scales_path(tmp_path) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps({"longitudinal": {"plan": 3000}}), encoding="utf-8") sheets = _plan_sheets(longitudinal) finally: use_project_templates(None) for sheet in sheets: assert "평면도 S=1/3,000" in sheet["texts"] assert any(entity.get("lineDash") for entity in sheet["plan"]) # 밖 노선 파선 box = entities_bbox(sheet["plan"]) xs = [x for x, _y in sheet["bold"]] # 이 장 중심 = 칸 노선 자리 가운데 assert (min(xs) + max(xs)) / 2.0 == pytest.approx((box[0] + box[2]) / 2.0, abs=1.0) def test_칸_앞_확정_종단만_한_번_풂(tmp_path) -> None: drawings = tmp_path / "B07_DesignDetail" / "drawings" drawings.mkdir(parents=True) old = {"kind": "longitudinal", "confirmed": True, "file": "x"} _write_manifest( tmp_path, { "drawings": { "longitudinal_1": dict(old), "longitudinal_2": dict(old), "cross_00020m": {"kind": "cross", "confirmed": True, "file": "x"}, } }, ) for name in ("longitudinal_1", "longitudinal_2", "cross_00020m"): (drawings / f"{name}.json").write_text('{"layers": []}', encoding="utf-8") release_old_long_sheets(tmp_path) after = _read_manifest(tmp_path)["drawings"] assert not after["longitudinal_1"]["confirmed"] and not after["longitudinal_2"]["confirmed"] assert after["cross_00020m"]["confirmed"] assert not (drawings / "longitudinal_1.json").exists() # 옛 저장본 걷힘 # 새 확정본(칸 층 있음)은 그대로 after["longitudinal_1"] = dict(old) _write_manifest(tmp_path, {"drawings": after}) (drawings / "longitudinal_1.json").write_text( json.dumps({"layers": [{"id": LONG_PLAN_LAYER_ID}]}), encoding="utf-8" ) release_old_long_sheets(tmp_path) assert _read_manifest(tmp_path)["drawings"]["longitudinal_1"]["confirmed"]