- 기본 종류 = 지형도 하나(옛 기본 이관 더하기 지움) · 대피소 조각 · 자리 폼 · plan-marks 끝점 지움 - 모달 양식 칸 = 평면도면 숨김 · 종류 척도 영역 2열 · 항목 3열 유지 + 그룹 구분선(PIECE_GROUPS 한 곳) - 낡은 시험 수선: 기본 종류가 하나로 줄어 route · layout · landuse · watershed 기본 전제를 add_kind 로 세움 · 대피소 자리 시험 지움 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
408 lines
20 KiB
Python
408 lines
20 KiB
Python
"""B07 계획평면도 종류 · 정보 조각 (PLAN 60장).
|
||
|
||
① 측점 둘에서 성토사면 외곽선 폭 = 횡단 설계 값(노견 끝 · 설계선이 지반에 닿는 자리)
|
||
② 종류 더하기 · 지우기(기본 종류 포함) · 다시 읽어 같음 · 기본 = 지형도 하나
|
||
③ (없앰 — 대피소 · 사토장 자리는 524-1 에서 지움)
|
||
④ 고른 조각만 도면 층으로 얹힘
|
||
⑤ 조각 잘게(67장) — 노견선 · 성토 외곽선 · 절토 외곽선 따로 · 기본 처음 체크 = 옛 그림 ·
|
||
옛 종류 파일의 slope_outline 펼침 · 종류 업데이트(기본 포함) · 확정본 풀림 · 배수시설 기호
|
||
⑥ IP 표(94장) — 예시 검산 · 도-분-초 올림 · 장마다 그 장 IP 한 번 · 표 밑 선 자름 · 층
|
||
⑦ 용지도 조각(93장) — 용지 조각 층
|
||
⑧ 배수유역도 종류(109장) — 새 종류로 더함 · 양식 watershed · 한 장 목록 · 지우기 · 다시 만들기 · IP 표 두 줄 차례 ·
|
||
용지도 상자 안 선 0
|
||
⑨ 종류 차례(140-2) — 끌어 놓은 차례 그대로 · 다시 읽어 같음 · 앞선 종류가 바뀐 종류만 확정 풂 · 목록 어긋나면 거절
|
||
"""
|
||
|
||
import json
|
||
import math
|
||
from pathlib import Path
|
||
|
||
import ezdxf
|
||
import pytest
|
||
|
||
from B06_Section.B06_Section_Engine_Design import compute_cross_design
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_IpTable import (
|
||
_place,
|
||
curve_values,
|
||
dms,
|
||
ip_blocks,
|
||
ip_table_piece,
|
||
)
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import (
|
||
DEFAULT_KINDS,
|
||
build_plan_kind_drawing,
|
||
layer_id,
|
||
slope_pieces,
|
||
)
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Export import drawing_to_dxf
|
||
from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import _read_manifest, _write_manifest
|
||
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
|
||
_kinds_dir,
|
||
add_kind,
|
||
delete_kind,
|
||
list_kinds,
|
||
plan_kind_items,
|
||
reorder_kinds,
|
||
slope_records,
|
||
update_kind,
|
||
)
|
||
|
||
|
||
def _samples(slope: float) -> list[dict[str, float]]:
|
||
"""좌(+)가 높은 비탈 지반 — 오프셋 -20 ~ 20 m, 0.5 m 간격."""
|
||
return [
|
||
{"offset_m": i * 0.5, "elevation_m": 100.0 + slope * i * 0.5, "valid": True}
|
||
for i in range(-40, 41)
|
||
]
|
||
|
||
|
||
def _project(tmp_path: Path) -> tuple[Path, dict, dict]:
|
||
"""+y 로 곧게 가는 노선 · 측점 0 · 20 · 40 · 횡단 파일 · B06 저장 설계."""
|
||
root = tmp_path / "project"
|
||
cross_dir = root / "B06_Section" / "cross_sections"
|
||
cross_dir.mkdir(parents=True)
|
||
longitudinal_path = root / "B06_Section" / "longitudinal" / "longitudinal.json"
|
||
longitudinal_path.parent.mkdir(parents=True)
|
||
stations, designs = [], {}
|
||
for chainage, slope in ((0, 0.3), (20, 0.5), (40, 0.4)):
|
||
stations.append(
|
||
{"chainage_m": float(chainage), "center_x": 0.0, "center_y": float(chainage)}
|
||
)
|
||
source = {
|
||
"chainage_m": float(chainage),
|
||
"samples": _samples(slope),
|
||
# +y 진행의 좌측 = -x
|
||
"frame": {"origin": {"x": 0.0, "y": float(chainage)}, "left_xy": [-1.0, 0.0]},
|
||
}
|
||
(cross_dir / f"cross_{chainage:05d}m.json").write_text(json.dumps(source), "utf-8")
|
||
designs[chainage] = compute_cross_design(
|
||
source["samples"], 100.0, ground_type="soil", section_mode="left_cut"
|
||
)
|
||
longitudinal = {"stations": stations}
|
||
longitudinal_path.write_text(json.dumps(longitudinal), "utf-8")
|
||
return longitudinal_path, longitudinal, designs
|
||
|
||
|
||
def _meets_ground(design: dict, side: str) -> float:
|
||
"""설계선이 바깥으로 가며 지반과 다시 같아지는 첫 오프셋 — 시험 쪽 독립 계산."""
|
||
edge = design["road_edges"][side]["offset_m"]
|
||
line = sorted((p["offset_m"], p["elevation_m"]) for p in design["design_line"])
|
||
outward = (
|
||
[p for p in line if p[0] > edge]
|
||
if side == "left"
|
||
else [p for p in line[::-1] if p[0] < edge]
|
||
)
|
||
slope = SLOPES[design["_c"]]
|
||
for offset, elevation in outward:
|
||
# 지반은 직선 비탈이라 어느 오프셋에서나 식으로 바로 잰다(설계선 꺾임점 포함).
|
||
if abs(100.0 + slope * offset - elevation) < 1e-3:
|
||
return offset
|
||
raise AssertionError("설계선이 지반에 닿지 않음")
|
||
|
||
|
||
SLOPES = {0: 0.3, 20: 0.5, 40: 0.4}
|
||
|
||
|
||
def test_outline_width_equals_cross_design_at_two_stations(tmp_path: Path) -> None:
|
||
longitudinal_path, longitudinal, designs = _project(tmp_path)
|
||
records = slope_records(longitudinal_path, longitudinal, designs, 0.0, 40.0)
|
||
route = [(0.0, 0.0, 0.0), (40.0, 0.0, 40.0)]
|
||
edge, fill, cut, ticks = slope_pieces(route, records, 0.0, 40.0)
|
||
left_edge, right_edge = edge["lines"]
|
||
# 좌(+)가 높은 지반 — 좌측 비탈은 절토 · 우측은 성토, 각각 한 줄로 이어짐
|
||
assert all(r["left_cut"] and not r["right_cut"] for r in records)
|
||
(left_end,), (right_end,) = cut["lines"], fill["lines"]
|
||
for chainage in (20, 40): # 견본 측점 둘
|
||
design = {**designs[chainage], "_c": chainage}
|
||
index = next(i for i, p in enumerate(left_edge) if abs(p[1] - chainage) < 1e-6)
|
||
expected = {
|
||
"left_edge": design["road_edges"]["left"]["offset_m"],
|
||
"left_end": _meets_ground(design, "left"),
|
||
"right_edge": design["road_edges"]["right"]["offset_m"],
|
||
"right_end": _meets_ground(design, "right"),
|
||
}
|
||
# 좌측은 -x 쪽 — 중심(0, c)에서 잰 거리가 횡단 설계 오프셋이다.
|
||
assert -left_edge[index][0] == pytest.approx(expected["left_edge"], abs=1e-3)
|
||
assert -left_end[index][0] == pytest.approx(expected["left_end"], abs=1e-3)
|
||
assert -right_edge[index][0] == pytest.approx(expected["right_edge"], abs=1e-3)
|
||
assert -right_end[index][0] == pytest.approx(expected["right_end"], abs=1e-3)
|
||
# 성토 · 절토 비탈이 실제로 노견 밖에 있다
|
||
assert abs(expected["left_end"]) > abs(expected["left_edge"])
|
||
assert abs(expected["right_end"]) > abs(expected["right_edge"])
|
||
# 비탈 눈금은 노견 끝에서 바깥으로 · 긴 것과 짧은 것이 섞임
|
||
lengths = {round(math.dist(*tick), 2) for tick in ticks["ticks"]}
|
||
assert len(ticks["ticks"]) > 20 and len(lengths) > 2
|
||
|
||
|
||
def test_kinds_add_delete_persist(tmp_path: Path) -> None:
|
||
root = tmp_path / "project"
|
||
assert [k["name"] for k in list_kinds(root)] == ["지형도"] # 기본 = 하나
|
||
assert [k["id"] for k in DEFAULT_KINDS] == ["terrain"]
|
||
assert "refuge" not in DEFAULT_KINDS[0]["pieces"]
|
||
added = add_kind(root, "내 평면도", ["centerline", "station", "nope"])
|
||
assert added["pieces"] == ["centerline", "station"]
|
||
with pytest.raises(ValueError):
|
||
add_kind(root, "내 평면도", ["centerline"]) # 같은 이름
|
||
with pytest.raises(ValueError):
|
||
add_kind(root, "빈 것", [])
|
||
delete_kind(root, "terrain") # 기본 종류도 지움
|
||
again = list_kinds(root) # 다시 읽어 같음
|
||
assert [k["name"] for k in again] == ["내 평면도"]
|
||
assert again[-1]["pieces"] == ["centerline", "station"]
|
||
with pytest.raises(FileNotFoundError):
|
||
delete_kind(root, "terrain")
|
||
delete_kind(root, again[0]["id"])
|
||
assert list_kinds(root) == [] # 다 지우면 비어 있음(기본이 되살아나지 않음)
|
||
|
||
|
||
def test_drawing_has_only_chosen_pieces() -> None:
|
||
pieces = {
|
||
"centerline": {"lines": [[[0.0, 0.0], [0.0, 40.0]]]},
|
||
"radius": {"items": [{"label": "R=15", "at": [1.0, 20.0]}]},
|
||
}
|
||
drawing = build_plan_kind_drawing(
|
||
"plan_k1", "계획평면도(시험)", (-50.0, -10.0, 50.0, 50.0), pieces
|
||
)
|
||
layers = {entity.get("layerId") for entity in drawing["entities"]}
|
||
assert layer_id("centerline") in layers and layer_id("radius") in layers
|
||
assert layer_id("contour") not in layers and layer_id("station") not in layers
|
||
labels = [e["shapeData"]["label"] for e in drawing["entities"] if e["type"] == "Text"]
|
||
assert "R=15" in labels and "계획평면도(시험)" in labels
|
||
|
||
|
||
def test_split_runs_switch_cut_fill_and_stay_joined() -> None:
|
||
route = [(0.0, 0.0, 0.0), (40.0, 0.0, 40.0)]
|
||
base = {"left_edge_m": 2.0, "left_end_m": 5.0, "right_edge_m": -2.0, "right_end_m": -5.0}
|
||
records = [
|
||
{"chainage_m": 0.0, **base, "left_cut": True, "right_cut": False},
|
||
{"chainage_m": 40.0, **base, "left_cut": False, "right_cut": False},
|
||
]
|
||
_edge, fill, cut, _ticks = slope_pieces(route, records, 0.0, 40.0)
|
||
(cut_left,) = cut["lines"]
|
||
fill_left = next(line for line in fill["lines"] if line[0][0] < 0)
|
||
assert cut_left[-1] == fill_left[0] # 바뀌는 자리에서 두 선이 이어짐
|
||
assert cut_left[0][1] == 0.0 and fill_left[-1][1] == 40.0
|
||
|
||
|
||
def test_default_kinds_keep_old_picture_and_legacy_file(tmp_path: Path) -> None:
|
||
root = tmp_path / "project"
|
||
outline = {"road_edge", "fill_outline", "cut_outline"}
|
||
# 옛 slope_outline 자리를 나눈 셋이 그대로 채움 · 배수시설은 옛 그림에 없음(용지도는 따로)
|
||
for kind in DEFAULT_KINDS:
|
||
assert outline <= set(kind["pieces"]) and "drainage" not in kind["pieces"]
|
||
folder = _kinds_dir(root)
|
||
folder.mkdir(parents=True)
|
||
(folder / "old.json").write_text(
|
||
json.dumps({"name": "옛것", "pieces": ["slope_outline", "station"], "order": 0}), "utf-8"
|
||
)
|
||
kind = next(k for k in list_kinds(root) if k["id"] == "old")
|
||
assert set(kind["pieces"]) == outline | {"station"}
|
||
|
||
|
||
def test_update_kind_default_and_release_confirmed(tmp_path: Path) -> None:
|
||
root = tmp_path / "project"
|
||
route = add_kind(root, "노선배치도", ["centerline"])["id"]
|
||
manifest = _read_manifest(root)
|
||
manifest["drawings"] = {
|
||
"plan_terrain": {"confirmed": True},
|
||
f"plan_{route}": {"confirmed": True},
|
||
}
|
||
_write_manifest(root, manifest)
|
||
update_kind(root, "terrain", "지형2", ["contour", "centerline"]) # 기본 종류 고침
|
||
again = {k["id"]: k for k in list_kinds(root)} # 다시 읽어 같음
|
||
assert again["terrain"] == {
|
||
"id": "terrain",
|
||
"name": "지형2",
|
||
"pieces": ["contour", "centerline"],
|
||
"layout": "plan",
|
||
"scale": 1200,
|
||
}
|
||
assert [k["id"] for k in list_kinds(root)][0] == "terrain" # 차례 그대로
|
||
drawings = _read_manifest(root)["drawings"]
|
||
assert drawings["plan_terrain"]["confirmed"] is False
|
||
assert drawings[f"plan_{route}"]["confirmed"] is True
|
||
with pytest.raises(ValueError):
|
||
update_kind(root, "terrain", "노선배치도", ["contour"]) # 다른 종류와 같은 이름
|
||
with pytest.raises(ValueError):
|
||
update_kind(root, "terrain", "지형", [])
|
||
with pytest.raises(FileNotFoundError):
|
||
update_kind(root, "nokind", "없음", ["contour"])
|
||
|
||
|
||
def test_drainage_symbol_piece() -> None:
|
||
pieces = {"drainage": {"items": [{"x": 0.0, "y": 20.0, "label": "D800"}]}}
|
||
drawing = build_plan_kind_drawing("plan_k2", "시험", (-50.0, -10.0, 50.0, 50.0), pieces)
|
||
mine = [e for e in drawing["entities"] if e.get("layerId") == layer_id("drainage")]
|
||
assert {e["type"] for e in mine} >= {"Text"} and len(mine) >= 2
|
||
assert any(e["type"] == "Text" and e["shapeData"]["label"] == "D800" for e in mine)
|
||
|
||
|
||
def test_ip_table_example_values_and_dms() -> None:
|
||
ia = 94 + 59 / 60 + 58.38 / 3600 # 예시 그림 IP1
|
||
values = curve_values(15.0, ia)
|
||
assert dms(ia) == "94-59-58.38"
|
||
assert [f"{values[k]:.5f}" for k in ("tl", "cl", "sl")] == ["16.36950", "24.87082", "7.20271"]
|
||
assert dms(1 + 59 / 60 + 59.9999 / 3600) == "2-00-00.00" # 초 60.00 올림
|
||
assert dms(5 + 3 / 60 + 7.2 / 3600) == "5-03-07.20"
|
||
|
||
|
||
# ㄱ자 노선 — (0,0)→(100,0)→(100,100) · 모서리 R 15 곡선(가운데 누가거리 100) · 앞에 노선 밖 옛 곡선
|
||
_ROUTE = [(0.0, 0.0, 0.0), (85.0, 85.0, 0.0), (115.0, 100.0, 15.0), (200.0, 100.0, 100.0)]
|
||
_CURVES = [
|
||
{"apex": [500.0, 500.0], "start": [490.0, 500.0], "end": [500.0, 510.0], "radius_m": 10.0},
|
||
{"apex": [100.0, 0.0], "start": [85.0, 0.0], "end": [100.0, 15.0], "radius_m": 15.0},
|
||
]
|
||
|
||
|
||
def test_ip_blocks_one_sheet_each_and_ends() -> None:
|
||
first = ip_blocks(_CURVES, _ROUTE, 1, 2, 0.0, 100.0)
|
||
second = ip_blocks(_CURVES, _ROUTE, 2, 2, 100.0, 200.0)
|
||
assert [b["name"] for b in first] == ["BP"] # 경계 IP 는 뒤 장에만
|
||
assert [b["name"] for b in second] == ["2", "EP"] # 번호 = 곡선표 차례(옛 곡선은 빠짐)
|
||
ip = second[0]
|
||
assert dms(ip["ia"]) == "90-00-00.00" and ip["tl"] == pytest.approx(15.0)
|
||
assert [b["name"] for b in ip_blocks(_CURVES, _ROUTE, 1, 1, 0.0, 200.0)] == ["BP", "2", "EP"]
|
||
|
||
|
||
def test_ip_table_drawing_clips_lines_under_table(tmp_path: Path) -> None:
|
||
box = (-20.0, -20.0, 300.0, 200.0)
|
||
contour = [[-20.0, 0.0], [300.0, 0.0]] # 표(왼쪽 아래 · 높이 48 m) 밑을 지나는 등고선
|
||
pieces = {
|
||
"contour": {"lines": [contour]},
|
||
"centerline": {"lines": [[[0.0, 100.0], [9.0, 100.0]]]},
|
||
}
|
||
pieces["ip_table"] = ip_table_piece(
|
||
pieces, _CURVES, _ROUTE, box, {"number": 1, "start_m": 0.0, "end_m": 200.0}, 1
|
||
)
|
||
# IP 표 칸(x 29.2 ~ 72.4 · y -14 ~ 34)만 빠짐 · BP · EP 칸(3행)은 y 0 위라 안 자름
|
||
assert pieces["contour"]["lines"] == [[[-20.0, 0.0], [29.2, 0.0]], [[72.4, 0.0], [300.0, 0.0]]]
|
||
assert pieces["centerline"]["lines"] == [[[0.0, 100.0], [9.0, 100.0]]] # 표 밖은 그대로
|
||
drawing = build_plan_kind_drawing("plan_ip", "시험", box, pieces)
|
||
mine = [e for e in drawing["entities"] if e.get("layerId") == layer_id("ip_table")]
|
||
assert [e["type"] for e in mine] == ["Table"] * 3
|
||
bp, ip, _ep = (e["shapeData"] for e in mine)
|
||
assert len(bp["rowHeights"]) == 3 and len(ip["rowHeights"]) == 8
|
||
assert bp["cells"][1][2]["text"] == "0.00000" and ip["cells"][1][2]["text"] == "0.00000"
|
||
assert ip["cells"][2][2]["text"] == "100.00000" # Y = 동(x)
|
||
assert ip["cells"][3][1]["text"] == "90-00-00.00"
|
||
assert {layer["id"] for layer in drawing["layers"]} >= {layer_id("ip_table")}
|
||
# DXF — 표 → 선 + 글(IP 묶음: 바깥 4 · 세로 10 · 가로 20 · 글 17)
|
||
raw, skipped = drawing_to_dxf({"entities": [mine[1]], "layers": drawing["layers"]})
|
||
(tmp_path / "ip.dxf").write_bytes(raw)
|
||
space = ezdxf.readfile(tmp_path / "ip.dxf").modelspace()
|
||
assert skipped == {} and len(space.query("LINE")) == 34 and len(space.query("TEXT")) == 17
|
||
assert {"90-00-00.00", "15.00000"} <= {t.dxf.text for t in space.query("TEXT")}
|
||
|
||
|
||
def test_landuse_pieces_become_layers() -> None:
|
||
pieces = {
|
||
"parcel": {"lines": [[[0.0, 0.0], [10.0, 0.0]]]},
|
||
"emd": {"lines": [[[0.0, 5.0], [10.0, 5.0]]]},
|
||
"sgg": {"lines": [[[0.0, 9.0], [10.0, 9.0]]]},
|
||
"parcel_info": {
|
||
"items": [
|
||
{"lines": ["1-1", "임야", "", "국"], "anchor": [5.0, 2.0], "center": [20.0, 20.0]}
|
||
]
|
||
},
|
||
}
|
||
drawing = build_plan_kind_drawing(
|
||
"plan_landuse", "평면도(용지도)", (-50.0, -10.0, 50.0, 50.0), pieces
|
||
)
|
||
names = {layer["id"] for layer in drawing["layers"]}
|
||
assert {layer_id(p) for p in ("parcel", "parcel_info", "emd", "sgg")} <= names
|
||
labels = [
|
||
e["shapeData"]["label"]
|
||
for e in drawing["entities"]
|
||
if e.get("layerId") == layer_id("parcel_info") and e["type"] == "Text"
|
||
]
|
||
assert labels == ["1-1", "임야", "국"]
|
||
|
||
|
||
def test_watershed_kind_layout_one_sheet(tmp_path: Path) -> None:
|
||
"""배수유역도 = 새 종류 · 양식 watershed · 조각 없음 · 장이 여럿이어도 목록은 한 장."""
|
||
root = tmp_path / "project"
|
||
added = add_kind(root, "배수유역도", [], "watershed")
|
||
assert next(k for k in list_kinds(root) if k["id"] == added["id"]) == {
|
||
"id": added["id"],
|
||
"name": "배수유역도",
|
||
"pieces": [],
|
||
"layout": "watershed",
|
||
"scale": 6000,
|
||
"contour_interval_m": 5, # 512-1 유역도 등고선 간격 칸(기본 5 m)
|
||
}
|
||
# 측점 0 ~ 2,000 m — 1/1,200 평면도는 여러 장 · 유역도는 한 장
|
||
stations = [
|
||
{"chainage_m": float(c), "center_x": 0.0, "center_y": float(c)} for c in range(0, 2001, 20)
|
||
]
|
||
items = plan_kind_items(root, {"stations": stations})
|
||
terrain = [item for item in items if item["plan_kind"] == "terrain"]
|
||
mine = [item for item in items if item["plan_kind"] == added["id"]]
|
||
assert len(terrain) > 1
|
||
assert mine == [
|
||
{
|
||
"id": f"plan_{added['id']}",
|
||
"label": "배수유역도",
|
||
"plan_kind": added["id"],
|
||
"plan_kind_name": "배수유역도",
|
||
}
|
||
]
|
||
|
||
|
||
def test_watershed_kind_delete_and_add_again(tmp_path: Path) -> None:
|
||
"""× 로 지우면 안 살아남 · 「새 종류」 양식 유역도로 다시 만듦 · 업데이트는 양식 그대로."""
|
||
root = tmp_path / "project"
|
||
first = add_kind(root, "배수유역도", [], "watershed")
|
||
delete_kind(root, first["id"])
|
||
assert first["id"] not in [k["id"] for k in list_kinds(root)]
|
||
assert first["id"] not in [k["id"] for k in list_kinds(root)] # 다시 읽어도 안 살아남
|
||
added = add_kind(root, "배수유역도", ["contour"], "watershed") # 조각은 버림
|
||
assert added["layout"] == "watershed" and added["pieces"] == []
|
||
again = next(k for k in list_kinds(root) if k["id"] == added["id"])
|
||
assert again == added
|
||
assert any(
|
||
item["id"] == f"plan_{added['id']}" for item in plan_kind_items(root, {"stations": []})
|
||
)
|
||
update_kind(root, added["id"], "유역도2", []) # 유역도 양식은 조각 없이 업데이트
|
||
assert next(k for k in list_kinds(root) if k["id"] == added["id"])["layout"] == "watershed"
|
||
with pytest.raises(ValueError):
|
||
add_kind(root, "이상한 양식", ["contour"], "nope")
|
||
|
||
|
||
def test_ip_table_two_lines_left_to_right_top_to_bottom() -> None:
|
||
"""한 줄 셋까지 들어가는 폭 · 묶음 넷 — 첫 줄(1 · 2 · 3) 맨 위 왼 → 오 · 넷째는 아랫줄 왼쪽."""
|
||
blocks = [{"name": str(i), "ia": 1.0} for i in range(1, 5)]
|
||
block_w = 7.0 + 6.0 + 23.0
|
||
_place(blocks, 2 * 5.0 + 3 * block_w + 1.0)
|
||
origins = [b["origin"] for b in blocks]
|
||
tops = [o[1] for o in origins]
|
||
assert tops[0] == tops[1] == tops[2] > tops[3] # 첫 줄이 위
|
||
assert origins[0][0] < origins[1][0] < origins[2][0] # 왼 → 오
|
||
assert origins[3][0] == origins[0][0] # 다음 줄은 다시 왼쪽
|
||
assert tops[3] == 5.0 + 8 * 5.0 # 마지막 줄이 도곽 왼쪽 아래 기준(옛 한 줄 자리와 같음)
|
||
assert tops[0] - tops[3] == 8 * 5.0 + 3.0 # 줄 간격 그대로
|
||
|
||
|
||
def test_reorder_kinds_order_and_release_renumbered(tmp_path: Path) -> None:
|
||
root = tmp_path / "project"
|
||
second, third = (add_kind(root, name, ["centerline"])["id"] for name in ("둘째", "셋째"))
|
||
manifest = _read_manifest(root)
|
||
manifest["drawings"] = {
|
||
"plan_terrain": {"confirmed": True},
|
||
f"plan_{second}": {"confirmed": True},
|
||
f"plan_{third}": {"confirmed": True},
|
||
}
|
||
_write_manifest(root, manifest)
|
||
before = [k["id"] for k in list_kinds(root)]
|
||
assert before == ["terrain", second, third]
|
||
after = [second, "terrain", third] # 앞 둘만 바꿈
|
||
reorder_kinds(root, after)
|
||
assert [k["id"] for k in list_kinds(root)] == after # 다시 읽어 같음
|
||
drawings = _read_manifest(root)["drawings"]
|
||
assert drawings["plan_terrain"]["confirmed"] is False # 번호 바뀜
|
||
assert drawings[f"plan_{second}"]["confirmed"] is False
|
||
assert drawings[f"plan_{third}"]["confirmed"] is True # 앞선 종류 묶음 그대로 = 번호 그대로
|
||
with pytest.raises(ValueError):
|
||
reorder_kinds(root, after[:-1]) # 빠진 종류
|
||
with pytest.raises(ValueError):
|
||
reorder_kinds(root, [*after[:-1], "terrain"]) # 겹친 종류
|