class EntityDB: """ A simple key/entity database. Every entity/object, except tables and sections, are represented as DXFEntity or inherited types, this entities are stored in the DXF document database, database-key is the `handle` as string. """ def __init__(self): self._database: Dict[str, DXFEntity] = {} # dxf entities to delete as set of handles self._trashcan: Set[str] = set() self.handles = HandleGenerator() self.locked = False # for debugging def __getitem__(self, handle: str) -> DXFEntity: """ Get entity by `handle`, does not filter destroyed entities nor entities in the trashcan. """ return self._database[handle] def __setitem__(self, handle: str, entity: DXFEntity) -> None: """ Set `entity` for `handle`. """ assert isinstance(handle, str), type(handle) assert isinstance(entity, DXFEntity), type(entity) assert entity.is_alive, 'Can not store destroyed entity.' if self.locked: raise DXFInternalEzdxfError('Locked entity database.') if handle == '0' or not is_valid_handle(handle): raise ValueError(f'Invalid handle {handle}.') self._database[handle] = entity def __delitem__(self, handle: str) -> None: """ Delete entity by `handle`. Removes entity only from database, does not destroy the entity. """ if self.locked: raise DXFInternalEzdxfError('Locked entity database.') del self._database[handle] def __contains__(self, handle: str) -> bool: """ ``True`` if database contains `handle`. """ if handle is None: return False assert isinstance(handle, str), type(handle) return handle in self._database def __len__(self) -> int: """ Count of database items. """ return len(self._database) def __iter__(self) -> Iterable[str]: """ Iterable of all handles, does filter destroyed entities but not entities in the trashcan. """ return self.keys() def get(self, handle: str) -> Optional[DXFEntity]: """ Returns entity for `handle` or ``None`` if no entry for `handle` exist, does not filter destroyed entities nor entities in the trashcan. """ return self._database.get(handle) def next_handle(self) -> str: """ Returns next unique handle.""" while True: handle = self.handles.next() if handle not in self._database: # you can not trust $HANDSEED value return handle def keys(self) -> Iterable[str]: """ Iterable of all handles, does filter destroyed entities but not entities in the trashcan. """ return (handle for handle, entity in self.items()) def values(self) -> Iterable[DXFEntity]: """ Iterable of all entities, does filter destroyed entities but not entities in the trashcan. """ return (entity for handle, entity in self.items()) def items(self) -> Iterable[Tuple[str, DXFEntity]]: """ Iterable of all (handle, entities) pairs, does filter destroyed entities but not entities in the trashcan. """ return ((handle, entity) for handle, entity in self._database.items() if entity.is_alive) def add(self, entity: DXFEntity) -> None: """ Add `entity` to database, assigns a new handle to the `entity` if :attr:`entity.dxf.handle` is ``None``. Adding the same entity multiple times is possible, but creates only a single entry. """ if entity.dxftype() in DATABASE_EXCLUDE: if entity.dxf.handle is not None: # store entities with handles (TABLE, maybe others) to avoid # reassigning of its handle self[entity.dxf.handle] = entity return handle: str = entity.dxf.handle if handle is None: handle = self.next_handle() # update_handle() requires the objects section to update the owner # handle of the extension dictionary, but this is no problem at # file loading, all entities have handles, and DXF R12 (without handles) # have no extension dictionaries. entity.update_handle(handle) self[handle] = entity # add sub entities like ATTRIB, VERTEX and SEQEND to database if isinstance(entity, LinkedEntities): entity.add_sub_entities_to_entitydb(self) def delete_entity(self, entity: DXFEntity) -> None: """ Removes `entity` from database and destroys the `entity`. """ if entity.is_alive: del self[entity.dxf.handle] entity.destroy() def discard(self, entity: 'DXFEntity') -> None: """ Discard entity from database without destroying the entity. """ if entity.is_alive: if isinstance(entity, LinkedEntities): entity.process_sub_entities(lambda e: self.discard(e)) handle = entity.dxf.handle try: del self._database[handle] entity.dxf.handle = None except KeyError: pass def duplicate_entity(self, entity: DXFEntity) -> DXFEntity: """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND) and store them with new handles in the entity database. Graphical entities have to be added to a layout by :meth:`~ezdxf.layouts.BaseLayout.add_entity`, for other DXF entities: DON'T DUPLICATE THEM. To import DXF entities into another drawing use the :class:`~ezdxf.addons.importer.Importer` add-on. An existing owner tag is not changed because this is not the domain of the :class:`EntityDB` class, will be set by adding the duplicated entity to a layout. This is not a deep copy in the meaning of Python, because handles and links are changed. """ new_entity: DXFEntity = entity.copy() new_entity.dxf.handle = self.next_handle() factory.bind(new_entity, entity.doc) return new_entity def audit(self, auditor: 'Auditor'): """ Restore database integrity: - restore database entries with modified handles (key != entity.dxf.handle) - remove entities with invalid handles - empty trashcan - destroy all entities in the trashcan - removes destroyed database entries (purge) """ assert self.locked is False, 'Database is locked!' add_entities = [] # Destroyed entities already filtered in self.items()! for handle, entity in self.items(): if not is_valid_handle(handle): auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} with invalid ' f'handle "{handle}" from entity database.', ) self.trash(handle) if handle != entity.dxf.get('handle'): # database handle != stored entity handle # prevent entity from being destroyed: self._database[handle] = None self.trash(handle) add_entities.append(entity) # Destroy entities in trashcan: self.empty_trashcan() # Remove all destroyed entities from database: self.purge() for entity in add_entities: handle = entity.dxf.get('handle') if handle is None: auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} without handle ' f'from entity database.', ) continue if not is_valid_handle(handle) or handle == '0': auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} with invalid ' f'handle "{handle}" from entity database.', ) continue self[handle] = entity def trash(self, handle: str) -> None: """ Put handle into trashcan to delete an entity later, required while iterating the database. """ self._trashcan.add(handle) def empty_trashcan(self) -> None: """ Remove handles in trashcan from database and destroy entities if still alive. """ # Important: operate on underlying data structure: db = self._database for handle in self._trashcan: entity = db.get(handle) if entity and entity.is_alive: entity.destroy() if handle in db: del db[handle] self._trashcan.clear() def purge(self) -> None: """ Remove all destroyed entities from database, but does not empty the trashcan. """ # Important: operate on underlying data structure: db = self._database dead_handles = [ handle for handle, entity in db.items() if not entity.is_alive ] for handle in dead_handles: del db[handle] def dxf_types_in_use(self) -> Set[str]: return set(entity.dxftype() for entity in self.values())
def test_init_and_reset_rejects_invalid_ints(): with pytest.raises(ValueError): HandleGenerator("XXXX") handles = HandleGenerator() with pytest.raises(ValueError): handles.reset("xyz")
def test_next(): handles = HandleGenerator("100") assert "100" == handles.next()
class EntityDB: """ A simple key/entity database. Every entity/object, except tables and sections, are represented as DXFEntity or inherited types, this entities are stored in the drawing-associated database, database-key is the `handle` as string (group code == 5 or 105). """ def __init__(self): self._database = {} self.handles = HandleGenerator() def __getitem__(self, handle: str) -> DXFEntity: """ Get entity by `handle`. """ return self._database[handle] def __setitem__(self, handle: str, entity: DXFEntity) -> None: """ Set `entity` for `handle`. """ self._database[handle] = entity def __delitem__(self, handle: str) -> None: """ Delete entity by `handle`. Removes entity only from database, does not destroy the entity. """ del self._database[handle] def __contains__(self, item: Union[str, DXFEntity]) -> bool: """ ``True`` if database contains `item`, `item` can be a handle or an entity. """ if isinstance(item, str): handle = item else: handle = item.dxf.handle return handle in self._database def __len__(self) -> int: """ Count of database items. """ return len(self._database) def __iter__(self) -> Iterable[str]: """ Iterable of all handles. """ return iter(self._database.keys()) def get(self, handle: str) -> Optional[DXFEntity]: """ Returns entity for `handle` or ``None`` if no entry for `handle` exist. """ try: return self.__getitem__(handle) except KeyError: # internal exception return None def next_handle(self) -> str: """ Returns next unique handle.""" while True: handle = self.handles.next() if handle not in self._database: # you can not trust $HANDSEED value return handle def keys(self) -> Iterable[str]: """ Iterable of all handles. """ return self._database.keys() def values(self) -> Iterable[DXFEntity]: """ Iterable of all entities. """ return self._database.values() def items(self) -> Iterable[Tuple[str, DXFEntity]]: """ Iterable of all (handle, entities) pairs. """ return self._database.items() def add(self, entity: DXFEntity) -> None: """ Add `entity` to database, assigns a new handle to the `entity` if :attr:`entity.dxf.handle` is ``None``. """ if entity.dxftype() in DATABASE_EXCLUDE: if entity.dxf.handle is not None: # store entities with handles (TABLE, maybe others) to avoid reassigning of its handle self[entity.dxf.handle] = entity return handle = entity.dxf.handle # type: str if handle is None: handle = self.next_handle() # update_handle() requires the objects section to update the owner handle of the extension dictionary, # but this is no problem at file loading, all entities have handles, and DXF R12 (without handles) have no # extension dictionaries. entity.update_handle(handle) self[handle] = entity # add sub entities like ATTRIB, VERTEX and SEQEND to database # only INSERT and POLYLINE using this feature if hasattr(entity, 'add_sub_entities_to_entitydb'): entity.add_sub_entities_to_entitydb() def delete_entity(self, entity: DXFEntity) -> None: """ Removes `entity` from database and destroys the `entity`. """ del self[entity.dxf.handle] entity.destroy() def duplicate_entity(self, entity: DXFEntity) -> DXFEntity: """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND) and store them with new handles in the drawing database. This is the recommend method to duplicate DXF entities in a drawing. Graphical entities have to be added to a layout by :meth:`~ezdxf.layouts.BaseLayout.add_entity`, for other DXF entities: DON'T DUPLICATE THEM. To import DXF entities into another drawing use the :class:`~ezdxf.addons.importer.Importer` add-on. An existing owner tag is not changed because this is not the domain of the :class:`EntityDB` class, will be set by adding the duplicated entity to a layout. This is not a deep copy in the meaning of Python, because handles and links are changed. """ new_entity = entity.copy() # type: DXFEntity new_entity.dxf.handle = self.next_handle() self.add(new_entity) return new_entity
def test_seed(): handles = HandleGenerator('200') handles.next() assert '201' == str(handles)
class EntityDB: """ A simple key/entity database. Every entity/object, except tables and sections, are represented as DXFEntity or inherited types, this entities are stored in the DXF document database, database-key is the `handle` as string. """ class Trashcan: """ Store handles to entities which should be deleted later. """ def __init__(self, db: 'EntityDB'): self._database = db._database self._handles: Set[str] = set() def add(self, handle: str): """ Put handle into trashcan to delete the entity later, this is required for deleting entities while iterating the database. """ self._handles.add(handle) def clear(self): """ Remove handles in trashcan from database and destroy entities if still alive. """ db = self._database for handle in self._handles: entity = db.get(handle) if entity and entity.is_alive: entity.destroy() if handle in db: del db[handle] self._handles.clear() def __init__(self): self._database: Dict[str, DXFEntity] = {} # DXF handles of entities to delete later: self.handles = HandleGenerator() self.locked: bool = False # used only for debugging def __getitem__(self, handle: str) -> DXFEntity: """ Get entity by `handle`, does not filter destroyed entities nor entities in the trashcan. """ return self._database[handle] def __setitem__(self, handle: str, entity: DXFEntity) -> None: """ Set `entity` for `handle`. """ assert isinstance(handle, str), type(handle) assert isinstance(entity, DXFEntity), type(entity) assert entity.is_alive, 'Can not store destroyed entity.' if self.locked: raise DXFInternalEzdxfError('Locked entity database.') if handle == '0' or not is_valid_handle(handle): raise ValueError(f'Invalid handle {handle}.') self._database[handle] = entity def __delitem__(self, handle: str) -> None: """ Delete entity by `handle`. Removes entity only from database, does not destroy the entity. """ if self.locked: raise DXFInternalEzdxfError('Locked entity database.') del self._database[handle] def __contains__(self, handle: str) -> bool: """ ``True`` if database contains `handle`. """ if handle is None: return False assert isinstance(handle, str), type(handle) return handle in self._database def __len__(self) -> int: """ Count of database items. """ return len(self._database) def __iter__(self) -> Iterable[str]: """ Iterable of all handles, does filter destroyed entities but not entities in the trashcan. """ return self.keys() def get(self, handle: str) -> Optional[DXFEntity]: """ Returns entity for `handle` or ``None`` if no entry exist, does not filter destroyed entities. """ return self._database.get(handle) def next_handle(self) -> str: """ Returns next unique handle.""" while True: handle = self.handles.next() if handle not in self._database: return handle def keys(self) -> Iterable[str]: """ Iterable of all handles, does filter destroyed entities. """ return (handle for handle, entity in self.items()) def values(self) -> Iterable[DXFEntity]: """ Iterable of all entities, does filter destroyed entities. """ return (entity for handle, entity in self.items()) def items(self) -> Iterable[Tuple[str, DXFEntity]]: """ Iterable of all (handle, entities) pairs, does filter destroyed entities. """ return ( (handle, entity) for handle, entity in self._database.items() if entity.is_alive ) def add(self, entity: DXFEntity) -> None: """ Add `entity` to database, assigns a new handle to the `entity` if :attr:`entity.dxf.handle` is ``None``. Adding the same entity multiple times is possible and creates only a single database entry. """ if entity.dxftype() in DATABASE_EXCLUDE: if entity.dxf.handle is not None: # Mark existing entity handle as used to avoid # reassigning the same handle again. self[entity.dxf.handle] = entity return handle: str = entity.dxf.handle if handle is None: handle = self.next_handle() entity.update_handle(handle) self[handle] = entity # Add sub entities ATTRIB, VERTEX and SEQEND to database: # Add linked MTEXT columns to database: if hasattr(entity, 'add_sub_entities_to_entitydb'): entity.add_sub_entities_to_entitydb(self) def delete_entity(self, entity: DXFEntity) -> None: """ Remove `entity` from database and destroy the `entity`. """ if entity.is_alive: del self[entity.dxf.handle] entity.destroy() def discard(self, entity: DXFEntity) -> None: """ Discard `entity` from database without destroying the `entity`. """ if entity.is_alive: if hasattr(entity, 'process_sub_entities'): entity.process_sub_entities(lambda e: self.discard(e)) handle = entity.dxf.handle try: del self._database[handle] entity.dxf.handle = None except KeyError: pass def duplicate_entity(self, entity: DXFEntity) -> DXFEntity: """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND) and store them with new handles in the entity database. Graphical entities have to be added to a layout by :meth:`~ezdxf.layouts.BaseLayout.add_entity`. To import DXF entities from another drawing use the :class:`~ezdxf.addons.importer.Importer` add-on. A new owner handle will be set by adding the duplicated entity to a layout. """ new_entity: DXFEntity = entity.copy() new_entity.dxf.handle = self.next_handle() factory.bind(new_entity, entity.doc) return new_entity def audit(self, auditor: 'Auditor'): """ Restore database integrity: - restore database entries with modified handles (key != entity.dxf.handle) - remove entities with invalid handles - empty trashcan - destroy all entities in the trashcan - removes destroyed database entries (purge) """ assert self.locked is False, 'Database is locked!' add_entities = [] with self.trashcan() as trash: for handle, entity in self.items(): # Destroyed entities are already filtered! if not is_valid_handle(handle): auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} with invalid ' f'handle "{handle}" from entity database.', ) trash.add(handle) if handle != entity.dxf.get('handle'): # database handle != stored entity handle # prevent entity from being destroyed: self._database[handle] = None trash.add(handle) add_entities.append(entity) # Remove all destroyed entities from database: self.purge() for entity in add_entities: handle = entity.dxf.get('handle') if handle is None: auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} without handle ' f'from entity database.', ) continue if not is_valid_handle(handle) or handle == '0': auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message=f'Removed entity {entity.dxftype()} with invalid ' f'handle "{handle}" from entity database.', ) continue self[handle] = entity def new_trashcan(self) -> 'EntityDB.Trashcan': """ Returns a new trashcan, empty trashcan manually by: : func:`Trashcan.clear()`. """ return EntityDB.Trashcan(self) @contextmanager def trashcan(self) -> 'EntityDB.Trashcan': """ Returns a new trashcan in context manager mode, trashcan will be emptied when leaving context. """ trashcan_ = self.new_trashcan() yield trashcan_ # try ... finally is not required, in case of an exception the database # is maybe already in an unreliable state. trashcan_.clear() def purge(self) -> None: """ Remove all destroyed entities from database, but does not empty the trashcan. """ # Important: operate on underlying data structure: db = self._database dead_handles = [ handle for handle, entity in db.items() if not entity.is_alive ] for handle in dead_handles: del db[handle] def dxf_types_in_use(self) -> Set[str]: return set(entity.dxftype() for entity in self.values())
class EntityDB: """ A simple key/entity database. Every entity/object, except tables and sections, are represented as DXFEntity or inherited types, this entities are stored in the drawing-associated database, database-key is the `handle` as string (group code == 5 or 105). """ def __init__(self): self._database = {} self.handles = HandleGenerator() self.locked = False # for debugging def __getitem__(self, handle: str) -> DXFEntity: """ Get entity by `handle`. """ return self._database[handle] def __setitem__(self, handle: str, entity: DXFEntity) -> None: """ Set `entity` for `handle`. """ assert isinstance(handle, str), type(handle) assert isinstance(entity, DXFEntity), type(entity) if self.locked: raise DXFInternalEzdxfError('Locked entity database.') if handle == '0' or not is_valid_handle(handle): raise ValueError(f'Invalid handle {handle}.') self._database[handle] = entity def __delitem__(self, handle: str) -> None: """ Delete entity by `handle`. Removes entity only from database, does not destroy the entity. """ del self._database[handle] def __contains__(self, handle: str) -> bool: """ ``True`` if database contains `handle`. """ assert isinstance(handle, str), type(handle) return handle in self._database def __len__(self) -> int: """ Count of database items. """ return len(self._database) def __iter__(self) -> Iterable[str]: """ Iterable of all handles. """ return iter(self._database.keys()) def get(self, handle: str) -> Optional[DXFEntity]: """ Returns entity for `handle` or ``None`` if no entry for `handle` exist. """ return self._database.get(handle) def next_handle(self) -> str: """ Returns next unique handle.""" while True: handle = self.handles.next() if handle not in self._database: # you can not trust $HANDSEED value return handle def keys(self) -> Iterable[str]: """ Iterable of all handles. """ return self._database.keys() def values(self) -> Iterable[DXFEntity]: """ Iterable of all entities. """ return self._database.values() def items(self) -> Iterable[Tuple[str, DXFEntity]]: """ Iterable of all (handle, entities) pairs. """ return self._database.items() def add(self, entity: DXFEntity) -> None: """ Add `entity` to database, assigns a new handle to the `entity` if :attr:`entity.dxf.handle` is ``None``. """ if entity.dxftype() in DATABASE_EXCLUDE: if entity.dxf.handle is not None: # store entities with handles (TABLE, maybe others) to avoid reassigning of its handle self[entity.dxf.handle] = entity return handle = entity.dxf.handle # type: str if handle is None: handle = self.next_handle() # update_handle() requires the objects section to update the owner handle of the extension dictionary, # but this is no problem at file loading, all entities have handles, and DXF R12 (without handles) have no # extension dictionaries. entity.update_handle(handle) self[handle] = entity # add sub entities like ATTRIB, VERTEX and SEQEND to database # only INSERT and POLYLINE using this feature if hasattr(entity, 'add_sub_entities_to_entitydb'): entity.add_sub_entities_to_entitydb() def delete_entity(self, entity: DXFEntity) -> None: """ Removes `entity` from database and destroys the `entity`. """ if entity.is_alive: del self[entity.dxf.handle] entity.destroy() def duplicate_entity(self, entity: DXFEntity) -> DXFEntity: """ Duplicates `entity` and its sub entities (VERTEX, ATTRIB, SEQEND) and store them with new handles in the drawing database. This is the recommend method to duplicate DXF entities in a drawing. Graphical entities have to be added to a layout by :meth:`~ezdxf.layouts.BaseLayout.add_entity`, for other DXF entities: DON'T DUPLICATE THEM. To import DXF entities into another drawing use the :class:`~ezdxf.addons.importer.Importer` add-on. An existing owner tag is not changed because this is not the domain of the :class:`EntityDB` class, will be set by adding the duplicated entity to a layout. This is not a deep copy in the meaning of Python, because handles and links are changed. """ new_entity = entity.copy() # type: DXFEntity new_entity.dxf.handle = self.next_handle() self.add(new_entity) return new_entity def audit(self, auditor: 'Auditor'): """ Restore database integrity: - removes deleted database entries (purge) - restore database entries with modified handles (key != entity.dxf.handle) - remove entities with invalid handles """ db = self._database remove_handles = [] add_entities = [] for handle, entity in db.items(): if not is_valid_handle(handle): auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message= f'Removed entity {entity.dxftype()} with invalid handle "{handle}" from entity database.', ) remove_handles.append(handle) if not entity.is_alive: remove_handles.append(handle) elif handle != entity.dxf.get('handle'): remove_handles.append(handle) add_entities.append(entity) for handle in remove_handles: del db[handle] for entity in add_entities: handle = entity.dxf.get('handle') if handle is None: auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message= f'Removed entity {entity.dxftype()} without handle from entity database.', ) continue if not is_valid_handle(handle) or handle == '0': auditor.fixed_error( code=AuditError.INVALID_ENTITY_HANDLE, message= f'Removed entity {entity.dxftype()} with invalid handle "{handle}" from entity database.', ) continue self[handle] = entity
def test_reset(self): handles = HandleGenerator('200') handles.reset('300') self.assertEqual('300', str(handles))
def test_next_function(): handles = HandleGenerator("100") assert "100" == next(handles)
def test_next_function(self): handles = HandleGenerator('100') self.assertEqual('100', next(handles))
def test_seed(self): handles = HandleGenerator('200') handles.next() self.assertEqual('201', str(handles))
def test_next(self): handles = HandleGenerator('100') self.assertEqual('100', handles.next())
def test_next(): handles = HandleGenerator('100') assert '100' == handles.next()
def test_reset(): handles = HandleGenerator('200') handles.reset('300') assert '300' == str(handles)
def __init__(self): self._database: Dict[str, DXFEntity] = {} # dxf entities to delete as set of handles self._trashcan: Set[str] = set() self.handles = HandleGenerator() self.locked = False # for debugging
def test_seed(): handles = HandleGenerator("200") handles.next() assert "201" == str(handles)
class EntityDB: """ A simple key/value database a.k.a. dict(), but can be replaced by other classes that implements all of the methods of `EntityDB`. The entities have no order. The Data Model Every entity/object, except tables and sections, are represented as tag-list (see ExtendedTags Class), this lists are stored in the drawing-associated database, database-key is the 'handle' tag (code == 5 or 105). For the entity/object manipulation this tag-list will be wrapped into separated classes, which are generated by the dxffactory-object. The dxffactory-object generates DXF-Version specific wrapper classes. """ def __init__(self): self._database = {} self.handles = HandleGenerator() def __delitem__(self, handle: str) -> None: del self._database[handle] def __getitem__(self, handle: str) -> ExtendedTags: return self._database[handle] def get(self, handle: str) -> Optional[ExtendedTags]: try: return self.__getitem__(handle) except KeyError: # internal exception return None def __setitem__(self, handle: str, tags: ExtendedTags) -> None: self._database[handle] = tags def __contains__(self, handle: str) -> bool: """ Database contains handle? """ return handle in self._database def __len__(self) -> int: """ Count of database items. """ return len(self._database) def __iter__(self) -> Iterable[str]: """ Iterate over all handles. """ return iter(self._database.keys()) def keys(self) -> Iterable[str]: """ Iterate over all handles. """ return self._database.keys() def values(self) -> Iterable[ExtendedTags]: """ Iterate over all entities. """ return self._database.values() def items(self) -> Iterable[Tuple[str, ExtendedTags]]: """ Iterate over all (handle, entities) pairs. """ return self._database.items() def add_tags(self, tags: ExtendedTags) -> str: try: handle = tags.get_handle() except DXFValueError: # create new handle handle = self.get_unique_handle() handle_code = 105 if tags.dxftype() == 'DIMSTYLE' else 5 # legacy shit!!! tags.noclass.insert(1, DXFTag(handle_code, handle)) # handle should be the 2. tag self.__setitem__(handle, tags) return handle def delete_entity(self, entity: DXFEntity) -> None: entity.destroy() self.delete_handle(entity.dxf.handle) def delete_handle(self, handle: str) -> None: del self._database[handle] def get_unique_handle(self)-> str: while True: handle = self.handles.next() if handle not in self._database: # you can not trust $HANDSEED value return handle def duplicate_tags(self, tags: ExtendedTags) -> ExtendedTags: """ Deep copy of tags with new handle and duplicated linked entities (VERTEX, ATTRIB, SEQEND) with also new handles. An existing owner tag is not changed because this is not the domain of the EntityDB() class. The new entity tags are added to the drawing database. This is not a deep copy in the meaning of Python, because handle and link is changed. """ new_tags = tags.clone() new_tags.noclass.replace_handle(self.get_unique_handle()) # set new handle self.add_tags(new_tags) # add new tags to database source_link = tags.link # follow link structure of original entity parent_copy = new_tags while source_link is not None: # duplicate linked entities (VERTEX, ATTRIB, SEQEND) source_linked_entity = self.get(source_link) # extended tags linked_entity_copy = source_linked_entity.clone() new_handle = self.get_unique_handle() linked_entity_copy.noclass.replace_handle(new_handle) # set new handle self.add_tags(linked_entity_copy) # add new tags to database parent_copy.link = new_handle source_link = source_linked_entity.link # follow link structure of original entity parent_copy = linked_entity_copy return new_tags
def test_returns_not_zero(): handles = HandleGenerator("0") assert handles.next() != "0"
def __init__(self): self._database: Dict[str, DXFEntity] = {} # DXF handles of entities to delete later: self.handles = HandleGenerator() self.locked: bool = False # used only for debugging
def test_returns_not_negative(): handles = HandleGenerator("-2") assert int(handles.next(), 16) > 0
def __init__(self): self._database = {} self.handles = HandleGenerator() self.locked = False # for debugging
def test_reset(): handles = HandleGenerator("200") handles.reset("300") assert "300" == str(handles)
def __init__(self): self._database = {} self.handles = HandleGenerator()
def test_next_function(): handles = HandleGenerator('100') assert '100' == next(handles)