123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- # Copyright (c) 2011-2018, Manfred Moitzi
- # License: MIT License
- from collections import namedtuple
- from enum import Enum
- from typing import Any, Tuple, Iterable, List, Dict, Union, ItemsView, KeysView, TYPE_CHECKING
- from .const import DXFAttributeError, DXFValueError, DXFInternalEzdxfError, DXFStructureError
- from .types import dxftag, DXFVertex
- from .tags import Tags
- from .extendedtags import ExtendedTags
- if TYPE_CHECKING: # import forward declarations
- from ezdxf.eztypes import DXFEntity, TagValue
- DefSubclass = namedtuple('DefSubclass', 'name attribs')
- VIRTUAL_TAG = -666
- class XType(Enum):
- """ Extended Attribute Types
- """
- point2d = 1 # 2D points only
- point3d = 2 # 3D points only
- any_point = 3 # 2D or 3D points
- callback = 4 #
- class DXFAttr:
- """
- Defines a DXF attribute, accessible by DXFEntity.dxf.name.
- Extended Attribute Types
- ------------------------
- - XType.point2d: 2D points only
- - XType.point3d: 3D point only
- - XType.any_point: mixed 2D/3D point
- - XType.callback: Calls get_value(entity) to get the value of DXF attribute 'name', and calls
- set_value(entity, value) to set value of DXF attribute 'name'.
- For example definitions see TestDXFEntity.test_callback() in file test_dxfentity.py
- """
- def __init__(self,
- code: int,
- subclass: int = 0,
- xtype: XType = None,
- default=None,
- dxfversion: str = None,
- getter: str = None, # name of getter method
- setter: str = None, # name of setter method
- ):
- self.name = '' # type: str # set by DXFAttributes._add_subclass_attribs()
- self.code = code # DXF group code
- self.subclass = subclass # subclass index
- self.xtype = xtype # Point2D, Point3D, Point2D/3D, Callback
- self.default = default # type: TagValue # DXF default value
- # If dxfversion is None - this attribute is valid for all supported DXF versions, set dxfversion to a specific
- # DXF version like 'AC1018' and this attribute can only be set by DXF version 'AC1018' or later.
- self.dxfversion = dxfversion
- self.getter = getter # DXF entity getter method name for callback attributes
- self.setter = setter # DXF entity setter method name for callback attributes
- def get_callback_value(self, entity: 'DXFEntity') -> 'TagValue':
- """
- Executes a callback function in 'entity' to get a DXF value.
- Callback function is defined by self.getter as string.
- Args:
- entity: DXF entity
- Returns: DXF attribute value
- """
- try:
- return getattr(entity, self.getter)()
- except AttributeError:
- raise DXFAttributeError('DXF attribute {}: invalid getter {}.'.format(self.name, self.getter))
- except TypeError: # None
- DXFAttributeError('DXF attribute {} has no getter.'.format(self.name))
- def set_callback_value(self, entity: 'DXFEntity', value: 'TagValue') -> None:
- """
- Executes a callback function in 'entity' to set a DXF value.
- Callback function is defined by self.setter as string.
- Args:
- entity: DXF entity
- value: DXF attribute value
- """
- try:
- getattr(entity, self.setter)(value)
- except AttributeError:
- raise DXFAttributeError('DXF attribute {}: invalid setter {}.'.format(self.name, self.setter))
- except TypeError: # None
- raise DXFAttributeError('DXF attribute {} has no setter.'.format(self.name))
- def get_attrib(self, entity: 'DXFEntity', key: str, default: Any = DXFValueError) -> 'TagValue':
- """
- Return value of DXF attribute 'key'.
- Args:
- entity: DXF entity
- key: attribute name
- default: default value or DXFValueError for raising an exception if attribute does not exist
- Returns: value of DXF attribute
- """
- if self.xtype is XType.callback:
- return self.get_callback_value(entity)
- try: # No check if attribute is valid for DXF version of drawing, if it is there you get it
- return self._get_dxf_attrib(entity.tags)
- except DXFValueError:
- if default is DXFValueError:
- # no DXF default values if DXF version is incorrect
- if self.dxfversion is not None and entity.drawing.dxfversion < self.dxfversion:
- msg = "DXFAttrib '{0}' not supported by DXF version '{1}', requires at least DXF version '{2}'."
- raise DXFValueError(msg.format(key, entity.drawing.dxfversion, self.dxfversion))
- result = self.default # default value defined by DXF specs
- if result is not None:
- return result
- else:
- raise DXFValueError("DXFAttrib '%s' does not exist." % key)
- else:
- return default
- def _get_dxf_attrib(self, tags: ExtendedTags) -> 'TagValue':
- subclass_tags = self._get_dxf_attrib_subclass_tags(tags, self.subclass)
- if self.xtype is not None:
- return self._get_extended_type(subclass_tags)
- else:
- return subclass_tags.get_first_value(self.code)
- def _get_extended_type(self, tags: Tags) -> Tuple[float, ...]:
- value = tags.get_first_value(self.code)
- if len(value) == 3:
- if self.xtype is XType.point2d:
- raise DXFStructureError("expected 2D point but found 3D point")
- elif self.xtype is XType.point3d: # len(value) == 2
- raise DXFStructureError("expected 3D point but found 2D point")
- return value
- def _get_dxf_attrib_subclass_tags(self, tags: ExtendedTags, subclass_key: Union[int, str]) -> Tags:
- try: # fast access subclass by index as int, no subclass is subclass index 0
- return tags.subclasses[subclass_key]
- except IndexError:
- raise DXFInternalEzdxfError('Subclass index error in {entity} subclass={index}.'.format(
- entity=str(self),
- index=subclass_key,
- ))
- except TypeError: # slow access subclass by name as string
- # raises DXFKeyError if subclass does not exist
- return tags.get_subclass(subclass_key)
- def set_attrib(self, entity: 'DXFEntity', key: str, value: 'TagValue') -> None:
- """
- Set DXF attribute 'key' to value.
- Args:
- entity: DXF entity
- key: attribute name
- value: attribute value
- """
- if self.dxfversion is not None:
- if entity.drawing.dxfversion < self.dxfversion:
- msg = "DXFAttrib '{0}' not supported by DXF version '{1}', requires at least DXF version '{2}'."
- raise DXFAttributeError(msg.format(key, entity.drawing.dxfversion, self.dxfversion))
- if self.xtype is XType.callback:
- self.set_callback_value(entity, value)
- return
- subclass_tags = self._get_dxf_attrib_subclass_tags(entity.tags, self.subclass)
- if self.xtype is not None:
- self._set_extended_type(subclass_tags, value)
- else:
- subclass_tags.set_first(dxftag(self.code, value))
- def _set_extended_type(self, tags: Tags, value: Iterable) -> None:
- value = tuple(value)
- vlen = len(value)
- if vlen == 3:
- if self.xtype is XType.point2d:
- raise DXFValueError('2 axis required')
- elif vlen == 2:
- if self.xtype is XType.point3d:
- raise DXFValueError('3 axis required')
- else:
- raise DXFValueError('2 or 3 axis required')
- tags.set_first(DXFVertex(self.code, value))
- def del_attrib(self, entity: 'DXFEntity') -> None:
- """
- Remove tag of DXF attribute in 'entity'.
- Args:
- entity: DXF entity
- """
- subclass_tags = self._get_dxf_attrib_subclass_tags(entity.tags, self.subclass)
- subclass_tags.remove_tags(codes=(self.code,))
- class DXFAttributes:
- def __init__(self, *subclassdefs: DefSubclass):
- self._subclasses = [] # type: List[DefSubclass]
- self._attribs = {} # type: Dict[str, DXFAttr]
- for subclass in subclassdefs:
- self.add_subclass(subclass)
- def add_subclass(self, subclass: DefSubclass) -> None:
- subclass_index = len(self._subclasses)
- self._subclasses.append(subclass)
- self._add_subclass_attribs(subclass, subclass_index)
- def _add_subclass_attribs(self, subclass: DefSubclass, subclass_index: int) -> None:
- for name, dxfattrib in subclass.attribs.items():
- dxfattrib.name = name
- dxfattrib.subclass = subclass_index
- self._attribs[name] = dxfattrib
- def __getitem__(self, name: str) -> DXFAttr:
- return self._attribs[name]
- def __contains__(self, name: str) -> bool:
- return name in self._attribs
- def get(self, key: str, default: Any = None) -> Any:
- return self._attribs.get(key, default)
- def keys(self) -> KeysView[str]:
- return self._attribs.keys()
- def items(self) -> ItemsView[str, DXFAttr]:
- return self._attribs.items()
- def subclasses(self) -> Iterable[DefSubclass]:
- return iter(self._subclasses)
- def build_group_code_items(self, func=lambda x: True):
- for name, attrib in self.items():
- if attrib.code > 0 and func(name): # skip internal tags
- yield attrib.code, name
|