fix(task-2): find_replace rewrites every surface of a multi-line attribute; COM explode propagates a failing read and undoes a failed burst

Headless text_find_replace matched and wrote only dxf.text of an ATTRIB/ATTDEF, so on a multi-line attribute it reported replaced: 1 while block_get_attributes and block_explode still carried the old embedded MTEXT (and a needle on the second line was never found). The ATTRIB/ATTDEF branch now reads through _attrib_value and writes through _write_attrib_value, the same pair block_get_attributes / block_set_attributes use.

COM block_explode swallowed a failing GetAttributes() (attrs = ()), GetConstantAttributes() and Invisible, then exploded, deleted every ATTDEF placeholder and the reference and returned ok: True with no attribute text -- every value destroyed silently on a transient RPC_E_CALL_REJECTED. Those reads now happen before Explode() and propagate with nothing written; a failure after the explode deletes the members it added and the TEXTs written so far and leaves the reference in place (_undo_explode), so the drawing is never left half-burst under an exception. The fakes gain the members a live AcadBlockReference / AcadAttributeDefinition always expose (GetConstantAttributes, Constant, Invisible) instead of relying on the swallow.

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-17 23:32:56 +03:00
co-authored by Claude Opus 5
parent 5f33613460
commit b753a12bb6
4 changed files with 304 additions and 36 deletions
+3 -1
View File
@@ -2417,7 +2417,9 @@ async def text_find_replace(
never searched" are different answers. Block *definitions* are included, so
the next insert does not reintroduce the old text. DIMENSION text is out of
scope: its text field holds the `<>` override placeholder rather than the
measurement, so editing it would break the association.
measurement, so editing it would break the association. A multi-line
attribute is matched and rewritten as the whole value (`Line1\\PLine2`),
the same value `block_get_attributes` reports and `block_explode` bursts.
"""
await ctx.info(f"Replacing {find!r} with {replace!r}{' (dry run)' if dry_run else ''}")
return await _backend(ctx).text_find_replace(find, replace, layer or None, match_case, dry_run)