This commit is contained in:
Christian Anetzberger
2026-01-22 20:23:51 +01:00
commit a197de9456
4327 changed files with 1235205 additions and 0 deletions

View File

@@ -0,0 +1,241 @@
# Copyright (c) 2019-2023 Manfred Moitzi
# License: MIT License
from __future__ import annotations
from typing import TYPE_CHECKING, Union, Optional
from ezdxf.lldxf.tags import Tags
from ezdxf.lldxf.const import DXFStructureError
from ezdxf.lldxf.const import (
ACAD_XDICTIONARY,
XDICT_HANDLE_CODE,
APP_DATA_MARKER,
)
from .copy import default_copy
if TYPE_CHECKING:
from ezdxf.document import Drawing
from ezdxf.lldxf.tagwriter import AbstractTagWriter
from ezdxf.entities import (
Dictionary,
DXFEntity,
DXFObject,
Placeholder,
DictionaryVar,
XRecord,
)
__all__ = ["ExtensionDict"]
# Example for table head and -entries with extension dicts:
# AutodeskSamples\lineweights.dxf
class ExtensionDict:
"""Stores extended data of entities in app data 'ACAD_XDICTIONARY', app
data contains just one entry to a hard-owned DICTIONARY objects, which is
not shared with other entities, each entity copy has its own extension
dictionary and the extension dictionary is destroyed when the owner entity
is deleted from database.
"""
__slots__ = ("_xdict",)
def __init__(self, xdict: Union[str, Dictionary]):
# 1st loading stage: xdict as string -> handle to dict
# 2nd loading stage: xdict as DXF Dictionary
self._xdict = xdict
@property
def dictionary(self) -> Dictionary:
"""Returns the underlying :class:`~ezdxf.entities.Dictionary` object."""
xdict = self._xdict
assert xdict is not None, "destroyed extension dictionary"
assert not isinstance(xdict, str), f"dictionary handle #{xdict} not resolved"
return xdict
@property
def handle(self) -> str:
"""Returns the handle of the underlying :class:`~ezdxf.entities.Dictionary`
object.
"""
return self.dictionary.dxf.handle
def __getitem__(self, key: str):
"""Get self[key]."""
return self.dictionary[key]
def __setitem__(self, key: str, value):
"""Set self[key] to value.
Only DXF objects stored in the OBJECTS section are allowed as content
of the extension dictionary. DXF entities stored in layouts are not
allowed.
Raises:
DXFTypeError: invalid DXF type
"""
self.dictionary[key] = value
def __delitem__(self, key: str):
"""Delete self[key], destroys referenced entity."""
del self.dictionary[key]
def __contains__(self, key: str):
"""Return `key` in self."""
return key in self.dictionary
def __len__(self):
"""Returns count of extension dictionary entries."""
return len(self.dictionary)
def keys(self):
"""Returns a :class:`KeysView` of all extension dictionary keys."""
return self.dictionary.keys()
def items(self):
"""Returns an :class:`ItemsView` for all extension dictionary entries as
(key, entity) pairs. An entity can be a handle string if the entity
does not exist.
"""
return self.dictionary.items()
def get(self, key: str, default=None) -> Optional[DXFEntity]:
"""Return extension dictionary entry `key`."""
return self.dictionary.get(key, default)
def discard(self, key: str) -> None:
"""Discard extension dictionary entry `key`."""
return self.dictionary.discard(key)
@classmethod
def new(cls, owner_handle: str, doc: Drawing):
xdict = doc.objects.add_dictionary(
owner=owner_handle,
# All data in the extension dictionary belongs only to the owner
hard_owned=True,
)
return cls(xdict)
def copy(self, copy_strategy=default_copy) -> ExtensionDict:
"""Deep copy of the extension dictionary all entries are virtual
entities.
"""
new_xdict = copy_strategy.copy(self.dictionary)
return ExtensionDict(new_xdict)
@property
def is_alive(self):
"""Returns ``True`` if the underlying :class:`~ezdxf.entities.Dictionary`
object is not deleted.
"""
# Can not check if _xdict (as handle or Dictionary) really exist:
return self._xdict is not None
@property
def has_valid_dictionary(self):
"""Returns ``True`` if the underlying :class:`~ezdxf.entities.Dictionary`
really exist and is valid.
"""
xdict = self._xdict
if xdict is None or isinstance(xdict, str):
return False
return xdict.is_alive
def update_owner(self, handle: str) -> None:
"""Update owner tag of underlying :class:`~ezdxf.entities.Dictionary`
object.
Internal API.
"""
assert self.is_alive, "destroyed extension dictionary"
self.dictionary.dxf.owner = handle
@classmethod
def from_tags(cls, tags: Tags):
assert tags is not None
# Expected DXF structure:
# [(102, '{ACAD_XDICTIONARY', (360, handle), (102, '}')]
if len(tags) != 3 or tags[1].code != XDICT_HANDLE_CODE:
raise DXFStructureError("ACAD_XDICTIONARY error.")
return cls(tags[1].value)
def load_resources(self, doc: Drawing) -> None:
handle = self._xdict
assert isinstance(handle, str)
self._xdict = doc.entitydb.get(handle) # type: ignore
def export_dxf(self, tagwriter: AbstractTagWriter) -> None:
assert self._xdict is not None
xdict = self._xdict
handle = xdict if isinstance(xdict, str) else xdict.dxf.handle
tagwriter.write_tag2(APP_DATA_MARKER, ACAD_XDICTIONARY)
tagwriter.write_tag2(XDICT_HANDLE_CODE, handle)
tagwriter.write_tag2(APP_DATA_MARKER, "}")
def destroy(self):
"""Destroy the underlying :class:`~ezdxf.entities.Dictionary` object."""
if self.has_valid_dictionary:
self._xdict.destroy()
self._xdict = None
def add_dictionary(self, name: str, hard_owned: bool = True) -> Dictionary:
"""Create a new :class:`~ezdxf.entities.Dictionary` object as
extension dictionary entry `name`.
"""
dictionary = self.dictionary
doc = dictionary.doc
assert doc is not None, "valid DXF document required"
new_dict = doc.objects.add_dictionary(
owner=dictionary.dxf.handle,
hard_owned=hard_owned,
)
dictionary[name] = new_dict
return new_dict
def add_xrecord(self, name: str) -> XRecord:
"""Create a new :class:`~ezdxf.entities.XRecord` object as
extension dictionary entry `name`.
"""
dictionary = self.dictionary
doc = dictionary.doc
assert doc is not None, "valid DXF document required"
xrecord = doc.objects.add_xrecord(dictionary.dxf.handle)
dictionary[name] = xrecord
return xrecord
def add_dictionary_var(self, name: str, value: str) -> DictionaryVar:
"""Create a new :class:`~ezdxf.entities.DictionaryVar` object as
extension dictionary entry `name`.
"""
dictionary = self.dictionary
doc = dictionary.doc
assert doc is not None, "valid DXF document required"
dict_var = doc.objects.add_dictionary_var(dictionary.dxf.handle, value)
dictionary[name] = dict_var
return dict_var
def add_placeholder(self, name: str) -> Placeholder:
"""Create a new :class:`~ezdxf.entities.Placeholder` object as
extension dictionary entry `name`.
"""
dictionary = self.dictionary
doc = dictionary.doc
assert doc is not None, "valid DXF document required"
placeholder = doc.objects.add_placeholder(dictionary.dxf.handle)
dictionary[name] = placeholder
return placeholder
def link_dxf_object(self, name: str, obj: DXFObject) -> None:
"""Link `obj` to the extension dictionary as entry `name`.
Linked objects are owned by the extensions dictionary and therefore
cannot be a graphical entity, which have to be owned by a
:class:`~ezdxf.layouts.BaseLayout`.
Raises:
DXFTypeError: `obj` has invalid DXF type
"""
self.dictionary.link_dxf_object(name, obj)