fix(blocks): entity_create_block_ref refuses an undefined or layout block before writing, on both engines

Track A's task-1 gate covered block_insert only; the sibling tool still
returned a handle for a typo'd name that doc.audit() later deleted, and
both tools accepted *Model_Space (a reference cycle). One gate per engine
(_require_block / _require_block_defined) is now shared by both placing
tools: non-string name -> TypeError, unknown -> ValueError naming it,
layout block -> ValueError, all before add_blockref / InsertBlock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-16 21:13:44 +03:00
co-authored by Claude Opus 5
parent 49fc6ac6a5
commit b9510bf377
4 changed files with 278 additions and 8 deletions
+26
View File
@@ -193,6 +193,30 @@ def _msp():
return doc.ModelSpace
def _require_block_defined(doc, name):
"""``doc.Blocks.Item(name)``, or a ``ValueError`` before ``InsertBlock`` runs.
``InsertBlock`` with an unknown name raises a bare COM error after the call
has already been dispatched; refusing here names the block, costs one
``Blocks.Item`` probe and matches the headless engine's message. A layout
block (``IsLayout``) cannot be inserted into itself — same refusal by name.
Shared by ``block_insert`` and ``entity_create_block_ref``.
"""
if not isinstance(name, str) or not name.strip():
raise TypeError("block name must be a non-empty string")
try:
block = doc.Blocks.Item(name)
except Exception as exc:
raise ValueError(f"block {name!r} is not defined") from exc
try:
is_layout = bool(block.IsLayout)
except Exception:
is_layout = False
if is_layout:
raise ValueError(f"block {name!r} is a layout block and cannot be inserted")
return block
_BUILTIN_LINETYPES = {"continuous", "bylayer", "byblock"}
@@ -2479,6 +2503,7 @@ class ComBackend(AutoCADBackend):
layer=None,
) -> EntityInfo:
def _sync():
_require_block_defined(_acad_doc(), name)
mspace = _msp()
ref = mspace.InsertBlock(
_apoint(x, y),
@@ -3248,6 +3273,7 @@ class ComBackend(AutoCADBackend):
layer=None,
) -> EntityInfo:
def _sync():
_require_block_defined(_acad_doc(), name)
mspace = _msp()
ref = mspace.InsertBlock(
_apoint(x, y),
+24 -7
View File
@@ -1087,6 +1087,28 @@ class EzdxfBackend(AutoCADBackend):
raise RuntimeError("No document open. Call drawing_new() or drawing_open() first.")
return self._doc
def _require_block(self, name):
"""The block definition ``name`` names, or a refusal before any write.
``add_blockref`` never checks the name and ``add_auto_attribs`` skips
autofill when ``Insert.block()`` resolves to None, so a typo'd name
used to yield a dangling INSERT that dropped its attribute values and
that ``doc.audit()`` later deletes. A layout block (``*Model_Space``,
``*Paper_Space``) is in ``doc.blocks`` too, but inserting one is the
reference cycle AutoCAD's INSERT refuses, so it is refused here by
name. Shared by ``block_insert`` and ``entity_create_block_ref`` so the
two tools that place a block reference agree on what a typo does.
"""
doc = self._require_doc()
if not isinstance(name, str) or not name.strip():
raise TypeError("block name must be a non-empty string")
if name not in doc.blocks:
raise ValueError(f"block {name!r} is not defined")
blk = doc.blocks.get(name)
if blk.block_record.is_any_layout:
raise ValueError(f"block {name!r} is a layout block and cannot be inserted")
return blk
def _msp(self):
"""The space new geometry goes into — model space, or the current layout.
@@ -3002,6 +3024,7 @@ class EzdxfBackend(AutoCADBackend):
) -> EntityInfo:
def _sync():
msp = self._msp()
self._require_block(name)
ent = msp.add_blockref(
name,
(float(x), float(y)),
@@ -4485,13 +4508,7 @@ class EzdxfBackend(AutoCADBackend):
}
if layer:
attribs["layer"] = layer
# `add_blockref` never checks the name, and `add_auto_attribs`
# silently skips autofill when `Insert.block()` resolves to None,
# so a typo'd name used to yield a dangling INSERT that dropped its
# attribute values and that `doc.audit()` later deletes. Refuse
# before writing, as COM's `InsertBlock` does for an unknown name.
if name not in self._doc.blocks:
raise ValueError(f"block {name!r} is not defined")
self._require_block(name)
ref = msp.add_blockref(name, (float(x), float(y)), dxfattribs=attribs)
if attributes:
# `add_auto_blockref` wraps the INSERT in an anonymous *U block,