Files
Aislo/resources/tester/test_141_b07_plan_labels.py
T
eomsangdonandClaude Opus 5.5 f70345e704 feat(B07): 평면도 IP 글 · 지시선 색을 노선 중심선 색으로(153-2)
- 실측 = IP 글 #4d7dff 와 교각선 · 원 #4d7dff 이미 같음 → 정함대로 중심선 #ff4d4d
- IP 기호 · R 글 색 그대로 · test_141 색 단정 추가

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
2026-10-09 19:04:50 +09:00

184 lines
6.8 KiB
Python

# -*- coding: utf-8 -*-
"""141-1 B07 평면도 글자 겹침 피하기.
못박는 것
- 우선순위 = 시종점 > 측점 NO > IP > R > 배수 > 대피소 — 앞 글이 첫 후보(옛 자리)를 가짐.
- 후보 중 첫 빈 자리 · 모두 막히면 첫 후보 그대로(글은 남음).
- 장애물(IP 표 · 필지 상자) 비켜 감.
- 가까운 IP · R · 배수 둘 · NO.0 + 시점 한 장 → 글 사각 겹침 0 · 글 모두 남음.
- 멀리 놓인 글(후보 far)엔 지시선.
"""
from itertools import combinations
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import build_plan_kind_drawing
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanLabels import (
overlaps,
place_labels,
text_rect,
)
def _label(piece: str, label: str, spots: list[dict], anchor=None) -> dict:
return {
"piece": piece,
"seed": f"s:{piece}:{label}",
"label": label,
"size": 2.5,
"layer": "l",
"color": "#fff",
"anchor": anchor,
"candidates": spots,
}
def test_priority_keeps_first_spot_and_next_moves() -> None:
spot = [{"x": 0.0, "y": 0.0}, {"x": 0.0, "y": 10.0}]
placed = place_labels([_label("refuge", "대피소", spot), _label("endpoints", "시점", spot)], [])
by = {item["piece"]: item["spot"] for item in placed}
assert by["endpoints"] == spot[0] # 시종점이 먼저 — 옛 자리
assert by["refuge"] == spot[1]
def test_all_blocked_falls_back_to_first() -> None:
spot = [{"x": 0.0, "y": 0.0}]
placed = place_labels([_label("ip", "IP1", spot), _label("radius", "R=12", spot)], [])
assert [item["spot"] for item in placed] == [spot[0], spot[0]] # 글은 남음
def test_obstacle_is_avoided() -> None:
spots = [{"x": 0.0, "y": 0.0}, {"x": 30.0, "y": 0.0}]
placed = place_labels([_label("ip", "IP1", spots)], [(-5.0, -5.0, 5.0, 5.0)])
assert placed[0]["spot"] == spots[1]
def _texts(drawing: dict) -> list[dict]:
return [
e
for e in drawing["entities"]
if e["type"] == "Text"
and e["layerId"].startswith("b07-plan-")
and e["layerId"] != "b07-plan-title"
]
def _rect(entity: dict) -> tuple:
shape = entity["shapeData"]
opts = shape["options"]
direction = (opts["textDirection"]["x"], opts["textDirection"]["y"])
return text_rect(
shape["label"],
shape["basePoint"]["x"],
shape["basePoint"]["y"],
opts["fontSize"],
opts["textAlign"],
direction,
)
def test_crowded_sheet_has_no_overlapping_labels() -> None:
box = (0.0, 0.0, 300.0, 200.0)
pieces = {
"ip": {
"items": [
{
"name": "IP1",
"apex": [100.0, 100.0],
"start": [90.0, 100.0],
"end": [100.0, 110.0],
},
{
"name": "IP2",
"apex": [103.0, 101.0],
"start": [95.0, 101.0],
"end": [103.0, 111.0],
},
]
},
"radius": {
"items": [
{"label": "R=12", "middle": [100.0, 98.0], "dir": [0.0, -1.0]},
{"label": "R=15", "middle": [103.0, 99.0], "dir": [0.0, -1.0]},
]
},
"drainage": {
"items": [
{"x": 101.0, "y": 96.0, "label": "D800 L=8.0"},
{"x": 102.0, "y": 95.0, "label": "D600 L=6.0"},
]
},
"station": {
"items": [
{"x": 50.0, "y": 50.0, "nx": 0.0, "ny": 1.0, "label": "NO.0", "major": True},
]
},
"endpoints": {
"items": [{"label": "시점 NO.0", "x": 50.0, "y": 50.0, "nx": 0.0, "ny": 1.0}]
},
}
drawing = build_plan_kind_drawing("plan_x", "시험", box, pieces)
texts = _texts(drawing)
labels = sorted(e["shapeData"]["label"] for e in texts)
assert labels == sorted(
["IP1", "IP2", "R=12", "R=15", "D800 L=8.0", "D600 L=6.0", "NO.0", "시점 NO.0"]
)
pairs = [
(a["shapeData"]["label"], b["shapeData"]["label"])
for a, b in combinations(texts, 2)
if overlaps(_rect(a), _rect(b))
]
assert pairs == []
def test_far_spot_draws_leader() -> None:
near = [{"x": 0.0, "y": 0.0}, {"x": 40.0, "y": 0.0, "far": True}]
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanLabels import label_entities
placed = place_labels([_label("ip", "IP1", near, (0.0, 0.0))], [(-3.0, -3.0, 3.0, 3.0)])
entities = label_entities("plan_x", placed)
assert [e["type"] for e in entities] == ["Text", "PolyLine"]
def test_blocked_spots_push_farther_with_leader() -> None:
"""141-2 — 후보가 다 막히면 기호에서 2 · 3 배 먼 자리(지시선) · 작은 축척에 몰린 장."""
anchor = (0.0, 0.0)
spot = [{"x": 0.0, "y": -4.0}]
placed = place_labels(
[_label("ip", "IP1", spot, anchor), _label("radius", "R=12", spot, anchor)], []
)
assert placed[0]["spot"] == spot[0]
assert placed[1]["spot"] == {"x": 0.0, "y": -8.0, "far": True}
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanLabels import label_entities
entities = label_entities("d", placed)
assert [e["type"] for e in entities] == ["Text", "Text", "PolyLine"]
def test_ip_text_takes_centerline_color() -> None:
"""153-2 — IP 글 · 지시선 = 노선 중심선 색 · 교각선 · 원 · R 글은 조각 색 그대로."""
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import _COLORS, _piece_entities
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanLabels import label_entities
item = {"name": "IP1", "apex": [100.0, 100.0], "start": [90.0, 100.0], "end": [100.0, 110.0]}
pieces = {
"ip": {"items": [item]},
"radius": {"items": [{"label": "R=12", "middle": [100.0, 98.0], "dir": [0.0, -1.0]}]},
}
drawing = build_plan_kind_drawing("plan_x", "시험", (0.0, 0.0, 300.0, 200.0), pieces)
texts = {e["shapeData"]["label"]: e for e in _texts(drawing)}
ip_text = texts["IP1"]
assert ip_text["lineColor"] == _COLORS["centerline"]
assert ip_text["shapeData"]["options"]["textColor"] == _COLORS["centerline"]
assert texts["R=12"]["shapeData"]["options"]["textColor"] == _COLORS["radius"]
marks = [
e for e in drawing["entities"] if e["layerId"] == "b07-plan-ip" and e["type"] == "PolyLine"
]
assert len(marks) == 2 # 교각선 · 원
assert {e["lineColor"] for e in marks} == {_COLORS["ip"]}
labels: list = []
_piece_entities("d", "ip", {"items": [item]}, lambda p: (p[0], p[1]), labels=labels)
far = [{**labels[0], "spot": {"x": 0.0, "y": 0.0, "far": True}, "rect": (0, 0, 1, 1)}]
leader = label_entities("d", far)[1]
assert leader["lineColor"] == _COLORS["centerline"]