feat(M02): 도면 양식 작도 영역 칸 drawing_area — 박힌 _A1_INNER 대신 양식에서 읽음 (PLAN 10-3)
- 00_template_A1.json 에 drawing_area [42, 47, 812, 567] - Engine_Template drawing_area(이름) · 칸이 없거나 어긋나면 A1 기본값 · usable_area · usable_bbox · frame_entities 가 씀 - 도각 저장은 고치기 전 양식의 작도 영역을 이어 받음 - 시험 test_m02_drawing_area.py Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxvYb5ufV1kWdBvZbpDfu6
This commit is contained in:
@@ -6,7 +6,8 @@ resources/master_template/drawing/의 사전 변환 템플릿(A1 도각 등)을
|
||||
템플릿 쪽을 확대해 콘텐츠를 감싼다.
|
||||
|
||||
A1 템플릿 기하(변환 시점 고정값): 전체 840x594, 하단 y17~47 표제란,
|
||||
내부 작도 영역 (42, 47) ~ (812, 567).
|
||||
내부 작도 영역 (42, 47) ~ (812, 567). 작도 영역은 양식 칸 `drawing_area` 가 정본이고
|
||||
칸이 없는 양식(옛 회사 도각)은 이 값으로 떨어진다.
|
||||
"""
|
||||
|
||||
import binascii
|
||||
@@ -37,7 +38,7 @@ _SVG_NS_UUID = uuid5(NAMESPACE_URL, "aislo/b07/signature-vector")
|
||||
|
||||
A1_TEMPLATE = "00_template_A1"
|
||||
COMPASS_TEMPLATE = "00_template_compass" # 유역도 등 평면 도면의 방위표
|
||||
# A1 내부 작도 영역(템플릿 좌표) — 콘텐츠가 이 영역 중앙에 오도록 배치한다.
|
||||
# A1 내부 작도 영역(템플릿 좌표) — 양식에 `drawing_area` 칸이 없을 때만 쓴다.
|
||||
_A1_INNER = (42.0, 47.0, 812.0, 567.0)
|
||||
# 내부 작도 영역 대비 콘텐츠 여백 비율(각 방향). 5%는 도각 안쪽에 70mm 가까이를
|
||||
# 비워 횡단 장이 한 장 더 늘었다 — 2%로 줄여 작도 영역을 쓴다(2026-08-30 사용자).
|
||||
@@ -102,6 +103,23 @@ def _load_template(name: str) -> dict[str, Any] | None:
|
||||
return _read_template(str(path), path.stat().st_mtime_ns)
|
||||
|
||||
|
||||
def drawing_area(name: str = A1_TEMPLATE) -> tuple[float, float, float, float]:
|
||||
"""양식의 내부 작도 영역 (x0, y0, x1, y1) — 콘텐츠가 이 영역 중앙에 놓인다.
|
||||
|
||||
양식 칸 `drawing_area` 를 읽는다. 없거나 어긋나면 A1 기본값(`_A1_INNER`).
|
||||
"""
|
||||
area = (_load_template(name) or {}).get("drawing_area")
|
||||
if (
|
||||
isinstance(area, list)
|
||||
and len(area) == 4
|
||||
and all(isinstance(value, (int, float)) for value in area)
|
||||
and area[0] < area[2]
|
||||
and area[1] < area[3]
|
||||
):
|
||||
return (float(area[0]), float(area[1]), float(area[2]), float(area[3]))
|
||||
return _A1_INNER
|
||||
|
||||
|
||||
def template_entities(name: str = A1_TEMPLATE) -> list[dict[str, Any]]:
|
||||
"""도각 원본 엔티티(실치수 1:1). 편집 화면이 그대로 싣고, 저장도 이 좌표계로 받는다."""
|
||||
template = _load_template(name)
|
||||
@@ -173,9 +191,17 @@ def save_company_template(
|
||||
validate_template_entities(entities)
|
||||
path = company_template_path(company_dir, name)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
# 작도 영역은 CAD 가 모르는 칸이라 편집본에 없다 — 고치기 전 양식의 값을 이어 받는다.
|
||||
previous = path if path.is_file() else _TEMPLATE_DIR / f"{name}.json"
|
||||
area = (
|
||||
json.loads(previous.read_text(encoding="utf-8")).get("drawing_area")
|
||||
if previous.is_file()
|
||||
else None
|
||||
)
|
||||
document = {
|
||||
"format": DRAWING_FORMAT,
|
||||
"source": "B07 도각 편집 화면",
|
||||
**({"drawing_area": area} if area else {}),
|
||||
# 도각은 도면마다 잠금 층 하나로 붙으므로 편집 중 새로 만든 층은 도각으로 모은다.
|
||||
"entities": [{**entity, "layerId": FRAME_LAYER_ID} for entity in entities],
|
||||
"layers": [{"id": FRAME_LAYER_ID, "name": "도각", "isVisible": True, "isLocked": False}],
|
||||
@@ -293,15 +319,15 @@ def usable_bbox() -> tuple[float, float, float, float]:
|
||||
여백을 뺀 한도(`usable_area()`)보다 커서 "작도 영역을 넘습니다" 경고가 뜬다 —
|
||||
내용이 없는데 넘칠 리 없다. 중심이 같으므로 도각 배치(이동량 0)는 그대로다.
|
||||
"""
|
||||
ix0, iy0, ix1, iy1 = _A1_INNER
|
||||
ix0, iy0, ix1, iy1 = drawing_area()
|
||||
width, height = usable_area()
|
||||
cx, cy = (ix0 + ix1) / 2.0, (iy0 + iy1) / 2.0
|
||||
return (cx - width / 2.0, cy - height / 2.0, cx + width / 2.0, cy + height / 2.0)
|
||||
|
||||
|
||||
def usable_area() -> tuple[float, float]:
|
||||
"""A1 내부 작도 영역에서 여백을 뺀 유효 크기(mm). 척도 고정 도면의 수용 한도."""
|
||||
ix0, iy0, ix1, iy1 = _A1_INNER
|
||||
def usable_area(name: str = A1_TEMPLATE) -> tuple[float, float]:
|
||||
"""양식 작도 영역에서 여백을 뺀 유효 크기(mm). 척도 고정 도면의 수용 한도."""
|
||||
ix0, iy0, ix1, iy1 = drawing_area(name)
|
||||
return (
|
||||
(ix1 - ix0) * (1.0 - 2.0 * _CONTENT_MARGIN),
|
||||
(iy1 - iy0) * (1.0 - 2.0 * _CONTENT_MARGIN),
|
||||
@@ -444,8 +470,8 @@ def frame_entities(
|
||||
content_w = max(max_x - min_x, 1e-6)
|
||||
content_h = max(max_y - min_y, 1e-6)
|
||||
|
||||
ix0, iy0, ix1, iy1 = _A1_INNER
|
||||
usable_w, usable_h = usable_area()
|
||||
ix0, iy0, ix1, iy1 = drawing_area(template_name)
|
||||
usable_w, usable_h = usable_area(template_name)
|
||||
scale = max(content_w / usable_w, content_h / usable_h) if fit else 1.0
|
||||
# 한도와 **같은** 크기는 넘친 것이 아니다 — 부동소수 오차만큼의 여유를 둔다
|
||||
# (수용 한도를 그대로 넘기는 빈 도면이 마지막 자리 오차로 경고를 냈다).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"format": 6,
|
||||
"source": "00_templete_A1.dxf (남의 프로젝트 자료 제거 · 플레이스홀더화)",
|
||||
"drawing_area": [42, 47, 812, 567],
|
||||
"entities": [
|
||||
{
|
||||
"id": "4afa84ae-9c15-50ec-8a76-db87d04d6311",
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"""M02 도면 양식 작도 영역 칸(`drawing_area`) — 양식이 정본 · 없으면 A1 기본값 (PLAN 10-3)."""
|
||||
|
||||
import json
|
||||
|
||||
from B07_DesignDetail import B07_DesignDetail_Engine_Template as engine
|
||||
|
||||
|
||||
def _company_template(tmp_path, document):
|
||||
path = engine.company_template_path(tmp_path)
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text(json.dumps(document), encoding="utf-8")
|
||||
return path
|
||||
|
||||
|
||||
def test_system_a1_reads_drawing_area():
|
||||
engine.use_company_templates(None)
|
||||
assert engine.drawing_area() == (42.0, 47.0, 812.0, 567.0)
|
||||
# 도각 배치는 옮기기 전 박힌 값과 같다 — 콘텐츠 중심 = 작도 영역 중심.
|
||||
frame = engine.frame_entities("t", (0.0, 0.0, 100.0, 100.0), fit=False)
|
||||
assert frame
|
||||
|
||||
|
||||
def test_missing_area_falls_back_to_a1(tmp_path):
|
||||
_company_template(tmp_path, {"format": 6, "entities": [], "layers": []})
|
||||
engine.use_company_templates(tmp_path)
|
||||
try:
|
||||
assert engine.drawing_area() == engine._A1_INNER
|
||||
finally:
|
||||
engine.use_company_templates(None)
|
||||
|
||||
|
||||
def test_template_area_moves_content(tmp_path):
|
||||
line = {
|
||||
"id": "a",
|
||||
"type": "Line",
|
||||
"shapeData": {"startPoint": {"x": 0, "y": 0}, "endPoint": {"x": 10, "y": 0}},
|
||||
}
|
||||
_company_template(
|
||||
tmp_path,
|
||||
{"format": 6, "drawing_area": [0, 0, 400, 200], "entities": [line], "layers": []},
|
||||
)
|
||||
engine.use_company_templates(tmp_path)
|
||||
try:
|
||||
assert engine.drawing_area() == (0.0, 0.0, 400.0, 200.0)
|
||||
assert engine.usable_area() == (400 * 0.96, 200 * 0.96)
|
||||
# 콘텐츠 중심(500, 500) 이 작도 영역 중심(200, 100) 에 온다 — 이동량 (300, 400).
|
||||
placed = engine.frame_entities("t", (490.0, 490.0, 510.0, 510.0), fit=False)
|
||||
assert placed[0]["shapeData"]["startPoint"] == {"x": 300.0, "y": 400.0}
|
||||
finally:
|
||||
engine.use_company_templates(None)
|
||||
|
||||
|
||||
def test_bad_area_falls_back(tmp_path):
|
||||
_company_template(tmp_path, {"drawing_area": [10, 10, 5, 5], "entities": []})
|
||||
engine.use_company_templates(tmp_path)
|
||||
try:
|
||||
assert engine.drawing_area() == engine._A1_INNER
|
||||
finally:
|
||||
engine.use_company_templates(None)
|
||||
|
||||
|
||||
def test_save_keeps_area(tmp_path):
|
||||
# 편집본(CAD)에는 작도 영역 칸이 없다 — 저장이 시스템 양식 값을 이어 받는다.
|
||||
path = engine.save_company_template(tmp_path, [])
|
||||
assert json.loads(path.read_text(encoding="utf-8"))["drawing_area"] == [42, 47, 812, 567]
|
||||
Reference in New Issue
Block a user