From b14aa29a7e55eef6da38f7c089c80f2af6526949 Mon Sep 17 00:00:00 2001 From: mozman Date: Fri, 8 Nov 2024 11:36:36 +0100 Subject: [PATCH] add BaseAttrib.audit() method Destroys ATTRIB and ATTDEF entities without a "tag" attribute. --- src/ezdxf/audit.py | 3 ++- src/ezdxf/entities/attrib.py | 14 +++++++++++++- tests/test_02_dxf_graphics/test_208_attrib.py | 14 ++++++++++++++ .../test_424_audit.py | 3 ++- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/ezdxf/audit.py b/src/ezdxf/audit.py index 805604846..92d4d1ea1 100644 --- a/src/ezdxf/audit.py +++ b/src/ezdxf/audit.py @@ -93,6 +93,7 @@ class AuditError(IntEnum): INVALID_CREASE_VALUE_COUNT = 224 INVALID_ELLIPSE_RATIO = 225 INVALID_HATCH_BOUNDARY_PATH = 226 + TAG_ATTRIBUTE_MISSING = 227 REQUIRED_ROOT_DICT_ENTRIES = ("ACAD_GROUP", "ACAD_PLOTSTYLENAME") @@ -111,7 +112,7 @@ def __init__( self.message: str = message # error message self.data: Any = data # additional data as an arbitrary object -# pylint: disable=too-many-public-methods + class Auditor: def __init__(self, doc: Drawing) -> None: assert doc is not None and doc.rootdict is not None and doc.entitydb is not None diff --git a/src/ezdxf/entities/attrib.py b/src/ezdxf/entities/attrib.py index 89e749bc9..2693be77f 100644 --- a/src/ezdxf/entities/attrib.py +++ b/src/ezdxf/entities/attrib.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Optional from typing_extensions import Self import copy + +from ezdxf.audit import Auditor, AuditError from ezdxf.lldxf import validator from ezdxf.math import NULLVEC, Vec3, Z_AXIS, OCS, Matrix44 from ezdxf.lldxf.attributes import ( @@ -40,7 +42,6 @@ if TYPE_CHECKING: from ezdxf.lldxf.tagwriter import AbstractTagWriter from ezdxf.lldxf.tags import Tags - from ezdxf.entities import DXFEntity from ezdxf import xref @@ -81,6 +82,7 @@ # ezdxf stores the last group code 280 as "lock_position" attribute and does # not export a version tag for any DXF version. # Tag string (cannot contain spaces): + # Mandatory by AutoCAD! "tag": DXFAttr( 2, default="", @@ -402,6 +404,16 @@ def transform(self, m: Matrix44) -> Self: self.post_transform(m) return self + def audit(self, auditor: Auditor) -> None: + """Validity check.""" + super().audit(auditor) + if not self.dxf.hasattr("tag"): + auditor.fixed_error( + code=AuditError.TAG_ATTRIBUTE_MISSING, + message=f'Missing mandatory "tag" attribute, entity {str(self)} deleted.', + ) + auditor.trash(self) + def _update_content_from_mtext(text: Text, mtext: MText) -> None: content = mtext.plain_text(split=True, fast=True) diff --git a/tests/test_02_dxf_graphics/test_208_attrib.py b/tests/test_02_dxf_graphics/test_208_attrib.py index da646cc59..a43afe884 100644 --- a/tests/test_02_dxf_graphics/test_208_attrib.py +++ b/tests/test_02_dxf_graphics/test_208_attrib.py @@ -9,6 +9,7 @@ from ezdxf.lldxf.const import DXF12, DXF2000 from ezdxf.lldxf.tagwriter import TagCollector, basic_tags_from_text from ezdxf.math import Matrix44 +from ezdxf.audit import Auditor TEST_CLASS = Attrib TEST_TYPE = "ATTRIB" @@ -295,6 +296,19 @@ def test_version_without_lock_position(): assert attrib.dxf.lock_position == 7 +def test_audit_destroys_attrib_without_tag_attribute(doc): + # Unbound entities cannot be audited! + attrib = Attrib.new() + doc.modelspace().add_entity(attrib) + + auditor = Auditor(doc) + attrib.audit(auditor) + auditor.empty_trashcan() + + assert len(auditor.fixes) == 1 + assert attrib.is_alive is False + + LOCK_POSITION_AND_VERSION = """0 ATTRIB 5 diff --git a/tests/test_04_dxf_high_level_structs/test_424_audit.py b/tests/test_04_dxf_high_level_structs/test_424_audit.py index 2499dd459..2bec5ec73 100644 --- a/tests/test_04_dxf_high_level_structs/test_424_audit.py +++ b/tests/test_04_dxf_high_level_structs/test_424_audit.py @@ -211,7 +211,8 @@ def test_remove_standalone_attrib_entities_from_blocks(): # The model space is just a BLOCK! doc = ezdxf.new() msp = doc.modelspace() - msp.add_entity(Attrib()) + # Missing tag is a different issue! + msp.add_entity(Attrib.new(dxfattribs={"tag": "test"})) auditor = doc.audit() assert len(list(msp)) == 0 assert len(auditor.fixes) == 1