242 lines
8.1 KiB
Python
242 lines
8.1 KiB
Python
# 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)
|