N1: COM hatch non-associative (AddHatch associativity=False) N2: ezdxf refuses DWG saves with clear error; server.py derives fmt from extension R19: dimension_aligned dist→distance; dimension_angular correct add_angular_dim_2l args (base/line1/line2/location) R9: polar array 360° no-duplicate (divisor=n when fill_angle%360<ε) R13: entity_offset side_x/side_y honored in ezdxf via cross-product (LINE) and distance-to-center (CIRCLE) R18: drawing_new bootstrap failure surfaces status="degraded" instead of swallowing silently Dead code: engineering/section.py, gear.generate_tooth_profile, CommandResult, _ACI_NAMES, set_layer_active, ensure_linetypes, ComBackend._batch_mode instance attr, engineering/__init__ build-seam comment Ruff: 38 auto-fixed + 5 manual → codebase is now ruff-clean; CI lint is now blocking 184 tests pass.
255 lines
7.9 KiB
Python
255 lines
7.9 KiB
Python
"""ISO 7200 / A3 (420 x 297 mm) title block generator.
|
|
|
|
Deterministic — the title text is written verbatim from metadata.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from backends.base import AutoCADBackend
|
|
|
|
|
|
# A3 paper, ISO 7200 layout constants (mm)
|
|
SHEET_W = 420.0
|
|
SHEET_H = 297.0
|
|
LEFT_MARGIN = 20.0
|
|
RIGHT_MARGIN = 10.0
|
|
TOP_MARGIN = 10.0
|
|
BOTTOM_MARGIN = 10.0
|
|
|
|
TB_WIDTH = 180.0
|
|
TB_HEIGHT = 60.0
|
|
|
|
ROW1_H = 20.0 # title
|
|
ROW2_H = 15.0 # drawing_no | revision | sheet
|
|
ROW3_H = 15.0 # part_no | material | scale
|
|
ROW4_H = 10.0 # drawn_by | checked_by | date | company
|
|
|
|
LABEL_HEIGHT = 2.0
|
|
VALUE_HEIGHT = 3.5
|
|
TITLE_HEIGHT = 5.0
|
|
|
|
LAYER_BORDER = "TITLEBLOCK"
|
|
LAYER_TEXT = "TEXT"
|
|
|
|
|
|
@dataclass
|
|
class TitleBlockMetadata:
|
|
"""ISO 7200 title block fields (all strings — no LLM transformation)."""
|
|
|
|
title: str
|
|
drawing_no: str
|
|
part_no: str = ""
|
|
material: str = ""
|
|
scale: str = "1:1"
|
|
units: str = "mm"
|
|
drawn_by: str = ""
|
|
checked_by: str = ""
|
|
date: str = ""
|
|
sheet: str = "1/1"
|
|
revision: str = "A"
|
|
company: str = "Anka-Makine"
|
|
|
|
|
|
async def apply_iso_a3_titleblock(
|
|
backend: AutoCADBackend,
|
|
*,
|
|
metadata: TitleBlockMetadata,
|
|
origin: tuple[float, float] = (0.0, 0.0),
|
|
) -> dict:
|
|
"""Draw a 420x297 ISO 7200 title block at `origin` (lower-left of sheet)."""
|
|
|
|
ox, oy = float(origin[0]), float(origin[1])
|
|
|
|
# ---- outer + inner borders -------------------------------------------------
|
|
outer_pts = [
|
|
[ox, oy],
|
|
[ox + SHEET_W, oy],
|
|
[ox + SHEET_W, oy + SHEET_H],
|
|
[ox, oy + SHEET_H],
|
|
]
|
|
outer = await backend.entity_create_polyline(
|
|
points=outer_pts, closed=True, layer=LAYER_BORDER,
|
|
)
|
|
|
|
inner_x0 = ox + LEFT_MARGIN
|
|
inner_y0 = oy + BOTTOM_MARGIN
|
|
inner_x1 = ox + SHEET_W - RIGHT_MARGIN
|
|
inner_y1 = oy + SHEET_H - TOP_MARGIN
|
|
inner_pts = [
|
|
[inner_x0, inner_y0],
|
|
[inner_x1, inner_y0],
|
|
[inner_x1, inner_y1],
|
|
[inner_x0, inner_y1],
|
|
]
|
|
inner = await backend.entity_create_polyline(
|
|
points=inner_pts, closed=True, layer=LAYER_BORDER,
|
|
)
|
|
|
|
# ---- title block (lower-right corner of inner border) ----------------------
|
|
tb_x0 = inner_x1 - TB_WIDTH
|
|
tb_y0 = inner_y0
|
|
tb_x1 = inner_x1
|
|
tb_y1 = inner_y0 + TB_HEIGHT
|
|
|
|
# Title block frame (closed polyline)
|
|
tb_frame_pts = [
|
|
[tb_x0, tb_y0],
|
|
[tb_x1, tb_y0],
|
|
[tb_x1, tb_y1],
|
|
[tb_x0, tb_y1],
|
|
]
|
|
tb_frame = await backend.entity_create_polyline(
|
|
points=tb_frame_pts, closed=True, layer=LAYER_BORDER,
|
|
)
|
|
|
|
# Row baselines (from bottom up): row4_top, row3_top, row2_top
|
|
row4_top = tb_y0 + ROW4_H
|
|
row3_top = row4_top + ROW3_H
|
|
row2_top = row3_top + ROW2_H # == tb_y1 - ROW1_H
|
|
|
|
titleblock_lines: list[str] = [tb_frame.handle]
|
|
|
|
async def hline(y: float) -> str:
|
|
ent = await backend.entity_create_line(
|
|
tb_x0, y, tb_x1, y, layer=LAYER_BORDER,
|
|
)
|
|
return ent.handle
|
|
|
|
async def vline(x: float, y0: float, y1: float) -> str:
|
|
ent = await backend.entity_create_line(
|
|
x, y0, x, y1, layer=LAYER_BORDER,
|
|
)
|
|
return ent.handle
|
|
|
|
titleblock_lines.append(await hline(row4_top))
|
|
titleblock_lines.append(await hline(row3_top))
|
|
titleblock_lines.append(await hline(row2_top))
|
|
|
|
# Row 2 splits: left half = drawing_no, then quarter rev, quarter sheet
|
|
r2_split1 = tb_x0 + TB_WIDTH * 0.5
|
|
r2_split2 = tb_x0 + TB_WIDTH * 0.75
|
|
titleblock_lines.append(await vline(r2_split1, row3_top, row2_top))
|
|
titleblock_lines.append(await vline(r2_split2, row3_top, row2_top))
|
|
|
|
# Row 3 splits: thirds — part_no | material | scale
|
|
r3_split1 = tb_x0 + TB_WIDTH / 3.0
|
|
r3_split2 = tb_x0 + TB_WIDTH * 2.0 / 3.0
|
|
titleblock_lines.append(await vline(r3_split1, row4_top, row3_top))
|
|
titleblock_lines.append(await vline(r3_split2, row4_top, row3_top))
|
|
|
|
# Row 4 splits: quarters — drawn_by | checked_by | date | company
|
|
r4_split1 = tb_x0 + TB_WIDTH * 0.25
|
|
r4_split2 = tb_x0 + TB_WIDTH * 0.50
|
|
r4_split3 = tb_x0 + TB_WIDTH * 0.75
|
|
titleblock_lines.append(await vline(r4_split1, tb_y0, row4_top))
|
|
titleblock_lines.append(await vline(r4_split2, tb_y0, row4_top))
|
|
titleblock_lines.append(await vline(r4_split3, tb_y0, row4_top))
|
|
|
|
# ---- text content ----------------------------------------------------------
|
|
label_pad_x = 1.5
|
|
label_pad_y = 1.5
|
|
value_pad_x = 3.0
|
|
value_pad_y = 5.0
|
|
|
|
async def label(text: str, x: float, y: float) -> str:
|
|
ent = await backend.entity_create_text(
|
|
text=text, x=x, y=y, height=LABEL_HEIGHT, layer=LAYER_TEXT,
|
|
)
|
|
return ent.handle
|
|
|
|
async def value(text: str, x: float, y: float, height: float = VALUE_HEIGHT) -> str:
|
|
ent = await backend.entity_create_text(
|
|
text=text, x=x, y=y, height=height, layer=LAYER_TEXT,
|
|
)
|
|
return ent.handle
|
|
|
|
# Row 1 — TITLE (verbatim)
|
|
title_x = tb_x0 + TB_WIDTH * 0.5 - (len(metadata.title) * TITLE_HEIGHT * 0.3)
|
|
title_y = row2_top + (ROW1_H - TITLE_HEIGHT) / 2.0
|
|
title_text_handle = await value(metadata.title, title_x, title_y, height=TITLE_HEIGHT)
|
|
|
|
value_texts: dict[str, str] = {}
|
|
|
|
# Row 2: drawing_no (left half) | revision (3rd qtr) | sheet (4th qtr)
|
|
await label("DWG NO", tb_x0 + label_pad_x, row2_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["drawing_no"] = await value(
|
|
metadata.drawing_no,
|
|
tb_x0 + value_pad_x,
|
|
row3_top + value_pad_y,
|
|
)
|
|
await label("REV", r2_split1 + label_pad_x, row2_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["revision"] = await value(
|
|
metadata.revision,
|
|
r2_split1 + value_pad_x,
|
|
row3_top + value_pad_y,
|
|
)
|
|
await label("SHEET", r2_split2 + label_pad_x, row2_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["sheet"] = await value(
|
|
metadata.sheet,
|
|
r2_split2 + value_pad_x,
|
|
row3_top + value_pad_y,
|
|
)
|
|
|
|
# Row 3: part_no | material | scale
|
|
await label("PART NO", tb_x0 + label_pad_x, row3_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["part_no"] = await value(
|
|
metadata.part_no,
|
|
tb_x0 + value_pad_x,
|
|
row4_top + value_pad_y,
|
|
)
|
|
await label("MATERIAL", r3_split1 + label_pad_x, row3_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["material"] = await value(
|
|
metadata.material,
|
|
r3_split1 + value_pad_x,
|
|
row4_top + value_pad_y,
|
|
)
|
|
await label("SCALE", r3_split2 + label_pad_x, row3_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["scale"] = await value(
|
|
metadata.scale,
|
|
r3_split2 + value_pad_x,
|
|
row4_top + value_pad_y,
|
|
)
|
|
|
|
# Row 4: drawn_by | checked_by | date | company
|
|
await label("DRAWN", tb_x0 + label_pad_x, row4_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["drawn_by"] = await value(
|
|
metadata.drawn_by,
|
|
tb_x0 + value_pad_x,
|
|
tb_y0 + 1.5,
|
|
)
|
|
await label("CHECKED", r4_split1 + label_pad_x, row4_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["checked_by"] = await value(
|
|
metadata.checked_by,
|
|
r4_split1 + value_pad_x,
|
|
tb_y0 + 1.5,
|
|
)
|
|
await label("DATE", r4_split2 + label_pad_x, row4_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["date"] = await value(
|
|
metadata.date,
|
|
r4_split2 + value_pad_x,
|
|
tb_y0 + 1.5,
|
|
)
|
|
await label("COMPANY", r4_split3 + label_pad_x, row4_top - LABEL_HEIGHT - label_pad_y)
|
|
value_texts["company"] = await value(
|
|
metadata.company,
|
|
r4_split3 + value_pad_x,
|
|
tb_y0 + 1.5,
|
|
)
|
|
|
|
return {
|
|
"outer_border": outer.handle,
|
|
"inner_border": inner.handle,
|
|
"titleblock_lines": titleblock_lines,
|
|
"title_text": title_text_handle,
|
|
"value_texts": value_texts,
|
|
"metadata": asdict(metadata),
|
|
"bbox": {
|
|
"min": [ox, oy],
|
|
"max": [ox + SHEET_W, oy + SHEET_H],
|
|
},
|
|
}
|