ours: наш оверлей к апстриму 1.5.1 (дельта для переноса в 1.6.0)
10 изменённых файлов + 2 новых (engineering/gost_titleblock.py, _fonts/isocpeur.ttf). Ветка-донор для трёхстороннего переноса в ours-1.6.
This commit is contained in:
+279
-25
@@ -193,6 +193,34 @@ def _msp():
|
||||
return doc.ModelSpace
|
||||
|
||||
|
||||
def _activate_layout(name: str | None) -> None:
|
||||
"""Make the named layout the active tab, so the next COM call draws on it.
|
||||
|
||||
The ezdxf backend takes ``layout`` as a per-call target; COM has one active
|
||||
layout per document, so the equivalent is the same switch
|
||||
``layout_set_current`` performs — and it stays switched, because the sheet is
|
||||
where the drafter carries on. Unknown names are refused instead of silently
|
||||
drawing into model space.
|
||||
"""
|
||||
wanted = str(name or "").strip()
|
||||
if not wanted:
|
||||
return
|
||||
doc = _acad_doc()
|
||||
try:
|
||||
current = str(doc.ActiveLayout.Name)
|
||||
except Exception as exc: # a probe must never block a draw
|
||||
log.debug("ActiveLayout.Name unavailable (%s)", exc)
|
||||
current = ""
|
||||
if current and current.lower() == wanted.lower():
|
||||
return
|
||||
try:
|
||||
doc.ActiveLayout = doc.Layouts.Item(wanted)
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
f"layout {wanted!r} does not exist: create it with layout_create first"
|
||||
) from exc
|
||||
|
||||
|
||||
_BUILTIN_LINETYPES = {"continuous", "bylayer", "byblock"}
|
||||
|
||||
|
||||
@@ -815,7 +843,21 @@ class ComBackend(AutoCADBackend):
|
||||
async def drawing_export_dxf(self, path: str) -> dict:
|
||||
return await self.drawing_save_as(path, "dxf")
|
||||
|
||||
async def drawing_export_pdf(self, path: str, layout: str | None = None) -> dict:
|
||||
async def drawing_export_pdf(
|
||||
self,
|
||||
path: str,
|
||||
layout: str | None = None,
|
||||
text_layer: bool | None = None,
|
||||
page_size: str | None = None,
|
||||
) -> dict:
|
||||
# ``text_layer`` is accepted for signature parity with the ezdxf backend:
|
||||
# AutoCAD plots real text natively, so the flag has nothing to switch.
|
||||
# ``page_size`` likewise: PlotToFile plots the layout's own paper at its
|
||||
# own scale (that is what a .pc3 device does), so there is nothing to
|
||||
# choose between "layout" and "extents" here.
|
||||
del text_layer
|
||||
del page_size
|
||||
|
||||
def _sync():
|
||||
doc = _acad_doc()
|
||||
previous = None
|
||||
@@ -1934,8 +1976,12 @@ class ComBackend(AutoCADBackend):
|
||||
layer=None,
|
||||
color=None,
|
||||
linetype=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
# Layout ровно как у ezdxf: COM рисует в активный лист, поэтому
|
||||
# вызов сначала переключает вкладку (см. _activate_layout).
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
line = mspace.AddLine(_apoint(x1, y1, z1), _apoint(x2, y2, z2))
|
||||
_apply_entity_attrs(line, layer, color, linetype)
|
||||
@@ -1951,8 +1997,10 @@ class ComBackend(AutoCADBackend):
|
||||
radius,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
circle = mspace.AddCircle(_apoint(cx, cy), float(radius))
|
||||
_apply_entity_attrs(circle, layer, color, None)
|
||||
@@ -1970,8 +2018,10 @@ class ComBackend(AutoCADBackend):
|
||||
end_angle,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
arc = mspace.AddArc(
|
||||
_apoint(cx, cy),
|
||||
@@ -1991,8 +2041,10 @@ class ComBackend(AutoCADBackend):
|
||||
closed=False,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
flat = []
|
||||
for pt in points:
|
||||
@@ -2014,11 +2066,25 @@ class ComBackend(AutoCADBackend):
|
||||
rotation=0.0,
|
||||
layer=None,
|
||||
color=None,
|
||||
style=None,
|
||||
layout=None,
|
||||
inline_font=True,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
# Layout ровно как у ezdxf: COM рисует в активный лист, поэтому
|
||||
# вызов сначала переключает вкладку (см. _activate_layout).
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
txt = mspace.AddText(text, _apoint(x, y), float(height))
|
||||
txt.Rotation = deg2rad(rotation)
|
||||
if style:
|
||||
# AutoCAD refuses an unknown style name outright; a caller who
|
||||
# asked for a face the drawing does not carry gets the current
|
||||
# style instead of a failed call (matching ezdxf's fallback).
|
||||
try:
|
||||
txt.StyleName = str(style)
|
||||
except Exception:
|
||||
pass
|
||||
_apply_entity_attrs(txt, layer, color, None)
|
||||
_regen()
|
||||
return _entity_info(txt)
|
||||
@@ -2035,13 +2101,26 @@ class ComBackend(AutoCADBackend):
|
||||
rotation=0.0,
|
||||
layer=None,
|
||||
color=None,
|
||||
style=None,
|
||||
layout=None,
|
||||
inline_font=True,
|
||||
) -> EntityInfo:
|
||||
from engineering.annotation import wrap_inline_font
|
||||
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
mt = mspace.AddMText(_apoint(x, y), float(width), text)
|
||||
mt = mspace.AddMText(
|
||||
_apoint(x, y), float(width), wrap_inline_font(text, inline_font)
|
||||
)
|
||||
mt.Height = float(height)
|
||||
if rotation:
|
||||
mt.Rotation = deg2rad(rotation) # NEW-mtext-1: honor caller rotation
|
||||
if style:
|
||||
try:
|
||||
mt.StyleName = str(style)
|
||||
except Exception:
|
||||
pass
|
||||
_apply_entity_attrs(mt, layer, color, None)
|
||||
_regen()
|
||||
return _entity_info(mt)
|
||||
@@ -2059,30 +2138,43 @@ class ComBackend(AutoCADBackend):
|
||||
text_height=2.5,
|
||||
title=None,
|
||||
layer="TEXT",
|
||||
style=None,
|
||||
layout=None,
|
||||
inline_font=True,
|
||||
) -> EntityInfo:
|
||||
from engineering.annotation import prepare_table_layout
|
||||
from engineering.annotation import prepare_table_layout, wrap_inline_font
|
||||
|
||||
layout = prepare_table_layout(rows, headers, column_widths, row_height, text_height, title)
|
||||
table_layout = prepare_table_layout(
|
||||
rows, headers, column_widths, row_height, text_height, title
|
||||
)
|
||||
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
table = mspace.AddTable(
|
||||
_apoint(x, y),
|
||||
layout.row_count,
|
||||
layout.column_count,
|
||||
layout.row_height,
|
||||
layout.column_widths[0],
|
||||
table_layout.row_count,
|
||||
table_layout.column_count,
|
||||
table_layout.row_height,
|
||||
table_layout.column_widths[0],
|
||||
)
|
||||
for column, width in enumerate(layout.column_widths):
|
||||
for column, width in enumerate(table_layout.column_widths):
|
||||
table.SetColumnWidth(column, width)
|
||||
for row_index, row in enumerate(layout.cells):
|
||||
table.SetRowHeight(row_index, layout.row_height)
|
||||
for row_index, row in enumerate(table_layout.cells):
|
||||
table.SetRowHeight(row_index, table_layout.row_height)
|
||||
for column_index, value in enumerate(row):
|
||||
table.SetText(row_index, column_index, value)
|
||||
table.SetText(row_index, column_index, wrap_inline_font(value, inline_font))
|
||||
try:
|
||||
table.TextHeight = layout.text_height
|
||||
table.TextHeight = table_layout.text_height
|
||||
except Exception as exc:
|
||||
log.debug("setting table TextHeight failed: %s", exc)
|
||||
cell_style = None
|
||||
if style:
|
||||
try:
|
||||
table.TextStyle = str(style)
|
||||
cell_style = str(style)
|
||||
except Exception as exc:
|
||||
log.debug("setting table TextStyle failed: %s", exc)
|
||||
_apply_entity_attrs(table, layer, None, None)
|
||||
_regen()
|
||||
info = _entity_info(table)
|
||||
@@ -2093,11 +2185,13 @@ class ComBackend(AutoCADBackend):
|
||||
"representation": "native",
|
||||
"logical_group_id": f"table:{uuid.uuid4().hex}",
|
||||
"child_handles": [info.handle],
|
||||
"rows": layout.row_count,
|
||||
"columns": layout.column_count,
|
||||
"rows": table_layout.row_count,
|
||||
"columns": table_layout.column_count,
|
||||
"text_style": cell_style,
|
||||
"layout": str(layout or ""),
|
||||
"bounds": {
|
||||
"min": [float(x), float(y) - layout.height],
|
||||
"max": [float(x) + layout.width, float(y)],
|
||||
"min": [float(x), float(y) - table_layout.height],
|
||||
"max": [float(x) + table_layout.width, float(y)],
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -2113,8 +2207,10 @@ class ComBackend(AutoCADBackend):
|
||||
landing_gap=1.0,
|
||||
arrow_size=2.5,
|
||||
layer="DIM",
|
||||
layout=None,
|
||||
inline_font=True,
|
||||
) -> EntityInfo:
|
||||
from engineering.annotation import validate_mleader
|
||||
from engineering.annotation import validate_mleader, wrap_inline_font
|
||||
|
||||
normalized = validate_mleader(points, text)
|
||||
if text_height <= 0 or landing_gap < 0 or arrow_size <= 0:
|
||||
@@ -2123,11 +2219,12 @@ class ComBackend(AutoCADBackend):
|
||||
)
|
||||
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
flat = []
|
||||
for px, py in normalized:
|
||||
flat.extend([px, py, 0.0])
|
||||
leader = _msp().AddMLeader(_av(flat), 0)
|
||||
leader.TextString = str(text)
|
||||
leader.TextString = wrap_inline_font(text, inline_font)
|
||||
leader.TextHeight = float(text_height)
|
||||
leader.LandingGap = float(landing_gap)
|
||||
leader.ArrowheadSize = float(arrow_size)
|
||||
@@ -2161,8 +2258,10 @@ class ComBackend(AutoCADBackend):
|
||||
angle=0.0,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
# acPatternTypePreDefined = 0
|
||||
hatch = mspace.AddHatch(0, pattern, False)
|
||||
@@ -2189,8 +2288,10 @@ class ComBackend(AutoCADBackend):
|
||||
fit_points,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
flat = []
|
||||
for pt in fit_points:
|
||||
@@ -2215,8 +2316,10 @@ class ComBackend(AutoCADBackend):
|
||||
ratio=0.5,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
ellipse = mspace.AddEllipse(
|
||||
_apoint(cx, cy),
|
||||
@@ -2235,8 +2338,10 @@ class ComBackend(AutoCADBackend):
|
||||
y,
|
||||
layer=None,
|
||||
color=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
pt = mspace.AddPoint(_apoint(x, y))
|
||||
_apply_entity_attrs(pt, layer, color, None)
|
||||
@@ -2254,8 +2359,10 @@ class ComBackend(AutoCADBackend):
|
||||
scale_y=1.0,
|
||||
rotation=0.0,
|
||||
layer=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
ref = mspace.InsertBlock(
|
||||
_apoint(x, y),
|
||||
@@ -2288,8 +2395,10 @@ class ComBackend(AutoCADBackend):
|
||||
tol_lower=None,
|
||||
tol_mode="none",
|
||||
text_override=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
# The ActiveX API has no AddDimLinear — rotated linear dims are
|
||||
# created with AddDimRotated (GH issue #3).
|
||||
@@ -2316,8 +2425,10 @@ class ComBackend(AutoCADBackend):
|
||||
dim_x,
|
||||
dim_y,
|
||||
layer=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
dim = mspace.AddDimAligned(_apoint(x1, y1), _apoint(x2, y2), _apoint(dim_x, dim_y))
|
||||
if layer:
|
||||
@@ -2338,8 +2449,10 @@ class ComBackend(AutoCADBackend):
|
||||
tx,
|
||||
ty,
|
||||
layer=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
dim = mspace.AddDimAngular(
|
||||
_apoint(vx, vy),
|
||||
@@ -2366,8 +2479,10 @@ class ComBackend(AutoCADBackend):
|
||||
tol_lower=None,
|
||||
tol_mode="none",
|
||||
text_override=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
dim = mspace.AddDimRadial(
|
||||
_apoint(cx, cy), _apoint(chord_x, chord_y), float(leader_length)
|
||||
@@ -2392,8 +2507,10 @@ class ComBackend(AutoCADBackend):
|
||||
tol_lower=None,
|
||||
tol_mode="none",
|
||||
text_override=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
dim = mspace.AddDimDiametric(_apoint(x1, y1), _apoint(x2, y2), float(leader_length))
|
||||
if layer:
|
||||
@@ -2404,6 +2521,73 @@ class ComBackend(AutoCADBackend):
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
async def dimstyle_ensure(
|
||||
self,
|
||||
name="СПДС",
|
||||
text_style=None,
|
||||
base=None,
|
||||
set_current=True,
|
||||
text_height=2.5,
|
||||
arrow_size=2.5,
|
||||
) -> dict:
|
||||
"""Создать размерный стиль (по умолчанию СПДС) и/или сделать его текущим.
|
||||
|
||||
COM-эквивалент метода ezdxf: текстовый стиль размера переводим на
|
||||
ГОСТ-начертание (``text_style``), высоту текста и стрелки задаём явно
|
||||
(ГОСТ 2.304: 2.5 мм при 1:1), при ``set_current`` стиль становится
|
||||
активным (``ActiveDimStyle``) — следующий размер выйдет им.
|
||||
"""
|
||||
|
||||
def _sync():
|
||||
doc = _acad_doc()
|
||||
style_name = str(name or "").strip()
|
||||
if not style_name:
|
||||
raise RuntimeError("dimstyle_ensure: name must not be empty")
|
||||
names = [str(doc.DimStyles.Item(i).Name) for i in range(doc.DimStyles.Count)]
|
||||
created = False
|
||||
if style_name in names:
|
||||
style = doc.DimStyles.Item(style_name)
|
||||
else:
|
||||
style = doc.DimStyles.Add(style_name)
|
||||
created = True
|
||||
source = str(base or "").strip() or "Standard"
|
||||
if source in names:
|
||||
try:
|
||||
style.CopyFrom(doc.DimStyles.Item(source))
|
||||
except Exception as exc:
|
||||
log.debug("dimstyle_ensure: CopyFrom %s failed (%s)", source, exc)
|
||||
for attr, value in (("TextStyle", text_style), ("TextHeight", text_height),
|
||||
("ArrowheadSize", arrow_size)):
|
||||
if value in (None, "", 0):
|
||||
continue
|
||||
try:
|
||||
setattr(style, attr, value)
|
||||
except Exception as exc:
|
||||
# AutoCAD отказывает на незнакомое имя стиля — не валим вызов.
|
||||
log.debug("dimstyle_ensure: %s=%s rejected (%s)", attr, value, exc)
|
||||
if set_current:
|
||||
try:
|
||||
doc.ActiveDimStyle = style
|
||||
except Exception as exc:
|
||||
log.debug("dimstyle_ensure: ActiveDimStyle failed (%s)", exc)
|
||||
_regen()
|
||||
current = ""
|
||||
try:
|
||||
current = str(doc.ActiveDimStyle.Name)
|
||||
except Exception:
|
||||
current = ""
|
||||
return {
|
||||
"ok": True,
|
||||
"name": style_name,
|
||||
"created": created,
|
||||
"text_style": str(text_style or ""),
|
||||
"set_current": bool(set_current),
|
||||
"current": current,
|
||||
"dimstyles": names + ([] if style_name in names else [style_name]),
|
||||
}
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
# ── entity modification ───────────────────────────────────────────────────
|
||||
|
||||
async def entity_move(self, handle, dx, dy, dz=0.0) -> dict:
|
||||
@@ -3023,8 +3207,11 @@ class ComBackend(AutoCADBackend):
|
||||
rotation=0.0,
|
||||
attributes=None,
|
||||
layer=None,
|
||||
style=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
ref = mspace.InsertBlock(
|
||||
_apoint(x, y),
|
||||
@@ -3045,6 +3232,15 @@ class ComBackend(AutoCADBackend):
|
||||
attr.TextString = str(attributes[tag])
|
||||
except Exception as exc:
|
||||
log.debug("Block insert: GetAttributes or attribute setting failed: %s", exc)
|
||||
if style:
|
||||
# The ATTRIB inherits its ATTDEF's text style; a definition made
|
||||
# before the ГОСТ style existed carries ``Standard``, which plots
|
||||
# the block marks in the wrong face.
|
||||
try:
|
||||
for attr in ref.GetAttributes():
|
||||
attr.StyleName = str(style)
|
||||
except Exception as exc:
|
||||
log.debug("Block insert: setting attribute style failed: %s", exc)
|
||||
return _entity_info(ref)
|
||||
|
||||
return await self._run(_sync)
|
||||
@@ -3070,18 +3266,32 @@ class ComBackend(AutoCADBackend):
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
async def block_set_attributes(self, handle, attributes) -> dict:
|
||||
async def block_set_attributes(self, handle, attributes, style=None) -> dict:
|
||||
def _sync():
|
||||
doc = _acad_doc()
|
||||
ref = doc.HandleToObject(handle)
|
||||
attrs = ref.GetAttributes()
|
||||
updated = []
|
||||
restyled = 0
|
||||
for attr in attrs:
|
||||
tag = attr.TagString
|
||||
if tag in attributes:
|
||||
attr.TextString = str(attributes[tag])
|
||||
updated.append(tag)
|
||||
return {"ok": True, "updated_tags": updated}
|
||||
if style:
|
||||
# The reference's ATTDEF inherited ``Standard`` when there was
|
||||
# no ГОСТ style yet; a value written into it would still plot
|
||||
# in the wrong face, so the update carries the style with it.
|
||||
try:
|
||||
attr.StyleName = str(style)
|
||||
restyled += 1
|
||||
except Exception:
|
||||
pass
|
||||
result = {"ok": True, "updated_tags": updated}
|
||||
if style:
|
||||
result["style"] = str(style)
|
||||
result["restyled_tags"] = restyled
|
||||
return result
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
@@ -3091,26 +3301,70 @@ class ComBackend(AutoCADBackend):
|
||||
handles,
|
||||
base_x=0.0,
|
||||
base_y=0.0,
|
||||
layout=None,
|
||||
) -> dict:
|
||||
"""Build a block definition from entities already in the drawing.
|
||||
|
||||
This used to refuse and point at ``system_run_command`` with ``_BLOCK``,
|
||||
pushing callers through the free-text escape hatch for something ActiveX
|
||||
does directly: ``Blocks.Add`` creates the definition and
|
||||
``Document.CopyObjects`` puts entities into it. The originals stay in
|
||||
model space, matching the headless backend rather than AutoCAD's BLOCK
|
||||
``Document.CopyObjects`` puts entities into it. The originals stay where
|
||||
they were, matching the headless backend rather than AutoCAD's BLOCK
|
||||
command (which consumes them).
|
||||
|
||||
``layout`` matches the headless backend: a definition is document-global
|
||||
(it has no owner sheet), so the parameter constrains the *source* — only
|
||||
handles whose ``OwnerID`` is that layout's own block are taken, and a
|
||||
foreign handle is named in a refusal instead of copied silently.
|
||||
"""
|
||||
|
||||
def _sync():
|
||||
doc = _acad_doc()
|
||||
objects, skipped = [], []
|
||||
owner_id = None
|
||||
space_label = ""
|
||||
if layout is not None:
|
||||
wanted = self._layout_name(layout)
|
||||
if wanted.lower() == "model":
|
||||
space_label = "Model"
|
||||
owner_id = int(doc.ModelSpace.ObjectID)
|
||||
else:
|
||||
resolved = self._find_layout(doc, wanted)
|
||||
if resolved is None:
|
||||
raise RuntimeError(
|
||||
f"layout {wanted!r} does not exist: create it with "
|
||||
f"layout_create first"
|
||||
)
|
||||
space_label = resolved
|
||||
owner_id = int(doc.Layouts.Item(resolved).Block.ObjectID)
|
||||
|
||||
objects, skipped, foreign = [], [], []
|
||||
for handle in handles:
|
||||
try:
|
||||
objects.append(doc.HandleToObject(str(handle)))
|
||||
obj = doc.HandleToObject(str(handle))
|
||||
except Exception as exc:
|
||||
log.debug("resolving %s for block %s: %s", handle, name, exc)
|
||||
skipped.append(str(handle))
|
||||
continue
|
||||
if owner_id is not None:
|
||||
# An unreadable OwnerID is refused with the foreign handles
|
||||
# rather than waved through: the point of layout= is to
|
||||
# guarantee the definition came off that sheet.
|
||||
try:
|
||||
same_space = int(obj.OwnerID) == owner_id
|
||||
except Exception as exc:
|
||||
log.debug("OwnerID of %s unreadable (%s)", handle, exc)
|
||||
same_space = False
|
||||
if not same_space:
|
||||
foreign.append(str(handle))
|
||||
continue
|
||||
objects.append(obj)
|
||||
if foreign:
|
||||
raise RuntimeError(
|
||||
f"block_create_from_entities: handle(s) {', '.join(foreign)} are not in "
|
||||
f"space {space_label!r}; a definition is built from one sheet's entities "
|
||||
f"at a time. Drop layout= to collect drawing-wide, or pass the handles "
|
||||
f"of that sheet. No definition was created."
|
||||
)
|
||||
# Resolve before creating, so a call where every handle was a typo
|
||||
# does not leave an empty definition behind.
|
||||
if not objects:
|
||||
|
||||
@@ -30,6 +30,7 @@ class DimensionContract(ABC):
|
||||
tol_lower: float | None = None,
|
||||
tol_mode: str = "none",
|
||||
text_override: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -42,6 +43,7 @@ class DimensionContract(ABC):
|
||||
dim_x: float,
|
||||
dim_y: float,
|
||||
layer: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -56,6 +58,7 @@ class DimensionContract(ABC):
|
||||
tx: float,
|
||||
ty: float,
|
||||
layer: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -71,6 +74,7 @@ class DimensionContract(ABC):
|
||||
tol_lower: float | None = None,
|
||||
tol_mode: str = "none",
|
||||
text_override: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -86,4 +90,5 @@ class DimensionContract(ABC):
|
||||
tol_lower: float | None = None,
|
||||
tol_mode: str = "none",
|
||||
text_override: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@@ -72,6 +72,9 @@ class EntityCreationContract(ABC):
|
||||
rotation: float = 0.0,
|
||||
layer: str | None = None,
|
||||
color: int | None = None,
|
||||
style: str | None = None,
|
||||
layout: str | None = None,
|
||||
inline_font: bool = True,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -85,6 +88,9 @@ class EntityCreationContract(ABC):
|
||||
rotation: float = 0.0,
|
||||
layer: str | None = None,
|
||||
color: int | None = None,
|
||||
style: str | None = None,
|
||||
layout: str | None = None,
|
||||
inline_font: bool = True,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -99,6 +105,9 @@ class EntityCreationContract(ABC):
|
||||
text_height: float = 2.5,
|
||||
title: str | None = None,
|
||||
layer: str = "TEXT",
|
||||
style: str | None = None,
|
||||
layout: str | None = None,
|
||||
inline_font: bool = True,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
@@ -110,6 +119,8 @@ class EntityCreationContract(ABC):
|
||||
landing_gap: float = 1.0,
|
||||
arrow_size: float = 2.5,
|
||||
layer: str = "DIM",
|
||||
layout: str | None = None,
|
||||
inline_font: bool = True,
|
||||
) -> EntityInfo: ...
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@@ -28,6 +28,29 @@ class LayoutContract(ABC):
|
||||
"""Activate a layout tab ('Model' or a paper-space layout name)."""
|
||||
...
|
||||
|
||||
# MCP card t_b950b5c4 (01.10.2026). A sheet's format is part of the page
|
||||
# setup, and every plot of the sheet reads it: without a way to state the
|
||||
# format, a 210x297 frame drawn on a layout that still says A3 exports onto
|
||||
# A3 with the frame in a corner. The headless backend stores it in the
|
||||
# layout's paper fields; ActiveX exposes no verified equivalent.
|
||||
@capability( # noqa: B027
|
||||
"paper_space",
|
||||
reason=(
|
||||
"ActiveX has no verified member for a layout's sheet format (the page setup lives in "
|
||||
"the drawing's plot configuration, which has not been exercised against a live "
|
||||
"AutoCAD). Set the format in AutoCAD's Page Setup, or use the headless backend "
|
||||
"(AUTOCAD_MCP_BACKEND=ezdxf), where layout_create + layout_set_paper store it."
|
||||
),
|
||||
)
|
||||
async def layout_set_paper(self, name: str, width: float, height: float) -> dict:
|
||||
"""Set the sheet a layout is set up for, in millimetres.
|
||||
|
||||
Returns ``{ok, layout, paper_mm}``. This is what
|
||||
``drawing_export_pdf(page_size='layout')`` reads to size the page, so a
|
||||
sheet exported at 1:1 is the format it was drawn for.
|
||||
"""
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
async def layout_delete(self, name: str) -> dict:
|
||||
"""Delete a paper-space layout and everything drawn on it.
|
||||
|
||||
@@ -615,11 +615,14 @@ class PremiumContract(ABC):
|
||||
style: DimStyle = "chain",
|
||||
offset: float = 10.0,
|
||||
layer: str | None = None,
|
||||
layout: str | None = None,
|
||||
) -> list[EntityInfo]:
|
||||
"""Generate ISO 129 dimensions across `handles` in the chosen style
|
||||
(chain | baseline | ordinate). `offset` is the dimension-line
|
||||
distance from the reference geometry (mm). `layer` defaults to the
|
||||
active layer set's dimension layer (DIM, or M-DIMEN-T-N for iso13567)."""
|
||||
active layer set's dimension layer (DIM, or M-DIMEN-T-N for iso13567).
|
||||
`layout` puts the dimensions on a named paper-space sheet instead of the
|
||||
current space, so a sheet's dimensions live in paper millimetres."""
|
||||
if not handles:
|
||||
return []
|
||||
dim_layer = layer or self._role_layer("dim")
|
||||
@@ -661,6 +664,7 @@ class PremiumContract(ABC):
|
||||
my + ny * off,
|
||||
rotation=math.degrees(math.atan2(dy, dx)),
|
||||
layer=dim_layer,
|
||||
layout=layout,
|
||||
)
|
||||
results.append(dim)
|
||||
|
||||
@@ -682,6 +686,7 @@ class PremiumContract(ABC):
|
||||
# baseline, not horizontal.
|
||||
rotation=base_rot,
|
||||
layer=dim_layer,
|
||||
layout=layout,
|
||||
)
|
||||
results.append(dim)
|
||||
|
||||
@@ -697,6 +702,7 @@ class PremiumContract(ABC):
|
||||
sx + (ex - sx) / 2.0,
|
||||
max(sy, ey) + rung,
|
||||
layer=dim_layer,
|
||||
layout=layout,
|
||||
)
|
||||
results.append(dim)
|
||||
if abs(ey - sy) > 1e-9:
|
||||
@@ -709,6 +715,7 @@ class PremiumContract(ABC):
|
||||
(sy + ey) / 2.0,
|
||||
rotation=90.0,
|
||||
layer=dim_layer,
|
||||
layout=layout,
|
||||
)
|
||||
results.append(dim)
|
||||
|
||||
|
||||
+1481
-75
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user