port: наш оверлей 1.5.1 → апстрим 1.6.0 (ветка ours-1.6)
Перенос нашей дельты (10 изменённых + 2 новых файла) с v1.5.1 на тег v1.6.0 трёхсторонним слиянием с ветвью ours-1.5 (v1.5.1 + /opt/data/autocad-mcp-build/patch). Файлы 1.6.0 не перезаписывались копированием: 30 конфликтов разрешены по смыслу. Что перенесено (наше, чего в 1.6.0 не было): - инструменты: titleblock_apply_gost_form3, file_put, file_get, drawing_open_base64, drawing_save_base64, entity_create_attdef, block_get_attdefs, textstyle_ensure, text_apply_style, text_style_report, dimstyle_ensure, layout_set_paper (+ таблица алиасов и регистрация в server.py); - параметр `layout` (адресация в пространство листа) — 36 инструментов, включая геометрию (line/polyline/circle/arc/...), block_insert/block_create_from_entities, dimension_* и аннотацию; в COM-бэкенде этому служит _activate_layout(); - `inline_font` (ГОСТ-шрифт ISOCPEUR прямо в содержимое MTEXT/таблиц) — 5 инструментов; - ГОСТ-стиль по умолчанию (_ensure_default_text_style, DEFAULT_TEXT_STYLE=GOST_A) при drawing_new/drawing_open; - ATTDEF → ATTRIB: block_insert создаёт именованную вставку (не анонимный *U-блок), block_set_attributes достраивает отсутствующие ATTRIB из определений блока; - отчётность block_insert/block_set_attributes: filled / unknown_tags / created_tags и штамп стиля на ATTRIB (mark_style, attr_style); - PDF: page_size («layout» — страница равна формату листа 1:1, «extents» — по содержимому), настоящий текстовый слой (pdf.fonttype=42 + отрисовка текста поверх геометрии, включая текст через видовые экраны) и предупреждения о нечитаемо мелких надписях; - engineering/gost_titleblock.py (основная надпись по ГОСТ Р 21.1101-2013, форма 3) и _fonts/isocpeur.ttf. Что взято у 1.6.0 (наши дубли заменены, чтобы не спорить с апстримом): - drawing_export_pdf: рамка листа считается его _paper_frame() (масштаб, plot area, поворот, поля) вместо нашей _layout_paper_mm(); наш текстовый слой и page_size наложены сверху; - размерные стили: _current_dimstyle_name() вместо нашего _current_dimstyle() (наш хелпер оставлен — им пользуется отчёт о стилях); - таблицы (ezdxf и COM): объединённая строка-заголовок, «span», масштабируемый отступ ячеек и SetCellTextHeight() из 1.6.0 + наш inline_font/style; - block_insert: структура вставки и обработка многострочных ATTRIB из 1.6.0 + наша отчётность и штамп стиля; - экспорт DWG/xref/image_attach/профили, группы arch/pid/mech/sheet/understand — сохранены целиком. Три структурные правки после слияния (git сшил код механически, найдены pyflakes и аудитом дерева): - com_backend: наша подпись drawing_export_pdf попала в тело xref_attach и дублировала метод; вставка убрана, паритетные text_layer/page_size перенесены в настоящую COM-функцию; - _space_name в ezdxf-бэкенде был занят дважды (нашин и 1.6.0): наш переименован в _reply_space_name, иначе он затенял хелпер апстрима; - entity_create_text: ключ "style" в dxfattribs стоял дважды (наш оставлен). Прод :30015 не затронут.
This commit is contained in:
+278
-25
@@ -412,6 +412,35 @@ def _block_names(doc) -> set[str]:
|
||||
return names
|
||||
|
||||
|
||||
|
||||
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"}
|
||||
|
||||
#: `drawing_properties_*` field → `IAcadSummaryInfo` property.
|
||||
@@ -2199,7 +2228,19 @@ class ComBackend(AutoCADBackend):
|
||||
|
||||
return await self._run(_sync)
|
||||
|
||||
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`` and ``page_size`` are accepted for signature parity with
|
||||
# the ezdxf backend: AutoCAD plots real text natively, and PlotToFile
|
||||
# plots the layout's own paper at its own scale (that is what a .pc3
|
||||
# device does), so neither flag has anything to switch here.
|
||||
del text_layer
|
||||
del page_size
|
||||
"""Plot one layout to PDF through ``Plot.PlotToFile`` and confirm the file.
|
||||
|
||||
``layout=None`` means model space, as the tool advertises — the Model
|
||||
@@ -4677,8 +4718,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)
|
||||
@@ -4694,8 +4739,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)
|
||||
@@ -4713,8 +4760,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),
|
||||
@@ -4734,8 +4783,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:
|
||||
@@ -4757,11 +4808,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)
|
||||
@@ -4778,13 +4843,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)
|
||||
@@ -4802,31 +4880,44 @@ 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)
|
||||
# MEASURED on AutoCAD 2026: IAcadTable has no TextHeight property
|
||||
# (the old `table.TextHeight = h` raised and was swallowed, leaving
|
||||
# the Standard style's 6.0 / 4.5 - illegible at 1:50). The member
|
||||
# is SetCellTextHeight(row, col, h), set per cell.
|
||||
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.SetCellTextHeight(row_index, column_index, layout.text_height)
|
||||
table.SetText(row_index, column_index, wrap_inline_font(value, inline_font))
|
||||
table.SetCellTextHeight(row_index, column_index, table_layout.text_height)
|
||||
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)
|
||||
@@ -4837,11 +4928,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)],
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -4857,8 +4950,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:
|
||||
@@ -4867,11 +4962,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)
|
||||
@@ -4905,8 +5001,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)
|
||||
@@ -4933,8 +5031,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:
|
||||
@@ -4959,8 +5059,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),
|
||||
@@ -4979,8 +5081,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)
|
||||
@@ -4998,9 +5102,11 @@ class ComBackend(AutoCADBackend):
|
||||
scale_y=1.0,
|
||||
rotation=0.0,
|
||||
layer=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_require_block_defined(_acad_doc(), name)
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
ref = mspace.InsertBlock(
|
||||
_apoint(x, y),
|
||||
@@ -5033,8 +5139,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).
|
||||
@@ -5061,8 +5169,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:
|
||||
@@ -5083,8 +5193,10 @@ class ComBackend(AutoCADBackend):
|
||||
tx,
|
||||
ty,
|
||||
layer=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
def _sync():
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
dim = mspace.AddDimAngular(
|
||||
_apoint(vx, vy),
|
||||
@@ -5111,8 +5223,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)
|
||||
@@ -5137,8 +5251,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:
|
||||
@@ -5149,6 +5265,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:
|
||||
@@ -5746,6 +5929,8 @@ class ComBackend(AutoCADBackend):
|
||||
rotation=0.0,
|
||||
attributes=None,
|
||||
layer=None,
|
||||
style=None,
|
||||
layout=None,
|
||||
) -> EntityInfo:
|
||||
from backends.block_specs import validate_attribute_values
|
||||
|
||||
@@ -5753,6 +5938,7 @@ class ComBackend(AutoCADBackend):
|
||||
|
||||
def _sync():
|
||||
_require_block_defined(_acad_doc(), name)
|
||||
_activate_layout(layout)
|
||||
mspace = _msp()
|
||||
ref = mspace.InsertBlock(
|
||||
_apoint(x, y),
|
||||
@@ -5773,6 +5959,15 @@ class ComBackend(AutoCADBackend):
|
||||
attr.TextString = values[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)
|
||||
@@ -6165,7 +6360,7 @@ 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:
|
||||
from backends.block_specs import validate_attribute_values
|
||||
|
||||
values = validate_attribute_values(attributes)
|
||||
@@ -6175,12 +6370,26 @@ class ComBackend(AutoCADBackend):
|
||||
ref = doc.HandleToObject(handle)
|
||||
attrs = ref.GetAttributes()
|
||||
updated = []
|
||||
restyled = 0
|
||||
for attr in attrs:
|
||||
tag = attr.TagString
|
||||
if tag in values:
|
||||
attr.TextString = values[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)
|
||||
|
||||
@@ -6259,26 +6468,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:
|
||||
|
||||
Reference in New Issue
Block a user