- 축척 = 1/500 · 600 · 1,000 · 1,200(기본) · 2,500 · 3,000 · 5,000 · 6,000(지식DB 근거) - 비탈 눈금 간격 · 대피소 띄움 · 필지 버퍼 · 틈 = 1/1,200 실거리 × 축척 비(종이 거리 같게 · 1/6,000 눈금 10 m) - 글 후보가 다 막히면 기호에서 2 · 3 배 먼 자리 + 지시선 — 검증_sub8 1/6,000 겹친 쌍 55 → 4 - 시험 test_119 · test_141 늘림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
137 lines
4.9 KiB
Python
137 lines
4.9 KiB
Python
"""B07 평면도 글 자리 잡기(141-1) — 글끼리 · 장애물(IP 표 · 필지 상자)과 안 겹치게.
|
|
|
|
조각(`_piece_entities`)은 글을 바로 그리지 않고 후보 자리째 모은다. 여기서 우선순위 차례로
|
|
후보 중 첫 빈 자리를 고르고(못 찾으면 첫 후보) 멀리 놓인 글엔 지시선을 단다.
|
|
우선순위 = 시종점 > 측점 NO > IP > R > 배수 > 대피소. 서버 단독 계산(CLAUDE.md 5장 ③).
|
|
|
|
좌표 = 종이 mm. 글 사각 = 글 크기 × (한글 1.0 · 그 밖 0.6) 폭 · 글 크기 높이(세로 가운데 기준 —
|
|
CAD 가 textBaseline middle 로 그림) · 방향이 있으면 돌린 사각의 바깥 상자.
|
|
"""
|
|
|
|
import math
|
|
from typing import Any
|
|
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import _text_entity, polyline_entity
|
|
|
|
PRIORITY = ("endpoints", "station", "ip", "radius", "drainage", "refuge")
|
|
_GAP_MM = 0.6 # 글 사이 틈(종이 mm)
|
|
_PUSH = (2.0, 3.0) # 먼 고리 — 후보 거리의 배수
|
|
|
|
Rect = tuple[float, float, float, float]
|
|
|
|
|
|
def text_width(label: str, size: float) -> float:
|
|
return sum(1.0 if ord(char) > 0x2E7F else 0.6 for char in label) * size
|
|
|
|
|
|
def text_rect(
|
|
label: str,
|
|
x: float,
|
|
y: float,
|
|
size: float,
|
|
align: str = "center",
|
|
direction: tuple[float, float] = (1.0, 0.0),
|
|
) -> Rect:
|
|
width = text_width(label, size)
|
|
left = {"left": 0.0, "right": -width}.get(align, -width / 2.0)
|
|
dx, dy = direction
|
|
length = math.hypot(dx, dy) or 1.0
|
|
dx, dy = dx / length, dy / length
|
|
corners = [
|
|
(x + cx * dx - cy * dy, y + cx * dy + cy * dx)
|
|
for cx in (left, left + width)
|
|
for cy in (-size / 2.0, size / 2.0)
|
|
]
|
|
xs, ys = [p[0] for p in corners], [p[1] for p in corners]
|
|
return min(xs), min(ys), max(xs), max(ys)
|
|
|
|
|
|
def overlaps(a: Rect, b: Rect, gap: float = 0.0) -> bool:
|
|
return a[0] < b[2] + gap and b[0] < a[2] + gap and a[1] < b[3] + gap and b[1] < a[3] + gap
|
|
|
|
|
|
def _rect(item: dict[str, Any], spot: dict[str, Any]) -> Rect:
|
|
return text_rect(
|
|
item["label"],
|
|
spot["x"],
|
|
spot["y"],
|
|
item["size"],
|
|
spot.get("align", "center"),
|
|
spot.get("direction", (1.0, 0.0)),
|
|
)
|
|
|
|
|
|
def place_labels(labels: list[dict[str, Any]], obstacles: list[Rect]) -> list[dict[str, Any]]:
|
|
"""우선순위 차례로 후보 중 첫 빈 자리 — 글마다 고른 자리(`spot`) · 사각(`rect`)을 붙여 냄.
|
|
|
|
labels = [{piece, seed, label, size, layer, color, anchor?, candidates: [{x, y, align?,
|
|
direction?, far?}]}] — 첫 후보 = 옛 자리.
|
|
"""
|
|
taken = list(obstacles)
|
|
placed = []
|
|
rank = {piece: index for index, piece in enumerate(PRIORITY)}
|
|
for item in sorted(labels, key=lambda label: rank.get(label["piece"], len(PRIORITY))):
|
|
spots = [*item["candidates"], *_pushed(item)]
|
|
rects = [_rect(item, spot) for spot in spots]
|
|
# ponytail: 먼 고리까지 모두 막히면 첫 후보 그대로(겹침 남음 · 작은 축척에 글이 몰린 장)
|
|
index = next(
|
|
(
|
|
i
|
|
for i, rect in enumerate(rects)
|
|
if not any(overlaps(rect, t, _GAP_MM) for t in taken)
|
|
),
|
|
0,
|
|
)
|
|
taken.append(rects[index])
|
|
placed.append({**item, "spot": spots[index], "rect": rects[index]})
|
|
return placed
|
|
|
|
|
|
def _pushed(item: dict[str, Any]) -> list[dict[str, Any]]:
|
|
"""후보가 다 막혔을 때 — 기호에서 후보 쪽으로 2 · 3 배 먼 자리(지시선 · 141-2 작은 축척)."""
|
|
anchor = item.get("anchor")
|
|
if not anchor:
|
|
return []
|
|
ax, ay = anchor
|
|
return [
|
|
{**spot, "x": ax + (spot["x"] - ax) * k, "y": ay + (spot["y"] - ay) * k, "far": True}
|
|
for k in _PUSH
|
|
for spot in item["candidates"]
|
|
]
|
|
|
|
|
|
def _nearest_on_rect(point: tuple[float, float], rect: Rect) -> tuple[float, float]:
|
|
return (min(max(point[0], rect[0]), rect[2]), min(max(point[1], rect[1]), rect[3]))
|
|
|
|
|
|
def label_entities(drawing_id: str, placed: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
"""자리 잡은 글 + 멀리 놓인 글(후보 far)의 지시선."""
|
|
out: list[dict[str, Any]] = []
|
|
for item in placed:
|
|
spot = item["spot"]
|
|
out.append(
|
|
_text_entity(
|
|
item["seed"],
|
|
item["label"],
|
|
spot["x"],
|
|
spot["y"],
|
|
item["layer"],
|
|
item["size"],
|
|
item["color"],
|
|
align=spot.get("align", "center"),
|
|
direction=spot.get("direction", (1.0, 0.0)),
|
|
)
|
|
)
|
|
anchor = item.get("anchor")
|
|
if spot.get("far") and anchor:
|
|
leader = polyline_entity(
|
|
drawing_id,
|
|
[anchor, _nearest_on_rect(anchor, item["rect"])],
|
|
item["layer"],
|
|
item["color"],
|
|
suffix=f":leader:{item['seed']}",
|
|
)
|
|
if leader:
|
|
out.append(leader)
|
|
return out
|