123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- # Created: 11.03.2011
- # Copyright (c) 2011-2018, Manfred Moitzi
- # License: MIT License
- from typing import TYPE_CHECKING, Any, Iterable, Mapping, List, cast, Tuple
- from ezdxf.lldxf.const import DXFStructureError, DXFAttributeError, DXFInvalidLayerName, DXFValueError, DXFXDataError
- from ezdxf.lldxf.validator import is_valid_layer_name
- from ezdxf.lldxf.tags import Tags
- from ezdxf.lldxf.extendedtags import ExtendedTags
- from ezdxf.tools import set_flag_state
- from ezdxf.math import OCS
- if TYPE_CHECKING: # import forward dependencies
- from ezdxf.eztypes import TagValue, IterableTags, DXFAttr, DXFDictionary
- from ezdxf.eztypes import Drawing, EntityDB, DXFFactoryType, GenericLayoutType
- ACAD_REACTORS = '{ACAD_REACTORS'
- ACAD_XDICTIONARY = '{ACAD_XDICTIONARY'
- class NotFoundException(Exception):
- pass
- class DXFNamespace:
- """
- Provides the dxf namespace for GenericWrapper.
- """
- __slots__ = ('_wrapper',)
- def __init__(self, wrapper: 'DXFEntity'):
- # DXFNamespace.__setattr__ can not set _wrapper
- super(DXFNamespace, self).__setattr__('_wrapper', wrapper)
- def __getattr__(self, attrib: str):
- """
- Returns value of DXF attribute *attrib*. usage: value = DXFEntity.dxf.attrib
- """
- return self._wrapper.get_dxf_attrib(attrib)
- def __setattr__(self, attrib: str, value) -> None:
- """
- Set DXF attribute *attrib* to *value. usage: DXFEntity.dxf.attrib = value
- """
- return self._wrapper.set_dxf_attrib(attrib, value)
- def __delattr__(self, attrib: str) -> None:
- """
- Remove DXF attribute *attrib*. usage: del DXFEntity.dxf.attrib
- """
- return self._wrapper.del_dxf_attrib(attrib)
- class DXFEntity:
- __slots__ = ('tags', 'dxf', 'drawing')
- TEMPLATE = None
- CLASS = None
- DXFATTRIBS = {}
- def __init__(self, tags: ExtendedTags, drawing: 'Drawing' = None):
- self.tags = tags # DXF tags stored as DXFTag (and inherited) in an ExtendedTags container
- self.dxf = DXFNamespace(self) # type: Any # dynamic DXF attribute dispatching, e.g. DXFEntity.dxf.layer
- self.drawing = drawing
- def __str__(self) -> str:
- """
- Returns a simple string representation.
- """
- return "{}(#{})".format(self.dxftype(), self.dxf.handle)
- def __repr__(self) -> str:
- """
- Returns a simple string representation including the class.
- """
- return str(self.__class__) + " " + str(self)
- @property
- def dxffactory(self) -> 'DXFFactoryType':
- return self.drawing.dxffactory
- @property
- def dxfversion(self) -> str:
- return self.drawing.dxfversion
- @property
- def entitydb(self) -> 'EntityDB':
- return self.drawing.entitydb
- @classmethod
- def new(cls, handle: str, dxfattribs: dict = None, drawing: 'Drawing' = None) -> 'DXFEntity':
- if cls.TEMPLATE is None:
- raise NotImplementedError("new() for type %s not implemented." % cls.__name__)
- entity = cls(cls.TEMPLATE.clone(), drawing)
- entity.dxf.handle = handle
- if dxfattribs is not None:
- if 'layer' in dxfattribs:
- layer_name = dxfattribs['layer']
- if not is_valid_layer_name(layer_name):
- raise DXFInvalidLayerName("Invalid layer name '{}'".format(layer_name))
- entity.update_dxf_attribs(dxfattribs)
- entity.post_new_hook()
- return entity
- def post_new_hook(self) -> None:
- """
- Called after entity creation.
- """
- pass
- def _new_entity(self, type_: str, dxfattribs: dict) -> 'DXFEntity':
- """
- Create new entity with same layout settings as *self*.
- Used by INSERT & POLYLINE to create appended DXF entities, don't use it to create new standalone entities.
- """
- entity = self.dxffactory.create_db_entry(type_, dxfattribs)
- self.dxffactory.copy_layout(self, entity)
- return entity
- def __copy__(self) -> 'DXFEntity':
- """
- Deep copy of DXFEntity with new handle and duplicated linked entities (VERTEX, ATTRIB, SEQEND).
- The new entity is not included in any layout space, so the owner tag is set to '0' for undefined owner/layout.
- Use Layout.add_entity(new_entity) to add the duplicated entity to a layout, layout can be the model space,
- a paper space layout or a block layout.
- It is not called __deepcopy__, because this is not a deep copy in the meaning of Python, because handle, link
- and owner is changed.
- """
- new_tags = self.entitydb.duplicate_tags(self.tags)
- entity = self.dxffactory.wrap_entity(new_tags)
- if self.supports_dxf_attrib('owner'): # R2000+
- entity.dxf.owner = '0' # reset ownership/layout
- return entity
- copy = __copy__
- def linked_entities(self) -> Iterable['DXFEntity']:
- """
- Iterate over all linked entities, only POLYLINE and INSERT has linked entities (VERTEX, ATTRIB, SEQEND)
- Yields: DXFEntity() objects
- """
- link = self.tags.link # type: str
- wrap = self.dxffactory.wrap_handle
- while link is not None:
- entity = wrap(link)
- yield entity
- link = entity.tags.link
- def copy_to_layout(self, layout: 'GenericLayoutType') -> 'DXFEntity':
- """
- Copy entity to another layout.
- Args:
- layout: any layout (model space, paper space, block)
- Returns: new created entity as DXFEntity() object
- """
- new_entity = self.copy()
- layout.add_entity(new_entity)
- return new_entity
- def move_to_layout(self, layout: 'GenericLayoutType', source: 'GenericLayoutType' = None) -> None:
- """
- Move entity from model space or a paper space layout to another layout. For block layout as source, the
- block layout has to be specified.
- Args:
- layout: any layout (model space, paper space, block)
- source: provide source layout, faster for DXF R12, if entity is in a block layout
- """
- if source is None:
- source = self.get_layout()
- if source is None:
- raise DXFValueError('Source layout for entity not found.')
- source.move_to_layout(self, layout)
- def dxftype(self) -> str:
- return self.tags.noclass[0].value
- def _get_dxfattr_definition(self, key: str) -> 'DXFAttr':
- try:
- return self.DXFATTRIBS[key]
- except KeyError:
- raise DXFAttributeError(key)
- def get_dxf_attrib(self, key: str, default: Any = DXFValueError) -> 'TagValue':
- dxfattr = self._get_dxfattr_definition(key)
- return dxfattr.get_attrib(self, key, default)
- def set_dxf_attrib(self, key: str, value: 'TagValue') -> None:
- dxfattr = self._get_dxfattr_definition(key)
- dxfattr.set_attrib(self, key, value)
- def del_dxf_attrib(self, key: str) -> None:
- dxfattr = self._get_dxfattr_definition(key)
- dxfattr.del_attrib(self)
- def supports_dxf_attrib(self, key: str) -> bool:
- """
- Returns True if DXF attribute key is supported else False. Does not grant that attribute key really exists.
- """
- dxfattr = self.DXFATTRIBS.get(key, None)
- if dxfattr is None:
- return False
- if dxfattr.dxfversion is None:
- return True
- return self.drawing.dxfversion >= dxfattr.dxfversion
- def dxf_attrib_exists(self, key: str) -> bool:
- """
- Returns True if DXF attrib key really exists else False. Raises AttributeError if key isn't supported.
- """
- # attributes with default values don't raise an exception!
- return self.get_dxf_attrib(key, default=None) is not None
- def valid_dxf_attrib_names(self) -> Iterable[str]:
- """
- Returns a list of supported DXF attribute names.
- """
- return [key for key, attrib in self.DXFATTRIBS.items() if
- attrib.dxfversion is None or (attrib.dxfversion <= self.drawing.dxfversion)]
- def get_dxf_default_value(self, key: str) -> 'TagValue':
- """
- Returns the default value as defined in the DXF standard.
- """
- return self._get_dxfattr_definition(key).default
- def has_dxf_default_value(self, key: str) -> bool:
- """
- Returns True if the DXF attribute key has a DXF standard default value.
- """
- return self._get_dxfattr_definition(key).default is not None
- def dxfattribs(self) -> dict:
- """
- Clones defined and existing DXF attributes as dict.
- """
- dxfattribs = {}
- for key in self.DXFATTRIBS.keys():
- value = self.get_dxf_attrib(key, default=None)
- if value is not None:
- dxfattribs[key] = value
- return dxfattribs
- clone_dxf_attribs = dxfattribs
- def update_dxf_attribs(self, dxfattribs: Mapping) -> None:
- for key, value in dxfattribs.items():
- self.set_dxf_attrib(key, value)
- def set_flag_state(self, flag: int, state: bool = True, name: str = 'flags') -> None:
- flags = self.get_dxf_attrib(name, 0)
- self.set_dxf_attrib(name, set_flag_state(flags, flag, state=state))
- def get_flag_state(self, flag: int, name: str = 'flags') -> bool:
- return bool(self.get_dxf_attrib(name, 0) & flag)
- def ocs(self) -> OCS:
- extrusion = self.get_dxf_attrib('extrusion', default=(0, 0, 1))
- return OCS(extrusion)
- def destroy(self) -> None:
- if self.has_extension_dict():
- xdict = self.get_extension_dict()
- self.drawing.objects.delete_entity(xdict)
- def has_app_data(self, appid: str) -> bool:
- return self.tags.has_app_data(appid)
- def get_app_data(self, appid: str) -> Tags:
- return self.tags.get_app_data_content(appid)
- def set_app_data(self, appid: str, app_data_tags: 'IterableTags') -> None:
- if self.tags.has_app_data(appid):
- self.tags.set_app_data_content(appid, app_data_tags)
- else:
- self.tags.new_app_data(appid, app_data_tags)
- def has_xdata(self, appid: str) -> bool:
- return self.tags.has_xdata(appid)
- def get_xdata(self, appid: str) -> Tags:
- return Tags(self.tags.get_xdata(appid)[1:]) # without app id tag
- def set_xdata(self, appid: str, xdata_tags: 'IterableTags') -> None:
- if self.tags.has_xdata(appid):
- self.tags.set_xdata(appid, xdata_tags)
- else:
- self.tags.new_xdata(appid, xdata_tags)
- def has_xdata_list(self, appid: str, name: str) -> bool:
- """
- Returns if list `name` from XDATA `appid` exists.
- Args:
- appid: APPID
- name: list name
- """
- try:
- self.get_xdata_list(appid, name)
- except DXFValueError:
- return False
- else:
- return True
- def get_xdata_list(self, appid: str, name: str) -> List[Tuple]:
- """
- Get list `name` from XDATA `appid`.
- Args:
- appid: APPID
- name: list name
- Returns: list of DXFTags including list name and curly braces '{' '}' tags
- Raises:
- DXFValueError: XDATA `appid` do not exist or list `name` do not exist
- """
- xdata = self.get_xdata(appid)
- try:
- return get_named_list_from_xdata(name, xdata)
- except NotFoundException:
- raise DXFValueError('No data list "{}" not found for APPID "{}"'.format(name, appid))
- def set_xdata_list(self, appid: str, name: str, xdata_tags: 'IterableTags') -> None:
- """
- Create new list `name` of XDATA `appid` with `xdata_tags` and replaces list `name` if already exists.
- Args:
- appid: APPID
- name: list name
- xdata_tags: list content as DXFTags or (code, value) tuples, list name and curly braces '{' '}' tags will
- be added
- """
- if not self.tags.has_xdata(appid):
- self.tags.new_xdata(appid, xdata_list(name, xdata_tags))
- else:
- self.replace_xdata_list(appid, name, xdata_tags)
- def discard_xdata_list(self, appid: str, name: str) -> None:
- """
- Deletes list `name` from XDATA `appid`. Ignores silently if XDATA `appid` or list `name` not exists.
- Args:
- appid: APPID
- name: list name
- """
- try:
- xdata = self.get_xdata(appid)
- except DXFValueError:
- pass
- else:
- try:
- tags = remove_named_list_from_xdata(name, xdata)
- except NotFoundException:
- pass
- else:
- self.set_xdata(appid, tags)
- def replace_xdata_list(self, appid: str, name: str, xdata_tags: 'IterableTags') -> None:
- """
- Replaces list `name` of existing XDATA `appid` with `xdata_tags`. Appends new list if list `name` do not exist,
- but raises `DXFValueError` if XDATA `appid` do not exist.
- Low level interface, if not sure use `set_xdata_list()` instead.
- Args:
- appid: APPID
- name: list name
- xdata_tags: list content as DXFTags or (code, value) tuples, list name and curly braces '{' '}' tags will
- be added
- Raises:
- DXFValueError: XDATA `appid` do not exist
- """
- xdata = self.get_xdata(appid)
- try:
- tags = remove_named_list_from_xdata(name, xdata)
- except NotFoundException:
- tags = xdata
- tags.extend(xdata_list(name, xdata_tags))
- self.tags.set_xdata(appid, tags)
- def has_reactors(self) -> bool:
- return self.has_app_data(ACAD_REACTORS)
- def get_reactors(self) -> List[str]:
- reactor_tags = self.get_app_data(ACAD_REACTORS)
- return [tag.value for tag in reactor_tags]
- def set_reactors(self, reactor_handles: Iterable[str]) -> None:
- reactor_tags = [(330, handle) for handle in reactor_handles]
- self.set_app_data(ACAD_REACTORS, reactor_tags)
- def append_reactor_handle(self, handle: str) -> None:
- reactors = set(self.get_reactors())
- reactors.add(handle)
- self.set_reactors(sorted(reactors, key=lambda x: int(x, base=16)))
- def remove_reactor_handle(self, handle: str) -> None:
- reactors = set(self.get_reactors())
- reactors.discard(handle)
- self.set_reactors(reactors)
- def has_extension_dict(self) -> bool:
- return self.has_app_data(ACAD_XDICTIONARY)
- def get_extension_dict(self) -> 'DXFDictionary':
- """
- Get associated extension dictionary as DXFDictionary() object.
- """
- app_data = self.get_app_data(ACAD_XDICTIONARY)
- if len(app_data) == 0 or app_data[0].code != 360:
- raise DXFStructureError("XDICTIONARY error in entity: " + str(self))
- # are more than one XDICTIONARY possible?
- xdict_handle = app_data[0].value
- return cast('DXFDictionary', self.dxffactory.wrap_handle(xdict_handle))
- def new_extension_dict(self) -> 'DXFDictionary':
- """
- Creates and assigns a new extensions dictionary. Link to an existing extension dictionary will be lost.
- """
- xdict = self.drawing.objects.add_dictionary(owner=self.dxf.handle)
- self.set_app_data(ACAD_XDICTIONARY, [(360, xdict.dxf.handle)])
- return xdict
- def get_layout(self) -> 'GenericLayoutType':
- return self.dxffactory.get_layout_for_entity(self)
- def audit(self, auditor):
- """
- Audit entity for errors.
- Args:
- auditor: Audit() object
- """
- pass
- def has_embedded_objects(self) -> bool:
- return any(tags.has_embedded_objects() for tags in self.tags.subclasses)
- OPEN_LIST = (1002, '{')
- CLOSE_LIST = (1002, '}')
- def xdata_list(name: str, xdata_tags: 'IterableTags') -> List[Tuple]:
- tags = []
- if name:
- tags.append((1000, name))
- tags.append(OPEN_LIST)
- tags.extend(xdata_tags)
- tags.append(CLOSE_LIST)
- return tags
- def remove_named_list_from_xdata(name: str, tags: Tags) -> List[Tuple]:
- start, end = get_start_and_end_of_named_list_in_xdata(name, tags)
- del tags[start: end]
- return tags
- def get_named_list_from_xdata(name: str, tags: Tags) -> List[Tuple]:
- start, end = get_start_and_end_of_named_list_in_xdata(name, tags)
- return tags[start: end]
- def get_start_and_end_of_named_list_in_xdata(name: str, tags: List[Tuple]) -> Tuple[int, int]:
- start = None
- end = None
- level = 0
- for index in range(len(tags)):
- tag = tags[index]
- if start is None and tag == (1000, name):
- next_tag = tags[index + 1]
- if next_tag == OPEN_LIST:
- start = index
- continue
- if start is not None:
- if tag == OPEN_LIST:
- level += 1
- elif tag == CLOSE_LIST:
- level -= 1
- if level == 0:
- end = index
- break
- if start is None:
- raise NotFoundException
- if end is None:
- raise DXFXDataError('Invalid XDATA structure: missing (1002, "}").')
- return start, end + 1
|