123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- # Created: 22.03.2011
- # Copyright (c) 2011-2018, Manfred Moitzi
- # License: MIT-License
- from typing import TYPE_CHECKING, Iterable, Union, cast, List
- from contextlib import contextmanager
- from ezdxf.lldxf.types import DXFTag
- from ezdxf.lldxf.extendedtags import ExtendedTags
- from ezdxf.lldxf.attributes import DXFAttr, DXFAttributes, DefSubclass
- from ezdxf.dxfentity import DXFEntity
- from ezdxf.lldxf.const import DXFValueError
- from .dxfobjects import none_subclass
- from .object_manager import ObjectManager
- if TYPE_CHECKING:
- from ezdxf.eztypes import Tags, Drawing
- _GROUP_TPL = """0
- GROUP
- 5
- 0
- 330
- 0
- 100
- AcDbGroup
- 300
- 70
- 1
- 71
- 1
- """
- GROUP_ITEM_CODE = 340
- class DXFGroup(DXFEntity):
- __slots__ = ()
- # groups are not allowed in block definitions
- TEMPLATE = ExtendedTags.from_text(_GROUP_TPL)
- DXFATTRIBS = DXFAttributes(
- none_subclass,
- DefSubclass('AcDbGroup', {
- 'description': DXFAttr(300),
- 'unnamed': DXFAttr(70),
- 'selectable': DXFAttr(71),
- }),
- )
- @property
- def AcDbGroup(self) -> 'Tags':
- return self.tags.subclasses[1]
- def __iter__(self) -> Iterable[DXFEntity]:
- """ Yields all DXF entities of this group as wrapped DXFEntity (LINE, CIRCLE, ...) objects.
- """
- wrap = self.dxffactory.wrap_handle
- for handle in self.handles():
- try:
- entity = wrap(handle)
- yield entity
- except KeyError: # handle not in entity database, maybe entity were deleted; internal exception
- pass
- def __len__(self) -> int:
- return sum(1 for tag in self.AcDbGroup if tag.code == GROUP_ITEM_CODE)
- def __contains__(self, item: Union[str, DXFEntity]) -> bool:
- handle = item if isinstance(item, str) else item.dxf.handle
- return handle in set(self.handles())
- def handles(self) -> Iterable[str]:
- return (tag.value for tag in self.AcDbGroup if tag.code == GROUP_ITEM_CODE)
- def get_name(self):
- group_table = cast('DXFDictionary', self.dxffactory.wrap_handle(self.dxf.owner))
- my_handle = self.dxf.handle
- for name, handle in group_table.items():
- if handle == my_handle:
- return name
- return None
- @contextmanager
- def edit_data(self) -> List['DXFEntity']:
- data = list(self)
- yield data
- self.set_data(data)
- def set_data(self, entities: List['DXFEntity']) -> None:
- entities = list(entities) # for generators
- if not all_entities_on_same_layout(entities):
- raise DXFValueError(
- "All entities have to be on the same layout (model space or any paper layout but not block).")
- self.clear()
- self.AcDbGroup.extend(DXFTag(GROUP_ITEM_CODE, entity.dxf.handle) for entity in entities)
- def extend(self, entities: Iterable['DXFEntity']) -> None:
- """
- Add `entities` to group.
- Args:
- entities: iterable of DXFEntity
- """
- with self.edit_data() as e:
- e.extend(entities)
- def clear(self) -> None:
- """
- Remove all entity references, does not delete any drawing entities referenced by this group.
- """
- self.AcDbGroup.remove_tags((GROUP_ITEM_CODE,))
- def remove_invalid_handles(self) -> None:
- """
- Remove invalid handles from group.
- Invalid handles are: deleted entities, entities in a block layout
- """
- def handle_not_in_block_definition(handle: str) -> bool:
- wrap = self.dxffactory.wrap_handle # shortcut
- # owner block_record.layout is 0 if entity is in a block definition
- owner_handle = wrap(handle).dxf.owner
- return wrap(owner_handle).dxf.layout != 0
- db = self.entitydb # faster local var
- valid_handles = [handle for handle in self.handles() if handle in db]
- self.clear()
- # If one entity is in a block layout remove all entities, because they have to be on the same layout
- if len(valid_handles) and handle_not_in_block_definition(valid_handles[0]):
- self.AcDbGroup.extend(DXFTag(GROUP_ITEM_CODE, handle) for handle in valid_handles)
- def all_entities_on_same_layout(entities: Iterable['DXFEntity']):
- """
- Check if all entities are on the same layout (model space or any paper layout but not block).
- """
- owners = set(entity.dxf.owner for entity in entities)
- return len(owners) < 2 # 0 for no entities; 1 for all entities on the same layout
- class GroupManager(ObjectManager):
- def __init__(self, drawing: 'Drawing'):
- super().__init__(drawing, dict_name='ACAD_GROUP', object_type='GROUP')
- self._next_unnamed_number = 0
- def groups(self) -> Iterable[DXFGroup]:
- for name, group in self:
- yield group
- def next_name(self) -> str:
- name = self._next_name()
- while name in self:
- name = self._next_name()
- return name
- def _next_name(self) -> str:
- self._next_unnamed_number += 1
- return "*A{}".format(self._next_unnamed_number)
- def new(self, name: str = None, description: str = "", selectable: int = 1) -> DXFGroup:
- if name in self:
- raise DXFValueError("GROUP '{}' already exists.".format(name))
- if name is None:
- name = self.next_name()
- unnamed = 1
- else:
- unnamed = 0
- # The group name isn't stored in the group entity itself.
- dxfattribs = {
- 'description': description,
- 'unnamed': unnamed,
- 'selectable': selectable,
- }
- return cast(DXFGroup, self._new(name, dxfattribs))
- def delete(self, group: DXFGroup) -> None:
- """
- Delete GROUP by name or Group() object.
- """
- if isinstance(group, str): # delete group by name
- super(GroupManager, self).delete(group)
- else: # group should be a DXFEntity
- group_handle = group.dxf.handle
- for name, _group in self:
- if group_handle == _group.dxf.handle:
- super(GroupManager, self).delete(name)
- return
- raise DXFValueError("GROUP not in group table registered.")
- def cleanup(self) -> None:
- """
- Removes invalid handles in all groups and removes empty groups.
- """
- empty_groups = []
- for name, group in self:
- group.remove_invalid_handles()
- if not len(group): # remove empty group
- # do not delete groups while iterating over groups!
- empty_groups.append(name)
- # now delete empty groups
- for name in empty_groups:
- self.delete(name)
|