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:
co-authored by
Claude Opus 5
parent
49fc6ac6a5
commit
b9510bf377
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user